Browse Source

merged v2_develop

Tig Kindel 1 year ago
parent
commit
4330c12b16
87 changed files with 11537 additions and 9411 deletions
  1. 23 1
      .editorconfig
  2. 66 33
      Terminal.Gui/Application.cs
  3. 11 3
      Terminal.Gui/Configuration/ConfigurationManager.cs
  4. 9 41
      Terminal.Gui/Configuration/KeyJsonConverter.cs
  5. 210 153
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  6. 1669 541
      Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs
  7. 79 89
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  8. 19 5
      Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs
  9. 1 1
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  10. 22 22
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  11. 296 322
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  12. 159 166
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  13. 226 192
      Terminal.Gui/Drawing/Color.cs
  14. 218 144
      Terminal.Gui/Input/Key.cs
  15. 6 0
      Terminal.Gui/Input/Responder.cs
  16. 3 9
      Terminal.Gui/Resources/config.json
  17. 1 0
      Terminal.Gui/Terminal.Gui.csproj
  18. 179 182
      Terminal.Gui/Text/CollectionNavigatorBase.cs
  19. 128 56
      Terminal.Gui/View/Layout/ViewLayout.cs
  20. 438 451
      Terminal.Gui/View/View.cs
  21. 448 440
      Terminal.Gui/View/ViewDrawing.cs
  22. 9 3
      Terminal.Gui/View/ViewKeyboard.cs
  23. 3 2
      Terminal.Gui/View/ViewSubViews.cs
  24. 212 216
      Terminal.Gui/View/ViewText.cs
  25. 6 2
      Terminal.Gui/Views/ColorPicker.cs
  26. 684 709
      Terminal.Gui/Views/ComboBox.cs
  27. 1 1
      Terminal.Gui/Views/DateField.cs
  28. 6 3
      Terminal.Gui/Views/FileDialog.cs
  29. 274 240
      Terminal.Gui/Views/Slider.cs
  30. 4 2
      Terminal.Gui/Views/TabView.cs
  31. 6 4
      Terminal.Gui/Views/TextField.cs
  32. 1 1
      Terminal.Gui/Views/TextValidateField.cs
  33. 1307 1317
      Terminal.Gui/Views/TextView.cs
  34. 1 1
      Terminal.Gui/Views/TimeField.cs
  35. 844 842
      Terminal.Gui/Views/Toplevel.cs
  36. 1 0
      Terminal.sln
  37. 10 9
      UICatalog/Properties/launchSettings.json
  38. 1 3
      UICatalog/Resources/config.json
  39. 1 0
      UICatalog/Scenarios/ASCIICustomButton.cs
  40. 354 385
      UICatalog/Scenarios/AllViewsTester.cs
  41. 32 31
      UICatalog/Scenarios/CharacterMap.cs
  42. 53 0
      UICatalog/Scenarios/ChineseUI.cs
  43. 0 1
      UICatalog/Scenarios/ClassExplorer.cs
  44. 1 0
      UICatalog/Scenarios/ColorPicker.cs
  45. 11 3
      UICatalog/Scenarios/ComputedLayout.cs
  46. 420 419
      UICatalog/Scenarios/CsvEditor.cs
  47. 0 1
      UICatalog/Scenarios/GraphViewExample.cs
  48. 1 2
      UICatalog/Scenarios/InteractiveTree.cs
  49. 0 1
      UICatalog/Scenarios/LineViewExample.cs
  50. 1 2
      UICatalog/Scenarios/ListColumns.cs
  51. 0 1
      UICatalog/Scenarios/MultiColouredTable.cs
  52. 1 2
      UICatalog/Scenarios/Notepad.cs
  53. 0 1
      UICatalog/Scenarios/ProcessTable.cs
  54. 5 5
      UICatalog/Scenarios/Progress.cs
  55. 155 146
      UICatalog/Scenarios/Sliders.cs
  56. 0 1
      UICatalog/Scenarios/TabViewExample.cs
  57. 2 3
      UICatalog/Scenarios/TableEditor.cs
  58. 0 1
      UICatalog/Scenarios/TreeUseCases.cs
  59. 334 336
      UICatalog/Scenarios/TreeViewFileSystem.cs
  60. 55 69
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  61. 111 83
      UICatalog/UICatalog.cs
  62. 1 0
      UICatalog/UICatalog.csproj
  63. 37 27
      UnitTests/Application/ApplicationTests.cs
  64. 470 474
      UnitTests/Configuration/ConfigurationMangerTests.cs
  65. 10 10
      UnitTests/Configuration/JsonConverterTests.cs
  66. 69 69
      UnitTests/Configuration/SettingsScopeTests.cs
  67. 12 10
      UnitTests/ConsoleDrivers/AddRuneTests.cs
  68. 121 104
      UnitTests/ConsoleDrivers/ClipRegionTests.cs
  69. 254 247
      UnitTests/ConsoleDrivers/ConsoleDriverTests.cs
  70. 415 148
      UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs
  71. 31 27
      UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs
  72. 12 9
      UnitTests/ConsoleDrivers/ContentsTests.cs
  73. 64 64
      UnitTests/ConsoleDrivers/DriverColorTests.cs
  74. 29 22
      UnitTests/ConsoleDrivers/MainLoopDriverTests.cs
  75. 220 36
      UnitTests/Input/KeyTests.cs
  76. 125 9
      UnitTests/Input/ResponderTests.cs
  77. 17 18
      UnitTests/Text/CollectionNavigatorTests.cs
  78. 12 12
      UnitTests/Text/TextFormatterTests.cs
  79. 12 12
      UnitTests/View/HotKeyTests.cs
  80. 337 345
      UnitTests/View/Layout/AbsoluteLayoutTests.cs
  81. 1 1
      UnitTests/Views/AppendAutocompleteTests.cs
  82. 11 4
      UnitTests/Views/ComboBoxTests.cs
  83. 1 1
      UnitTests/Views/DateFieldTests.cs
  84. 113 37
      UnitTests/Views/SliderTests.cs
  85. 5 5
      UnitTests/Views/TextFieldTests.cs
  86. 39 20
      UnitTests/Views/TextViewTests.cs
  87. 1 1
      UnitTests/Views/TimeFieldTests.cs

+ 23 - 1
.editorconfig

@@ -27,14 +27,24 @@ 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_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
@@ -82,7 +92,19 @@ csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
 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 = false:silent
+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
+resharper_xmldoc_indent_size = 2
+resharper_xmldoc_indent_style = space
+resharper_xmldoc_indent_text = DoNotTouch
+resharper_xmldoc_linebreaks_inside_tags_for_elements_longer_than = 120
+resharper_xmldoc_max_blank_lines_between_tags = 1
+resharper_xmldoc_max_line_length = 100
+resharper_xmldoc_space_before_self_closing = false
+resharper_xmldoc_tab_width = 2
+resharper_xmldoc_use_indent_from_vs = true
 
 [*.{cs,vb}]
 dotnet_style_operator_placement_when_wrapping = beginning_of_line

+ 66 - 33
Terminal.Gui/Application.cs

@@ -33,15 +33,20 @@ namespace Terminal.Gui;
 /// </remarks>
 public static partial class Application {
 	/// <summary>
-	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="UseSystemConsole"/>.
+	/// Gets the <see cref="ConsoleDriver"/> that has been selected. See also <see cref="ForceDriver"/>.
 	/// </summary>
 	public static ConsoleDriver Driver { get; internal set; }
 
 	/// <summary>
-	/// If <see langword="true"/>, forces the use of the System.Console-based (see <see cref="NetDriver"/>) driver. The default is <see langword="false"/>.
+	/// Forces the use of the specified driver (one of "fake", "ansi", "curses", "net", or "windows"). If
+	/// not specified, the driver is selected based on the platform.
 	/// </summary>
+	/// <remarks>
+	/// Note, <see cref="Application.Init(ConsoleDriver, string)"/> will override this configuration setting if
+	/// called with either `driver` or `driverName` specified.
+	/// </remarks>
 	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-	public static bool UseSystemConsole { get; set; } = false;
+	public static string ForceDriver { get; set; } = string.Empty;
 
 	/// <summary>
 	/// Gets or sets whether <see cref="Application.Driver"/> will be forced to output only the 16 colors defined in <see cref="ColorName"/>.
@@ -98,14 +103,13 @@ public static partial class Application {
 	/// </para>
 	/// <para>
 	/// The <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> function 
-	/// combines <see cref="Init(ConsoleDriver)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
+	/// combines <see cref="Init(ConsoleDriver, string)"/> and <see cref="Run(Toplevel, Func{Exception, bool})"/>
 	/// into a single call. An application cam use <see cref="Run{T}(Func{Exception, bool}, ConsoleDriver)"/> 
-	/// without explicitly calling <see cref="Init(ConsoleDriver)"/>.
+	/// without explicitly calling <see cref="Init(ConsoleDriver, string)"/>.
 	/// </para>
-	/// <param name="driver">
-	/// The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
-	/// platform will be used (see <see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, and <see cref="NetDriver"/>).</param>
-	public static void Init (ConsoleDriver driver = null) => InternalInit (() => Toplevel.Create (), driver);
+	/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
+	/// <param name="driverName">The short name (e.g. "net", "windows", "ansi", "fake", or "curses") of the <see cref="ConsoleDriver"/> to use. If neither <paramref name="driver"/> or <paramref name="driverName"/> are specified the default driver for the platform will be used.</param>
+	public static void Init (ConsoleDriver driver = null, string driverName = null) => InternalInit (Toplevel.Create, driver, driverName);
 
 	internal static bool _initialized = false;
 	internal static int _mainThreadId = -1;
@@ -119,7 +123,7 @@ public static partial class Application {
 	// Unit Tests - To initialize the app with a custom Toplevel, using the FakeDriver. calledViaRunT will be false, causing all state to be reset.
 	// 
 	// calledViaRunT: If false (default) all state will be reset. If true the state will not be reset.
-	internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, bool calledViaRunT = false)
+	internal static void InternalInit (Func<Toplevel> topLevelFactory, ConsoleDriver driver = null, string driverName = null, bool calledViaRunT = false)
 	{
 		if (_initialized && driver == null) {
 			return;
@@ -147,15 +151,28 @@ public static partial class Application {
 		Load (true);
 		Apply ();
 
-		Driver ??= Environment.OSVersion.Platform switch {
-			_ when _forceFakeConsole => new FakeDriver (), // for unit testing only
-			_ when UseSystemConsole => new NetDriver (),
-			PlatformID.Win32NT or PlatformID.Win32S or PlatformID.Win32Windows => new WindowsDriver (),
-			_ => new CursesDriver ()
-		};
+		// Ignore Configuration for ForceDriver if driverName is specified
+		if (!string.IsNullOrEmpty (driverName)) {
+			ForceDriver = driverName;
+		}
 
 		if (Driver == null) {
-			throw new InvalidOperationException ("Init could not determine the ConsoleDriver to use.");
+			var p = Environment.OSVersion.Platform;
+			if (string.IsNullOrEmpty (ForceDriver)) {
+				if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) {
+					Driver = new WindowsDriver ();
+				} else {
+					Driver = new CursesDriver ();
+				}
+			} else {
+				var drivers = GetDriverTypes ();
+				var driverType = drivers.FirstOrDefault (t => t.Name.ToLower () == ForceDriver.ToLower ());
+				if (driverType != null) {
+					Driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+				} else {
+					throw new ArgumentException ($"Invalid driver name: {ForceDriver}. Valid names are {string.Join (", ", drivers.Select (t => t.Name))}");
+				}
+			}
 		}
 
 		try {
@@ -168,10 +185,10 @@ public static partial class Application {
 			throw new InvalidOperationException ("Unable to initialize the console. This can happen if the console is already in use by another process or in unit tests.", ex);
 		}
 
-		Driver.SizeChanged += Driver_SizeChanged;
-		Driver.KeyDown += Driver_KeyDown;
-		Driver.KeyUp += Driver_KeyUp;
-		Driver.MouseEvent += Driver_MouseEvent;
+		Driver.SizeChanged += (s, args) => OnSizeChanging (args);
+		Driver.KeyDown += (s, args) => OnKeyDown (args);
+		Driver.KeyUp += (s, args) => OnKeyUp (args);
+		Driver.MouseEvent += (s, args) => OnMouseEvent (args);
 
 		SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());
 
@@ -190,12 +207,29 @@ public static partial class Application {
 
 	static void Driver_MouseEvent (object sender, MouseEventEventArgs e) => OnMouseEvent (e);
 
+	/// <summary>
+	/// Gets of list of <see cref="ConsoleDriver"/> types that are available.
+	/// </summary>
+	/// <returns></returns>
+	public static List<Type> GetDriverTypes ()
+	{
+		// use reflection to get the list of drivers
+		var driverTypes = new List<Type> ();
+		foreach (var asm in AppDomain.CurrentDomain.GetAssemblies ()) {
+			foreach (var type in asm.GetTypes ()) {
+				if (type.IsSubclassOf (typeof (ConsoleDriver)) && !type.IsAbstract) {
+					driverTypes.Add (type);
+				}
+			}
+		}
+		return driverTypes;
+	}
 
 	/// <summary>
-	/// Shutdown an application initialized with <see cref="Init(ConsoleDriver)"/>.
+	/// Shutdown an application initialized with <see cref="Init"/>.
 	/// </summary>
 	/// <remarks>
-	/// Shutdown must be called for every call to <see cref="Init(ConsoleDriver)"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
+	/// Shutdown must be called for every call to <see cref="Init"/> or <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>
 	/// to ensure all resources are cleaned up (Disposed) and terminal settings are restored.
 	/// </remarks>
 	public static void Shutdown ()
@@ -394,7 +428,7 @@ public static partial class Application {
 	/// Runs the application by calling <see cref="Run(Toplevel, Func{Exception, bool})"/> 
 	/// with a new instance of the specified <see cref="Toplevel"/>-derived class.
 	/// <para>
-	/// Calling <see cref="Init(ConsoleDriver)"/> first is not needed as this function will initialize the application.
+	/// Calling <see cref="Init"/> first is not needed as this function will initialize the application.
 	/// </para>
 	/// <para>
 	/// <see cref="Shutdown"/> must be called when the application is closing (typically after Run> has 
@@ -407,7 +441,7 @@ public static partial class Application {
 	/// <param name="errorHandler"></param>
 	/// <param name="driver">The <see cref="ConsoleDriver"/> to use. If not specified the default driver for the
 	/// platform will be used (<see cref="WindowsDriver"/>, <see cref="CursesDriver"/>, or <see cref="NetDriver"/>).
-	/// Must be <see langword="null"/> if <see cref="Init(ConsoleDriver)"/> has already been called. 
+	/// 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 ()
 	{
@@ -429,7 +463,7 @@ public static partial class Application {
 			}
 		} else {
 			// Init() has NOT been called.
-			InternalInit (() => new T (), driver, true);
+			InternalInit (() => new T (), driver, null, true);
 			Run (Top, errorHandler);
 		}
 	}
@@ -838,13 +872,12 @@ public static partial class Application {
 	#endregion Run (Begin, Run, End)
 
 	#region Toplevel handling
-
 	/// <summary>
 	/// Holds the stack of TopLevel views.
 	/// </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 ();
+	static readonly Stack<Toplevel> _topLevels = new Stack<Toplevel> ();
 
 	/// <summary>
 	/// The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Application.Top"/>)
@@ -1296,7 +1329,7 @@ public static partial class Application {
 	#endregion Mouse handling
 
 	#region Keyboard handling
-	static Key _alternateForwardKey = new (KeyCode.PageDown | KeyCode.CtrlMask);
+	static Key _alternateForwardKey = new Key (KeyCode.PageDown | KeyCode.CtrlMask);
 
 	/// <summary>
 	/// Alternative key to navigate forwards through views. Ctrl+Tab is the primary key.
@@ -1320,7 +1353,7 @@ public static partial class Application {
 		}
 	}
 
-	static Key _alternateBackwardKey = new (KeyCode.PageUp | KeyCode.CtrlMask);
+	static Key _alternateBackwardKey = new Key (KeyCode.PageUp | KeyCode.CtrlMask);
 
 	/// <summary>
 	/// Alternative key to navigate backwards through views. Shift+Ctrl+Tab is the primary key.
@@ -1344,7 +1377,7 @@ public static partial class Application {
 		}
 	}
 
-	static Key _quitKey = new (KeyCode.Q | KeyCode.CtrlMask);
+	static Key _quitKey = new Key (KeyCode.Q | KeyCode.CtrlMask);
 
 	/// <summary>
 	/// Gets or sets the key to quit the application.
@@ -1481,8 +1514,8 @@ public static partial class Application {
 	}
 	#endregion Keyboard handling
 }
-
 /// <summary>
 /// Event arguments for the <see cref="Application.Iteration"/> event.
 /// </summary>
-public class IterationEventArgs { }
+public class IterationEventArgs {
+}

+ 11 - 3
Terminal.Gui/Configuration/ConfigurationManager.cs

@@ -9,6 +9,7 @@ using System.IO;
 using System.Linq;
 using System.Reflection;
 using System.Text;
+using System.Text.Encodings.Web;
 using System.Text.Json;
 using System.Text.Json.Serialization;
 using static Terminal.Gui.SpinnerStyle;
@@ -70,8 +71,13 @@ public static partial class ConfigurationManager {
 				// 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.
@@ -251,7 +257,7 @@ public static partial class ConfigurationManager {
 
 	/// <summary>
 	/// Resets the state of <see cref="ConfigurationManager"/>. Should be called whenever a new app session
-	/// (e.g. in <see cref="Application.Init(ConsoleDriver)"/> starts. Called by <see cref="Load"/>
+	/// (e.g. in <see cref="Application.Init"/> starts. Called by <see cref="Load"/>
 	/// if the <c>reset</c> parameter is <see langword="true"/>.
 	/// </summary>
 	/// <remarks>
@@ -406,7 +412,9 @@ public static partial class ConfigurationManager {
 	{
 		Debug.WriteLine ($"ConfigurationManager.Load()");
 
-		if (reset) Reset ();
+		if (reset) {
+			Reset ();
+		}
 
 		// LibraryResources is always loaded by Reset
 		if (Locations == ConfigLocations.All) {

+ 9 - 41
Terminal.Gui/Configuration/KeyJsonConverter.cs

@@ -3,46 +3,14 @@ using System.Text.Json;
 using System.Text.Json.Serialization;
 
 namespace Terminal.Gui;
-class KeyJsonConverter : JsonConverter<Key> {
-	
-	public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
-	{
-		if (reader.TokenType == JsonTokenType.StartObject) {
-			Key key = Key.Empty;
-			while (reader.Read ()) {
-				if (reader.TokenType == JsonTokenType.EndObject) {
-					break;
-				}
 
-				if (reader.TokenType == JsonTokenType.PropertyName) {
-					string propertyName = reader.GetString ();
-					reader.Read ();
+/// <summary>
+/// Support for <see cref="Key"/> in JSON in the form of "Ctrl-X" or "Alt-Shift-F1".
+/// </summary>
+public class KeyJsonConverter : JsonConverter<Key> {
+	/// <inheritdoc />
+	public override Key Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => Key.TryParse (reader.GetString (), out var key) ? key : Key.Empty;
 
-					switch (propertyName?.ToLowerInvariant ()) {
-					case "key":
-						if (reader.TokenType == JsonTokenType.String) {
-							string keyValue = reader.GetString ();
-							if (Key.TryParse (keyValue, out key)) {
-								break;
-							}
-							throw new JsonException ($"Error parsing Key: {keyValue}.");
-
-						}
-						break;
-					default:
-						throw new JsonException ($"Unexpected Key property \"{propertyName}\".");
-					}
-				}
-			}
-			return key;
-		}
-		throw new JsonException ($"Unexpected StartObject token when parsing Key: {reader.TokenType}.");
-	}
-
-	public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options)
-	{
-		writer.WriteStartObject ();
-		writer.WriteString ("Key", value.ToString ());
-		writer.WriteEndObject ();
-	}
-}
+	/// <inheritdoc />
+	public override void Write (Utf8JsonWriter writer, Key value, JsonSerializerOptions options) => writer.WriteStringValue (value.ToString ());
+}

+ 210 - 153
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System;
 using System.Diagnostics;
 using System.Linq;
+using Terminal.Gui.ConsoleDrivers;
 
 namespace Terminal.Gui;
 
@@ -31,7 +32,6 @@ public abstract class ConsoleDriver {
 	internal static bool RunningUnitTests { get; set; }
 
 	#region Setup & Teardown
-
 	/// <summary>
 	/// Initializes the driver
 	/// </summary>
@@ -42,7 +42,6 @@ public abstract class ConsoleDriver {
 	/// Ends the execution of the console driver.
 	/// </summary>
 	internal abstract void End ();
-
 	#endregion
 
 	/// <summary>
@@ -59,12 +58,24 @@ public abstract class ConsoleDriver {
 	/// <summary>
 	/// The number of columns visible in the terminal.
 	/// </summary>
-	public virtual int Cols { get; internal set; }
+	public virtual int Cols {
+		get => _cols;
+		internal set {
+			_cols = value;
+			ClearContents();
+		}
+	}
 
 	/// <summary>
 	/// The number of rows visible in the terminal.
 	/// </summary>
-	public virtual int Rows { get; internal set; }
+	public virtual int Rows {
+		get => _rows;
+		internal set {
+			_rows = value;
+			ClearContents();
+		}
+	}
 
 	/// <summary>
 	/// The leftmost column in the terminal.
@@ -129,10 +140,7 @@ public abstract class ConsoleDriver {
 	/// <param name="rune"></param>
 	/// <returns><see langword="true"/> if the rune can be properly presented; <see langword="false"/> if the driver
 	/// does not support displaying this rune.</returns>
-	public virtual bool IsRuneSupported (Rune rune)
-	{
-		return Rune.IsValid (rune.Value);
-	}
+	public virtual bool IsRuneSupported (Rune rune) => Rune.IsValid (rune.Value);
 
 	/// <summary>
 	/// Adds the specified rune to the display at the current cursor position. 
@@ -151,16 +159,24 @@ public abstract class ConsoleDriver {
 	public void AddRune (Rune rune)
 	{
 		int runeWidth = -1;
-		var validLocation = IsValidLocation (Col, Row);
+		bool validLocation = IsValidLocation (Col, Row);
 		if (validLocation) {
 			rune = rune.MakePrintable ();
 			runeWidth = rune.GetColumns ();
 			if (runeWidth == 0 && rune.IsCombiningMark ()) {
+				// AtlasEngine does not support NON-NORMALIZED combining marks in a way
+				// compatible with the driver architecture. Any CMs (except in the first col)
+				// are correctly combined with the base char, but are ALSO treated as 1 column
+				// width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é  ]`.
+				// 
+				// Until this is addressed (see Issue #), we do our best by 
+				// a) Attempting to normalize any CM with the base char to it's left
+				// b) Ignoring any CMs that don't normalize
 				if (Col > 0) {
 					if (Contents [Row, Col - 1].CombiningMarks.Count > 0) {
 						// Just add this mark to the list
 						Contents [Row, Col - 1].CombiningMarks.Add (rune);
-						// Don't move to next column (let the driver figure out what to do).
+						// Ignore. Don't move to next column (let the driver figure out what to do).
 					} else {
 						// Attempt to normalize the cell to our left combined with this mark
 						string combined = Contents [Row, Col - 1].Rune + rune.ToString ();
@@ -171,11 +187,11 @@ public abstract class ConsoleDriver {
 							// It normalized! We can just set the Cell to the left with the
 							// normalized codepoint 
 							Contents [Row, Col - 1].Rune = (Rune)normalized [0];
-							// Don't move to next column because we're already there
+							// Ignore. Don't move to next column because we're already there
 						} else {
 							// It didn't normalize. Add it to the Cell to left's CM list
 							Contents [Row, Col - 1].CombiningMarks.Add (rune);
-							// Don't move to next column (let the driver figure out what to do).
+							// Ignore. Don't move to next column (let the driver figure out what to do).
 						}
 					}
 					Contents [Row, Col - 1].Attribute = CurrentAttribute;
@@ -275,7 +291,7 @@ public abstract class ConsoleDriver {
 	public void AddStr (string str)
 	{
 		var runes = str.EnumerateRunes ().ToList ();
-		for (var i = 0; i < runes.Count; i++) {
+		for (int i = 0; i < runes.Count; i++) {
 			//if (runes [i].IsCombiningMark()) {
 
 			//	// Attempt to normalize
@@ -361,8 +377,8 @@ public abstract class ConsoleDriver {
 		lock (Contents) {
 			// Can raise an exception while is still resizing.
 			try {
-				for (var row = 0; row < Rows; row++) {
-					for (var c = 0; c < Cols; c++) {
+				for (int row = 0; row < Rows; row++) {
+					for (int c = 0; c < Cols; c++) {
 						Contents [row, c] = new Cell () {
 							Rune = (Rune)' ',
 							Attribute = new Attribute (Color.White, Color.Black),
@@ -381,11 +397,10 @@ public abstract class ConsoleDriver {
 	public abstract void UpdateScreen ();
 
 	#region Color Handling
-
 	/// <summary>
 	/// Gets whether the <see cref="ConsoleDriver"/> supports TrueColor output.
 	/// </summary>
-	public virtual bool SupportsTrueColor { get => true; }
+	public virtual bool SupportsTrueColor => true;
 
 	/// <summary>
 	/// Gets or sets whether the <see cref="ConsoleDriver"/> should use 16 colors instead of the default TrueColors. See <see cref="Application.Force16Colors"/>
@@ -399,10 +414,12 @@ public abstract class ConsoleDriver {
 	/// </remarks>
 	internal virtual bool Force16Colors {
 		get => Application.Force16Colors || !SupportsTrueColor;
-		set => Application.Force16Colors = (value || !SupportsTrueColor);
+		set => Application.Force16Colors = value || !SupportsTrueColor;
 	}
 
 	Attribute _currentAttribute;
+	int _cols;
+	int _rows;
 
 	/// <summary>
 	/// The <see cref="Attribute"/> that will be used for the next <see cref="AddRune(Rune)"/> or <see cref="AddStr"/> call.
@@ -447,17 +464,13 @@ public abstract class ConsoleDriver {
 	/// <param name="foreground">The foreground color.</param>
 	/// <param name="background">The background color.</param>
 	/// <returns>The attribute for the foreground and background colors.</returns>
-	public virtual Attribute MakeColor (Color foreground, Color background)
-	{
+	public virtual Attribute MakeColor (Color foreground, Color background) =>
 		// Encode the colors into the int value.
-		return new Attribute (
-			platformColor: 0, // only used by cursesdriver!
-			foreground: foreground,
-			background: background
+		new (
+			0, // only used by cursesdriver!
+			foreground,
+			background
 		);
-	}
-
-
 	#endregion
 
 	#region Mouse and Keyboard
@@ -509,7 +522,6 @@ public abstract class ConsoleDriver {
 	/// <param name="alt">If <see langword="true"/> simulates the Alt key being pressed.</param>
 	/// <param name="ctrl">If <see langword="true"/> simulates the Ctrl key being pressed.</param>
 	public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl);
-
 	#endregion
 
 	/// <summary>
@@ -521,16 +533,18 @@ public abstract class ConsoleDriver {
 		/// All diagnostics off
 		/// </summary>
 		Off = 0b_0000_0000,
+
 		/// <summary>
 		/// When enabled, <see cref="Frame.OnDrawFrames"/> 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 
 		/// 'L', 'R', 'T', and 'B' when clearing <see cref="Thickness"/>'s instead of ' '.
 		/// </summary>
-		FramePadding = 0b_0000_0010,
+		FramePadding = 0b_0000_0010
 	}
 
 	/// <summary>
@@ -552,8 +566,8 @@ public abstract class ConsoleDriver {
 	/// <param name="rune"></param>
 	public void FillRect (Rect rect, Rune rune = default)
 	{
-		for (var r = rect.Y; r < rect.Y + rect.Height; r++) {
-			for (var c = rect.X; c < rect.X + rect.Width; c++) {
+		for (int r = rect.Y; r < rect.Y + rect.Height; r++) {
+			for (int c = rect.X; c < rect.X + rect.Width; c++) {
 				Application.Driver.Move (c, r);
 				Application.Driver.AddRune (rune == default ? new Rune (' ') : rune);
 			}
@@ -575,7 +589,6 @@ public abstract class ConsoleDriver {
 	public virtual string GetVersionInfo () => GetType ().Name;
 }
 
-
 /// <summary>
 /// Terminal Cursor Visibility settings.
 /// </summary>
@@ -631,10 +644,9 @@ public enum CursorVisibility {
 	///	Cursor caret is displayed a block ▉
 	/// </summary>
 	/// <remarks>Works under Xterm-like terminal otherwise this is equivalent to <see ref="Block"/></remarks>
-	BoxFix = 0x02020164,
+	BoxFix = 0x02020164
 }
 
-
 /// <summary>
 /// The <see cref="KeyCode"/> enumeration encodes key information from <see cref="ConsoleDriver"/>s and provides a consistent
 /// way for application code to specify keys and receive key events. 
@@ -649,7 +661,6 @@ public enum CursorVisibility {
 /// Lowercase alpha keys are encoded as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
 /// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
 /// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
-/// TODO: Strongly consider renaming these from .A to .Z to .A_Lowercase to .Z_Lowercase (or .a to .z).
 /// </para>
 /// <para>
 /// Numeric keys are the values between 48 and 57 corresponding to 0 to 9 (e.g. <see cref="KeyCode.D0"/>, <see cref="KeyCode.D1"/>, etc.).
@@ -672,72 +683,59 @@ public enum CursorVisibility {
 [Flags]
 public enum KeyCode : uint {
 	/// <summary>
-	/// Mask that indicates that this is a character value, values outside this range
-	/// indicate special characters like Alt-key combinations or special keys on the
-	/// keyboard like function keys, arrows keys and so on.
+	/// Mask that indicates that the key is a unicode codepoint. Values outside this range
+	/// indicate the key has shift modifiers or is a special key like function keys, arrows keys and so on.
 	/// </summary>
-	CharMask = 0xfffff,
+	CharMask = 0x_f_ffff,
 
 	/// <summary>
 	/// If the <see cref="SpecialMask"/> is set, then the value is that of the special mask,
-	/// otherwise, the value is the one of the lower bits (as extracted by <see cref="CharMask"/>).
+	/// otherwise, the value is in the the lower bits (as extracted by <see cref="CharMask"/>).
 	/// </summary>
-	SpecialMask = 0xfff00000,
+	SpecialMask = 0x_fff0_0000,
 
 	/// <summary>
-	/// The key code representing null or empty
+	/// When this value is set, the Key encodes the sequence Shift-KeyValue.
+	/// The actual value must be extracted by removing the ShiftMask.
 	/// </summary>
-	Null = '\0',
+	ShiftMask = 0x_1000_0000,
 
 	/// <summary>
-	/// Backspace key.
+	/// When this value is set, the Key encodes the sequence Alt-KeyValue.
+	/// The actual value must be extracted by removing the AltMask.
 	/// </summary>
-	Backspace = 8,
+	AltMask = 0x_8000_0000,
 
 	/// <summary>
-	/// The key code for the tab key (forwards tab key).
+	/// When this value is set, the Key encodes the sequence Ctrl-KeyValue.
+	/// The actual value must be extracted by removing the CtrlMask.
 	/// </summary>
-	Tab = 9,
+	CtrlMask = 0x_4000_0000,
 
 	/// <summary>
-	/// The key code for the return key.
+	/// The key code representing an invalid or empty key.
 	/// </summary>
-	Enter = '\n',
+	Null = 0,
 
 	/// <summary>
-	/// The key code for the clear key.
-	/// </summary>
-	Clear = 12,
-
-	/// <summary>
-	/// The key code for the Shift key.
+	/// Backspace key.
 	/// </summary>
-	ShiftKey = 16,
+	Backspace = 8,
 
 	/// <summary>
-	/// The key code for the Ctrl key.
+	/// The key code for the tab key (forwards tab key).
 	/// </summary>
-	CtrlKey = 17,
+	Tab = 9,
 
 	/// <summary>
-	/// The key code for the Alt key.
+	/// The key code for the return key.
 	/// </summary>
-	AltKey = 18,
+	Enter = ConsoleKey.Enter,
 
 	/// <summary>
-	/// The key code for the CapsLock key.
+	/// The key code for the clear key.
 	/// </summary>
-	CapsLock = 20,
-
-	///// <summary>
-	///// The key code for the NumLock key.
-	///// </summary>
-	//NumLock = 144,
-
-	///// <summary>
-	///// The key code for the ScrollLock key.
-	///// </summary>
-	//ScrollLock = 145,
+	Clear = 12,
 
 	/// <summary>
 	/// The key code for the escape key.
@@ -753,312 +751,371 @@ public enum KeyCode : uint {
 	/// Digit 0.
 	/// </summary>
 	D0 = 48,
+
 	/// <summary>
 	/// Digit 1.
 	/// </summary>
 	D1,
+
 	/// <summary>
 	/// Digit 2.
 	/// </summary>
 	D2,
+
 	/// <summary>
 	/// Digit 3.
 	/// </summary>
 	D3,
+
 	/// <summary>
 	/// Digit 4.
 	/// </summary>
 	D4,
+
 	/// <summary>
 	/// Digit 5.
 	/// </summary>
 	D5,
+
 	/// <summary>
 	/// Digit 6.
 	/// </summary>
 	D6,
+
 	/// <summary>
 	/// Digit 7.
 	/// </summary>
 	D7,
+
 	/// <summary>
 	/// Digit 8.
 	/// </summary>
 	D8,
+
 	/// <summary>
 	/// Digit 9.
 	/// </summary>
 	D9,
 
 	/// <summary>
-	/// The key code for the user pressing Shift-A
+	/// The key code for the A key
 	/// </summary>
 	A = 65,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-B
+	/// The key code for the B key
 	/// </summary>
 	B,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-C
+	/// The key code for the C key
 	/// </summary>
 	C,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-D
+	/// The key code for the D key
 	/// </summary>
 	D,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-E
+	/// The key code for the E key
 	/// </summary>
 	E,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-F
+	/// The key code for the F key
 	/// </summary>
 	F,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-G
+	/// The key code for the G key
 	/// </summary>
 	G,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-H
+	/// The key code for the H key
 	/// </summary>
 	H,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-I
+	/// The key code for the I key
 	/// </summary>
 	I,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-J
+	/// The key code for the J key
 	/// </summary>
 	J,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-K
+	/// The key code for the K key
 	/// </summary>
 	K,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-L
+	/// The key code for the L key
 	/// </summary>
 	L,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-M
+	/// The key code for the M key
 	/// </summary>
 	M,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-N
+	/// The key code for the N key
 	/// </summary>
 	N,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-O
+	/// The key code for the O key
 	/// </summary>
 	O,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-P
+	/// The key code for the P key
 	/// </summary>
 	P,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-Q
+	/// The key code for the Q key
 	/// </summary>
 	Q,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-R
+	/// The key code for the R key
 	/// </summary>
 	R,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-S
+	/// The key code for the S key
 	/// </summary>
 	S,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-T
+	/// The key code for the T key
 	/// </summary>
 	T,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-U
+	/// The key code for the U key
 	/// </summary>
 	U,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-V
+	/// The key code for the V key
 	/// </summary>
 	V,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-W
+	/// The key code for the W key
 	/// </summary>
 	W,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-X
+	/// The key code for the X key
 	/// </summary>
 	X,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-Y
+	/// The key code for the Y key
 	/// </summary>
 	Y,
+
 	/// <summary>
-	/// The key code for the user pressing Shift-Z
+	/// The key code for the Z key
 	/// </summary>
 	Z,
-	/// <summary>
-	/// The key code for the user pressing A
-	/// </summary>
-	Delete = 127,
 
-	/// <summary>
-	/// When this value is set, the Key encodes the sequence Shift-KeyValue.
-	/// </summary>
-	ShiftMask = 0x10000000,
+	///// <summary>
+	///// The key code for the Delete key.
+	///// </summary>
+	//Delete = 127,
 
-	/// <summary>
-	///   When this value is set, the Key encodes the sequence Alt-KeyValue.
-	///   And the actual value must be extracted by removing the AltMask.
-	/// </summary>
-	AltMask = 0x80000000,
+	// --- Special keys ---
+	// The values below are common non-alphanum keys. Their values are 
+	// based on the .NET ConsoleKey values, which, in-turn are based on the
+	// VK_ values from the Windows API. 
+	// We add MaxCodePoint to avoid conflicts with the Unicode values.
 
 	/// <summary>
-	///   When this value is set, the Key encodes the sequence Ctrl-KeyValue.
-	///   And the actual value must be extracted by removing the CtrlMask.
+	/// The maximum Unicode codepoint value. Used to encode the non-alphanumeric control
+	/// keys. 
 	/// </summary>
-	CtrlMask = 0x40000000,
+	MaxCodePoint = 0x10FFFF,
 
 	/// <summary>
 	/// Cursor up key
 	/// </summary>
-	CursorUp = 0x100000,
+	CursorUp = MaxCodePoint + ConsoleKey.UpArrow,
+
 	/// <summary>
 	/// Cursor down key.
 	/// </summary>
-	CursorDown,
+	CursorDown = MaxCodePoint + ConsoleKey.DownArrow,
+
 	/// <summary>
 	/// Cursor left key.
 	/// </summary>
-	CursorLeft,
+	CursorLeft = MaxCodePoint + ConsoleKey.LeftArrow,
+
 	/// <summary>
 	/// Cursor right key.
 	/// </summary>
-	CursorRight,
+	CursorRight = MaxCodePoint + ConsoleKey.RightArrow,
+
 	/// <summary>
 	/// Page Up key.
 	/// </summary>
-	PageUp,
+	PageUp = MaxCodePoint + ConsoleKey.PageUp,
+
 	/// <summary>
 	/// Page Down key.
 	/// </summary>
-	PageDown,
+	PageDown = MaxCodePoint + ConsoleKey.PageDown,
+
 	/// <summary>
 	/// Home key.
 	/// </summary>
-	Home,
+	Home = MaxCodePoint + ConsoleKey.Home,
+
 	/// <summary>
 	/// End key.
 	/// </summary>
-	End,
+	End = MaxCodePoint + ConsoleKey.End,
 
 	/// <summary>
-	/// Insert character key.
+	/// Insert (INS) key.
 	/// </summary>
-	InsertChar,
+	Insert = MaxCodePoint + ConsoleKey.Insert,
 
 	/// <summary>
-	/// Delete character key.
+	/// Delete (DEL) key.
 	/// </summary>
-	DeleteChar,
+	Delete = MaxCodePoint + ConsoleKey.Delete,
 
 	/// <summary>
 	/// Print screen character key.
 	/// </summary>
-	PrintScreen,
+	PrintScreen = MaxCodePoint + ConsoleKey.PrintScreen,
 
 	/// <summary>
 	/// F1 key.
 	/// </summary>
-	F1,
+	F1 = MaxCodePoint + ConsoleKey.F1,
+
 	/// <summary>
 	/// F2 key.
 	/// </summary>
-	F2,
+	F2 = MaxCodePoint + ConsoleKey.F2,
+
 	/// <summary>
 	/// F3 key.
 	/// </summary>
-	F3,
+	F3 = MaxCodePoint + ConsoleKey.F3,
+
 	/// <summary>
 	/// F4 key.
 	/// </summary>
-	F4,
+	F4 = MaxCodePoint + ConsoleKey.F4,
+
 	/// <summary>
 	/// F5 key.
 	/// </summary>
-	F5,
+	F5 = MaxCodePoint + ConsoleKey.F5,
+
 	/// <summary>
 	/// F6 key.
 	/// </summary>
-	F6,
+	F6 = MaxCodePoint + ConsoleKey.F6,
+
 	/// <summary>
 	/// F7 key.
 	/// </summary>
-	F7,
+	F7 = MaxCodePoint + ConsoleKey.F7,
+
 	/// <summary>
 	/// F8 key.
 	/// </summary>
-	F8,
+	F8 = MaxCodePoint + ConsoleKey.F8,
+
 	/// <summary>
 	/// F9 key.
 	/// </summary>
-	F9,
+	F9 = MaxCodePoint + ConsoleKey.F9,
+
 	/// <summary>
 	/// F10 key.
 	/// </summary>
-	F10,
+	F10 = MaxCodePoint + ConsoleKey.F10,
+
 	/// <summary>
 	/// F11 key.
 	/// </summary>
-	F11,
+	F11 = MaxCodePoint + ConsoleKey.F11,
+
 	/// <summary>
 	/// F12 key.
 	/// </summary>
-	F12,
+	F12 = MaxCodePoint + ConsoleKey.F12,
+
 	/// <summary>
 	/// F13 key.
 	/// </summary>
-	F13,
+	F13 = MaxCodePoint + ConsoleKey.F13,
+
 	/// <summary>
 	/// F14 key.
 	/// </summary>
-	F14,
+	F14 = MaxCodePoint + ConsoleKey.F14,
+
 	/// <summary>
 	/// F15 key.
 	/// </summary>
-	F15,
+	F15 = MaxCodePoint + ConsoleKey.F15,
+
 	/// <summary>
 	/// F16 key.
 	/// </summary>
-	F16,
+	F16 = MaxCodePoint + ConsoleKey.F16,
+
 	/// <summary>
 	/// F17 key.
 	/// </summary>
-	F17,
+	F17 = MaxCodePoint + ConsoleKey.F17,
+
 	/// <summary>
 	/// F18 key.
 	/// </summary>
-	F18,
+	F18 = MaxCodePoint + ConsoleKey.F18,
+
 	/// <summary>
 	/// F19 key.
 	/// </summary>
-	F19,
+	F19 = MaxCodePoint + ConsoleKey.F19,
+
 	/// <summary>
 	/// F20 key.
 	/// </summary>
-	F20,
+	F20 = MaxCodePoint + ConsoleKey.F20,
+
 	/// <summary>
 	/// F21 key.
 	/// </summary>
-	F21,
+	F21 = MaxCodePoint + ConsoleKey.F21,
+
 	/// <summary>
 	/// F22 key.
 	/// </summary>
-	F22,
+	F22 = MaxCodePoint + ConsoleKey.F22,
+
 	/// <summary>
 	/// F23 key.
 	/// </summary>
-	F23,
+	F23 = MaxCodePoint + ConsoleKey.F23,
+
 	/// <summary>
 	/// F24 key.
 	/// </summary>
-	F24,
-}
-
+	F24 = MaxCodePoint + ConsoleKey.F24,
+}

+ 1669 - 541
Terminal.Gui/ConsoleDrivers/ConsoleKeyMapping.cs

@@ -2,597 +2,1725 @@
 using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
 
-namespace Terminal.Gui.ConsoleDrivers {
+namespace Terminal.Gui.ConsoleDrivers;
+
+/// <summary>
+/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
+/// </summary>
+public static class ConsoleKeyMapping {
+
+#if !WT_ISSUE_8871_FIXED // https://github.com/microsoft/terminal/issues/8871
 	/// <summary>
-	/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
+	/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
 	/// </summary>
-	public static class ConsoleKeyMapping {
-		class ScanCodeMapping : IEquatable<ScanCodeMapping> {
-			public uint ScanCode;
-			public uint VirtualKey;
-			public ConsoleModifiers Modifiers;
-			public uint UnicodeChar;
-
-			public ScanCodeMapping (uint scanCode, uint virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
-			{
-				ScanCode = scanCode;
-				VirtualKey = virtualKey;
-				Modifiers = modifiers;
-				UnicodeChar = unicodeChar;
-			}
+	/// <param name="vk"></param>
+	/// <param name="uMapType">
+	/// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an un-shifted
+	/// character value in the low order word of the return value. 
+	/// </param>
+	/// <param name="dwhkl"></param>
+	/// <returns>An un-shifted character value in the low order word of the return value. Dead keys (diacritics)
+	/// are indicated by setting the top bit of the return value. If there is no translation,
+	/// the function returns 0. See Remarks.</returns>
+	[DllImport ("user32.dll", EntryPoint = "MapVirtualKeyExW", CharSet = CharSet.Unicode)]
+	extern static uint MapVirtualKeyEx (VK vk, uint uMapType, IntPtr dwhkl);
 
-			public bool Equals (ScanCodeMapping other)
-			{
-				return ScanCode.Equals (other.ScanCode) &&
-					VirtualKey.Equals (other.VirtualKey) &&
-					Modifiers.Equals (other.Modifiers) &&
-					UnicodeChar.Equals (other.UnicodeChar);
-			}
-		}
+	/// <summary>
+	/// Retrieves the active input locale identifier (formerly called the keyboard layout).
+	/// </summary>
+	/// <param name="idThread">0 for current thread</param>
+	/// <returns>The return value is the input locale identifier for the thread.
+	/// The low word contains a Language Identifier for the input language
+	/// and the high word contains a device handle to the physical layout of the keyboard.
+	/// </returns>
+	[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayout", CharSet = CharSet.Unicode)]
+	extern static IntPtr GetKeyboardLayout (IntPtr idThread);
 
-		static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
-		{
-			if (modifiers.HasFlag (ConsoleModifiers.Shift)
-			&& !modifiers.HasFlag (ConsoleModifiers.Alt)
-			&& !modifiers.HasFlag (ConsoleModifiers.Control)) {
-				return ConsoleModifiers.Shift;
-			} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-				return modifiers;
-			}
+	//[DllImport ("user32.dll", EntryPoint = "GetKeyboardLayoutNameW", CharSet = CharSet.Unicode)]
+	//extern static uint GetKeyboardLayoutName (uint idThread);
+
+	[DllImport ("user32.dll")]
+	extern static IntPtr GetForegroundWindow ();
 
+	[DllImport ("user32.dll")]
+	extern static IntPtr GetWindowThreadProcessId (IntPtr hWnd, IntPtr ProcessId);
+
+	/// <summary>
+	/// Translates the specified virtual-key code and keyboard state to the corresponding Unicode character or characters using
+	/// the Win32 API MapVirtualKey.
+	/// </summary>
+	/// <param name="vk"></param>
+	/// <returns>An un-shifted character value in the low order word of the return value. Dead keys (diacritics)
+	/// are indicated by setting the top bit of the return value. If there is no translation,
+	/// the function returns 0.</returns>
+	public static uint MapVKtoChar (VK vk)
+	{
+		if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
 			return 0;
 		}
+		var tid = GetWindowThreadProcessId (GetForegroundWindow (), 0);
+		var hkl = GetKeyboardLayout (tid);
+		return MapVirtualKeyEx (vk, 2, hkl);
+	}
+#else
+	/// <summary>
+	/// Translates (maps) a virtual-key code into a scan code or character value, or translates a scan code into a virtual-key code.
+	/// </summary>
+	/// <param name="vk"></param>
+	/// <param name="uMapType">
+	/// If MAPVK_VK_TO_CHAR (2) - The uCode parameter is a virtual-key code and is translated into an unshifted
+	/// character value in the low order word of the return value. 
+	/// </param>
+	/// <returns>An unshifted character value in the low order word of the return value. Dead keys (diacritics)
+	/// are indicated by setting the top bit of the return value. If there is no translation,
+	/// the function returns 0. See Remarks.</returns>
+	[DllImport ("user32.dll", EntryPoint = "MapVirtualKeyW", CharSet = CharSet.Unicode)]
+	extern static uint MapVirtualKey (VK vk, uint uMapType = 2);
 
-		static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
-		{
-			switch (propName) {
-			case "UnicodeChar":
-				var sCode = scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
-				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-					return scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
-				}
-				return sCode;
-			case "VirtualKey":
-				sCode = scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == modifiers);
-				if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
-					return scanCodes.FirstOrDefault ((e) => e.VirtualKey == keyValue && e.Modifiers == 0);
-				}
-				return sCode;
-			}
+	uint MapVKtoChar (VK vk) => MapVirtualKeyToCharEx (vk);
+#endif
+	/// <summary>
+	/// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.
+	/// </summary>
+	/// <param name="pwszKLID"></param>
+	/// <returns></returns>
+	[DllImport ("user32.dll")]
+	extern static bool GetKeyboardLayoutName ([Out] StringBuilder pwszKLID);
 
-			return null;
+	/// <summary>
+	/// Retrieves the name of the active input locale identifier (formerly called the keyboard layout) for the calling thread.
+	/// </summary>
+	/// <returns></returns>
+	public static string GetKeyboardLayoutName ()
+	{
+		if (Environment.OSVersion.Platform != PlatformID.Win32NT) {
+			return "none";
 		}
 
-		/// <summary>
-		/// Gets the <see cref="ConsoleKey"/> from the provided <see cref="KeyCode"/>.
-		/// </summary>
-		/// <param name="key"></param>
-		/// <returns></returns>
-		public static ConsoleKeyInfo GetConsoleKeyFromKey (KeyCode key)
+		StringBuilder klidSB = new StringBuilder ();
+		GetKeyboardLayoutName (klidSB);
+		return klidSB.ToString ();
+	}
+
+	class ScanCodeMapping : IEquatable<ScanCodeMapping> {
+		public uint ScanCode;
+		public VK VirtualKey;
+		public ConsoleModifiers Modifiers;
+		public uint UnicodeChar;
+
+		public ScanCodeMapping (uint scanCode, VK virtualKey, ConsoleModifiers modifiers, uint unicodeChar)
 		{
-			var mod = new ConsoleModifiers ();
-			if (key.HasFlag (KeyCode.ShiftMask)) {
-				mod |= ConsoleModifiers.Shift;
-			}
-			if (key.HasFlag (KeyCode.AltMask)) {
-				mod |= ConsoleModifiers.Alt;
+			ScanCode = scanCode;
+			VirtualKey = virtualKey;
+			Modifiers = modifiers;
+			UnicodeChar = unicodeChar;
+		}
+
+		public bool Equals (ScanCodeMapping other)
+		{
+			return ScanCode.Equals (other.ScanCode) &&
+				VirtualKey.Equals (other.VirtualKey) &&
+				Modifiers.Equals (other.Modifiers) &&
+				UnicodeChar.Equals (other.UnicodeChar);
+		}
+	}
+
+	static ConsoleModifiers GetModifiers (ConsoleModifiers modifiers)
+	{
+		if (modifiers.HasFlag (ConsoleModifiers.Shift)
+		&& !modifiers.HasFlag (ConsoleModifiers.Alt)
+		&& !modifiers.HasFlag (ConsoleModifiers.Control)) {
+			return ConsoleModifiers.Shift;
+		} else if (modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+			return modifiers;
+		}
+
+		return 0;
+	}
+
+	static ScanCodeMapping GetScanCode (string propName, uint keyValue, ConsoleModifiers modifiers)
+	{
+		switch (propName) {
+		case "UnicodeChar":
+			var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == modifiers);
+			if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+				return _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue && e.Modifiers == 0);
 			}
-			if (key.HasFlag (KeyCode.CtrlMask)) {
-				mod |= ConsoleModifiers.Control;
+			return sCode;
+		case "VirtualKey":
+			sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == modifiers);
+			if (sCode == null && modifiers == (ConsoleModifiers.Alt | ConsoleModifiers.Control)) {
+				return _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)keyValue && e.Modifiers == 0);
 			}
-			return GetConsoleKeyFromKey ((uint)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask), mod, out _);
+			return sCode;
 		}
 
-		/// <summary>
-		/// Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask).
-		/// </summary>
-		/// <param name="keyValue">The key as a unicode codepoint.</param>
-		/// <param name="modifiers">The modifier keys.</param>
-		/// <param name="scanCode">The resulting scan code.</param>
-		/// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
-		public static ConsoleKeyInfo GetConsoleKeyFromKey (uint keyValue, ConsoleModifiers modifiers, out uint scanCode)
-		{
-			scanCode = 0;
-			uint outputChar = keyValue;
-			if (keyValue == 0) {
-				return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+		return null;
+	}
+
+	// BUGBUG: This API is not correct. It is only used by WindowsDriver in VKPacket scenarios
+	/// <summary>
+	/// Get the scan code from a <see cref="ConsoleKeyInfo"/>.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key info.</param>
+	/// <returns>The value if apply.</returns>
+	public static uint GetScanCodeFromConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		var mod = GetModifiers (consoleKeyInfo.Modifiers);
+		ScanCodeMapping scode = GetScanCode ("VirtualKey", (uint)consoleKeyInfo.Key, mod);
+		if (scode != null) {
+			return scode.ScanCode;
+		}
+
+		return 0;
+	}
+
+	// BUGBUG: This API is not correct. It is only used by FakeDriver and VkeyPacketSimulator
+	/// <summary>
+	/// Gets the <see cref="ConsoleKeyInfo"/> from the provided <see cref="KeyCode"/>.
+	/// </summary>
+	/// <param name="key">The key code.</param>
+	/// <returns>The console key info.</returns>
+	public static ConsoleKeyInfo GetConsoleKeyInfoFromKeyCode (KeyCode key)
+	{
+		var modifiers = MapToConsoleModifiers (key);
+		var keyValue = MapKeyCodeToConsoleKey (key, out bool isConsoleKey);
+		if (isConsoleKey) {
+			var mod = GetModifiers (modifiers);
+			var scode = GetScanCode ("VirtualKey", (uint)keyValue, mod);
+			if (scode != null) {
+				return new ConsoleKeyInfo ((char)scode.UnicodeChar, (ConsoleKey)scode.VirtualKey, modifiers.HasFlag (ConsoleModifiers.Shift),
+					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+			}
+		} else {
+			var keyChar = GetKeyCharFromUnicodeChar ((uint)keyValue, modifiers, out uint consoleKey, out _, isConsoleKey);
+			if (consoleKey != 0) {
+				return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
 					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
 			}
+		}
+
+		return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+			modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+	}
+
+	/// <summary>
+	/// Map existing <see cref="KeyCode"/> modifiers to <see cref="ConsoleModifiers"/>.
+	/// </summary>
+	/// <param name="key">The key code.</param>
+	/// <returns>The console modifiers.</returns>
+	public static ConsoleModifiers MapToConsoleModifiers (KeyCode key)
+	{
+		var modifiers = new ConsoleModifiers ();
+		if (key.HasFlag (KeyCode.ShiftMask)) {
+			modifiers |= ConsoleModifiers.Shift;
+		}
+		if (key.HasFlag (KeyCode.AltMask)) {
+			modifiers |= ConsoleModifiers.Alt;
+		}
+		if (key.HasFlag (KeyCode.CtrlMask)) {
+			modifiers |= ConsoleModifiers.Control;
+		}
+
+		return modifiers;
+	}
+
+	/// <summary>
+	/// Gets <see cref="ConsoleModifiers"/> from <see cref="bool"/> modifiers.
+	/// </summary>
+	/// <param name="shift">The shift key.</param>
+	/// <param name="alt">The alt key.</param>
+	/// <param name="control">The control key.</param>
+	/// <returns>The console modifiers.</returns>
+	public static ConsoleModifiers GetModifiers (bool shift, bool alt, bool control)
+	{
+		var modifiers = new ConsoleModifiers ();
+		if (shift) {
+			modifiers |= ConsoleModifiers.Shift;
+		}
+		if (alt) {
+			modifiers |= ConsoleModifiers.Alt;
+		}
+		if (control) {
+			modifiers |= ConsoleModifiers.Control;
+		}
+
+		return modifiers;
+	}
+
+	/// <summary>
+	/// Get the <see cref="ConsoleKeyInfo"/> from a unicode character and modifiers (e.g. (Key)'a' and (Key)Key.CtrlMask).
+	/// </summary>
+	/// <param name="keyValue">The key as a unicode codepoint.</param>
+	/// <param name="modifiers">The modifier keys.</param>
+	/// <param name="scanCode">The resulting scan code.</param>
+	/// <returns>The <see cref="ConsoleKeyInfo"/>.</returns>
+	static ConsoleKeyInfo GetConsoleKeyInfoFromKeyChar (uint keyValue, ConsoleModifiers modifiers, out uint scanCode)
+	{
+		scanCode = 0;
+		if (keyValue == 0) {
+			return new ConsoleKeyInfo ((char)keyValue, ConsoleKey.None, modifiers.HasFlag (ConsoleModifiers.Shift),
+				modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+		}
 
-			uint consoleKey = (uint)MapKeyToConsoleKey ((KeyCode)keyValue, modifiers, out bool mappable);
-			if (mappable) {
-				var mod = GetModifiers (modifiers);
-				var scode = GetScanCode ("UnicodeChar", keyValue, mod);
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					scanCode = scode.ScanCode;
-					outputChar = scode.UnicodeChar;
+		uint outputChar = keyValue;
+		uint consoleKey;
+		if (keyValue > byte.MaxValue) {
+			var sCode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyValue);
+			if (sCode == null) {
+				consoleKey = (byte)(keyValue & byte.MaxValue);
+				sCode = _scanCodes.FirstOrDefault ((e) => e.VirtualKey == (VK)consoleKey);
+				if (sCode == null) {
+					consoleKey = 0;
+					outputChar = keyValue;
 				} else {
-					// If the consoleKey is < 255, retain the lower 8 bits of the key value and set the upper bits to 0xff.
-					// This is a shifted value that will be used by the GetKeyCharFromConsoleKey to do the correct action
-					// because keyValue maybe a UnicodeChar or a ConsoleKey, e.g. for PageUp is passed the ConsoleKey.PageUp
-					consoleKey = consoleKey < 0xff ? consoleKey & 0xff | 0xff << 8 : consoleKey;
-					outputChar = GetKeyCharFromConsoleKey (consoleKey, modifiers, out consoleKey, out scanCode);
+					outputChar = (char)(keyValue >> 8);
 				}
 			} else {
-				var mod = GetModifiers (modifiers);
-				var scode = GetScanCode ("VirtualKey", consoleKey, mod);
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					scanCode = scode.ScanCode;
-					outputChar = scode.UnicodeChar;
-				}
+				consoleKey = (byte)sCode.VirtualKey;
+				outputChar = keyValue;
 			}
+		} else {
+			consoleKey = (byte)keyValue;
+			outputChar = '\0';
+		}
 
-			return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
-					modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+		return new ConsoleKeyInfo ((char)outputChar, (ConsoleKey)consoleKey, modifiers.HasFlag (ConsoleModifiers.Shift),
+			modifiers.HasFlag (ConsoleModifiers.Alt), modifiers.HasFlag (ConsoleModifiers.Control));
+	}
+
+	// Used only by unit tests
+	internal static uint GetKeyChar (uint keyValue, ConsoleModifiers modifiers)
+	{
+		if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'A' and <= 'Z') {
+			return keyValue - 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue is >= 'A' and <= 'Z') {
+			return keyValue + 32;
 		}
 
-		/// <summary>
-		/// Get the output character from the <see cref="ConsoleKey"/>, the correct <see cref="ConsoleKey"/>
-		/// and the scan code used on <see cref="WindowsDriver"/>.
-		/// </summary>
-		/// <param name="unicodeChar">The unicode character.</param>
-		/// <param name="modifiers">The modifiers keys.</param>
-		/// <param name="consoleKey">The resulting console key.</param>
-		/// <param name="scanCode">The resulting scan code.</param>
-		/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
-		static uint GetKeyCharFromConsoleKey (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode)
-		{
-			uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
-			uint keyChar = decodedChar;
-			consoleKey = 0;
-			var mod = GetModifiers (modifiers);
-			scanCode = 0;
-			var scode = unicodeChar != 0 && unicodeChar >> 8 != 0xff ? GetScanCode ("VirtualKey", decodedChar, mod) : null;
+		if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= 'À' and <= 'Ý') {
+			return keyValue - 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue is >= 'À' and <= 'Ý') {
+			return keyValue + 32;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '0') {
+			return keyValue + 13;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 13 is '0') {
+			return keyValue - 13;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is >= '1' and <= '9' and not '7') {
+			return keyValue - 16;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 16 is >= '1' and <= '9' and not '7') {
+			return keyValue + 16;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '7') {
+			return keyValue - 8;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 8 is '7') {
+			return keyValue + 8;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\'') {
+			return keyValue + 24;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 24 is '\'') {
+			return keyValue - 24;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '«') {
+			return keyValue + 16;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 16 is '«') {
+			return keyValue - 16;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '\\') {
+			return keyValue + 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 32 is '\\') {
+			return keyValue - 32;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '+') {
+			return keyValue - 1;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 1 is '+') {
+			return keyValue + 1;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '´') {
+			return keyValue - 84;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 84 is '´') {
+			return keyValue + 84;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is 'º') {
+			return keyValue - 16;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 16 is 'º') {
+			return keyValue + 16;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '~') {
+			return keyValue - 32;
+		} else if (modifiers == ConsoleModifiers.None && keyValue + 32 is '~') {
+			return keyValue + 32;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '<') {
+			return keyValue + 2;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 2 is '<') {
+			return keyValue - 2;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is ',') {
+			return keyValue + 15;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 15 is ',') {
+			return keyValue - 15;
+		}
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '.') {
+			return keyValue + 12;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 12 is '.') {
+			return keyValue - 12;
+		}
+
+		if (modifiers.HasFlag (ConsoleModifiers.Shift) && keyValue is '-') {
+			return keyValue + 50;
+		} else if (modifiers == ConsoleModifiers.None && keyValue - 50 is '-') {
+			return keyValue - 50;
+		}
+
+		return keyValue;
+	}
+
+	/// <summary>
+	/// Get the output character from the <see cref="GetConsoleKeyInfoFromKeyCode"/>, with the correct <see cref="ConsoleKey"/>
+	/// and the scan code used on <see cref="WindowsDriver"/>.
+	/// </summary>
+	/// <param name="unicodeChar">The unicode character.</param>
+	/// <param name="modifiers">The modifiers keys.</param>
+	/// <param name="consoleKey">The resulting console key.</param>
+	/// <param name="scanCode">The resulting scan code.</param>
+	/// <param name="isConsoleKey">Indicates if the <paramref name="unicodeChar"/> is a <see cref="ConsoleKey"/>.</param>
+	/// <returns>The output character or the <paramref name="consoleKey"/>.</returns>
+	/// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
+	internal static uint GetKeyCharFromUnicodeChar (uint unicodeChar, ConsoleModifiers modifiers, out uint consoleKey, out uint scanCode, bool isConsoleKey = false)
+	{
+		uint decodedChar = unicodeChar >> 8 == 0xff ? unicodeChar & 0xff : unicodeChar;
+		uint keyChar = decodedChar;
+		consoleKey = 0;
+		var mod = GetModifiers (modifiers);
+		scanCode = 0;
+		ScanCodeMapping scode = null;
+		if (unicodeChar != 0 && unicodeChar >> 8 != 0xff && isConsoleKey) {
+			scode = GetScanCode ("VirtualKey", decodedChar, mod);
+		}
+		if (isConsoleKey && scode != null) {
+			consoleKey = (uint)scode.VirtualKey;
+			keyChar = scode.UnicodeChar;
+			scanCode = scode.ScanCode;
+		}
+		if (scode == null) {
+			scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
 			if (scode != null) {
-				consoleKey = scode.VirtualKey;
+				consoleKey = (uint)scode.VirtualKey;
 				keyChar = scode.UnicodeChar;
 				scanCode = scode.ScanCode;
 			}
-			if (scode == null) {
-				scode = unicodeChar != 0 ? GetScanCode ("UnicodeChar", decodedChar, mod) : null;
-				if (scode != null) {
-					consoleKey = scode.VirtualKey;
-					keyChar = scode.UnicodeChar;
-					scanCode = scode.ScanCode;
-				}
-			}
-			if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
-				string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
-				for (int i = 0; i < stFormD.Length; i++) {
-					var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
-					if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
-						consoleKey = char.ToUpper (stFormD [i]);
-						scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
-						if (scode != null) {
-							scanCode = scode.ScanCode;
-						}
+		}
+		if (decodedChar != 0 && scanCode == 0 && char.IsLetter ((char)decodedChar)) {
+			string stFormD = ((char)decodedChar).ToString ().Normalize (System.Text.NormalizationForm.FormD);
+			for (int i = 0; i < stFormD.Length; i++) {
+				var uc = CharUnicodeInfo.GetUnicodeCategory (stFormD [i]);
+				if (uc != UnicodeCategory.NonSpacingMark && uc != UnicodeCategory.OtherLetter) {
+					consoleKey = char.ToUpper (stFormD [i]);
+					scode = GetScanCode ("VirtualKey", char.ToUpper (stFormD [i]), 0);
+					if (scode != null) {
+						scanCode = scode.ScanCode;
 					}
 				}
 			}
+		}
+		if (keyChar < 255 && consoleKey == 0 && scanCode == 0) {
+			scode = GetScanCode ("VirtualKey", keyChar, mod);
+			if (scode != null) {
+				consoleKey = (uint)scode.VirtualKey;
+				keyChar = scode.UnicodeChar;
+				scanCode = scode.ScanCode;
+			}
+		}
+
+		return keyChar;
+	}
+
+	/// <summary>
+	/// Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.
+	/// </summary>
+	/// <param name="keyValue">The key value.</param>
+	/// <param name="isConsoleKey">Indicates if the <paramref name="keyValue"/> is a <see cref="ConsoleKey"/>.
+	/// <see langword="true"/> means the return value is in the ConsoleKey enum.
+	/// <see langword="false"/> means the return value can be mapped to a valid unicode character.
+	/// </param>
+	/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
+	/// <remarks>This is only used by the <see cref="GetConsoleKeyInfoFromKeyCode"/> and by unit tests.</remarks>
+	internal static uint MapKeyCodeToConsoleKey (KeyCode keyValue, out bool isConsoleKey)
+	{
+		isConsoleKey = true;
+		keyValue = keyValue & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask;
+
+		switch (keyValue) {
+		case KeyCode.Enter:
+			return (uint)ConsoleKey.Enter;
+		case KeyCode.CursorUp:
+			return (uint)ConsoleKey.UpArrow;
+		case KeyCode.CursorDown:
+			return (uint)ConsoleKey.DownArrow;
+		case KeyCode.CursorLeft:
+			return (uint)ConsoleKey.LeftArrow;
+		case KeyCode.CursorRight:
+			return (uint)ConsoleKey.RightArrow;
+		case KeyCode.PageUp:
+			return (uint)ConsoleKey.PageUp;
+		case KeyCode.PageDown:
+			return (uint)ConsoleKey.PageDown;
+		case KeyCode.Home:
+			return (uint)ConsoleKey.Home;
+		case KeyCode.End:
+			return (uint)ConsoleKey.End;
+		case KeyCode.Insert:
+			return (uint)ConsoleKey.Insert;
+		case KeyCode.Delete:
+			return (uint)ConsoleKey.Delete;
+		case KeyCode.F1:
+			return (uint)ConsoleKey.F1;
+		case KeyCode.F2:
+			return (uint)ConsoleKey.F2;
+		case KeyCode.F3:
+			return (uint)ConsoleKey.F3;
+		case KeyCode.F4:
+			return (uint)ConsoleKey.F4;
+		case KeyCode.F5:
+			return (uint)ConsoleKey.F5;
+		case KeyCode.F6:
+			return (uint)ConsoleKey.F6;
+		case KeyCode.F7:
+			return (uint)ConsoleKey.F7;
+		case KeyCode.F8:
+			return (uint)ConsoleKey.F8;
+		case KeyCode.F9:
+			return (uint)ConsoleKey.F9;
+		case KeyCode.F10:
+			return (uint)ConsoleKey.F10;
+		case KeyCode.F11:
+			return (uint)ConsoleKey.F11;
+		case KeyCode.F12:
+			return (uint)ConsoleKey.F12;
+		case KeyCode.F13:
+			return (uint)ConsoleKey.F13;
+		case KeyCode.F14:
+			return (uint)ConsoleKey.F14;
+		case KeyCode.F15:
+			return (uint)ConsoleKey.F15;
+		case KeyCode.F16:
+			return (uint)ConsoleKey.F16;
+		case KeyCode.F17:
+			return (uint)ConsoleKey.F17;
+		case KeyCode.F18:
+			return (uint)ConsoleKey.F18;
+		case KeyCode.F19:
+			return (uint)ConsoleKey.F19;
+		case KeyCode.F20:
+			return (uint)ConsoleKey.F20;
+		case KeyCode.F21:
+			return (uint)ConsoleKey.F21;
+		case KeyCode.F22:
+			return (uint)ConsoleKey.F22;
+		case KeyCode.F23:
+			return (uint)ConsoleKey.F23;
+		case KeyCode.F24:
+			return (uint)ConsoleKey.F24;
+		case KeyCode.Tab | KeyCode.ShiftMask:
+			return (uint)ConsoleKey.Tab;
+		}
+
+		isConsoleKey = false;
+		return (uint)keyValue;
+	}
+
+	/// <summary>
+	/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key.</param>
+	/// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+	public static KeyCode MapConsoleKeyInfoToKeyCode (ConsoleKeyInfo consoleKeyInfo)
+	{
+		KeyCode keyCode;
+
+		switch (consoleKeyInfo.Key) {
+		case ConsoleKey.Enter:
+			keyCode = KeyCode.Enter;
+			break;
+		case ConsoleKey.Delete:
+			keyCode = KeyCode.Delete;
+			break;
+		case ConsoleKey.UpArrow:
+			keyCode = KeyCode.CursorUp;
+			break;
+		case ConsoleKey.DownArrow:
+			keyCode = KeyCode.CursorDown;
+			break;
+		case ConsoleKey.LeftArrow:
+			keyCode = KeyCode.CursorLeft;
+			break;
+		case ConsoleKey.RightArrow:
+			keyCode = KeyCode.CursorRight;
+			break;
+		case ConsoleKey.PageUp:
+			keyCode = KeyCode.PageUp;
+			break;
+		case ConsoleKey.PageDown:
+			keyCode = KeyCode.PageDown;
+			break;
+		case ConsoleKey.Home:
+			keyCode = KeyCode.Home;
+			break;
+		case ConsoleKey.End:
+			keyCode = KeyCode.End;
+			break;
+		case ConsoleKey.Insert:
+			keyCode = KeyCode.Insert;
+			break;
+		case ConsoleKey.F1:
+			keyCode = KeyCode.F1;
+			break;
+		case ConsoleKey.F2:
+			keyCode = KeyCode.F2;
+			break;
+		case ConsoleKey.F3:
+			keyCode = KeyCode.F3;
+			break;
+		case ConsoleKey.F4:
+			keyCode = KeyCode.F4;
+			break;
+		case ConsoleKey.F5:
+			keyCode = KeyCode.F5;
+			break;
+		case ConsoleKey.F6:
+			keyCode = KeyCode.F6;
+			break;
+		case ConsoleKey.F7:
+			keyCode = KeyCode.F7;
+			break;
+		case ConsoleKey.F8:
+			keyCode = KeyCode.F8;
+			break;
+		case ConsoleKey.F9:
+			keyCode = KeyCode.F9;
+			break;
+		case ConsoleKey.F10:
+			keyCode = KeyCode.F10;
+			break;
+		case ConsoleKey.F11:
+			keyCode = KeyCode.F11;
+			break;
+		case ConsoleKey.F12:
+			keyCode = KeyCode.F12;
+			break;
+		case ConsoleKey.F13:
+			keyCode = KeyCode.F13;
+			break;
+		case ConsoleKey.F14:
+			keyCode = KeyCode.F14;
+			break;
+		case ConsoleKey.F15:
+			keyCode = KeyCode.F15;
+			break;
+		case ConsoleKey.F16:
+			keyCode = KeyCode.F16;
+			break;
+		case ConsoleKey.F17:
+			keyCode = KeyCode.F17;
+			break;
+		case ConsoleKey.F18:
+			keyCode = KeyCode.F18;
+			break;
+		case ConsoleKey.F19:
+			keyCode = KeyCode.F19;
+			break;
+		case ConsoleKey.F20:
+			keyCode = KeyCode.F20;
+			break;
+		case ConsoleKey.F21:
+			keyCode = KeyCode.F21;
+			break;
+		case ConsoleKey.F22:
+			keyCode = KeyCode.F22;
+			break;
+		case ConsoleKey.F23:
+			keyCode = KeyCode.F23;
+			break;
+		case ConsoleKey.F24:
+			keyCode = KeyCode.F24;
+			break;
+		case ConsoleKey.Tab:
+			keyCode = KeyCode.Tab;
+			break;
+		default:
+			keyCode = (KeyCode)consoleKeyInfo.KeyChar;
+			break;
+		}
+		keyCode |= MapToKeyCodeModifiers (consoleKeyInfo.Modifiers, keyCode);
+
+		return keyCode;
+	}
 
-			return keyChar;
+	/// <summary>
+	/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+	/// </summary>
+	/// <param name="modifiers">The console modifiers.</param>
+	/// <param name="key">The key code.</param>
+	/// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
+	public static KeyCode MapToKeyCodeModifiers (ConsoleModifiers modifiers, KeyCode key)
+	{
+		var keyMod = new KeyCode ();
+		if ((modifiers & ConsoleModifiers.Shift) != 0) {
+			keyMod = KeyCode.ShiftMask;
+		}
+		if ((modifiers & ConsoleModifiers.Control) != 0) {
+			keyMod |= KeyCode.CtrlMask;
 		}
+		if ((modifiers & ConsoleModifiers.Alt) != 0) {
+			keyMod |= KeyCode.AltMask;
+		}
+
+		return keyMod != KeyCode.Null ? keyMod | key : key;
+	}
 
+	/// <summary>
+	/// Generated from winuser.h. See https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
+	/// </summary>
+	public enum VK : ushort {
 		/// <summary>
-		/// Maps a unicode character (e.g. (Key)'a') to a uint representing a <see cref="ConsoleKey"/>.
+		/// Left mouse button.
 		/// </summary>
-		/// <param name="keyValue">The key value.</param>
-		/// <param name="modifiers">The modifiers keys.</param>
-		/// <param name="isMappable">
-		/// <see langword="true"/> means the return value can be mapped to a valid unicode character.
-		/// <see langword="false"/> means the return value is in the ConsoleKey enum.
-		/// </param>
-		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
-		public static ConsoleKey MapKeyToConsoleKey (KeyCode keyValue, ConsoleModifiers modifiers, out bool isMappable)
-		{
-			isMappable = false;
-
-			switch (keyValue) {
-			case KeyCode.Delete:
-				return ConsoleKey.Delete;
-			case KeyCode.CursorUp:
-				return ConsoleKey.UpArrow;
-			case KeyCode.CursorDown:
-				return ConsoleKey.DownArrow;
-			case KeyCode.CursorLeft:
-				return ConsoleKey.LeftArrow;
-			case KeyCode.CursorRight:
-				return ConsoleKey.RightArrow;
-			case KeyCode.PageUp:
-				return ConsoleKey.PageUp;
-			case KeyCode.PageDown:
-				return ConsoleKey.PageDown;
-			case KeyCode.Home:
-				return ConsoleKey.Home;
-			case KeyCode.End:
-				return ConsoleKey.End;
-			case KeyCode.InsertChar:
-				return ConsoleKey.Insert;
-			case KeyCode.DeleteChar:
-				return ConsoleKey.Delete;
-			case KeyCode.F1:
-				return ConsoleKey.F1;
-			case KeyCode.F2:
-				return ConsoleKey.F2;
-			case KeyCode.F3:
-				return ConsoleKey.F3;
-			case KeyCode.F4:
-				return ConsoleKey.F4;
-			case KeyCode.F5:
-				return ConsoleKey.F5;
-			case KeyCode.F6:
-				return ConsoleKey.F6;
-			case KeyCode.F7:
-				return ConsoleKey.F7;
-			case KeyCode.F8:
-				return ConsoleKey.F8;
-			case KeyCode.F9:
-				return ConsoleKey.F9;
-			case KeyCode.F10:
-				return ConsoleKey.F10;
-			case KeyCode.F11:
-				return ConsoleKey.F11;
-			case KeyCode.F12:
-				return ConsoleKey.F12;
-			case KeyCode.F13:
-				return ConsoleKey.F13;
-			case KeyCode.F14:
-				return ConsoleKey.F14;
-			case KeyCode.F15:
-				return ConsoleKey.F15;
-			case KeyCode.F16:
-				return ConsoleKey.F16;
-			case KeyCode.F17:
-				return ConsoleKey.F17;
-			case KeyCode.F18:
-				return ConsoleKey.F18;
-			case KeyCode.F19:
-				return ConsoleKey.F19;
-			case KeyCode.F20:
-				return ConsoleKey.F20;
-			case KeyCode.F21:
-				return ConsoleKey.F21;
-			case KeyCode.F22:
-				return ConsoleKey.F22;
-			case KeyCode.F23:
-				return ConsoleKey.F23;
-			case KeyCode.F24:
-				return ConsoleKey.F24;
-			case KeyCode.Tab | KeyCode.ShiftMask:
-				return ConsoleKey.Tab;
-			}
+		LBUTTON = 0x01,
+
+		/// <summary>
+		/// Right mouse button.
+		/// </summary>
+		RBUTTON = 0x02,
 
-			isMappable = true;
+		/// <summary>
+		/// Control-break processing.
+		/// </summary>
+		CANCEL = 0x03,
 
-			if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= KeyCode.A and <= KeyCode.Z) {
-				return (ConsoleKey)(keyValue - 32);
-			} else if (modifiers == ConsoleModifiers.None && keyValue is >= KeyCode.A and <= KeyCode.Z) {
-				return (ConsoleKey)(keyValue + 32);
-			}
-			if (modifiers == ConsoleModifiers.Shift && keyValue - 32 is >= (KeyCode)'À' and <= (KeyCode)'Ý') {
-				return (ConsoleKey)(keyValue - 32);
-			} else if (modifiers == ConsoleModifiers.None && keyValue is >= (KeyCode)'À' and <= (KeyCode)'Ý') {
-				return (ConsoleKey)(keyValue + 32);
-			}
+		/// <summary>
+		/// Middle mouse button (three-button mouse).
+		/// </summary>
+		MBUTTON = 0x04,
 
-			return (ConsoleKey)keyValue;
-		}
+		/// <summary>
+		/// X1 mouse button.
+		/// </summary>
+		XBUTTON1 = 0x05,
 
 		/// <summary>
-		/// Maps a <see cref="ConsoleKey"/> to a <see cref="KeyCode"/>.
+		/// X2 mouse button.
 		/// </summary>
-		/// <param name="consoleKey">The console key.</param>
-		/// <param name="isMappable">If <see langword="true"/> is mapped to a valid character, otherwise <see langword="false"/>.</param>
-		/// <returns>The <see cref="KeyCode"/> or the <paramref name="consoleKey"/>.</returns>
-		public static KeyCode MapConsoleKeyToKey (ConsoleKey consoleKey, out bool isMappable)
-		{
-			isMappable = false;
-
-			switch (consoleKey) {
-			case ConsoleKey.Delete:
-				return KeyCode.Delete;
-			case ConsoleKey.UpArrow:
-				return KeyCode.CursorUp;
-			case ConsoleKey.DownArrow:
-				return KeyCode.CursorDown;
-			case ConsoleKey.LeftArrow:
-				return KeyCode.CursorLeft;
-			case ConsoleKey.RightArrow:
-				return KeyCode.CursorRight;
-			case ConsoleKey.PageUp:
-				return KeyCode.PageUp;
-			case ConsoleKey.PageDown:
-				return KeyCode.PageDown;
-			case ConsoleKey.Home:
-				return KeyCode.Home;
-			case ConsoleKey.End:
-				return KeyCode.End;
-			case ConsoleKey.Insert:
-				return KeyCode.InsertChar;
-			case ConsoleKey.F1:
-				return KeyCode.F1;
-			case ConsoleKey.F2:
-				return KeyCode.F2;
-			case ConsoleKey.F3:
-				return KeyCode.F3;
-			case ConsoleKey.F4:
-				return KeyCode.F4;
-			case ConsoleKey.F5:
-				return KeyCode.F5;
-			case ConsoleKey.F6:
-				return KeyCode.F6;
-			case ConsoleKey.F7:
-				return KeyCode.F7;
-			case ConsoleKey.F8:
-				return KeyCode.F8;
-			case ConsoleKey.F9:
-				return KeyCode.F9;
-			case ConsoleKey.F10:
-				return KeyCode.F10;
-			case ConsoleKey.F11:
-				return KeyCode.F11;
-			case ConsoleKey.F12:
-				return KeyCode.F12;
-			case ConsoleKey.F13:
-				return KeyCode.F13;
-			case ConsoleKey.F14:
-				return KeyCode.F14;
-			case ConsoleKey.F15:
-				return KeyCode.F15;
-			case ConsoleKey.F16:
-				return KeyCode.F16;
-			case ConsoleKey.F17:
-				return KeyCode.F17;
-			case ConsoleKey.F18:
-				return KeyCode.F18;
-			case ConsoleKey.F19:
-				return KeyCode.F19;
-			case ConsoleKey.F20:
-				return KeyCode.F20;
-			case ConsoleKey.F21:
-				return KeyCode.F21;
-			case ConsoleKey.F22:
-				return KeyCode.F22;
-			case ConsoleKey.F23:
-				return KeyCode.F23;
-			case ConsoleKey.F24:
-				return KeyCode.F24;
-			case ConsoleKey.Tab:
-				return KeyCode.Tab;
-			}
-			isMappable = true;
+		XBUTTON2 = 0x06,
 
-			if (consoleKey is >= ConsoleKey.A and <= ConsoleKey.Z) {
-				return (KeyCode)(consoleKey + 32);
-			}
+		/// <summary>
+		/// BACKSPACE key.
+		/// </summary>
+		BACK = 0x08,
 
-			return (KeyCode)consoleKey;
-		}
+		/// <summary>
+		/// TAB key.
+		/// </summary>
+		TAB = 0x09,
 
 		/// <summary>
-		/// Maps a <see cref="ConsoleKeyInfo"/> to a <see cref="KeyCode"/>.
+		/// CLEAR key.
 		/// </summary>
-		/// <param name="keyInfo">The console key info.</param>
-		/// <param name="key">The key.</param>
-		/// <returns>The <see cref="KeyCode"/> with <see cref="ConsoleModifiers"/> or the <paramref name="key"/></returns>
-		public static KeyCode MapKeyModifiers (ConsoleKeyInfo keyInfo, KeyCode key)
-		{
-			var keyMod = new KeyCode ();
-			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0) {
-				keyMod = KeyCode.ShiftMask;
-			}
-			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0) {
-				keyMod |= KeyCode.CtrlMask;
-			}
-			if ((keyInfo.Modifiers & ConsoleModifiers.Alt) != 0) {
-				keyMod |= KeyCode.AltMask;
-			}
+		CLEAR = 0x0C,
 
-			return keyMod != KeyCode.Null ? keyMod | key : key;
-		}
-
-		static HashSet<ScanCodeMapping> scanCodes = new HashSet<ScanCodeMapping> {
-			new ScanCodeMapping (1, 27, 0, 27), // Escape
-			new ScanCodeMapping (1, 27, ConsoleModifiers.Shift, 27),
-			new ScanCodeMapping (2, 49, 0, 49), // D1
-			new ScanCodeMapping (2, 49, ConsoleModifiers.Shift, 33),
-			new ScanCodeMapping (3, 50, 0, 50), // D2
-			new ScanCodeMapping (3, 50, ConsoleModifiers.Shift, 34),
-			new ScanCodeMapping (3, 50, ConsoleModifiers.Alt | ConsoleModifiers.Control, 64),
-			new ScanCodeMapping (4, 51, 0, 51), // D3
-			new ScanCodeMapping (4, 51, ConsoleModifiers.Shift, 35),
-			new ScanCodeMapping (4, 51, ConsoleModifiers.Alt | ConsoleModifiers.Control, 163),
-			new ScanCodeMapping (5, 52, 0, 52), // D4
-			new ScanCodeMapping (5, 52, ConsoleModifiers.Shift, 36),
-			new ScanCodeMapping (5, 52, ConsoleModifiers.Alt | ConsoleModifiers.Control, 167),
-			new ScanCodeMapping (6, 53, 0, 53), // D5
-			new ScanCodeMapping (6, 53, ConsoleModifiers.Shift, 37),
-			new ScanCodeMapping (6, 53, ConsoleModifiers.Alt | ConsoleModifiers.Control, 8364),
-			new ScanCodeMapping (7, 54, 0, 54), // D6
-			new ScanCodeMapping (7, 54, ConsoleModifiers.Shift, 38),
-			new ScanCodeMapping (8, 55, 0, 55), // D7
-			new ScanCodeMapping (8, 55, ConsoleModifiers.Shift, 47),
-			new ScanCodeMapping (8, 55, ConsoleModifiers.Alt | ConsoleModifiers.Control, 123),
-			new ScanCodeMapping (9, 56, 0, 56), // D8
-			new ScanCodeMapping (9, 56, ConsoleModifiers.Shift, 40),
-			new ScanCodeMapping (9, 56, ConsoleModifiers.Alt | ConsoleModifiers.Control, 91),
-			new ScanCodeMapping (10, 57, 0, 57), // D9
-			new ScanCodeMapping (10, 57, ConsoleModifiers.Shift, 41),
-			new ScanCodeMapping (10, 57, ConsoleModifiers.Alt | ConsoleModifiers.Control, 93),
-			new ScanCodeMapping (11, 48, 0, 48), // D0
-			new ScanCodeMapping (11, 48, ConsoleModifiers.Shift, 61),
-			new ScanCodeMapping (11, 48, ConsoleModifiers.Alt | ConsoleModifiers.Control, 125),
-			new ScanCodeMapping (12, 219, 0, 39), // Oem4
-			new ScanCodeMapping (12, 219, ConsoleModifiers.Shift, 63),
-			new ScanCodeMapping (13, 221, 0, 171), // Oem6
-			new ScanCodeMapping (13, 221, ConsoleModifiers.Shift, 187),
-			new ScanCodeMapping (14, 8, 0, 8), // Backspace
-			new ScanCodeMapping (14, 8, ConsoleModifiers.Shift, 8),
-			new ScanCodeMapping (15, 9, 0, 9), // Tab
-			new ScanCodeMapping (15, 9, ConsoleModifiers.Shift, 15),
-			new ScanCodeMapping (16, 81, 0, 113), // Q
-			new ScanCodeMapping (16, 81, ConsoleModifiers.Shift, 81),
-			new ScanCodeMapping (17, 87, 0, 119), // W
-			new ScanCodeMapping (17, 87, ConsoleModifiers.Shift, 87),
-			new ScanCodeMapping (18, 69, 0, 101), // E
-			new ScanCodeMapping (18, 69, ConsoleModifiers.Shift, 69),
-			new ScanCodeMapping (19, 82, 0, 114), // R
-			new ScanCodeMapping (19, 82, ConsoleModifiers.Shift, 82),
-			new ScanCodeMapping (20, 84, 0, 116), // T
-			new ScanCodeMapping (20, 84, ConsoleModifiers.Shift, 84),
-			new ScanCodeMapping (21, 89, 0, 121), // Y
-			new ScanCodeMapping (21, 89, ConsoleModifiers.Shift, 89),
-			new ScanCodeMapping (22, 85, 0, 117), // U
-			new ScanCodeMapping (22, 85, ConsoleModifiers.Shift, 85),
-			new ScanCodeMapping (23, 73, 0, 105), // I
-			new ScanCodeMapping (23, 73, ConsoleModifiers.Shift, 73),
-			new ScanCodeMapping (24, 79, 0, 111), // O
-			new ScanCodeMapping (24, 79, ConsoleModifiers.Shift, 79),
-			new ScanCodeMapping (25, 80, 0, 112), // P
-			new ScanCodeMapping (25, 80, ConsoleModifiers.Shift, 80),
-			new ScanCodeMapping (26, 187, 0, 43), // OemPlus
-			new ScanCodeMapping (26, 187, ConsoleModifiers.Shift, 42),
-			new ScanCodeMapping (26, 187, ConsoleModifiers.Alt | ConsoleModifiers.Control, 168),
-			new ScanCodeMapping (27, 186, 0, 180), // Oem1
-			new ScanCodeMapping (27, 186, ConsoleModifiers.Shift, 96),
-			new ScanCodeMapping (28, 13, 0, 13), // Enter
-			new ScanCodeMapping (28, 13, ConsoleModifiers.Shift, 13),
-			new ScanCodeMapping (29, 17, 0, 0), // Control
-			new ScanCodeMapping (29, 17, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (scanCode: 30, virtualKey: 65, modifiers: 0, unicodeChar: 97), // VK = A, UC = 'a'
-			new ScanCodeMapping (30, 65, ConsoleModifiers.Shift, 65),  // VK = A | Shift, UC = 'A'
-			new ScanCodeMapping (31, 83, 0, 115), // S
-			new ScanCodeMapping (31, 83, ConsoleModifiers.Shift, 83),
-			new ScanCodeMapping (32, 68, 0, 100), // D
-			new ScanCodeMapping (32, 68, ConsoleModifiers.Shift, 68),
-			new ScanCodeMapping (33, 70, 0, 102), // F
-			new ScanCodeMapping (33, 70, ConsoleModifiers.Shift, 70),
-			new ScanCodeMapping (34, 71, 0, 103), // G
-			new ScanCodeMapping (34, 71, ConsoleModifiers.Shift, 71),
-			new ScanCodeMapping (35, 72, 0, 104), // H
-			new ScanCodeMapping (35, 72, ConsoleModifiers.Shift, 72),
-			new ScanCodeMapping (36, 74, 0, 106), // J
-			new ScanCodeMapping (36, 74, ConsoleModifiers.Shift, 74),
-			new ScanCodeMapping (37, 75, 0, 107), // K
-			new ScanCodeMapping (37, 75, ConsoleModifiers.Shift, 75),
-			new ScanCodeMapping (38, 76, 0, 108), // L
-			new ScanCodeMapping (38, 76, ConsoleModifiers.Shift, 76),
-			new ScanCodeMapping (39, 192, 0, 231), // Oem3
-			new ScanCodeMapping (39, 192, ConsoleModifiers.Shift, 199),
-			new ScanCodeMapping (40, 222, 0, 186), // Oem7
-			new ScanCodeMapping (40, 222, ConsoleModifiers.Shift, 170),
-			new ScanCodeMapping (41, 220, 0, 92), // Oem5
-			new ScanCodeMapping (41, 220, ConsoleModifiers.Shift, 124),
-			new ScanCodeMapping (42, 16, 0, 0), // LShift
-			new ScanCodeMapping (42, 16, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (43, 191, 0, 126), // Oem2
-			new ScanCodeMapping (43, 191, ConsoleModifiers.Shift, 94),
-			new ScanCodeMapping (44, 90, 0, 122), // Z
-			new ScanCodeMapping (44, 90, ConsoleModifiers.Shift, 90),
-			new ScanCodeMapping (45, 88, 0, 120), // X
-			new ScanCodeMapping (45, 88, ConsoleModifiers.Shift, 88),
-			new ScanCodeMapping (46, 67, 0, 99), // C
-			new ScanCodeMapping (46, 67, ConsoleModifiers.Shift, 67),
-			new ScanCodeMapping (47, 86, 0, 118), // V
-			new ScanCodeMapping (47, 86, ConsoleModifiers.Shift, 86),
-			new ScanCodeMapping (48, 66, 0, 98), // B
-			new ScanCodeMapping (48, 66, ConsoleModifiers.Shift, 66),
-			new ScanCodeMapping (49, 78, 0, 110), // N
-			new ScanCodeMapping (49, 78, ConsoleModifiers.Shift, 78),
-			new ScanCodeMapping (50, 77, 0, 109), // M
-			new ScanCodeMapping (50, 77, ConsoleModifiers.Shift, 77),
-			new ScanCodeMapping (51, 188, 0, 44), // OemComma
-			new ScanCodeMapping (51, 188, ConsoleModifiers.Shift, 59),
-			new ScanCodeMapping (52, 190, 0, 46), // OemPeriod
-			new ScanCodeMapping (52, 190, ConsoleModifiers.Shift, 58),
-			new ScanCodeMapping (53, 189, 0, 45), // OemMinus
-			new ScanCodeMapping (53, 189, ConsoleModifiers.Shift, 95),
-			new ScanCodeMapping (54, 16, 0, 0), // RShift
-			new ScanCodeMapping (54, 16, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (55, 44, 0, 0), // PrintScreen
-			new ScanCodeMapping (55, 44, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (56, 18, 0, 0), // Alt
-			new ScanCodeMapping (56, 18, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (57, 32, 0, 32), // Spacebar
-			new ScanCodeMapping (57, 32, ConsoleModifiers.Shift, 32),
-			new ScanCodeMapping (58, 20, 0, 0), // Caps
-			new ScanCodeMapping (58, 20, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (59, 112, 0, 0), // F1
-			new ScanCodeMapping (59, 112, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (60, 113, 0, 0), // F2
-			new ScanCodeMapping (60, 113, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (61, 114, 0, 0), // F3
-			new ScanCodeMapping (61, 114, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (62, 115, 0, 0), // F4
-			new ScanCodeMapping (62, 115, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (63, 116, 0, 0), // F5
-			new ScanCodeMapping (63, 116, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (64, 117, 0, 0), // F6
-			new ScanCodeMapping (64, 117, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (65, 118, 0, 0), // F7
-			new ScanCodeMapping (65, 118, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (66, 119, 0, 0), // F8
-			new ScanCodeMapping (66, 119, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (67, 120, 0, 0), // F9
-			new ScanCodeMapping (67, 120, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (68, 121, 0, 0), // F10
-			new ScanCodeMapping (68, 121, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (69, 144, 0, 0), // Num
-			new ScanCodeMapping (69, 144, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (70, 145, 0, 0), // Scroll
-			new ScanCodeMapping (70, 145, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (71, 36, 0, 0), // Home
-			new ScanCodeMapping (71, 36, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (72, 38, 0, 0), // UpArrow
-			new ScanCodeMapping (72, 38, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (73, 33, 0, 0), // PageUp
-			new ScanCodeMapping (73, 33, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (74, 109, 0, 45), // Subtract
-			new ScanCodeMapping (74, 109, ConsoleModifiers.Shift, 45),
-			new ScanCodeMapping (75, 37, 0, 0), // LeftArrow
-			new ScanCodeMapping (75, 37, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (76, 12, 0, 0), // Center
-			new ScanCodeMapping (76, 12, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (77, 39, 0, 0), // RightArrow
-			new ScanCodeMapping (77, 39, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (78, 107, 0, 43), // Add
-			new ScanCodeMapping (78, 107, ConsoleModifiers.Shift, 43),
-			new ScanCodeMapping (79, 35, 0, 0), // End
-			new ScanCodeMapping (79, 35, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (80, 40, 0, 0), // DownArrow
-			new ScanCodeMapping (80, 40, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (81, 34, 0, 0), // PageDown
-			new ScanCodeMapping (81, 34, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (82, 45, 0, 0), // Insert
-			new ScanCodeMapping (82, 45, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (83, 46, 0, 0), // Delete
-			new ScanCodeMapping (83, 46, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (86, 226, 0, 60), // OEM 102
-			new ScanCodeMapping (86, 226, ConsoleModifiers.Shift, 62),
-			new ScanCodeMapping (87, 122, 0, 0), // F11
-			new ScanCodeMapping (87, 122, ConsoleModifiers.Shift, 0),
-			new ScanCodeMapping (88, 123, 0, 0), // F12
-			new ScanCodeMapping (88, 123, ConsoleModifiers.Shift, 0)
-		};
-
-		/// <summary>
-		/// Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.
-		/// </summary>
-		/// <param name="consoleKeyInfo">The console key info.</param>
-		/// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
-		/// <remarks>If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be
-		/// a <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
-		/// </remarks>
-		public static ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
-		{
-			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
-				return consoleKeyInfo;
-			}
+		/// <summary>
+		/// ENTER key.
+		/// </summary>
+		RETURN = 0x0D,
 
-			return GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
-		}
+		/// <summary>
+		/// SHIFT key.
+		/// </summary>
+		SHIFT = 0x10,
+
+		/// <summary>
+		/// CTRL key.
+		/// </summary>
+		CONTROL = 0x11,
+
+		/// <summary>
+		/// ALT key.
+		/// </summary>
+		MENU = 0x12,
+
+		/// <summary>
+		/// PAUSE key.
+		/// </summary>
+		PAUSE = 0x13,
+
+		/// <summary>
+		/// CAPS LOCK key.
+		/// </summary>
+		CAPITAL = 0x14,
+
+		/// <summary>
+		/// IME Kana mode.
+		/// </summary>
+		KANA = 0x15,
+
+		/// <summary>
+		/// IME Hangul mode.
+		/// </summary>
+		HANGUL = 0x15,
+
+		/// <summary>
+		/// IME Junja mode.
+		/// </summary>
+		JUNJA = 0x17,
+
+		/// <summary>
+		/// IME final mode.
+		/// </summary>
+		FINAL = 0x18,
+
+		/// <summary>
+		/// IME Hanja mode.
+		/// </summary>
+		HANJA = 0x19,
+
+		/// <summary>
+		/// IME Kanji mode.
+		/// </summary>
+		KANJI = 0x19,
+
+		/// <summary>
+		/// ESC key.
+		/// </summary>
+		ESCAPE = 0x1B,
+
+		/// <summary>
+		/// IME convert.
+		/// </summary>
+		CONVERT = 0x1C,
+
+		/// <summary>
+		/// IME nonconvert.
+		/// </summary>
+		NONCONVERT = 0x1D,
+
+		/// <summary>
+		/// IME accept.
+		/// </summary>
+		ACCEPT = 0x1E,
+
+		/// <summary>
+		/// IME mode change request.
+		/// </summary>
+		MODECHANGE = 0x1F,
+
+		/// <summary>
+		/// SPACEBAR.
+		/// </summary>
+		SPACE = 0x20,
+
+		/// <summary>
+		/// PAGE UP key.
+		/// </summary>
+		PRIOR = 0x21,
+
+		/// <summary>
+		/// PAGE DOWN key.
+		/// </summary>
+		NEXT = 0x22,
+
+		/// <summary>
+		/// END key.
+		/// </summary>
+		END = 0x23,
+
+		/// <summary>
+		/// HOME key.
+		/// </summary>
+		HOME = 0x24,
+
+		/// <summary>
+		/// LEFT ARROW key.
+		/// </summary>
+		LEFT = 0x25,
+
+		/// <summary>
+		/// UP ARROW key.
+		/// </summary>
+		UP = 0x26,
+
+		/// <summary>
+		/// RIGHT ARROW key.
+		/// </summary>
+		RIGHT = 0x27,
+
+		/// <summary>
+		/// DOWN ARROW key.
+		/// </summary>
+		DOWN = 0x28,
+
+		/// <summary>
+		/// SELECT key.
+		/// </summary>
+		SELECT = 0x29,
+
+		/// <summary>
+		/// PRINT key.
+		/// </summary>
+		PRINT = 0x2A,
+
+		/// <summary>
+		/// EXECUTE key
+		/// </summary>
+		EXECUTE = 0x2B,
+
+		/// <summary>
+		/// PRINT SCREEN key
+		/// </summary>
+		SNAPSHOT = 0x2C,
+
+		/// <summary>
+		/// INS key
+		/// </summary>
+		INSERT = 0x2D,
+
+		/// <summary>
+		/// DEL key
+		/// </summary>
+		DELETE = 0x2E,
+
+		/// <summary>
+		/// HELP key
+		/// </summary>
+		HELP = 0x2F,
+
+		/// <summary>
+		/// Left Windows key (Natural keyboard)
+		/// </summary>
+		LWIN = 0x5B,
+
+		/// <summary>
+		/// Right Windows key (Natural keyboard)
+		/// </summary>
+		RWIN = 0x5C,
+
+		/// <summary>
+		/// Applications key (Natural keyboard)
+		/// </summary>
+		APPS = 0x5D,
+
+		/// <summary>
+		/// Computer Sleep key
+		/// </summary>
+		SLEEP = 0x5F,
+
+		/// <summary>
+		/// Numeric keypad 0 key
+		/// </summary>
+		NUMPAD0 = 0x60,
+
+		/// <summary>
+		/// Numeric keypad 1 key
+		/// </summary>
+		NUMPAD1 = 0x61,
+
+		/// <summary>
+		/// Numeric keypad 2 key
+		/// </summary>
+		NUMPAD2 = 0x62,
+
+		/// <summary>
+		/// Numeric keypad 3 key
+		/// </summary>
+		NUMPAD3 = 0x63,
+
+		/// <summary>
+		/// Numeric keypad 4 key
+		/// </summary>
+		NUMPAD4 = 0x64,
+
+		/// <summary>
+		/// Numeric keypad 5 key
+		/// </summary>
+		NUMPAD5 = 0x65,
+
+		/// <summary>
+		/// Numeric keypad 6 key
+		/// </summary>
+		NUMPAD6 = 0x66,
+
+		/// <summary>
+		/// Numeric keypad 7 key
+		/// </summary>
+		NUMPAD7 = 0x67,
+
+		/// <summary>
+		/// Numeric keypad 8 key
+		/// </summary>
+		NUMPAD8 = 0x68,
+
+		/// <summary>
+		/// Numeric keypad 9 key
+		/// </summary>
+		NUMPAD9 = 0x69,
+
+		/// <summary>
+		/// Multiply key
+		/// </summary>
+		MULTIPLY = 0x6A,
+
+		/// <summary>
+		/// Add key
+		/// </summary>
+		ADD = 0x6B,
+
+		/// <summary>
+		/// Separator key
+		/// </summary>
+		SEPARATOR = 0x6C,
+
+		/// <summary>
+		/// Subtract key
+		/// </summary>
+		SUBTRACT = 0x6D,
+
+		/// <summary>
+		/// Decimal key
+		/// </summary>
+		DECIMAL = 0x6E,
+
+		/// <summary>
+		/// Divide key
+		/// </summary>
+		DIVIDE = 0x6F,
+
+		/// <summary>
+		/// F1 key
+		/// </summary>
+		F1 = 0x70,
+
+		/// <summary>
+		/// F2 key
+		/// </summary>
+		F2 = 0x71,
+
+		/// <summary>
+		/// F3 key
+		/// </summary>
+		F3 = 0x72,
+
+		/// <summary>
+		/// F4 key
+		/// </summary>
+		F4 = 0x73,
+
+		/// <summary>
+		/// F5 key
+		/// </summary>
+		F5 = 0x74,
+
+		/// <summary>
+		/// F6 key
+		/// </summary>
+		F6 = 0x75,
+
+		/// <summary>
+		/// F7 key
+		/// </summary>
+		F7 = 0x76,
+
+		/// <summary>
+		/// F8 key
+		/// </summary>
+		F8 = 0x77,
+
+		/// <summary>
+		/// F9 key
+		/// </summary>
+		F9 = 0x78,
+
+		/// <summary>
+		/// F10 key
+		/// </summary>
+		F10 = 0x79,
+
+		/// <summary>
+		/// F11 key
+		/// </summary>
+		F11 = 0x7A,
+
+		/// <summary>
+		/// F12 key
+		/// </summary>
+		F12 = 0x7B,
+
+		/// <summary>
+		/// F13 key
+		/// </summary>
+		F13 = 0x7C,
+
+		/// <summary>
+		/// F14 key
+		/// </summary>
+		F14 = 0x7D,
+
+		/// <summary>
+		/// F15 key
+		/// </summary>
+		F15 = 0x7E,
+
+		/// <summary>
+		/// F16 key
+		/// </summary>
+		F16 = 0x7F,
+
+		/// <summary>
+		/// F17 key
+		/// </summary>
+		F17 = 0x80,
+
+		/// <summary>
+		/// F18 key
+		/// </summary>
+		F18 = 0x81,
+
+		/// <summary>
+		/// F19 key
+		/// </summary>
+		F19 = 0x82,
+
+		/// <summary>
+		/// F20 key
+		/// </summary>
+		F20 = 0x83,
+
+		/// <summary>
+		/// F21 key
+		/// </summary>
+		F21 = 0x84,
+
+		/// <summary>
+		/// F22 key
+		/// </summary>
+		F22 = 0x85,
+
+		/// <summary>
+		/// F23 key
+		/// </summary>
+		F23 = 0x86,
+
+		/// <summary>
+		/// F24 key
+		/// </summary>
+		F24 = 0x87,
+
+		/// <summary>
+		/// NUM LOCK key
+		/// </summary>
+		NUMLOCK = 0x90,
+
+		/// <summary>
+		/// SCROLL LOCK key
+		/// </summary>
+		SCROLL = 0x91,
+
+		/// <summary>
+		/// NEC PC-9800 kbd definition: '=' key on numpad
+		/// </summary>
+		OEM_NEC_EQUAL = 0x92,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Dictionary' key
+		/// </summary>
+		OEM_FJ_JISHO = 0x92,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Unregister word' key
+		/// </summary>
+		OEM_FJ_MASSHOU = 0x93,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Register word' key
+		/// </summary>
+		OEM_FJ_TOUROKU = 0x94,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Left OYAYUBI' key
+		/// </summary>
+		OEM_FJ_LOYA = 0x95,
+
+		/// <summary>
+		/// Fujitsu/OASYS kbd definition: 'Right OYAYUBI' key
+		/// </summary>
+		OEM_FJ_ROYA = 0x96,
+
+		/// <summary>
+		/// Left SHIFT key
+		/// </summary>
+		LSHIFT = 0xA0,
+
+		/// <summary>
+		/// Right SHIFT key
+		/// </summary>
+		RSHIFT = 0xA1,
+
+		/// <summary>
+		/// Left CONTROL key
+		/// </summary>
+		LCONTROL = 0xA2,
+
+		/// <summary>
+		/// Right CONTROL key
+		/// </summary>
+		RCONTROL = 0xA3,
+
+		/// <summary>
+		/// Left MENU key (Left Alt key)
+		/// </summary>
+		LMENU = 0xA4,
+
+		/// <summary>
+		/// Right MENU key (Right Alt key)
+		/// </summary>
+		RMENU = 0xA5,
+
+		/// <summary>
+		/// Browser Back key
+		/// </summary>
+		BROWSER_BACK = 0xA6,
+
+		/// <summary>
+		/// Browser Forward key
+		/// </summary>
+		BROWSER_FORWARD = 0xA7,
+
+		/// <summary>
+		/// Browser Refresh key
+		/// </summary>
+		BROWSER_REFRESH = 0xA8,
+
+		/// <summary>
+		/// Browser Stop key
+		/// </summary>
+		BROWSER_STOP = 0xA9,
+
+		/// <summary>
+		/// Browser Search key
+		/// </summary>
+		BROWSER_SEARCH = 0xAA,
+
+		/// <summary>
+		/// Browser Favorites key
+		/// </summary>
+		BROWSER_FAVORITES = 0xAB,
+
+		/// <summary>
+		/// Browser Home key
+		/// </summary>
+		BROWSER_HOME = 0xAC,
+
+		/// <summary>
+		/// Volume Mute key
+		/// </summary>
+		VOLUME_MUTE = 0xAD,
+
+		/// <summary>
+		/// Volume Down key
+		/// </summary>
+		VOLUME_DOWN = 0xAE,
+
+		/// <summary>
+		/// Volume Up key
+		/// </summary>
+		VOLUME_UP = 0xAF,
+
+		/// <summary>
+		/// Next Track key
+		/// </summary>
+		MEDIA_NEXT_TRACK = 0xB0,
+
+		/// <summary>
+		/// Previous Track key
+		/// </summary>
+		MEDIA_PREV_TRACK = 0xB1,
+
+		/// <summary>
+		/// Stop Media key
+		/// </summary>
+		MEDIA_STOP = 0xB2,
+
+		/// <summary>
+		/// Play/Pause Media key
+		/// </summary>
+		MEDIA_PLAY_PAUSE = 0xB3,
+
+		/// <summary>
+		/// Start Mail key
+		/// </summary>
+		LAUNCH_MAIL = 0xB4,
+
+		/// <summary>
+		/// Select Media key
+		/// </summary>
+		LAUNCH_MEDIA_SELECT = 0xB5,
+
+		/// <summary>
+		/// Start Application 1 key
+		/// </summary>
+		LAUNCH_APP1 = 0xB6,
+
+		/// <summary>
+		/// Start Application 2 key
+		/// </summary>
+		LAUNCH_APP2 = 0xB7,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ';:' key
+		/// </summary>
+		OEM_1 = 0xBA,
+
+		/// <summary>
+		/// For any country/region, the '+' key
+		/// </summary>
+		OEM_PLUS = 0xBB,
+
+		/// <summary>
+		/// For any country/region, the ',' key
+		/// </summary>
+		OEM_COMMA = 0xBC,
+
+		/// <summary>
+		/// For any country/region, the '-' key
+		/// </summary>
+		OEM_MINUS = 0xBD,
+
+		/// <summary>
+		/// For any country/region, the '.' key
+		/// </summary>
+		OEM_PERIOD = 0xBE,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '/?' key
+		/// </summary>
+		OEM_2 = 0xBF,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '`~' key
+		/// </summary>
+		OEM_3 = 0xC0,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '[{' key
+		/// </summary>
+		OEM_4 = 0xDB,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the '\|' key
+		/// </summary>
+		OEM_5 = 0xDC,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the ']}' key
+		/// </summary>
+		OEM_6 = 0xDD,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the 'single-quote/double-quote' key
+		/// </summary>
+		OEM_7 = 0xDE,
+
+		/// <summary>
+		/// Used for miscellaneous characters; it can vary by keyboard.
+		/// </summary>
+		OEM_8 = 0xDF,
+
+		/// <summary>
+		/// 'AX' key on Japanese AX kbd
+		/// </summary>
+		OEM_AX = 0xE1,
+
+		/// <summary>
+		/// Either the angle bracket key or the backslash key on the RT 102-key keyboard
+		/// </summary>
+		OEM_102 = 0xE2,
+
+		/// <summary>
+		/// Help key on ICO
+		/// </summary>
+		ICO_HELP = 0xE3,
+
+		/// <summary>
+		/// 00 key on ICO
+		/// </summary>
+		ICO_00 = 0xE4,
+
+		/// <summary>
+		/// Process key
+		/// </summary>
+		PROCESSKEY = 0xE5,
+
+		/// <summary>
+		/// Clear key on ICO
+		/// </summary>
+		ICO_CLEAR = 0xE6,
+
+		/// <summary>
+		/// Packet key to be used to pass Unicode characters as if they were keystrokes
+		/// </summary>
+		PACKET = 0xE7,
+
+		/// <summary>
+		/// Reset key
+		/// </summary>
+		OEM_RESET = 0xE9,
+
+		/// <summary>
+		/// Jump key
+		/// </summary>
+		OEM_JUMP = 0xEA,
+
+		/// <summary>
+		/// PA1 key
+		/// </summary>
+		OEM_PA1 = 0xEB,
+
+		/// <summary>
+		/// PA2 key
+		/// </summary>
+		OEM_PA2 = 0xEC,
+
+		/// <summary>
+		/// PA3 key
+		/// </summary>
+		OEM_PA3 = 0xED,
+
+		/// <summary>
+		/// WsCtrl key
+		/// </summary>
+		OEM_WSCTRL = 0xEE,
+
+		/// <summary>
+		/// CuSel key
+		/// </summary>
+		OEM_CUSEL = 0xEF,
+
+		/// <summary>
+		/// Attn key
+		/// </summary>
+		OEM_ATTN = 0xF0,
+
+		/// <summary>
+		/// Finish key
+		/// </summary>
+		OEM_FINISH = 0xF1,
+
+		/// <summary>
+		/// Copy key
+		/// </summary>
+		OEM_COPY = 0xF2,
+
+		/// <summary>
+		/// Auto key
+		/// </summary>
+		OEM_AUTO = 0xF3,
+
+		/// <summary>
+		/// Enlw key
+		/// </summary>
+		OEM_ENLW = 0xF4,
+
+		/// <summary>
+		/// BackTab key
+		/// </summary>
+		OEM_BACKTAB = 0xF5,
+
+		/// <summary>
+		/// Attn key
+		/// </summary>
+		ATTN = 0xF6,
+
+		/// <summary>
+		/// CrSel key
+		/// </summary>
+		CRSEL = 0xF7,
+
+		/// <summary>
+		/// ExSel key
+		/// </summary>
+		EXSEL = 0xF8,
+
+		/// <summary>
+		/// Erase EOF key
+		/// </summary>
+		EREOF = 0xF9,
+
+		/// <summary>
+		/// Play key
+		/// </summary>
+		PLAY = 0xFA,
+
+		/// <summary>
+		/// Zoom key
+		/// </summary>
+		ZOOM = 0xFB,
+
+		/// <summary>
+		/// Reserved
+		/// </summary>
+		NONAME = 0xFC,
+
+		/// <summary>
+		/// PA1 key
+		/// </summary>
+		PA1 = 0xFD,
+
+		/// <summary>
+		/// Clear key
+		/// </summary>
+		OEM_CLEAR = 0xFE
+	}
+
+	// BUGBUG: This database makes no sense. It is not possible to map a VK code to a character without knowing the keyboard layout
+	//         It should be deleted.
+	static HashSet<ScanCodeMapping> _scanCodes = new HashSet<ScanCodeMapping> {
+		new (1, VK.ESCAPE, 0, '\u001B'), // Escape
+		new (1, VK.ESCAPE, ConsoleModifiers.Shift, '\u001B'),
+		new (2, (VK)'1', 0, '1'), // D1
+		new (2, (VK)'1', ConsoleModifiers.Shift, '!'),
+		new (3, (VK)'2', 0, '2'), // D2
+		new (3, (VK)'2', ConsoleModifiers.Shift, '\"'), // BUGBUG: This is true for Portugese keyboard, but not ENG (@) or DEU (")
+		new (3, (VK)'2', ConsoleModifiers.Alt | ConsoleModifiers.Control, '@'),
+		new (4, (VK)'3', 0, '3'), // D3
+		new (4, (VK)'3', ConsoleModifiers.Shift, '#'),
+		new (4, (VK)'3', ConsoleModifiers.Alt | ConsoleModifiers.Control, '£'),
+		new (5, (VK)'4', 0, '4'), // D4
+		new (5, (VK)'4', ConsoleModifiers.Shift, '$'),
+		new (5, (VK)'4', ConsoleModifiers.Alt | ConsoleModifiers.Control, '§'),
+		new (6, (VK)'5', 0, '5'), // D5
+		new (6, (VK)'5', ConsoleModifiers.Shift, '%'),
+		new (6, (VK)'5', ConsoleModifiers.Alt | ConsoleModifiers.Control, '€'),
+		new (7, (VK)'6', 0, '6'), // D6
+		new (7, (VK)'6', ConsoleModifiers.Shift, '&'),
+		new (8, (VK)'7', 0, '7'), // D7
+		new (8, (VK)'7', ConsoleModifiers.Shift, '/'),
+		new (8, (VK)'7', ConsoleModifiers.Alt | ConsoleModifiers.Control, '{'),
+		new (9, (VK)'8', 0, '8'), // D8
+		new (9, (VK)'8', ConsoleModifiers.Shift, '('),
+		new (9, (VK)'8', ConsoleModifiers.Alt | ConsoleModifiers.Control, '['),
+		new (10, (VK)'9', 0, '9'), // D9
+		new (10, (VK)'9', ConsoleModifiers.Shift, ')'),
+		new (10, (VK)'9', ConsoleModifiers.Alt | ConsoleModifiers.Control, ']'),
+		new (11, (VK)'0', 0, '0'), // D0
+		new (11, (VK)'0', ConsoleModifiers.Shift, '='),
+		new (11, (VK)'0', ConsoleModifiers.Alt | ConsoleModifiers.Control, '}'),
+		new (12, VK.OEM_4, 0, '\''), // Oem4
+		new (12, VK.OEM_4, ConsoleModifiers.Shift, '?'),
+		new (13, VK.OEM_6, 0, '+'), // Oem6
+		new (13, VK.OEM_6, ConsoleModifiers.Shift, '*'),
+		new (14, VK.BACK, 0, '\u0008'), // Backspace
+		new (14, VK.BACK, ConsoleModifiers.Shift, '\u0008'),
+		new (15, VK.TAB, 0, '\u0009'), // Tab
+		new (15, VK.TAB, ConsoleModifiers.Shift, '\u000F'),
+		new (16, (VK)'Q', 0, 'q'), // Q
+		new (16, (VK)'Q', ConsoleModifiers.Shift, 'Q'),
+		new (17, (VK)'W', 0, 'w'), // W
+		new (17, (VK)'W', ConsoleModifiers.Shift, 'W'),
+		new (18, (VK)'E', 0, 'e'), // E
+		new (18, (VK)'E', ConsoleModifiers.Shift, 'E'),
+		new (19, (VK)'R', 0, 'r'), // R
+		new (19, (VK)'R', ConsoleModifiers.Shift, 'R'),
+		new (20, (VK)'T', 0, 't'), // T
+		new (20, (VK)'T', ConsoleModifiers.Shift, 'T'),
+		new (21, (VK)'Y', 0, 'y'), // Y
+		new (21, (VK)'Y', ConsoleModifiers.Shift, 'Y'),
+		new (22, (VK)'U', 0, 'u'), // U
+		new (22, (VK)'U', ConsoleModifiers.Shift, 'U'),
+		new (23, (VK)'I', 0, 'i'), // I
+		new (23, (VK)'I', ConsoleModifiers.Shift, 'I'),
+		new (24, (VK)'O', 0, 'o'), // O
+		new (24, (VK)'O', ConsoleModifiers.Shift, 'O'),
+		new (25, (VK)'P', 0, 'p'), // P
+		new (25, (VK)'P', ConsoleModifiers.Shift, 'P'),
+		new (26, VK.OEM_PLUS, 0, '+'), // OemPlus
+		new (26, VK.OEM_PLUS, ConsoleModifiers.Shift, '*'),
+		new (26, VK.OEM_PLUS, ConsoleModifiers.Alt | ConsoleModifiers.Control, '¨'),
+		new (27, VK.OEM_1, 0, '´'), // Oem1
+		new (27, VK.OEM_1, ConsoleModifiers.Shift, '`'),
+		new (28, VK.RETURN, 0, '\u000D'), // Enter
+		new (28, VK.RETURN, ConsoleModifiers.Shift, '\u000D'),
+		new (29, VK.CONTROL, 0, '\0'), // Control
+		new (29, VK.CONTROL, ConsoleModifiers.Shift, '\0'),
+		new (30, (VK)'A', 0, 'a'), // A
+		new (30, (VK)'A', ConsoleModifiers.Shift, 'A'),
+		new (31, (VK)'S', 0, 's'), // S
+		new (31, (VK)'S', ConsoleModifiers.Shift, 'S'),
+		new (32, (VK)'D', 0, 'd'), // D
+		new (32, (VK)'D', ConsoleModifiers.Shift, 'D'),
+		new (33, (VK)'F', 0, 'f'), // F
+		new (33, (VK)'F', ConsoleModifiers.Shift, 'F'),
+		new (34, (VK)'G', 0, 'g'), // G
+		new (34, (VK)'G', ConsoleModifiers.Shift, 'G'),
+		new (35, (VK)'H', 0, 'h'), // H
+		new (35, (VK)'H', ConsoleModifiers.Shift, 'H'),
+		new (36, (VK)'J', 0, 'j'), // J
+		new (36, (VK)'J', ConsoleModifiers.Shift, 'J'),
+		new (37, (VK)'K', 0, 'k'), // K
+		new (37, (VK)'K', ConsoleModifiers.Shift, 'K'),
+		new (38, (VK)'L', 0, 'l'), // L
+		new (38, (VK)'L', ConsoleModifiers.Shift, 'L'),
+		new (39, VK.OEM_3, 0, '`'), // Oem3 (Backtick/Grave)
+		new (39, VK.OEM_3, ConsoleModifiers.Shift, '~'),
+		new (40, VK.OEM_7, 0, '\''), // Oem7 (Single Quote)
+		new (40, VK.OEM_7, ConsoleModifiers.Shift, '\"'),
+		new (41, VK.OEM_5, 0, '\\'), // Oem5 (Backslash)
+		new (41, VK.OEM_5, ConsoleModifiers.Shift, '|'),
+		new (42, VK.LSHIFT, 0, '\0'), // Left Shift
+		new (42, VK.LSHIFT, ConsoleModifiers.Shift, '\0'),
+		new (43, VK.OEM_2, 0, '/'), // Oem2 (Forward Slash)
+		new (43, VK.OEM_2, ConsoleModifiers.Shift, '?'),
+		new (44, (VK)'Z', 0, 'z'), // Z
+		new (44, (VK)'Z', ConsoleModifiers.Shift, 'Z'),
+		new (45, (VK)'X', 0, 'x'), // X
+		new (45, (VK)'X', ConsoleModifiers.Shift, 'X'),
+		new (46, (VK)'C', 0, 'c'), // C
+		new (46, (VK)'C', ConsoleModifiers.Shift, 'C'),
+		new (47, (VK)'V', 0, 'v'), // V
+		new (47, (VK)'V', ConsoleModifiers.Shift, 'V'),
+		new (48, (VK)'B', 0, 'b'), // B
+		new (48, (VK)'B', ConsoleModifiers.Shift, 'B'),
+		new (49, (VK)'N', 0, 'n'), // N
+		new (49, (VK)'N', ConsoleModifiers.Shift, 'N'),
+		new (50, (VK)'M', 0, 'm'), // M
+		new (50, (VK)'M', ConsoleModifiers.Shift, 'M'),
+		new (51, VK.OEM_COMMA, 0, ','), // OemComma
+		new (51, VK.OEM_COMMA, ConsoleModifiers.Shift, '<'),
+		new (52, VK.OEM_PERIOD, 0, '.'), // OemPeriod
+		new (52, VK.OEM_PERIOD, ConsoleModifiers.Shift, '>'),
+		new (53, VK.OEM_MINUS, 0, '-'), // OemMinus
+		new (53, VK.OEM_MINUS, ConsoleModifiers.Shift, '_'),
+		new (54, VK.RSHIFT, 0, '\0'), // Right Shift
+		new (54, VK.RSHIFT, ConsoleModifiers.Shift, '\0'),
+		new (55, VK.PRINT, 0, '\0'), // Print Screen
+		new (55, VK.PRINT, ConsoleModifiers.Shift, '\0'),
+		new (56, VK.LMENU, 0, '\0'), // Alt
+		new (56, VK.LMENU, ConsoleModifiers.Shift, '\0'),
+		new (57, VK.SPACE, 0, ' '), // Spacebar
+		new (57, VK.SPACE, ConsoleModifiers.Shift, ' '),
+		new (58, VK.CAPITAL, 0, '\0'), // Caps Lock
+		new (58, VK.CAPITAL, ConsoleModifiers.Shift, '\0'),
+		new (59, VK.F1, 0, '\0'), // F1
+		new (59, VK.F1, ConsoleModifiers.Shift, '\0'),
+		new (60, VK.F2, 0, '\0'), // F2
+		new (60, VK.F2, ConsoleModifiers.Shift, '\0'),
+		new (61, VK.F3, 0, '\0'), // F3
+		new (61, VK.F3, ConsoleModifiers.Shift, '\0'),
+		new (62, VK.F4, 0, '\0'), // F4
+		new (62, VK.F4, ConsoleModifiers.Shift, '\0'),
+		new (63, VK.F5, 0, '\0'), // F5
+		new (63, VK.F5, ConsoleModifiers.Shift, '\0'),
+		new (64, VK.F6, 0, '\0'), // F6
+		new (64, VK.F6, ConsoleModifiers.Shift, '\0'),
+		new (65, VK.F7, 0, '\0'), // F7
+		new (65, VK.F7, ConsoleModifiers.Shift, '\0'),
+		new (66, VK.F8, 0, '\0'), // F8
+		new (66, VK.F8, ConsoleModifiers.Shift, '\0'),
+		new (67, VK.F9, 0, '\0'), // F9
+		new (67, VK.F9, ConsoleModifiers.Shift, '\0'),
+		new (68, VK.F10, 0, '\0'), // F10
+		new (68, VK.F10, ConsoleModifiers.Shift, '\0'),
+		new (69, VK.NUMLOCK, 0, '\0'), // Num Lock
+		new (69, VK.NUMLOCK, ConsoleModifiers.Shift, '\0'),
+		new (70, VK.SCROLL, 0, '\0'), // Scroll Lock
+		new (70, VK.SCROLL, ConsoleModifiers.Shift, '\0'),
+		new (71, VK.HOME, 0, '\0'), // Home
+		new (71, VK.HOME, ConsoleModifiers.Shift, '\0'),
+		new (72, VK.UP, 0, '\0'), // Up Arrow
+		new (72, VK.UP, ConsoleModifiers.Shift, '\0'),
+		new (73, VK.PRIOR, 0, '\0'), // Page Up
+		new (73, VK.PRIOR, ConsoleModifiers.Shift, '\0'),
+		new (74, VK.SUBTRACT, 0, '-'), // Subtract (Num Pad '-')
+		new (74, VK.SUBTRACT, ConsoleModifiers.Shift, '-'),
+		new (75, VK.LEFT, 0, '\0'), // Left Arrow
+		new (75, VK.LEFT, ConsoleModifiers.Shift, '\0'),
+		new (76, VK.CLEAR, 0, '\0'), // Center key (Num Pad 5 with Num Lock off)
+		new (76, VK.CLEAR, ConsoleModifiers.Shift, '\0'),
+		new (77, VK.RIGHT, 0, '\0'), // Right Arrow
+		new (77, VK.RIGHT, ConsoleModifiers.Shift, '\0'),
+		new (78, VK.ADD, 0, '+'), // Add (Num Pad '+')
+		new (78, VK.ADD, ConsoleModifiers.Shift, '+'),
+		new (79, VK.END, 0, '\0'), // End
+		new (79, VK.END, ConsoleModifiers.Shift, '\0'),
+		new (80, VK.DOWN, 0, '\0'), // Down Arrow
+		new (80, VK.DOWN, ConsoleModifiers.Shift, '\0'),
+		new (81, VK.NEXT, 0, '\0'), // Page Down
+		new (81, VK.NEXT, ConsoleModifiers.Shift, '\0'),
+		new (82, VK.INSERT, 0, '\0'), // Insert
+		new (82, VK.INSERT, ConsoleModifiers.Shift, '\0'),
+		new (83, VK.DELETE, 0, '\0'), // Delete
+		new (83, VK.DELETE, ConsoleModifiers.Shift, '\0'),
+		new (86, VK.OEM_102, 0, '<'), // OEM 102 (Typically '<' or '|' key next to Left Shift)
+		new (86, VK.OEM_102, ConsoleModifiers.Shift, '>'),
+		new (87, VK.F11, 0, '\0'), // F11
+		new (87, VK.F11, ConsoleModifiers.Shift, '\0'),
+		new (88, VK.F12, 0, '\0'), // F12
+		new (88, VK.F12, ConsoleModifiers.Shift, '\0')
+	};
+
+	/// <summary>
+	/// Decode a <see cref="ConsoleKeyInfo"/> that is using <see cref="ConsoleKey.Packet"/>.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key info.</param>
+	/// <returns>The decoded <see cref="ConsoleKeyInfo"/> or the <paramref name="consoleKeyInfo"/>.</returns>
+	/// <remarks>If it's a <see cref="ConsoleKey.Packet"/> the <see cref="ConsoleKeyInfo.KeyChar"/> may be
+	/// a <see cref="ConsoleKeyInfo.Key"/> or a <see cref="ConsoleKeyInfo.KeyChar"/> value.
+	/// </remarks>
+	public static ConsoleKeyInfo DecodeVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+	{
+		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+			return consoleKeyInfo;
+		}
+
+		return GetConsoleKeyInfoFromKeyChar (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
+	}
+
+	/// <summary>
+	/// Encode the <see cref="ConsoleKeyInfo.KeyChar"/> with the <see cref="ConsoleKeyInfo.Key"/>
+	/// if the first a byte length, otherwise only the KeyChar is considered and searched on the database.
+	/// </summary>
+	/// <param name="consoleKeyInfo">The console key info.</param>
+	/// <returns>The encoded KeyChar with the Key if both can be shifted, otherwise only the KeyChar.</returns>
+	/// <remarks>This is useful to use with the <see cref="ConsoleKey.Packet"/>.</remarks>
+	public static char EncodeKeyCharForVKPacket (ConsoleKeyInfo consoleKeyInfo)
+	{
+		char keyChar = consoleKeyInfo.KeyChar;
+		ConsoleKey consoleKey = consoleKeyInfo.Key;
+		if (keyChar != 0 && consoleKeyInfo.KeyChar < byte.MaxValue && consoleKey == ConsoleKey.None) {
+			// try to get the ConsoleKey
+			var scode = _scanCodes.FirstOrDefault ((e) => e.UnicodeChar == keyChar);
+			if (scode != null) {
+				consoleKey = (ConsoleKey)scode.VirtualKey;
+			}
+		}
+		if (keyChar < byte.MaxValue && consoleKey != ConsoleKey.None) {
+			keyChar = (char)(consoleKeyInfo.KeyChar << 8 | (byte)consoleKey);
+		}
+
+		return keyChar;
 	}
-}
+}

+ 79 - 89
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -14,15 +14,21 @@ namespace Terminal.Gui;
 /// <summary>
 /// This is the Curses driver for the gui.cs/Terminal framework.
 /// </summary>
-internal class CursesDriver : ConsoleDriver {
-
+class CursesDriver : ConsoleDriver {
 	public override int Cols {
 		get => Curses.Cols;
-		internal set => Curses.Cols = value;
+		internal set {
+			Curses.Cols = value;
+			ClearContents();
+		}
 	}
+
 	public override int Rows {
 		get => Curses.Lines;
-		internal set => Curses.Lines = value;
+		internal set {
+			Curses.Lines = value;
+			ClearContents();
+		}
 	}
 
 	CursorVisibility? _initialCursorVisibility = null;
@@ -30,6 +36,7 @@ internal class CursesDriver : ConsoleDriver {
 
 	public override string GetVersionInfo () => $"{Curses.curses_version ()}";
 	UnixMainLoop _mainLoopDriver = null;
+
 	public override bool SupportsTrueColor => false;
 
 	object _processInputToken;
@@ -132,11 +139,9 @@ internal class CursesDriver : ConsoleDriver {
 		}
 	}
 
-	public override bool IsRuneSupported (Rune rune)
-	{
+	public override bool IsRuneSupported (Rune rune) =>
 		// See Issue #2615 - CursesDriver is broken with non-BMP characters
-		return base.IsRuneSupported (rune) && rune.IsBmp;
-	}
+		base.IsRuneSupported (rune) && rune.IsBmp;
 
 	public override void Refresh ()
 	{
@@ -153,7 +158,6 @@ internal class CursesDriver : ConsoleDriver {
 	}
 
 	#region Color Handling
-
 	/// <summary>
 	/// Creates an Attribute from the provided curses-based foreground and background color numbers
 	/// </summary>
@@ -162,16 +166,17 @@ internal class CursesDriver : ConsoleDriver {
 	/// <returns></returns>
 	static Attribute MakeColor (short foreground, short background)
 	{
-		var v = (short)((int)foreground | background << 4);
+		short v = (short)((int)foreground | background << 4);
 
 		// TODO: for TrueColor - Use InitExtendedPair
 		Curses.InitColorPair (v, foreground, background);
 		return new Attribute (
-			platformColor: Curses.ColorPair (v),
-			foreground: CursesColorNumberToColorName (foreground),
-			background: CursesColorNumberToColorName (background));
+			Curses.ColorPair (v),
+			CursesColorNumberToColorName (foreground),
+			CursesColorNumberToColorName (background));
 	}
 
+	/// <inheritdoc/>
 	/// <remarks>
 	/// In the CursesDriver, colors are encoded as an int. 
 	/// The foreground color is stored in the most significant 4 bits, 
@@ -184,9 +189,9 @@ internal class CursesDriver : ConsoleDriver {
 			return MakeColor (ColorNameToCursesColorNumber (foreground.ColorName), ColorNameToCursesColorNumber (background.ColorName));
 		} else {
 			return new Attribute (
-				platformColor: 0,
-				foreground: foreground,
-				background: background);
+				0,
+				foreground,
+				background);
 		}
 	}
 
@@ -267,7 +272,6 @@ internal class CursesDriver : ConsoleDriver {
 		}
 		throw new ArgumentException ("Invalid curses color code");
 	}
-
 	#endregion
 
 	public override void UpdateCursor ()
@@ -367,8 +371,8 @@ internal class CursesDriver : ConsoleDriver {
 		case Curses.KeyEnd: return KeyCode.End;
 		case Curses.KeyNPage: return KeyCode.PageDown;
 		case Curses.KeyPPage: return KeyCode.PageUp;
-		case Curses.KeyDeleteChar: return KeyCode.DeleteChar;
-		case Curses.KeyInsertChar: return KeyCode.InsertChar;
+		case Curses.KeyDeleteChar: return KeyCode.Delete;
+		case Curses.KeyInsertChar: return KeyCode.Insert;
 		case Curses.KeyTab: return KeyCode.Tab;
 		case Curses.KeyBackTab: return KeyCode.Tab | KeyCode.ShiftMask;
 		case Curses.KeyBackspace: return KeyCode.Backspace;
@@ -423,12 +427,12 @@ internal class CursesDriver : ConsoleDriver {
 	internal void ProcessInput ()
 	{
 		int wch;
-		var code = Curses.get_wch (out wch);
+		int code = Curses.get_wch (out wch);
 		//System.Diagnostics.Debug.WriteLine ($"code: {code}; wch: {wch}");
 		if (code == Curses.ERR) {
 			return;
 		}
-		KeyCode k = KeyCode.Null;
+		var k = KeyCode.Null;
 
 		if (code == Curses.KEY_CODE_YES) {
 			while (code == Curses.KEY_CODE_YES && wch == Curses.KeyResize) {
@@ -443,11 +447,11 @@ internal class CursesDriver : ConsoleDriver {
 
 				while (wch2 == Curses.KeyMouse) {
 					Key kea = null;
-					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
-							new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false),
-							new ConsoleKeyInfo ('[', 0, false, false, false),
-							new ConsoleKeyInfo ('<', 0, false, false, false)
-						};
+					var cki = new ConsoleKeyInfo [] {
+						new ((char)KeyCode.Esc, 0, false, false, false),
+						new ('[', 0, false, false, false),
+						new ('<', 0, false, false, false)
+					};
 					code = 0;
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref kea, ref cki);
 				}
@@ -503,27 +507,27 @@ internal class CursesDriver : ConsoleDriver {
 				} else if (wch2 >= (uint)KeyCode.D0 && wch2 <= (uint)KeyCode.D9) {
 					k = (KeyCode)((uint)KeyCode.AltMask + (uint)KeyCode.D0 + (wch2 - (uint)KeyCode.D0));
 				} else if (wch2 == Curses.KeyCSI) {
-					ConsoleKeyInfo [] cki = new ConsoleKeyInfo [] {
-							new ConsoleKeyInfo ((char)KeyCode.Esc, 0, false, false, false),
-							new ConsoleKeyInfo ('[', 0, false, false, false)
-						};
+					var cki = new ConsoleKeyInfo [] {
+						new ((char)KeyCode.Esc, 0, false, false, false),
+						new ('[', 0, false, false, false)
+					};
 					HandleEscSeqResponse (ref code, ref k, ref wch2, ref key, ref cki);
 					return;
 				} else {
 					// Unfortunately there are no way to differentiate Ctrl+Alt+alfa and Ctrl+Shift+Alt+alfa.
 					if (((KeyCode)wch2 & KeyCode.CtrlMask) != 0) {
-						k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~((int)KeyCode.CtrlMask)));
+						k = (KeyCode)((uint)KeyCode.CtrlMask + (wch2 & ~(int)KeyCode.CtrlMask));
 					}
 					if (wch2 == 0) {
 						k = KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Space;
 					} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
 						k = KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Space;
 					} else if (wch2 < 256) {
-						k = (KeyCode)wch2 | KeyCode.AltMask;
+						k = (KeyCode)wch2;// | KeyCode.AltMask;
 					} else {
 						k = (KeyCode)((uint)(KeyCode.AltMask | KeyCode.CtrlMask) + wch2);
 					}
-				}
+				} 
 				key = new Key (k);
 			} else {
 				key = new Key (KeyCode.Esc);
@@ -545,9 +549,11 @@ internal class CursesDriver : ConsoleDriver {
 				}
 			} else if (wch >= (uint)KeyCode.A && wch <= (uint)KeyCode.Z) {
 				k = (KeyCode)wch | KeyCode.ShiftMask;
-			} else if (wch <= 'z') {
-				k = (KeyCode)wch & ~KeyCode.Space;
 			} 
+			
+			if (wch == '\n' || wch == '\r') {
+				k = KeyCode.Enter;
+			}
 			OnKeyDown (new Key (k));
 			OnKeyUp (new Key (k));
 		}
@@ -561,7 +567,7 @@ internal class CursesDriver : ConsoleDriver {
 			code = Curses.get_wch (out wch2);
 			var consoleKeyInfo = new ConsoleKeyInfo ((char)wch2, 0, false, false, false);
 			if (wch2 == 0 || wch2 == 27 || wch2 == Curses.KeyMouse) {
-				EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out List<MouseFlags> mouseFlags, out Point pos, out _, ProcessMouseEvent);
+				EscSeqUtils.DecodeEscSeq (null, ref consoleKeyInfo, ref ck, cki, ref mod, out _, out _, out _, out _, out bool isKeyMouse, out var mouseFlags, out var pos, out _, ProcessMouseEvent);
 				if (isKeyMouse) {
 					foreach (var mf in mouseFlags) {
 						ProcessMouseEvent (mf, pos);
@@ -572,9 +578,8 @@ internal class CursesDriver : ConsoleDriver {
 							false, false, false), cki);
 					}
 				} else {
-					k = ConsoleKeyMapping.MapConsoleKeyToKey (consoleKeyInfo.Key, out _);
-					k = ConsoleKeyMapping.MapKeyModifiers (consoleKeyInfo, k);
-					keyEventArgs = new (k);
+					k = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+					keyEventArgs = new Key (k);
 					OnKeyDown (keyEventArgs);
 				}
 			} else {
@@ -587,36 +592,27 @@ internal class CursesDriver : ConsoleDriver {
 
 	void ProcessMouseEvent (MouseFlags mouseFlag, Point pos)
 	{
-		bool WasButtonReleased (MouseFlags flag)
-		{
-			return flag.HasFlag (MouseFlags.Button1Released) ||
-				flag.HasFlag (MouseFlags.Button2Released) ||
-				flag.HasFlag (MouseFlags.Button3Released) ||
-				flag.HasFlag (MouseFlags.Button4Released);
-		}
-
-		bool IsButtonNotPressed (MouseFlags flag)
-		{
-			return !flag.HasFlag (MouseFlags.Button1Pressed) &&
-				!flag.HasFlag (MouseFlags.Button2Pressed) &&
-				!flag.HasFlag (MouseFlags.Button3Pressed) &&
-				!flag.HasFlag (MouseFlags.Button4Pressed);
-		}
-
-		bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
-		{
-			return flag.HasFlag (MouseFlags.Button1Clicked) ||
-				flag.HasFlag (MouseFlags.Button2Clicked) ||
-				flag.HasFlag (MouseFlags.Button3Clicked) ||
-				flag.HasFlag (MouseFlags.Button4Clicked) ||
-				flag.HasFlag (MouseFlags.Button1DoubleClicked) ||
-				flag.HasFlag (MouseFlags.Button2DoubleClicked) ||
-				flag.HasFlag (MouseFlags.Button3DoubleClicked) ||
-				flag.HasFlag (MouseFlags.Button4DoubleClicked);
-		}
-
-		if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) ||
-			(IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0)) {
+		bool WasButtonReleased (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Released) ||
+							flag.HasFlag (MouseFlags.Button2Released) ||
+							flag.HasFlag (MouseFlags.Button3Released) ||
+							flag.HasFlag (MouseFlags.Button4Released);
+
+		bool IsButtonNotPressed (MouseFlags flag) => !flag.HasFlag (MouseFlags.Button1Pressed) &&
+								!flag.HasFlag (MouseFlags.Button2Pressed) &&
+								!flag.HasFlag (MouseFlags.Button3Pressed) &&
+								!flag.HasFlag (MouseFlags.Button4Pressed);
+
+		bool IsButtonClickedOrDoubleClicked (MouseFlags flag) => flag.HasFlag (MouseFlags.Button1Clicked) ||
+									flag.HasFlag (MouseFlags.Button2Clicked) ||
+									flag.HasFlag (MouseFlags.Button3Clicked) ||
+									flag.HasFlag (MouseFlags.Button4Clicked) ||
+									flag.HasFlag (MouseFlags.Button1DoubleClicked) ||
+									flag.HasFlag (MouseFlags.Button2DoubleClicked) ||
+									flag.HasFlag (MouseFlags.Button3DoubleClicked) ||
+									flag.HasFlag (MouseFlags.Button4DoubleClicked);
+
+		if (WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags) ||
+		IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0) {
 			return;
 		}
 
@@ -638,7 +634,7 @@ internal class CursesDriver : ConsoleDriver {
 		//	// If xclip is installed on Linux under WSL, this will return true.
 		//	return false;
 		//}
-		var (exitCode, result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
+		(int exitCode, string result) = ClipboardProcessRunner.Bash ("uname -a", waitForOutput: true);
 		if (exitCode == 0 && result.Contains ("microsoft") && result.Contains ("WSL")) {
 			return true;
 		}
@@ -675,8 +671,9 @@ internal class CursesDriver : ConsoleDriver {
 	{
 		visibility = CursorVisibility.Invisible;
 
-		if (!_currentCursorVisibility.HasValue)
+		if (!_currentCursorVisibility.HasValue) {
 			return false;
+		}
 
 		visibility = _currentCursorVisibility.Value;
 
@@ -691,11 +688,11 @@ internal class CursesDriver : ConsoleDriver {
 		}
 
 		if (!RunningUnitTests) {
-			Curses.curs_set (((int)visibility >> 16) & 0x000000FF);
+			Curses.curs_set ((int)visibility >> 16 & 0x000000FF);
 		}
 
 		if (visibility != CursorVisibility.Invisible) {
-			Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) & 0xFF)));
+			Console.Out.Write (EscSeqUtils.CSI_SetCursorStyle ((EscSeqUtils.DECSCUSR_Style)((int)visibility >> 24 & 0xFF)));
 		}
 
 		_currentCursorVisibility = visibility;
@@ -704,17 +701,14 @@ internal class CursesDriver : ConsoleDriver {
 	}
 
 	/// <inheritdoc/>
-	public override bool EnsureCursorVisibility ()
-	{
-		return false;
-	}
+	public override bool EnsureCursorVisibility () => false;
 
 	public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
 	{
 		KeyCode key;
 
 		if (consoleKey == ConsoleKey.Packet) {
-			ConsoleModifiers mod = new ConsoleModifiers ();
+			var mod = new ConsoleModifiers ();
 			if (shift) {
 				mod |= ConsoleModifiers.Shift;
 			}
@@ -724,11 +718,9 @@ internal class CursesDriver : ConsoleDriver {
 			if (control) {
 				mod |= ConsoleModifiers.Control;
 			}
-			var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyChar, mod, out _);
-			key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)cKeyInfo.Key, out bool mappable);
-			if (mappable) {
-				key = (KeyCode)cKeyInfo.KeyChar;
-			}
+			var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+			cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
+			key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo);
 		} else {
 			key = (KeyCode)keyChar;
 		}
@@ -737,16 +729,14 @@ internal class CursesDriver : ConsoleDriver {
 		OnKeyUp (new Key (key));
 		//OnKeyPressed (new KeyEventArgsEventArgs (key));
 	}
-
-
 }
 
-internal static class Platform {
+static class Platform {
 	[DllImport ("libc")]
-	static extern int uname (IntPtr buf);
+	extern static int uname (IntPtr buf);
 
 	[DllImport ("libc")]
-	static extern int killpg (int pgrp, int pid);
+	extern static int killpg (int pgrp, int pid);
 
 	static int _suspendSignal;
 
@@ -793,7 +783,7 @@ internal static class Platform {
 	/// Suspends the process by sending SIGTSTP to itself
 	/// </summary>
 	/// <returns>The suspend.</returns>
-	static public bool Suspend ()
+	public static bool Suspend ()
 	{
 		int signal = GetSuspendSignal ();
 		if (signal == -1) {

+ 19 - 5
Terminal.Gui/ConsoleDrivers/EscSeqUtils/EscSeqUtils.cs

@@ -170,10 +170,10 @@ public static class EscSeqUtils {
 	/// <summary>
 	/// ESC [ y ; x H - CUP Cursor Position - Cursor moves to x ; y coordinate within the viewport, where x is the column of the y line
 	/// </summary>
-	/// <param name="x"></param>
-	/// <param name="y"></param>
+	/// <param name="row">Origin is (1,1).</param>
+	/// <param name="col">Origin is (1,1).</param>
 	/// <returns></returns>
-	public static string CSI_SetCursorPosition (int y, int x) => $"{CSI}{y};{x}H";
+	public static string CSI_SetCursorPosition (int row, int col) => $"{CSI}{row};{col}H";
 
 
 	//ESC [ <y> ; <x> f - HVP     Horizontal Vertical Position* Cursor moves to<x>; <y> coordinate within the viewport, where <x> is the column of the<y> line
@@ -248,15 +248,29 @@ public static class EscSeqUtils {
 	/// </summary>
 	public static string CSI_SetGraphicsRendition (params int [] parameters) => $"{CSI}{string.Join (";", parameters)}m";
 
+	/// <summary>
+	/// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])" /> to set the foreground color.
+	/// </summary>
+	/// <param name="code">One of the 16 color codes.</param>
+	/// <returns></returns>
+	public static string CSI_SetForegroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code);
+
+	/// <summary>
+	/// ESC [ (n) m - Uses <see cref="CSI_SetGraphicsRendition(int[])" /> to set the background color.
+	/// </summary>
+	/// <param name="code">One of the 16 color codes.</param>
+	/// <returns></returns>
+	public static string CSI_SetBackgroundColor (AnsiColorCode code) => CSI_SetGraphicsRendition ((int)code+10);
+
 	/// <summary>
 	/// ESC[38;5;{id}m - Set foreground color (256 colors)
 	/// </summary>
-	public static string CSI_SetForegroundColor (int id) => $"{CSI}38;5;{id}m";
+	public static string CSI_SetForegroundColor256 (int color) => $"{CSI}38;5;{color}m";
 
 	/// <summary>
 	/// ESC[48;5;{id}m - Set background color (256 colors)
 	/// </summary>
-	public static string CSI_SetBackgroundColor (int id) => $"{CSI}48;5;{id}m";
+	public static string CSI_SetBackgroundColor256 (int color) => $"{CSI}48;5;{color}m";
 
 	/// <summary>
 	/// ESC[38;2;{r};{g};{b}m	Set foreground color as RGB.

+ 1 - 1
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -800,7 +800,7 @@ public static class FakeConsole {
 	{
 		MockKeyPresses.Push (new ConsoleKeyInfo (
 			(char)(key & ~KeyCode.CtrlMask & ~KeyCode.ShiftMask & ~KeyCode.AltMask),
-			ConsoleKeyMapping.GetConsoleKeyFromKey (key).Key,
+			ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key).Key,
 			key.HasFlag (KeyCode.ShiftMask),
 			key.HasFlag (KeyCode.AltMask),
 			key.HasFlag (KeyCode.CtrlMask)));

+ 22 - 22
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -205,39 +205,39 @@ public class FakeDriver : ConsoleDriver {
 	{
 		switch (keyInfo.Key) {
 		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Esc);
 		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Tab);
 		case ConsoleKey.Clear:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Clear);
 		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Home);
 		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.End);
 		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorLeft);
 		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorRight);
 		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorUp);
 		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.CursorDown);
 		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageUp);
 		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PageDown);
 		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Enter);
 		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
 		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Backspace);
 		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Delete);
 		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.Insert);
 		case ConsoleKey.PrintScreen:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode.PrintScreen);
 
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
@@ -256,25 +256,25 @@ public class FakeDriver : ConsoleDriver {
 				return KeyCode.Null;
 			}
 
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+			return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
 		}
 
 		var key = keyInfo.Key;
 		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
 			var delta = key - ConsoleKey.A;
 			if (keyInfo.KeyChar != (uint)key) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)keyInfo.KeyChar);
+				return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)keyInfo.KeyChar);
 			}
 			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)
 			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)
 			|| keyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
+				return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)KeyCode.A + delta));
 			}
 			var alphaBase = ((keyInfo.Modifiers != ConsoleModifiers.Shift)) ? 'A' : 'a';
 			return (KeyCode)((uint)alphaBase + delta);
 		}
 
-		return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+		return ConsoleKeyMapping.MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.KeyChar));
 	}
 
 	private CursorVisibility _savedCursorVisibility;
@@ -282,7 +282,7 @@ public class FakeDriver : ConsoleDriver {
 	void MockKeyPressedHandler (ConsoleKeyInfo consoleKeyInfo)
 	{
 		if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-			consoleKeyInfo = ConsoleKeyMapping.FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+			consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 		}
 
 		var map = MapKey (consoleKeyInfo);

+ 296 - 322
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -7,13 +7,15 @@ using System.Diagnostics;
 using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
+using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using System.Text;
-using Terminal.Gui.ConsoleDrivers;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 using static Terminal.Gui.NetEvents;
+using static Terminal.Gui.WindowsConsole;
 
 namespace Terminal.Gui;
+
 class NetWinVTConsole {
 	IntPtr _inputHandle, _outputHandle, _errorHandle;
 	uint _originalInputConsoleMode, _originalOutputConsoleMode, _originalErrorConsoleMode;
@@ -49,7 +51,7 @@ class NetWinVTConsole {
 			throw new ApplicationException ($"Failed to get error console mode, error code: {GetLastError ()}.");
 		}
 		_originalErrorConsoleMode = mode;
-		if ((mode & (DISABLE_NEWLINE_AUTO_RETURN)) < DISABLE_NEWLINE_AUTO_RETURN) {
+		if ((mode & DISABLE_NEWLINE_AUTO_RETURN) < DISABLE_NEWLINE_AUTO_RETURN) {
 			mode |= DISABLE_NEWLINE_AUTO_RETURN;
 			if (!SetConsoleMode (_errorHandle, mode)) {
 				throw new ApplicationException ($"Failed to set error console mode, error code: {GetLastError ()}.");
@@ -93,28 +95,28 @@ class NetWinVTConsole {
 	const uint ENABLE_LVB_GRID_WORLDWIDE = 10;
 
 	[DllImport ("kernel32.dll", SetLastError = true)]
-	static extern IntPtr GetStdHandle (int nStdHandle);
+	extern static IntPtr GetStdHandle (int nStdHandle);
 
 	[DllImport ("kernel32.dll")]
-	static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
+	extern static bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
 
 	[DllImport ("kernel32.dll")]
-	static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
+	extern static bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
 
 	[DllImport ("kernel32.dll")]
-	static extern uint GetLastError ();
+	extern static uint GetLastError ();
 }
 
-internal class NetEvents : IDisposable {
-	readonly ManualResetEventSlim _inputReady = new ManualResetEventSlim (false);
+class NetEvents : IDisposable {
+	readonly ManualResetEventSlim _inputReady = new (false);
 	CancellationTokenSource _inputReadyCancellationTokenSource;
 
-	readonly ManualResetEventSlim _waitForStart = new ManualResetEventSlim (false);
+	readonly ManualResetEventSlim _waitForStart = new (false);
 	//CancellationTokenSource _waitForStartCancellationTokenSource;
 
-	readonly ManualResetEventSlim _winChange = new ManualResetEventSlim (false);
+	readonly ManualResetEventSlim _winChange = new (false);
 
-	readonly Queue<InputResult?> _inputQueue = new Queue<InputResult?> ();
+	readonly Queue<InputResult?> _inputQueue = new ();
 
 	readonly ConsoleDriver _consoleDriver;
 	ConsoleKeyInfo [] _cki;
@@ -123,7 +125,7 @@ internal class NetEvents : IDisposable {
 #if PROCESS_REQUEST
 		bool _neededProcessRequest;
 #endif
-	public EscSeqRequests EscSeqRequests { get; } = new EscSeqRequests ();
+	public EscSeqRequests EscSeqRequests { get; } = new ();
 
 	public NetEvents (ConsoleDriver consoleDriver)
 	{
@@ -209,17 +211,19 @@ internal class NetEvents : IDisposable {
 					} catch (OperationCanceledException) {
 						return;
 					}
-					if ((consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq)
-						|| (consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq)) {
+					if (consoleKeyInfo.KeyChar == (char)KeyCode.Esc && !_isEscSeq
+					|| consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
 
 						if (_cki == null && consoleKeyInfo.KeyChar != (char)KeyCode.Esc && _isEscSeq) {
 							_cki = EscSeqUtils.ResizeArray (new ConsoleKeyInfo ((char)KeyCode.Esc, 0,
-							    false, false, false), _cki);
+								false, false, false), _cki);
 						}
 						_isEscSeq = true;
 						newConsoleKeyInfo = consoleKeyInfo;
 						_cki = EscSeqUtils.ResizeArray (consoleKeyInfo, _cki);
-						if (Console.KeyAvailable) continue;
+						if (Console.KeyAvailable) {
+							continue;
+						}
 						ProcessRequestResponse (ref newConsoleKeyInfo, ref key, _cki, ref mod);
 						_cki = null;
 						_isEscSeq = false;
@@ -270,10 +274,10 @@ internal class NetEvents : IDisposable {
 					buffWidth = _consoleDriver.Cols;
 				}
 				if (EnqueueWindowSizeEvent (
-				    Math.Max (Console.WindowHeight, 0),
-				    Math.Max (Console.WindowWidth, 0),
-				    buffHeight,
-				    buffWidth)) {
+					Math.Max (Console.WindowHeight, 0),
+					Math.Max (Console.WindowWidth, 0),
+					buffHeight,
+					buffWidth)) {
 
 					return;
 				}
@@ -306,9 +310,11 @@ internal class NetEvents : IDisposable {
 	/// <returns></returns>
 	bool EnqueueWindowSizeEvent (int winHeight, int winWidth, int buffHeight, int buffWidth)
 	{
-		if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) return false;
-		var w = Math.Max (winWidth, 0);
-		var h = Math.Max (winHeight, 0);
+		if (winWidth == _consoleDriver.Cols && winHeight == _consoleDriver.Rows) {
+			return false;
+		}
+		int w = Math.Max (winWidth, 0);
+		int h = Math.Max (winHeight, 0);
 		_inputQueue.Enqueue (new InputResult () {
 			EventType = EventType.WindowSize,
 			WindowSizeEvent = new WindowSizeEvent () {
@@ -323,10 +329,10 @@ internal class NetEvents : IDisposable {
 	{
 		// isMouse is true if it's CSI<, false otherwise
 		EscSeqUtils.DecodeEscSeq (EscSeqRequests, ref newConsoleKeyInfo, ref key, cki, ref mod,
-		    out var c1Control, out var code, out var values, out var terminating,
-		    out var isMouse, out var mouseFlags,
-		    out var pos, out var isReq,
-		    (f, p) => HandleMouseEvent (MapMouseFlags (f), p));
+			out string c1Control, out string code, out string [] values, out string terminating,
+			out bool isMouse, out var mouseFlags,
+			out var pos, out bool isReq,
+			(f, p) => HandleMouseEvent (MapMouseFlags (f), p));
 
 		if (isMouse) {
 			foreach (var mf in mouseFlags) {
@@ -343,7 +349,7 @@ internal class NetEvents : IDisposable {
 	MouseButtonState MapMouseFlags (MouseFlags mouseFlags)
 	{
 		MouseButtonState mbs = default;
-		foreach (var flag in Enum.GetValues (mouseFlags.GetType ())) {
+		foreach (object flag in Enum.GetValues (mouseFlags.GetType ())) {
 			if (mouseFlags.HasFlag ((MouseFlags)flag)) {
 				switch (flag) {
 				case MouseFlags.Button1Pressed:
@@ -446,7 +452,7 @@ internal class NetEvents : IDisposable {
 		switch (terminating) {
 		// BUGBUG: I can't find where we send a request for cursor position (ESC[?6n), so I'm not sure if this is needed.
 		case EscSeqUtils.CSI_RequestCursorPositionReport_Terminator:
-			Point point = new Point {
+			var point = new Point {
 				X = int.Parse (values [1]) - 1,
 				Y = int.Parse (values [0]) - 1
 			};
@@ -469,10 +475,10 @@ internal class NetEvents : IDisposable {
 			switch (values [0]) {
 			case EscSeqUtils.CSI_ReportTerminalSizeInChars_ResponseValue:
 				EnqueueWindowSizeEvent (
-				    Math.Max (int.Parse (values [1]), 0),
-				    Math.Max (int.Parse (values [2]), 0),
-				    Math.Max (int.Parse (values [1]), 0),
-				    Math.Max (int.Parse (values [2]), 0));
+					Math.Max (int.Parse (values [1]), 0),
+					Math.Max (int.Parse (values [2]), 0),
+					Math.Max (int.Parse (values [1]), 0),
+					Math.Max (int.Parse (values [2]), 0));
 				break;
 			default:
 				EnqueueRequestResponseEvent (c1Control, code, values, terminating);
@@ -489,7 +495,7 @@ internal class NetEvents : IDisposable {
 
 	void EnqueueRequestResponseEvent (string c1Control, string code, string [] values, string terminating)
 	{
-		EventType eventType = EventType.RequestResponse;
+		var eventType = EventType.RequestResponse;
 		var requestRespEv = new RequestResponseEvent () {
 			ResultTuple = (c1Control, code, values, terminating)
 		};
@@ -501,9 +507,9 @@ internal class NetEvents : IDisposable {
 
 	void HandleMouseEvent (MouseButtonState buttonState, Point pos)
 	{
-		MouseEvent mouseEvent = new MouseEvent () {
+		var mouseEvent = new MouseEvent () {
 			Position = pos,
-			ButtonState = buttonState,
+			ButtonState = buttonState
 		};
 
 		_inputQueue.Enqueue (new InputResult () {
@@ -581,11 +587,40 @@ internal class NetEvents : IDisposable {
 		public WindowSizeEvent WindowSizeEvent;
 		public WindowPositionEvent WindowPositionEvent;
 		public RequestResponseEvent RequestResponseEvent;
+
+		public override readonly string ToString ()
+		{
+			return EventType switch {
+				EventType.Key => ToString (ConsoleKeyInfo),
+				EventType.Mouse => MouseEvent.ToString (),
+				//EventType.WindowSize => WindowSize.ToString (),
+				//EventType.RequestResponse => RequestResponse.ToString (),
+				_ => "Unknown event type: " + EventType
+			};
+		}
+
+		/// <summary>
+		/// Prints a ConsoleKeyInfoEx structure
+		/// </summary>
+		/// <param name="cki"></param>
+		/// <returns></returns>
+		public readonly string ToString (ConsoleKeyInfo cki)
+		{
+			var ke = new Key ((KeyCode)cki.KeyChar);
+			var sb = new StringBuilder ();
+			sb.Append ($"Key: {(KeyCode)cki.Key} ({cki.Key})");
+			sb.Append ((cki.Modifiers & ConsoleModifiers.Shift) != 0 ? " | Shift" : string.Empty);
+			sb.Append ((cki.Modifiers & ConsoleModifiers.Control) != 0 ? " | Control" : string.Empty);
+			sb.Append ((cki.Modifiers & ConsoleModifiers.Alt) != 0 ? " | Alt" : string.Empty);
+			sb.Append ($", KeyChar: {ke.AsRune.MakePrintable ()} ({(uint)cki.KeyChar}) ");
+			var s = sb.ToString ().TrimEnd (',').TrimEnd (' ');
+			return $"[ConsoleKeyInfo({s})]";
+		}
 	}
 
 	void HandleKeyboardEvent (ConsoleKeyInfo cki)
 	{
-		InputResult inputResult = new InputResult {
+		var inputResult = new InputResult {
 			EventType = EventType.Key,
 			ConsoleKeyInfo = cki
 		};
@@ -611,7 +646,7 @@ internal class NetEvents : IDisposable {
 	}
 }
 
-internal class NetDriver : ConsoleDriver {
+class NetDriver : ConsoleDriver {
 	const int COLOR_BLACK = 30;
 	const int COLOR_RED = 31;
 	const int COLOR_GREEN = 32;
@@ -631,9 +666,10 @@ internal class NetDriver : ConsoleDriver {
 
 	NetMainLoop _mainLoopDriver = null;
 
-	public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || (IsWinPlatform && Environment.OSVersion.Version.Build >= 14931);
+	public override bool SupportsTrueColor => Environment.OSVersion.Platform == PlatformID.Unix || IsWinPlatform && Environment.OSVersion.Version.Build >= 14931;
 
 	public NetWinVTConsole NetWinConsole { get; private set; }
+
 	public bool IsWinPlatform { get; private set; }
 
 	internal override MainLoop Init ()
@@ -709,6 +745,21 @@ internal class NetDriver : ConsoleDriver {
 		}
 	}
 
+
+	#region Size and Position Handling
+	volatile bool _winSizeChanging;
+
+	void SetWindowPosition (int col, int row)
+	{
+		if (!RunningUnitTests) {
+			Top = Console.WindowTop;
+			Left = Console.WindowLeft;
+		} else {
+			Top = row;
+			Left = col;
+		}
+	}
+
 	public virtual void ResizeScreen ()
 	{
 		// Not supported on Unix.
@@ -727,7 +778,7 @@ internal class NetDriver : ConsoleDriver {
 					Console.SetBufferSize (Cols, Rows);
 				}
 #pragma warning restore CA1416
-			} catch (System.IO.IOException) {
+			} catch (IOException) {
 				Clip = new Rect (0, 0, Cols, Rows);
 			} catch (ArgumentOutOfRangeException) {
 				Clip = new Rect (0, 0, Cols, Rows);
@@ -739,6 +790,7 @@ internal class NetDriver : ConsoleDriver {
 
 		Clip = new Rect (0, 0, Cols, Rows);
 	}
+	#endregion
 
 	public override void Refresh ()
 	{
@@ -752,18 +804,18 @@ internal class NetDriver : ConsoleDriver {
 			return;
 		}
 
-		var top = 0;
-		var left = 0;
-		var rows = Rows;
-		var cols = Cols;
-		System.Text.StringBuilder output = new System.Text.StringBuilder ();
-		Attribute redrawAttr = new Attribute ();
-		var lastCol = -1;
+		int top = 0;
+		int left = 0;
+		int rows = Rows;
+		int cols = Cols;
+		var output = new StringBuilder ();
+		var redrawAttr = new Attribute ();
+		int lastCol = -1;
 
 		var savedVisibitity = _cachedCursorVisibility;
 		SetCursorVisibility (CursorVisibility.Invisible);
 
-		for (var row = top; row < rows; row++) {
+		for (int row = top; row < rows; row++) {
 			if (Console.WindowHeight < 1) {
 				return;
 			}
@@ -775,9 +827,9 @@ internal class NetDriver : ConsoleDriver {
 			}
 			_dirtyLines [row] = false;
 			output.Clear ();
-			for (var col = left; col < cols; col++) {
+			for (int col = left; col < cols; col++) {
 				lastCol = -1;
-				var outputWidth = 0;
+				int outputWidth = 0;
 				for (; col < cols; col++) {
 					if (!Contents [row, col].IsDirty) {
 						if (output.Length > 0) {
@@ -785,9 +837,8 @@ internal class NetDriver : ConsoleDriver {
 						} else if (lastCol == -1) {
 							lastCol = col;
 						}
-						if (lastCol + 1 < cols) {
+						if (lastCol + 1 < cols)
 							lastCol++;
-						}
 						continue;
 					}
 
@@ -795,7 +846,7 @@ internal class NetDriver : ConsoleDriver {
 						lastCol = col;
 					}
 
-					Attribute attr = Contents [row, col].Attribute.Value;
+					var attr = Contents [row, col].Attribute.Value;
 					// Performance: Only send the escape sequence if the attribute has changed.
 					if (attr != redrawAttr) {
 						redrawAttr = attr;
@@ -823,7 +874,7 @@ internal class NetDriver : ConsoleDriver {
 						//	output.Append (combMark);
 						//}
 						// WriteToConsole (output, ref lastCol, row, ref outputWidth);
-					} else if ((rune.IsSurrogatePair () && rune.GetColumns () < 2)) {
+					} else if (rune.IsSurrogatePair () && rune.GetColumns () < 2) {
 						WriteToConsole (output, ref lastCol, row, ref outputWidth);
 						SetCursorPosition (col - 1, row);
 					}
@@ -850,37 +901,33 @@ internal class NetDriver : ConsoleDriver {
 	}
 
 	#region Color Handling
-
 	// Cache the list of ConsoleColor values.
-	private static readonly HashSet<int> ConsoleColorValues = new HashSet<int> (
-	    Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
+	static readonly HashSet<int> ConsoleColorValues = new (
+		Enum.GetValues (typeof (ConsoleColor)).OfType<ConsoleColor> ().Select (c => (int)c)
 	);
 
 	// Dictionary for mapping ConsoleColor values to the values used by System.Net.Console.
-	private static Dictionary<ConsoleColor, int> colorMap = new Dictionary<ConsoleColor, int> {
-	{ ConsoleColor.Black, COLOR_BLACK },
-	{ ConsoleColor.DarkBlue, COLOR_BLUE },
-	{ ConsoleColor.DarkGreen, COLOR_GREEN },
-	{ ConsoleColor.DarkCyan, COLOR_CYAN },
-	{ ConsoleColor.DarkRed, COLOR_RED },
-	{ ConsoleColor.DarkMagenta, COLOR_MAGENTA },
-	{ ConsoleColor.DarkYellow, COLOR_YELLOW },
-	{ ConsoleColor.Gray, COLOR_WHITE },
-	{ ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
-	{ ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
-	{ ConsoleColor.Green, COLOR_BRIGHT_GREEN },
-	{ ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
-	{ ConsoleColor.Red, COLOR_BRIGHT_RED },
-	{ ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
-	{ ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
-	{ ConsoleColor.White, COLOR_BRIGHT_WHITE }
-    };
+	static Dictionary<ConsoleColor, int> colorMap = new () {
+		{ ConsoleColor.Black, COLOR_BLACK },
+		{ ConsoleColor.DarkBlue, COLOR_BLUE },
+		{ ConsoleColor.DarkGreen, COLOR_GREEN },
+		{ ConsoleColor.DarkCyan, COLOR_CYAN },
+		{ ConsoleColor.DarkRed, COLOR_RED },
+		{ ConsoleColor.DarkMagenta, COLOR_MAGENTA },
+		{ ConsoleColor.DarkYellow, COLOR_YELLOW },
+		{ ConsoleColor.Gray, COLOR_WHITE },
+		{ ConsoleColor.DarkGray, COLOR_BRIGHT_BLACK },
+		{ ConsoleColor.Blue, COLOR_BRIGHT_BLUE },
+		{ ConsoleColor.Green, COLOR_BRIGHT_GREEN },
+		{ ConsoleColor.Cyan, COLOR_BRIGHT_CYAN },
+		{ ConsoleColor.Red, COLOR_BRIGHT_RED },
+		{ ConsoleColor.Magenta, COLOR_BRIGHT_MAGENTA },
+		{ ConsoleColor.Yellow, COLOR_BRIGHT_YELLOW },
+		{ ConsoleColor.White, COLOR_BRIGHT_WHITE }
+	};
 
 	// Map a ConsoleColor to a platform dependent value.
-	int MapColors (ConsoleColor color, bool isForeground = true)
-	{
-		return colorMap.TryGetValue (color, out var colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
-	}
+	int MapColors (ConsoleColor color, bool isForeground = true) => colorMap.TryGetValue (color, out int colorValue) ? colorValue + (isForeground ? 0 : 10) : 0;
 
 	///// <remarks>
 	///// In the NetDriver, colors are encoded as an int. 
@@ -938,7 +985,7 @@ internal class NetDriver : ConsoleDriver {
 	public override bool SetCursorVisibility (CursorVisibility visibility)
 	{
 		_cachedCursorVisibility = visibility;
-		var isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default;
+		bool isVisible = RunningUnitTests ? visibility == CursorVisibility.Default : Console.CursorVisible = visibility == CursorVisibility.Default;
 		Console.Out.Write (isVisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor);
 		return isVisible;
 	}
@@ -946,7 +993,7 @@ internal class NetDriver : ConsoleDriver {
 	public override bool EnsureCursorVisibility ()
 	{
 		if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) {
-			GetCursorVisibility (out CursorVisibility cursorVisibility);
+			GetCursorVisibility (out var cursorVisibility);
 			_cachedCursorVisibility = cursorVisibility;
 			SetCursorVisibility (CursorVisibility.Invisible);
 			return false;
@@ -957,22 +1004,7 @@ internal class NetDriver : ConsoleDriver {
 	}
 	#endregion
 
-	#region Size and Position Handling
-
-	void SetWindowPosition (int col, int row)
-	{
-		if (!RunningUnitTests) {
-			Top = Console.WindowTop;
-			Left = Console.WindowLeft;
-		} else {
-			Top = row;
-			Left = col;
-		}
-	}
-
-	#endregion
-
-
+	#region Mouse Handling
 	public void StartReportingMouseMoves ()
 	{
 		if (!RunningUnitTests) {
@@ -987,6 +1019,106 @@ internal class NetDriver : ConsoleDriver {
 		}
 	}
 
+	MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
+	{
+		//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+
+		MouseFlags mouseFlag = 0;
+
+		if ((me.ButtonState & MouseButtonState.Button1Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button1Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1Released) != 0) {
+			mouseFlag |= MouseFlags.Button1Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button1Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button1DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button1TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button1TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button2Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2Released) != 0) {
+			mouseFlag |= MouseFlags.Button2Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button2Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button2DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button2TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button2TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button3Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3Released) != 0) {
+			mouseFlag |= MouseFlags.Button3Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button3Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button3DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button3TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button3TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledUp) != 0) {
+			mouseFlag |= MouseFlags.WheeledUp;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledDown) != 0) {
+			mouseFlag |= MouseFlags.WheeledDown;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledLeft) != 0) {
+			mouseFlag |= MouseFlags.WheeledLeft;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonWheeledRight) != 0) {
+			mouseFlag |= MouseFlags.WheeledRight;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4Pressed) != 0) {
+			mouseFlag |= MouseFlags.Button4Pressed;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4Released) != 0) {
+			mouseFlag |= MouseFlags.Button4Released;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4Clicked) != 0) {
+			mouseFlag |= MouseFlags.Button4Clicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4DoubleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button4DoubleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.Button4TripleClicked) != 0) {
+			mouseFlag |= MouseFlags.Button4TripleClicked;
+		}
+		if ((me.ButtonState & MouseButtonState.ReportMousePosition) != 0) {
+			mouseFlag |= MouseFlags.ReportMousePosition;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonShift) != 0) {
+			mouseFlag |= MouseFlags.ButtonShift;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonCtrl) != 0) {
+			mouseFlag |= MouseFlags.ButtonCtrl;
+		}
+		if ((me.ButtonState & MouseButtonState.ButtonAlt) != 0) {
+			mouseFlag |= MouseFlags.ButtonAlt;
+		}
+
+		return new MouseEvent () {
+			X = me.Position.X,
+			Y = me.Position.Y,
+			Flags = mouseFlag
+		};
+	}
+	#endregion Mouse Handling
+
+	#region Keyboard Handling
 	ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
 	{
 		if (consoleKeyInfo.Key != ConsoleKey.Packet) {
@@ -994,11 +1126,11 @@ internal class NetDriver : ConsoleDriver {
 		}
 
 		var mod = consoleKeyInfo.Modifiers;
-		var shift = (mod & ConsoleModifiers.Shift) != 0;
-		var alt = (mod & ConsoleModifiers.Alt) != 0;
-		var control = (mod & ConsoleModifiers.Control) != 0;
+		bool shift = (mod & ConsoleModifiers.Shift) != 0;
+		bool alt = (mod & ConsoleModifiers.Alt) != 0;
+		bool control = (mod & ConsoleModifiers.Control) != 0;
 
-		var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out _);
+		var cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (consoleKeyInfo);
 
 		return new ConsoleKeyInfo (cKeyInfo.KeyChar, cKeyInfo.Key, shift, alt, control);
 	}
@@ -1006,37 +1138,11 @@ internal class NetDriver : ConsoleDriver {
 	KeyCode MapKey (ConsoleKeyInfo keyInfo)
 	{
 		switch (keyInfo.Key) {
-		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
-		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
-		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
-		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
-		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
-		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
-		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
-		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
-		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
-		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
-		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
-		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
-		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
-		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
-		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
-
+		case ConsoleKey.OemPeriod:
+		case ConsoleKey.OemComma:
+		case ConsoleKey.OemPlus:
+		case ConsoleKey.OemMinus:
+		case ConsoleKey.Packet:
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
 		case ConsoleKey.Oem3:
@@ -1046,99 +1152,74 @@ internal class NetDriver : ConsoleDriver {
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem102:
-			var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
-			if (ret.HasFlag (KeyCode.ShiftMask)) {
-				ret &= ~KeyCode.ShiftMask;
-			}
-			return ret;
+			if (keyInfo.KeyChar == 0) {
+				// If the keyChar is 0, keyInfo.Key value is not a printable character. 
 
-		case ConsoleKey.OemPeriod:
-		case ConsoleKey.OemComma:
-		case ConsoleKey.OemPlus:
-		case ConsoleKey.OemMinus:
-			return (KeyCode)((uint)keyInfo.KeyChar);
+				return KeyCode.Null;// MapToKeyCodeModifiers (keyInfo.Modifiers, KeyCode)keyInfo.Key);
+			} else {
+				if (keyInfo.Modifiers != ConsoleModifiers.Shift) {
+					// If Shift wasn't down we don't need to do anything but return the keyInfo.KeyChar
+					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
+				}
+
+				// Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
+				// and passing on Shift would be redundant.
+				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
+			}
 		}
 
 		var key = keyInfo.Key;
-		if (key is >= ConsoleKey.A and <= ConsoleKey.Z) {
-			var delta = key - ConsoleKey.A;
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-				}
+		// A..Z are special cased:
+		// - Alone, they represent lowercase a...z
+		// - With ShiftMask they are A..Z
+		// - If CapsLock is on the above is reversed.
+		// - If Alt and/or Ctrl are present, treat as upper case
+		if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) {
+			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
 			}
 
-			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) /*^ (keyInfoEx.CapsLock)*/)) {
-				if (keyInfo.KeyChar <= (uint)KeyCode.Z) {
-					return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask;
+			if (keyInfo.Modifiers == ConsoleModifiers.Shift) {
+				// If ShiftMask is on  add the ShiftMask
+				if (char.IsUpper (keyInfo.KeyChar)) {
+					return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask;
 				}
 			}
-			// This is buggy because is converting a lower case to a upper case and mustn't
-			//if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == KeyCode.Space) {
-			//	return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
-			//}
 			return (KeyCode)(uint)keyInfo.KeyChar;
 		}
-		if (key is >= ConsoleKey.D0 and <= ConsoleKey.D9) {
-			var delta = key - ConsoleKey.D0;
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
-				}
-			}
-			return (KeyCode)((uint)keyInfo.KeyChar);
-		}
-		if (key is >= ConsoleKey.F1 and <= ConsoleKey.F12) {
-			var delta = key - ConsoleKey.F1;
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta));
-			}
 
-			return (KeyCode)((uint)KeyCode.F1 + delta);
+		// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+		if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) {
+			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.Key));
 		}
 
-		// Is it a key between a..z?
-		if ((char)keyInfo.KeyChar is >= 'a' and <= 'z') {
-			// 'a' should be Key.A
-			return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+		// Handle control keys (e.g. CursorUp)
+		if (keyInfo.Key != ConsoleKey.None && Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) {
+			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
 		}
 
-		// Is it a key between A..Z?
-		if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) is >= KeyCode.A and <= KeyCode.Z) {
-			// It's Key.A...Z.  Make it Key.A | Key.ShiftMask
-			return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space | KeyCode.ShiftMask;
-		}
 
 		return (KeyCode)(uint)keyInfo.KeyChar;
 	}
+	#endregion Keyboard Handling
 
-	volatile bool _winSizeChanging;
-
-	void ProcessInput (NetEvents.InputResult inputEvent)
+	void ProcessInput (InputResult inputEvent)
 	{
 		switch (inputEvent.EventType) {
 		case NetEvents.EventType.Key:
-			ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
-			if (consoleKeyInfo.Key == ConsoleKey.Packet) {
-				consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
-			}
+			var consoleKeyInfo = inputEvent.ConsoleKeyInfo;
+			//if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+			//	consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+			//}
+
+			//Debug.WriteLine ($"event: {inputEvent}");
+
 			var map = MapKey (consoleKeyInfo);
 
+			if (map == KeyCode.Null) {
+				break;
+			}
+
 			OnKeyDown (new Key (map));
 			OnKeyUp (new Key (map));
 			break;
@@ -1150,16 +1231,14 @@ internal class NetDriver : ConsoleDriver {
 			Top = 0;
 			Left = 0;
 			Cols = inputEvent.WindowSizeEvent.Size.Width;
-			Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0); ;
+			Rows = Math.Max (inputEvent.WindowSizeEvent.Size.Height, 0);
+			;
 			ResizeScreen ();
 			ClearContents ();
 			_winSizeChanging = false;
 			OnSizeChanged (new SizeChangedEventArgs (new Size (Cols, Rows)));
 			break;
 		case NetEvents.EventType.RequestResponse:
-			// BUGBUG: What is this for? It does not seem to be used anywhere. 
-			// It is also not clear what it does. View.Data is documented as "This property is not used internally"
-			Application.Top.Data = inputEvent.RequestResponseEvent.ResultTuple;
 			break;
 		case NetEvents.EventType.WindowPosition:
 			break;
@@ -1168,107 +1247,9 @@ internal class NetDriver : ConsoleDriver {
 		}
 	}
 
-	MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
-	{
-		//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
-
-		MouseFlags mouseFlag = 0;
-
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button1Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Released) != 0) {
-			mouseFlag |= MouseFlags.Button1Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button1Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button1DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button1TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button1TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button2Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Released) != 0) {
-			mouseFlag |= MouseFlags.Button2Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button2Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button2DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button2TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button2TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button3Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Released) != 0) {
-			mouseFlag |= MouseFlags.Button3Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button3Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button3DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button3TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button3TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledUp) != 0) {
-			mouseFlag |= MouseFlags.WheeledUp;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledDown) != 0) {
-			mouseFlag |= MouseFlags.WheeledDown;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledLeft) != 0) {
-			mouseFlag |= MouseFlags.WheeledLeft;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonWheeledRight) != 0) {
-			mouseFlag |= MouseFlags.WheeledRight;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Pressed) != 0) {
-			mouseFlag |= MouseFlags.Button4Pressed;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Released) != 0) {
-			mouseFlag |= MouseFlags.Button4Released;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4Clicked) != 0) {
-			mouseFlag |= MouseFlags.Button4Clicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4DoubleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button4DoubleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.Button4TripleClicked) != 0) {
-			mouseFlag |= MouseFlags.Button4TripleClicked;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ReportMousePosition) != 0) {
-			mouseFlag |= MouseFlags.ReportMousePosition;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonShift) != 0) {
-			mouseFlag |= MouseFlags.ButtonShift;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonCtrl) != 0) {
-			mouseFlag |= MouseFlags.ButtonCtrl;
-		}
-		if ((me.ButtonState & NetEvents.MouseButtonState.ButtonAlt) != 0) {
-			mouseFlag |= MouseFlags.ButtonAlt;
-		}
-
-		return new MouseEvent () {
-			X = me.Position.X,
-			Y = me.Position.Y,
-			Flags = mouseFlag
-		};
-	}
-
 	public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
 	{
-		NetEvents.InputResult input = new NetEvents.InputResult {
+		var input = new InputResult {
 			EventType = NetEvents.EventType.Key,
 			ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control)
 		};
@@ -1280,12 +1261,8 @@ internal class NetDriver : ConsoleDriver {
 
 
 	#region Not Implemented
-	public override void Suspend ()
-	{
-		throw new NotImplementedException ();
-	}
+	public override void Suspend () => throw new NotImplementedException ();
 	#endregion
-
 }
 
 /// <summary>
@@ -1296,19 +1273,19 @@ internal class NetDriver : ConsoleDriver {
 /// <remarks>
 /// This implementation is used for NetDriver.
 /// </remarks>
-internal class NetMainLoop : IMainLoopDriver {
-	readonly ManualResetEventSlim _eventReady = new ManualResetEventSlim (false);
-	readonly ManualResetEventSlim _waitForProbe = new ManualResetEventSlim (false);
-	readonly Queue<NetEvents.InputResult?> _resultQueue = new Queue<NetEvents.InputResult?> ();
+class NetMainLoop : IMainLoopDriver {
+	readonly ManualResetEventSlim _eventReady = new (false);
+	readonly ManualResetEventSlim _waitForProbe = new (false);
+	readonly Queue<InputResult?> _resultQueue = new ();
 	MainLoop _mainLoop;
-	CancellationTokenSource _eventReadyTokenSource = new CancellationTokenSource ();
-	readonly CancellationTokenSource _inputHandlerTokenSource = new CancellationTokenSource ();
+	CancellationTokenSource _eventReadyTokenSource = new ();
+	readonly CancellationTokenSource _inputHandlerTokenSource = new ();
 	internal NetEvents _netEvents;
 
 	/// <summary>
 	/// Invoked when a Key is pressed.
 	/// </summary>
-	internal Action<NetEvents.InputResult> ProcessInput;
+	internal Action<InputResult> ProcessInput;
 
 	/// <summary>
 	/// Initializes the class with the console driver.
@@ -1367,16 +1344,13 @@ internal class NetMainLoop : IMainLoopDriver {
 		Task.Run (NetInputHandler, _inputHandlerTokenSource.Token);
 	}
 
-	void IMainLoopDriver.Wakeup ()
-	{
-		_eventReady.Set ();
-	}
+	void IMainLoopDriver.Wakeup () => _eventReady.Set ();
 
 	bool IMainLoopDriver.EventsPending ()
 	{
 		_waitForProbe.Set ();
 
-		if (_mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout)) {
+		if (_mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout)) {
 			return true;
 		}
 

+ 159 - 166
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -24,6 +24,7 @@ using System.Threading.Tasks;
 using System.Diagnostics;
 using Terminal.Gui.ConsoleDrivers;
 using static Unix.Terminal.Delegates;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui;
 
@@ -371,7 +372,7 @@ internal class WindowsConsole {
 		[FieldOffset (4), MarshalAs (UnmanagedType.U2)]
 		public ushort wRepeatCount;
 		[FieldOffset (6), MarshalAs (UnmanagedType.U2)]
-		public ushort wVirtualKeyCode;
+		public VK wVirtualKeyCode;
 		[FieldOffset (8), MarshalAs (UnmanagedType.U2)]
 		public ushort wVirtualScanCode;
 		[FieldOffset (10)]
@@ -901,66 +902,31 @@ internal class WindowsDriver : ConsoleDriver {
 	}
 #endif
 
+
 	KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
 	{
 		var keyInfo = keyInfoEx.ConsoleKeyInfo;
 		switch (keyInfo.Key) {
-		case ConsoleKey.Escape:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Esc);
-		case ConsoleKey.Tab:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Tab);
-		case ConsoleKey.Clear:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Clear);
-		case ConsoleKey.Home:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Home);
-		case ConsoleKey.End:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.End);
-		case ConsoleKey.LeftArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorLeft);
-		case ConsoleKey.RightArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorRight);
-		case ConsoleKey.UpArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorUp);
-		case ConsoleKey.DownArrow:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.CursorDown);
-		case ConsoleKey.PageUp:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageUp);
-		case ConsoleKey.PageDown:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PageDown);
-		case ConsoleKey.Enter:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Enter);
-		case ConsoleKey.Spacebar:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, keyInfo.KeyChar == 0 ? KeyCode.Space : (KeyCode)keyInfo.KeyChar);
-		case ConsoleKey.Backspace:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.Backspace);
-		case ConsoleKey.Delete:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.DeleteChar);
-		case ConsoleKey.Insert:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.InsertChar);
-		case ConsoleKey.PrintScreen:
-			return ConsoleKeyMapping.MapKeyModifiers (keyInfo, KeyCode.PrintScreen);
-
-		//case ConsoleKey.NumPad0:
-		//	return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
-		//case ConsoleKey.NumPad1:
-		//	return keyInfoEx.NumLock ? Key.D1 : Key.End;
-		//case ConsoleKey.NumPad2:
-		//	return keyInfoEx.NumLock ? Key.D2 : Key.CursorDown;
-		//case ConsoleKey.NumPad3:
-		//	return keyInfoEx.NumLock ? Key.D3 : Key.PageDown;
-		//case ConsoleKey.NumPad4:
-		//	return keyInfoEx.NumLock ? Key.D4 : Key.CursorLeft;
-		//case ConsoleKey.NumPad5:
-		//	return keyInfoEx.NumLock ? Key.D5 : (Key)((uint)keyInfo.KeyChar);
-		//case ConsoleKey.NumPad6:
-		//	return keyInfoEx.NumLock ? Key.D6 : Key.CursorRight;
-		//case ConsoleKey.NumPad7:
-		//	return keyInfoEx.NumLock ? Key.D7 : Key.Home;
-		//case ConsoleKey.NumPad8:
-		//	return keyInfoEx.NumLock ? Key.D8 : Key.CursorUp;
-		//case ConsoleKey.NumPad9:
-		//	return keyInfoEx.NumLock ? Key.D9 : Key.PageUp;
-
+		case ConsoleKey.D0:
+		case ConsoleKey.D1:
+		case ConsoleKey.D2:
+		case ConsoleKey.D3:
+		case ConsoleKey.D4:
+		case ConsoleKey.D5:
+		case ConsoleKey.D6:
+		case ConsoleKey.D7:
+		case ConsoleKey.D8:
+		case ConsoleKey.D9:
+		case ConsoleKey.NumPad0:
+		case ConsoleKey.NumPad1:
+		case ConsoleKey.NumPad2:
+		case ConsoleKey.NumPad3:
+		case ConsoleKey.NumPad4:
+		case ConsoleKey.NumPad5:
+		case ConsoleKey.NumPad6:
+		case ConsoleKey.NumPad7:
+		case ConsoleKey.NumPad8:
+		case ConsoleKey.NumPad9:
 		case ConsoleKey.Oem1:
 		case ConsoleKey.Oem2:
 		case ConsoleKey.Oem3:
@@ -970,147 +936,170 @@ internal class WindowsDriver : ConsoleDriver {
 		case ConsoleKey.Oem7:
 		case ConsoleKey.Oem8:
 		case ConsoleKey.Oem102:
-			var ret = ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
-			if (ret.HasFlag (KeyCode.ShiftMask)) {
-				ret &= ~KeyCode.ShiftMask;
-			}
-			return ret;
-
+		case ConsoleKey.Multiply:
+		case ConsoleKey.Add:
+		case ConsoleKey.Separator:
+		case ConsoleKey.Subtract:
+		case ConsoleKey.Decimal:
+		case ConsoleKey.Divide:
 		case ConsoleKey.OemPeriod:
 		case ConsoleKey.OemComma:
 		case ConsoleKey.OemPlus:
 		case ConsoleKey.OemMinus:
-			return (KeyCode)((uint)keyInfo.KeyChar);
-		}
-
-		var key = keyInfo.Key;
-
-		if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-			var delta = key - ConsoleKey.A;
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.A + delta));
-			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.A + delta));
-				}
+			// These virtual key codes are mapped differently depending on the keyboard layout in use.
+			// We use the Win32 API to map them to the correct character.
+			var mapResult = MapVKtoChar ((VK)keyInfo.Key);
+			if (mapResult == 0) {
+				// There is no mapping - this should not happen
+				Debug.Assert (mapResult != 0, $@"Unable to map the virtual key code {keyInfo.Key}.");
+				return KeyCode.Null;
 			}
 
-			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) {
-				if (keyInfo.KeyChar <= (uint)KeyCode.Z) {
-					return (KeyCode)((uint)KeyCode.A + delta) | KeyCode.ShiftMask;
+			// An un-shifted character value is in the low order word of the return value.
+			var mappedChar = (char)(mapResult & 0x0000FFFF);
+
+			if (keyInfo.KeyChar == 0) {
+				// If the keyChar is 0, keyInfo.Key value is not a printable character. 
+
+				// Dead keys (diacritics) are indicated by setting the top bit of the return value. 
+				if ((mapResult & 0x80000000) != 0) {
+					// Dead key (e.g. Oem2 '~'/'^' on POR keyboard)
+					// Option 1: Throw it out. 
+					//    - Apps will never see the dead keys
+					//    - If user presses a key that can be combined with the dead key ('a'), the right thing happens (app will see 'ã').
+					//      - NOTE: With Dead Keys, KeyDown != KeyUp. The KeyUp event will have just the base char ('a').
+					//    - If user presses dead key again, the right thing happens (app will see `~~`)
+					//    - This is what Notepad etc... appear to do
+					// Option 2: Expand the API to indicate the KeyCode is a dead key
+					//    - Enables apps to do their own dead key processing
+					//    - Adds complexity; no dev has asked for this (yet).
+					// We choose Option 1 for now.
+					return KeyCode.Null;
+
+					// Note: Ctrl-Deadkey (like Oem3 '`'/'~` on ENG) can't be supported.
+					// Sadly, the charVal is just the deadkey and subsequent key events do not contain
+					// any info that the previous event was a deadkey.
+					// Note WT does not support Ctrl-Deadkey either.
 				}
-			}
 
-			if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) == 0) {
-				return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
-			}
+				if (keyInfo.Modifiers != 0) {
+					// These Oem keys have well defined chars. We ensure the representative char is used.
+					// If we don't do this, then on some keyboard layouts the wrong char is 
+					// returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important
+					// for key persistence ("Ctrl++" vs. "Ctrl+=").
+					mappedChar = keyInfo.Key switch {
+						ConsoleKey.OemPeriod => '.',
+						ConsoleKey.OemComma => ',',
+						ConsoleKey.OemPlus => '+',
+						ConsoleKey.OemMinus => '-',
+						_ => mappedChar
+					};
+				}
 
-			if (((KeyCode)((uint)keyInfo.KeyChar) & KeyCode.Space) != 0) {
-				if (((KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space) == (KeyCode)keyInfo.Key) {
-					return (KeyCode)((uint)keyInfo.KeyChar) & ~KeyCode.Space;
+				// Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down
+				// we should keep it
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)mappedChar);
+			} else {
+				// KeyChar is printable
+				if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+					// AltGr support - AltGr is equivalent to Ctrl+Alt - the correct char is in KeyChar
+					return (KeyCode)keyInfo.KeyChar;
 				}
-				return (KeyCode)((uint)keyInfo.KeyChar);
-			}
 
-			return (KeyCode)(uint)keyInfo.KeyChar;
+				if (keyInfo.Modifiers != ConsoleModifiers.Shift) {
+					// If Shift wasn't down we don't need to do anything but return the mappedChar
+					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(mappedChar));
+				}
 
+				// Strip off Shift - We got here because they KeyChar from Windows is the shifted char (e.g. "Ç")
+				// and passing on Shift would be redundant.
+				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)keyInfo.KeyChar);
+			}
 		}
 
-		if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-			var delta = key - ConsoleKey.D0;
-			if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
-				return (KeyCode)(((uint)KeyCode.AltMask) | ((uint)KeyCode.D0 + delta));
-			}
-			if (keyInfo.Modifiers == ConsoleModifiers.Control) {
-				return (KeyCode)(((uint)KeyCode.CtrlMask) | ((uint)KeyCode.D0 + delta));
+		// A..Z are special cased:
+		// - Alone, they represent lowercase a...z
+		// - With ShiftMask they are A..Z
+		// - If CapsLock is on the above is reversed.
+		// - If Alt and/or Ctrl are present, treat as upper case
+		if (keyInfo.Key is >= ConsoleKey.A and <= ConsoleKey.Z) {
+			if (keyInfo.KeyChar == 0) {
+				// KeyChar is not printable - possibly an AltGr key?
+				// AltGr support - AltGr is equivalent to Ctrl+Alt
+				if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) && keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+					return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
+				}
 			}
-			if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
+
+			if (keyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt) || keyInfo.Modifiers.HasFlag (ConsoleModifiers.Control)) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(uint)keyInfo.Key);
 			}
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)KeyCode.D0 + delta)) {
-					return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.D0 + delta));
+
+			if (((keyInfo.Modifiers == ConsoleModifiers.Shift) ^ (keyInfoEx.CapsLock))) {
+				// If (ShiftMask is on and CapsLock is off) or (ShiftMask is off and CapsLock is on) add the ShiftMask
+				if (char.IsUpper (keyInfo.KeyChar)) {
+					return (KeyCode)((uint)keyInfo.Key) | KeyCode.ShiftMask;
 				}
 			}
-			return (KeyCode)((uint)keyInfo.KeyChar);
+			return (KeyCode)(uint)keyInfo.KeyChar;
 		}
 
-		if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
-			var delta = key - ConsoleKey.F1;
-			if ((keyInfo.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-				return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)KeyCode.F1 + delta));
+		// Handle control keys whose VK codes match the related ASCII value (those below ASCII 33) like ESC
+		if (Enum.IsDefined (typeof (KeyCode), (uint)keyInfo.Key)) {
+			// If the key is JUST a modifier, return it as just that key
+			if (keyInfo.Key == (ConsoleKey)VK.SHIFT) { // Shift 16
+				return KeyCode.ShiftMask;
 			}
 
-			return (KeyCode)((uint)KeyCode.F1 + delta);
-		}
+			if (keyInfo.Key == (ConsoleKey)VK.CONTROL) { // Ctrl 17
+				return KeyCode.CtrlMask;
+			}
 
-		if (key == (ConsoleKey)16) { // Shift
-			return KeyCode.Null | KeyCode.ShiftMask;
-		}
+			if (keyInfo.Key == (ConsoleKey)VK.MENU) { // Alt 18
+				return KeyCode.AltMask;
+			}
 
-		if (key == (ConsoleKey)17) { // Ctrl
-			return KeyCode.Null | KeyCode.CtrlMask;
+			if (keyInfo.KeyChar == 0) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
+			} else if (keyInfo.Key != ConsoleKey.None) {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
+			} else {
+				return MapToKeyCodeModifiers (keyInfo.Modifiers & ~ConsoleModifiers.Shift, (KeyCode)(keyInfo.KeyChar));
+			}
 		}
 
-		if (key == (ConsoleKey)18) { // Alt
-			return KeyCode.Null | KeyCode.AltMask;
+		// Handle control keys (e.g. CursorUp)
+		if (Enum.IsDefined (typeof (KeyCode), ((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint))) {
+			return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)((uint)keyInfo.Key + (uint)KeyCode.MaxCodePoint));
 		}
 
-		return ConsoleKeyMapping.MapKeyModifiers (keyInfo, (KeyCode)((uint)keyInfo.KeyChar));
+		return MapToKeyCodeModifiers (keyInfo.Modifiers, (KeyCode)(keyInfo.KeyChar));
 	}
 
-	bool _altDown = false;
-
 	internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
 	{
 		switch (inputEvent.EventType) {
 		case WindowsConsole.EventType.Key:
-			var fromPacketKey = inputEvent.KeyEvent.wVirtualKeyCode == (uint)ConsoleKey.Packet;
-			if (fromPacketKey) {
+			if (inputEvent.KeyEvent.wVirtualKeyCode == (VK)ConsoleKey.Packet) {
+				// Used to pass Unicode characters as if they were keystrokes.
+				// The VK_PACKET key is the low word of a 32-bit
+				// Virtual Key value used for non-keyboard input methods.
 				inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
 			}
 			var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent);
-			//Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
-
+			//Debug.WriteLine ($"event: KBD: {GetKeyboardLayoutName()} {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
 
 			var map = MapKey (keyInfo);
 
+			if (map == KeyCode.Null) {
+				break;
+			}
+
 			if (inputEvent.KeyEvent.bKeyDown) {
-				_altDown = keyInfo.ConsoleKeyInfo.Modifiers == ConsoleModifiers.Alt;
-				// Avoid sending repeat keydowns
+				// Avoid sending repeat key down events
 				OnKeyDown (new Key (map));
 			} else {
-				var keyPressedEventArgs = new Key (map);
-
-				// PROTOTYPE: This logic enables `Alt` key presses (down, up, pressed).
-				// However, if while the 'Alt' key is down, if another key is pressed and
-				// released, there will be a keypressed event for that and the
-				// keypressed event for just `Alt` will be suppressed. 
-				// This allows MenuBar to have `Alt` as a keybinding
-				if (map != KeyCode.AltMask) {
-					if (keyInfo.ConsoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
-						if (_altDown) {
-							_altDown = false;
-							OnKeyUp (new Key (map));
-						}
-
-					}
-					_altDown = false;
-					// KeyUp of an Alt-key press. 
-					OnKeyUp (keyPressedEventArgs);
-				} else {
-					OnKeyUp (keyPressedEventArgs);
-					if (_altDown) {
-						_altDown = false;
-					}
-				}
+				OnKeyUp (new Key (map));
 			}
 
 			break;
@@ -1421,7 +1410,7 @@ internal class WindowsDriver : ConsoleDriver {
 
 	public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
 	{
-		if (keyEvent.wVirtualKeyCode != (uint)ConsoleKey.Packet) {
+		if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet) {
 			return keyEvent;
 		}
 
@@ -1437,14 +1426,17 @@ internal class WindowsDriver : ConsoleDriver {
 		    keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) {
 			mod |= ConsoleModifiers.Control;
 		}
-		var cKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (keyEvent.UnicodeChar, mod, out uint scanCode);
+		var cKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode,
+			mod.HasFlag (ConsoleModifiers.Shift), mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control));
+		cKeyInfo = DecodeVKPacketToKConsoleKeyInfo (cKeyInfo);
+		var scanCode = GetScanCodeFromConsoleKeyInfo (cKeyInfo);
 
 		return new WindowsConsole.KeyEventRecord {
 			UnicodeChar = cKeyInfo.KeyChar,
 			bKeyDown = keyEvent.bKeyDown,
 			dwControlKeyState = keyEvent.dwControlKeyState,
 			wRepeatCount = keyEvent.wRepeatCount,
-			wVirtualKeyCode = (ushort)cKeyInfo.Key,
+			wVirtualKeyCode = (VK)cKeyInfo.Key,
 			wVirtualScanCode = (ushort)scanCode
 		};
 	}
@@ -1590,19 +1582,19 @@ internal class WindowsDriver : ConsoleDriver {
 		if (shift) {
 			controlKey |= WindowsConsole.ControlKeyState.ShiftPressed;
 			keyEvent.UnicodeChar = '\0';
-			keyEvent.wVirtualKeyCode = 16;
+			keyEvent.wVirtualKeyCode = VK.SHIFT;
 		}
 		if (alt) {
 			controlKey |= WindowsConsole.ControlKeyState.LeftAltPressed;
 			controlKey |= WindowsConsole.ControlKeyState.RightAltPressed;
 			keyEvent.UnicodeChar = '\0';
-			keyEvent.wVirtualKeyCode = 18;
+			keyEvent.wVirtualKeyCode = VK.MENU;
 		}
 		if (control) {
 			controlKey |= WindowsConsole.ControlKeyState.LeftControlPressed;
 			controlKey |= WindowsConsole.ControlKeyState.RightControlPressed;
 			keyEvent.UnicodeChar = '\0';
-			keyEvent.wVirtualKeyCode = 17;
+			keyEvent.wVirtualKeyCode = VK.CONTROL;
 		}
 		keyEvent.dwControlKeyState = controlKey;
 
@@ -1613,11 +1605,12 @@ internal class WindowsDriver : ConsoleDriver {
 		}
 
 		keyEvent.UnicodeChar = keyChar;
-		if ((uint)key < 255) {
-			keyEvent.wVirtualKeyCode = (ushort)key;
-		} else {
-			keyEvent.wVirtualKeyCode = '\0';
-		}
+		//if ((uint)key < 255) {
+		//	keyEvent.wVirtualKeyCode = (ushort)key;
+		//} else {
+		//	keyEvent.wVirtualKeyCode = '\0';
+		//}
+		keyEvent.wVirtualKeyCode = (VK)key;
 
 		input.KeyEvent = keyEvent;
 

+ 226 - 192
Terminal.Gui/Drawing/Color.cs

@@ -9,6 +9,7 @@ using System.Text.Json.Serialization;
 using System.Text.RegularExpressions;
 
 namespace Terminal.Gui;
+
 /// <summary>
 /// Defines the 16 legacy color names and values that can be used to set the
 /// foreground and background colors in Terminal.Gui apps. Used with <see cref="Color"/>.
@@ -88,14 +89,82 @@ public enum ColorName {
 	/// </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.
+/// </summary>
+public enum AnsiColorCode {
+	/// <summary>
+	/// The ANSI color code for Black.
+	/// </summary>
+	BLACK = 30,
 
+	/// <summary>
+	/// 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"/>. 
 /// </summary>
 [JsonConverter (typeof (ColorJsonConverter))]
 public class Color : IEquatable<Color> {
-
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Color"/> class.
 	/// </summary>
@@ -115,10 +184,7 @@ 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) => Rgba = rgba;
 
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Color"/> color from a legacy 16-color value.
@@ -126,7 +192,7 @@ public class Color : IEquatable<Color> {
 	/// <param name="colorName">The 16-color value.</param>
 	public Color (ColorName colorName)
 	{
-		var c = Color.FromColorName (colorName);
+		var c = FromColorName (colorName);
 		R = c.R;
 		G = c.G;
 		B = c.B;
@@ -188,11 +254,11 @@ public class Color : IEquatable<Color> {
 	/// </code>
 	/// </summary>
 	public int Rgba {
-		get => (A << 24) | (R << 16) | (G << 8) | B;
+		get => A << 24 | R << 16 | G << 8 | B;
 		set {
-			A = (byte)((value >> 24) & 0xFF);
-			R = (byte)((value >> 16) & 0xFF);
-			G = (byte)((value >> 8) & 0xFF);
+			A = (byte)(value >> 24 & 0xFF);
+			R = (byte)(value >> 16 & 0xFF);
+			G = (byte)(value >> 8 & 0xFF);
 			B = (byte)(value & 0xFF);
 		}
 	}
@@ -203,39 +269,61 @@ public class Color : IEquatable<Color> {
 	/// 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),Gui.ColorName.Black },
-			{ new Color (0, 55, 218),Gui.ColorName.Blue },
-			{ new Color (19, 161, 14),Gui.ColorName.Green},
-			{ new Color (58, 150, 221),Gui.ColorName.Cyan},
-			{ new Color (197, 15, 31),Gui.ColorName.Red},
-			{ new Color (136, 23, 152),Gui.ColorName.Magenta},
-			{ new Color (128, 64, 32),Gui.ColorName.Yellow},
-			{ new Color (204, 204, 204),Gui.ColorName.Gray},
-			{ new Color (118, 118, 118),Gui.ColorName.DarkGray},
-			{ new Color (59, 120, 255),Gui.ColorName.BrightBlue},
-			{ new Color (22, 198, 12),Gui.ColorName.BrightGreen},
-			{ new Color (97, 214, 214),Gui.ColorName.BrightCyan},
-			{ new Color (231, 72, 86),Gui.ColorName.BrightRed},
-			{ new Color (180, 0, 158),Gui.ColorName.BrightMagenta },
-			{ new Color (249, 241, 165),Gui.ColorName.BrightYellow},
-			{ new Color (242, 242, 242),Gui.ColorName.White},
-		}.ToImmutableDictionary ();
+		// 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>
 	/// Gets or sets the 24-bit color value for each of the legacy 16-color values.
 	/// </summary>
 	[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
 	public static Dictionary<ColorName, string> Colors {
-		get {
+		get =>
 			// Transform _colorToNameMap into a Dictionary<ColorNames,string>
-			return _colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}");
-		}
+			_colorToNameMap.ToDictionary (kvp => kvp.Value, kvp => $"#{kvp.Key.R:X2}{kvp.Key.G:X2}{kvp.Key.B:X2}");
 		set {
 			// Transform Dictionary<ColorNames,string> into _colorToNameMap
 			var newMap = value.ToDictionary (kvp => new Color (kvp.Value), kvp => {
-				if (Enum.TryParse<ColorName> (kvp.Key.ToString (), ignoreCase: true, out ColorName colorName)) {
+				if (Enum.TryParse<ColorName> (kvp.Key.ToString (), true, out var colorName)) {
 					return colorName;
 				}
 				throw new ArgumentException ($"Invalid color name: {kvp.Key}");
@@ -247,9 +335,9 @@ public class Color : IEquatable<Color> {
 	/// <summary>
 	/// Converts a legacy <see cref="Gui.ColorName"/> to a 24-bit <see cref="Color"/>.
 	/// </summary>
-	/// <param name="consoleColor">The <see cref="Color"/> to convert.</param>
+	/// <param name="colorName">The <see cref="Color"/> to convert.</param>
 	/// <returns></returns>
-	private static Color FromColorName (ColorName consoleColor) => _colorToNameMap.FirstOrDefault (x => x.Value == consoleColor).Key;
+	static Color FromColorName (ColorName colorName) => _colorToNameMap.FirstOrDefault (x => x.Value == colorName).Key;
 
 	// Iterates through the entries in the _colorNames dictionary, calculates the
 	// Euclidean distance between the input color and each dictionary color in RGB space,
@@ -257,11 +345,11 @@ public class Color : IEquatable<Color> {
 	// representing the closest color entry and its associated color name.
 	internal static ColorName FindClosestColor (Color inputColor)
 	{
-		ColorName closestColor = Gui.ColorName.Black; // Default to Black
+		var closestColor = ColorName.Black; // Default to Black
 		double closestDistance = double.MaxValue;
 
 		foreach (var colorEntry in _colorToNameMap) {
-			var distance = CalculateColorDistance (inputColor, colorEntry.Key);
+			double distance = CalculateColorDistance (inputColor, colorEntry.Key);
 			if (distance < closestDistance) {
 				closestDistance = distance;
 				closestColor = colorEntry.Value;
@@ -271,12 +359,12 @@ public class Color : IEquatable<Color> {
 		return closestColor;
 	}
 
-	private static double CalculateColorDistance (Color color1, Color color2)
+	static double CalculateColorDistance (Color color1, Color color2)
 	{
 		// Calculate the Euclidean distance between two colors
-		var deltaR = (double)color1.R - (double)color2.R;
-		var deltaG = (double)color1.G - (double)color2.G;
-		var deltaB = (double)color1.B - (double)color2.B;
+		double deltaR = (double)color1.R - (double)color2.R;
+		double deltaG = (double)color1.G - (double)color2.G;
+		double deltaB = (double)color1.B - (double)color2.B;
 
 		return Math.Sqrt (deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
 	}
@@ -300,6 +388,16 @@ public class Color : IEquatable<Color> {
 		}
 	}
 
+	/// <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.
+	/// </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];
+
 	#region Legacy Color Names
 	/// <summary>
 	/// The black color.
@@ -382,49 +480,49 @@ public class Color : IEquatable<Color> {
 	public static bool TryParse (string text, [NotNullWhen (true)] out Color color)
 	{
 		// empty color
-		if ((text == null) || (text.Length == 0)) {
+		if (text == null || text.Length == 0) {
 			color = null;
 			return false;
 		}
 
 		// #RRGGBB, #RGB
-		if ((text [0] == '#') && text.Length is 7 or 4) {
+		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);
+				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 {
-				var rText = char.ToString (text [1]);
-				var gText = char.ToString (text [2]);
-				var bText = char.ToString (text [3]);
+				string rText = char.ToString (text [1]);
+				string gText = char.ToString (text [2]);
+				string 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);
+				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 [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);
+				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 {
-				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);
+				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;
@@ -433,9 +531,9 @@ public class Color : IEquatable<Color> {
 		// 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);
+			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;
 		}
@@ -443,15 +541,15 @@ public class Color : IEquatable<Color> {
 		// 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);
+			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, ignoreCase: true, out ColorName colorName)) {
+		if (Enum.TryParse<ColorName> (text, true, out var colorName)) {
 			color = new Color (colorName);
 			return true;
 		}
@@ -465,37 +563,25 @@ public class Color : IEquatable<Color> {
 	/// Cast from int.
 	/// </summary>
 	/// <param name="rgba"></param>
-	public static implicit operator Color (int rgba)
-	{
-		return new Color (rgba);
-	}
+	public static implicit operator Color (int rgba) => new Color (rgba);
 
 	/// <summary>
 	/// Cast to int.
 	/// </summary>
 	/// <param name="color"></param>
-	public static explicit operator int (Color color)
-	{
-		return color.Rgba;
-	}
+	public static explicit operator int (Color color) => color.Rgba;
 
 	/// <summary>
 	/// Cast from <see cref="Gui.ColorName"/>.
 	/// </summary>
 	/// <param name="colorName"></param>
-	public static explicit operator Color (ColorName colorName)
-	{
-		return new Color (colorName);
-	}
+	public static explicit operator Color (ColorName colorName) => new Color (colorName);
 
 	/// <summary>
 	/// Cast to <see cref="Gui.ColorName"/>.
 	/// </summary>
 	/// <param name="color"></param>
-	public static explicit operator ColorName (Color color)
-	{
-		return color.ColorName;
-	}
+	public static explicit operator ColorName (Color color) => color.ColorName;
 
 
 	/// <summary>
@@ -506,11 +592,13 @@ public class Color : IEquatable<Color> {
 	/// <returns></returns>
 	public static bool operator == (Color left, Color right)
 	{
-		if (left is null && right is null)
+		if (left is null && right is null) {
 			return true;
+		}
 
-		if (left is null || right is null)
+		if (left is null || right is null) {
 			return false;
+		}
 
 		return left.Equals (right);
 	}
@@ -524,11 +612,13 @@ public class Color : IEquatable<Color> {
 	/// <returns></returns>
 	public static bool operator != (Color left, Color right)
 	{
-		if (left is null && right is null)
+		if (left is null && right is null) {
 			return false;
+		}
 
-		if (left is null || right is null)
+		if (left is null || right is null) {
 			return true;
+		}
 
 		return !left.Equals (right);
 	}
@@ -539,10 +629,7 @@ public class Color : IEquatable<Color> {
 	/// <param name="left"></param>
 	/// <param name="right"></param>
 	/// <returns></returns>
-	public static bool operator == (ColorName left, Color right)
-	{
-		return left == right.ColorName;
-	}
+	public static bool operator == (ColorName left, Color right) => left == right.ColorName;
 
 	/// <summary>
 	/// Inequality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
@@ -550,10 +637,7 @@ public class Color : IEquatable<Color> {
 	/// <param name="left"></param>
 	/// <param name="right"></param>
 	/// <returns></returns>
-	public static bool operator != (ColorName left, Color right)
-	{
-		return left != right.ColorName;
-	}
+	public static bool operator != (ColorName left, Color right) => left != right.ColorName;
 
 	/// <summary>
 	/// Equality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
@@ -561,10 +645,7 @@ public class Color : IEquatable<Color> {
 	/// <param name="left"></param>
 	/// <param name="right"></param>
 	/// <returns></returns>
-	public static bool operator == (Color left, ColorName right)
-	{
-		return left.ColorName == right;
-	}
+	public static bool operator == (Color left, ColorName right) => left.ColorName == right;
 
 	/// <summary>
 	/// Inequality operator for <see cref="Color"/> and <see cref="Gui.ColorName"/> objects.
@@ -572,33 +653,20 @@ public class Color : IEquatable<Color> {
 	/// <param name="left"></param>
 	/// <param name="right"></param>
 	/// <returns></returns>
-	public static bool operator != (Color left, ColorName right)
-	{
-		return left.ColorName != right;
-	}
+	public static bool operator != (Color left, ColorName right) => left.ColorName != right;
 
 
 	/// <inheritdoc/>
-	public override bool Equals (object obj)
-	{
-		return obj is Color other && Equals (other);
-	}
+	public override bool Equals (object obj) => obj is Color other && Equals (other);
 
 	/// <inheritdoc/>
-	public bool Equals (Color other)
-	{
-		return
-			R == other.R &&
-			G == other.G &&
-			B == other.B &&
-			A == other.A;
-	}
+	public bool Equals (Color other) => R == other.R &&
+					G == other.G &&
+					B == other.B &&
+					A == other.A;
 
 	/// <inheritdoc/>
-	public override int GetHashCode ()
-	{
-		return HashCode.Combine (R, G, B, A);
-	}
+	public override int GetHashCode () => HashCode.Combine (R, G, B, A);
 	#endregion
 
 	/// <summary>
@@ -616,14 +684,13 @@ public class Color : IEquatable<Color> {
 	public override string ToString ()
 	{
 		// If Values has an exact match with a named color (in _colorNames), use that.
-		if (_colorToNameMap.TryGetValue (this, out ColorName colorName)) {
+		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. 
 /// </summary>
@@ -634,7 +701,6 @@ public class Color : IEquatable<Color> {
 /// </remarks>
 [JsonConverter (typeof (AttributeJsonConverter))]
 public readonly struct Attribute : IEquatable<Attribute> {
-
 	/// <summary>
 	/// Default empty attribute.
 	/// </summary>
@@ -665,19 +731,20 @@ public readonly struct Attribute : IEquatable<Attribute> {
 	{
 		PlatformColor = -1;
 		var d = Default;
-		Foreground = new (d.Foreground.ColorName);
-		Background = new (d.Background.ColorName);
+		Foreground = new Color (d.Foreground.ColorName);
+		Background = new Color (d.Background.ColorName);
 	}
 
 	/// <summary>
 	/// Initializes a new instance with platform specific color value.
 	/// </summary>
 	/// <param name="platformColor">Value.</param>
-	internal Attribute (int platformColor) {
+	internal Attribute (int platformColor)
+	{
 		PlatformColor = platformColor;
 		var d = Default;
-		Foreground = new (d.Foreground.ColorName);
-		Background = new (d.Background.ColorName);
+		Foreground = new Color (d.Foreground.ColorName);
+		Background = new Color (d.Background.ColorName);
 	}
 
 	/// <summary>
@@ -775,30 +842,21 @@ public readonly struct Attribute : IEquatable<Attribute> {
 	public static bool operator != (Attribute left, Attribute right) => !(left == right);
 
 	/// <inheritdoc />
-	public override bool Equals (object obj)
-	{
-		return obj is Attribute other && Equals (other);
-	}
+	public override bool Equals (object obj) => obj is Attribute other && Equals (other);
 
 	/// <inheritdoc />
-	public bool Equals (Attribute other)
-	{
-		return PlatformColor == other.PlatformColor &&
-			Foreground == other.Foreground &&
-			Background == other.Background;
-	}
+	public bool Equals (Attribute other) => PlatformColor == other.PlatformColor &&
+						Foreground == other.Foreground &&
+						Background == other.Background;
 
 	/// <inheritdoc />
 	public override int GetHashCode () => HashCode.Combine (PlatformColor, Foreground, Background);
 
 	/// <inheritdoc />
-	public override string ToString ()
-	{
+	public override string ToString () =>
 		// Note, Unit tests are dependent on this format
-		return $"{Foreground},{Background}";
-	}
+		$"{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
@@ -899,25 +957,19 @@ public class ColorScheme : IEquatable<ColorScheme> {
 	/// </summary>
 	/// <param name="obj"></param>
 	/// <returns>true if the two objects are equal</returns>
-	public override bool Equals (object obj)
-	{
-		return Equals (obj as ColorScheme);
-	}
+	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)
-	{
-		return 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);
-	}
+	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.
@@ -940,10 +992,7 @@ public class ColorScheme : IEquatable<ColorScheme> {
 	/// <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)
-	{
-		return EqualityComparer<ColorScheme>.Default.Equals (left, right);
-	}
+	public static bool operator == (ColorScheme left, ColorScheme right) => EqualityComparer<ColorScheme>.Default.Equals (left, right);
 
 	/// <summary>
 	/// Compares two <see cref="ColorScheme"/> objects for inequality.
@@ -951,12 +1000,8 @@ public class ColorScheme : IEquatable<ColorScheme> {
 	/// <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)
-	{
-		return !(left == right);
-	}
+	public static bool operator != (ColorScheme left, ColorScheme right) => !(left == right);
 }
-
 /// <summary>
 /// The default <see cref="ColorScheme"/>s for the application.
 /// </summary>
@@ -964,7 +1009,7 @@ public class ColorScheme : IEquatable<ColorScheme> {
 /// This property can be set in a Theme to change the default <see cref="Colors"/> for the application.
 /// </remarks>
 public static class Colors {
-	private class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
+	class SchemeNameComparerIgnoreCase : IEqualityComparer<string> {
 		public bool Equals (string x, string y)
 		{
 			if (x != null && y != null) {
@@ -973,29 +1018,21 @@ public static class Colors {
 			return false;
 		}
 
-		public int GetHashCode (string obj)
-		{
-			return obj.ToLowerInvariant ().GetHashCode ();
-		}
+		public int GetHashCode (string obj) => obj.ToLowerInvariant ().GetHashCode ();
 	}
 
-	static Colors ()
-	{
-		ColorSchemes = Create ();
-	}
+	static Colors () => ColorSchemes = Create ();
 
 	/// <summary>
 	/// Creates a new dictionary of new <see cref="ColorScheme"/> objects.
 	/// </summary>
-	public static Dictionary<string, ColorScheme> Create ()
-	{
+	public static Dictionary<string, ColorScheme> Create () =>
 		// Use reflection to dynamically create the default set of ColorSchemes from the list defined 
 		// by the class. 
-		return 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, comparer: new SchemeNameComparerIgnoreCase ());
-	}
+		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.
@@ -1047,10 +1084,7 @@ public static class Colors {
 	/// </remarks>
 	public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); }
 
-	static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null)
-	{
-		return ColorSchemes [schemeBeingSet];
-	}
+	static ColorScheme GetColorScheme ([CallerMemberName] string schemeBeingSet = null) => ColorSchemes [schemeBeingSet];
 
 	static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string schemeBeingSet = null)
 	{
@@ -1064,4 +1098,4 @@ public static class Colors {
 	[SerializableConfigurationProperty (Scope = typeof (ThemeScope), OmitClassName = true)]
 	[JsonConverter (typeof (DictionaryJsonConverter<ColorScheme>))]
 	public static Dictionary<string, ColorScheme> ColorSchemes { get; private set; }
-}
+}

+ 218 - 144
Terminal.Gui/Input/Key.cs

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
 using System.Linq;
 using System.Text;
 using System.Text.Json.Serialization;
+using static System.Runtime.CompilerServices.RuntimeHelpers;
 
 namespace Terminal.Gui;
 
@@ -64,7 +65,6 @@ namespace Terminal.Gui;
 /// </list>
 /// </para>
 /// </remarks>
-[JsonConverter (typeof (KeyJsonConverter))]
 public class Key : EventArgs, IEquatable<Key> {
 	/// <summary>
 	/// Constructs a new <see cref="Key"/>
@@ -72,11 +72,56 @@ public class Key : EventArgs, IEquatable<Key> {
 	public Key () : this (KeyCode.Null) { }
 
 	/// <summary>
-	///   Constructs a new <see cref="Key"/> from the provided Key value
+	/// Constructs a new <see cref="Key"/> from the provided Key value
 	/// </summary>
 	/// <param name="k">The key</param>
 	public Key (KeyCode k) => KeyCode = k;
 
+	/// <summary>
+	/// Constructs a new <see cref="Key"/> from a char.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// The key codes for the A..Z keys are encoded as values between 65 and 90 (<see cref="KeyCode.A"/> through <see cref="KeyCode.Z"/>).
+	/// While these are the same as the ASCII values for uppercase characters, they represent *keys*, not characters.
+	/// Therefore, this constructor will store 'A'..'Z' as <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/> with the
+	/// <see cref="KeyCode.ShiftMask"/> set and will
+	/// store `a`..`z` as <see cref="KeyCode.A"/>..<see cref="KeyCode.Z"/>.
+	/// </para>
+	/// </remarks>
+	/// <param name="ch"></param>
+	public Key (char ch)
+	{
+		switch (ch) {
+		case >= 'A' and <= 'Z':
+			// Upper case A..Z mean "Shift-char" so we need to add Shift
+			KeyCode = (KeyCode)ch | KeyCode.ShiftMask;
+			break;
+		case >= 'a' and <= 'z':
+			// Lower case a..z mean no shift, so we need to store as Key.A...Key.Z
+			KeyCode = (KeyCode)(ch - 32);
+			return;
+		default:
+			KeyCode = (KeyCode)ch;
+			break;
+		}
+	}
+
+	/// <summary>
+	/// Constructs a new Key from a string describing the key.
+	/// See <see cref="TryParse(string, out Terminal.Gui.Key)"/> for information
+	/// on the format of the string.
+	/// </summary>
+	/// <param name="str">The string describing the key.</param>
+	public Key (string str)
+	{
+		var result = Key.TryParse (str, out Key key);
+		if (!result) {
+			throw new ArgumentException (@$"Invalid key string: {str}", nameof (str));
+		}
+		KeyCode = key.KeyCode;
+	}
+
 	/// <summary>
 	/// Indicates if the current Key event has already been processed and the driver should stop notifying any other event subscriber.
 	/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
@@ -87,14 +132,14 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// The encoded key value. 
 	/// </summary>
 	/// <para>
-	/// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
+	/// IMPORTANT: Lowercase alpha keys are encoded (in <see cref="Gui.KeyCode"/>) as values between 65 and
+	/// 90 corresponding to the un-shifted A to Z keys on a keyboard. Enum values
 	/// are provided for these (e.g. <see cref="KeyCode.A"/>, <see cref="KeyCode.B"/>, etc.). Even though the values are the same as the ASCII
 	/// values for uppercase characters, these enum values represent *lowercase*, un-shifted characters.
 	/// </para>
 	/// <remarks>
 	/// This property is the backing data for the <see cref="Key"/>. It is a <see cref="KeyCode"/> enum value.
 	/// </remarks>
-	[JsonInclude] [JsonConverter (typeof (KeyCodeJsonConverter))]
 	public KeyCode KeyCode { get; init; }
 
 	/// <summary>
@@ -103,46 +148,67 @@ public class Key : EventArgs, IEquatable<Key> {
 	public KeyBindingScope Scope { get; set; } = KeyBindingScope.Focused;
 
 	/// <summary>
-	/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers.
+	/// The key value as a Rune. This is the actual value of the key pressed, and is independent of the modifiers. Useful
+	/// for determining if a key represents is a printable character.
 	/// </summary>
 	/// <remarks>
-	/// If the key pressed is a letter (a-z or A-Z), this will be the upper or lower case letter depending on whether the shift key is pressed.
-	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, this will be <see langword="default"/>.
+	/// <para>
+	/// Keys with Ctrl or Alt modifiers will return <see langword="default"/>. 
+	/// </para>
+	/// <para>
+	/// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
+	/// <see cref="KeyCode.ShiftMask"/> is set.
+	/// </para>
+	/// <para>
+	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be <see langword="default"/>.
+	/// </para>
 	/// </remarks>
 	public Rune AsRune => ToRune (KeyCode);
 
 	/// <summary>
-	/// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>.
+	/// Converts a <see cref="KeyCode"/> to a <see cref="Rune"/>. Useful
+	/// for determining if a key represents is a printable character.
 	/// </summary>
 	/// <remarks>
-	/// If the key is a letter (a-z or A-Z), this will be the upper or lower case letter depending on whether the shift key is pressed.
-	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, this will be <see langword="default"/>.
+	/// <para>
+	/// Keys with Ctrl or Alt modifiers will return <see langword="default"/>. 
+	/// </para>
+	/// <para>
+	/// If the key is a letter key (A-Z), the Rune will be the upper or lower case letter depending on whether
+	/// <see cref="KeyCode.ShiftMask"/> is set.
+	/// </para>
+	/// <para>
+	/// If the key is outside of the <see cref="KeyCode.CharMask"/> range, the returned Rune will be <see langword="default"/>.
+	/// </para>
 	/// </remarks>
 	/// <param name="key"></param>
-	/// <returns>The key converted to a rune. <see langword="default"/> if conversion is not possible.</returns>
+	/// <returns>The key converted to a Rune. <see langword="default"/> if conversion is not possible.</returns>
 	public static Rune ToRune (KeyCode key)
 	{
 		if (key is KeyCode.Null or KeyCode.SpecialMask || key.HasFlag (KeyCode.CtrlMask) || key.HasFlag (KeyCode.AltMask)) {
 			return default;
 		}
 
-		// Extract the base key (removing modifier flags)
-		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
+		// Extract the base key code
+		var baseKey = key;
+		if (baseKey.HasFlag(KeyCode.ShiftMask)) {
+			baseKey &= ~KeyCode.ShiftMask;
+		}
 
 		switch (baseKey) {
 		case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
-			return new Rune ((char)(baseKey + 32));
-		case >= KeyCode.A and <= KeyCode.Z:
-			return new Rune ((char)baseKey);
+			return new Rune ((uint)(baseKey + 32));
+		case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
+			return new Rune ((uint)baseKey);
 		case > KeyCode.Null and < KeyCode.A:
-			return new Rune ((char)baseKey);
+			return new Rune ((uint)baseKey);
 		}
 
 		if (Enum.IsDefined (typeof (KeyCode), baseKey)) {
 			return default;
 		}
 
-		return new Rune ((char)baseKey);
+		return new Rune ((uint)baseKey);
 	}
 
 	/// <summary>
@@ -164,7 +230,9 @@ public class Key : EventArgs, IEquatable<Key> {
 	public bool IsCtrl => (KeyCode & KeyCode.CtrlMask) != 0;
 
 	/// <summary>
-	/// Gets a value indicating whether the KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key.
+	/// Gets a value indicating whether the key represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
+	/// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which are
+	/// special cased.
 	/// </summary>
 	/// <remarks>
 	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
@@ -174,7 +242,9 @@ public class Key : EventArgs, IEquatable<Key> {
 	public bool IsKeyCodeAtoZ => GetIsKeyCodeAtoZ (KeyCode);
 
 	/// <summary>
-	/// Tests if a KeyCode is composed of a lower case letter from 'a' to 'z', independent of the shift key.
+	/// Tests if a KeyCode represents a key in the range of <see cref="KeyCode.A"/> to <see cref="KeyCode.Z"/>,
+	/// regardless of the <see cref="KeyCode.ShiftMask"/>. This is useful for testing if a key is based on these keys which are
+	/// special cased.
 	/// </summary>
 	/// <remarks>
 	/// IMPORTANT: Lowercase alpha keys are encoded in <see cref="Key.KeyCode"/> as values between 65 and 90 corresponding to
@@ -253,7 +323,8 @@ public class Key : EventArgs, IEquatable<Key> {
 
 	#region Operators
 	/// <summary>
-	/// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy. 
+	/// Explicitly cast a <see cref="Key"/> to a <see cref="Rune"/>. The conversion is lossy because properties
+	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
 	/// </summary>
 	/// <remarks>
 	/// Uses <see cref="AsRune"/>.
@@ -261,14 +332,17 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// <param name="kea"></param>
 	public static explicit operator Rune (Key kea) => kea.AsRune;
 
+	// BUGBUG: (Tig) I do not think this cast operator is really needed. 
 	/// <summary>
-	/// Explicitly cast <see cref="Key"/> to a <see langword="char"/>. The conversion is lossy. 
+	/// Explicitly cast <see cref="Key"/> to a <see langword="uint"/>. The conversion is lossy because properties
+	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
 	/// </summary>
 	/// <param name="kea"></param>
-	public static explicit operator char (Key kea) => (char)kea.AsRune.Value;
+	public static explicit operator uint (Key kea) => (uint)kea.KeyCode;
 
 	/// <summary>
-	/// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy. 
+	/// Explicitly cast <see cref="Key"/> to a <see cref="KeyCode"/>. The conversion is lossy because properties
+	/// such as <see cref="Handled"/> are not encoded in <see cref="KeyCode"/>.
 	/// </summary>
 	/// <param name="key"></param>
 	public static explicit operator KeyCode (Key key) => key.KeyCode;
@@ -277,14 +351,34 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// Cast <see cref="KeyCode"/> to a <see cref="Key"/>. 
 	/// </summary>
 	/// <param name="keyCode"></param>
-	public static implicit operator Key (KeyCode keyCode) => new (keyCode);
-
+	public static implicit operator Key (KeyCode keyCode) => new Key (keyCode);
 
 	/// <summary>
 	/// Cast <see langword="char"/> to a <see cref="Key"/>. 
 	/// </summary>
+	/// <remarks>
+	/// See <see cref="Key(char)"/> for more information.
+	/// </remarks>
 	/// <param name="ch"></param>
-	public static implicit operator Key (char ch) => new ((KeyCode)ch);
+	public static implicit operator Key (char ch) => new Key (ch);
+
+	/// <summary>
+	/// Cast <see langword="string"/> to a <see cref="Key"/>. 
+	/// </summary>
+	/// <remarks>
+	/// See <see cref="Key(string)"/> for more information.
+	/// </remarks>
+	/// <param name="str"></param>
+	public static implicit operator Key (string str) => new Key (str);
+
+	/// <summary>
+	/// Cast a <see cref="Key"/> to a <see langword="string"/>. 
+	/// </summary>
+	/// <remarks>
+	/// See <see cref="Key(string)"/> for more information.
+	/// </remarks>
+	/// <param name="key"></param>
+	public static implicit operator string (Key key) => key.ToString ();
 
 	/// <inheritdoc/>
 	public override bool Equals (object obj) => obj is Key k && k.KeyCode == KeyCode;
@@ -295,6 +389,7 @@ public class Key : EventArgs, IEquatable<Key> {
 	public override int GetHashCode () => (int)KeyCode;
 
 	/// <summary>
+	/// Compares two <see cref="Key"/>s for equality.
 	/// </summary>
 	/// <param name="a"></param>
 	/// <param name="b"></param>
@@ -302,6 +397,7 @@ public class Key : EventArgs, IEquatable<Key> {
 	public static bool operator == (Key a, Key b) => a?.KeyCode == b?.KeyCode;
 
 	/// <summary>
+	/// Compares two <see cref="Key"/>s for not equality.
 	/// </summary>
 	/// <param name="a"></param>
 	/// <param name="b"></param>
@@ -357,15 +453,15 @@ public class Key : EventArgs, IEquatable<Key> {
 		var baseKey = key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask;
 
 		if (!key.HasFlag (KeyCode.ShiftMask) && baseKey is >= KeyCode.A and <= KeyCode.Z) {
-			return ((char)(key + 32)).ToString ();
+			return ((Rune)(uint)(key + 32)).ToString ();
 		}
 
-		if (key is >= KeyCode.Space and < KeyCode.A) {
-			return ((char)key).ToString ();
+		if (key is > KeyCode.Space and < KeyCode.A) {
+			return ((Rune)(uint)key).ToString ();
 		}
 
 		string keyName = Enum.GetName (typeof (KeyCode), key);
-		return !string.IsNullOrEmpty (keyName) ? keyName : ((char)key).ToString ();
+		return !string.IsNullOrEmpty (keyName) ? keyName : ((Rune)(uint)key).ToString ();
 	}
 
 
@@ -384,7 +480,7 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// <returns>The formatted string. If the key is a printable character, it will be returned as a string. Otherwise, the key name will be returned.</returns>
 	public static string ToString (KeyCode key, Rune separator)
 	{
-		if (key is KeyCode.Null || (key & ~KeyCode.CtrlMask & ~KeyCode.AltMask & ~KeyCode.ShiftMask) == 0) {
+		if (key is KeyCode.Null) {
 			// Same as Key.IsValid
 			return @"Null";
 		}
@@ -419,21 +515,19 @@ public class Key : EventArgs, IEquatable<Key> {
 			}
 		}
 
-		string result = sb.ToString ();
-		result = TrimEndRune (result, separator);
-		return result;
+		return TrimEndSeparator (sb.ToString (), separator);
 	}
 
-	static string TrimEndRune (string input, Rune runeToTrim)
+	static string TrimEndSeparator (string input, Rune separator)
 	{
-		// Convert the Rune to a string (which may be one or two chars)
-		string runeString = runeToTrim.ToString ();
+		// Trim the trailing separator (+). Unless there are two separators at the end.
+		// "+" (don't trim)
+		// "Ctrl+" (trim)
+		// "Ctrl++" (trim)
 
-		if (input.EndsWith (runeString)) {
-			// Remove the rune from the end of the string
-			return input.Substring (0, input.Length - runeString.Length);
+		if (input.Length > 1 && new Rune(input [^1]) == separator && new Rune(input [^2]) != separator) {
+			return input [..^1];
 		}
-
 		return input;
 	}
 
@@ -473,13 +567,13 @@ public class Key : EventArgs, IEquatable<Key> {
 		if (parts.Length == 1) {
 			switch (parts [0]) {
 			case "Ctrl":
-				key = new Key (KeyCode.CtrlKey);
+				key = new Key (KeyCode.CtrlMask);
 				return true;
 			case "Alt":
-				key = new Key (KeyCode.AltKey);
+				key = new Key (KeyCode.AltMask);
 				return true;
 			case "Shift":
-				key = new Key (KeyCode.ShiftKey);
+				key = new Key (KeyCode.ShiftMask);
 				return true;
 			}
 		}
@@ -563,416 +657,396 @@ public class Key : EventArgs, IEquatable<Key> {
 	/// <summary>
 	/// An uninitialized The <see cref="Key"/> object.
 	/// </summary>
-	public new static readonly Key Empty = new ();
+	public new static Key Empty => new ();
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Backspace key.
 	/// </summary>
-	public static readonly Key Backspace = new (KeyCode.Backspace);
+	public static Key Backspace => new (KeyCode.Backspace);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the tab key (forwards tab key).
 	/// </summary>
-	public static readonly Key Tab = new (KeyCode.Tab);
+	public static Key Tab => new (KeyCode.Tab);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the return key.
 	/// </summary>
-	public static readonly Key Enter = new (KeyCode.Enter);
+	public static Key Enter => new (KeyCode.Enter);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the clear key.
 	/// </summary>
-	public static readonly Key Clear = new (KeyCode.Clear);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Shift key.
-	/// </summary>
-	public static readonly Key Shift = new (KeyCode.ShiftKey);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Ctrl key.
-	/// </summary>
-	public static readonly Key Ctrl = new (KeyCode.CtrlKey);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the Alt key.
-	/// </summary>
-	public static readonly Key Alt = new (KeyCode.AltKey);
-
-	/// <summary>
-	/// The <see cref="Key"/> object for the CapsLock key.
-	/// </summary>
-	public static readonly Key CapsLock = new (KeyCode.CapsLock);
+	public static Key Clear => new (KeyCode.Clear);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Escape key.
 	/// </summary>
-	public static readonly Key Esc = new (KeyCode.Esc);
+	public static Key Esc => new (KeyCode.Esc);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Space bar key.
 	/// </summary>
-	public static readonly Key Space = new (KeyCode.Space);
+	public static Key Space => new (KeyCode.Space);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 0 key.
 	/// </summary>
-	public static readonly Key D0 = new (KeyCode.D0);
+	public static Key D0 => new (KeyCode.D0);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 1 key.
 	/// </summary>
-	public static readonly Key D1 = new (KeyCode.D1);
+	public static Key D1 => new (KeyCode.D1);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 2 key.
 	/// </summary>
-	public static readonly Key D2 = new (KeyCode.D2);
+	public static Key D2 => new (KeyCode.D2);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 3 key.
 	/// </summary>
-	public static readonly Key D3 = new (KeyCode.D3);
+	public static Key D3 => new (KeyCode.D3);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 4 key.
 	/// </summary>
-	public static readonly Key D4 = new (KeyCode.D4);
+	public static Key D4 => new (KeyCode.D4);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 5 key.
 	/// </summary>
-	public static readonly Key D5 = new (KeyCode.D5);
+	public static Key D5 => new (KeyCode.D5);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 6 key.
 	/// </summary>
-	public static readonly Key D6 = new (KeyCode.D6);
+	public static Key D6 => new (KeyCode.D6);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 7 key.
 	/// </summary>
-	public static readonly Key D7 = new (KeyCode.D7);
+	public static Key D7 => new (KeyCode.D7);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 8 key.
 	/// </summary>
-	public static readonly Key D8 = new (KeyCode.D8);
+	public static Key D8 => new (KeyCode.D8);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for 9 key.
 	/// </summary>
-	public static readonly Key D9 = new (KeyCode.D9);
+	public static Key D9 => new (KeyCode.D9);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the A key (un-shifted). Use <c>Key.A.WithShift</c> for uppercase 'A'.
 	/// </summary>
-	public static readonly Key A = new (KeyCode.A);
+	public static Key A => new (KeyCode.A);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the B key (un-shifted). Use <c>Key.B.WithShift</c> for uppercase 'B'.
 	/// </summary>
-	public static readonly Key B = new (KeyCode.B);
+	public static Key B => new (KeyCode.B);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the C key (un-shifted). Use <c>Key.C.WithShift</c> for uppercase 'C'.
 	/// </summary>
-	public static readonly Key C = new (KeyCode.C);
+	public static Key C => new (KeyCode.C);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the D key (un-shifted). Use <c>Key.D.WithShift</c> for uppercase 'D'.
 	/// </summary>
-	public static readonly Key D = new (KeyCode.D);
+	public static Key D => new (KeyCode.D);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the E key (un-shifted). Use <c>Key.E.WithShift</c> for uppercase 'E'.
 	/// </summary>
-	public static readonly Key E = new (KeyCode.E);
+	public static Key E => new (KeyCode.E);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the F key (un-shifted). Use <c>Key.F.WithShift</c> for uppercase 'F'.
 	/// </summary>
-	public static readonly Key F = new (KeyCode.F);
+	public static Key F => new (KeyCode.F);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the G key (un-shifted). Use <c>Key.G.WithShift</c> for uppercase 'G'.
 	/// </summary>
-	public static readonly Key G = new (KeyCode.G);
+	public static Key G => new (KeyCode.G);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the H key (un-shifted). Use <c>Key.H.WithShift</c> for uppercase 'H'.
 	/// </summary>
-	public static readonly Key H = new (KeyCode.H);
+	public static Key H => new (KeyCode.H);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the I key (un-shifted). Use <c>Key.I.WithShift</c> for uppercase 'I'.
 	/// </summary>
-	public static readonly Key I = new (KeyCode.I);
+	public static Key I => new (KeyCode.I);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the J key (un-shifted). Use <c>Key.J.WithShift</c> for uppercase 'J'.
 	/// </summary>
-	public static readonly Key J = new (KeyCode.J);
+	public static Key J => new (KeyCode.J);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the K key (un-shifted). Use <c>Key.K.WithShift</c> for uppercase 'K'.
 	/// </summary>
-	public static readonly Key K = new (KeyCode.K);
+	public static Key K => new (KeyCode.K);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the L key (un-shifted). Use <c>Key.L.WithShift</c> for uppercase 'L'.
 	/// </summary>
-	public static readonly Key L = new (KeyCode.L);
+	public static Key L => new (KeyCode.L);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the M key (un-shifted). Use <c>Key.M.WithShift</c> for uppercase 'M'.
 	/// </summary>
-	public static readonly Key M = new (KeyCode.M);
+	public static Key M => new (KeyCode.M);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the N key (un-shifted). Use <c>Key.N.WithShift</c> for uppercase 'N'.
 	/// </summary>
-	public static readonly Key N = new (KeyCode.N);
+	public static Key N => new (KeyCode.N);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the O key (un-shifted). Use <c>Key.O.WithShift</c> for uppercase 'O'.
 	/// </summary>
-	public static readonly Key O = new (KeyCode.O);
+	public static Key O => new (KeyCode.O);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the P key (un-shifted). Use <c>Key.P.WithShift</c> for uppercase 'P'.
 	/// </summary>
-	public static readonly Key P = new (KeyCode.P);
+	public static Key P => new (KeyCode.P);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Q key (un-shifted). Use <c>Key.Q.WithShift</c> for uppercase 'Q'.
 	/// </summary>
-	public static readonly Key Q = new (KeyCode.Q);
+	public static Key Q => new (KeyCode.Q);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the R key (un-shifted). Use <c>Key.R.WithShift</c> for uppercase 'R'.
 	/// </summary>
-	public static readonly Key R = new (KeyCode.R);
+	public static Key R => new (KeyCode.R);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the S key (un-shifted). Use <c>Key.S.WithShift</c> for uppercase 'S'.
 	/// </summary>
-	public static readonly Key S = new (KeyCode.S);
+	public static Key S => new (KeyCode.S);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the T key (un-shifted). Use <c>Key.T.WithShift</c> for uppercase 'T'.
 	/// </summary>
-	public static readonly Key T = new (KeyCode.T);
+	public static Key T => new (KeyCode.T);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the U key (un-shifted). Use <c>Key.U.WithShift</c> for uppercase 'U'.
 	/// </summary>
-	public static readonly Key U = new (KeyCode.U);
+	public static Key U => new (KeyCode.U);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the V key (un-shifted). Use <c>Key.V.WithShift</c> for uppercase 'V'.
 	/// </summary>
-	public static readonly Key V = new (KeyCode.V);
+	public static Key V => new (KeyCode.V);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the W key (un-shifted). Use <c>Key.W.WithShift</c> for uppercase 'W'.
 	/// </summary>
-	public static readonly Key W = new (KeyCode.W);
+	public static Key W => new (KeyCode.W);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the X key (un-shifted). Use <c>Key.X.WithShift</c> for uppercase 'X'.
 	/// </summary>
-	public static readonly Key X = new (KeyCode.X);
+	public static Key X => new (KeyCode.X);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Y key (un-shifted). Use <c>Key.Y.WithShift</c> for uppercase 'Y'.
 	/// </summary>
-	public static readonly Key Y = new (KeyCode.Y);
+	public static Key Y => new (KeyCode.Y);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Z key (un-shifted). Use <c>Key.Z.WithShift</c> for uppercase 'Z'.
 	/// </summary>
-	public static readonly Key Z = new (KeyCode.Z);
+	public static Key Z => new (KeyCode.Z);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Delete key.
 	/// </summary>
-	public static readonly Key Delete = new (KeyCode.Delete);
+	public static Key Delete => new (KeyCode.Delete);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for the Cursor up key.
 	/// </summary>
-	public static readonly Key CursorUp = new (KeyCode.CursorUp);
+	public static Key CursorUp => new (KeyCode.CursorUp);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Cursor down key.
 	/// </summary>
-	public static readonly Key CursorDown = new (KeyCode.CursorDown);
+	public static Key CursorDown => new (KeyCode.CursorDown);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Cursor left key.
 	/// </summary>
-	public static readonly Key CursorLeft = new (KeyCode.CursorLeft);
+	public static Key CursorLeft => new (KeyCode.CursorLeft);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Cursor right key.
 	/// </summary>
-	public static readonly Key CursorRight = new (KeyCode.CursorRight);
+	public static Key CursorRight => new (KeyCode.CursorRight);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Page Up key.
 	/// </summary>
-	public static readonly Key PageUp = new (KeyCode.PageUp);
+	public static Key PageUp => new (KeyCode.PageUp);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Page Down key.
 	/// </summary>
-	public static readonly Key PageDown = new (KeyCode.PageDown);
+	public static Key PageDown => new (KeyCode.PageDown);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Home key.
 	/// </summary>
-	public static readonly Key Home = new (KeyCode.Home);
+	public static Key Home => new (KeyCode.Home);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for End key.
 	/// </summary>
-	public static readonly Key End = new (KeyCode.End);
+	public static Key End => new (KeyCode.End);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Insert Character key.
 	/// </summary>
-	public static readonly Key InsertChar = new (KeyCode.InsertChar);
+	public static Key InsertChar => new (KeyCode.Insert);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Delete Character key.
 	/// </summary>
-	public static readonly Key DeleteChar = new (KeyCode.DeleteChar);
+	public static Key DeleteChar => new (KeyCode.Delete);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for Print Screen key.
 	/// </summary>
-	public static readonly Key PrintScreen = new (KeyCode.PrintScreen);
+	public static Key PrintScreen => new (KeyCode.PrintScreen);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F1 key.
 	/// </summary>
-	public static readonly Key F1 = new (KeyCode.F1);
+	public static Key F1 => new (KeyCode.F1);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F2 key.
 	/// </summary>
-	public static readonly Key F2 = new (KeyCode.F2);
+	public static Key F2 => new (KeyCode.F2);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F3 key.
 	/// </summary>
-	public static readonly Key F3 = new (KeyCode.F3);
+	public static Key F3 => new (KeyCode.F3);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F4 key.
 	/// </summary>
-	public static readonly Key F4 = new (KeyCode.F4);
+	public static Key F4 => new (KeyCode.F4);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F5 key.
 	/// </summary>
-	public static readonly Key F5 = new (KeyCode.F5);
+	public static Key F5 => new (KeyCode.F5);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F6 key.
 	/// </summary>
-	public static readonly Key F6 = new (KeyCode.F6);
+	public static Key F6 => new (KeyCode.F6);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F7 key.
 	/// </summary>
-	public static readonly Key F7 = new (KeyCode.F7);
+	public static Key F7 => new (KeyCode.F7);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F8 key.
 	/// </summary>
-	public static readonly Key F8 = new (KeyCode.F8);
+	public static Key F8 => new (KeyCode.F8);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F9 key.
 	/// </summary>
-	public static readonly Key F9 = new (KeyCode.F9);
+	public static Key F9 => new (KeyCode.F9);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F10 key.
 	/// </summary>
-	public static readonly Key F10 = new (KeyCode.F10);
+	public static Key F10 => new (KeyCode.F10);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F11 key.
 	/// </summary>
-	public static readonly Key F11 = new (KeyCode.F11);
+	public static Key F11 => new (KeyCode.F11);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F12 key.
 	/// </summary>
-	public static readonly Key F12 = new (KeyCode.F12);
+	public static Key F12 => new (KeyCode.F12);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F13 key.
 	/// </summary>
-	public static readonly Key F13 = new (KeyCode.F13);
+	public static Key F13 => new (KeyCode.F13);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F14 key.
 	/// </summary>
-	public static readonly Key F14 = new (KeyCode.F14);
+	public static Key F14 => new (KeyCode.F14);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F15 key.
 	/// </summary>
-	public static readonly Key F15 = new (KeyCode.F15);
+	public static Key F15 => new (KeyCode.F15);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F16 key.
 	/// </summary>
-	public static readonly Key F16 = new (KeyCode.F16);
+	public static Key F16 => new (KeyCode.F16);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F17 key.
 	/// </summary>
-	public static readonly Key F17 = new (KeyCode.F17);
+	public static Key F17 => new (KeyCode.F17);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F18 key.
 	/// </summary>
-	public static readonly Key F18 = new (KeyCode.F18);
+	public static Key F18 => new (KeyCode.F18);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F19 key.
 	/// </summary>
-	public static readonly Key F19 = new (KeyCode.F19);
+	public static Key F19 => new (KeyCode.F19);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F20 key.
 	/// </summary>
-	public static readonly Key F20 = new (KeyCode.F20);
+	public static Key F20 => new (KeyCode.F20);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F21 key.
 	/// </summary>
-	public static readonly Key F21 = new (KeyCode.F21);
+	public static Key F21 => new (KeyCode.F21);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F22 key.
 	/// </summary>
-	public static readonly Key F22 = new (KeyCode.F22);
+	public static Key F22 => new (KeyCode.F22);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F23 key.
 	/// </summary>
-	public static readonly Key F23 = new (KeyCode.F23);
+	public static Key F23 => new (KeyCode.F23);
 
 	/// <summary>
 	/// The <see cref="Key"/> object for F24 key.
 	/// </summary>
-	public static readonly Key F24 = new (KeyCode.F24);
+	public static Key F24 => new (KeyCode.F24);
 	#endregion
 }

+ 6 - 0
Terminal.Gui/Input/Responder.cs

@@ -47,6 +47,11 @@ public class Responder : IDisposable {
 	}
 #endif
 
+	/// <summary>
+	/// Event raised when <see cref="Dispose()"/> has been called to signal that this object is being disposed.
+	/// </summary>
+	public event EventHandler Disposing;
+
 	/// <summary>
 	/// Gets or sets a value indicating whether this <see cref="Responder"/> can focus.
 	/// </summary>
@@ -186,6 +191,7 @@ public class Responder : IDisposable {
 	public void Dispose ()
 	{
 		// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+		Disposing?.Invoke (this, EventArgs.Empty);
 		Dispose (disposing: true);
 		GC.SuppressFinalize (this);
 #if DEBUG_IDISPOSABLE

+ 3 - 9
Terminal.Gui/Resources/config.json

@@ -16,15 +16,9 @@
   // to throw exceptions. 
   "ConfigurationManager.ThrowOnJsonErrors": false,
 
-  "Application.AlternateBackwardKey": {
-    "Key": "Ctrl+PageUp"
-  },
-  "Application.AlternateForwardKey": {
-    "Key": "Ctrl+PageDown"
-  },
-  "Application.QuitKey": {
-    "Key": "Ctrl+Q"
-  },
+  "Application.AlternateBackwardKey": "Ctrl+PageUp",
+  "Application.AlternateForwardKey": "Ctrl+PageDown",
+  "Application.QuitKey": "Ctrl+Q",
   "Application.IsMouseDisabled": false,
   "Theme": "Default",
   "Themes": [

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

@@ -113,5 +113,6 @@
     <EnableSourceLink>true</EnableSourceLink>
     <!--<DebugType>Embedded</DebugType>-->
     <Authors>Miguel de Icaza, Tig Kindel (@tig), @BDisp</Authors>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 </Project>

+ 179 - 182
Terminal.Gui/Text/CollectionNavigatorBase.cs

@@ -1,217 +1,214 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. 
+/// The <see cref="SearchString"/> is used to find the next item in the collection that matches the search string
+/// when <see cref="GetNextMatchingItem(int, char)"/> is called.
+/// <para>
+/// If the user types keystrokes that can't be found in the collection, 
+/// the search string is cleared and the next item is found that starts with the last keystroke.
+/// </para>
+/// <para>
+/// If the user pauses keystrokes for a short time (see <see cref="TypingDelay"/>), the search string is cleared.
+/// </para>
+/// </summary>
+public abstract class CollectionNavigatorBase {
+	DateTime _lastKeystroke = DateTime.Now;
 
-namespace Terminal.Gui {
 	/// <summary>
-	/// Navigates a collection of items using keystrokes. The keystrokes are used to build a search string. 
-	/// The <see cref="SearchString"/> is used to find the next item in the collection that matches the search string
-	/// when <see cref="GetNextMatchingItem(int, char)"/> is called.
-	/// <para>
-	/// If the user types keystrokes that can't be found in the collection, 
-	/// the search string is cleared and the next item is found that starts with the last keystroke.
-	/// </para>
-	/// <para>
-	/// If the user pauses keystrokes for a short time (see <see cref="TypingDelay"/>), the search string is cleared.
-	/// </para>
+	/// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is
+	/// reset on each call to <see cref="GetNextMatchingItem(int, char)"/>. The default is 500ms.
 	/// </summary>
-	public abstract class CollectionNavigatorBase {
-
-		DateTime lastKeystroke = DateTime.Now;
-		/// <summary>
-		/// Gets or sets the number of milliseconds to delay before clearing the search string. The delay is
-		/// reset on each call to <see cref="GetNextMatchingItem(int, char)"/>. The default is 500ms.
-		/// </summary>
-		public int TypingDelay { get; set; } = 500;
-
-		/// <summary>
-		/// The compararer function to use when searching the collection.
-		/// </summary>
-		public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
-
-		/// <summary>
-		/// This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.
-		/// </summary>
-		public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
-
-		private string _searchString = "";
-		/// <summary>
-		/// Gets the current search string. This includes the set of keystrokes that have been pressed
-		/// since the last unsuccessful match or after <see cref="TypingDelay"/>) milliseconds. Useful for debugging.
-		/// </summary>
-		public string SearchString {
-			get => _searchString;
-			private set {
-				_searchString = value;
-				OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
-			}
-		}
+	public int TypingDelay { get; set; } = 500;
 
-		/// <summary>
-		/// Invoked when the <see cref="SearchString"/> changes. Useful for debugging. Invokes the <see cref="SearchStringChanged"/> event.
-		/// </summary>
-		/// <param name="e"></param>
-		public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e)
-		{
-			SearchStringChanged?.Invoke (this, e);
-		}
+	/// <summary>
+	/// The comparer function to use when searching the collection.
+	/// </summary>
+	public StringComparer Comparer { get; set; } = StringComparer.InvariantCultureIgnoreCase;
 
-		/// <summary>
-		/// Gets the index of the next item in the collection that matches the current <see cref="SearchString"/> plus the provided character (typically
-		/// from a key press).
-		/// </summary>
-		/// <param name="currentIndex">The index in the collection to start the search from.</param>
-		/// <param name="keyStruck">The character of the key the user pressed.</param>
-		/// <returns>The index of the item that matches what the user has typed. 
-		/// Returns <see langword="-1"/> if no item in the collection matched.</returns>
-		public int GetNextMatchingItem (int currentIndex, char keyStruck)
-		{
-			if (!char.IsControl (keyStruck)) {
-
-				// maybe user pressed 'd' and now presses 'd' again.
-				// a candidate search is things that begin with "dd"
-				// but if we find none then we must fallback on cycling
-				// d instead and discard the candidate state
-				string candidateState = "";
-
-				// is it a second or third (etc) keystroke within a short time
-				if (SearchString.Length > 0 && DateTime.Now - lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
-					// "dd" is a candidate
-					candidateState = SearchString + keyStruck;
-				} else {
-					// its a fresh keystroke after some time
-					// or its first ever key press
-					SearchString = new string (keyStruck, 1);
-				}
+	/// <summary>
+	/// This event is invoked when <see cref="SearchString"/>  changes. Useful for debugging.
+	/// </summary>
+	public event EventHandler<KeystrokeNavigatorEventArgs> SearchStringChanged;
 
-				var idxCandidate = GetNextMatchingItem (currentIndex, candidateState,
-					// prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
-					candidateState.Length > 1);
+	string _searchString = "";
 
-				if (idxCandidate != -1) {
-					// found "dd" so candidate searchstring is accepted
-					lastKeystroke = DateTime.Now;
-					SearchString = candidateState;
-					return idxCandidate;
-				}
+	/// <summary>
+	/// Gets the current search string. This includes the set of keystrokes that have been pressed
+	/// since the last unsuccessful match or after <see cref="TypingDelay"/>) milliseconds. Useful for debugging.
+	/// </summary>
+	public string SearchString {
+		get => _searchString;
+		private set {
+			_searchString = value;
+			OnSearchStringChanged (new KeystrokeNavigatorEventArgs (value));
+		}
+	}
 
-				//// nothing matches "dd" so discard it as a candidate
-				//// and just cycle "d" instead
-				lastKeystroke = DateTime.Now;
-				idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
-
-				// if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
-				// instead of "can" + 'd').
-				if (SearchString.Length > 1 && idxCandidate == -1) {
-					// ignore it since we're still within the typing delay
-					// don't add it to SearchString either
-					return currentIndex;
-				}
+	/// <summary>
+	/// Invoked when the <see cref="SearchString"/> changes. Useful for debugging. Invokes the <see cref="SearchStringChanged"/> event.
+	/// </summary>
+	/// <param name="e"></param>
+	public virtual void OnSearchStringChanged (KeystrokeNavigatorEventArgs e) => SearchStringChanged?.Invoke (this, e);
 
-				// if no changes to current state manifested
-				if (idxCandidate == currentIndex || idxCandidate == -1) {
-					// clear history and treat as a fresh letter
-					ClearSearchString ();
+	/// <summary>
+	/// Gets the index of the next item in the collection that matches the current <see cref="SearchString"/> plus the provided character (typically
+	/// from a key press).
+	/// </summary>
+	/// <param name="currentIndex">The index in the collection to start the search from.</param>
+	/// <param name="keyStruck">The character of the key the user pressed.</param>
+	/// <returns>The index of the item that matches what the user has typed. 
+	/// Returns <see langword="-1"/> if no item in the collection matched.</returns>
+	public int GetNextMatchingItem (int currentIndex, char keyStruck)
+	{
+		if (!char.IsControl (keyStruck)) {
+
+			// maybe user pressed 'd' and now presses 'd' again.
+			// a candidate search is things that begin with "dd"
+			// but if we find none then we must fallback on cycling
+			// d instead and discard the candidate state
+			string candidateState = "";
+
+			// is it a second or third (etc) keystroke within a short time
+			if (SearchString.Length > 0 && DateTime.Now - _lastKeystroke < TimeSpan.FromMilliseconds (TypingDelay)) {
+				// "dd" is a candidate
+				candidateState = SearchString + keyStruck;
+			} else {
+				// its a fresh keystroke after some time
+				// or its first ever key press
+				SearchString = new string (keyStruck, 1);
+			}
 
-					// match on the fresh letter alone
-					SearchString = new string (keyStruck, 1);
-					idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
-					return idxCandidate == -1 ? currentIndex : idxCandidate;
-				}
+			int idxCandidate = GetNextMatchingItem (currentIndex, candidateState,
+				// prefer not to move if there are multiple characters e.g. "ca" + 'r' should stay on "car" and not jump to "cart"
+				candidateState.Length > 1);
 
-				// Found another "d" or just leave index as it was
+			if (idxCandidate != -1) {
+				// found "dd" so candidate search string is accepted
+				_lastKeystroke = DateTime.Now;
+				SearchString = candidateState;
 				return idxCandidate;
+			}
 
-			} else {
-				// clear state because keypress was a control char
-				ClearSearchString ();
+			//// nothing matches "dd" so discard it as a candidate
+			//// and just cycle "d" instead
+			_lastKeystroke = DateTime.Now;
+			idxCandidate = GetNextMatchingItem (currentIndex, candidateState);
 
-				// control char indicates no selection
-				return -1;
+			// if a match wasn't found, the user typed a 'wrong' key in their search ("can" + 'z'
+			// instead of "can" + 'd').
+			if (SearchString.Length > 1 && idxCandidate == -1) {
+				// ignore it since we're still within the typing delay
+				// don't add it to SearchString either
+				return currentIndex;
 			}
-		}
 
-		/// <summary>
-		/// Gets the index of the next item in the collection that matches <paramref name="search"/>. 
-		/// </summary>
-		/// <param name="currentIndex">The index in the collection to start the search from.</param>
-		/// <param name="search">The search string to use.</param>
-		/// <param name="minimizeMovement">Set to <see langword="true"/> to stop the search on the first match
-		/// if there are multiple matches for <paramref name="search"/>.
-		/// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If <see langword="false"/> (the default), 
-		/// the next matching item will be returned, even if it is above in the collection.
-		/// </param>
-		/// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
-		internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
-		{
-			if (string.IsNullOrEmpty (search)) {
-				return -1;
+			// if no changes to current state manifested
+			if (idxCandidate == currentIndex || idxCandidate == -1) {
+				// clear history and treat as a fresh letter
+				ClearSearchString ();
+
+				// match on the fresh letter alone
+				SearchString = new string (keyStruck, 1);
+				idxCandidate = GetNextMatchingItem (currentIndex, SearchString);
+				return idxCandidate == -1 ? currentIndex : idxCandidate;
 			}
 
-			var collectionLength = GetCollectionLength ();
+			// Found another "d" or just leave index as it was
+			return idxCandidate;
 
-			if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) {
-				// we are already at a match
-				if (minimizeMovement) {
-					// if we would rather not jump around (e.g. user is typing lots of text to get this match)
-					return currentIndex;
-				}
+		} else {
+			// clear state because keypress was a control char
+			ClearSearchString ();
 
-				for (int i = 1; i < collectionLength; i++) {
-					//circular
-					var idxCandidate = (i + currentIndex) % collectionLength;
-					if (IsMatch (search, ElementAt (idxCandidate))) {
-						return idxCandidate;
-					}
-				}
+			// control char indicates no selection
+			return -1;
+		}
+	}
+
+	/// <summary>
+	/// Gets the index of the next item in the collection that matches <paramref name="search"/>. 
+	/// </summary>
+	/// <param name="currentIndex">The index in the collection to start the search from.</param>
+	/// <param name="search">The search string to use.</param>
+	/// <param name="minimizeMovement">Set to <see langword="true"/> to stop the search on the first match
+	/// if there are multiple matches for <paramref name="search"/>.
+	/// e.g. "ca" + 'r' should stay on "car" and not jump to "cart". If <see langword="false"/> (the default), 
+	/// the next matching item will be returned, even if it is above in the collection.
+	/// </param>
+	/// <returns>The index of the next matching item or <see langword="-1"/> if no match was found.</returns>
+	internal int GetNextMatchingItem (int currentIndex, string search, bool minimizeMovement = false)
+	{
+		if (string.IsNullOrEmpty (search)) {
+			return -1;
+		}
 
-				// nothing else starts with the search term
+		int collectionLength = GetCollectionLength ();
+
+		if (currentIndex != -1 && currentIndex < collectionLength && IsMatch (search, ElementAt (currentIndex))) {
+			// we are already at a match
+			if (minimizeMovement) {
+				// if we would rather not jump around (e.g. user is typing lots of text to get this match)
 				return currentIndex;
-			} else {
-				// search terms no longer match the current selection or there is none
-				for (int i = 0; i < collectionLength; i++) {
-					if (IsMatch (search, ElementAt (i))) {
-						return i;
-					}
+			}
+
+			for (int i = 1; i < collectionLength; i++) {
+				//circular
+				int idxCandidate = (i + currentIndex) % collectionLength;
+				if (IsMatch (search, ElementAt (idxCandidate))) {
+					return idxCandidate;
 				}
+			}
 
-				// Nothing matches
-				return -1;
+			// nothing else starts with the search term
+			return currentIndex;
+		} else {
+			// search terms no longer match the current selection or there is none
+			for (int i = 0; i < collectionLength; i++) {
+				if (IsMatch (search, ElementAt (i))) {
+					return i;
+				}
 			}
+
+			// Nothing matches
+			return -1;
 		}
+	}
 
-		/// <summary>
-		/// Return the number of elements in the collection
-		/// </summary>
-		protected abstract int GetCollectionLength ();
+	/// <summary>
+	/// Return the number of elements in the collection
+	/// </summary>
+	protected abstract int GetCollectionLength ();
 
-		private bool IsMatch (string search, object value)
-		{
-			return value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false;
-		}
+	bool IsMatch (string search, object value) => value?.ToString ().StartsWith (search, StringComparison.InvariantCultureIgnoreCase) ?? false;
 
-		/// <summary>
-		/// Returns the collection being navigated element at <paramref name="idx"/>.
-		/// </summary>
-		/// <returns></returns>
-		protected abstract object ElementAt (int idx);
+	/// <summary>
+	/// Returns the collection being navigated element at <paramref name="idx"/>.
+	/// </summary>
+	/// <returns></returns>
+	protected abstract object ElementAt (int idx);
 
-		private void ClearSearchString ()
-		{
-			SearchString = "";
-			lastKeystroke = DateTime.Now;
-		}
+	void ClearSearchString ()
+	{
+		SearchString = "";
+		_lastKeystroke = DateTime.Now;
+	}
 
-		/// <summary>
-		/// Returns true if <paramref name="a"/> is a searchable key
-		/// (e.g. letters, numbers, etc) that are valid to pass to this
-		/// class for search filtering.
-		/// </summary>
-		/// <param name="a"></param>
-		/// <returns></returns>
-		public static bool IsCompatibleKey (Key a)
-		{
-			return !a.IsAlt && !a.IsCtrl;
-		}
+	/// <summary>
+	/// Returns true if <paramref name="a"/> is a searchable key
+	/// (e.g. letters, numbers, etc) that are valid to pass to this
+	/// class for search filtering.
+	/// </summary>
+	/// <param name="a"></param>
+	/// <returns></returns>
+	public static bool IsCompatibleKey (Key a)
+	{
+		var rune = a.AsRune;
+		return rune != default && !Rune.IsControl (rune);
 	}
-}
+}

+ 128 - 56
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -30,22 +30,31 @@ public partial class View {
 	Rect _frame;
 
 	/// <summary>
-	/// Gets or sets the frame for the view. The frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
+	/// Gets or sets location and size of the view. The frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
 	/// </summary>
-	/// <value>The frame.</value>
+	/// <value>The rectangle describing the location and size of the view, in coordinates relative to the <see cref="SuperView"/>.</value>
 	/// <remarks>
 	/// <para>
-	///    Change the Frame when using the <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout style to move or resize views. 
+	/// Change the Frame when using the <see cref="LayoutStyle.Absolute"/> layout style to move or resize views.
 	/// </para>
 	/// <para>
-	///    Altering the Frame of a view will trigger the redrawing of the
-	///    view as well as the redrawing of the affected regions of the <see cref="SuperView"/>.
+	/// Altering the Frame will change <see cref="LayoutStyle"/> to <see cref="LayoutStyle.Absolute"/>.
+	/// Additionally, <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> will be set
+	/// to the values of the Frame (using <see cref="Pos.PosAbsolute"/> and <see cref="Dim.DimAbsolute"/>).
+	/// </para>
+	/// <para>
+	/// Altering the Frame will eventually (when the view is next drawn) cause the <see cref="LayoutSubview(View, Rect)"/>
+	/// and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	/// </para>
 	/// </remarks>
 	public virtual Rect Frame {
 		get => _frame;
 		set {
 			_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
+			//X = _frame.X;
+			//Y = _frame.Y;
+			//Width = _frame.Width;
+			//Height = _frame.Height;
 			if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
 				LayoutFrames ();
 				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
@@ -161,7 +170,7 @@ public partial class View {
 	/// 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 Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
+	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
@@ -171,7 +180,9 @@ public partial class View {
 	{
 		void ThicknessChangedHandler (object sender, EventArgs e)
 		{
-			LayoutFrames ();
+			if (IsInitialized) {
+				LayoutFrames ();
+			}
 			SetNeedsLayout ();
 			SetNeedsDisplay ();
 		}
@@ -207,41 +218,74 @@ public partial class View {
 
 	/// <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
+	/// <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 => _layoutStyle;
+		get {
+			return _layoutStyle;
+			//if ((X == null || X is Pos.PosAbsolute) && (Y == null || Y is Pos.PosAbsolute) &&
+			//(Width == null || Width is Dim.DimAbsolute) && (Height == null || Height is Dim.DimAbsolute)) {
+			//	return LayoutStyle.Absolute;
+			//} else {
+			//	return LayoutStyle.Computed;
+			//}
+		}
 		set {
 			_layoutStyle = value;
+			//switch (_layoutStyle) {
+			//case LayoutStyle.Absolute:
+			//	X = Frame.X;
+			//	Y = Frame.Y;
+			//	Width = Frame.Width;
+			//	Height = Frame.Height;
+			//	break;
+
+			//case LayoutStyle.Computed:
+			//	X ??= Frame.X;
+			//	Y ??= Frame.Y;
+			//	Width ??= Frame.Width;
+			//	Height ??= Frame.Height;
+			//	break;
+			//}
 			SetNeedsLayout ();
 		}
 	}
 
 	/// <summary>
-	/// The view's content area.
-	/// <para>
-	/// SubViews are positioned relative to Bounds.
-	/// </para>
+	/// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and content are presented.
+	/// </summary>
+	/// <value>The rectangle describing the location and size of the area where the views' subviews and content are drawn.</value>
+	/// <remarks>
 	/// <para>
-	/// Drawing is clipped to Bounds (<see cref="Draw()"/> clips drawing to Bounds.<see cref="Rect.Size">Size</see>).
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value of Bounds is indeterminate until the 
+	/// view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been called.
 	/// </para>
 	/// <para>
-	/// Mouse events are reported relative to Bounds.
+	/// Updates to the Bounds updates <see cref="Frame"/>, and has the same side effects as updating the <see cref="Frame"/>.
 	/// </para>
-	/// </summary>
-	/// <value>The view's content area.</value>
-	/// <remarks>
 	/// <para>
-	/// The <see cref="Rect.Location"/> of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use 
-	/// <see cref="GetBoundsOffset"/>.
+	/// Altering the Bounds will eventually (when the view is next drawn) cause the <see cref="LayoutSubview(View, Rect)"/>
+	/// and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	/// </para>
 	/// <para>
-	/// When using <see cref="LayoutStyle.Computed"/>, Bounds is not valid until after the view has been initialized (after <see cref="EndInit"/> has been called and <see cref="Initialized"/>
-	/// has fired). Accessing this property before the view is initialized is considered an error./>
+	/// Because <see cref="Bounds"/> coordinates are relative to the upper-left corner of the <see cref="View"/>, 
+	/// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). 
+	/// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents.
 	/// </para>
 	/// </remarks>
 	public virtual Rect Bounds {
@@ -249,7 +293,6 @@ public partial class View {
 #if DEBUG
 			if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
 				Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
-				Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
 			}
 #endif // DEBUG
 			//var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
@@ -280,19 +323,28 @@ public partial class View {
 	Pos _x, _y;
 
 	/// <summary>
-	/// Gets or sets the X position for the view (the column). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// Gets or sets the X position for the view (the column). 
 	/// </summary>
-	/// <value>The X Position.</value>
+	/// <value>The <see cref="Pos"/> object representing the X position.</value>
 	/// <remarks>
 	/// <para>
-	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the 
+	/// view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been called.
+	/// </para>
+	/// <para>
+	/// Changing this property will eventually (when the view is next drawn) cause the <see cref="LayoutSubview(View, Rect)"/> and
+	/// <see cref="OnDrawContent(Rect)"/> methods to be called.
+	/// </para>
+	/// <para>
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the <see cref="Frame"/> to be updated. If 
+	/// the new value is not of type <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
 	/// </para>
 	/// <para>
 	/// <see langword="null"/> is the same as <c>Pos.Absolute(0)</c>.
 	/// </para>
 	/// </remarks>
 	public Pos X {
-		get => VerifyIsInitialized (_x);
+		get => VerifyIsInitialized (_x, nameof(X));
 		set {
 			// BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
 
@@ -306,21 +358,29 @@ public partial class View {
 		}
 	}
 
-
 	/// <summary>
-	/// Gets or sets the Y position for the view (the row). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// Gets or sets the Y position for the view (the row). 
 	/// </summary>
-	/// <value>The X Position.</value>
+	/// <value>The <see cref="Pos"/> object representing the Y position.</value>
 	/// <remarks>
 	/// <para>
-	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the 
+	/// view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been called.
+	/// </para>
+	/// <para>
+	/// Changing this property will eventually (when the view is next drawn) cause the <see cref="LayoutSubview(View, Rect)"/> and
+	/// <see cref="OnDrawContent(Rect)"/> methods to be called.
+	/// </para>
+	/// <para>
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the <see cref="Frame"/> to be updated. If 
+	/// the new value is not of type <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
 	/// </para>
 	/// <para>
 	/// <see langword="null"/> is the same as <c>Pos.Absolute(0)</c>.
 	/// </para>
 	/// </remarks>
 	public Pos Y {
-		get => VerifyIsInitialized (_y);
+		get => VerifyIsInitialized (_y, nameof(Y));
 		set {
 			// BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
 
@@ -333,23 +393,29 @@ public partial class View {
 			OnResizeNeeded ();
 		}
 	}
+
 	Dim _width, _height;
 
 	/// <summary>
-	/// Gets or sets the width of the view. Only used when <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// Gets or sets the width of the view. 
 	/// </summary>
-	/// <value>The width.</value>
+	/// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
 	/// <remarks>
 	/// <para>
-	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property
-	/// has no effect and its value is indeterminate. 
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the 
+	/// view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been called.
+	/// </para>
+	/// <para>
+	/// Changing this property will eventually (when the view is next drawn) cause the <see cref="LayoutSubview(View, Rect)"/>
+	/// and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	/// </para>
 	/// <para>
-	/// <see langword="null"/> is the same as <c>Dim.Fill (0)</c>.
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the <see cref="Frame"/> to be updated. If 
+	/// the new value is not of type <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
 	/// </para>
 	/// </remarks>
 	public Dim Width {
-		get => VerifyIsInitialized (_width);
+		get => VerifyIsInitialized (_width, nameof (Width));
 		set {
 			// BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
 			if (ValidatePosDim) {
@@ -373,20 +439,25 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets the height of the view. Only used when <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// Gets or sets the height of the view. 
 	/// </summary>
-	/// <value>The width.</value>
+	/// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
 	/// <remarks>
 	/// <para>
-	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property
-	/// has no effect and its value is indeterminate. 
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the 
+	/// view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been called.
+	/// </para>
+	/// <para>
+	/// Changing this property will eventually (when the view is next drawn) cause the <see cref="LayoutSubview(View, Rect)"/>
+	/// and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	/// </para>
 	/// <para>
-	/// <see langword="null"/> is the same as <c>Dim.Fill (0)</c>.
+	/// If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the <see cref="Frame"/> to be updated. If 
+	/// the new value is not of type <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to <see cref="LayoutStyle.Computed"/>.
 	/// </para>
 	/// </remarks>
 	public Dim Height {
-		get => VerifyIsInitialized (_height);
+		get => VerifyIsInitialized (_height, nameof (Height));
 		set {
 			// BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
 			if (ValidatePosDim) {
@@ -410,22 +481,22 @@ public partial class View {
 	}
 
 	// Diagnostics to highlight when X or Y is read before the view has been initialized
-	Pos VerifyIsInitialized (Pos pos)
+	Pos VerifyIsInitialized (Pos pos, string member)
 	{
 #if DEBUG
 		if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
-			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug.");
+			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug.");
 		}
 #endif // DEBUG
 		return pos;
 	}
 
 	// Diagnostics to highlight when Width or Height is read before the view has been initialized
-	Dim VerifyIsInitialized (Dim dim)
+	Dim VerifyIsInitialized (Dim dim, string member)
 	{
 #if DEBUG
 		if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
-			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug.");
+			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug.");
 		}
 #endif // DEBUG		
 		return dim;
@@ -880,7 +951,7 @@ public partial class View {
 		// BUGBUG: This should really only work on initialized subviews
 		foreach (var v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) {
 			nNodes.Add (v);
-			if (v._layoutStyle != LayoutStyle.Computed) {
+			if (v.LayoutStyle != LayoutStyle.Computed) {
 				continue;
 			}
 			CollectPos (v.X, v, ref nNodes, ref nEdges);
@@ -973,7 +1044,6 @@ public partial class View {
 			Margin.Width = Frame.Size.Width;
 			Margin.Height = Frame.Size.Height;
 			Margin.SetNeedsLayout ();
-			Margin.LayoutSubviews ();
 			Margin.SetNeedsDisplay ();
 		}
 
@@ -985,7 +1055,6 @@ public partial class View {
 			Border.Width = border.Size.Width;
 			Border.Height = border.Size.Height;
 			Border.SetNeedsLayout ();
-			Border.LayoutSubviews ();
 			Border.SetNeedsDisplay ();
 		}
 
@@ -997,7 +1066,6 @@ public partial class View {
 			Padding.Width = padding.Size.Width;
 			Padding.Height = padding.Size.Height;
 			Padding.SetNeedsLayout ();
-			Padding.LayoutSubviews ();
 			Padding.SetNeedsDisplay ();
 		}
 	}
@@ -1007,7 +1075,13 @@ public partial class View {
 	/// response to the container view or terminal resizing.
 	/// </summary>
 	/// <remarks>
+	/// <para>
+	/// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore,
+	/// the behavior of this method is indeterminate if <see cref="IsInitialized"/> is <see langword="false"/>.
+	/// </para>
+	/// <para>
 	/// Raises the <see cref="LayoutComplete"/> event) before it returns.
+	/// </para>
 	/// </remarks>
 	public virtual void LayoutSubviews ()
 	{
@@ -1118,8 +1192,6 @@ public partial class View {
 				Width = newFrameSize.Width;
 			}
 		}
-		// BUGBUG: This call may be redundant
-		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 		return boundsChanged;
 	}
 

+ 438 - 451
Terminal.Gui/View/View.cs

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

+ 448 - 440
Terminal.Gui/View/ViewDrawing.cs

@@ -3,509 +3,517 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
-namespace Terminal.Gui {
-	public partial class View {
-
-		ColorScheme _colorScheme;
-
-		/// <summary>
-		/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
-		/// color scheme. 
-		/// </summary>
-		public virtual ColorScheme ColorScheme {
-			get {
-				if (_colorScheme == null) {
-					return SuperView?.ColorScheme;
-				}
-				return _colorScheme;
-			}
-			set {
-				if (_colorScheme != value) {
-					_colorScheme = value;
-					SetNeedsDisplay ();
-				}
+namespace Terminal.Gui;
+
+public partial class View {
+	ColorScheme _colorScheme;
+
+	/// <summary>
+	/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
+	/// color scheme. 
+	/// </summary>
+	public virtual ColorScheme ColorScheme {
+		get {
+			if (_colorScheme == null) {
+				return SuperView?.ColorScheme;
 			}
+			return _colorScheme;
 		}
-
-		/// <summary>
-		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
-		/// </summary>
-		/// <returns><see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
-		/// 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 GetNormalColor ()
-		{
-			ColorScheme cs = ColorScheme;
-			if (ColorScheme == null) {
-				cs = new ColorScheme ();
+		set {
+			if (_colorScheme != value) {
+				_colorScheme = value;
+				SetNeedsDisplay ();
 			}
-			return Enabled ? cs.Normal : cs.Disabled;
 		}
+	}
 
-		/// <summary>
-		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
-		/// </summary>
-		/// <returns><see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
-		/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-		/// If it's overridden can return other values.</returns>
-		public virtual Attribute GetFocusColor ()
-		{
-			return Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+	/// <summary>
+	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
+	/// </summary>
+	/// <returns><see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
+	/// If it's overridden can return other values.</returns>
+	public virtual Attribute GetNormalColor ()
+	{
+		var cs = ColorScheme;
+		if (ColorScheme == null) {
+			cs = new ColorScheme ();
 		}
+		return Enabled ? cs.Normal : cs.Disabled;
+	}
 
-		/// <summary>
-		/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
-		/// </summary>
-		/// <returns><see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
-		/// 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 ()
-		{
-			return Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled;
+	/// <summary>
+	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
+	/// </summary>
+	/// <returns><see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
+	/// If it's overridden can return other values.</returns>
+	public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
+
+	/// <summary>
+	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
+	/// </summary>
+	/// <returns><see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// 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;
+
+	/// <summary>
+	/// Displays the specified character in the specified column and row of the View.
+	/// </summary>
+	/// <param name="col">Column (view-relative).</param>
+	/// <param name="row">Row (view-relative).</param>
+	/// <param name="ch">Ch.</param>
+	public void AddRune (int col, int row, Rune ch)
+	{
+		if (row < 0 || col < 0) {
+			return;
 		}
-
-		/// <summary>
-		/// Displays the specified character in the specified column and row of the View.
-		/// </summary>
-		/// <param name="col">Column (view-relative).</param>
-		/// <param name="row">Row (view-relative).</param>
-		/// <param name="ch">Ch.</param>
-		public void AddRune (int col, int row, Rune ch)
-		{
-			if (row < 0 || col < 0) {
-				return;
-			}
-			if (row > _frame.Height - 1 || col > _frame.Width - 1) {
-				return;
-			}
-			Move (col, row);
-			Driver.AddRune (ch);
+		if (row > _frame.Height - 1 || col > _frame.Width - 1) {
+			return;
 		}
+		Move (col, row);
+		Driver.AddRune (ch);
+	}
 
-		/// <summary>
-		/// Clears <see cref="NeedsDisplay"/> and <see cref="SubViewNeedsDisplay"/>.
-		/// </summary>
-		protected void ClearNeedsDisplay ()
-		{
-			_needsDisplayRect = Rect.Empty;
-			_subViewNeedsDisplay = false;
-		}
+	/// <summary>
+	/// Clears <see cref="NeedsDisplay"/> and <see cref="SubViewNeedsDisplay"/>.
+	/// </summary>
+	protected void ClearNeedsDisplay ()
+	{
+		_needsDisplayRect = Rect.Empty;
+		_subViewNeedsDisplay = false;
+	}
 
-		// The view-relative region that needs to be redrawn. Marked internal for unit tests.
-		internal Rect _needsDisplayRect = Rect.Empty;
-
-		/// <summary>
-		/// Gets or sets whether the view needs to be redrawn.
-		/// </summary>
-		public bool NeedsDisplay {
-			get => _needsDisplayRect != Rect.Empty;
-			set {
-				if (value) {
-					SetNeedsDisplay ();
-				} else {
-					ClearNeedsDisplay ();
-				}
+	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
+	internal Rect _needsDisplayRect = Rect.Empty;
+
+	/// <summary>
+	/// Gets or sets whether the view needs to be redrawn.
+	/// </summary>
+	public bool NeedsDisplay {
+		get => _needsDisplayRect != Rect.Empty;
+		set {
+			if (value) {
+				SetNeedsDisplay ();
+			} else {
+				ClearNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Sets the area of this view needing to be redrawn to <see cref="Bounds"/>.
-		/// </summary>
-		public void SetNeedsDisplay ()
-		{
-			if (!IsInitialized) {
-				return;
-			}
+	/// <summary>
+	/// Sets the area of this view needing to be redrawn to <see cref="Bounds"/>.
+	/// </summary>
+	/// <remarks>
+	/// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>),
+	/// this method does nothing.
+	/// </remarks>
+	public void SetNeedsDisplay ()
+	{
+		if (IsInitialized) {
 			SetNeedsDisplay (Bounds);
 		}
+	}
 
-		/// <summary>
-		/// Expands the area of this view needing to be redrawn to include <paramref name="region"/>.
-		/// </summary>
-		/// <param name="region">The view-relative region that needs to be redrawn.</param>
-		public void SetNeedsDisplay (Rect region)
-		{
-			if (_needsDisplayRect.IsEmpty) {
-				_needsDisplayRect = region;
-			} 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 h = Math.Max (_needsDisplayRect.Height, region.Height);
-				_needsDisplayRect = new Rect (x, y, w, h);
-			}
-			_superView?.SetSubViewNeedsDisplay ();
-
-			if (_needsDisplayRect.X < Bounds.X ||
-				_needsDisplayRect.Y < Bounds.Y ||
-				_needsDisplayRect.Width > Bounds.Width ||
-				_needsDisplayRect.Height > Bounds.Height) {
-				Margin?.SetNeedsDisplay (Margin.Bounds);
-				Border?.SetNeedsDisplay (Border.Bounds);
-				Padding?.SetNeedsDisplay (Padding.Bounds);
-			}
+	/// <summary>
+	/// Expands the area of this view needing to be redrawn to include <paramref name="region"/>.
+	/// </summary>
+	/// <remarks>
+	/// If the view has not been initialized (<see cref="IsInitialized"/> is <see langword="false"/>),
+	/// the area to be redrawn will be the <paramref name="region"/>.
+	/// </remarks>
+	/// <param name="region">The Bounds-relative region that needs to be redrawn.</param>
+	public void SetNeedsDisplay (Rect region)
+	{
+		if (!IsInitialized) {
+			_needsDisplayRect = region;
+			return;
+		}
+		if (_needsDisplayRect.IsEmpty) {
+			_needsDisplayRect = region;
+		} else {
+			int x = Math.Min (_needsDisplayRect.X, region.X);
+			int y = Math.Min (_needsDisplayRect.Y, region.Y);
+			int w = Math.Max (_needsDisplayRect.Width, region.Width);
+			int h = Math.Max (_needsDisplayRect.Height, region.Height);
+			_needsDisplayRect = new Rect (x, y, w, h);
+		}
+		_superView?.SetSubViewNeedsDisplay ();
+
+		if (_needsDisplayRect.X < Bounds.X ||
+		_needsDisplayRect.Y < Bounds.Y ||
+		_needsDisplayRect.Width > Bounds.Width ||
+		_needsDisplayRect.Height > Bounds.Height) {
+			Margin?.SetNeedsDisplay (Margin.Bounds);
+			Border?.SetNeedsDisplay (Border.Bounds);
+			Padding?.SetNeedsDisplay (Padding.Bounds);
+		}
 
-			if (_subviews == null) {
-				return;
-			}
+		if (_subviews == null) {
+			return;
+		}
 
-			foreach (var subview in _subviews) {
-				if (subview.Frame.IntersectsWith (region)) {
-					var subviewRegion = Rect.Intersect (subview.Frame, region);
-					subviewRegion.X -= subview.Frame.X;
-					subviewRegion.Y -= subview.Frame.Y;
-					subview.SetNeedsDisplay (subviewRegion);
-				}
+		foreach (var subview in _subviews) {
+			if (subview.Frame.IntersectsWith (region)) {
+				var subviewRegion = Rect.Intersect (subview.Frame, region);
+				subviewRegion.X -= subview.Frame.X;
+				subviewRegion.Y -= subview.Frame.Y;
+				subview.SetNeedsDisplay (subviewRegion);
 			}
 		}
+	}
 
-		/// <summary>
-		/// Gets whether any Subviews need to be redrawn.
-		/// </summary>
-		public bool SubViewNeedsDisplay {
-			get => _subViewNeedsDisplay;
+	/// <summary>
+	/// Gets whether any Subviews need to be redrawn.
+	/// </summary>
+	public bool SubViewNeedsDisplay => _subViewNeedsDisplay;
+
+	bool _subViewNeedsDisplay;
+
+	/// <summary>
+	/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
+	/// </summary>
+	public void SetSubViewNeedsDisplay ()
+	{
+		_subViewNeedsDisplay = true;
+		if (_superView != null && !_superView._subViewNeedsDisplay) {
+			_superView.SetSubViewNeedsDisplay ();
 		}
+	}
 
-		bool _subViewNeedsDisplay;
+	/// <summary>
+	///   Clears the <see cref="Bounds"/> with the normal background color.
+	/// </summary>
+	/// <remarks>
+	///   <para>
+	///     This clears the Bounds used by this view.
+	///   </para>
+	/// </remarks>
+	public void Clear ()
+	{
+		if (IsInitialized) {
+			Clear (BoundsToScreen (Bounds));
+		}
 
-		/// <summary>
-		/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
-		/// </summary>
-		public void SetSubViewNeedsDisplay ()
-		{
-			_subViewNeedsDisplay = true;
-			if (_superView != null && !_superView._subViewNeedsDisplay) {
-				_superView.SetSubViewNeedsDisplay ();
-			}
+	}
+
+	// BUGBUG: This version of the Clear API should be removed. We should have a tenet that says 
+	// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
+	// be refactored to use the View-relative version.
+	/// <summary>
+	///   Clears the specified screen-relative rectangle with the normal background. 
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	/// <param name="regionScreen">The screen-relative rectangle to clear.</param>
+	public void Clear (Rect regionScreen)
+	{
+		if (Driver == null) {
+			return;
 		}
+		var prev = Driver.SetAttribute (GetNormalColor ());
+		Driver.FillRect (regionScreen);
+		Driver.SetAttribute (prev);
+	}
+
+	// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
+	internal Rect ScreenClip (Rect regionScreen)
+	{
+		int x = regionScreen.X < 0 ? 0 : regionScreen.X;
+		int y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
+		int w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
+		int h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
+
+		return new Rect (x, y, w, h);
+	}
+
+	/// <summary>
+	/// Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.
+	/// </summary>
+	/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
+	/// <remarks>
+	/// <para>
+	/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
+	/// </para>
+	/// </remarks>
+	public Rect ClipToBounds ()
+	{
+		var previous = Driver.Clip;
+		Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds));
+		return previous;
+	}
 
-		/// <summary>
-		///   Clears the <see cref="Bounds"/> with the normal background color.
-		/// </summary>
-		/// <remarks>
-		///   <para>
-		///     This clears the Bounds used by this view.
-		///   </para>
-		/// </remarks>
-		public void Clear () => Clear (BoundsToScreen(Bounds));
-
-		// BUGBUG: This version of the Clear API should be removed. We should have a tenet that says 
-		// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
-		// be refactored to use the View-relative version.
-		/// <summary>
-		///   Clears the specified screen-relative rectangle with the normal background. 
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		/// <param name="regionScreen">The screen-relative rectangle to clear.</param>
-		public void Clear (Rect regionScreen)
-		{
-			if (Driver == null) {
-				return;
+	/// <summary>
+	/// Utility function to draw strings that contain a hotkey.
+	/// </summary>
+	/// <param name="text">String to display, the hotkey specifier before a letter flags the next letter as the hotkey.</param>
+	/// <param name="hotColor">Hot color.</param>
+	/// <param name="normalColor">Normal color.</param>
+	/// <remarks>
+	/// <para>The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default.</para>
+	/// <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
+	/// </remarks>
+	public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
+	{
+		var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
+		Application.Driver.SetAttribute (normalColor);
+		foreach (char rune in text) {
+			if (rune == hotkeySpec.Value) {
+				Application.Driver.SetAttribute (hotColor);
+				continue;
 			}
-			var prev = Driver.SetAttribute (GetNormalColor ());
-			Driver.FillRect (regionScreen);
-			Driver.SetAttribute (prev);
+			Application.Driver.AddRune ((Rune)rune);
+			Application.Driver.SetAttribute (normalColor);
 		}
+	}
 
-		// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
-		internal Rect ScreenClip (Rect regionScreen)
-		{
-			var x = regionScreen.X < 0 ? 0 : regionScreen.X;
-			var y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
-			var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
-			var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
-
-			return new Rect (x, y, w, h);
-		}		
-
-		/// <summary>
-		/// Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.
-		/// </summary>
-		/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
-		/// <remarks>
-		/// <para>
-		/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
-		/// </para>
-		/// </remarks>
-		public Rect ClipToBounds ()
-		{
-			var previous = Driver.Clip;
-			Driver.Clip = Rect.Intersect (previous, BoundsToScreen (Bounds));
-			return previous;
+	/// <summary>
+	/// Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused" state.
+	/// </summary>
+	/// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
+	/// <param name="focused">If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
+	/// <param name="scheme">The color scheme to use.</param>
+	public void DrawHotString (string text, bool focused, ColorScheme scheme)
+	{
+		if (focused) {
+			DrawHotString (text, scheme.HotFocus, scheme.Focus);
+		} else {
+			DrawHotString (text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled);
 		}
+	}
 
-		/// <summary>
-		/// Utility function to draw strings that contain a hotkey.
-		/// </summary>
-		/// <param name="text">String to display, the hotkey specifier before a letter flags the next letter as the hotkey.</param>
-		/// <param name="hotColor">Hot color.</param>
-		/// <param name="normalColor">Normal color.</param>
-		/// <remarks>
-		/// <para>The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default.</para>
-		/// <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
-		/// </remarks>
-		public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
-		{
-			var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
-			Application.Driver.SetAttribute (normalColor);
-			foreach (var rune in text) {
-				if (rune == hotkeySpec.Value) {
-					Application.Driver.SetAttribute (hotColor);
-					continue;
-				}
-				Application.Driver.AddRune ((Rune)rune);
-				Application.Driver.SetAttribute (normalColor);
-			}
+	/// <summary>
+	/// This moves the cursor to the specified column and row in the view.
+	/// </summary>
+	/// <returns>The move.</returns>
+	/// <param name="col">The column to move to, in view-relative coordinates.</param>
+	/// <param name="row">the row to move to, in view-relative coordinates.</param>
+	public void Move (int col, int row)
+	{
+		if (Driver == null || Driver?.Rows == 0) {
+			return;
 		}
 
-		/// <summary>
-		/// Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused" state.
-		/// </summary>
-		/// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
-		/// <param name="focused">If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
-		/// <param name="scheme">The color scheme to use.</param>
-		public void DrawHotString (string text, bool focused, ColorScheme scheme)
-		{
-			if (focused)
-				DrawHotString (text, scheme.HotFocus, scheme.Focus);
-			else
-				DrawHotString (text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled);
-		}
+		BoundsToScreen (col, row, out int rCol, out int rRow, false);
+		Driver?.Move (rCol, rRow);
+	}
 
-		/// <summary>
-		/// This moves the cursor to the specified column and row in the view.
-		/// </summary>
-		/// <returns>The move.</returns>
-		/// <param name="col">The column to move to, in view-relative coordinates.</param>
-		/// <param name="row">the row to move to, in view-relative coordinates.</param>
-		public void Move (int col, int row)
-		{
-			if (Driver.Rows == 0) {
-				return;
-			}
-			
-			BoundsToScreen (col, row, out var rCol, out var rRow, false);
-			Driver.Move (rCol, rRow);
+	/// <summary>
+	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
+	/// </summary>
+	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
+	public LineCanvas LineCanvas { get; } = new ();
+
+	/// <summary>
+	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
+	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
+	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
+	/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
+	/// </summary>
+	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
+
+	// TODO: Make this cancelable
+	/// <summary>
+	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool OnDrawFrames ()
+	{
+		if (!IsInitialized) {
+			return false;
 		}
-		/// <summary>
-		/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
-		/// </summary>
-		/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
-		public LineCanvas LineCanvas { get; } = new LineCanvas ();
-
-		/// <summary>
-		/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
-		/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
-		/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
-		/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
-		/// </summary>
-		public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
-
-		// TODO: Make this cancelable
-		/// <summary>
-		/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-		/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
-		/// method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool OnDrawFrames ()
-		{
-			if (!IsInitialized) {
-				return false;
-			}
 
-			// Each of these renders lines to either this View's LineCanvas 
-			// Those lines will be finally rendered in OnRenderLineCanvas
-			Margin?.OnDrawContent (Margin.Bounds);
-			Border?.OnDrawContent (Border.Bounds);
-			Padding?.OnDrawContent (Padding.Bounds);
+		// Each of these renders lines to either this View's LineCanvas 
+		// Those lines will be finally rendered in OnRenderLineCanvas
+		Margin?.OnDrawContent (Margin.Bounds);
+		Border?.OnDrawContent (Border.Bounds);
+		Padding?.OnDrawContent (Padding.Bounds);
 
-			return true;
+		return true;
+	}
+
+	/// <summary>
+	/// Draws the view. Causes the following virtual methods to be called (along with their related events): 
+	/// <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	///    Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT <see cref="Frame"/> (superview-relative).
+	/// </para>
+	/// <para>
+	///    Views should set the color that they want to use on entry, as otherwise this will inherit
+	///    the last color that was set globally on the driver.
+	/// </para>
+	/// <para>
+	///    Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
+	///    larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
+	/// </para>
+	/// </remarks>
+	public void Draw ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
+		OnDrawFrames ();
 
-		/// <summary>
-		/// Draws the view. Causes the following virtual methods to be called (along with their related events): 
-		/// <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///    Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT <see cref="Frame"/> (superview-relative).
-		/// </para>
-		/// <para>
-		///    Views should set the color that they want to use on entry, as otherwise this will inherit
-		///    the last color that was set globally on the driver.
-		/// </para>
-		/// <para>
-		///    Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
-		///    larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
-		/// </para>
-		/// </remarks>
-		public void Draw ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
-			OnDrawFrames ();
+		var prevClip = ClipToBounds ();
 
-			var prevClip = ClipToBounds ();
+		if (ColorScheme != null) {
+			//Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
+			Driver.SetAttribute (GetNormalColor ());
+		}
 
-			if (ColorScheme != null) {
-				//Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ());
-				Driver.SetAttribute (GetNormalColor ());
-			}
+		// Invoke DrawContentEvent
+		var dev = new DrawEventArgs (Bounds);
+		DrawContent?.Invoke (this, dev);
 
-			// Invoke DrawContentEvent
-			var dev = new DrawEventArgs (Bounds);
-			DrawContent?.Invoke (this, dev);
+		if (!dev.Cancel) {
+			OnDrawContent (Bounds);
+		}
 
-			if (!dev.Cancel) {
-				OnDrawContent (Bounds);
-			}
+		Driver.Clip = prevClip;
 
-			Driver.Clip = prevClip;
+		OnRenderLineCanvas ();
+		// Invoke DrawContentCompleteEvent
+		OnDrawContentComplete (Bounds);
 
-			OnRenderLineCanvas ();
-			// Invoke DrawContentCompleteEvent
-			OnDrawContentComplete (Bounds);
+		// BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
+		ClearLayoutNeeded ();
+		ClearNeedsDisplay ();
+	}
 
-			// BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details.
-			ClearLayoutNeeded ();
-			ClearNeedsDisplay ();
+	// TODO: Make this cancelable
+	/// <summary>
+	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// method will cause the <see cref="LineCanvas"/> to be rendered.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool OnRenderLineCanvas ()
+	{
+		if (!IsInitialized) {
+			return false;
 		}
 
-		// TODO: Make this cancelable
-		/// <summary>
-		/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-		/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
-		/// method will cause the <see cref="LineCanvas"/> to be rendered.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool OnRenderLineCanvas ()
-		{
-			if (!IsInitialized) {
-				return false;
+		// If we have a SuperView, it'll render our frames.
+		if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) {
+			foreach (var p in LineCanvas.GetCellMap ()) {
+				// Get the entire map
+				Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal);
+				Driver.Move (p.Key.X, p.Key.Y);
+				// TODO: #2616 - Support combining sequences that don't normalize
+				Driver.AddRune (p.Value.Rune);
 			}
+			LineCanvas.Clear ();
+		}
 
-			// If we have a SuperView, it'll render our frames.
-			if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rect.Empty) {
-				foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map
-					Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal);
-					Driver.Move (p.Key.X, p.Key.Y);
-					// TODO: #2616 - Support combining sequences that don't normalize
-					Driver.AddRune (p.Value.Rune);
-				}
-				LineCanvas.Clear ();
+		if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
+			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
+				// Combine the LineCanvas'
+				LineCanvas.Merge (subview.LineCanvas);
+				subview.LineCanvas.Clear ();
 			}
 
-			if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
-				foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
-					// Combine the LineCanvas'
-					LineCanvas.Merge (subview.LineCanvas);
-					subview.LineCanvas.Clear ();
-				}
-
-				foreach (var p in LineCanvas.GetCellMap ()) { // Get the entire map
-					Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal);
-					Driver.Move (p.Key.X, p.Key.Y);
-					// TODO: #2616 - Support combining sequences that don't normalize
-					Driver.AddRune (p.Value.Rune);
-				}
-				LineCanvas.Clear ();
+			foreach (var p in LineCanvas.GetCellMap ()) {
+				// Get the entire map
+				Driver.SetAttribute (p.Value.Attribute ?? ColorScheme.Normal);
+				Driver.Move (p.Key.X, p.Key.Y);
+				// TODO: #2616 - Support combining sequences that don't normalize
+				Driver.AddRune (p.Value.Rune);
 			}
-
-			return true;
+			LineCanvas.Clear ();
 		}
 
-		/// <summary>
-		/// Event invoked when the content area of the View is to be drawn.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
-		/// </para>
-		/// <para>
-		/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-		/// </para>
-		/// </remarks>
-		public event EventHandler<DrawEventArgs> DrawContent;
-
-		/// <summary>
-		/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. 
-		/// </summary>
-		/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
-		/// <remarks>
-		/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn. 
-		/// </remarks>
-		public virtual void OnDrawContent (Rect contentArea)
-		{
-			if (NeedsDisplay) {
-				if (SuperView != null) {
-					Clear (BoundsToScreen (Bounds));
-				}
+		return true;
+	}
 
-				if (!string.IsNullOrEmpty (TextFormatter.Text)) {
-					if (TextFormatter != null) {
-						TextFormatter.NeedsFormat = true;
-					}
-				}
-				// This should NOT clear 
-				TextFormatter?.Draw (BoundsToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
-					HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
-					Rect.Empty, false);
-				SetSubViewNeedsDisplay ();
+	/// <summary>
+	/// Event invoked when the content area of the View is to be drawn.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
+	/// </para>
+	/// <para>
+	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
+	/// </para>
+	/// </remarks>
+	public event EventHandler<DrawEventArgs> DrawContent;
+
+	/// <summary>
+	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. 
+	/// </summary>
+	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <remarks>
+	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn. 
+	/// </remarks>
+	public virtual void OnDrawContent (Rect contentArea)
+	{
+		if (NeedsDisplay) {
+			if (SuperView != null) {
+				Clear (BoundsToScreen (Bounds));
 			}
 
-			// Draw subviews
-			// TODO: Implement OnDrawSubviews (cancelable);
-			if (_subviews != null && SubViewNeedsDisplay) {
-				var subviewsNeedingDraw = _subviews.Where (
-					view => view.Visible &&
-						(view.NeedsDisplay ||
-						view.SubViewNeedsDisplay ||
-						view.LayoutNeeded)
-					);
-
-				foreach (var view in subviewsNeedingDraw) {
-					//view.Frame.IntersectsWith (bounds)) {
-					// && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
-					if (view.LayoutNeeded) {
-						view.LayoutSubviews ();
-					}
-
-					// Draw the subview
-					// Use the view's bounds (view-relative; Location will always be (0,0)
-					//if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
-					view.Draw ();
-					//}
+			if (!string.IsNullOrEmpty (TextFormatter.Text)) {
+				if (TextFormatter != null) {
+					TextFormatter.NeedsFormat = true;
 				}
 			}
+			// This should NOT clear 
+			TextFormatter?.Draw (BoundsToScreen (Bounds), HasFocus ? GetFocusColor () : GetNormalColor (),
+				HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
+				Rect.Empty, false);
+			SetSubViewNeedsDisplay ();
 		}
 
-		/// <summary>
-		/// Event invoked when the content area of the View is completed drawing.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
-		/// </para>
-		/// <para>
-		/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-		/// </para>
-		/// </remarks>
-		public event EventHandler<DrawEventArgs> DrawContentComplete;
-
-		/// <summary>
-		/// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
-		/// </summary>
-		/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
-		/// <remarks>
-		/// This method will be called after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
-		/// </remarks>
-		public virtual void OnDrawContentComplete (Rect contentArea)
-		{
-			DrawContentComplete?.Invoke (this, new DrawEventArgs (contentArea));
-		}
+		// Draw subviews
+		// TODO: Implement OnDrawSubviews (cancelable);
+		if (_subviews != null && SubViewNeedsDisplay) {
+			var subviewsNeedingDraw = _subviews.Where (
+				view => view.Visible &&
+					(view.NeedsDisplay ||
+					view.SubViewNeedsDisplay ||
+					view.LayoutNeeded)
+			);
+
+			foreach (var view in subviewsNeedingDraw) {
+				//view.Frame.IntersectsWith (bounds)) {
+				// && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) {
+				if (view.LayoutNeeded) {
+					view.LayoutSubviews ();
+				}
 
+				// Draw the subview
+				// Use the view's bounds (view-relative; Location will always be (0,0)
+				//if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) {
+				view.Draw ();
+				//}
+			}
+		}
 	}
+
+	/// <summary>
+	/// Event invoked when the content area of the View is completed drawing.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
+	/// </para>
+	/// <para>
+	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
+	/// </para>
+	/// </remarks>
+	public event EventHandler<DrawEventArgs> DrawContentComplete;
+
+	/// <summary>
+	/// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
+	/// </summary>
+	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <remarks>
+	/// This method will be called after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
+	/// </remarks>
+	public virtual void OnDrawContentComplete (Rect contentArea) => DrawContentComplete?.Invoke (this, new DrawEventArgs (contentArea));
 }

+ 9 - 3
Terminal.Gui/View/ViewKeyboard.cs

@@ -330,6 +330,10 @@ public partial class View {
 		}
 
 		// During (this is what can be cancelled)
+		InvokingKeyBindings?.Invoke (this, keyEvent);
+		if (keyEvent.Handled) {
+			return true;
+		}
 		var handled = OnInvokingKeyBindings (keyEvent);
 		if (handled != null && (bool)handled) {
 			return true;
@@ -553,9 +557,11 @@ public partial class View {
 	{
 		// fire event
 		// BUGBUG: KeyEventArgs doesn't include scope, so the event never sees it.
-		InvokingKeyBindings?.Invoke (this, keyEvent);
-		if (keyEvent.Handled) {
-			return true;
+		if (keyEvent.Scope == KeyBindingScope.Application || keyEvent.Scope == KeyBindingScope.HotKey) {
+			InvokingKeyBindings?.Invoke (this, keyEvent);
+			if (keyEvent.Handled) {
+				return true;
+			}
 		}
 
 		// * If no key binding was found, `InvokeKeyBindings` returns `null`.

+ 3 - 2
Terminal.Gui/View/ViewSubViews.cs

@@ -82,14 +82,15 @@ namespace Terminal.Gui {
 				view._oldEnabled = true;
 				view.Enabled = false;
 			}
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
 
 			OnAdded (new SuperViewChangedEventArgs (this, view));
 			if (IsInitialized && !view.IsInitialized) {
 				view.BeginInit ();
 				view.EndInit ();
 			}
+
+			SetNeedsLayout ();
+			SetNeedsDisplay ();
 		}
 
 		/// <summary>

+ 212 - 216
Terminal.Gui/View/ViewText.cs

@@ -1,260 +1,256 @@
-using System.Text;
-using System;
+using System;
 using System.Collections.Generic;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
 
-	public partial class View {
-		string _text;
+public partial class View {
+	string _text;
 
-		/// <summary>
-		///   The text displayed by the <see cref="View"/>.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		///  The text will be drawn before any subviews are drawn.
-		/// </para>
-		/// <para>
-		///  The text will be drawn starting at the view origin (0, 0) and will be formatted according
-		///  to <see cref="TextAlignment"/> and <see cref="TextDirection"/>. 
-		/// </para>
-		/// <para>
-		///  The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
-		///  is 1, the text will be clipped.	
-		///  </para>
-		/// <para>
-		///  Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set <see cref="HotKeySpecifier"/> to
-		///  <c>(Rune)0xffff</c>.
-		/// </para>
-		/// </remarks>
-		public virtual string Text {
-			get => _text;
-			set {
-				_text = value;
-				SetHotKey ();
-				UpdateTextFormatterText ();
-				//TextFormatter.Format ();
-				OnResizeNeeded ();
+	/// <summary>
+	///   The text displayed by the <see cref="View"/>.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	///  The text will be drawn before any subviews are drawn.
+	/// </para>
+	/// <para>
+	///  The text will be drawn starting at the view origin (0, 0) and will be formatted according
+	///  to <see cref="TextAlignment"/> and <see cref="TextDirection"/>. 
+	/// </para>
+	/// <para>
+	///  The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
+	///  is 1, the text will be clipped.	
+	///  </para>
+	/// <para>
+	///  Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set <see cref="HotKeySpecifier"/> to
+	///  <c>(Rune)0xffff</c>.
+	/// </para>
+	/// </remarks>
+	public virtual string Text {
+		get => _text;
+		set {
+			_text = value;
+			SetHotKey ();
+			UpdateTextFormatterText ();
+			//TextFormatter.Format ();
+			OnResizeNeeded ();
 
 #if DEBUG
-				if (_text != null && string.IsNullOrEmpty (Id)) {
-					Id = _text;
-				}
-#endif
+			if (_text != null && string.IsNullOrEmpty (Id)) {
+				Id = _text;
 			}
+#endif
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
-		/// </summary>
-		public TextFormatter TextFormatter { get; set; }
+	/// <summary>
+	/// Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
+	/// </summary>
+	public TextFormatter TextFormatter { get; set; }
 
-		/// <summary>
-		/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
-		///  different format than the default.
-		/// </summary>
-		protected virtual void UpdateTextFormatterText ()
-		{
-			if (TextFormatter != null) {
-				TextFormatter.Text = _text;
-			}
+	/// <summary>
+	/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
+	///  different format than the default.
+	/// </summary>
+	protected virtual void UpdateTextFormatterText ()
+	{
+		if (TextFormatter != null) {
+			TextFormatter.Text = _text;
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
-		/// or not when <see cref="TextFormatter.WordWrap"/> is enabled. 
-		/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
-		/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
-		/// </summary>
-		public virtual bool PreserveTrailingSpaces {
-			get => TextFormatter.PreserveTrailingSpaces;
-			set {
-				if (TextFormatter.PreserveTrailingSpaces != value) {
-					TextFormatter.PreserveTrailingSpaces = value;
-					TextFormatter.NeedsFormat = true;
-				}
+	/// <summary>
+	/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
+	/// or not when <see cref="TextFormatter.WordWrap"/> is enabled. 
+	/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
+	/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
+	/// </summary>
+	public virtual bool PreserveTrailingSpaces {
+		get => TextFormatter.PreserveTrailingSpaces;
+		set {
+			if (TextFormatter.PreserveTrailingSpaces != value) {
+				TextFormatter.PreserveTrailingSpaces = value;
+				TextFormatter.NeedsFormat = true;
 			}
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will redisplay the <see cref="View"/>.
-		/// </summary>
-		/// <value>The text alignment.</value>
-		public virtual TextAlignment TextAlignment {
-			get => TextFormatter.Alignment;
-			set {
-				TextFormatter.Alignment = value;
-				UpdateTextFormatterText ();
-				OnResizeNeeded ();
-			}
+	/// <summary>
+	/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will redisplay the <see cref="View"/>.
+	/// </summary>
+	/// <value>The text alignment.</value>
+	public virtual TextAlignment TextAlignment {
+		get => TextFormatter.Alignment;
+		set {
+			TextFormatter.Alignment = value;
+			UpdateTextFormatterText ();
+			OnResizeNeeded ();
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay the <see cref="View"/>.
-		/// </summary>
-		/// <value>The text alignment.</value>
-		public virtual VerticalTextAlignment VerticalTextAlignment {
-			get => TextFormatter.VerticalAlignment;
-			set {
-				TextFormatter.VerticalAlignment = value;
-				SetNeedsDisplay ();
-			}
+	/// <summary>
+	/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay the <see cref="View"/>.
+	/// </summary>
+	/// <value>The text alignment.</value>
+	public virtual VerticalTextAlignment VerticalTextAlignment {
+		get => TextFormatter.VerticalAlignment;
+		set {
+			TextFormatter.VerticalAlignment = value;
+			SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the <see cref="View"/>.
-		/// </summary>
-		/// <value>The text alignment.</value>
-		public virtual TextDirection TextDirection {
-			get => TextFormatter.Direction;
-			set {
-				UpdateTextDirection (value);
-				TextFormatter.Direction = value;
-			}
+	/// <summary>
+	/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the <see cref="View"/>.
+	/// </summary>
+	/// <value>The text alignment.</value>
+	public virtual TextDirection TextDirection {
+		get => TextFormatter.Direction;
+		set {
+			UpdateTextDirection (value);
+			TextFormatter.Direction = value;
 		}
+	}
 
-		private void UpdateTextDirection (TextDirection newDirection)
-		{
-			var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
-			    != TextFormatter.IsHorizontalDirection (newDirection);
-			TextFormatter.Direction = newDirection;
+	void UpdateTextDirection (TextDirection newDirection)
+	{
+		bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
+					!= TextFormatter.IsHorizontalDirection (newDirection);
+		TextFormatter.Direction = newDirection;
 
-			var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
+		bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
 
-			UpdateTextFormatterText ();
+		UpdateTextFormatterText ();
 
-			if ((!ValidatePosDim && directionChanged && AutoSize)
-			    || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) {
-				OnResizeNeeded ();
-			} else if (directionChanged && IsAdded) {
-				ResizeBoundsToFit (Bounds.Size);
-				// BUGBUG: I think this call is redundant.
-				SetFrameToFitText ();
-			} else {
-				SetFrameToFitText ();
-			}
-			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-			SetNeedsDisplay ();
+		if (!ValidatePosDim && directionChanged && AutoSize
+		|| ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) {
+			OnResizeNeeded ();
+		} else if (directionChanged && IsAdded) {
+			ResizeBoundsToFit (Bounds.Size);
+			// BUGBUG: I think this call is redundant.
+			SetFrameToFitText ();
+		} else {
+			SetFrameToFitText ();
 		}
+		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+		SetNeedsDisplay ();
+	}
 
 
-		/// <summary>
-		/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>. 
-		/// </summary>
-		/// <returns><see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> == <see langword="true"/> or
-		/// <see cref="Text"/> will not fit.</returns>
-		/// <remarks>
-		/// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
-		/// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
-		/// Does not take into account word wrapping.
-		/// </remarks>
-		bool SetFrameToFitText ()
+	/// <summary>
+	/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>. 
+	/// </summary>
+	/// <returns><see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> == <see langword="true"/> or
+	/// <see cref="Text"/> will not fit.</returns>
+	/// <remarks>
+	/// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
+	/// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
+	/// Does not take into account word wrapping.
+	/// </remarks>
+	bool SetFrameToFitText ()
+	{
+		// BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
+		// <summary>
+		// Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
+		// </summary>
+		// <param name="sizeRequired">The minimum dimensions required.</param>
+		// <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
+		// <remarks>
+		// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
+		// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
+		// Does not take into account word wrapping.
+		// </remarks>
+		bool GetMinimumSizeOfText (out Size sizeRequired)
 		{
-			// BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
-			// <summary>
-			// Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
-			// </summary>
-			// <param name="sizeRequired">The minimum dimensions required.</param>
-			// <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</returns>
-			// <remarks>
-			// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
-			// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
-			// Does not take into account word wrapping.
-			// </remarks>
-			bool GetMinimumSizeOfText (out Size sizeRequired)
-			{
-				if (!IsInitialized) {
-					sizeRequired = new Size (0, 0);
-					return false;
-				}
-				sizeRequired = Bounds.Size;
-
-				if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
-					switch (TextFormatter.IsVerticalDirection (TextDirection)) {
-					case true:
-						int colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
-						// TODO: v2 - This uses frame.Width; it should only use Bounds
-						if (_frame.Width < colWidth &&
-						(Width == null ||
-						Bounds.Width >= 0 &&
-						Width is Dim.DimAbsolute &&
-						Width.Anchor (0) >= 0 &&
-						Width.Anchor (0) < colWidth)) {
-							sizeRequired = new Size (colWidth, Bounds.Height);
-							return true;
-						}
-						break;
-					default:
-						if (_frame.Height < 1 &&
-						(Height == null ||
-						Height is Dim.DimAbsolute &&
-						Height.Anchor (0) == 0)) {
-							sizeRequired = new Size (Bounds.Width, 1);
-							return true;
-						}
-						break;
-					}
-				}
+			if (!IsInitialized) {
+				sizeRequired = new Size (0, 0);
 				return false;
 			}
+			sizeRequired = Bounds.Size;
 
-			if (GetMinimumSizeOfText (out var size)) {
-				_frame = new Rect (_frame.Location, size);
-				return true;
+			if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
+				switch (TextFormatter.IsVerticalDirection (TextDirection)) {
+				case true:
+					int colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
+					// TODO: v2 - This uses frame.Width; it should only use Bounds
+					if (_frame.Width < colWidth &&
+					(Width == null ||
+					Bounds.Width >= 0 &&
+					Width is Dim.DimAbsolute &&
+					Width.Anchor (0) >= 0 &&
+					Width.Anchor (0) < colWidth)) {
+						sizeRequired = new Size (colWidth, Bounds.Height);
+						return true;
+					}
+					break;
+				default:
+					if (_frame.Height < 1 &&
+					(Height == null ||
+					Height is Dim.DimAbsolute &&
+					Height.Anchor (0) == 0)) {
+						sizeRequired = new Size (Bounds.Width, 1);
+						return true;
+					}
+					break;
+				}
 			}
 			return false;
 		}
 
-		/// <summary>
-		/// Gets the width or height of the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> characters 
-		/// in the <see cref="Text"/> property.
-		/// </summary>
-		/// <remarks>
-		/// Only the first hotkey specifier found in <see cref="Text"/> is supported.
-		/// </remarks>
-		/// <param name="isWidth">If <see langword="true"/> (the default) the width required for the hotkey specifier is returned. Otherwise the height is returned.</param>
-		/// <returns>The number of characters required for the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>. If the text direction specified
-		/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.</returns>
-		public int GetHotKeySpecifierLength (bool isWidth = true)
-		{
-			if (isWidth) {
-				return TextFormatter.IsHorizontalDirection (TextDirection) &&
-				    TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-				    ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
-			} else {
-				return TextFormatter.IsVerticalDirection (TextDirection) &&
-				    TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-				    ? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
-			}
+		if (GetMinimumSizeOfText (out var size)) {
+			_frame = new Rect (_frame.Location, size);
+			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>.
-		/// </summary>
-		/// <returns></returns>
-		public Size GetSizeNeededForTextWithoutHotKey ()
-		{
-			return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
-			    TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
+	/// <summary>
+	/// Gets the width or height of the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> characters 
+	/// in the <see cref="Text"/> property.
+	/// </summary>
+	/// <remarks>
+	/// Only the first hotkey specifier found in <see cref="Text"/> is supported.
+	/// </remarks>
+	/// <param name="isWidth">If <see langword="true"/> (the default) the width required for the hotkey specifier is returned. Otherwise the height is returned.</param>
+	/// <returns>The number of characters required for the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>. If the text direction specified
+	/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.</returns>
+	public int GetHotKeySpecifierLength (bool isWidth = true)
+	{
+		if (isWidth) {
+			return TextFormatter.IsHorizontalDirection (TextDirection) &&
+				TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
+		} else {
+			return TextFormatter.IsVerticalDirection (TextDirection) &&
+				TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
 		}
+	}
 
-		/// <summary>
-		/// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
-		/// </summary>
-		/// <returns></returns>
-		public Size GetTextFormatterSizeNeededForTextAndHotKey ()
-		{
-			if (string.IsNullOrEmpty (TextFormatter.Text)) {
-
-				if (!IsInitialized) return Size.Empty;
+	/// <summary>
+	/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>.
+	/// </summary>
+	/// <returns></returns>
+	public Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
+		TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
 
-				return Bounds.Size;
-			}
+	/// <summary>
+	/// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
+	/// </summary>
+	/// <returns></returns>
+	public Size GetTextFormatterSizeNeededForTextAndHotKey ()
+	{
+		if (!IsInitialized) {
+			return Size.Empty;
+		}
 
-			// BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
-			// BUGBUG: This uses Frame; in v2 it should be Bounds
-			return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
-					 Bounds.Size.Height + GetHotKeySpecifierLength (false));
+		if (string.IsNullOrEmpty (TextFormatter.Text)) {
+			return Bounds.Size;
 		}
+
+		// BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
+		// BUGBUG: This uses Frame; in v2 it should be Bounds
+		return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
+			Bounds.Size.Height + GetHotKeySpecifierLength (false));
 	}
 }

+ 6 - 2
Terminal.Gui/Views/ColorPicker.cs

@@ -51,7 +51,9 @@ namespace Terminal.Gui {
 			set {
 				if (_boxWidth != value) {
 					_boxWidth = value;
-					Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
+					if (IsInitialized) {
+						Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
+					}
 				}
 			}
 		}
@@ -65,7 +67,9 @@ namespace Terminal.Gui {
 			set {
 				if (_boxHeight != value) {
 					_boxHeight = value;
-					Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
+					if (IsInitialized) {
+						Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
+					}
 				}
 			}
 		}

+ 684 - 709
Terminal.Gui/Views/ComboBox.cs

@@ -10,878 +10,853 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Text;
 
-namespace Terminal.Gui {
-	/// <summary>
-	/// Provides a drop-down list of items the user can select from.
-	/// </summary>
-	public class ComboBox : View {
+namespace Terminal.Gui; 
 
-		private class ComboListView : ListView {
-			private int highlighted = -1;
-			private bool isFocusing;
-			private ComboBox container;
-			private bool hideDropdownListOnClick;
+/// <summary>
+/// Provides a drop-down list of items the user can select from.
+/// </summary>
+public class ComboBox : View {
+	class ComboListView : ListView {
+		int _highlighted = -1;
+		bool _isFocusing;
+		ComboBox _container;
+		bool _hideDropdownListOnClick;
 
-			public ComboListView (ComboBox container, bool hideDropdownListOnClick)
-			{
-				Initialize (container, hideDropdownListOnClick);
-			}
+		public ComboListView (ComboBox container, bool hideDropdownListOnClick) => SetInitialProperties (container, hideDropdownListOnClick);
 
-			public ComboListView (ComboBox container, Rect rect, IList source, bool hideDropdownListOnClick) : base (rect, source)
-			{
-				Initialize (container, hideDropdownListOnClick);
-			}
+		public ComboListView (ComboBox container, Rect rect, IList source, bool hideDropdownListOnClick) : base (rect, source) => SetInitialProperties (container, hideDropdownListOnClick);
 
-			public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick) : base (source)
-			{
-				Initialize (container, hideDropdownListOnClick);
-			}
+		public ComboListView (ComboBox container, IList source, bool hideDropdownListOnClick) : base (source) => SetInitialProperties (container, hideDropdownListOnClick);
 
-			private void Initialize (ComboBox container, bool hideDropdownListOnClick)
-			{
-				this.container = container ?? throw new ArgumentNullException (nameof (container), "ComboBox container cannot be null.");
-				HideDropdownListOnClick = hideDropdownListOnClick;
-			}
+		void SetInitialProperties (ComboBox container, bool hideDropdownListOnClick)
+		{
+			_container = container ?? throw new ArgumentNullException (nameof (container), "ComboBox container cannot be null.");
+			HideDropdownListOnClick = hideDropdownListOnClick;
+		}
 
-			public bool HideDropdownListOnClick {
-				get => hideDropdownListOnClick;
-				set => hideDropdownListOnClick = WantContinuousButtonPressed = value;
-			}
+		public bool HideDropdownListOnClick {
+			get => _hideDropdownListOnClick;
+			set => _hideDropdownListOnClick = WantContinuousButtonPressed = value;
+		}
 
-			public override bool MouseEvent (MouseEvent me)
-			{
-				var res = false;
-				var isMousePositionValid = IsMousePositionValid (me);
+		public override bool MouseEvent (MouseEvent me)
+		{
+			bool res = false;
+			bool isMousePositionValid = IsMousePositionValid (me);
 
+			if (isMousePositionValid) {
+				res = base.MouseEvent (me);
+			}
+
+			if (HideDropdownListOnClick && me.Flags == MouseFlags.Button1Clicked) {
+				if (!isMousePositionValid && !_isFocusing) {
+					_container._isShow = false;
+					_container.HideList ();
+				} else if (isMousePositionValid) {
+					OnOpenSelectedItem ();
+				} else {
+					_isFocusing = false;
+				}
+				return true;
+			} else if (me.Flags == MouseFlags.ReportMousePosition && HideDropdownListOnClick) {
 				if (isMousePositionValid) {
-					res = base.MouseEvent (me);
+					_highlighted = Math.Min (TopItem + me.Y, Source.Count);
+					SetNeedsDisplay ();
 				}
+				_isFocusing = false;
+				return true;
+			}
 
-				if (HideDropdownListOnClick && me.Flags == MouseFlags.Button1Clicked) {
-					if (!isMousePositionValid && !isFocusing) {
-						container.isShow = false;
-						container.HideList ();
-					} else if (isMousePositionValid) {
-						OnOpenSelectedItem ();
-					} else {
-						isFocusing = false;
-					}
-					return true;
-				} else if (me.Flags == MouseFlags.ReportMousePosition && HideDropdownListOnClick) {
-					if (isMousePositionValid) {
-						highlighted = Math.Min (TopItem + me.Y, Source.Count);
-						SetNeedsDisplay ();
-					}
-					isFocusing = false;
-					return true;
-				}
+			return res;
+		}
 
-				return res;
+		bool IsMousePositionValid (MouseEvent me)
+		{
+			if (me.X >= 0 && me.X < Frame.Width && me.Y >= 0 && me.Y < Frame.Height) {
+				return true;
 			}
+			return false;
+		}
 
-			private bool IsMousePositionValid (MouseEvent me)
-			{
-				if (me.X >= 0 && me.X < Frame.Width && me.Y >= 0 && me.Y < Frame.Height) {
-					return true;
+		public override void OnDrawContent (Rect contentArea)
+		{
+			var current = ColorScheme.Focus;
+			Driver.SetAttribute (current);
+			Move (0, 0);
+			var f = Frame;
+			int item = TopItem;
+			bool focused = HasFocus;
+			int col = AllowsMarking ? 2 : 0;
+			int start = LeftItem;
+
+			for (int row = 0; row < f.Height; row++, item++) {
+				bool isSelected = item == _container.SelectedItem;
+				bool isHighlighted = _hideDropdownListOnClick && item == _highlighted;
+
+				Attribute newcolor;
+				if (isHighlighted || isSelected && !_hideDropdownListOnClick) {
+					newcolor = focused ? ColorScheme.Focus : ColorScheme.HotNormal;
+				} else if (isSelected && _hideDropdownListOnClick) {
+					newcolor = focused ? ColorScheme.HotFocus : ColorScheme.HotNormal;
+				} else {
+					newcolor = focused ? GetNormalColor () : GetNormalColor ();
 				}
-				return false;
-			}
 
-			public override void OnDrawContent (Rect contentArea)
-			{
-				var current = ColorScheme.Focus;
-				Driver.SetAttribute (current);
-				Move (0, 0);
-				var f = Frame;
-				var item = TopItem;
-				bool focused = HasFocus;
-				int col = AllowsMarking ? 2 : 0;
-				int start = LeftItem;
-
-				for (int row = 0; row < f.Height; row++, item++) {
-					bool isSelected = item == container.SelectedItem;
-					bool isHighlighted = hideDropdownListOnClick && item == highlighted;
-
-					Attribute newcolor;
-					if (isHighlighted || (isSelected && !hideDropdownListOnClick)) {
-						newcolor = focused ? ColorScheme.Focus : ColorScheme.HotNormal;
-					} else if (isSelected && hideDropdownListOnClick) {
-						newcolor = focused ? ColorScheme.HotFocus : ColorScheme.HotNormal;
-					} else {
-						newcolor = focused ? GetNormalColor () : GetNormalColor ();
-					}
+				if (newcolor != current) {
+					Driver.SetAttribute (newcolor);
+					current = newcolor;
+				}
 
-					if (newcolor != current) {
-						Driver.SetAttribute (newcolor);
-						current = newcolor;
+				Move (0, row);
+				if (Source == null || item >= Source.Count) {
+					for (int c = 0; c < f.Width; c++) {
+						Driver.AddRune ((Rune)' ');
 					}
-
-					Move (0, row);
-					if (Source == null || item >= Source.Count) {
-						for (int c = 0; c < f.Width; c++)
-							Driver.AddRune ((Rune)' ');
-					} else {
-						var rowEventArgs = new ListViewRowEventArgs (item);
-						OnRowRender (rowEventArgs);
-						if (rowEventArgs.RowAttribute != null && current != rowEventArgs.RowAttribute) {
-							current = (Attribute)rowEventArgs.RowAttribute;
-							Driver.SetAttribute (current);
-						}
-						if (AllowsMarking) {
-							Driver.AddRune (Source.IsMarked (item) ? (AllowsMultipleSelection ? CM.Glyphs.Checked : CM.Glyphs.Selected) : (AllowsMultipleSelection ? CM.Glyphs.UnChecked : CM.Glyphs.UnSelected));
-							Driver.AddRune ((Rune)' ');
-						}
-						Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start);
+				} else {
+					var rowEventArgs = new ListViewRowEventArgs (item);
+					OnRowRender (rowEventArgs);
+					if (rowEventArgs.RowAttribute != null && current != rowEventArgs.RowAttribute) {
+						current = (Attribute)rowEventArgs.RowAttribute;
+						Driver.SetAttribute (current);
 					}
+					if (AllowsMarking) {
+						Driver.AddRune (Source.IsMarked (item) ? AllowsMultipleSelection ? Glyphs.Checked : Glyphs.Selected : AllowsMultipleSelection ? Glyphs.UnChecked : Glyphs.UnSelected);
+						Driver.AddRune ((Rune)' ');
+					}
+					Source.Render (this, Driver, isSelected, item, col, row, f.Width - col, start);
 				}
 			}
+		}
 
-			public override bool OnEnter (View view)
-			{
-				if (hideDropdownListOnClick) {
-					isFocusing = true;
-					highlighted = container.SelectedItem;
-					Application.GrabMouse (this);
-				}
-
-				return base.OnEnter (view);
+		public override bool OnEnter (View view)
+		{
+			if (_hideDropdownListOnClick) {
+				_isFocusing = true;
+				_highlighted = _container.SelectedItem;
+				Application.GrabMouse (this);
 			}
 
-			public override bool OnLeave (View view)
-			{
-				if (hideDropdownListOnClick) {
-					isFocusing = false;
-					highlighted = container.SelectedItem;
-					Application.UngrabMouse ();
-				}
+			return base.OnEnter (view);
+		}
 
-				return base.OnLeave (view);
+		public override bool OnLeave (View view)
+		{
+			if (_hideDropdownListOnClick) {
+				_isFocusing = false;
+				_highlighted = _container.SelectedItem;
+				Application.UngrabMouse ();
 			}
 
-			public override bool OnSelectedChanged ()
-			{
-				var res = base.OnSelectedChanged ();
+			return base.OnLeave (view);
+		}
 
-				highlighted = SelectedItem;
+		public override bool OnSelectedChanged ()
+		{
+			bool res = base.OnSelectedChanged ();
 
-				return res;
-			}
-		}
+			_highlighted = SelectedItem;
 
-		IListDataSource source;
-		/// <summary>
-		/// Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ComboBox"/>, enabling custom rendering.
-		/// </summary>
-		/// <value>The source.</value>
-		/// <remarks>
-		///  Use <see cref="SetSource"/> to set a new <see cref="IList"/> source.
-		/// </remarks>
-		public IListDataSource Source {
-			get => source;
-			set {
-				source = value;
-
-				// Only need to refresh list if its been added to a container view
-				if (SuperView != null && SuperView.Subviews.Contains (this)) {
-					SelectedItem = -1;
-					search.Text = "";
-					Search_Changed (this, new TextChangedEventArgs (""));
-					SetNeedsDisplay ();
-				}
-			}
+			return res;
 		}
+	}
 
-		/// <summary>
-		/// Sets the source of the <see cref="ComboBox"/> to an <see cref="IList"/>.
-		/// </summary>
-		/// <value>An object implementing the IList interface.</value>
-		/// <remarks>
-		///  Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome rendering.
-		/// </remarks>
-		public void SetSource (IList source)
-		{
-			if (source == null) {
-				Source = null;
-			} else {
-				listview.SetSource (source);
-				Source = listview.Source;
+	IListDataSource _source;
+
+	/// <summary>
+	/// Gets or sets the <see cref="IListDataSource"/> backing this <see cref="ComboBox"/>, enabling custom rendering.
+	/// </summary>
+	/// <value>The source.</value>
+	/// <remarks>
+	///  Use <see cref="SetSource"/> to set a new <see cref="IList"/> source.
+	/// </remarks>
+	public IListDataSource Source {
+		get => _source;
+		set {
+			_source = value;
+
+			// Only need to refresh list if its been added to a container view
+			if (SuperView != null && SuperView.Subviews.Contains (this)) {
+				SelectedItem = -1;
+				_search.Text = "";
+				Search_Changed (this, new TextChangedEventArgs (""));
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// This event is raised when the selected item in the <see cref="ComboBox"/> has changed.
-		/// </summary>
-		public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
-
-		/// <summary>
-		/// This event is raised when the drop-down list is expanded.
-		/// </summary>
-		public event EventHandler Expanded;
-
-		/// <summary>
-		/// This event is raised when the drop-down list is collapsed.
-		/// </summary>
-		public event EventHandler Collapsed;
-
-		/// <summary>
-		/// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item.
-		/// </summary>
-		public event EventHandler<ListViewItemEventArgs> OpenSelectedItem;
-
-		readonly IList searchset = new List<object> ();
-		string text = "";
-		readonly TextField search;
-		readonly ComboListView listview;
-		bool autoHide = true;
-		readonly int minimumHeight = 2;
-
-		/// <summary>
-		/// Public constructor
-		/// </summary>
-		public ComboBox () : this (string.Empty)
-		{
+	/// <summary>
+	/// Sets the source of the <see cref="ComboBox"/> to an <see cref="IList"/>.
+	/// </summary>
+	/// <value>An object implementing the IList interface.</value>
+	/// <remarks>
+	///  Use the <see cref="Source"/> property to set a new <see cref="IListDataSource"/> source and use custome rendering.
+	/// </remarks>
+	public void SetSource (IList source)
+	{
+		if (source == null) {
+			Source = null;
+		} else {
+			_listview.SetSource (source);
+			Source = _listview.Source;
 		}
+	}
 
-		/// <summary>
-		/// Public constructor
-		/// </summary>
-		/// <param name="text"></param>
-		public ComboBox (string text) : base ()
-		{
-			search = new TextField ("");
-			listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
+	/// <summary>
+	/// This event is raised when the selected item in the <see cref="ComboBox"/> has changed.
+	/// </summary>
+	public event EventHandler<ListViewItemEventArgs> SelectedItemChanged;
 
-			Initialize ();
-			Text = text;
-		}
+	/// <summary>
+	/// This event is raised when the drop-down list is expanded.
+	/// </summary>
+	public event EventHandler Expanded;
 
-		/// <summary>
-		/// Public constructor
-		/// </summary>
-		/// <param name="rect"></param>
-		/// <param name="source"></param>
-		public ComboBox (Rect rect, IList source) : base (rect)
-		{
-			search = new TextField ("") { Width = rect.Width };
-			listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+	/// <summary>
+	/// This event is raised when the drop-down list is collapsed.
+	/// </summary>
+	public event EventHandler Collapsed;
 
-			Initialize ();
-			SetSource (source);
-		}
+	/// <summary>
+	/// This event is raised when the user Double Clicks on an item or presses ENTER to open the selected item.
+	/// </summary>
+	public event EventHandler<ListViewItemEventArgs> OpenSelectedItem;
 
-		/// <summary>
-		/// Initialize with the source.
-		/// </summary>
-		/// <param name="source">The source.</param>
-		public ComboBox (IList source) : this (string.Empty)
-		{
-			search = new TextField ("");
-			listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+	readonly IList _searchset = new List<object> ();
+	string _text = "";
+	readonly TextField _search;
+	readonly ComboListView _listview;
+	bool _autoHide = true;
+	readonly int _minimumHeight = 2;
 
-			Initialize ();
-			SetSource (source);
-		}
+	/// <summary>
+	/// Public constructor
+	/// </summary>
+	public ComboBox () : this (string.Empty) { }
 
-		private void Initialize ()
-		{
-			if (Bounds.Height < minimumHeight && (Height == null || Height is Dim.DimAbsolute)) {
-				Height = minimumHeight;
-			}
+	/// <summary>
+	/// Public constructor
+	/// </summary>
+	/// <param name="text"></param>
+	public ComboBox (string text) : base ()
+	{
+		_search = new TextField ("");
+		_listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
+
+		SetInitialProperties ();
+		Text = text;
+	}
 
-			search.TextChanged += Search_Changed;
+	/// <summary>
+	/// Public constructor
+	/// </summary>
+	/// <param name="rect"></param>
+	/// <param name="source"></param>
+	public ComboBox (Rect rect, IList source) : base (rect)
+	{
+		_search = new TextField ("") { Width = rect.Width };
+		_listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+
+		SetInitialProperties ();
+		SetSource (source);
+	}
 
-			listview.Y = Pos.Bottom (search);
-			listview.OpenSelectedItem += (object sender, ListViewItemEventArgs a) => Selected ();
+	/// <summary>
+	/// Initialize with the source.
+	/// </summary>
+	/// <param name="source">The source.</param>
+	public ComboBox (IList source) : this (string.Empty)
+	{
+		_search = new TextField ("");
+		_listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+
+		SetInitialProperties ();
+		SetSource (source);
+	}
 
-			Add (search, listview);
+	void SetInitialProperties ()
+	{
+		_search.TextChanged += Search_Changed;
 
-			// On resize
-			LayoutComplete += (object sender, LayoutEventArgs a) => {
-				if ((!autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width) ||
-					(autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width - 1)) {
-					search.Width = listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width;
-					listview.Height = CalculatetHeight ();
-					search.SetRelativeLayout (Bounds);
-					listview.SetRelativeLayout (Bounds);
-				}
-			};
+		_listview.Y = Pos.Bottom (_search);
+		_listview.OpenSelectedItem += (object sender, ListViewItemEventArgs a) => Selected ();
 
-			listview.SelectedItemChanged += (object sender, ListViewItemEventArgs e) => {
+		Add (_search, _listview);
 
-				if (!HideDropdownListOnClick && searchset.Count > 0) {
-					SetValue (searchset [listview.SelectedItem]);
-				}
-			};
+		// On resize
+		LayoutComplete += (object sender, LayoutEventArgs a) => {
+			if (Bounds.Height < _minimumHeight && (Height == null || Height is Dim.DimAbsolute)) {
+				Height = _minimumHeight;
+			}
+			if (!_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width ||
+			_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width - 1) {
+				_search.Width = _listview.Width = _autoHide ? Bounds.Width - 1 : Bounds.Width;
+				_listview.Height = CalculatetHeight ();
+				_search.SetRelativeLayout (Bounds);
+				_listview.SetRelativeLayout (Bounds);
+			}
+		};
 
-			Added += (s, e) => {
+		_listview.SelectedItemChanged += (object sender, ListViewItemEventArgs e) => {
 
-				// Determine if this view is hosted inside a dialog and is the only control
-				for (View view = this.SuperView; view != null; view = view.SuperView) {
-					if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews [0] == this) {
-						autoHide = false;
-						break;
-					}
-				}
+			if (!HideDropdownListOnClick && _searchset.Count > 0) {
+				SetValue (_searchset [_listview.SelectedItem]);
+			}
+		};
 
-				SetNeedsLayout ();
-				SetNeedsDisplay ();
-				Search_Changed (this, new TextChangedEventArgs (Text));
-			};
-
-			// Things this view knows how to do
-			AddCommand (Command.Accept, () => ActivateSelected ());
-			AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ());
-			AddCommand (Command.Expand, () => Expand ());
-			AddCommand (Command.Collapse, () => Collapse ());
-			AddCommand (Command.LineDown, () => MoveDown ());
-			AddCommand (Command.LineUp, () => MoveUp ());
-			AddCommand (Command.PageDown, () => PageDown ());
-			AddCommand (Command.PageUp, () => PageUp ());
-			AddCommand (Command.TopHome, () => MoveHome ());
-			AddCommand (Command.BottomEnd, () => MoveEnd ());
-			AddCommand (Command.Cancel, () => CancelSelected ());
-			AddCommand (Command.UnixEmulation, () => UnixEmulation ());
-
-			// Default keybindings for this view
-			KeyBindings.Add (KeyCode.Enter, Command.Accept);
-			KeyBindings.Add (KeyCode.F4, Command.ToggleExpandCollapse);
-			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
-			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
-			KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
-			KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
-			KeyBindings.Add (KeyCode.Home, Command.TopHome);
-			KeyBindings.Add (KeyCode.End, Command.BottomEnd);
-			KeyBindings.Add (KeyCode.Esc, Command.Cancel);
-			KeyBindings.Add (KeyCode.U | KeyCode.CtrlMask, Command.UnixEmulation);
-		}
-
-		private bool isShow = false;
-		private int selectedItem = -1;
-		private int lastSelectedItem = -1;
-		private bool hideDropdownListOnClick;
-
-		/// <summary>
-		/// Gets the index of the currently selected item in the <see cref="Source"/>
-		/// </summary>
-		/// <value>The selected item or -1 none selected.</value>
-		public int SelectedItem {
-			get => selectedItem;
-			set {
-				if (selectedItem != value && (value == -1
-					|| (source != null && value > -1 && value < source.Count))) {
-
-					selectedItem = lastSelectedItem = value;
-					if (selectedItem != -1) {
-						SetValue (source.ToList () [selectedItem].ToString (), true);
-					} else {
-						SetValue ("", true);
-					}
-					OnSelectedChanged ();
+		Added += (s, e) => {
+
+			// Determine if this view is hosted inside a dialog and is the only control
+			for (var view = SuperView; view != null; view = view.SuperView) {
+				if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews [0] == this) {
+					_autoHide = false;
+					break;
 				}
 			}
-		}
 
-		/// <summary>
-		/// Gets the drop down list state, expanded or collapsed.
-		/// </summary>
-		public bool IsShow => isShow;
+			SetNeedsLayout ();
+			SetNeedsDisplay ();
+			Search_Changed (this, new TextChangedEventArgs (Text));
+		};
+
+		// Things this view knows how to do
+		AddCommand (Command.Accept, () => ActivateSelected ());
+		AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ());
+		AddCommand (Command.Expand, () => Expand ());
+		AddCommand (Command.Collapse, () => Collapse ());
+		AddCommand (Command.LineDown, () => MoveDown ());
+		AddCommand (Command.LineUp, () => MoveUp ());
+		AddCommand (Command.PageDown, () => PageDown ());
+		AddCommand (Command.PageUp, () => PageUp ());
+		AddCommand (Command.TopHome, () => MoveHome ());
+		AddCommand (Command.BottomEnd, () => MoveEnd ());
+		AddCommand (Command.Cancel, () => CancelSelected ());
+		AddCommand (Command.UnixEmulation, () => UnixEmulation ());
+
+		// Default keybindings for this view
+		KeyBindings.Add (KeyCode.Enter, Command.Accept);
+		KeyBindings.Add (KeyCode.F4, Command.ToggleExpandCollapse);
+		KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
+		KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
+		KeyBindings.Add (KeyCode.PageDown, Command.PageDown);
+		KeyBindings.Add (KeyCode.PageUp, Command.PageUp);
+		KeyBindings.Add (KeyCode.Home, Command.TopHome);
+		KeyBindings.Add (KeyCode.End, Command.BottomEnd);
+		KeyBindings.Add (KeyCode.Esc, Command.Cancel);
+		KeyBindings.Add (KeyCode.U | KeyCode.CtrlMask, Command.UnixEmulation);
+	}
 
-		///<inheritdoc/>
-		public new ColorScheme ColorScheme {
-			get {
-				return base.ColorScheme;
-			}
-			set {
-				listview.ColorScheme = value;
-				base.ColorScheme = value;
-				SetNeedsDisplay ();
-			}
-		}
+	bool _isShow = false;
+	int _selectedItem = -1;
+	int _lastSelectedItem = -1;
+	bool _hideDropdownListOnClick;
 
-		/// <summary>
-		///If set to true its not allow any changes in the text.
-		/// </summary>
-		public bool ReadOnly {
-			get => search.ReadOnly;
-			set {
-				search.ReadOnly = value;
-				if (search.ReadOnly) {
-					if (search.ColorScheme != null) {
-						search.ColorScheme.Normal = search.ColorScheme.Focus;
-					}
+	/// <summary>
+	/// Gets the index of the currently selected item in the <see cref="Source"/>
+	/// </summary>
+	/// <value>The selected item or -1 none selected.</value>
+	public int SelectedItem {
+		get => _selectedItem;
+		set {
+			if (_selectedItem != value && (value == -1
+							|| _source != null && value > -1 && value < _source.Count)) {
+
+				_selectedItem = _lastSelectedItem = value;
+				if (_selectedItem != -1) {
+					SetValue (_source.ToList () [_selectedItem].ToString (), true);
+				} else {
+					SetValue ("", true);
 				}
+				OnSelectedChanged ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Gets or sets if the drop-down list can be hide with a button click event.
-		/// </summary>
-		public bool HideDropdownListOnClick {
-			get => hideDropdownListOnClick;
-			set => hideDropdownListOnClick = listview.HideDropdownListOnClick = value;
+	/// <summary>
+	/// Gets the drop down list state, expanded or collapsed.
+	/// </summary>
+	public bool IsShow => _isShow;
+
+	///<inheritdoc/>
+	public new ColorScheme ColorScheme {
+		get => base.ColorScheme;
+		set {
+			_listview.ColorScheme = value;
+			base.ColorScheme = value;
+			SetNeedsDisplay ();
 		}
+	}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (me.X == Bounds.Right - 1 && me.Y == Bounds.Top && me.Flags == MouseFlags.Button1Pressed
-				&& autoHide) {
+	/// <summary>
+	///If set to true its not allow any changes in the text.
+	/// </summary>
+	public bool ReadOnly {
+		get => _search.ReadOnly;
+		set {
+			_search.ReadOnly = value;
+			if (_search.ReadOnly) {
+				if (_search.ColorScheme != null) {
+					_search.ColorScheme.Normal = _search.ColorScheme.Focus;
+				}
+			}
+		}
+	}
 
-				if (isShow) {
-					isShow = false;
-					HideList ();
-				} else {
-					SetSearchSet ();
+	/// <summary>
+	/// Gets or sets if the drop-down list can be hide with a button click event.
+	/// </summary>
+	public bool HideDropdownListOnClick {
+		get => _hideDropdownListOnClick;
+		set => _hideDropdownListOnClick = _listview.HideDropdownListOnClick = value;
+	}
 
-					isShow = true;
-					ShowList ();
-					FocusSelectedItem ();
-				}
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (me.X == Bounds.Right - 1 && me.Y == Bounds.Top && me.Flags == MouseFlags.Button1Pressed
+		&& _autoHide) {
 
-				return true;
-			} else if (me.Flags == MouseFlags.Button1Pressed) {
-				if (!search.HasFocus) {
-					search.SetFocus ();
-				}
+			if (_isShow) {
+				_isShow = false;
+				HideList ();
+			} else {
+				SetSearchSet ();
 
-				return true;
+				_isShow = true;
+				ShowList ();
+				FocusSelectedItem ();
 			}
 
-			return false;
-		}
+			return true;
+		} else if (me.Flags == MouseFlags.Button1Pressed) {
+			if (!_search.HasFocus) {
+				_search.SetFocus ();
+			}
 
-		private void FocusSelectedItem ()
-		{
-			listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0;
-			listview.TabStop = true;
-			listview.SetFocus ();
-			OnExpanded ();
+			return true;
 		}
 
-		/// <summary>
-		/// Virtual method which invokes the <see cref="Expanded"/> event.
-		/// </summary>
-		public virtual void OnExpanded ()
-		{
-			Expanded?.Invoke (this, EventArgs.Empty);
-		}
+		return false;
+	}
 
-		/// <summary>
-		/// Virtual method which invokes the <see cref="Collapsed"/> event.
-		/// </summary>
-		public virtual void OnCollapsed ()
-		{
-			Collapsed?.Invoke (this, EventArgs.Empty);
-		}
+	void FocusSelectedItem ()
+	{
+		_listview.SelectedItem = SelectedItem > -1 ? SelectedItem : 0;
+		_listview.TabStop = true;
+		_listview.SetFocus ();
+		OnExpanded ();
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			if (!search.HasFocus && !listview.HasFocus) {
-				search.SetFocus ();
-			}
+	/// <summary>
+	/// Virtual method which invokes the <see cref="Expanded"/> event.
+	/// </summary>
+	public virtual void OnExpanded () => Expanded?.Invoke (this, EventArgs.Empty);
 
-			search.CursorPosition = search.Text.GetRuneCount ();
+	/// <summary>
+	/// Virtual method which invokes the <see cref="Collapsed"/> event.
+	/// </summary>
+	public virtual void OnCollapsed () => Collapsed?.Invoke (this, EventArgs.Empty);
 
-			return base.OnEnter (view);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		if (!_search.HasFocus && !_listview.HasFocus) {
+			_search.SetFocus ();
 		}
 
-		///<inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			if (source?.Count > 0 && selectedItem > -1 && selectedItem < source.Count - 1
-				&& text != source.ToList () [selectedItem].ToString ()) {
+		_search.CursorPosition = _search.Text.GetRuneCount ();
 
-				SetValue (source.ToList () [selectedItem].ToString ());
-			}
-			if (autoHide && isShow && view != this && view != search && view != listview) {
-				isShow = false;
-				HideList ();
-			} else if (listview.TabStop) {
-				listview.TabStop = false;
-			}
+		return base.OnEnter (view);
+	}
 
-			return base.OnLeave (view);
+	///<inheritdoc/>
+	public override bool OnLeave (View view)
+	{
+		if (_source?.Count > 0 && _selectedItem > -1 && _selectedItem < _source.Count - 1
+		&& _text != _source.ToList () [_selectedItem].ToString ()) {
+
+			SetValue (_source.ToList () [_selectedItem].ToString ());
+		}
+		if (_autoHide && _isShow && view != this && view != _search && view != _listview) {
+			_isShow = false;
+			HideList ();
+		} else if (_listview.TabStop) {
+			_listview.TabStop = false;
 		}
 
-		/// <summary>
-		/// Invokes the SelectedChanged event if it is defined.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool OnSelectedChanged ()
-		{
-			// Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. 
-			// So we cannot optimize. Ie: Don't call if not changed
-			SelectedItemChanged?.Invoke (this, new ListViewItemEventArgs (SelectedItem, search.Text));
+		return base.OnLeave (view);
+	}
 
-			return true;
-		}
+	/// <summary>
+	/// Invokes the SelectedChanged event if it is defined.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool OnSelectedChanged ()
+	{
+		// Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. 
+		// So we cannot optimize. Ie: Don't call if not changed
+		SelectedItemChanged?.Invoke (this, new ListViewItemEventArgs (SelectedItem, _search.Text));
+
+		return true;
+	}
 
-		/// <summary>
-		/// Invokes the OnOpenSelectedItem event if it is defined.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool OnOpenSelectedItem ()
-		{
-			var value = search.Text;
-			lastSelectedItem = SelectedItem;
-			OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (SelectedItem, value));
+	/// <summary>
+	/// Invokes the OnOpenSelectedItem event if it is defined.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool OnOpenSelectedItem ()
+	{
+		string value = _search.Text;
+		_lastSelectedItem = SelectedItem;
+		OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (SelectedItem, value));
+
+		return true;
+	}
 
-			return true;
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
+
+		if (!_autoHide) {
+			return;
 		}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
+		Driver.SetAttribute (ColorScheme.Focus);
+		Move (Bounds.Right - 1, 0);
+		Driver.AddRune (Glyphs.DownArrow);
+	}
 
-			if (!autoHide) {
-				return;
+	bool UnixEmulation ()
+	{
+		// Unix emulation
+		Reset ();
+		return true;
+	}
+
+	bool CancelSelected ()
+	{
+		_search.SetFocus ();
+		if (ReadOnly || HideDropdownListOnClick) {
+			SelectedItem = _lastSelectedItem;
+			if (SelectedItem > -1 && _listview.Source?.Count > 0) {
+				_search.Text = _text = _listview.Source.ToList () [SelectedItem].ToString ();
 			}
+		} else if (!ReadOnly) {
+			_search.Text = _text = "";
+			_selectedItem = _lastSelectedItem;
+			OnSelectedChanged ();
+		}
+		return Collapse ();
+	}
 
-			Driver.SetAttribute (ColorScheme.Focus);
-			Move (Bounds.Right - 1, 0);
-			Driver.AddRune (CM.Glyphs.DownArrow);
+	bool? MoveEnd ()
+	{
+		if (!_isShow && _search.HasFocus) {
+			return null;
+		}
+		if (HasItems ()) {
+			_listview.MoveEnd ();
 		}
+		return true;
+	}
 
-		bool UnixEmulation ()
-		{
-			// Unix emulation
-			Reset ();
-			return true;
+	bool? MoveHome ()
+	{
+		if (!_isShow && _search.HasFocus) {
+			return null;
+		}
+		if (HasItems ()) {
+			_listview.MoveHome ();
 		}
+		return true;
+	}
 
-		bool CancelSelected ()
-		{
-			search.SetFocus ();
-			if (ReadOnly || HideDropdownListOnClick) {
-				SelectedItem = lastSelectedItem;
-				if (SelectedItem > -1 && listview.Source?.Count > 0) {
-					search.Text = text = listview.Source.ToList () [SelectedItem].ToString ();
-				}
-			} else if (!ReadOnly) {
-				search.Text = text = "";
-				selectedItem = lastSelectedItem;
-				OnSelectedChanged ();
-			}
-			Collapse ();
-			return true;
+	bool PageUp ()
+	{
+		if (HasItems ()) {
+			_listview.MovePageUp ();
 		}
+		return true;
+	}
 
-		bool? MoveEnd ()
-		{
-			if (!isShow && search.HasFocus) {
-				return null;
-			}
-			if (HasItems ()) {
-				listview.MoveEnd ();
-			}
-			return true;
+	bool PageDown ()
+	{
+		if (HasItems ()) {
+			_listview.MovePageDown ();
 		}
+		return true;
+	}
 
-		bool? MoveHome ()
-		{
-			if (!isShow && search.HasFocus) {
-				return null;
-			}
-			if (HasItems ()) {
-				listview.MoveHome ();
-			}
+	bool? MoveUp ()
+	{
+		if (_search.HasFocus) {
+			// stop odd behavior on KeyUp when search has focus
 			return true;
 		}
 
-		bool PageUp ()
+		if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchset?.Count > 0) // jump back to search
 		{
-			if (HasItems ()) {
-				listview.MovePageUp ();
-			}
+			_search.CursorPosition = _search.Text.GetRuneCount ();
+			_search.SetFocus ();
 			return true;
 		}
+		return null;
+	}
 
-		bool PageDown ()
-		{
-			if (HasItems ()) {
-				listview.MovePageDown ();
+	bool? MoveDown ()
+	{
+		if (_search.HasFocus) {
+			// jump to list
+			if (_searchset?.Count > 0) {
+				_listview.TabStop = true;
+				_listview.SetFocus ();
+				if (_listview.SelectedItem > -1) {
+					SetValue (_searchset [_listview.SelectedItem]);
+				}
+			} else {
+				_listview.TabStop = false;
+				SuperView?.FocusNext ();
 			}
 			return true;
 		}
+		return null;
+	}
 
-		bool? MoveUp ()
-		{
-			if (search.HasFocus) { // stop odd behavior on KeyUp when search has focus
-				return true;
-			}
-
-			if (listview.HasFocus && listview.SelectedItem == 0 && searchset?.Count > 0) // jump back to search
-			{
-				search.CursorPosition = search.Text.GetRuneCount ();
-				search.SetFocus ();
-				return true;
+	/// <summary>
+	/// Toggles the expand/collapse state of the sublist in the combo box
+	/// </summary>
+	/// <returns></returns>
+	bool ExpandCollapse ()
+	{
+		if (_search.HasFocus || _listview.HasFocus) {
+			if (!_isShow) {
+				return Expand ();
+			} else {
+				return Collapse ();
 			}
-			return null;
 		}
+		return false;
+	}
 
-		bool? MoveDown ()
-		{
-			if (search.HasFocus) { // jump to list
-				if (searchset?.Count > 0) {
-					listview.TabStop = true;
-					listview.SetFocus ();
-					if (listview.SelectedItem > -1) {
-						SetValue (searchset [listview.SelectedItem]);
-					}
-				} else {
-					listview.TabStop = false;
-					SuperView?.FocusNext ();
-				}
-				return true;
-			}
-			return null;
+	bool ActivateSelected ()
+	{
+		if (HasItems ()) {
+			Selected ();
+			return true;
 		}
+		return false;
+	}
 
-		/// <summary>
-		/// Toggles the expand/collapse state of the sublist in the combo box
-		/// </summary>
-		/// <returns></returns>
-		bool ExpandCollapse ()
-		{
-			if (search.HasFocus || listview.HasFocus) {
-				if (!isShow) {
-					return Expand ();
-				} else {
-					return Collapse ();
-				}
-			}
-			return false;
-		}
+	bool HasItems () => Source?.Count > 0;
 
-		bool ActivateSelected ()
-		{
-			if (HasItems ()) {
-				Selected ();
-				return true;
-			}
+	/// <summary>
+	/// Collapses the drop down list.  Returns true if the state chagned or false
+	/// if it was already collapsed and no action was taken
+	/// </summary>
+	public virtual bool Collapse ()
+	{
+		if (!_isShow) {
 			return false;
 		}
 
-		bool HasItems ()
-		{
-			return Source?.Count > 0;
-		}
-
-		/// <summary>
-		/// Collapses the drop down list.  Returns true if the state chagned or false
-		/// if it was already collapsed and no action was taken
-		/// </summary>
-		public virtual bool Collapse ()
-		{
-			if (!isShow) {
-				return false;
-			}
+		_isShow = false;
+		HideList ();
+		return true;
+	}
 
-			isShow = false;
-			HideList ();
-			return true;
+	/// <summary>
+	/// Expands the drop down list.  Returns true if the state chagned or false
+	/// if it was already expanded and no action was taken
+	/// </summary>
+	public virtual bool Expand ()
+	{
+		if (_isShow) {
+			return false;
 		}
 
-		/// <summary>
-		/// Expands the drop down list.  Returns true if the state chagned or false
-		/// if it was already expanded and no action was taken
-		/// </summary>
-		public virtual bool Expand ()
-		{
-			if (isShow) {
-				return false;
-			}
+		SetSearchSet ();
+		_isShow = true;
+		ShowList ();
+		FocusSelectedItem ();
 
-			SetSearchSet ();
-			isShow = true;
-			ShowList ();
-			FocusSelectedItem ();
-
-			return true;
-		}
+		return true;
+	}
 
-		/// <summary>
-		/// The currently selected list item
-		/// </summary>
-		public new string Text {
-			get {
-				return text;
-			}
-			set {
-				SetSearchText (value);
-			}
-		}
+	/// <summary>
+	/// The currently selected list item
+	/// </summary>
+	public new string Text {
+		get => _text;
+		set => SetSearchText (value);
+	}
 
-		/// <summary>
-		/// Current search text 
-		/// </summary>
-		public string SearchText {
-			get {
-				return search.Text;
-			}
-			set {
-				SetSearchText (value);
-			}
-		}
+	/// <summary>
+	/// Current search text 
+	/// </summary>
+	public string SearchText {
+		get => _search.Text;
+		set => SetSearchText (value);
+	}
 
-		private void SetValue (object text, bool isFromSelectedItem = false)
-		{
-			search.TextChanged -= Search_Changed;
-			this.text = search.Text = text.ToString ();
-			search.CursorPosition = 0;
-			search.TextChanged += Search_Changed;
-			if (!isFromSelectedItem) {
-				selectedItem = GetSelectedItemFromSource (this.text);
-				OnSelectedChanged ();
-			}
+	void SetValue (object text, bool isFromSelectedItem = false)
+	{
+		_search.TextChanged -= Search_Changed;
+		this._text = _search.Text = text.ToString ();
+		_search.CursorPosition = 0;
+		_search.TextChanged += Search_Changed;
+		if (!isFromSelectedItem) {
+			_selectedItem = GetSelectedItemFromSource (this._text);
+			OnSelectedChanged ();
 		}
+	}
 
-		private void Selected ()
-		{
-			isShow = false;
-			listview.TabStop = false;
-
-			if (listview.Source.Count == 0 || (searchset?.Count ?? 0) == 0) {
-				text = "";
-				HideList ();
-				isShow = false;
-				return;
-			}
+	void Selected ()
+	{
+		_isShow = false;
+		_listview.TabStop = false;
 
-			SetValue (listview.SelectedItem > -1 ? searchset [listview.SelectedItem] : text);
-			search.CursorPosition = search.Text.GetColumns ();
-			Search_Changed (this, new TextChangedEventArgs (search.Text));
-			OnOpenSelectedItem ();
-			Reset (keepSearchText: true);
+		if (_listview.Source.Count == 0 || (_searchset?.Count ?? 0) == 0) {
+			_text = "";
 			HideList ();
-			isShow = false;
+			_isShow = false;
+			return;
 		}
 
-		private int GetSelectedItemFromSource (string value)
+		SetValue (_listview.SelectedItem > -1 ? _searchset [_listview.SelectedItem] : _text);
+		_search.CursorPosition = _search.Text.GetColumns ();
+		Search_Changed (this, new TextChangedEventArgs (_search.Text));
+		OnOpenSelectedItem ();
+		Reset (keepSearchText: true);
+		HideList ();
+		_isShow = false;
+	}
+
+		private int GetSelectedItemFromSource (string searchText)
 		{
-			if (source == null) {
+			if (_source is null) {
 				return -1;
 			}
-			for (int i = 0; i < source.Count; i++) {
-				if (source.ToList () [i].ToString () == value) {
+			for (int i = 0; i < _searchset.Count; i++) {
+				if (_searchset [i].ToString () == searchText) {
 					return i;
 				}
 			}
 			return -1;
 		}
 
-		/// <summary>
-		/// Reset to full original list
-		/// </summary>
-		private void Reset (bool keepSearchText = false)
-		{
-			if (!keepSearchText) {
-				SetSearchText (string.Empty);
-			}
+	/// <summary>
+	/// Reset to full original list
+	/// </summary>
+	void Reset (bool keepSearchText = false)
+	{
+		if (!keepSearchText) {
+			SetSearchText (string.Empty);
+		}
 
-			ResetSearchSet ();
+		ResetSearchSet ();
 
-			listview.SetSource (searchset);
-			listview.Height = CalculatetHeight ();
+		_listview.SetSource (_searchset);
+		_listview.Height = CalculatetHeight ();
 
-			if (Subviews.Count > 0) {
-				search.SetFocus ();
-			}
-		}
-		private void SetSearchText (string value)
-		{
-			search.Text = text = value;
+		if (Subviews.Count > 0 && HasFocus) {
+			_search.SetFocus ();
 		}
+	}
 
-		private void ResetSearchSet (bool noCopy = false)
-		{
-			searchset.Clear ();
+	void SetSearchText (string value) => _search.Text = _text = value;
 
-			if (autoHide || noCopy)
-				return;
-			SetSearchSet ();
+	void ResetSearchSet (bool noCopy = false)
+	{
+		_searchset.Clear ();
+
+		if (_autoHide || noCopy) {
+			return;
 		}
+		SetSearchSet ();
+	}
 
-		private void SetSearchSet ()
-		{
-			if (Source == null) { return; }
-			// force deep copy
-			foreach (var item in Source.ToList ()) {
-				searchset.Add (item);
-			}
+	void SetSearchSet ()
+	{
+		if (Source == null) { return; }
+		// force deep copy
+		foreach (object item in Source.ToList ()) {
+			_searchset.Add (item);
 		}
+	}
 
 		private void Search_Changed (object sender, TextChangedEventArgs e)
 		{
-			if (source == null) { // Object initialization		
+			if (_source is null) { // Object initialization		
 				return;
 			}
 
-			if (string.IsNullOrEmpty (search.Text) && string.IsNullOrEmpty (e.OldValue)) {
+			if (string.IsNullOrEmpty (_search.Text) && string.IsNullOrEmpty (e.OldValue)) {
 				ResetSearchSet ();
-			} else if (search.Text != e.OldValue) {
-				isShow = true;
+			} else if (_search.Text != e.OldValue) {
+				if (_search.Text.Length < e.OldValue.Length) {
+					_selectedItem = -1;
+				}
+				_isShow = true;
 				ResetSearchSet (noCopy: true);
 
-				foreach (var item in source.ToList ()) { // Iterate to preserver object type and force deep copy
-					if (item.ToString ().StartsWith (search.Text, StringComparison.CurrentCultureIgnoreCase)) {
-						searchset.Add (item);
-					}
+			foreach (object item in _source.ToList ()) {
+				// Iterate to preserver object type and force deep copy
+				if (item.ToString ().StartsWith (_search.Text, StringComparison.CurrentCultureIgnoreCase)) {
+					_searchset.Add (item);
 				}
 			}
+		}
 
-			if (HasFocus) {
-				ShowList ();
-			} else if (autoHide) {
-				isShow = false;
-				HideList ();
-			}
+		if (HasFocus) {
+			ShowList ();
+		} else if (_autoHide) {
+			_isShow = false;
+			HideList ();
 		}
+	}
 
-		/// <summary>
-		/// Show the search list
-		/// </summary>
-		/// 
-		/// Consider making public
-		private void ShowList ()
-		{
-			listview.SetSource (searchset);
-			listview.Clear (); // Ensure list shrinks in Dialog as you type
-			listview.Height = CalculatetHeight ();
-			SuperView?.BringSubviewToFront (this);
-		}
-
-		/// <summary>
-		/// Hide the search list
-		/// </summary>
-		/// 
-		/// Consider making public
-		private void HideList ()
-		{
-			if (lastSelectedItem != selectedItem) {
-				OnOpenSelectedItem ();
-			}
-			var rect = listview.BoundsToScreen (listview.Bounds);
-			Reset (keepSearchText: true);
-			listview.Clear (rect);
-			listview.TabStop = false;
-			SuperView?.SendSubviewToBack (this);
-			SuperView?.SetNeedsDisplay (rect);
-			OnCollapsed ();
-		}
-
-		/// <summary>
-		/// Internal height of dynamic search list
-		/// </summary>
-		/// <returns></returns>
-		private int CalculatetHeight ()
-		{
-			if (Bounds.Height == 0)
-				return 0;
+	/// <summary>
+	/// Show the search list
+	/// </summary>
+	/// 
+	/// Consider making public
+	void ShowList ()
+	{
+		_listview.SetSource (_searchset);
+		_listview.Clear (); // Ensure list shrinks in Dialog as you type
+		_listview.Height = CalculatetHeight ();
+		SuperView?.BringSubviewToFront (this);
+	}
+
+	/// <summary>
+	/// Hide the search list
+	/// </summary>
+	/// 
+	/// Consider making public
+	void HideList ()
+	{
+		if (_lastSelectedItem != _selectedItem) {
+			OnOpenSelectedItem ();
+		}
+		var rect = _listview.BoundsToScreen (_listview.IsInitialized ? _listview.Bounds : Rect.Empty);
+		Reset (keepSearchText: true);
+		_listview.Clear (rect);
+		_listview.TabStop = false;
+		SuperView?.SendSubviewToBack (this);
+		SuperView?.SetNeedsDisplay (rect);
+		OnCollapsed ();
+	}
 
-			return Math.Min (Math.Max (Bounds.Height - 1, minimumHeight - 1), searchset?.Count > 0 ? searchset.Count : isShow ? Math.Max (Bounds.Height - 1, minimumHeight - 1) : 0);
+	/// <summary>
+	/// Internal height of dynamic search list
+	/// </summary>
+	/// <returns></returns>
+	int CalculatetHeight ()
+	{
+		if (!IsInitialized || Bounds.Height == 0) {
+			return 0;
 		}
+
+		return Math.Min (Math.Max (Bounds.Height - 1, _minimumHeight - 1), _searchset?.Count > 0 ? _searchset.Count : _isShow ? Math.Max (Bounds.Height - 1, _minimumHeight - 1) : 0);
 	}
-}
+}

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

@@ -92,7 +92,7 @@ public class DateField : TextField {
 		AddCommand (Command.Right, () => MoveRight ());
 
 		// Default keybindings for this view
-		KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+		KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 		KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
 
 		KeyBindings.Add (Key.Delete, Command.DeleteCharLeft);

+ 6 - 3
Terminal.Gui/Views/FileDialog.cs

@@ -198,9 +198,12 @@ namespace Terminal.Gui {
 				Width = Dim.Fill (0),
 				Height = Dim.Fill (1),
 			};
-			this.splitContainer.SetSplitterPos (0, 30);
+			
+			Initialized += (s, e) => {
+				this.splitContainer.SetSplitterPos (0, 30);
+				this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
+			};
 			//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
-			this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
 
 			this.tableView = new TableView {
 				Width = Dim.Fill (),
@@ -960,7 +963,7 @@ namespace Terminal.Gui {
 				return this.history.Forward ();
 			}
 
-			if (keyEvent.KeyCode == KeyCode.DeleteChar) {
+			if (keyEvent.KeyCode == KeyCode.Delete) {
 
 				Delete ();
 				return true;

File diff suppressed because it is too large
+ 274 - 240
Terminal.Gui/Views/Slider.cs


+ 4 - 2
Terminal.Gui/Views/TabView.cs

@@ -171,7 +171,9 @@ namespace Terminal.Gui {
 				// Should be able to just use 0 but switching between top/bottom tabs repeatedly breaks in ValidatePosDim if just using the absolute value 0
 				tabsBar.Y = Pos.Percent (0);
 			}
-			LayoutSubviews ();
+			if (IsInitialized) {
+				LayoutSubviews ();
+			}
 			SetNeedsDisplay ();
 		}
 
@@ -279,7 +281,7 @@ namespace Terminal.Gui {
 		/// </summary>
 		public void EnsureSelectedTabIsVisible ()
 		{
-			if (SelectedTab == null) {
+			if (!IsInitialized || SelectedTab == null) {
 				return;
 			}
 

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

@@ -143,10 +143,9 @@ namespace Terminal.Gui {
 
 			// Default keybindings for this view
 			// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
-			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			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 | KeyCode.ShiftMask, Command.LeftHomeExtend);
@@ -201,9 +200,9 @@ namespace Terminal.Gui {
 			KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.WordRight);
 			KeyBindings.Add ((KeyCode)((int)'F' + KeyCode.AltMask), Command.WordRight);
 
-			KeyBindings.Add (KeyCode.DeleteChar | KeyCode.CtrlMask, Command.KillWordForwards);
+			KeyBindings.Add (KeyCode.Delete | KeyCode.CtrlMask, Command.KillWordForwards);
 			KeyBindings.Add (KeyCode.Backspace | KeyCode.CtrlMask, Command.KillWordBackwards);
-			KeyBindings.Add (KeyCode.InsertChar, Command.ToggleOverwrite);
+			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);
@@ -391,6 +390,9 @@ namespace Terminal.Gui {
 		/// </summary>
 		public override void PositionCursor ()
 		{
+			if (!IsInitialized) {
+				return;
+			}
 			ProcessAutocomplete ();
 
 			var col = 0;

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

@@ -404,7 +404,7 @@ namespace Terminal.Gui {
 			KeyBindings.Add (KeyCode.End, Command.RightEnd);
 
 			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
-			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 
 			KeyBindings.Add (KeyCode.Backspace, Command.DeleteCharLeft);
 			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);

File diff suppressed because it is too large
+ 1307 - 1317
Terminal.Gui/Views/TextView.cs


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

@@ -87,7 +87,7 @@ namespace Terminal.Gui {
 			AddCommand (Command.Right, () => MoveRight ());
 
 			// Default keybindings for this view
-			KeyBindings.Add (KeyCode.DeleteChar, Command.DeleteCharRight);
+			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 			KeyBindings.Add (KeyCode.D | KeyCode.CtrlMask, Command.DeleteCharRight);
 
 			KeyBindings.Add (KeyCode.Delete, Command.DeleteCharLeft);

+ 844 - 842
Terminal.Gui/Views/Toplevel.cs

@@ -3,238 +3,252 @@ using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and
+/// for pop-up views such as <see cref="Dialog"/>, <see cref="MessageBox"/>, and <see cref="Wizard"/>.
+/// </summary>
+/// <remarks>
+///   <para>
+///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>. 
+///     They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has 
+///     been called (which sets the <see cref="Toplevel.Running"/> property to <c>false</c>). 
+///   </para>
+///   <para>
+///     A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="Application.Init"/>.
+///     The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created 
+///     and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and 
+///     call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
+///   </para>
+/// </remarks>
+public partial class Toplevel : View {
 	/// <summary>
-	/// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and
-	/// for pop-up views such as <see cref="Dialog"/>, <see cref="MessageBox"/>, and <see cref="Wizard"/>.
+	/// Gets or sets whether the main loop for this <see cref="Toplevel"/> is running or not. 
 	/// </summary>
 	/// <remarks>
-	///   <para>
-	///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>. 
-	///     They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has 
-	///     been called (which sets the <see cref="Toplevel.Running"/> property to <c>false</c>). 
-	///   </para>
-	///   <para>
-	///     A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="Application.Init(ConsoleDriver)"/>.
-	///     The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional Toplevels can be created 
-	///     and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and 
-	///     call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
-	///   </para>
+	///    Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead. 
 	/// </remarks>
-	public partial class Toplevel : View {
-		/// <summary>
-		/// Gets or sets whether the main loop for this <see cref="Toplevel"/> is running or not. 
-		/// </summary>
-		/// <remarks>
-		///    Setting this property directly is discouraged. Use <see cref="Application.RequestStop"/> instead. 
-		/// </remarks>
-		public bool Running { get; set; }
-
-		/// <summary>
-		/// Invoked when the <see cref="Toplevel"/> <see cref="RunState"/> has begun to be loaded.
-		/// A Loaded event handler is a good place to finalize initialization before calling 
-		/// <see cref="Application.RunLoop(RunState)"/>.
-		/// </summary>
-		public event EventHandler Loaded;
-
-		/// <summary>
-		/// Invoked when the <see cref="Toplevel"/> main loop has started it's first iteration.
-		/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
-		/// changes. 
-		/// <para>A Ready event handler is a good place to finalize initialization after calling 
-		/// <see cref="Application.Run(Func{Exception, bool})"/> on this <see cref="Toplevel"/>.</para>
-		/// </summary>
-		public event EventHandler Ready;
-
-		/// <summary>
-		/// Invoked when the Toplevel <see cref="RunState"/> has been unloaded.
-		/// A Unloaded event handler is a good place to dispose objects after calling <see cref="Application.End(RunState)"/>.
-		/// </summary>
-		public event EventHandler Unloaded;
-
-		/// <summary>
-		/// Invoked when the Toplevel <see cref="RunState"/> becomes the <see cref="Application.Current"/> Toplevel.
-		/// </summary>
-		public event EventHandler<ToplevelEventArgs> Activate;
-
-		/// <summary>
-		/// Invoked when the Toplevel<see cref="RunState"/> ceases to be the <see cref="Application.Current"/> Toplevel.
-		/// </summary>
-		public event EventHandler<ToplevelEventArgs> Deactivate;
-
-		/// <summary>
-		/// Invoked when a child of the Toplevel <see cref="RunState"/> is closed by  
-		/// <see cref="Application.End(RunState)"/>.
-		/// </summary>
-		public event EventHandler<ToplevelEventArgs> ChildClosed;
-
-		/// <summary>
-		/// Invoked when the last child of the Toplevel <see cref="RunState"/> is closed from 
-		/// by <see cref="Application.End(RunState)"/>.
-		/// </summary>
-		public event EventHandler AllChildClosed;
-
-		/// <summary>
-		/// Invoked when the Toplevel's <see cref="RunState"/> is being closed by  
-		/// <see cref="Application.RequestStop(Toplevel)"/>.
-		/// </summary>
-		public event EventHandler<ToplevelClosingEventArgs> Closing;
-
-		/// <summary>
-		/// Invoked when the Toplevel's <see cref="RunState"/> is closed by <see cref="Application.End(RunState)"/>.
-		/// </summary>
-		public event EventHandler<ToplevelEventArgs> Closed;
-
-		/// <summary>
-		/// Invoked when a child Toplevel's <see cref="RunState"/> has been loaded.
-		/// </summary>
-		public event EventHandler<ToplevelEventArgs> ChildLoaded;
-
-		/// <summary>
-		/// Invoked when a cjhild Toplevel's <see cref="RunState"/> has been unloaded.
-		/// </summary>
-		public event EventHandler<ToplevelEventArgs> ChildUnloaded;
-
-		/// <summary>
-		/// Invoked when the terminal has been resized. The new <see cref="Size"/> of the terminal is provided.
-		/// </summary>
-		public event EventHandler<SizeChangedEventArgs> SizeChanging;
-
-		// TODO: Make cancelable?
-		internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size);
-
-		internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top));
-
-		internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top));
-
-		internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top));
-
-		internal virtual bool OnClosing (ToplevelClosingEventArgs ev)
-		{
-			Closing?.Invoke (this, ev);
-			return ev.Cancel;
-		}
-
-		internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty);
-
-		internal virtual void OnChildClosed (Toplevel top)
-		{
-			if (IsOverlappedContainer) {
-				SetSubViewNeedsDisplay ();
-			}
-			ChildClosed?.Invoke (this, new ToplevelEventArgs (top));
-		}
+	public bool Running { get; set; }
+
+	/// <summary>
+	/// Invoked when the <see cref="Toplevel"/> <see cref="RunState"/> has begun to be loaded.
+	/// A Loaded event handler is a good place to finalize initialization before calling 
+	/// <see cref="Application.RunLoop(RunState)"/>.
+	/// </summary>
+	public event EventHandler Loaded;
+
+	/// <summary>
+	/// Invoked when the <see cref="Toplevel"/> main loop has started it's first iteration.
+	/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
+	/// changes. 
+	/// <para>A Ready event handler is a good place to finalize initialization after calling 
+	/// <see cref="Application.Run(Func{Exception, bool})"/> on this <see cref="Toplevel"/>.</para>
+	/// </summary>
+	public event EventHandler Ready;
+
+	/// <summary>
+	/// Invoked when the Toplevel <see cref="RunState"/> has been unloaded.
+	/// A Unloaded event handler is a good place to dispose objects after calling <see cref="Application.End(RunState)"/>.
+	/// </summary>
+	public event EventHandler Unloaded;
+
+	/// <summary>
+	/// Invoked when the Toplevel <see cref="RunState"/> becomes the <see cref="Application.Current"/> Toplevel.
+	/// </summary>
+	public event EventHandler<ToplevelEventArgs> Activate;
+
+	/// <summary>
+	/// Invoked when the Toplevel<see cref="RunState"/> ceases to be the <see cref="Application.Current"/> Toplevel.
+	/// </summary>
+	public event EventHandler<ToplevelEventArgs> Deactivate;
 
-		internal virtual void OnDeactivate (Toplevel activated)
-		{
-			Deactivate?.Invoke (this, new ToplevelEventArgs (activated));
+	/// <summary>
+	/// Invoked when a child of the Toplevel <see cref="RunState"/> is closed by  
+	/// <see cref="Application.End(RunState)"/>.
+	/// </summary>
+	public event EventHandler<ToplevelEventArgs> ChildClosed;
+
+	/// <summary>
+	/// Invoked when the last child of the Toplevel <see cref="RunState"/> is closed from 
+	/// by <see cref="Application.End(RunState)"/>.
+	/// </summary>
+	public event EventHandler AllChildClosed;
+
+	/// <summary>
+	/// Invoked when the Toplevel's <see cref="RunState"/> is being closed by  
+	/// <see cref="Application.RequestStop(Toplevel)"/>.
+	/// </summary>
+	public event EventHandler<ToplevelClosingEventArgs> Closing;
+
+	/// <summary>
+	/// Invoked when the Toplevel's <see cref="RunState"/> is closed by <see cref="Application.End(RunState)"/>.
+	/// </summary>
+	public event EventHandler<ToplevelEventArgs> Closed;
+
+	/// <summary>
+	/// Invoked when a child Toplevel's <see cref="RunState"/> has been loaded.
+	/// </summary>
+	public event EventHandler<ToplevelEventArgs> ChildLoaded;
+
+	/// <summary>
+	/// Invoked when a cjhild Toplevel's <see cref="RunState"/> has been unloaded.
+	/// </summary>
+	public event EventHandler<ToplevelEventArgs> ChildUnloaded;
+
+	/// <summary>
+	/// Invoked when the terminal has been resized. The new <see cref="Size"/> of the terminal is provided.
+	/// </summary>
+	public event EventHandler<SizeChangedEventArgs> SizeChanging;
+
+	// TODO: Make cancelable?
+	internal virtual void OnSizeChanging (SizeChangedEventArgs size) => SizeChanging?.Invoke (this, size);
+
+	internal virtual void OnChildUnloaded (Toplevel top) => ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top));
+
+	internal virtual void OnChildLoaded (Toplevel top) => ChildLoaded?.Invoke (this, new ToplevelEventArgs (top));
+
+	internal virtual void OnClosed (Toplevel top) => Closed?.Invoke (this, new ToplevelEventArgs (top));
+
+	internal virtual bool OnClosing (ToplevelClosingEventArgs ev)
+	{
+		Closing?.Invoke (this, ev);
+		return ev.Cancel;
+	}
+
+	internal virtual void OnAllChildClosed () => AllChildClosed?.Invoke (this, EventArgs.Empty);
+
+	internal virtual void OnChildClosed (Toplevel top)
+	{
+		if (IsOverlappedContainer) {
+			SetSubViewNeedsDisplay ();
 		}
+		ChildClosed?.Invoke (this, new ToplevelEventArgs (top));
+	}
+
+	internal virtual void OnDeactivate (Toplevel activated) => Deactivate?.Invoke (this, new ToplevelEventArgs (activated));
 
-		internal virtual void OnActivate (Toplevel deactivated)
-		{
-			Activate?.Invoke (this, new ToplevelEventArgs (deactivated));
+	internal virtual void OnActivate (Toplevel deactivated) => Activate?.Invoke (this, new ToplevelEventArgs (deactivated));
+
+	/// <summary>
+	/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
+	/// </summary>
+	public virtual void OnLoaded ()
+	{
+		IsLoaded = true;
+		foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+			tl.OnLoaded ();
 		}
+		Loaded?.Invoke (this, EventArgs.Empty);
+	}
 
-		/// <summary>
-		/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
-		/// </summary>
-		public virtual void OnLoaded ()
-		{
-			IsLoaded = true;
-			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
-				tl.OnLoaded ();
-			}
-			Loaded?.Invoke (this, EventArgs.Empty);
+	/// <summary>
+	/// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered the 
+	/// first iteration of the loop.
+	/// </summary>
+	internal virtual void OnReady ()
+	{
+		foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+			tl.OnReady ();
 		}
+		Ready?.Invoke (this, EventArgs.Empty);
+	}
 
-		/// <summary>
-		/// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered the 
-		/// first iteration of the loop.
-		/// </summary>
-		internal virtual void OnReady ()
-		{
-			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
-				tl.OnReady ();
-			}
-			Ready?.Invoke (this, EventArgs.Empty);
+	/// <summary>
+	/// Called from <see cref="Application.End(RunState)"/> before the <see cref="Toplevel"/> is disposed.
+	/// </summary>
+	internal virtual void OnUnloaded ()
+	{
+		foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+			tl.OnUnloaded ();
 		}
+		Unloaded?.Invoke (this, EventArgs.Empty);
+	}
+
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified <see cref="LayoutStyle.Absolute"/> layout.
+	/// </summary>
+	/// <param name="frame">A Superview-relative rectangle specifying the location and size for the new Toplevel</param>
+	public Toplevel (Rect frame) : base (frame) => SetInitialProperties ();
 
-		/// <summary>
-		/// Called from <see cref="Application.End(RunState)"/> before the <see cref="Toplevel"/> is disposed.
-		/// </summary>
-		internal virtual void OnUnloaded ()
-		{
-			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
-				tl.OnUnloaded ();
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Toplevel"/> class with <see cref="LayoutStyle.Computed"/> layout, 
+	/// defaulting to full screen.
+	/// </summary>
+	public Toplevel () : base ()
+	{
+		SetInitialProperties ();
+		Width = Dim.Fill ();
+		Height = Dim.Fill ();
+	}
+
+	void SetInitialProperties ()
+	{
+		ColorScheme = Colors.TopLevel;
+
+		Application.GrabbingMouse += Application_GrabbingMouse;
+		Application.UnGrabbingMouse += Application_UnGrabbingMouse;
+
+		// TODO: v2 - ALL Views (Responders??!?!) should support the commands related to 
+		//    - Focus
+		//  Move the appropriate AddCommand calls to `Responder`
+
+		// Things this view knows how to do
+		AddCommand (Command.QuitToplevel, () => {
+			QuitToplevel ();
+			return true;
+		});
+		AddCommand (Command.Suspend, () => {
+			Driver.Suspend ();
+			;
+			return true;
+		});
+		AddCommand (Command.NextView, () => {
+			MoveNextView ();
+			return true;
+		});
+		AddCommand (Command.PreviousView, () => {
+			MovePreviousView ();
+			return true;
+		});
+		AddCommand (Command.NextViewOrTop, () => {
+			MoveNextViewOrTop ();
+			return true;
+		});
+		AddCommand (Command.PreviousViewOrTop, () => {
+			MovePreviousViewOrTop ();
+			return true;
+		});
+		AddCommand (Command.Refresh, () => {
+			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;
 			}
-			Unloaded?.Invoke (this, EventArgs.Empty);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="frame">A Superview-relative rectangle specifying the location and size for the new Toplevel</param>
-		public Toplevel (Rect frame) : base (frame)
-		{
-			SetInitialProperties ();
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Toplevel"/> class with <see cref="LayoutStyle.Computed"/> layout, 
-		/// defaulting to full screen.
-		/// </summary>
-		public Toplevel () : base ()
-		{
-			SetInitialProperties ();
-			Width = Dim.Fill ();
-			Height = Dim.Fill ();
-		}
-
-		void SetInitialProperties ()
-		{
-			ColorScheme = Colors.TopLevel;
-
-			Application.GrabbingMouse += Application_GrabbingMouse;
-			Application.UnGrabbingMouse += Application_UnGrabbingMouse;
-
-			// TODO: v2 - ALL Views (Responders??!?!) should support the commands related to 
-			//    - Focus
-			//  Move the appropriate AddCommand calls to `Responder`
-
-			// Things this view knows how to do
-			AddCommand (Command.QuitToplevel, () => { QuitToplevel (); return true; });
-			AddCommand (Command.Suspend, () => { Driver.Suspend (); ; return true; });
-			AddCommand (Command.NextView, () => { MoveNextView (); return true; });
-			AddCommand (Command.PreviousView, () => { MovePreviousView (); return true; });
-			AddCommand (Command.NextViewOrTop, () => { MoveNextViewOrTop (); return true; });
-			AddCommand (Command.PreviousViewOrTop, () => { MovePreviousViewOrTop (); return true; });
-			AddCommand (Command.Refresh, () => { 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;
-			});
+			return false;
+		});
 
-			// Default keybindings for this view
-			KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel);
+		// 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.CursorRight, Command.NextView);
+		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 | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop);
+		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)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // 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
 
 #if UNIX_KEY_BINDINGS
 			KeyBindings.Add (Key.Z | Key.CtrlMask, Command.Suspend);
@@ -243,726 +257,714 @@ namespace Terminal.Gui {
 			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);
-		}
+		// This enables the default button to be activated by the Enter key.
+		KeyBindings.Add (KeyCode.Enter, Command.Accept);
+	}
 
-		private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
-		{
-			if (Application.MouseGrabView == this && _dragPosition.HasValue) {
-				e.Cancel = true;
-			}
+	void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
+	{
+		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
+			e.Cancel = true;
 		}
+	}
 
-		private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
-		{
-			if (Application.MouseGrabView == this && _dragPosition.HasValue) {
-				e.Cancel = true;
-			}
+	void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
+	{
+		if (Application.MouseGrabView == this && _dragPosition.HasValue) {
+			e.Cancel = true;
 		}
+	}
+
+	/// <summary>
+	/// Invoked when the <see cref="Application.AlternateForwardKey"/> is changed.
+	/// </summary>
+	public event EventHandler<KeyChangedEventArgs> AlternateForwardKeyChanged;
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="AlternateForwardKeyChanged"/> event.
+	/// </summary>
+	/// <param name="e"></param>
+	public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
+	{
+		KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+		AlternateForwardKeyChanged?.Invoke (this, e);
+	}
+
+	/// <summary>
+	/// Invoked when the <see cref="Application.AlternateBackwardKey"/> is changed.
+	/// </summary>
+	public event EventHandler<KeyChangedEventArgs> AlternateBackwardKeyChanged;
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="AlternateBackwardKeyChanged"/> event.
+	/// </summary>
+	/// <param name="e"></param>
+	public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
+	{
+		KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
+		AlternateBackwardKeyChanged?.Invoke (this, e);
+	}
+
+	/// <summary>
+	/// Invoked when the <see cref="Application.QuitKey"/> is changed.
+	/// </summary>
+	public event EventHandler<KeyChangedEventArgs> QuitKeyChanged;
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="QuitKeyChanged"/> event.
+	/// </summary>
+	/// <param name="e"></param>
+	public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
+	{
+		KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)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 Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
+
+	/// <summary>
+	/// Gets or sets a value indicating whether this <see cref="Toplevel"/> can focus.
+	/// </summary>
+	/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
+	public override bool CanFocus => SuperView == null ? true : base.CanFocus;
+
+	/// <summary>
+	/// Determines whether the <see cref="Toplevel"/> is modal or not. 
+	/// If set to <c>false</c> (the default):
+	/// 
+	/// <list type="bullet">
+	///   <item>
+	///		<description><see cref="View.OnKeyDown"/> events will propagate keys upwards.</description>
+	///   </item>
+	///   <item>
+	///		<description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
+	///   </item>
+	/// </list>
+	///
+	/// If set to <c>true</c>:
+	/// 
+	/// <list type="bullet">
+	///   <item>
+	///		<description><see cref="View.OnKeyDown"/> events will NOT propagate keys upwards.</description>
+	///	  </item>
+	///   <item>
+	///		<description>The Toplevel will and look like a modal (pop-up) (e.g. see <see cref="Dialog"/>.</description>
+	///   </item>
+	/// </list>
+	/// </summary>
+	public bool Modal { get; set; }
 
-		/// <summary>
-		/// Invoked when the <see cref="Application.AlternateForwardKey"/> is changed.
-		/// </summary>
-		public event EventHandler<KeyChangedEventArgs> AlternateForwardKeyChanged;
-
-		/// <summary>
-		/// Virtual method to invoke the <see cref="AlternateForwardKeyChanged"/> event.
-		/// </summary>
-		/// <param name="e"></param>
-		public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e)
-		{
-			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
-			AlternateForwardKeyChanged?.Invoke (this, e);
-		}
-
-		/// <summary>
-		/// Invoked when the <see cref="Application.AlternateBackwardKey"/> is changed.
-		/// </summary>
-		public event EventHandler<KeyChangedEventArgs> AlternateBackwardKeyChanged;
-
-		/// <summary>
-		/// Virtual method to invoke the <see cref="AlternateBackwardKeyChanged"/> event.
-		/// </summary>
-		/// <param name="e"></param>
-		public virtual void OnAlternateBackwardKeyChanged (KeyChangedEventArgs e)
-		{
-			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)e.NewKey);
-			AlternateBackwardKeyChanged?.Invoke (this, e);
-		}
-
-		/// <summary>
-		/// Invoked when the <see cref="Application.QuitKey"/> is changed.
-		/// </summary>
-		public event EventHandler<KeyChangedEventArgs> QuitKeyChanged;
-
-		/// <summary>
-		/// Virtual method to invoke the <see cref="QuitKeyChanged"/> event.
-		/// </summary>
-		/// <param name="e"></param>
-		public virtual void OnQuitKeyChanged (KeyChangedEventArgs e)
-		{
-			KeyBindings.Replace ((KeyCode)e.OldKey, (KeyCode)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 ()
-		{
-			return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
-		}
-
-		/// <summary>
-		/// Gets or sets a value indicating whether this <see cref="Toplevel"/> can focus.
-		/// </summary>
-		/// <value><c>true</c> if can focus; otherwise, <c>false</c>.</value>
-		public override bool CanFocus {
-			get => SuperView == null ? true : base.CanFocus;
-		}
-
-		/// <summary>
-		/// Determines whether the <see cref="Toplevel"/> is modal or not. 
-		/// If set to <c>false</c> (the default):
-		/// 
-		/// <list type="bullet">
-		///   <item>
-		///		<description><see cref="View.OnKeyDown"/> events will propagate keys upwards.</description>
-		///   </item>
-		///   <item>
-		///		<description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
-		///   </item>
-		/// </list>
-		///
-		/// If set to <c>true</c>:
-		/// 
-		/// <list type="bullet">
-		///   <item>
-		///		<description><see cref="View.OnKeyDown"/> events will NOT propagate keys upwards.</description>
-		///	  </item>
-		///   <item>
-		///		<description>The Toplevel will and look like a modal (pop-up) (e.g. see <see cref="Dialog"/>.</description>
-		///   </item>
-		/// </list>
-		/// </summary>
-		public bool Modal { get; set; }
-
-		/// <summary>
-		/// Gets or sets the menu for this Toplevel.
-		/// </summary>
-		public virtual MenuBar MenuBar { get; set; }
-
-		/// <summary>
-		/// Gets or sets the status bar for this Toplevel.
-		/// </summary>
-		public virtual StatusBar StatusBar { get; set; }
-
-		/// <summary>
-		/// <see langword="true"/> if was already loaded by the <see cref="Application.Begin(Toplevel)"/>
-		/// <see langword="false"/>, otherwise.
-		/// </summary>
-		public bool IsLoaded { get; private set; }
-
-		private void MovePreviousViewOrTop ()
-		{
-			if (Application.OverlappedTop == null) {
-				var top = Modal ? this : Application.Top;
+	/// <summary>
+	/// Gets or sets the menu for this Toplevel.
+	/// </summary>
+	public virtual MenuBar MenuBar { get; set; }
+
+	/// <summary>
+	/// Gets or sets the status bar for this Toplevel.
+	/// </summary>
+	public virtual StatusBar StatusBar { get; set; }
+
+	/// <summary>
+	/// <see langword="true"/> if was already loaded by the <see cref="Application.Begin(Toplevel)"/>
+	/// <see langword="false"/>, otherwise.
+	/// </summary>
+	public bool IsLoaded { get; private set; }
+
+	void MovePreviousViewOrTop ()
+	{
+		if (Application.OverlappedTop == null) {
+			var top = Modal ? this : Application.Top;
+			top.FocusPrev ();
+			if (top.Focused == null) {
 				top.FocusPrev ();
-				if (top.Focused == null) {
-					top.FocusPrev ();
-				}
-				top.SetNeedsDisplay ();
-				Application.BringOverlappedTopToFront ();
-			} else {
-				Application.OverlappedMovePrevious ();
 			}
+			top.SetNeedsDisplay ();
+			Application.BringOverlappedTopToFront ();
+		} else {
+			Application.OverlappedMovePrevious ();
 		}
+	}
 
-		private void MoveNextViewOrTop ()
-		{
-			if (Application.OverlappedTop == null) {
-				var top = Modal ? this : Application.Top;
+	void MoveNextViewOrTop ()
+	{
+		if (Application.OverlappedTop == null) {
+			var top = Modal ? this : Application.Top;
+			top.FocusNext ();
+			if (top.Focused == null) {
 				top.FocusNext ();
-				if (top.Focused == null) {
-					top.FocusNext ();
-				}
-				top.SetNeedsDisplay ();
-				Application.BringOverlappedTopToFront ();
-			} else {
-				Application.OverlappedMoveNext ();
 			}
+			top.SetNeedsDisplay ();
+			Application.BringOverlappedTopToFront ();
+		} else {
+			Application.OverlappedMoveNext ();
 		}
+	}
 
-		private void MovePreviousView ()
-		{
-			var old = GetDeepestFocusedSubview (Focused);
-			if (!FocusPrev ())
-				FocusPrev ();
-			if (old != Focused && old != Focused?.Focused) {
-				old?.SetNeedsDisplay ();
-				Focused?.SetNeedsDisplay ();
-			} else {
-				FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
-			}
+	void MovePreviousView ()
+	{
+		var old = GetDeepestFocusedSubview (Focused);
+		if (!FocusPrev ()) {
+			FocusPrev ();
+		}
+		if (old != Focused && old != Focused?.Focused) {
+			old?.SetNeedsDisplay ();
+			Focused?.SetNeedsDisplay ();
+		} else {
+			FocusNearestView (SuperView?.TabIndexes?.Reverse (), Direction.Backward);
 		}
+	}
 
-		private void MoveNextView ()
-		{
-			var old = GetDeepestFocusedSubview (Focused);
-			if (!FocusNext ())
-				FocusNext ();
-			if (old != Focused && old != Focused?.Focused) {
-				old?.SetNeedsDisplay ();
-				Focused?.SetNeedsDisplay ();
-			} else {
-				FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
-			}
+	void MoveNextView ()
+	{
+		var old = GetDeepestFocusedSubview (Focused);
+		if (!FocusNext ()) {
+			FocusNext ();
 		}
+		if (old != Focused && old != Focused?.Focused) {
+			old?.SetNeedsDisplay ();
+			Focused?.SetNeedsDisplay ();
+		} else {
+			FocusNearestView (SuperView?.TabIndexes, Direction.Forward);
+		}
+	}
 
-		private void QuitToplevel ()
-		{
-			if (Application.OverlappedTop != null) {
-				Application.OverlappedTop.RequestStop ();
-			} else {
-				Application.RequestStop ();
-			}
+	void QuitToplevel ()
+	{
+		if (Application.OverlappedTop != null) {
+			Application.OverlappedTop.RequestStop ();
+		} else {
+			Application.RequestStop ();
 		}
+	}
 
-		View GetDeepestFocusedSubview (View view)
-		{
-			if (view == null) {
-				return null;
-			}
+	View GetDeepestFocusedSubview (View view)
+	{
+		if (view == null) {
+			return null;
+		}
 
-			foreach (var v in view.Subviews) {
-				if (v.HasFocus) {
-					return GetDeepestFocusedSubview (v);
-				}
+		foreach (var v in view.Subviews) {
+			if (v.HasFocus) {
+				return GetDeepestFocusedSubview (v);
 			}
-			return view;
 		}
+		return view;
+	}
 
-		void FocusNearestView (IEnumerable<View> views, Direction direction)
-		{
-			if (views == null) {
-				return;
-			}
+	void FocusNearestView (IEnumerable<View> views, Direction direction)
+	{
+		if (views == null) {
+			return;
+		}
 
-			bool found = false;
-			bool focusProcessed = false;
-			int idx = 0;
+		bool found = false;
+		bool focusProcessed = false;
+		int idx = 0;
 
-			foreach (var v in views) {
-				if (v == this) {
-					found = true;
+		foreach (var v in views) {
+			if (v == this) {
+				found = true;
+			}
+			if (found && v != this) {
+				if (direction == Direction.Forward) {
+					SuperView?.FocusNext ();
+				} else {
+					SuperView?.FocusPrev ();
 				}
-				if (found && v != this) {
-					if (direction == Direction.Forward) {
-						SuperView?.FocusNext ();
-					} else {
-						SuperView?.FocusPrev ();
-					}
-					focusProcessed = true;
-					if (SuperView.Focused != null && SuperView.Focused != this) {
-						return;
-					}
-				} else if (found && !focusProcessed && idx == views.Count () - 1) {
-					views.ToList () [0].SetFocus ();
+				focusProcessed = true;
+				if (SuperView.Focused != null && SuperView.Focused != this) {
+					return;
 				}
-				idx++;
+			} else if (found && !focusProcessed && idx == views.Count () - 1) {
+				views.ToList () [0].SetFocus ();
 			}
+			idx++;
 		}
+	}
 
-		///<inheritdoc/>
-		public override void Add (View view)
-		{
-			CanFocus = true;
-			AddMenuStatusBar (view);
-			base.Add (view);
-		}
+	///<inheritdoc/>
+	public override void Add (View view)
+	{
+		CanFocus = true;
+		AddMenuStatusBar (view);
+		base.Add (view);
+	}
 
-		internal void AddMenuStatusBar (View view)
-		{
-			if (view is MenuBar) {
-				MenuBar = view as MenuBar;
-			}
-			if (view is StatusBar) {
-				StatusBar = view as StatusBar;
-			}
+	internal void AddMenuStatusBar (View view)
+	{
+		if (view is MenuBar) {
+			MenuBar = view as MenuBar;
 		}
+		if (view is StatusBar) {
+			StatusBar = view as StatusBar;
+		}
+	}
 
-		///<inheritdoc/>
-		public override void Remove (View view)
-		{
-			if (this is Toplevel Toplevel && Toplevel.MenuBar != null) {
-				RemoveMenuStatusBar (view);
-			}
-			base.Remove (view);
+	///<inheritdoc/>
+	public override void Remove (View view)
+	{
+		if (this is Toplevel Toplevel && Toplevel.MenuBar != null) {
+			RemoveMenuStatusBar (view);
 		}
+		base.Remove (view);
+	}
 
-		///<inheritdoc/>
-		public override void RemoveAll ()
-		{
-			if (this == Application.Top) {
-				MenuBar?.Dispose ();
-				MenuBar = null;
-				StatusBar?.Dispose ();
-				StatusBar = null;
-			}
-			base.RemoveAll ();
+	///<inheritdoc/>
+	public override void RemoveAll ()
+	{
+		if (this == Application.Top) {
+			MenuBar?.Dispose ();
+			MenuBar = null;
+			StatusBar?.Dispose ();
+			StatusBar = null;
 		}
+		base.RemoveAll ();
+	}
 
-		internal void RemoveMenuStatusBar (View view)
-		{
-			if (view is MenuBar) {
-				MenuBar?.Dispose ();
-				MenuBar = null;
-			}
-			if (view is StatusBar) {
-				StatusBar?.Dispose ();
-				StatusBar = null;
-			}
+	internal void RemoveMenuStatusBar (View view)
+	{
+		if (view is MenuBar) {
+			MenuBar?.Dispose ();
+			MenuBar = null;
 		}
+		if (view is StatusBar) {
+			StatusBar?.Dispose ();
+			StatusBar = null;
+		}
+	}
 
-		/// <summary>
-		///  Gets a new location of the <see cref="Toplevel"/> that is within the Bounds of the <paramref name="top"/>'s 
-		///  <see cref="View.SuperView"/> (e.g. for dragging a Window).
-		///  The `out` parameters are the new X and Y coordinates.
-		/// </summary>
-		/// <remarks>
-		/// If <paramref name="top"/> does not have a <see cref="View.SuperView"/> or it's SuperView is not <see cref="Application.Top"/>
-		/// the position will be bound by the <see cref="ConsoleDriver.Cols"/> and <see cref="ConsoleDriver.Rows"/>.
-		/// </remarks>
-		/// <param name="top">The Toplevel that is to be moved.</param>
-		/// <param name="targetX">The target x location.</param>
-		/// <param name="targetY">The target y location.</param>
-		/// <param name="nx">The x location that will ensure <paramref name="top"/> will be visible.</param>
-		/// <param name="ny">The y location that will ensure <paramref name="top"/> will be visible.</param>
-		/// <param name="menuBar">The new top most menuBar</param>
-		/// <param name="statusBar">The new top most statusBar</param>
-		/// <returns>
-		///  Either <see cref="Application.Top"/> (if <paramref name="top"/> does not have a Super View) or
-		///  <paramref name="top"/>'s SuperView. This can be used to ensure LayoutSubviews is called on the correct View.
-		///  </returns>
-		internal View GetLocationThatFits (Toplevel top, int targetX, int targetY,
+	/// <summary>
+	///  Gets a new location of the <see cref="Toplevel"/> that is within the Bounds of the <paramref name="top"/>'s 
+	///  <see cref="View.SuperView"/> (e.g. for dragging a Window).
+	///  The `out` parameters are the new X and Y coordinates.
+	/// </summary>
+	/// <remarks>
+	/// If <paramref name="top"/> does not have a <see cref="View.SuperView"/> or it's SuperView is not <see cref="Application.Top"/>
+	/// the position will be bound by the <see cref="ConsoleDriver.Cols"/> and <see cref="ConsoleDriver.Rows"/>.
+	/// </remarks>
+	/// <param name="top">The Toplevel that is to be moved.</param>
+	/// <param name="targetX">The target x location.</param>
+	/// <param name="targetY">The target y location.</param>
+	/// <param name="nx">The x location that will ensure <paramref name="top"/> will be visible.</param>
+	/// <param name="ny">The y location that will ensure <paramref name="top"/> will be visible.</param>
+	/// <param name="menuBar">The new top most menuBar</param>
+	/// <param name="statusBar">The new top most statusBar</param>
+	/// <returns>
+	///  Either <see cref="Application.Top"/> (if <paramref name="top"/> does not have a Super View) or
+	///  <paramref name="top"/>'s SuperView. This can be used to ensure LayoutSubviews is called on the correct View.
+	///  </returns>
+	internal View GetLocationThatFits (Toplevel top, int targetX, int targetY,
 					out int nx, out int ny, out MenuBar menuBar, out StatusBar statusBar)
-		{
-			int maxWidth;
-			View superView;
-			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
-				maxWidth = Driver.Cols;
-				superView = Application.Top;
-			} else {
-				// Use the SuperView's Bounds, not Frame
-				maxWidth = top.SuperView.Bounds.Width;
-				superView = top.SuperView;
-			}
-			if (superView.Margin != null && superView == top.SuperView) {
-				maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
-			}
-			if (top.Frame.Width <= maxWidth) {
-				nx = Math.Max (targetX, 0);
-				nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx;
-				if (nx > top.Frame.X + top.Frame.Width) {
-					nx = Math.Max (top.Frame.Right, 0);
-				}
-			} else {
-				nx = targetX;
-			}
-			//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
-			bool menuVisible, statusVisible;
-			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
-				menuVisible = Application.Top.MenuBar?.Visible == true;
-				menuBar = Application.Top.MenuBar;
-			} else {
-				var t = top.SuperView;
-				while (t is not Toplevel) {
-					t = t.SuperView;
-				}
-				menuVisible = ((Toplevel)t).MenuBar?.Visible == true;
-				menuBar = ((Toplevel)t).MenuBar;
-			}
-			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
-				maxWidth = menuVisible ? 1 : 0;
-			} else {
-				maxWidth = 0;
-			}
-			ny = Math.Max (targetY, maxWidth);
-			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
-				statusVisible = Application.Top.StatusBar?.Visible == true;
-				statusBar = Application.Top.StatusBar;
-			} else {
-				var t = top.SuperView;
-				while (t is not Toplevel) {
-					t = t.SuperView;
-				}
-				statusVisible = ((Toplevel)t).StatusBar?.Visible == true;
-				statusBar = ((Toplevel)t).StatusBar;
-			}
-			if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
-				maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows;
-			} else {
-				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;
-			}
-			ny = Math.Min (ny, maxWidth);
-			if (top.Frame.Height <= maxWidth) {
-				ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny;
-				if (ny > top.Frame.Y + top.Frame.Height) {
-					ny = Math.Max (top.Frame.Bottom, 0);
-				}
-			}
-			//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
-
-			return superView;
-		}
+	{
+		int maxWidth;
+		View superView;
+		if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+			maxWidth = Driver.Cols;
+			superView = Application.Top;
+		} else {
+			// Use the SuperView's Bounds, not Frame
+			maxWidth = top.SuperView.Bounds.Width;
+			superView = top.SuperView;
+		}
+		if (superView.Margin != null && superView == top.SuperView) {
+			maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
+		}
+		if (top.Frame.Width <= maxWidth) {
+			nx = Math.Max (targetX, 0);
+			nx = nx + top.Frame.Width > maxWidth ? Math.Max (maxWidth - top.Frame.Width, 0) : nx;
+			if (nx > top.Frame.X + top.Frame.Width) {
+				nx = Math.Max (top.Frame.Right, 0);
+			}
+		} else {
+			nx = targetX;
+		}
+		//System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}");
+		bool menuVisible, statusVisible;
+		if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+			menuVisible = Application.Top.MenuBar?.Visible == true;
+			menuBar = Application.Top.MenuBar;
+		} else {
+			var t = top.SuperView;
+			while (t is not Toplevel) {
+				t = t.SuperView;
+			}
+			menuVisible = ((Toplevel)t).MenuBar?.Visible == true;
+			menuBar = ((Toplevel)t).MenuBar;
+		}
+		if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+			maxWidth = menuVisible ? 1 : 0;
+		} else {
+			maxWidth = 0;
+		}
+		ny = Math.Max (targetY, maxWidth);
+		if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+			statusVisible = Application.Top.StatusBar?.Visible == true;
+			statusBar = Application.Top.StatusBar;
+		} else {
+			var t = top.SuperView;
+			while (t is not Toplevel) {
+				t = t.SuperView;
+			}
+			statusVisible = ((Toplevel)t).StatusBar?.Visible == true;
+			statusBar = ((Toplevel)t).StatusBar;
+		}
+		if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) {
+			maxWidth = statusVisible ? Driver.Rows - 1 : Driver.Rows;
+		} else {
+			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;
+		}
+		ny = Math.Min (ny, maxWidth);
+		if (top.Frame.Height <= maxWidth) {
+			ny = ny + top.Frame.Height > maxWidth ? Math.Max (maxWidth - top.Frame.Height, menuVisible ? 1 : 0) : ny;
+			if (ny > top.Frame.Y + top.Frame.Height) {
+				ny = Math.Max (top.Frame.Bottom, 0);
+			}
+		}
+		//System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}");
+
+		return superView;
+	}
 
-		// TODO: v2 - Not sure this is needed anymore.
-		internal void PositionToplevels ()
-		{
-			PositionToplevel (this);
-			foreach (var top in Subviews) {
-				if (top is Toplevel) {
-					PositionToplevel ((Toplevel)top);
-				}
+	// TODO: v2 - Not sure this is needed anymore.
+	internal void PositionToplevels ()
+	{
+		PositionToplevel (this);
+		foreach (var top in Subviews) {
+			if (top is Toplevel) {
+				PositionToplevel ((Toplevel)top);
 			}
 		}
+	}
 
-		/// <summary>
-		/// Adjusts the location and size of <paramref name="top"/> within this Toplevel.
-		/// Virtual method enabling implementation of specific positions for inherited <see cref="Toplevel"/> views.
-		/// </summary>
-		/// <param name="top">The Toplevel to adjust.</param>
-		public virtual void PositionToplevel (Toplevel top)
-		{
-			var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y,
-				out int nx, out int ny, out _, out StatusBar sb);
-			bool layoutSubviews = false;
-			var maxWidth = 0;
-			if (superView.Margin != null && superView == top.SuperView) {
-				maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
-			}
-			if ((superView != top || top?.SuperView != null || (top != Application.Top && top.Modal)
-				|| (top?.SuperView == null && top.IsOverlapped))
-				&& (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
-
-				if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) {
-					top.X = nx;
-					layoutSubviews = true;
-				}
-				if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) {
-					top.Y = ny;
-					layoutSubviews = true;
-				}
+	/// <summary>
+	/// Adjusts the location and size of <paramref name="top"/> within this Toplevel.
+	/// Virtual method enabling implementation of specific positions for inherited <see cref="Toplevel"/> views.
+	/// </summary>
+	/// <param name="top">The Toplevel to adjust.</param>
+	public virtual void PositionToplevel (Toplevel top)
+	{
+		var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y,
+			out int nx, out int ny, out _, out var sb);
+		bool layoutSubviews = false;
+		int maxWidth = 0;
+		if (superView.Margin != null && superView == top.SuperView) {
+			maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
+		}
+		if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal
+			|| top?.SuperView == null && top.IsOverlapped)
+		&& (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) {
+
+			if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) {
+				top.X = nx;
+				layoutSubviews = true;
 			}
-
-			// TODO: v2 - This is a hack to get the StatusBar to be positioned correctly.
-			if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0)
-				&& top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) {
-
-				top.Height = Dim.Fill (sb.Visible ? 1 : 0);
+			if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Frame.Y != ny) {
+				top.Y = ny;
 				layoutSubviews = true;
 			}
+		}
 
-			if (superView.LayoutNeeded || layoutSubviews) {
-				superView.LayoutSubviews ();
-			}
-			if (LayoutNeeded) {
-				LayoutSubviews ();
-			}
+		// TODO: v2 - This is a hack to get the StatusBar to be positioned correctly.
+		if (sb != null && !top.Subviews.Contains (sb) && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0)
+		&& top.Height is Dim.DimFill && -top.Height.Anchor (0) < 1) {
+
+			top.Height = Dim.Fill (sb.Visible ? 1 : 0);
+			layoutSubviews = true;
 		}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (!Visible) {
-				return;
-			}
+		if (superView.LayoutNeeded || layoutSubviews) {
+			superView.LayoutSubviews ();
+		}
+		if (LayoutNeeded) {
+			LayoutSubviews ();
+		}
+	}
 
-			if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) {
-				//Driver.SetAttribute (GetNormalColor ());
-				// TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc...
-				Clear ();
-				LayoutSubviews ();
-				PositionToplevels ();
-
-				if (this == Application.OverlappedTop) {
-					foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) {
-						if (top.Frame.IntersectsWith (Bounds)) {
-							if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) {
-								top.SetNeedsLayout ();
-								top.SetNeedsDisplay (top.Bounds);
-								top.Draw ();
-								top.OnRenderLineCanvas ();
-							}
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (!Visible) {
+			return;
+		}
+
+		if (NeedsDisplay || SubViewNeedsDisplay || LayoutNeeded) {
+			//Driver.SetAttribute (GetNormalColor ());
+			// TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc...
+			Clear ();
+			LayoutSubviews ();
+			PositionToplevels ();
+
+			if (this == Application.OverlappedTop) {
+				foreach (var top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) {
+					if (top.Frame.IntersectsWith (Bounds)) {
+						if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) {
+							top.SetNeedsLayout ();
+							top.SetNeedsDisplay (top.Bounds);
+							top.Draw ();
+							top.OnRenderLineCanvas ();
 						}
 					}
 				}
+			}
 
-				// This should not be here, but in base
-				foreach (var view in Subviews) {
-					if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) {
-						//view.SetNeedsLayout ();
-						view.SetNeedsDisplay (view.Bounds);
-						view.SetSubViewNeedsDisplay ();
-					}
+			// This should not be here, but in base
+			foreach (var view in Subviews) {
+				if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) {
+					//view.SetNeedsLayout ();
+					view.SetNeedsDisplay (view.Bounds);
+					view.SetSubViewNeedsDisplay ();
 				}
+			}
 
-				base.OnDrawContent (contentArea);
+			base.OnDrawContent (contentArea);
 
-				// This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true
-				//if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) {
-				//	// TODO: Hack until we can get compositing working right.
-				//	this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds);
-				//}
-			}
+			// This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true
+			//if (this.MenuBar != null && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu != null) {
+			//	// TODO: Hack until we can get compositing working right.
+			//	this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds);
+			//}
 		}
+	}
 
-		bool OutsideTopFrame (Toplevel top)
-		{
-			if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) {
-				return true;
-			}
-			return false;
+	bool OutsideTopFrame (Toplevel top)
+	{
+		if (top.Frame.X > Driver.Cols || top.Frame.Y > Driver.Rows) {
+			return true;
 		}
+		return false;
+	}
 
-		internal static Point? _dragPosition;
-		Point _startGrabPoint;
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent mouseEvent)
-		{
-			if (!CanFocus) {
-				return true;
-			}
-
-			//System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}");
-
-			int nx, ny;
-			if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed
-				|| mouseEvent.Flags == MouseFlags.Button2Pressed
-				|| mouseEvent.Flags == MouseFlags.Button3Pressed)) {
-
-				SetFocus ();
-				Application.BringOverlappedTopToFront ();
-
-				// Only start grabbing if the user clicks on the title bar.
-				// BUGBUG: Assumes Frame == Border and Title is always at Y == 0
-				if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) {
-					_startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y);
-					_dragPosition = new Point ();
-					nx = mouseEvent.X - mouseEvent.OfX;
-					ny = mouseEvent.Y - mouseEvent.OfY;
-					_dragPosition = new Point (nx, ny);
-					Application.GrabMouse (this);
+	internal static Point? _dragPosition;
+	Point _startGrabPoint;
+
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent mouseEvent)
+	{
+		if (!CanFocus) {
+			return true;
+		}
+
+		//System.Diagnostics.Debug.WriteLine ($"dragPosition before: {dragPosition.HasValue}");
+
+		int nx, ny;
+		if (!_dragPosition.HasValue && (mouseEvent.Flags == MouseFlags.Button1Pressed
+						|| mouseEvent.Flags == MouseFlags.Button2Pressed
+						|| mouseEvent.Flags == MouseFlags.Button3Pressed)) {
+
+			SetFocus ();
+			Application.BringOverlappedTopToFront ();
+
+			// Only start grabbing if the user clicks on the title bar.
+			// BUGBUG: Assumes Frame == Border and Title is always at Y == 0
+			if (mouseEvent.Y == 0 && mouseEvent.Flags == MouseFlags.Button1Pressed) {
+				_startGrabPoint = new Point (mouseEvent.X, mouseEvent.Y);
+				_dragPosition = new Point ();
+				nx = mouseEvent.X - mouseEvent.OfX;
+				ny = mouseEvent.Y - mouseEvent.OfY;
+				_dragPosition = new Point (nx, ny);
+				Application.GrabMouse (this);
+			}
+
+			//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
+			return true;
+		} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
+			mouseEvent.Flags == MouseFlags.Button3Pressed) {
+			if (_dragPosition.HasValue) {
+				if (SuperView == null) {
+					// Redraw the entire app window using just our Frame. Since we are 
+					// Application.Top, and our Frame always == our Bounds (Location is always (0,0))
+					// our Frame is actually view-relative (which is what Redraw takes).
+					// We need to pass all the view bounds because since the windows was 
+					// moved around, we don't know exactly what was the affected region.
+					Application.Top.SetNeedsDisplay ();
+				} else {
+					SuperView.SetNeedsDisplay ();
 				}
+				// BUGBUG: Assumes Frame == Border?
+				GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X),
+					mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y),
+					out nx, out ny, out _, out _);
 
-				//System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}");
-				return true;
-			} else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) ||
-				mouseEvent.Flags == MouseFlags.Button3Pressed) {
-				if (_dragPosition.HasValue) {
-					if (SuperView == null) {
-						// Redraw the entire app window using just our Frame. Since we are 
-						// Application.Top, and our Frame always == our Bounds (Location is always (0,0))
-						// our Frame is actually view-relative (which is what Redraw takes).
-						// We need to pass all the view bounds because since the windows was 
-						// moved around, we don't know exactly what was the affected region.
-						Application.Top.SetNeedsDisplay ();
-					} else {
-						SuperView.SetNeedsDisplay ();
-					}
-					// BUGBUG: Assumes Frame == Border?
-					GetLocationThatFits (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - _startGrabPoint.X : Frame.X - _startGrabPoint.X),
-						mouseEvent.Y + (SuperView == null ? mouseEvent.OfY - _startGrabPoint.Y : Frame.Y - _startGrabPoint.Y),
-						out nx, out ny, out _, out _);
-
-					_dragPosition = new Point (nx, ny);
-					X = nx;
-					Y = ny;
-					//System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}");
-
-					SetNeedsDisplay ();
-					return true;
-				}
-			}
+				_dragPosition = new Point (nx, ny);
+				X = nx;
+				Y = ny;
+				//System.Diagnostics.Debug.WriteLine ($"Drag: nx:{nx},ny:{ny}");
 
-			if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) {
-				_dragPosition = null;
-				Application.UngrabMouse ();
+				SetNeedsDisplay ();
+				return true;
 			}
+		}
 
-			//System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}");
-			//System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}");
-			return false;
+		if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && _dragPosition.HasValue) {
+			_dragPosition = null;
+			Application.UngrabMouse ();
 		}
 
-		/// <summary>
-		/// Stops and closes this <see cref="Toplevel"/>. If this Toplevel is the top-most Toplevel, 
-		/// <see cref="Application.RequestStop(Toplevel)"/> will be called, causing the application to exit.
-		/// </summary>
-		public virtual void RequestStop ()
-		{
-			if (IsOverlappedContainer && Running
-				&& (Application.Current == this
-				|| Application.Current?.Modal == false
-				|| Application.Current?.Modal == true && Application.Current?.Running == false)) {
-
-				foreach (var child in Application.OverlappedChildren) {
-					var ev = new ToplevelClosingEventArgs (this);
-					if (child.OnClosing (ev)) {
-						return;
-					}
-					child.Running = false;
-					Application.RequestStop (child);
-				}
-				Running = false;
-				Application.RequestStop (this);
-			} else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) {
-				var ev = new ToplevelClosingEventArgs (Application.Current);
-				if (OnClosing (ev)) {
-					return;
-				}
-				Application.RequestStop (Application.Current);
-			} else if (!IsOverlappedContainer && Running && (!Modal || (Modal && Application.Current != this))) {
+		//System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}");
+		//System.Diagnostics.Debug.WriteLine ($"Toplevel: {mouseEvent}");
+		return false;
+	}
+
+	/// <summary>
+	/// Stops and closes this <see cref="Toplevel"/>. If this Toplevel is the top-most Toplevel, 
+	/// <see cref="Application.RequestStop(Toplevel)"/> will be called, causing the application to exit.
+	/// </summary>
+	public virtual void RequestStop ()
+	{
+		if (IsOverlappedContainer && Running
+					&& (Application.Current == this
+					|| Application.Current?.Modal == false
+					|| Application.Current?.Modal == true && Application.Current?.Running == false)) {
+
+			foreach (var child in Application.OverlappedChildren) {
 				var ev = new ToplevelClosingEventArgs (this);
-				if (OnClosing (ev)) {
+				if (child.OnClosing (ev)) {
 					return;
 				}
-				Running = false;
-				Application.RequestStop (this);
-			} else {
-				Application.RequestStop (Application.Current);
+				child.Running = false;
+				Application.RequestStop (child);
+			}
+			Running = false;
+			Application.RequestStop (this);
+		} else if (IsOverlappedContainer && Running && Application.Current?.Modal == true && Application.Current?.Running == true) {
+			var ev = new ToplevelClosingEventArgs (Application.Current);
+			if (OnClosing (ev)) {
+				return;
 			}
+			Application.RequestStop (Application.Current);
+		} else if (!IsOverlappedContainer && Running && (!Modal || Modal && Application.Current != this)) {
+			var ev = new ToplevelClosingEventArgs (this);
+			if (OnClosing (ev)) {
+				return;
+			}
+			Running = false;
+			Application.RequestStop (this);
+		} else {
+			Application.RequestStop (Application.Current);
 		}
+	}
 
-		/// <summary>
-		/// Stops and closes the <see cref="Toplevel"/> specified by <paramref name="top"/>. If <paramref name="top"/> is the top-most Toplevel, 
-		/// <see cref="Application.RequestStop(Toplevel)"/> will be called, causing the application to exit.
-		/// </summary>
-		/// <param name="top">The Toplevel to request stop.</param>
-		public virtual void RequestStop (Toplevel top)
-		{
-			top.RequestStop ();
-		}
+	/// <summary>
+	/// Stops and closes the <see cref="Toplevel"/> specified by <paramref name="top"/>. If <paramref name="top"/> is the top-most Toplevel, 
+	/// <see cref="Application.RequestStop(Toplevel)"/> will be called, causing the application to exit.
+	/// </summary>
+	/// <param name="top">The Toplevel to request stop.</param>
+	public virtual void RequestStop (Toplevel top) => top.RequestStop ();
 
-		///<inheritdoc/>
-		public override void PositionCursor ()
-		{
-			if (!IsOverlappedContainer) {
-				base.PositionCursor ();
+	///<inheritdoc/>
+	public override void PositionCursor ()
+	{
+		if (!IsOverlappedContainer) {
+			base.PositionCursor ();
+			if (Focused == null) {
+				EnsureFocus ();
 				if (Focused == null) {
-					EnsureFocus ();
-					if (Focused == null) {
-						Driver.SetCursorVisibility (CursorVisibility.Invisible);
-					}
+					Driver.SetCursorVisibility (CursorVisibility.Invisible);
 				}
-				return;
 			}
+			return;
+		}
 
-			if (Focused == null) {
-				foreach (var top in Application.OverlappedChildren) {
-					if (top != this && top.Visible) {
-						top.SetFocus ();
-						return;
-					}
+		if (Focused == null) {
+			foreach (var top in Application.OverlappedChildren) {
+				if (top != this && top.Visible) {
+					top.SetFocus ();
+					return;
 				}
 			}
-			base.PositionCursor ();
-			if (Focused == null) {
-				Driver.SetCursorVisibility (CursorVisibility.Invisible);
-			}
 		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			return MostFocused?.OnEnter (view) ?? base.OnEnter (view);
+		base.PositionCursor ();
+		if (Focused == null) {
+			Driver.SetCursorVisibility (CursorVisibility.Invisible);
 		}
+	}
 
-		///<inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			return MostFocused?.OnLeave (view) ?? base.OnLeave (view);
-		}
+	///<inheritdoc/>
+	public override bool OnEnter (View view) => MostFocused?.OnEnter (view) ?? base.OnEnter (view);
 
-		///<inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			Application.GrabbingMouse -= Application_GrabbingMouse;
-			Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+	///<inheritdoc/>
+	public override bool OnLeave (View view) => MostFocused?.OnLeave (view) ?? base.OnLeave (view);
 
-			_dragPosition = null;
-			base.Dispose (disposing);
+	///<inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		Application.GrabbingMouse -= Application_GrabbingMouse;
+		Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+
+		_dragPosition = null;
+		base.Dispose (disposing);
+	}
+}
+/// <summary>
+/// Implements the <see cref="IEqualityComparer{T}"/> for comparing two <see cref="Toplevel"/>s
+/// used by <see cref="StackExtensions"/>.
+/// </summary>
+public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
+	/// <summary>Determines whether the specified objects are equal.</summary>
+	/// <param name="x">The first object of type <see cref="Toplevel" /> to compare.</param>
+	/// <param name="y">The second object of type <see cref="Toplevel" /> to compare.</param>
+	/// <returns>
+	///     <see langword="true" /> if the specified objects are equal; otherwise, <see langword="false" />.</returns>
+	public bool Equals (Toplevel x, Toplevel y)
+	{
+		if (y == null && x == null) {
+			return true;
+		} else if (x == null || y == null) {
+			return false;
+		} else if (x.Id == y.Id) {
+			return true;
+		} else {
+			return false;
 		}
 	}
 
-	/// <summary>
-	/// Implements the <see cref="IEqualityComparer{T}"/> for comparing two <see cref="Toplevel"/>s
-	/// used by <see cref="StackExtensions"/>.
-	/// </summary>
-	public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
-		/// <summary>Determines whether the specified objects are equal.</summary>
-		/// <param name="x">The first object of type <see cref="Toplevel" /> to compare.</param>
-		/// <param name="y">The second object of type <see cref="Toplevel" /> to compare.</param>
-		/// <returns>
-		///     <see langword="true" /> if the specified objects are equal; otherwise, <see langword="false" />.</returns>
-		public bool Equals (Toplevel x, Toplevel y)
-		{
-			if (y == null && x == null)
-				return true;
-			else if (x == null || y == null)
-				return false;
-			else if (x.Id == y.Id)
-				return true;
-			else
-				return false;
-		}
-
-		/// <summary>Returns a hash code for the specified object.</summary>
-		/// <param name="obj">The <see cref="Toplevel" /> for which a hash code is to be returned.</param>
-		/// <returns>A hash code for the specified object.</returns>
-		/// <exception cref="ArgumentNullException">The type of <paramref name="obj" /> 
-		/// is a reference type and <paramref name="obj" /> is <see langword="null" />.</exception>
-		public int GetHashCode (Toplevel obj)
-		{
-			if (obj == null)
-				throw new ArgumentNullException ();
-
-			int hCode = 0;
-			if (int.TryParse (obj.Id, out int result)) {
-				hCode = result;
-			}
-			return hCode.GetHashCode ();
+	/// <summary>Returns a hash code for the specified object.</summary>
+	/// <param name="obj">The <see cref="Toplevel" /> for which a hash code is to be returned.</param>
+	/// <returns>A hash code for the specified object.</returns>
+	/// <exception cref="ArgumentNullException">The type of <paramref name="obj" /> 
+	/// is a reference type and <paramref name="obj" /> is <see langword="null" />.</exception>
+	public int GetHashCode (Toplevel obj)
+	{
+		if (obj == null) {
+			throw new ArgumentNullException ();
 		}
-	}
 
-	/// <summary>
-	/// Implements the <see cref="IComparer{T}"/> to sort the <see cref="Toplevel"/> 
-	/// from the <see cref="Application.OverlappedChildren"/> if needed.
-	/// </summary>
-	public sealed class ToplevelComparer : IComparer<Toplevel> {
-		/// <summary>Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.</summary>
-		/// <param name="x">The first object to compare.</param>
-		/// <param name="y">The second object to compare.</param>
-		/// <returns>A signed integer that indicates the relative values of <paramref name="x" /> and <paramref name="y" />, as shown in the following table.Value Meaning Less than zero
-		///             <paramref name="x" /> is less than <paramref name="y" />.Zero
-		///             <paramref name="x" /> equals <paramref name="y" />.Greater than zero
-		///             <paramref name="x" /> is greater than <paramref name="y" />.</returns>
-		public int Compare (Toplevel x, Toplevel y)
-		{
-			if (ReferenceEquals (x, y))
-				return 0;
-			else if (x == null)
-				return -1;
-			else if (y == null)
-				return 1;
-			else
-				return string.Compare (x.Id, y.Id);
+		int hCode = 0;
+		if (int.TryParse (obj.Id, out int result)) {
+			hCode = result;
 		}
+		return hCode.GetHashCode ();
 	}
 }
+/// <summary>
+/// Implements the <see cref="IComparer{T}"/> to sort the <see cref="Toplevel"/> 
+/// from the <see cref="Application.OverlappedChildren"/> if needed.
+/// </summary>
+public sealed class ToplevelComparer : IComparer<Toplevel> {
+	/// <summary>Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.</summary>
+	/// <param name="x">The first object to compare.</param>
+	/// <param name="y">The second object to compare.</param>
+	/// <returns>A signed integer that indicates the relative values of <paramref name="x" /> and <paramref name="y" />, as shown in the following table.Value Meaning Less than zero
+	///             <paramref name="x" /> is less than <paramref name="y" />.Zero
+	///             <paramref name="x" /> equals <paramref name="y" />.Greater than zero
+	///             <paramref name="x" /> is greater than <paramref name="y" />.</returns>
+	public int Compare (Toplevel x, Toplevel y)
+	{
+		if (ReferenceEquals (x, y)) {
+			return 0;
+		} else if (x == null) {
+			return -1;
+		} else if (y == null) {
+			return 1;
+		} else {
+			return string.Compare (x.Id, y.Id);
+		}
+	}
+}

+ 1 - 0
Terminal.sln

@@ -14,6 +14,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "Example\Example.
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E143FB1F-0B88-48CB-9086-72CDCECFCD22}"
 	ProjectSection(SolutionItems) = preProject
+		.editorconfig = .editorconfig
 		.gitignore = .gitignore
 		.github\workflows\api-docs.yml = .github\workflows\api-docs.yml
 		.github\CODEOWNERS = .github\CODEOWNERS

+ 10 - 9
UICatalog/Properties/launchSettings.json

@@ -1,10 +1,15 @@
 {
   "profiles": {
     "UICatalog": {
+      "commandName": "Project"
+    },
+    "UICatalog --driver NetDriver": {
+      "commandName": "Project",
+      "commandLineArgs": "--driver NetDriver"
+    },
+    "UICatalog --driver WindowsDriver": {
       "commandName": "Project",
-      "environmentVariables": {
-        "WT_SESSION": "yes"
-      }
+      "commandLineArgs": "--driver WindowsDriver"
     },
     "WSL : UICatalog": {
       "commandName": "Executable",
@@ -12,14 +17,10 @@
       "commandLineArgs": "dotnet UICatalog.dll",
       "distributionName": ""
     },
-    "UICatalog -usc": {
-      "commandName": "Project",
-      "commandLineArgs": "-usc"
-    },
-    "WSL: UICatalog -usc": {
+    "WSL: UICatalog --driver NetDriver": {
       "commandName": "Executable",
       "executablePath": "wsl",
-      "commandLineArgs": "dotnet UICatalog.dll -usc",
+      "commandLineArgs": "dotnet UICatalog.dll --driver NetDriver",
       "distributionName": ""
     },
     "Sliders": {

+ 1 - 3
UICatalog/Resources/config.json

@@ -1,8 +1,6 @@
 {
   "$schema": "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
-  "Application.QuitKey": {
-    "Key": "Esc"
-  },
+  "Application.QuitKey": "Esc",
   "FileDialog.MaxSearchResults": 10000,
   "FileDialogStyle.DefaultUseColors": false,
   "FileDialogStyle.DefaultUseUnicodeCharacters": false,

+ 1 - 0
UICatalog/Scenarios/ASCIICustomButton.cs

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

+ 354 - 385
UICatalog/Scenarios/AllViewsTester.cs

@@ -1,438 +1,407 @@
 using System;
-using System.Collections;
 using System.Collections.Generic;
-using System.Diagnostics;
 using System.Linq;
 using System.Reflection;
-using System.Text;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "All Views Tester", Description: "Provides a test UI for all classes derived from View.")]
-	[ScenarioCategory ("Layout")]
-	[ScenarioCategory ("Tests")]
-	[ScenarioCategory ("Top Level Windows")]
-	public class AllViewsTester : Scenario {
-		FrameView _leftPane;
-		ListView _classListView;
-		FrameView _hostPane;
-
-		Dictionary<string, Type> _viewClasses;
-		View _curView = null;
-
-		// Settings
-		FrameView _settingsPane;
-		CheckBox _computedCheckBox;
-		FrameView _locationFrame;
-		RadioGroup _xRadioGroup;
-		TextField _xText;
-		int _xVal = 0;
-		RadioGroup _yRadioGroup;
-		TextField _yText;
-		int _yVal = 0;
-
-		FrameView _sizeFrame;
-		RadioGroup _wRadioGroup;
-		TextField _wText;
-		int _wVal = 0;
-		RadioGroup _hRadioGroup;
-		TextField _hText;
-		int _hVal = 0;
-
-		public override void Init ()
-		{
-			// Don't create a sub-win (Scenario.Win); just use Application.Top
-			Application.Init ();
-			ConfigurationManager.Themes.Theme = Theme;
-			ConfigurationManager.Apply ();
-			Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
-		}
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("All Views Tester", "Provides a test UI for all classes derived from View.")]
+[ScenarioCategory ("Layout")]
+[ScenarioCategory ("Tests")]
+[ScenarioCategory ("Top Level Windows")]
+public class AllViewsTester : Scenario {
+	FrameView _leftPane;
+	ListView _classListView;
+	FrameView _hostPane;
+
+	Dictionary<string, Type> _viewClasses;
+	View _curView = null;
+
+	// Settings
+	FrameView _settingsPane;
+	CheckBox _computedCheckBox;
+	FrameView _locationFrame;
+	RadioGroup _xRadioGroup;
+	TextField _xText;
+	int _xVal = 0;
+	RadioGroup _yRadioGroup;
+	TextField _yText;
+	int _yVal = 0;
+
+	FrameView _sizeFrame;
+	RadioGroup _wRadioGroup;
+	TextField _wText;
+	int _wVal = 0;
+	RadioGroup _hRadioGroup;
+	TextField _hText;
+	int _hVal = 0;
+
+	public override void Init ()
+	{
+		// Don't create a sub-win (Scenario.Win); just use Application.Top
+		Application.Init ();
+		ConfigurationManager.Themes.Theme = Theme;
+		ConfigurationManager.Apply ();
+		Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+	}
 
-		public override void Setup ()
-		{
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-				new StatusItem(KeyCode.F2, "~F2~ Toggle Frame Ruler", () => {
-					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
-					Application.Top.SetNeedsDisplay ();
-				}),
-				new StatusItem(KeyCode.F3, "~F3~ Toggle Frame Padding", () => {
-					ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
-					Application.Top.SetNeedsDisplay ();
-				}),
-			});
-			Application.Top.Add (statusBar);
-
-			_viewClasses = GetAllViewClassesCollection ()
+	public override void Setup ()
+	{
+		var statusBar = new StatusBar (new StatusItem [] {
+			new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ()),
+			new (KeyCode.F2, "~F2~ Toggle Frame Ruler", () => {
+				ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
+				Application.Top.SetNeedsDisplay ();
+			}),
+			new (KeyCode.F3, "~F3~ Toggle Frame Padding", () => {
+				ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
+				Application.Top.SetNeedsDisplay ();
+			})
+		});
+		Application.Top.Add (statusBar);
+
+		_viewClasses = GetAllViewClassesCollection ()
 				.OrderBy (t => t.Name)
 				.Select (t => new KeyValuePair<string, Type> (t.Name, t))
 				.ToDictionary (t => t.Key, t => t.Value);
 
-			_leftPane = new FrameView ("Classes") {
-				X = 0,
-				Y = 0,
-				Width = 15,
-				Height = Dim.Fill (1), // for status bar
-				CanFocus = false,
-				ColorScheme = Colors.TopLevel,
-			};
-
-			_classListView = new ListView (_viewClasses.Keys.ToList ()) {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (0),
-				AllowsMarking = false,
-				ColorScheme = Colors.TopLevel,
-				SelectedItem = 0
-			};
-			_classListView.OpenSelectedItem += (s, a) => {
-				_settingsPane.SetFocus ();
-			};
-			_classListView.SelectedItemChanged += (s,args) => {
-				// Remove existing class, if any
-				if (_curView != null) {
-					_curView.LayoutComplete -= LayoutCompleteHandler;
-					_hostPane.Remove (_curView);
-					_curView.Dispose ();
-					_curView = null;
-					_hostPane.Clear ();
-				}
-				_curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
-			};
-			_leftPane.Add (_classListView);
-
-			_settingsPane = new FrameView ("Settings") {
-				X = Pos.Right (_leftPane),
-				Y = 0, // for menu
-				Width = Dim.Fill (),
-				Height = 10,
-				CanFocus = false,
-				ColorScheme = Colors.TopLevel,
-			};
-			_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
-			_computedCheckBox.Toggled += (s,e) => {
-				if (_curView != null) {
-					_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed;
-					_hostPane.LayoutSubviews ();
-				}
-			};
-			_settingsPane.Add (_computedCheckBox);
-
-			var radioItems = new string [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
-			_locationFrame = new FrameView ("Location (Pos)") {
-				X = Pos.Left (_computedCheckBox),
-				Y = Pos.Bottom (_computedCheckBox),
-				Height = 3 + radioItems.Length,
-				Width = 36,
-			};
-			_settingsPane.Add (_locationFrame);
-
-			var label = new Label ("x:") { X = 0, Y = 0 };
-			_locationFrame.Add (label);
-			_xRadioGroup = new RadioGroup (radioItems) {
-				X = 0,
-				Y = Pos.Bottom (label),
-			};
-			_xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
-			_xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
-			_xText.TextChanged += (s, args) => {
-				try {
-					_xVal = int.Parse (_xText.Text);
-					DimPosChanged (_curView);
-				} catch {
-
-				}
-			};
-			_locationFrame.Add (_xText);
-
-			_locationFrame.Add (_xRadioGroup);
-
-			radioItems = new string [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
-			label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
-			_locationFrame.Add (label);
-			_yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
-			_yText.TextChanged += (s,args) => {
-				try {
-					_yVal = int.Parse (_yText.Text);
-					DimPosChanged (_curView);
-				} catch {
-
-				}
-			};
-			_locationFrame.Add (_yText);
-			_yRadioGroup = new RadioGroup (radioItems) {
-				X = Pos.X (label),
-				Y = Pos.Bottom (label),
-			};
-			_yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
-			_locationFrame.Add (_yRadioGroup);
-
-			_sizeFrame = new FrameView ("Size (Dim)") {
-				X = Pos.Right (_locationFrame),
-				Y = Pos.Y (_locationFrame),
-				Height = 3 + radioItems.Length,
-				Width = 40,
-			};
-
-			radioItems = new string [] { "Percent(width)", "Fill(width)", "Sized(width)" };
-			label = new Label ("width:") { X = 0, Y = 0 };
-			_sizeFrame.Add (label);
-			_wRadioGroup = new RadioGroup (radioItems) {
-				X = 0,
-				Y = Pos.Bottom (label),
-			};
-			_wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
-			_wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
-			_wText.TextChanged += (s,args) => {
-				try {
-					switch (_wRadioGroup.SelectedItem) {
-					case 0:
-						_wVal = Math.Min (int.Parse (_wText.Text), 100);
-						break;
-					case 1:
-					case 2:
-						_wVal = int.Parse (_wText.Text);
-						break;
-					}
-					DimPosChanged (_curView);
-				} catch {
-
-				}
-			};
-			_sizeFrame.Add (_wText);
-			_sizeFrame.Add (_wRadioGroup);
-
-			radioItems = new string [] { "Percent(height)", "Fill(height)", "Sized(height)" };
-			label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
-			_sizeFrame.Add (label);
-			_hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
-			_hText.TextChanged += (s, args) => {
-				try {
-					switch (_hRadioGroup.SelectedItem) {
-					case 0:
-						_hVal = Math.Min (int.Parse (_hText.Text), 100);
-						break;
-					case 1:
-					case 2:
-						_hVal = int.Parse (_hText.Text);
-						break;
-					}
-					DimPosChanged (_curView);
-				} catch {
-
-				}
-			};
-			_sizeFrame.Add (_hText);
-
-			_hRadioGroup = new RadioGroup (radioItems) {
-				X = Pos.X (label),
-				Y = Pos.Bottom (label),
-			};
-			_hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
-			_sizeFrame.Add (_hRadioGroup);
-
-			_settingsPane.Add (_sizeFrame);
-
-			_hostPane = new FrameView ("") {
-				X = Pos.Right (_leftPane),
-				Y = Pos.Bottom (_settingsPane),
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1), // + 1 for status bar
-				ColorScheme = Colors.Dialog,
-			};
-
-			Application.Top.Add (_leftPane, _settingsPane, _hostPane);
-
-			_curView = CreateClass (_viewClasses.First ().Value);
-		}
-
-		void DimPosChanged (View view)
-		{
-			if (view == null) {
-				return;
+		_leftPane = new FrameView ("Classes") {
+			X = 0,
+			Y = 0,
+			Width = 15,
+			Height = Dim.Fill (1), // for status bar
+			CanFocus = false,
+			ColorScheme = Colors.TopLevel
+		};
+
+		_classListView = new ListView (_viewClasses.Keys.ToList ()) {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (0),
+			Height = Dim.Fill (0),
+			AllowsMarking = false,
+			ColorScheme = Colors.TopLevel,
+			SelectedItem = 0
+		};
+		_classListView.OpenSelectedItem += (s, a) => {
+			_settingsPane.SetFocus ();
+		};
+		_classListView.SelectedItemChanged += (s, args) => {
+			// Remove existing class, if any
+			if (_curView != null) {
+				_curView.LayoutComplete -= LayoutCompleteHandler;
+				_hostPane.Remove (_curView);
+				_curView.Dispose ();
+				_curView = null;
+				_hostPane.Clear ();
 			}
-
-			var layout = view.LayoutStyle;
-
+			_curView = CreateClass (_viewClasses.Values.ToArray () [_classListView.SelectedItem]);
+		};
+		_leftPane.Add (_classListView);
+
+		_settingsPane = new FrameView ("Settings") {
+			X = Pos.Right (_leftPane),
+			Y = 0, // for menu
+			Width = Dim.Fill (),
+			Height = 10,
+			CanFocus = false,
+			ColorScheme = Colors.TopLevel
+		};
+		_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
+		_computedCheckBox.Toggled += (s, e) => {
+			if (_curView != null) {
+				_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed;
+				_hostPane.LayoutSubviews ();
+			}
+		};
+		_settingsPane.Add (_computedCheckBox);
+
+		string [] radioItems = new string [] { "Percent(x)", "AnchorEnd(x)", "Center", "At(x)" };
+		_locationFrame = new FrameView ("Location (Pos)") {
+			X = Pos.Left (_computedCheckBox),
+			Y = Pos.Bottom (_computedCheckBox),
+			Height = 3 + radioItems.Length,
+			Width = 36
+		};
+		_settingsPane.Add (_locationFrame);
+
+		var label = new Label ("x:") { X = 0, Y = 0 };
+		_locationFrame.Add (label);
+		_xRadioGroup = new RadioGroup (radioItems) {
+			X = 0,
+			Y = Pos.Bottom (label)
+		};
+		_xRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
+		_xText = new TextField ($"{_xVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+		_xText.TextChanged += (s, args) => {
+			try {
+				_xVal = int.Parse (_xText.Text);
+				DimPosChanged (_curView);
+			} catch { }
+		};
+		_locationFrame.Add (_xText);
+
+		_locationFrame.Add (_xRadioGroup);
+
+		radioItems = new string [] { "Percent(y)", "AnchorEnd(y)", "Center", "At(y)" };
+		label = new Label ("y:") { X = Pos.Right (_xRadioGroup) + 1, Y = 0 };
+		_locationFrame.Add (label);
+		_yText = new TextField ($"{_yVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+		_yText.TextChanged += (s, args) => {
+			try {
+				_yVal = int.Parse (_yText.Text);
+				DimPosChanged (_curView);
+			} catch { }
+		};
+		_locationFrame.Add (_yText);
+		_yRadioGroup = new RadioGroup (radioItems) {
+			X = Pos.X (label),
+			Y = Pos.Bottom (label)
+		};
+		_yRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
+		_locationFrame.Add (_yRadioGroup);
+
+		_sizeFrame = new FrameView ("Size (Dim)") {
+			X = Pos.Right (_locationFrame),
+			Y = Pos.Y (_locationFrame),
+			Height = 3 + radioItems.Length,
+			Width = 40
+		};
+
+		radioItems = new string [] { "Percent(width)", "Fill(width)", "Sized(width)" };
+		label = new Label ("width:") { X = 0, Y = 0 };
+		_sizeFrame.Add (label);
+		_wRadioGroup = new RadioGroup (radioItems) {
+			X = 0,
+			Y = Pos.Bottom (label)
+		};
+		_wRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
+		_wText = new TextField ($"{_wVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+		_wText.TextChanged += (s, args) => {
 			try {
-				view.LayoutStyle = LayoutStyle.Absolute;
-
-				switch (_xRadioGroup.SelectedItem) {
-				case 0:
-					view.X = Pos.Percent (_xVal);
-					break;
-				case 1:
-					view.X = Pos.AnchorEnd (_xVal);
-					break;
-				case 2:
-					view.X = Pos.Center ();
-					break;
-				case 3:
-					view.X = Pos.At (_xVal);
-					break;
-				}
-
-				switch (_yRadioGroup.SelectedItem) {
-				case 0:
-					view.Y = Pos.Percent (_yVal);
-					break;
-				case 1:
-					view.Y = Pos.AnchorEnd (_yVal);
-					break;
-				case 2:
-					view.Y = Pos.Center ();
-					break;
-				case 3:
-					view.Y = Pos.At (_yVal);
-					break;
-				}
-
 				switch (_wRadioGroup.SelectedItem) {
 				case 0:
-					view.Width = Dim.Percent (_wVal);
+					_wVal = Math.Min (int.Parse (_wText.Text), 100);
 					break;
 				case 1:
-					view.Width = Dim.Fill (_wVal);
-					break;
 				case 2:
-					view.Width = Dim.Sized (_wVal);
+					_wVal = int.Parse (_wText.Text);
 					break;
 				}
-
+				DimPosChanged (_curView);
+			} catch { }
+		};
+		_sizeFrame.Add (_wText);
+		_sizeFrame.Add (_wRadioGroup);
+
+		radioItems = new string [] { "Percent(height)", "Fill(height)", "Sized(height)" };
+		label = new Label ("height:") { X = Pos.Right (_wRadioGroup) + 1, Y = 0 };
+		_sizeFrame.Add (label);
+		_hText = new TextField ($"{_hVal}") { X = Pos.Right (label) + 1, Y = 0, Width = 4 };
+		_hText.TextChanged += (s, args) => {
+			try {
 				switch (_hRadioGroup.SelectedItem) {
 				case 0:
-					view.Height = Dim.Percent (_hVal);
+					_hVal = Math.Min (int.Parse (_hText.Text), 100);
 					break;
 				case 1:
-					view.Height = Dim.Fill (_hVal);
-					break;
 				case 2:
-					view.Height = Dim.Sized (_hVal);
+					_hVal = int.Parse (_hText.Text);
 					break;
 				}
-			} catch (Exception e) {
-				MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
-			} finally {
-				view.LayoutStyle = layout;
-			}
-			UpdateTitle (view);
-		}
+				DimPosChanged (_curView);
+			} catch { }
+		};
+		_sizeFrame.Add (_hText);
+
+		_hRadioGroup = new RadioGroup (radioItems) {
+			X = Pos.X (label),
+			Y = Pos.Bottom (label)
+		};
+		_hRadioGroup.SelectedItemChanged += (s, selected) => DimPosChanged (_curView);
+		_sizeFrame.Add (_hRadioGroup);
+
+		_settingsPane.Add (_sizeFrame);
+
+		_hostPane = new FrameView ("") {
+			X = Pos.Right (_leftPane),
+			Y = Pos.Bottom (_settingsPane),
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1), // + 1 for status bar
+			ColorScheme = Colors.Dialog
+		};
+
+		Application.Top.Add (_leftPane, _settingsPane, _hostPane);
+
+		_curView = CreateClass (_viewClasses.First ().Value);
+	}
 
-		List<string> posNames = new List<String> { "Factor", "AnchorEnd", "Center", "Absolute" };
-		List<string> dimNames = new List<String> { "Factor", "Fill", "Absolute" };
-
-		void UpdateSettings (View view)
-		{
-			var x = view.X.ToString ();
-			var y = view.Y.ToString ();
-			_xRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => x.Contains (s)).First ());
-			_yRadioGroup.SelectedItem = posNames.IndexOf (posNames.Where (s => y.Contains (s)).First ());
-			_xText.Text = $"{view.Frame.X}";
-			_yText.Text = $"{view.Frame.Y}";
-
-			var w = view.Width.ToString ();
-			var h = view.Height.ToString ();
-			_wRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => w.Contains (s)).First ());
-			_hRadioGroup.SelectedItem = dimNames.IndexOf (dimNames.Where (s => h.Contains (s)).First ());
-			_wText.Text = $"{view.Frame.Width}";
-			_hText.Text = $"{view.Frame.Height}";
+	void DimPosChanged (View view)
+	{
+		if (view == null) {
+			return;
 		}
 
-		void UpdateTitle (View view)
-		{
-			_hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}";
-		}
+		var layout = view.LayoutStyle;
 
-		List<Type> GetAllViewClassesCollection ()
-		{
-			List<Type> types = new List<Type> ();
-			foreach (Type type in typeof (View).Assembly.GetTypes ()
-			 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
-				types.Add (type);
-			}
-			types.Add (typeof (View));
-			return types;
+		try {
+			view.LayoutStyle = LayoutStyle.Absolute;
+
+			view.X = _xRadioGroup.SelectedItem switch {
+				0 => Pos.Percent (_xVal),
+				1 => Pos.AnchorEnd (_xVal),
+				2 => Pos.Center (),
+				3 => Pos.At (_xVal),
+				_ => view.X
+			};
+
+			view.Y = _yRadioGroup.SelectedItem switch {
+				0 => Pos.Percent (_yVal),
+				1 => Pos.AnchorEnd (_yVal),
+				2 => Pos.Center (),
+				3 => Pos.At (_yVal),
+				_ => view.Y
+			};
+
+			view.Width = _wRadioGroup.SelectedItem switch {
+				0 => Dim.Percent (_wVal),
+				1 => Dim.Fill (_wVal),
+				2 => Dim.Sized (_wVal),
+				_ => view.Width
+			};
+
+			view.Height = _hRadioGroup.SelectedItem switch {
+				0 => Dim.Percent (_hVal),
+				1 => Dim.Fill (_hVal),
+				2 => Dim.Sized (_hVal),
+				_ => view.Height
+			};
+		} catch (Exception e) {
+			MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
+		} finally {
+			view.LayoutStyle = layout;
 		}
-		
+		UpdateTitle (view);
+	}
 
-		View CreateClass (Type type)
-		{
-			// If we are to create a generic Type
-			if (type.IsGenericType) {
+	// TODO: This is missing some
+	List<string> _posNames = new() { "Factor", "AnchorEnd", "Center", "Absolute" };
+	List<string> _dimNames = new() { "Factor", "Fill", "Absolute" };
+
+	void UpdateSettings (View view)
+	{
+		string x = view.X.ToString ();
+		string y = view.Y.ToString ();
+		_xRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => x.Contains (s)).First ());
+		_yRadioGroup.SelectedItem = _posNames.IndexOf (_posNames.Where (s => y.Contains (s)).First ());
+		_xText.Text = $"{view.Frame.X}";
+		_yText.Text = $"{view.Frame.Y}";
+
+		string w = view.Width.ToString ();
+		string h = view.Height.ToString ();
+		_wRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => w.Contains (s)).First ());
+		_hRadioGroup.SelectedItem = _dimNames.IndexOf (_dimNames.Where (s => h.Contains (s)).First ());
+		_wText.Text = $"{view.Frame.Width}";
+		_hText.Text = $"{view.Frame.Height}";
+	}
 
-				// For each of the <T> arguments
-				List<Type> typeArguments = new List<Type> ();
+	void UpdateTitle (View view) => _hostPane.Title = $"{view.GetType ().Name} - {view.X}, {view.Y}, {view.Width}, {view.Height}";
 
-				// use <object>
-				foreach (var arg in type.GetGenericArguments ()) {
-					typeArguments.Add (typeof (object));
-				}
+	List<Type> GetAllViewClassesCollection ()
+	{
+		var types = new List<Type> ();
+		foreach (var type in typeof (View).Assembly.GetTypes ()
+						.Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsPublic && myType.IsSubclassOf (typeof (View)))) {
+			types.Add (type);
+		}
+		types.Add (typeof (View));
+		return types;
+	}
 
-				// And change what type we are instantiating from MyClass<T> to MyClass<object>
-				type = type.MakeGenericType (typeArguments.ToArray ());
-			}
-			// Instantiate view
-			var view = (View)Activator.CreateInstance (type);
+	// TODO: Add Command.Default handler (pop a message box?)
+	View CreateClass (Type type)
+	{
+		// If we are to create a generic Type
+		if (type.IsGenericType) {
 
-			//_curView.X = Pos.Center ();
-			//_curView.Y = Pos.Center ();
-			view.Width = Dim.Percent (75);
-			view.Height = Dim.Percent (75);
+			// For each of the <T> arguments
+			var typeArguments = new List<Type> ();
 
-			// Set the colorscheme to make it stand out if is null by default
-			if (view.ColorScheme == null) {
-				view.ColorScheme = Colors.Base;
+			// use <object>
+			foreach (var arg in type.GetGenericArguments ()) {
+				typeArguments.Add (typeof (object));
 			}
 
-			// If the view supports a Text property, set it so we have something to look at
-			if (view.GetType ().GetProperty ("Text") != null) {
-				try {
-					view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { "Test Text" });
-				} catch (TargetInvocationException e) {
-					MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok");
-					view = null;
-				}
-			}
+			// And change what type we are instantiating from MyClass<T> to MyClass<object>
+			type = type.MakeGenericType (typeArguments.ToArray ());
+		}
+		// Instantiate view
+		var view = (View)Activator.CreateInstance (type);
 
-			// If the view supports a Title property, set it so we have something to look at
-			if (view != null && view.GetType ().GetProperty ("Title") != null) {
-				if (view.GetType ().GetProperty ("Title").PropertyType == typeof (string)) {
-					view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" });
-				} else {
-					view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" });
-				}
+		// Set the colorscheme to make it stand out if is null by default
+		if (view.ColorScheme == null) {
+			view.ColorScheme = Colors.Base;
+		}
+
+		// If the view supports a Text property, set it so we have something to look at
+		if (view.GetType ().GetProperty ("Text") != null) {
+			try {
+				view.GetType ().GetProperty ("Text")?.GetSetMethod ()?.Invoke (view, new [] { "Test Text" });
+			} catch (TargetInvocationException e) {
+				MessageBox.ErrorQuery ("Exception", e.InnerException.Message, "Ok");
+				view = null;
 			}
+		}
 
-			// If the view supports a Source property, set it so we have something to look at
-			if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (Terminal.Gui.IListDataSource)) {
-				var source = new ListWrapper (new List<string> () { "Test Text #1", "Test Text #2", "Test Text #3" });
-				view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+		// If the view supports a Title property, set it so we have something to look at
+		if (view != null && view.GetType ().GetProperty ("Title") != null) {
+			if (view.GetType ().GetProperty ("Title").PropertyType == typeof (string)) {
+				view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" });
+			} else {
+				view?.GetType ().GetProperty ("Title")?.GetSetMethod ()?.Invoke (view, new [] { "Test Title" });
 			}
+		}
 
-			// Set Settings
-			_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
+		// If the view supports a Source property, set it so we have something to look at
+		if (view != null && view.GetType ().GetProperty ("Source") != null && view.GetType ().GetProperty ("Source").PropertyType == typeof (IListDataSource)) {
+			var source = new ListWrapper (new List<string> () { "Test Text #1", "Test Text #2", "Test Text #3" });
+			view?.GetType ().GetProperty ("Source")?.GetSetMethod ()?.Invoke (view, new [] { source });
+		}
 
-			// Add
-			_hostPane.Add (view);
-			_hostPane.Clear ();
-			_hostPane.SetNeedsDisplay ();
-			UpdateSettings (view);
-			UpdateTitle (view);
+		// Set Settings
+		_computedCheckBox.Checked = view.LayoutStyle == LayoutStyle.Computed;
 
-			view.LayoutComplete += LayoutCompleteHandler;
+		view.Initialized += View_Initialized;
 
-			return view;
-		}
+		// Add
+		_hostPane.Add (view);
+		_hostPane.SetNeedsDisplay ();
 
-		void LayoutCompleteHandler (object sender, LayoutEventArgs args)
-		{
-			UpdateTitle (_curView);
-		}
+		return view;
+	}
+
+	void View_Initialized (object sender, EventArgs e)
+	{
+		var view = sender as View;
 
-		private void Quit ()
-		{
-			Application.RequestStop ();
+		//view.X = Pos.Center ();
+		//view.Y = Pos.Center ();
+		if (view.Width == null || view.Frame.Width == 0) {
+			view.Width = Dim.Fill();
+		}
+		if (view.Height == null || view.Frame.Height == 0) {
+			view.Height = Dim.Fill();
 		}
+		UpdateSettings (view);
+		UpdateTitle (view);
 	}
+
+	void LayoutCompleteHandler (object sender, LayoutEventArgs args)
+	{
+		UpdateSettings (_curView);
+		UpdateTitle (_curView);
+	}
+
+	void Quit () => Application.RequestStop ();
 }

+ 32 - 31
UICatalog/Scenarios/CharacterMap.cs

@@ -114,10 +114,9 @@ public class CharacterMap : Scenario {
 		Application.Top.Add (_categoryList);
 
 		_charMap.SelectedCodePoint = 0;
-		//jumpList.Refresh ();
 		_charMap.SetFocus ();
-
-		_charMap.Width = Dim.Fill () - _categoryList.Width;
+		// TODO: Replace this with Dim.Auto when that's ready
+		_categoryList.Initialized += _categoryList_Initialized;
 
 		var menu = new MenuBar (new MenuBarItem [] {
 			new ("_File", new MenuItem [] {
@@ -128,12 +127,11 @@ public class CharacterMap : Scenario {
 			})
 		});
 		Application.Top.Add (menu);
-
-		//_charMap.Hover += (s, a) => {
-		//	_errorLabel.Text = $"U+{a.Item:x5} {(Rune)a.Item}";
-		//};
+	
 	}
 
+	private void _categoryList_Initialized (object sender, EventArgs e) => _charMap.Width = Dim.Fill () - _categoryList.Width;
+
 	MenuItem CreateMenuShowWidth ()
 	{
 		var item = new MenuItem {
@@ -232,6 +230,7 @@ public class CharacterMap : Scenario {
 }
 
 class CharMap : ScrollView {
+	const CursorVisibility _cursor = CursorVisibility.Default;
 	/// <summary>
 	/// Specifies the starting offset for the character map. The default is 0x2500 
 	/// which is the Box Drawing characters.
@@ -255,24 +254,26 @@ class CharMap : ScrollView {
 		get => _selected;
 		set {
 			_selected = value;
-			int row = SelectedCodePoint / 16 * _rowHeight;
-			int col = SelectedCodePoint % 16 * COLUMN_WIDTH;
-
-			int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1);
-			if (row + ContentOffset.Y < 0) {
-				// Moving up.
-				ContentOffset = new Point (ContentOffset.X, row);
-			} else if (row + ContentOffset.Y >= height) {
-				// Moving down.
-				ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight));
-			}
-			int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
-			if (col + ContentOffset.X < 0) {
-				// Moving left.
-				ContentOffset = new Point (col, ContentOffset.Y);
-			} else if (col + ContentOffset.X >= width) {
-				// Moving right.
-				ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y);
+			if (IsInitialized) {
+				int row = SelectedCodePoint / 16 * _rowHeight;
+				int col = SelectedCodePoint % 16 * COLUMN_WIDTH;
+
+				int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1);
+				if (row + ContentOffset.Y < 0) {
+					// Moving up.
+					ContentOffset = new Point (ContentOffset.X, row);
+				} else if (row + ContentOffset.Y >= height) {
+					// Moving down.
+					ContentOffset = new Point (ContentOffset.X, Math.Min (row, row - height + _rowHeight));
+				}
+				int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth);
+				if (col + ContentOffset.X < 0) {
+					// Moving left.
+					ContentOffset = new Point (col, ContentOffset.Y);
+				} else if (col + ContentOffset.X >= width) {
+					// Moving right.
+					ContentOffset = new Point (Math.Min (col, col - width + COLUMN_WIDTH), ContentOffset.Y);
+				}
 			}
 			SetNeedsDisplay ();
 			SelectedCodePointChanged?.Invoke (this, new ListViewItemEventArgs (SelectedCodePoint, null));
@@ -296,11 +297,11 @@ class CharMap : ScrollView {
 	public override void PositionCursor ()
 	{
 		if (HasFocus &&
-		Cursor.X >= RowLabelWidth &&
-		Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
-		Cursor.Y > 0 &&
-		Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
-			Driver.SetCursorVisibility (CursorVisibility.Default);
+			Cursor.X >= RowLabelWidth &&
+			Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) &&
+			Cursor.Y > 0 &&
+			Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) {
+			Driver.SetCursorVisibility (_cursor);
 			Move (Cursor.X, Cursor.Y);
 		} else {
 			Driver.SetCursorVisibility (CursorVisibility.Invisible);
@@ -807,7 +808,7 @@ class CharMap : ScrollView {
 	public override bool OnEnter (View view)
 	{
 		if (IsInitialized) {
-			Application.Driver.SetCursorVisibility (CursorVisibility.Default);
+			Application.Driver.SetCursorVisibility (_cursor);
 		}
 		return base.OnEnter (view);
 	}

+ 53 - 0
UICatalog/Scenarios/ChineseUI.cs

@@ -0,0 +1,53 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios; 
+
+[ScenarioMetadata ("ChineseUI", "Chinese UI")]
+[ScenarioCategory ("Unicode")]
+public class ChineseUI : Scenario {
+	public override void Init ()
+	{
+		Application.Init ();
+		var top = Application.Top;
+
+		var win = new Window () {
+			Title = "Test",
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		top.Add (win);
+
+		var buttonPanel = new FrameView () {
+			Title = "Command",
+			X = 0,
+			Y = 1,
+			Width = Dim.Fill (),
+			Height = 5
+		};
+		win.Add (buttonPanel);
+
+		var btn = new Button (1, 1, "你", true); // v1: A
+		btn.Clicked += (s, e) => {
+			int result = MessageBox.Query ("Confirm",
+				"Are you sure you want to quit ui?", 0,
+				"Yes", "No");
+			if (result == 0) {
+				RequestStop ();
+			}
+		};
+
+		buttonPanel.Add (
+			btn,
+			new Button (12, 1, "好"), // v1: B
+			new Button (22, 1, "呀") // v1: C
+		);
+
+		Application.Run ();
+	}
+
+	public override void Run ()
+	{
+	}
+}

+ 0 - 1
UICatalog/Scenarios/ClassExplorer.cs

@@ -59,7 +59,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {

+ 1 - 0
UICatalog/Scenarios/ColorPicker.cs

@@ -91,6 +91,7 @@ namespace UICatalog.Scenarios {
 			// 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>

+ 11 - 3
UICatalog/Scenarios/ComputedLayout.cs

@@ -79,7 +79,9 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Fill (margin),
 				Height = 7
 			};
-			subWin.Title = $"{subWin.GetType ().Name} {{X={subWin.X},Y={subWin.Y},Width={subWin.Width},Height={subWin.Height}}}";
+			subWin.Initialized += (s, a) => {
+				subWin.Title = $"{subWin.GetType ().Name} {{X={subWin.X},Y={subWin.Y},Width={subWin.Width},Height={subWin.Height}}}";
+			};
 			Application.Top.Add (subWin);
 
 			int i = 1;
@@ -98,7 +100,10 @@ namespace UICatalog.Scenarios {
 				Width = 30,
 				Height = 7
 			};
-			frameView.Title = $"{frameView.GetType ().Name} {{X={frameView.X},Y={frameView.Y},Width={frameView.Width},Height={frameView.Height}}}";
+			frameView.Initialized += (sender, args) => {
+				var fv = sender as FrameView;
+				fv.Title = $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}";
+			};
 			i = 1;
 			labelList = new List<Label> ();
 			labelList.Add (new Label ($"The lines below show different TextAlignments"));
@@ -115,7 +120,10 @@ namespace UICatalog.Scenarios {
 				Width = Dim.Fill (),
 				Height = 7,
 			};
-			frameView.Title = $"{frameView.GetType ().Name} {{X={frameView.X},Y={frameView.Y},Width={frameView.Width},Height={frameView.Height}}}";
+			frameView.Initialized += (sender, args) => {
+				var fv = sender as FrameView;
+				fv.Title = $"{frameView.GetType ().Name} {{X={fv.X},Y={fv.Y},Width={fv.Width},Height={fv.Height}}}";
+			};
 			Application.Top.Add (frameView);
 
 			// Demonstrate Dim & Pos using percentages - a TextField that is 30% height and 80% wide

+ 420 - 419
UICatalog/Scenarios/CsvEditor.cs

@@ -1,553 +1,554 @@
-using System;
+using CsvHelper;
+using System;
+using System.Collections.Generic;
 using System.Data;
-using System.Linq;
 using System.Globalization;
 using System.IO;
+using System.Linq;
 using System.Text.RegularExpressions;
-using System.Text;
 using Terminal.Gui;
-using CsvHelper;
-using System.Collections.Generic;
 
-namespace UICatalog.Scenarios {
-
-	[ScenarioMetadata (Name: "Csv Editor", Description: "Open and edit simple CSV files using the TableView class.")]
-	[ScenarioCategory ("TableView"), ScenarioCategory ("TextView"), ScenarioCategory ("Controls"), ScenarioCategory ("Dialogs"), 
-	ScenarioCategory ("Text and Formatting"), ScenarioCategory ("Dialogs"), ScenarioCategory ("Top Level Windows"), ScenarioCategory ("Files and IO")]
-	public class CsvEditor : Scenario {
-		TableView tableView;
-		private string _currentFile;
-		DataTable currentTable;
-		private MenuItem _miLeft;
-		private MenuItem _miRight;
-		private MenuItem _miCentered;
-		private TextField _selectedCellLabel;
-
-		public override void Setup ()
-		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
-
-			this.tableView = new TableView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1),
-			};
-
-			var fileMenu = new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Open CSV", "", () => Open()),
-					new MenuItem ("_Save", "", () => Save()),
-					new MenuItem ("_Quit", "Quits The App", () => Quit()),
-				});
-			//fileMenu.Help = "Help";
-			var menu = new MenuBar (new MenuBarItem [] {
-				fileMenu,
-				new MenuBarItem ("_Edit", new MenuItem [] {
-					new MenuItem ("_New Column", "", () => AddColumn()),
-					new MenuItem ("_New Row", "", () => AddRow()),
-					new MenuItem ("_Rename Column", "", () => RenameColumn()),
-					new MenuItem ("_Delete Column", "", () => DeleteColum()),
-					new MenuItem ("_Move Column", "", () => MoveColumn()),
-					new MenuItem ("_Move Row", "", () => MoveRow()),
-					new MenuItem ("_Sort Asc", "", () => Sort(true)),
-					new MenuItem ("_Sort Desc", "", () => Sort(false)),
-				}),
-				new MenuBarItem ("_View", new MenuItem [] {
-					_miLeft = new MenuItem ("_Align Left", "", () => Align(TextAlignment.Left)),
-					_miRight = new MenuItem ("_Align Right", "", () => Align(TextAlignment.Right)),
-					_miCentered = new MenuItem ("_Align Centered", "", () => Align(TextAlignment.Centered)),
-					
-					// Format requires hard typed data table, when we read a CSV everything is untyped (string) so this only works for new columns in this demo
-					_miCentered = new MenuItem ("_Set Format Pattern", "", () => SetFormat()),
-				})
-			});
-			Application.Top.Add (menu);
-
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(KeyCode.CtrlMask | KeyCode.O, "~^O~ Open", () => Open()),
-				new StatusItem(KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save()),
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-			});
-			Application.Top.Add (statusBar);
-
-			Win.Add (tableView);
-
-			_selectedCellLabel = new TextField () {
-				X = 0,
-				Y = Pos.Bottom (tableView),
-				Text = "0,0",
-				Width = Dim.Fill (),
-				TextAlignment = TextAlignment.Right
-			};
-			_selectedCellLabel.TextChanged += SelectedCellLabel_TextChanged;
-
-			Win.Add (_selectedCellLabel);
-
-			tableView.SelectedCellChanged += OnSelectedCellChanged;
-			tableView.CellActivated += EditCurrentCell;
-			tableView.KeyDown += TableViewKeyPress;
-
-			SetupScrollBar ();
-		}
-
-		private void SelectedCellLabel_TextChanged (object sender, TextChangedEventArgs e)
-		{
-			// if user is in the text control and editing the selected cell
-			if (!_selectedCellLabel.HasFocus)
-				return;
-
-			// change selected cell to the one the user has typed into the box
-			var match = Regex.Match (_selectedCellLabel.Text, "^(\\d+),(\\d+)$");
-			if (match.Success) {
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("Csv Editor", "Open and edit simple CSV files using the TableView class.")]
+[ScenarioCategory ("TableView")] [ScenarioCategory ("TextView")] [ScenarioCategory ("Controls")] [ScenarioCategory ("Dialogs")] [ScenarioCategory ("Text and Formatting")] [ScenarioCategory ("Dialogs")] [ScenarioCategory ("Top Level Windows")] [ScenarioCategory ("Files and IO")]
+public class CsvEditor : Scenario {
+	TableView tableView;
+	string _currentFile;
+	DataTable _currentTable;
+	MenuItem _miLeft;
+	MenuItem _miRight;
+	MenuItem _miCentered;
+	TextField _selectedCellLabel;
+
+	public override void Setup ()
+	{
+		Win.Title = GetName ();
+		Win.Y = 1; // menu
+		Win.Height = Dim.Fill (1); // status bar
+
+		tableView = new TableView () {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1)
+		};
+
+		var fileMenu = new MenuBarItem ("_File", new MenuItem [] {
+			new ("_Open CSV", "", () => Open ()),
+			new ("_Save", "", () => Save ()),
+			new ("_Quit", "Quits The App", () => Quit ())
+		});
+		//fileMenu.Help = "Help";
+		var menu = new MenuBar (new MenuBarItem [] {
+			fileMenu,
+			new ("_Edit", new MenuItem [] {
+				new ("_New Column", "", () => AddColumn ()),
+				new ("_New Row", "", () => AddRow ()),
+				new ("_Rename Column", "", () => RenameColumn ()),
+				new ("_Delete Column", "", () => DeleteColum ()),
+				new ("_Move Column", "", () => MoveColumn ()),
+				new ("_Move Row", "", () => MoveRow ()),
+				new ("_Sort Asc", "", () => Sort (true)),
+				new ("_Sort Desc", "", () => Sort (false))
+			}),
+			new ("_View", new MenuItem [] {
+				_miLeft = new MenuItem ("_Align Left", "", () => Align (TextAlignment.Left)),
+				_miRight = new MenuItem ("_Align Right", "", () => Align (TextAlignment.Right)),
+				_miCentered = new MenuItem ("_Align Centered", "", () => Align (TextAlignment.Centered)),
+
+				// Format requires hard typed data table, when we read a CSV everything is untyped (string) so this only works for new columns in this demo
+				_miCentered = new MenuItem ("_Set Format Pattern", "", () => SetFormat ())
+			})
+		});
+		Application.Top.Add (menu);
+
+		var statusBar = new StatusBar (new StatusItem [] {
+			new (KeyCode.CtrlMask | KeyCode.O, "~^O~ Open", () => Open ()),
+			new (KeyCode.CtrlMask | KeyCode.S, "~^S~ Save", () => Save ()),
+			new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ())
+		});
+		Application.Top.Add (statusBar);
+
+		Win.Add (tableView);
+
+		_selectedCellLabel = new TextField () {
+			X = 0,
+			Y = Pos.Bottom (tableView),
+			Text = "0,0",
+			Width = Dim.Fill (),
+			TextAlignment = TextAlignment.Right
+		};
+		_selectedCellLabel.TextChanged += SelectedCellLabel_TextChanged;
+
+		Win.Add (_selectedCellLabel);
+
+		tableView.SelectedCellChanged += OnSelectedCellChanged;
+		tableView.CellActivated += EditCurrentCell;
+		tableView.KeyDown += TableViewKeyPress;
+
+		SetupScrollBar ();
+	}
 
-				tableView.SelectedColumn = int.Parse (match.Groups [1].Value);
-				tableView.SelectedRow = int.Parse (match.Groups [2].Value);
-			}
+	void SelectedCellLabel_TextChanged (object sender, TextChangedEventArgs e)
+	{
+		// if user is in the text control and editing the selected cell
+		if (!_selectedCellLabel.HasFocus) {
+			return;
 		}
 
-		private void OnSelectedCellChanged (object sender, SelectedCellChangedEventArgs e)
-		{
-			// only update the text box if the user is not manually editing it
-			if (!_selectedCellLabel.HasFocus)
-				_selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}";
+		// change selected cell to the one the user has typed into the box
+		var match = Regex.Match (_selectedCellLabel.Text, "^(\\d+),(\\d+)$");
+		if (match.Success) {
 
-			if (tableView.Table == null || tableView.SelectedColumn == -1)
-				return;
+			tableView.SelectedColumn = int.Parse (match.Groups [1].Value);
+			tableView.SelectedRow = int.Parse (match.Groups [2].Value);
+		}
+	}
 
-			var style = tableView.Style.GetColumnStyleIfAny (tableView.SelectedColumn);
+	void OnSelectedCellChanged (object sender, SelectedCellChangedEventArgs e)
+	{
+		// only update the text box if the user is not manually editing it
+		if (!_selectedCellLabel.HasFocus) {
+			_selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}";
+		}
 
-			_miLeft.Checked = style?.Alignment == TextAlignment.Left;
-			_miRight.Checked = style?.Alignment == TextAlignment.Right;
-			_miCentered.Checked = style?.Alignment == TextAlignment.Centered;
+		if (tableView.Table == null || tableView.SelectedColumn == -1) {
+			return;
 		}
 
-		private void RenameColumn ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		var style = tableView.Style.GetColumnStyleIfAny (tableView.SelectedColumn);
 
-			var currentCol = currentTable.Columns [tableView.SelectedColumn];
+		_miLeft.Checked = style?.Alignment == TextAlignment.Left;
+		_miRight.Checked = style?.Alignment == TextAlignment.Right;
+		_miCentered.Checked = style?.Alignment == TextAlignment.Centered;
+	}
 
-			if (GetText ("Rename Column", "Name:", currentCol.ColumnName, out string newName)) {
-				currentCol.ColumnName = newName;
-				tableView.Update ();
-			}
+	void RenameColumn ()
+	{
+		if (NoTableLoaded ()) {
+			return;
 		}
 
-		private void DeleteColum ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		var currentCol = _currentTable.Columns [tableView.SelectedColumn];
 
-			if (tableView.SelectedColumn == -1) {
+		if (GetText ("Rename Column", "Name:", currentCol.ColumnName, out string newName)) {
+			currentCol.ColumnName = newName;
+			tableView.Update ();
+		}
+	}
 
-				MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
-				return;
-			}
+	void DeleteColum ()
+	{
+		if (NoTableLoaded ()) {
+			return;
+		}
 
-			try {
-				currentTable.Columns.RemoveAt (tableView.SelectedColumn);
-				tableView.Update ();
+		if (tableView.SelectedColumn == -1) {
 
-			} catch (Exception ex) {
-				MessageBox.ErrorQuery ("Could not remove column", ex.Message, "Ok");
-			}
+			MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
+			return;
 		}
 
-		private void MoveColumn ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		try {
+			_currentTable.Columns.RemoveAt (tableView.SelectedColumn);
+			tableView.Update ();
 
-			if (tableView.SelectedColumn == -1) {
+		} catch (Exception ex) {
+			MessageBox.ErrorQuery ("Could not remove column", ex.Message, "Ok");
+		}
+	}
 
-				MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
-				return;
-			}
+	void MoveColumn ()
+	{
+		if (NoTableLoaded ()) {
+			return;
+		}
 
-			try {
+		if (tableView.SelectedColumn == -1) {
 
-				var currentCol = currentTable.Columns [tableView.SelectedColumn];
+			MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
+			return;
+		}
 
-				if (GetText ("Move Column", "New Index:", currentCol.Ordinal.ToString (), out string newOrdinal)) {
+		try {
 
-					var newIdx = Math.Min (Math.Max (0, int.Parse (newOrdinal)), tableView.Table.Columns - 1);
+			var currentCol = _currentTable.Columns [tableView.SelectedColumn];
 
-					currentCol.SetOrdinal (newIdx);
+			if (GetText ("Move Column", "New Index:", currentCol.Ordinal.ToString (), out string newOrdinal)) {
 
-					tableView.SetSelection (newIdx, tableView.SelectedRow, false);
-					tableView.EnsureSelectedCellIsVisible ();
-					tableView.SetNeedsDisplay ();
-				}
+				int newIdx = Math.Min (Math.Max (0, int.Parse (newOrdinal)), tableView.Table.Columns - 1);
 
-			} catch (Exception ex) {
-				MessageBox.ErrorQuery ("Error moving column", ex.Message, "Ok");
-			}
-		}
-		private void Sort (bool asc)
-		{
+				currentCol.SetOrdinal (newIdx);
 
-			if (NoTableLoaded ()) {
-				return;
+				tableView.SetSelection (newIdx, tableView.SelectedRow, false);
+				tableView.EnsureSelectedCellIsVisible ();
+				tableView.SetNeedsDisplay ();
 			}
 
-			if (tableView.SelectedColumn == -1) {
-
-				MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
-				return;
-			}
+		} catch (Exception ex) {
+			MessageBox.ErrorQuery ("Error moving column", ex.Message, "Ok");
+		}
+	}
 
-			var colName = tableView.Table.ColumnNames [tableView.SelectedColumn];
+	void Sort (bool asc)
+	{
 
-			currentTable.DefaultView.Sort = colName + (asc ? " asc" : " desc");
-			SetTable(currentTable.DefaultView.ToTable ());
+		if (NoTableLoaded ()) {
+			return;
 		}
 
-		private void SetTable (DataTable dataTable)
-		{			
-			tableView.Table = new DataTableSource(currentTable = dataTable);
+		if (tableView.SelectedColumn == -1) {
+
+			MessageBox.ErrorQuery ("No Column", "No column selected", "Ok");
+			return;
 		}
 
-		private void MoveRow ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		string colName = tableView.Table.ColumnNames [tableView.SelectedColumn];
 
-			if (tableView.SelectedRow == -1) {
+		_currentTable.DefaultView.Sort = colName + (asc ? " asc" : " desc");
+		SetTable (_currentTable.DefaultView.ToTable ());
+	}
 
-				MessageBox.ErrorQuery ("No Rows", "No row selected", "Ok");
-				return;
-			}
+	void SetTable (DataTable dataTable) => tableView.Table = new DataTableSource (_currentTable = dataTable);
 
-			try {
-
-				int oldIdx = tableView.SelectedRow;
+	void MoveRow ()
+	{
+		if (NoTableLoaded ()) {
+			return;
+		}
 
-				var currentRow = currentTable.Rows [oldIdx];
+		if (tableView.SelectedRow == -1) {
 
-				if (GetText ("Move Row", "New Row:", oldIdx.ToString (), out string newOrdinal)) {
+			MessageBox.ErrorQuery ("No Rows", "No row selected", "Ok");
+			return;
+		}
 
-					var newIdx = Math.Min (Math.Max (0, int.Parse (newOrdinal)), tableView.Table.Rows - 1);
+		try {
 
-					if (newIdx == oldIdx)
-						return;
+			int oldIdx = tableView.SelectedRow;
 
-					var arrayItems = currentRow.ItemArray;
-					currentTable.Rows.Remove (currentRow);
+			var currentRow = _currentTable.Rows [oldIdx];
 
-					// Removing and Inserting the same DataRow seems to result in it loosing its values so we have to create a new instance
-					var newRow = currentTable.NewRow ();
-					newRow.ItemArray = arrayItems;
+			if (GetText ("Move Row", "New Row:", oldIdx.ToString (), out string newOrdinal)) {
 
-					currentTable.Rows.InsertAt (newRow, newIdx);
+				int newIdx = Math.Min (Math.Max (0, int.Parse (newOrdinal)), tableView.Table.Rows - 1);
 
-					tableView.SetSelection (tableView.SelectedColumn, newIdx, false);
-					tableView.EnsureSelectedCellIsVisible ();
-					tableView.SetNeedsDisplay ();
+				if (newIdx == oldIdx) {
+					return;
 				}
 
-			} catch (Exception ex) {
-				MessageBox.ErrorQuery ("Error moving column", ex.Message, "Ok");
+				object [] arrayItems = currentRow.ItemArray;
+				_currentTable.Rows.Remove (currentRow);
+
+				// Removing and Inserting the same DataRow seems to result in it loosing its values so we have to create a new instance
+				var newRow = _currentTable.NewRow ();
+				newRow.ItemArray = arrayItems;
+
+				_currentTable.Rows.InsertAt (newRow, newIdx);
+
+				tableView.SetSelection (tableView.SelectedColumn, newIdx, false);
+				tableView.EnsureSelectedCellIsVisible ();
+				tableView.SetNeedsDisplay ();
 			}
+
+		} catch (Exception ex) {
+			MessageBox.ErrorQuery ("Error moving column", ex.Message, "Ok");
 		}
+	}
 
-		private void Align (TextAlignment newAlignment)
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+	void Align (TextAlignment newAlignment)
+	{
+		if (NoTableLoaded ()) {
+			return;
+		}
 
-			var style = tableView.Style.GetOrCreateColumnStyle (tableView.SelectedColumn);
-			style.Alignment = newAlignment;
+		var style = tableView.Style.GetOrCreateColumnStyle (tableView.SelectedColumn);
+		style.Alignment = newAlignment;
 
-			_miLeft.Checked = style.Alignment == TextAlignment.Left;
-			_miRight.Checked = style.Alignment == TextAlignment.Right;
-			_miCentered.Checked = style.Alignment == TextAlignment.Centered;
+		_miLeft.Checked = style.Alignment == TextAlignment.Left;
+		_miRight.Checked = style.Alignment == TextAlignment.Right;
+		_miCentered.Checked = style.Alignment == TextAlignment.Centered;
 
-			tableView.Update ();
+		tableView.Update ();
+	}
+
+	void SetFormat ()
+	{
+		if (NoTableLoaded ()) {
+			return;
 		}
 
-		private void SetFormat ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		var col = _currentTable.Columns [tableView.SelectedColumn];
 
-			var col = currentTable.Columns [tableView.SelectedColumn];
+		if (col.DataType == typeof (string)) {
+			MessageBox.ErrorQuery ("Cannot Format Column", "String columns cannot be Formatted, try adding a new column to the table with a date/numerical Type", "Ok");
+			return;
+		}
 
-			if (col.DataType == typeof (string)) {
-				MessageBox.ErrorQuery ("Cannot Format Column", "String columns cannot be Formatted, try adding a new column to the table with a date/numerical Type", "Ok");
-				return;
-			}
+		var style = tableView.Style.GetOrCreateColumnStyle (col.Ordinal);
 
-			var style = tableView.Style.GetOrCreateColumnStyle (col.Ordinal);
+		if (GetText ("Format", "Pattern:", style.Format ?? "", out string newPattern)) {
+			style.Format = newPattern;
+			tableView.Update ();
+		}
+	}
 
-			if (GetText ("Format", "Pattern:", style.Format ?? "", out string newPattern)) {
-				style.Format = newPattern;
-				tableView.Update ();
-			}
+	bool NoTableLoaded ()
+	{
+		if (tableView.Table == null) {
+			MessageBox.ErrorQuery ("No Table Loaded", "No table has currently be opened", "Ok");
+			return true;
 		}
 
-		private bool NoTableLoaded ()
-		{
-			if (tableView.Table == null) {
-				MessageBox.ErrorQuery ("No Table Loaded", "No table has currently be opened", "Ok");
-				return true;
-			}
+		return false;
+	}
 
-			return false;
+	void AddRow ()
+	{
+		if (NoTableLoaded ()) {
+			return;
 		}
 
-		private void AddRow ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		var newRow = _currentTable.NewRow ();
 
-			var newRow = currentTable.NewRow ();
+		int newRowIdx = Math.Min (Math.Max (0, tableView.SelectedRow + 1), tableView.Table.Rows);
 
-			var newRowIdx = Math.Min (Math.Max (0, tableView.SelectedRow + 1), tableView.Table.Rows);
+		_currentTable.Rows.InsertAt (newRow, newRowIdx);
+		tableView.Update ();
+	}
 
-			currentTable.Rows.InsertAt (newRow, newRowIdx);
-			tableView.Update ();
+	void AddColumn ()
+	{
+		if (NoTableLoaded ()) {
+			return;
 		}
 
-		private void AddColumn ()
-		{
-			if (NoTableLoaded ()) {
-				return;
-			}
+		if (GetText ("Enter column name", "Name:", "", out string colName)) {
 
-			if (GetText ("Enter column name", "Name:", "", out string colName)) {
+			var col = new DataColumn (colName);
 
-				var col = new DataColumn (colName);
+			int newColIdx = Math.Min (Math.Max (0, tableView.SelectedColumn + 1), tableView.Table.Columns);
 
-				var newColIdx = Math.Min (Math.Max (0, tableView.SelectedColumn + 1), tableView.Table.Columns);
+			int result = MessageBox.Query ("Column Type", "Pick a data type for the column", new string [] { "Date", "Integer", "Double", "Text", "Cancel" });
+
+			if (result <= -1 || result >= 4) {
+				return;
+			}
+			switch (result) {
+			case 0:
+				col.DataType = typeof (DateTime);
+				break;
+			case 1:
+				col.DataType = typeof (int);
+				break;
+			case 2:
+				col.DataType = typeof (double);
+				break;
+			case 3:
+				col.DataType = typeof (string);
+				break;
+			}
 
-				int result = MessageBox.Query ("Column Type", "Pick a data type for the column", new string [] { "Date", "Integer", "Double", "Text", "Cancel" });
+			_currentTable.Columns.Add (col);
+			col.SetOrdinal (newColIdx);
+			tableView.Update ();
+		}
 
-				if (result <= -1 || result >= 4)
-					return;
-				switch (result) {
-				case 0:
-					col.DataType = typeof (DateTime);
-					break;
-				case 1:
-					col.DataType = typeof (int);
-					break;
-				case 2:
-					col.DataType = typeof (double);
-					break;
-				case 3:
-					col.DataType = typeof (string);
-					break;
-				}
 
-				currentTable.Columns.Add (col);
-				col.SetOrdinal (newColIdx);
-				tableView.Update ();
-			}
+	}
 
+	void Save ()
+	{
+		if (tableView.Table == null || string.IsNullOrWhiteSpace (_currentFile)) {
+			MessageBox.ErrorQuery ("No file loaded", "No file is currently loaded", "Ok");
+			return;
+		}
+		using var writer = new CsvWriter (
+			new StreamWriter (File.OpenWrite (_currentFile)),
+			CultureInfo.InvariantCulture);
 
+		foreach (string col in _currentTable.Columns.Cast<DataColumn> ().Select (c => c.ColumnName)) {
+			writer.WriteField (col);
 		}
 
-		private void Save ()
-		{
-			if (tableView.Table == null || string.IsNullOrWhiteSpace (_currentFile)) {
-				MessageBox.ErrorQuery ("No file loaded", "No file is currently loaded", "Ok");
-				return;
-			}
-			using var writer = new CsvWriter (
-				new StreamWriter (File.OpenWrite (_currentFile)),
-				CultureInfo.InvariantCulture);
+		writer.NextRecord ();
 
-			foreach (var col in currentTable.Columns.Cast<DataColumn> ().Select (c => c.ColumnName)) {
-				writer.WriteField (col);
+		foreach (DataRow row in _currentTable.Rows) {
+			foreach (object item in row.ItemArray) {
+				writer.WriteField (item);
 			}
-
 			writer.NextRecord ();
+		}
 
-			foreach (DataRow row in currentTable.Rows) {
-				foreach (var item in row.ItemArray) {
-					writer.WriteField (item);
-				}
-				writer.NextRecord ();
-			}
+	}
 
-		}
-		
-		private void Open ()
-		{
-			var ofd = new FileDialog () {
-				AllowedTypes = new List<IAllowedType> { new AllowedType("Comma Separated Values", ".csv") }
-			};
-			ofd.Style.OkButtonText = "Open";
+	void Open ()
+	{
+		var ofd = new FileDialog () {
+			AllowedTypes = new List<IAllowedType> { new AllowedType ("Comma Separated Values", ".csv") }
+		};
+		ofd.Style.OkButtonText = "Open";
 
-			Application.Run (ofd);
+		Application.Run (ofd);
 
-			if (!ofd.Canceled && !string.IsNullOrWhiteSpace (ofd.Path)) {
-				Open (ofd.Path);
-			}
+		if (!ofd.Canceled && !string.IsNullOrWhiteSpace (ofd.Path)) {
+			Open (ofd.Path);
 		}
+	}
 
-		private void Open (string filename)
-		{
+	void Open (string filename)
+	{
 
-			int lineNumber = 0;
-			_currentFile = null;
+		int lineNumber = 0;
+		_currentFile = null;
 
-			try {
-				using var reader = new CsvReader (File.OpenText (filename), CultureInfo.InvariantCulture);
+		try {
+			using var reader = new CsvReader (File.OpenText (filename), CultureInfo.InvariantCulture);
 
-				var dt = new DataTable ();
+			var dt = new DataTable ();
 
-				reader.Read ();
+			reader.Read ();
 
-				if (reader.ReadHeader ()) {
-					foreach (var h in reader.HeaderRecord) {
-						dt.Columns.Add (h);
-					}
+			if (reader.ReadHeader ()) {
+				foreach (string h in reader.HeaderRecord) {
+					dt.Columns.Add (h);
 				}
+			}
 
-				while (reader.Read ()) {
-					lineNumber++;
+			while (reader.Read ()) {
+				lineNumber++;
 
-					var newRow = dt.Rows.Add ();
-					for (int i = 0; i < dt.Columns.Count; i++) {
-						newRow [i] = reader [i];
-					}
+				var newRow = dt.Rows.Add ();
+				for (int i = 0; i < dt.Columns.Count; i++) {
+					newRow [i] = reader [i];
 				}
+			}
 
-				SetTable(dt);
+			SetTable (dt);
 
-				// Only set the current filename if we successfully loaded the entire file
-				_currentFile = filename;
-				Win.Title = $"{this.GetName ()} - {Path.GetFileName (_currentFile)}";
+			// Only set the current filename if we successfully loaded the entire file
+			_currentFile = filename;
+			Win.Title = $"{GetName ()} - {Path.GetFileName (_currentFile)}";
 
-			} catch (Exception ex) {
-				MessageBox.ErrorQuery ("Open Failed", $"Error on line {lineNumber}{Environment.NewLine}{ex.Message}", "Ok");
-			}
+		} catch (Exception ex) {
+			MessageBox.ErrorQuery ("Open Failed", $"Error on line {lineNumber}{Environment.NewLine}{ex.Message}", "Ok");
 		}
-		private void SetupScrollBar ()
-		{
-			var scrollBar = new ScrollBarView (tableView, true);
+	}
 
-			scrollBar.ChangedPosition += (s, e) => {
-				tableView.RowOffset = scrollBar.Position;
-				if (tableView.RowOffset != scrollBar.Position) {
-					scrollBar.Position = tableView.RowOffset;
-				}
-				tableView.SetNeedsDisplay ();
-			};
-			/*
-			scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
-				tableView.LeftItem = scrollBar.OtherScrollBarView.Position;
-				if (tableView.LeftItem != scrollBar.OtherScrollBarView.Position) {
-					scrollBar.OtherScrollBarView.Position = tableView.LeftItem;
-				}
-				tableView.SetNeedsDisplay ();
-			};*/
+	void SetupScrollBar ()
+	{
+		var scrollBar = new ScrollBarView (tableView, true);
 
-			tableView.DrawContent += (s, e) => {
-				scrollBar.Size = tableView.Table?.Rows ?? 0;
+		scrollBar.ChangedPosition += (s, e) => {
+			tableView.RowOffset = scrollBar.Position;
+			if (tableView.RowOffset != scrollBar.Position) {
 				scrollBar.Position = tableView.RowOffset;
-				//scrollBar.OtherScrollBarView.Size = tableView.Maxlength - 1;
-				//scrollBar.OtherScrollBarView.Position = tableView.LeftItem;
-				scrollBar.Refresh ();
-			};
+			}
+			tableView.SetNeedsDisplay ();
+		};
+		/*
+		scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
+			tableView.LeftItem = scrollBar.OtherScrollBarView.Position;
+			if (tableView.LeftItem != scrollBar.OtherScrollBarView.Position) {
+				scrollBar.OtherScrollBarView.Position = tableView.LeftItem;
+			}
+			tableView.SetNeedsDisplay ();
+		};*/
 
-		}
+		tableView.DrawContent += (s, e) => {
+			scrollBar.Size = tableView.Table?.Rows ?? 0;
+			scrollBar.Position = tableView.RowOffset;
+			//scrollBar.OtherScrollBarView.Size = tableView.Maxlength - 1;
+			//scrollBar.OtherScrollBarView.Position = tableView.LeftItem;
+			scrollBar.Refresh ();
+		};
+
+	}
 
 		private void TableViewKeyPress (object sender, Key e)
 		{
-			if (e.KeyCode == KeyCode.DeleteChar) {
-
-				if (tableView.FullRowSelect) {
-					// Delete button deletes all rows when in full row mode
-					foreach (int toRemove in tableView.GetAllSelectedCells ().Select (p => p.Y).Distinct ().OrderByDescending (i => i))
-						currentTable.Rows.RemoveAt (toRemove);
-				} else {
-
-					// otherwise set all selected cells to null
-					foreach (var pt in tableView.GetAllSelectedCells ()) {
-						currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
-					}
+			if (e.KeyCode == KeyCode.Delete) {
+
+			if (tableView.FullRowSelect) {
+				// Delete button deletes all rows when in full row mode
+				foreach (int toRemove in tableView.GetAllSelectedCells ().Select (p => p.Y).Distinct ().OrderByDescending (i => i)) {
+					_currentTable.Rows.RemoveAt (toRemove);
 				}
+			} else {
 
-				tableView.Update ();
-				e.Handled = true;
+				// otherwise set all selected cells to null
+				foreach (var pt in tableView.GetAllSelectedCells ()) {
+					_currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
+				}
 			}
-		}
 
-		private void ClearColumnStyles ()
-		{
-			tableView.Style.ColumnStyles.Clear ();
 			tableView.Update ();
+			e.Handled = true;
 		}
+	}
 
-		private void CloseExample ()
-		{
-			tableView.Table = null;
-		}
-
-		private void Quit ()
-		{
-			Application.RequestStop ();
-		}
-		private bool GetText (string title, string label, string initialText, out string enteredText)
-		{
-			bool okPressed = false;
-
-			var ok = new Button ("Ok", is_default: true);
-			ok.Clicked += (s, e) => { okPressed = true; Application.RequestStop (); };
-			var cancel = new Button ("Cancel");
-			cancel.Clicked += (s, e) => { Application.RequestStop (); };
-			var d = new Dialog (ok, cancel) { Title = title };
+	void ClearColumnStyles ()
+	{
+		tableView.Style.ColumnStyles.Clear ();
+		tableView.Update ();
+	}
 
-			var lbl = new Label () {
-				X = 0,
-				Y = 1,
-				Text = label
-			};
+	void CloseExample () => tableView.Table = null;
 
-			var tf = new TextField () {
-				Text = initialText,
-				X = 0,
-				Y = 2,
-				Width = Dim.Fill ()
-			};
+	void Quit () => Application.RequestStop ();
 
-			d.Add (lbl, tf);
-			tf.SetFocus ();
+	bool GetText (string title, string label, string initialText, out string enteredText)
+	{
+		bool okPressed = false;
 
-			Application.Run (d);
+		var ok = new Button ("Ok", true);
+		ok.Clicked += (s, e) => {
+			okPressed = true;
+			Application.RequestStop ();
+		};
+		var cancel = new Button ("Cancel");
+		cancel.Clicked += (s, e) => { Application.RequestStop (); };
+		var d = new Dialog (ok, cancel) { Title = title };
+
+		var lbl = new Label () {
+			X = 0,
+			Y = 1,
+			Text = label
+		};
+
+		var tf = new TextField () {
+			Text = initialText,
+			X = 0,
+			Y = 2,
+			Width = Dim.Fill ()
+		};
+
+		d.Add (lbl, tf);
+		tf.SetFocus ();
+
+		Application.Run (d);
+
+		enteredText = okPressed ? tf.Text : null;
+		return okPressed;
+	}
 
-			enteredText = okPressed ? tf.Text : null;
-			return okPressed;
+	void EditCurrentCell (object sender, CellActivatedEventArgs e)
+	{
+		if (e.Table == null) {
+			return;
 		}
-		private void EditCurrentCell (object sender, CellActivatedEventArgs e)
-		{
-			if (e.Table == null)
-				return;
-
-			var oldValue = currentTable.Rows [e.Row] [e.Col].ToString ();
 
-			if (GetText ("Enter new value", currentTable.Columns [e.Col].ColumnName, oldValue, out string newText)) {
-				try {
-					currentTable.Rows [e.Row] [e.Col] = string.IsNullOrWhiteSpace (newText) ? DBNull.Value : (object)newText;
-				} catch (Exception ex) {
-					MessageBox.ErrorQuery (60, 20, "Failed to set text", ex.Message, "Ok");
-				}
+		string oldValue = _currentTable.Rows [e.Row] [e.Col].ToString ();
 
-				tableView.Update ();
+		if (GetText ("Enter new value", _currentTable.Columns [e.Col].ColumnName, oldValue, out string newText)) {
+			try {
+				_currentTable.Rows [e.Row] [e.Col] = string.IsNullOrWhiteSpace (newText) ? DBNull.Value : (object)newText;
+			} catch (Exception ex) {
+				MessageBox.ErrorQuery (60, 20, "Failed to set text", ex.Message, "Ok");
 			}
+
+			tableView.Update ();
 		}
 	}
-}
+}

+ 0 - 1
UICatalog/Scenarios/GraphViewExample.cs

@@ -23,7 +23,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			graphs = new Action [] {
 				 ()=>SetupPeriodicTableScatterPlot(),    //0

+ 1 - 2
UICatalog/Scenarios/InteractiveTree.cs

@@ -15,7 +15,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
@@ -46,7 +45,7 @@ namespace UICatalog.Scenarios {
 
 		private void TreeView_KeyPress (object sender, Key obj)
 		{
-			if (obj.KeyCode == KeyCode.DeleteChar) {
+			if (obj.KeyCode == KeyCode.Delete) {
 
 				var toDelete = treeView.SelectedObject;
 

+ 0 - 1
UICatalog/Scenarios/LineViewExample.cs

@@ -17,7 +17,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {

+ 1 - 2
UICatalog/Scenarios/ListColumns.cs

@@ -34,7 +34,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			this.listColView = new TableView () {
 				X = 0,
@@ -155,7 +154,7 @@ namespace UICatalog.Scenarios {
 
 		private void TableViewKeyPress (object sender, Key e)
 		{
-			if (e.KeyCode == KeyCode.DeleteChar) {
+			if (e.KeyCode == KeyCode.Delete) {
 
 				// set all selected cells to null
 				foreach (var pt in listColView.GetAllSelectedCells ()) {

+ 0 - 1
UICatalog/Scenarios/MultiColouredTable.cs

@@ -18,7 +18,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableViewColors () {
 				X = 0,

+ 1 - 2
UICatalog/Scenarios/Notepad.cs

@@ -70,8 +70,7 @@ namespace UICatalog.Scenarios {
 			tabView.Enter += (s, e) => focusedTabView = tabView;
 
 			Application.Top.Add (statusBar);
-
-			New ();
+			Application.Top.Ready += (s, e) => New ();
 		}
 
 		private void TabView_SelectedTabChanged (object sender, TabChangedEventArgs e)

+ 0 - 1
UICatalog/Scenarios/ProcessTable.cs

@@ -18,7 +18,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			this.tableView = new TableView () {
 				X = 0,

+ 5 - 5
UICatalog/Scenarios/Progress.cs

@@ -112,11 +112,11 @@ namespace UICatalog.Scenarios {
 				};
 				Add (_startedLabel);
 
-				// Explictly cause layout so the setting of Height below works
-				LayoutSubviews ();
-
-				// Set height to height of controls + spacing + frame
-				Height = 2 + _verticalSpace + startButton.Frame.Height + _verticalSpace + ActivityProgressBar.Frame.Height + _verticalSpace + PulseProgressBar.Frame.Height + _verticalSpace;
+				// TODO: Great use of Dim.Auto
+				Initialized += (s, e) => {
+					// Set height to height of controls + spacing + frame
+					Height = 2 + _verticalSpace + startButton.Frame.Height + _verticalSpace + ActivityProgressBar.Frame.Height + _verticalSpace + PulseProgressBar.Frame.Height + _verticalSpace;
+				};
 			}
 
 			internal void Start ()

+ 155 - 146
UICatalog/Scenarios/Sliders.cs

@@ -6,15 +6,12 @@ using Terminal.Gui;
 
 namespace UICatalog.Scenarios;
 
-[ScenarioMetadata (Name: "Sliders", Description: "Demonstrates the Slider view.")]
+[ScenarioMetadata ("Sliders", "Demonstrates the Slider view.")]
 [ScenarioCategory ("Controls")]
 public class Sliders : Scenario {
 	public override void Setup ()
 	{
 		MakeSliders (Win, new List<object> { 500, 1000, 1500, 2000, 2500, 3000, 3500, 4000, 4500, 5000 });
-
-		#region configView
-
 		var configView = new FrameView {
 			Title = "Configuration",
 			X = Pos.Percent (50),
@@ -27,86 +24,70 @@ public class Sliders : Scenario {
 		Win.Add (configView);
 
 		#region Config Slider
-
-		var slider = new Slider<string> () {
+		var slider = new Slider<string> {
 			Title = "Options",
-			X = Pos.Center (),
+			X = 0,
 			Y = 0,
 			Type = SliderType.Multiple,
 			Width = Dim.Fill (),
+			Height = 4,
 			AllowEmpty = true,
 			BorderStyle = LineStyle.Single
 		};
 
-		slider.Style.SetChar.Attribute = new Terminal.Gui.Attribute (Color.BrightGreen, Color.Black);
-		slider.Style.LegendAttributes.SetAttribute = new Terminal.Gui.Attribute (Color.Green, Color.Black);
+		slider.Style.SetChar.Attribute = new Attribute (Color.BrightGreen,       Color.Black);
+		slider.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Black);
 
 		slider.Options = new List<SliderOption<string>> {
-					new SliderOption<string>{
-						Legend="Legends"
-					},
-					new SliderOption<string>{
-						Legend="RangeAllowSingle"
-					},
-					new SliderOption<string>{
-						Legend="Spacing"
-					}
-				};
+			new () {
+				Legend = "Legends"
+			},
+			new () {
+				Legend = "RangeAllowSingle"
+			},
+			new () {
+				Legend = "EndSpacing"
+			},
+			new () {
+				Legend = "AutoSize"
+			}
+		};
 
 		configView.Add (slider);
 
 		slider.OptionsChanged += (sender, e) => {
 			foreach (var s in Win.Subviews.OfType<Slider> ()) {
-				if (e.Options.ContainsKey (0))
-					s.ShowLegends = true;
-				else
-					s.ShowLegends = false;
-
-				if (e.Options.ContainsKey (1))
-					s.RangeAllowSingle = true;
-				else
-					s.RangeAllowSingle = false;
-
-				if (e.Options.ContainsKey (2))
-					s.ShowSpacing = true;
-				else
-					s.ShowSpacing = false;
+				s.ShowLegends = e.Options.ContainsKey (0);
+				s.RangeAllowSingle = e.Options.ContainsKey (1);
+				s.ShowEndSpacing = e.Options.ContainsKey (2);
+				s.AutoSize = e.Options.ContainsKey (3);
+				if (!s.AutoSize) {
+					if (s.Orientation == Orientation.Horizontal) {
+						s.Width = Dim.Percent (50);
+						var h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical ? s.Options.Max (o => o.Legend.Length) + 3 : 4;
+						s.Height = h;
+					} else {
+						var w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3;
+						s.Width = w;
+						s.Height = Dim.Fill ();
+					}
+				}
+			}
+			if (Win.IsInitialized) {
+				Win.LayoutSubviews ();
 			}
-			Win.LayoutSubviews ();
 		};
-		slider.SetOption (0);
-		slider.SetOption (1);
-
-		#endregion
-
-		#region InnerSpacing Input
-		// var innerspacing_slider = new Slider<string> ("Innerspacing", new List<string> { "Auto", "0", "1", "2", "3", "4", "5" }) {
-		// 	X = Pos.Center (),
-		// 	Y = Pos.Bottom (slider) + 1
-		// };
-
-		// innerspacing_slider.SetOption (0);
-
-		// configView.Add (innerspacing_slider);
-
-		// innerspacing_slider.OptionsChanged += (options) => {
-		// 	foreach (var s in leftView.Subviews.OfType<Slider> () ()) {
-		// 		if (options.ContainsKey (0)) { }
-		// 		//s.la = S.SliderLayout.Auto;
-		// 		else {
-		// 			s.InnerSpacing = options.Keys.First () - 1;
-		// 		}
-		// 	}
-		// };
-		#endregion
+		slider.SetOption (0); // Legends
+		slider.SetOption (1); // RangeAllowSingle
+		//slider.SetOption (3); // AutoSize
 
 		#region Slider Orientation Slider
-
 		var slider_orientation_slider = new Slider<string> (new List<string> { "Horizontal", "Vertical" }) {
 			Title = "Slider Orientation",
 			X = 0,
 			Y = Pos.Bottom (slider) + 1,
 			Width = Dim.Fill (),
+			Height = 4,
 			BorderStyle = LineStyle.Single
 		};
 
@@ -115,17 +96,12 @@ public class Sliders : Scenario {
 		configView.Add (slider_orientation_slider);
 
 		slider_orientation_slider.OptionsChanged += (sender, e) => {
-
 			View prev = null;
 			foreach (var s in Win.Subviews.OfType<Slider> ()) {
-
 				if (e.Options.ContainsKey (0)) {
 					s.Orientation = Orientation.Horizontal;
 
-					s.AdjustBestHeight ();
-					s.Width = Dim.Percent (50);
-
-					s.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.HLine };
+					s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.HLine };
 
 					if (prev == null) {
 						s.LayoutStyle = LayoutStyle.Absolute;
@@ -140,11 +116,7 @@ public class Sliders : Scenario {
 				} else if (e.Options.ContainsKey (1)) {
 					s.Orientation = Orientation.Vertical;
 
-					s.AdjustBestWidth ();
-					s.Height = Dim.Fill ();
-
-					s.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.VLine };
-
+					s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.VLine };
 
 					if (prev == null) {
 						s.LayoutStyle = LayoutStyle.Absolute;
@@ -156,19 +128,29 @@ public class Sliders : Scenario {
 					s.Y = 0;
 					prev = s;
 				}
+
+				if (s.Orientation == Orientation.Horizontal) {
+					s.Width = Dim.Percent (50);
+					var h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical ? s.Options.Max (o => o.Legend.Length) + 3 : 4;
+					s.Height = h;
+				} else {
+					var w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3;
+					s.Width = w;
+					s.Height = Dim.Fill ();
+				}
+
 			}
 			Win.LayoutSubviews ();
 		};
-
-		#endregion
+		#endregion Slider Orientation Slider
 
 		#region Legends Orientation Slider
-
 		var legends_orientation_slider = new Slider<string> (new List<string> { "Horizontal", "Vertical" }) {
 			Title = "Legends Orientation",
 			X = Pos.Center (),
 			Y = Pos.Bottom (slider_orientation_slider) + 1,
 			Width = Dim.Fill (),
+			Height = 4,
 			BorderStyle = LineStyle.Single
 		};
 
@@ -178,143 +160,170 @@ public class Sliders : Scenario {
 
 		legends_orientation_slider.OptionsChanged += (sender, e) => {
 			foreach (var s in Win.Subviews.OfType<Slider> ()) {
-				if (e.Options.ContainsKey (0))
+				if (e.Options.ContainsKey (0)) {
 					s.LegendsOrientation = Orientation.Horizontal;
-				else if (e.Options.ContainsKey (1))
+				} else if (e.Options.ContainsKey (1)) {
 					s.LegendsOrientation = Orientation.Vertical;
+				}
+				if (s.Orientation == Orientation.Horizontal) {
+					s.Width = Dim.Percent (50);
+					var h = s.ShowLegends && s.LegendsOrientation == Orientation.Vertical ? s.Options.Max (o => o.Legend.Length) + 3 : 4;
+					s.Height = h;
+				} else {
+					var w = s.ShowLegends ? s.Options.Max (o => o.Legend.Length) + 3 : 3;
+					s.Width = w;
+					s.Height = Dim.Fill ();
+				}
 			}
 			Win.LayoutSubviews ();
 		};
-
-		#endregion
+		#endregion Legends Orientation Slider
 
 		#region Color Slider
+		foreach (var s in Win.Subviews.OfType<Slider> ()) {
+			s.Style.OptionChar.Attribute = Win.GetNormalColor ();
+			s.Style.SetChar.Attribute = Win.GetNormalColor ();
+			s.Style.LegendAttributes.SetAttribute = Win.GetNormalColor ();
+			s.Style.RangeChar.Attribute = Win.GetNormalColor ();
+		}
 
-		var sliderColor = new Slider<(Color, Color)> () {
-			Title = "Color",
-			X = Pos.Center (),
+		var sliderFGColor = new Slider<(Color, Color)> {
+			Title = "FG Color",
+			X = 0,
 			Y = Pos.Bottom (legends_orientation_slider) + 1,
 			Type = SliderType.Single,
-			Width = Dim.Fill (),
 			BorderStyle = LineStyle.Single,
-			AllowEmpty = false
+			AllowEmpty = false,
+			Orientation = Orientation.Vertical,
+			LegendsOrientation = Orientation.Horizontal,
+			AutoSize = true
 		};
 
-		sliderColor.Style.SetChar.Attribute = new Terminal.Gui.Attribute (Color.BrightGreen, Color.Black);
-		sliderColor.Style.LegendAttributes.SetAttribute = new Terminal.Gui.Attribute (Color.Green, Color.Blue);
+		sliderFGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen,       Color.Black);
+		sliderFGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue);
 
-		sliderColor.LegendsOrientation = Orientation.Vertical;
 		var colorOptions = new List<SliderOption<(Color, Color)>> ();
 		foreach (var colorIndex in Enum.GetValues<ColorName> ()) {
 			var colorName = colorIndex.ToString ();
 			colorOptions.Add (new SliderOption<(Color, Color)> {
-				Data = (new Color((ColorName)colorIndex), Win.GetNormalColor ().Background),
+				Data = (new Color (colorIndex), new Color (colorIndex)),
 				Legend = colorName,
-				LegendAbbr = (Rune)colorName [0],
+				LegendAbbr = (Rune)colorName [0]
 			});
 		}
-		sliderColor.Options = colorOptions;
+		sliderFGColor.Options = colorOptions;
 
-		configView.Add (sliderColor);
+		configView.Add (sliderFGColor);
 
-		sliderColor.OptionsChanged += (sender, e) => {
+		sliderFGColor.OptionsChanged += (sender, e) => {
 			if (e.Options.Count != 0) {
 				var data = e.Options.First ().Value.Data;
-
-				foreach (var s in Win.Subviews.OfType<Slider> ()) {
-					s.Style.OptionChar.Attribute = new Attribute (data.Item1, data.Item2);
-					s.Style.SetChar.Attribute = new Attribute (data.Item1, data.Item2);
-					s.Style.LegendAttributes.SetAttribute = new Attribute (data.Item1, Win.GetNormalColor ().Background);
-					s.Style.RangeChar.Attribute = new Attribute (data.Item1, Win.GetNormalColor ().Background);
-					s.Style.SpaceChar.Attribute = new Attribute (data.Item1, Win.GetNormalColor ().Background);
-					s.Style.LegendAttributes.NormalAttribute = new Attribute (data.Item1, Win.GetNormalColor ().Background);
-					// Here we can not call SetNeedDisplay(), because the OptionsChanged was triggered by Key Pressing,
-					// that internaly calls SetNeedDisplay.
-				}
-			} else {
 				foreach (var s in Win.Subviews.OfType<Slider> ()) {
-					s.Style.SetChar.Attribute = null;
-					s.Style.LegendAttributes.SetAttribute = null;
-					s.Style.RangeChar.Attribute = null;
+					s.ColorScheme = new ColorScheme (s.ColorScheme);
+					s.ColorScheme.Normal = new Attribute (data.Item2, s.ColorScheme.Normal.Background);
+
+					s.Style.OptionChar.Attribute = new Attribute (data.Item1,             s.ColorScheme.Normal.Background);
+					s.Style.SetChar.Attribute = new Attribute (data.Item1,                s.Style.SetChar.Attribute?.Background ?? s.ColorScheme.Normal.Background);
+					s.Style.LegendAttributes.SetAttribute = new Attribute (data.Item1,    s.ColorScheme.Normal.Background);
+					s.Style.RangeChar.Attribute = new Attribute (data.Item1,              s.ColorScheme.Normal.Background);
+					s.Style.SpaceChar.Attribute = new Attribute (data.Item1,              s.ColorScheme.Normal.Background);
+					s.Style.LegendAttributes.NormalAttribute = new Attribute (data.Item1, s.ColorScheme.Normal.Background);
 				}
 			}
 		};
 
-		// Set option after Eventhandler def, so it updates the sliders color.
-		// sliderColor.SetOption (2);
+		var sliderBGColor = new Slider<(Color, Color)> {
+			Title = "BG Color",
+			X = Pos.Right (sliderFGColor),
+			Y = Pos.Top (sliderFGColor),
+			Type = SliderType.Single,
+			BorderStyle = LineStyle.Single,
+			AllowEmpty = false,
+			Orientation = Orientation.Vertical,
+			LegendsOrientation = Orientation.Horizontal,
+			AutoSize = true
+		};
 
-		#endregion
+		sliderBGColor.Style.SetChar.Attribute = new Attribute (Color.BrightGreen,       Color.Black);
+		sliderBGColor.Style.LegendAttributes.SetAttribute = new Attribute (Color.Green, Color.Blue);
 
-		#endregion
+		sliderBGColor.Options = colorOptions;
 
-		Win.FocusFirst ();
+		configView.Add (sliderBGColor);
+
+		sliderBGColor.OptionsChanged += (sender, e) => {
+			if (e.Options.Count != 0) {
+				var data = e.Options.First ().Value.Data;
+
+				foreach (var s in Win.Subviews.OfType<Slider> ()) {
+					s.ColorScheme = new ColorScheme (s.ColorScheme);
+					s.ColorScheme.Normal = new Attribute (s.ColorScheme.Normal.Foreground, data.Item2);
+				}
+			}
+		};
+		#endregion Color Slider
+		#endregion Config Slider
 
+		Win.FocusFirst ();
+		Application.Top.Initialized += (s, e) => Application.Top.LayoutSubviews ();
 	}
 
 	public void MakeSliders (View v, List<object> options)
 	{
 		var types = Enum.GetValues (typeof (SliderType)).Cast<SliderType> ().ToList ();
-
 		Slider prev = null;
 
 		foreach (var type in types) {
-			var view = new Slider (options, Orientation.Horizontal) {
+			var view = new Slider (options) {
 				Title = type.ToString (),
 				X = 0,
-				//X = Pos.Right (view) + 1,
 				Y = prev == null ? 0 : Pos.Bottom (prev),
-				//Y = Pos.Center (),
-				Width = Dim.Percent (50),
 				BorderStyle = LineStyle.Single,
 				Type = type,
-				LegendsOrientation = Orientation.Horizontal,
-				AllowEmpty = true,
+				AllowEmpty = true
 			};
 			v.Add (view);
 			prev = view;
-		};
-
-		var singleOptions = new List<object> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39 };
+		}
 
-		var single = new Slider (singleOptions, Orientation.Horizontal) {
-			Title = "Actual slider",
+		var singleOptions = new List<object>
+			{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39 };
+		var single = new Slider (singleOptions) {
+			Title = "Continuous",
 			X = 0,
-			//X = Pos.Right (view) + 1,
 			Y = prev == null ? 0 : Pos.Bottom (prev),
-			//Y = Pos.Center (),
 			Type = SliderType.Single,
-			//BorderStyle = LineStyle.Single,
-			LegendsOrientation = Orientation.Horizontal,
-			Width = Dim.Percent (50),
-			AllowEmpty = false,
-			//ShowSpacing = true
+			BorderStyle = LineStyle.Single,
+			AllowEmpty = false
 		};
 
 		single.LayoutStarted += (s, e) => {
 			if (single.Orientation == Orientation.Horizontal) {
-				single.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.HLine };
-				single.Style.OptionChar = new Cell () { Rune = CM.Glyphs.HLine };
+				single.Style.SpaceChar = new Cell { Rune = CM.Glyphs.HLine };
+				single.Style.OptionChar = new Cell { Rune = CM.Glyphs.HLine };
 			} else {
-				single.Style.SpaceChar = new Cell () { Rune = CM.Glyphs.VLine };
-				single.Style.OptionChar = new Cell () { Rune = CM.Glyphs.VLine };
+				single.Style.SpaceChar = new Cell { Rune = CM.Glyphs.VLine };
+				single.Style.OptionChar = new Cell { Rune = CM.Glyphs.VLine };
 			}
 		};
-		single.Style.SetChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment };
-		single.Style.DragChar = new Cell () { Rune = CM.Glyphs.ContinuousMeterSegment };
+		single.Style.SetChar = new Cell { Rune = CM.Glyphs.ContinuousMeterSegment };
+		single.Style.DragChar = new Cell { Rune = CM.Glyphs.ContinuousMeterSegment };
 
 		v.Add (single);
 
-		var label = new Label () {
-			X = 0,
-			Y = Pos.Bottom (single),
-			Height = 1,
-			Width = Dim.Width (single),
-			Text = $"{single.GetSetOptions ().FirstOrDefault ()}"
-		};
 		single.OptionsChanged += (s, e) => {
-			label.Text = $"{e.Options.FirstOrDefault ().Key}";
+			single.Title = $"Continuous {e.Options.FirstOrDefault ().Key}";
 		};
 
-		v.Add (label);
+		var oneOption = new List<object> { "The Only Option" };
+		var one = new Slider (oneOption) {
+			Title = "One Option",
+			X = 0,
+			Y = prev == null ? 0 : Pos.Bottom (single),
+			Type = SliderType.Single,
+			BorderStyle = LineStyle.Single,
+			AllowEmpty = false
+		};
+		v.Add (one);
 	}
-}
+}

+ 0 - 1
UICatalog/Scenarios/TabViewExample.cs

@@ -24,7 +24,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {

+ 2 - 3
UICatalog/Scenarios/TableEditor.cs

@@ -49,8 +49,7 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
-
+			
 			this.tableView = new TableView () {
 				X = 0,
 				Y = 0,
@@ -392,7 +391,7 @@ namespace UICatalog.Scenarios {
 				return;
 			}
 
-			if (e.KeyCode == KeyCode.DeleteChar) {
+			if (e.KeyCode == KeyCode.Delete) {
 
 				if (tableView.FullRowSelect) {
 					// Delete button deletes all rows when in full row mode

+ 0 - 1
UICatalog/Scenarios/TreeUseCases.cs

@@ -14,7 +14,6 @@ namespace UICatalog.Scenarios {
 			Win.Title = this.GetName ();
 			Win.Y = 1; // menu
 			Win.Height = Dim.Fill (1); // status bar
-			Application.Top.LayoutSubviews ();
 
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {

+ 334 - 336
UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -1,400 +1,398 @@
-using System;
-using System.Collections.Generic;
 using System.IO;
 using System.IO.Abstractions;
 using System.Linq;
 using System.Text;
 using Terminal.Gui;
 
-namespace UICatalog.Scenarios {
-	[ScenarioMetadata (Name: "File System Explorer", Description: "Hierarchical file system explorer demonstrating TreeView.")]
-	[ScenarioCategory ("Controls"), ScenarioCategory ("TreeView"), ScenarioCategory ("Files and IO")]
-	public class TreeViewFileSystem : Scenario {
-
-		/// <summary>
-		/// A tree view where nodes are files and folders
-		/// </summary>
-		TreeView<IFileSystemInfo> treeViewFiles;
-
-		MenuItem miShowLines;
-		private MenuItem _miPlusMinus;
-		private MenuItem _miArrowSymbols;
-		private MenuItem _miNoSymbols;
-		private MenuItem _miColoredSymbols;
-		private MenuItem _miInvertSymbols;
-
-		private MenuItem _miBasicIcons;
-		private MenuItem _miUnicodeIcons;
-		private MenuItem _miNerdIcons;
-
-		private MenuItem _miFullPaths;
-		private MenuItem _miLeaveLastRow;
-		private MenuItem _miHighlightModelTextOnly;
-		private MenuItem _miCustomColors;
-		private MenuItem _miCursor;
-		private MenuItem _miMultiSelect;
-
-		private DetailsFrame _detailsFrame;
-		private FileSystemIconProvider _iconProvider = new ();
-
-		public override void Setup ()
-		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill ();
-			Application.Top.LayoutSubviews ();
-
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Quit", $"{Application.QuitKey}", () => Quit()),
-				}),
-				new MenuBarItem ("_View", new MenuItem [] {
-					_miFullPaths = new MenuItem ("_Full Paths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					_miMultiSelect = new MenuItem ("_Multi Select", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
-				}),
-				new MenuBarItem ("_Style", new MenuItem [] {
-					miShowLines = new MenuItem ("_Show Lines", "", () => ShowLines()){
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("File System Explorer", "Hierarchical file system explorer demonstrating TreeView.")]
+[ScenarioCategory ("Controls")] [ScenarioCategory ("TreeView")] [ScenarioCategory ("Files and IO")]
+public class TreeViewFileSystem : Scenario {
+	/// <summary>
+	/// A tree view where nodes are files and folders
+	/// </summary>
+	TreeView<IFileSystemInfo> _treeViewFiles;
+
+	MenuItem _miShowLines;
+	MenuItem _miPlusMinus;
+	MenuItem _miArrowSymbols;
+	MenuItem _miNoSymbols;
+	MenuItem _miColoredSymbols;
+	MenuItem _miInvertSymbols;
+
+	MenuItem _miBasicIcons;
+	MenuItem _miUnicodeIcons;
+	MenuItem _miNerdIcons;
+
+	MenuItem _miFullPaths;
+	MenuItem _miLeaveLastRow;
+	MenuItem _miHighlightModelTextOnly;
+	MenuItem _miCustomColors;
+	MenuItem _miCursor;
+	MenuItem _miMultiSelect;
+
+	DetailsFrame _detailsFrame;
+	FileSystemIconProvider _iconProvider = new ();
+
+	public override void Setup ()
+	{
+		Win.Title = GetName ();
+		Win.Y = 1; // menu
+		Win.Height = Dim.Fill ();
+
+		var menu = new MenuBar (new MenuBarItem [] {
+			new ("_File", new MenuItem [] {
+				new ("_Quit", $"{Application.QuitKey}", () => Quit ())
+			}),
+			new ("_View", new MenuItem [] {
+				_miFullPaths = new MenuItem ("_Full Paths", "", () => SetFullName ()) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
+				_miMultiSelect = new MenuItem ("_Multi Select", "", () => SetMultiSelect ()) { Checked = true, CheckType = MenuItemCheckStyle.Checked }
+			}),
+			new ("_Style", new MenuItem [] {
+				_miShowLines = new MenuItem ("_Show Lines", "", () => ShowLines ()) {
 					Checked = true, CheckType = MenuItemCheckStyle.Checked
-						},
-					null /*separator*/,
-					_miPlusMinus = new MenuItem ("_Plus Minus Symbols", "+ -", () => SetExpandableSymbols((Rune)'+',(Rune)'-')){Checked = true, CheckType = MenuItemCheckStyle.Radio},
-					_miArrowSymbols = new MenuItem ("_Arrow Symbols", "> v", () => SetExpandableSymbols((Rune)'>',(Rune)'v')){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					_miNoSymbols = new MenuItem ("_No Symbols", "", () => SetExpandableSymbols(default,null)){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					null /*separator*/,
-					_miColoredSymbols = new MenuItem ("_Colored Symbols", "", () => ShowColoredExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					_miInvertSymbols = new MenuItem ("_Invert Symbols", "", () => InvertExpandableSymbols()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					null /*separator*/,
-					_miBasicIcons = new MenuItem ("_Basic Icons",null, SetNoIcons){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					_miUnicodeIcons = new MenuItem ("_Unicode Icons", null, SetUnicodeIcons){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					_miNerdIcons = new MenuItem ("_Nerd Icons", null, SetNerdIcons){Checked = false, CheckType = MenuItemCheckStyle.Radio},
-					null /*separator*/,
-					_miLeaveLastRow = new MenuItem ("_Leave Last Row", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
-					_miHighlightModelTextOnly = new MenuItem ("_Highlight Model Text Only", "", () => SetCheckHighlightModelTextOnly()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					null /*separator*/,
-					_miCustomColors = new MenuItem ("C_ustom Colors Hidden Files", "Yellow/Red", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-					null /*separator*/,
-					_miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
-				}),
-			});
-			Application.Top.Add (menu);
-
-			treeViewFiles = new TreeView<IFileSystemInfo> () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Percent (50),
-				Height = Dim.Fill (),
-			};
-			treeViewFiles.DrawLine += TreeViewFiles_DrawLine;
-
-			_detailsFrame = new DetailsFrame (_iconProvider) {
-				X = Pos.Right (treeViewFiles),
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
-
-			Win.Add (_detailsFrame);
-			treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
-			treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
-			treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
-
-			SetupFileTree ();
+				},
+				null /*separator*/,
+				_miPlusMinus = new MenuItem ("_Plus Minus Symbols", "+ -", () => SetExpandableSymbols ((Rune)'+', (Rune)'-')) { Checked = true, CheckType = MenuItemCheckStyle.Radio },
+				_miArrowSymbols = new MenuItem ("_Arrow Symbols", "> v", () => SetExpandableSymbols ((Rune)'>', (Rune)'v')) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
+				_miNoSymbols = new MenuItem ("_No Symbols", "", () => SetExpandableSymbols (default, null)) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
+				null /*separator*/,
+				_miColoredSymbols = new MenuItem ("_Colored Symbols", "", () => ShowColoredExpandableSymbols ()) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
+				_miInvertSymbols = new MenuItem ("_Invert Symbols", "", () => InvertExpandableSymbols ()) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
+				null /*separator*/,
+				_miBasicIcons = new MenuItem ("_Basic Icons", null, SetNoIcons) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
+				_miUnicodeIcons = new MenuItem ("_Unicode Icons", null, SetUnicodeIcons) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
+				_miNerdIcons = new MenuItem ("_Nerd Icons", null, SetNerdIcons) { Checked = false, CheckType = MenuItemCheckStyle.Radio },
+				null /*separator*/,
+				_miLeaveLastRow = new MenuItem ("_Leave Last Row", "", () => SetLeaveLastRow ()) { Checked = true, CheckType = MenuItemCheckStyle.Checked },
+				_miHighlightModelTextOnly = new MenuItem ("_Highlight Model Text Only", "", () => SetCheckHighlightModelTextOnly ()) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
+				null /*separator*/,
+				_miCustomColors = new MenuItem ("C_ustom Colors Hidden Files", "Yellow/Red", () => SetCustomColors ()) { Checked = false, CheckType = MenuItemCheckStyle.Checked },
+				null /*separator*/,
+				_miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor ()) { Checked = false, CheckType = MenuItemCheckStyle.Checked }
+			})
+		});
+		Application.Top.Add (menu);
+
+		_treeViewFiles = new TreeView<IFileSystemInfo> () {
+			X = 0,
+			Y = 0,
+			Width = Dim.Percent (50),
+			Height = Dim.Fill ()
+		};
+		_treeViewFiles.DrawLine += TreeViewFiles_DrawLine;
+
+		_detailsFrame = new DetailsFrame (_iconProvider) {
+			X = Pos.Right (_treeViewFiles),
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+
+		Win.Add (_detailsFrame);
+		_treeViewFiles.MouseClick += TreeViewFiles_MouseClick;
+		_treeViewFiles.KeyDown += TreeViewFiles_KeyPress;
+		_treeViewFiles.SelectionChanged += TreeViewFiles_SelectionChanged;
+
+		SetupFileTree ();
+
+		Win.Add (_treeViewFiles);
+		_treeViewFiles.GoToFirst ();
+		_treeViewFiles.Expand ();
+
+		SetupScrollBar ();
+
+		_treeViewFiles.SetFocus ();
+
+		UpdateIconCheckedness ();
+	}
 
-			Win.Add (treeViewFiles);
-			treeViewFiles.GoToFirst ();
-			treeViewFiles.Expand ();
+	void SetNoIcons ()
+	{
+		_iconProvider.UseUnicodeCharacters = false;
+		_iconProvider.UseNerdIcons = false;
+		UpdateIconCheckedness ();
+	}
 
-			SetupScrollBar ();
+	void SetUnicodeIcons ()
+	{
+		_iconProvider.UseUnicodeCharacters = true;
+		UpdateIconCheckedness ();
+	}
 
-			treeViewFiles.SetFocus ();
+	void SetNerdIcons ()
+	{
+		_iconProvider.UseNerdIcons = true;
+		UpdateIconCheckedness ();
+	}
 
-			UpdateIconCheckedness ();
-		}
+	void UpdateIconCheckedness ()
+	{
+		_miBasicIcons.Checked = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters;
+		_miUnicodeIcons.Checked = _iconProvider.UseUnicodeCharacters;
+		_miNerdIcons.Checked = _iconProvider.UseNerdIcons;
+		_treeViewFiles.SetNeedsDisplay ();
+	}
 
-		private void SetNoIcons ()
-		{
-			_iconProvider.UseUnicodeCharacters = false;
-			_iconProvider.UseNerdIcons = false;
-			UpdateIconCheckedness ();
+	void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e) => ShowPropertiesOf (e.NewValue);
+
+	void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
+	{
+		// Render directory icons in yellow
+		if (e.Model is IDirectoryInfo d) {
+			if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters) {
+				if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.RuneCells.Count) {
+					var cell = e.RuneCells [e.IndexOfModelText];
+					cell.ColorScheme = new ColorScheme (
+						new Attribute (
+							Color.BrightYellow,
+							cell.ColorScheme.Normal.Background)
+					);
+				}
+			}
 		}
+	}
 
-		private void SetUnicodeIcons ()
-		{
-			_iconProvider.UseUnicodeCharacters = true;
-			UpdateIconCheckedness ();
-		}
-		private void SetNerdIcons ()
-		{
-			_iconProvider.UseNerdIcons = true;
-			UpdateIconCheckedness ();
-		}
-		private void UpdateIconCheckedness ()
-		{
-			_miBasicIcons.Checked = !_iconProvider.UseNerdIcons && !_iconProvider.UseUnicodeCharacters;
-			_miUnicodeIcons.Checked = _iconProvider.UseUnicodeCharacters;
-			_miNerdIcons.Checked = _iconProvider.UseNerdIcons;
-			treeViewFiles.SetNeedsDisplay ();
-		}
+	void TreeViewFiles_KeyPress (object sender, Key obj)
+	{
+		if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask)) {
 
-		private void TreeViewFiles_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
-		{
-			ShowPropertiesOf (e.NewValue);
-		}
+			var selected = _treeViewFiles.SelectedObject;
 
-		private void TreeViewFiles_DrawLine (object sender, DrawTreeViewLineEventArgs<IFileSystemInfo> e)
-		{
-			// Render directory icons in yellow
-			if (e.Model is IDirectoryInfo d) {
-				if (_iconProvider.UseNerdIcons || _iconProvider.UseUnicodeCharacters) {
-					if (e.IndexOfModelText > 0 && e.IndexOfModelText < e.RuneCells.Count) {
-						var cell = e.RuneCells [e.IndexOfModelText];
-						cell.ColorScheme = new ColorScheme (
-							new Terminal.Gui.Attribute (
-								Color.BrightYellow,
-								cell.ColorScheme.Normal.Background)
-						);
-					}
-				}
+			// nothing is selected
+			if (selected == null) {
+				return;
 			}
-		}
 
-		private void TreeViewFiles_KeyPress (object sender, Key obj)
-		{
-			if (obj.KeyCode == (KeyCode.R | KeyCode.CtrlMask)) {
+			int? location = _treeViewFiles.GetObjectRow (selected);
 
-				var selected = treeViewFiles.SelectedObject;
+			//selected object is offscreen or somehow not found
+			if (location == null || location < 0 || location > _treeViewFiles.Frame.Height) {
+				return;
+			}
 
-				// nothing is selected
-				if (selected == null)
-					return;
+			ShowContextMenu (new Point (
+					5 + _treeViewFiles.Frame.X,
+					location.Value + _treeViewFiles.Frame.Y + 2),
+				selected);
+		}
+	}
 
-				var location = treeViewFiles.GetObjectRow (selected);
+	void TreeViewFiles_MouseClick (object sender, MouseEventEventArgs obj)
+	{
+		// if user right clicks
+		if (obj.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
 
-				//selected object is offscreen or somehow not found
-				if (location == null || location < 0 || location > treeViewFiles.Frame.Height)
-					return;
+			var rightClicked = _treeViewFiles.GetObjectOnRow (obj.MouseEvent.Y);
 
-				ShowContextMenu (new Point (
-					5 + treeViewFiles.Frame.X,
-					location.Value + treeViewFiles.Frame.Y + 2),
-					selected);
+			// nothing was clicked
+			if (rightClicked == null) {
+				return;
 			}
+
+			ShowContextMenu (new Point (
+					obj.MouseEvent.X + _treeViewFiles.Frame.X,
+					obj.MouseEvent.Y + _treeViewFiles.Frame.Y + 2),
+				rightClicked);
 		}
+	}
 
-		private void TreeViewFiles_MouseClick (object sender, MouseEventEventArgs obj)
-		{
-			// if user right clicks
-			if (obj.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
+	{
+		var menu = new ContextMenu ();
+		menu.Position = screenPoint;
 
-				var rightClicked = treeViewFiles.GetObjectOnRow (obj.MouseEvent.Y);
+		menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) });
 
-				// nothing was clicked
-				if (rightClicked == null)
-					return;
+		Application.Invoke (menu.Show);
+	}
 
-				ShowContextMenu (new Point (
-					obj.MouseEvent.X + treeViewFiles.Frame.X,
-					obj.MouseEvent.Y + treeViewFiles.Frame.Y + 2),
-					rightClicked);
-			}
-		}
+	class DetailsFrame : FrameView {
+		IFileSystemInfo fileInfo;
+		FileSystemIconProvider _iconProvider;
 
-		private void ShowContextMenu (Point screenPoint, IFileSystemInfo forObject)
+		public DetailsFrame (FileSystemIconProvider iconProvider)
 		{
-			var menu = new ContextMenu ();
-			menu.Position = screenPoint;
-
-			menu.MenuItems = new MenuBarItem (new [] { new MenuItem ("Properties", null, () => ShowPropertiesOf (forObject)) });
-
-			Application.Invoke (menu.Show);
+			Title = "Details";
+			Visible = true;
+			CanFocus = true;
+			_iconProvider = iconProvider;
 		}
 
-		class DetailsFrame : FrameView {
-			private IFileSystemInfo fileInfo;
-			private FileSystemIconProvider _iconProvider;
-
-			public DetailsFrame (FileSystemIconProvider iconProvider)
-			{
-				Title = "Details";
-				Visible = true;
-				CanFocus = true;
-				_iconProvider = iconProvider;
-			}
+		public IFileSystemInfo FileInfo {
+			get => fileInfo;
+			set {
+				fileInfo = value;
+				StringBuilder sb = null;
+
+				if (fileInfo is IFileInfo f) {
+					Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
+					sb = new StringBuilder ();
+					sb.AppendLine ($"Path:\n {f.FullName}\n");
+					sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
+					sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
+					sb.AppendLine ($"Created:\n {f.CreationTime}");
+				}
 
-			public IFileSystemInfo FileInfo {
-				get => fileInfo; set {
-					fileInfo = value;
-					System.Text.StringBuilder sb = null;
-
-					if (fileInfo is IFileInfo f) {
-						Title = $"{_iconProvider.GetIconWithOptionalSpace (f)}{f.Name}".Trim ();
-						sb = new System.Text.StringBuilder ();
-						sb.AppendLine ($"Path:\n {f.FullName}\n");
-						sb.AppendLine ($"Size:\n {f.Length:N0} bytes\n");
-						sb.AppendLine ($"Modified:\n {f.LastWriteTime}\n");
-						sb.AppendLine ($"Created:\n {f.CreationTime}");
-					}
-
-					if (fileInfo is IDirectoryInfo dir) {
-						Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
-						sb = new System.Text.StringBuilder ();
-						sb.AppendLine ($"Path:\n {dir?.FullName}\n");
-						sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
-						sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
-					}
-					Text = sb.ToString ();
+				if (fileInfo is IDirectoryInfo dir) {
+					Title = $"{_iconProvider.GetIconWithOptionalSpace (dir)}{dir.Name}".Trim ();
+					sb = new StringBuilder ();
+					sb.AppendLine ($"Path:\n {dir?.FullName}\n");
+					sb.AppendLine ($"Modified:\n {dir.LastWriteTime}\n");
+					sb.AppendLine ($"Created:\n {dir.CreationTime}\n");
 				}
+				Text = sb.ToString ();
 			}
 		}
+	}
 
-		private void ShowPropertiesOf (IFileSystemInfo fileSystemInfo)
-		{
-			_detailsFrame.FileInfo = fileSystemInfo;
-		}
+	void ShowPropertiesOf (IFileSystemInfo fileSystemInfo) => _detailsFrame.FileInfo = fileSystemInfo;
 
-		private void SetupScrollBar ()
-		{
-			// When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
-			treeViewFiles.Style.LeaveLastRow = true;
+	void SetupScrollBar ()
+	{
+		// When using scroll bar leave the last row of the control free (for over-rendering with scroll bar)
+		_treeViewFiles.Style.LeaveLastRow = true;
 
-			var scrollBar = new ScrollBarView (treeViewFiles, true);
+		var scrollBar = new ScrollBarView (_treeViewFiles, true);
 
-			scrollBar.ChangedPosition += (s, e) => {
-				treeViewFiles.ScrollOffsetVertical = scrollBar.Position;
-				if (treeViewFiles.ScrollOffsetVertical != scrollBar.Position) {
-					scrollBar.Position = treeViewFiles.ScrollOffsetVertical;
-				}
-				treeViewFiles.SetNeedsDisplay ();
-			};
+		scrollBar.ChangedPosition += (s, e) => {
+			_treeViewFiles.ScrollOffsetVertical = scrollBar.Position;
+			if (_treeViewFiles.ScrollOffsetVertical != scrollBar.Position) {
+				scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
+			}
+			_treeViewFiles.SetNeedsDisplay ();
+		};
 
-			scrollBar.OtherScrollBarView.ChangedPosition += (s, e) => {
-				treeViewFiles.ScrollOffsetHorizontal = scrollBar.OtherScrollBarView.Position;
-				if (treeViewFiles.ScrollOffsetHorizontal != scrollBar.OtherScrollBarView.Position) {
-					scrollBar.OtherScrollBarView.Position = treeViewFiles.ScrollOffsetHorizontal;
-				}
-				treeViewFiles.SetNeedsDisplay ();
-			};
+		scrollBar.OtherScrollBarView.ChangedPosition += (s, e) => {
+			_treeViewFiles.ScrollOffsetHorizontal = scrollBar.OtherScrollBarView.Position;
+			if (_treeViewFiles.ScrollOffsetHorizontal != scrollBar.OtherScrollBarView.Position) {
+				scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
+			}
+			_treeViewFiles.SetNeedsDisplay ();
+		};
+
+		_treeViewFiles.DrawContent += (s, e) => {
+			scrollBar.Size = _treeViewFiles.ContentHeight;
+			scrollBar.Position = _treeViewFiles.ScrollOffsetVertical;
+			scrollBar.OtherScrollBarView.Size = _treeViewFiles.GetContentWidth (true);
+			scrollBar.OtherScrollBarView.Position = _treeViewFiles.ScrollOffsetHorizontal;
+			scrollBar.Refresh ();
+		};
+	}
 
-			treeViewFiles.DrawContent += (s, e) => {
-				scrollBar.Size = treeViewFiles.ContentHeight;
-				scrollBar.Position = treeViewFiles.ScrollOffsetVertical;
-				scrollBar.OtherScrollBarView.Size = treeViewFiles.GetContentWidth (true);
-				scrollBar.OtherScrollBarView.Position = treeViewFiles.ScrollOffsetHorizontal;
-				scrollBar.Refresh ();
-			};
-		}
+	void SetupFileTree ()
+	{
+		// setup how to build tree
+		var fs = new FileSystem ();
+		var rootDirs = DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
+		_treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
+		_treeViewFiles.AddObjects (rootDirs);
 
-		private void SetupFileTree ()
-		{
-			// setup how to build tree
-			var fs = new FileSystem ();
-			var rootDirs = DriveInfo.GetDrives ().Select (d => fs.DirectoryInfo.New (d.RootDirectory.FullName));
-			treeViewFiles.TreeBuilder = new FileSystemTreeBuilder ();
-			treeViewFiles.AddObjects (rootDirs);
+		// Determines how to represent objects as strings on the screen
+		_treeViewFiles.AspectGetter = AspectGetter;
 
-			// Determines how to represent objects as strings on the screen
-			treeViewFiles.AspectGetter = AspectGetter;
+		_iconProvider.IsOpenGetter = _treeViewFiles.IsExpanded;
+	}
 
-			_iconProvider.IsOpenGetter = treeViewFiles.IsExpanded;
-		}
+	string AspectGetter (IFileSystemInfo f) => (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim ();
 
-		private string AspectGetter (IFileSystemInfo f)
-		{
-			return (_iconProvider.GetIconWithOptionalSpace (f) + f.Name).Trim ();
-		}
+	void ShowLines ()
+	{
+		_miShowLines.Checked = !_miShowLines.Checked;
 
-		private void ShowLines ()
-		{
-			miShowLines.Checked = !miShowLines.Checked;
+		_treeViewFiles.Style.ShowBranchLines = (bool)_miShowLines.Checked;
+		_treeViewFiles.SetNeedsDisplay ();
+	}
 
-			treeViewFiles.Style.ShowBranchLines = (bool)miShowLines.Checked;
-			treeViewFiles.SetNeedsDisplay ();
-		}
+	void SetExpandableSymbols (Rune expand, Rune? collapse)
+	{
+		_miPlusMinus.Checked = expand.Value == '+';
+		_miArrowSymbols.Checked = expand.Value == '>';
+		_miNoSymbols.Checked = expand.Value == default;
 
-		private void SetExpandableSymbols (Rune expand, Rune? collapse)
-		{
-			_miPlusMinus.Checked = expand.Value == '+';
-			_miArrowSymbols.Checked = expand.Value == '>';
-			_miNoSymbols.Checked = expand.Value == default;
+		_treeViewFiles.Style.ExpandableSymbol = expand;
+		_treeViewFiles.Style.CollapseableSymbol = collapse;
+		_treeViewFiles.SetNeedsDisplay ();
+	}
 
-			treeViewFiles.Style.ExpandableSymbol = expand;
-			treeViewFiles.Style.CollapseableSymbol = collapse;
-			treeViewFiles.SetNeedsDisplay ();
-		}
-		private void ShowColoredExpandableSymbols ()
-		{
-			_miColoredSymbols.Checked = !_miColoredSymbols.Checked;
+	void ShowColoredExpandableSymbols ()
+	{
+		_miColoredSymbols.Checked = !_miColoredSymbols.Checked;
 
-			treeViewFiles.Style.ColorExpandSymbol = (bool)_miColoredSymbols.Checked;
-			treeViewFiles.SetNeedsDisplay ();
-		}
-		private void InvertExpandableSymbols ()
-		{
-			_miInvertSymbols.Checked = !_miInvertSymbols.Checked;
+		_treeViewFiles.Style.ColorExpandSymbol = (bool)_miColoredSymbols.Checked;
+		_treeViewFiles.SetNeedsDisplay ();
+	}
 
-			treeViewFiles.Style.InvertExpandSymbolColors = (bool)_miInvertSymbols.Checked;
-			treeViewFiles.SetNeedsDisplay ();
-		}
+	void InvertExpandableSymbols ()
+	{
+		_miInvertSymbols.Checked = !_miInvertSymbols.Checked;
 
-		private void SetFullName ()
-		{
-			_miFullPaths.Checked = !_miFullPaths.Checked;
+		_treeViewFiles.Style.InvertExpandSymbolColors = (bool)_miInvertSymbols.Checked;
+		_treeViewFiles.SetNeedsDisplay ();
+	}
 
-			if (_miFullPaths.Checked == true) {
-				treeViewFiles.AspectGetter = (f) => f.FullName;
-			} else {
-				treeViewFiles.AspectGetter = (f) => f.Name;
-			}
-			treeViewFiles.SetNeedsDisplay ();
-		}
+	void SetFullName ()
+	{
+		_miFullPaths.Checked = !_miFullPaths.Checked;
 
-		private void SetLeaveLastRow ()
-		{
-			_miLeaveLastRow.Checked = !_miLeaveLastRow.Checked;
-			treeViewFiles.Style.LeaveLastRow = (bool)_miLeaveLastRow.Checked;
-		}
-		private void SetCursor ()
-		{
-			_miCursor.Checked = !_miCursor.Checked;
-			treeViewFiles.DesiredCursorVisibility = _miCursor.Checked == true ? CursorVisibility.Default : CursorVisibility.Invisible;
-		}
-		private void SetMultiSelect ()
-		{
-			_miMultiSelect.Checked = !_miMultiSelect.Checked;
-			treeViewFiles.MultiSelect = (bool)_miMultiSelect.Checked;
+		if (_miFullPaths.Checked == true) {
+			_treeViewFiles.AspectGetter = (f) => f.FullName;
+		} else {
+			_treeViewFiles.AspectGetter = (f) => f.Name;
 		}
+		_treeViewFiles.SetNeedsDisplay ();
+	}
 
-		private void SetCustomColors ()
-		{
-			var hidden = new ColorScheme {
-				Focus = new Terminal.Gui.Attribute (Color.BrightRed, treeViewFiles.ColorScheme.Focus.Background),
-				Normal = new Terminal.Gui.Attribute (Color.BrightYellow, treeViewFiles.ColorScheme.Normal.Background),
-			};
+	void SetLeaveLastRow ()
+	{
+		_miLeaveLastRow.Checked = !_miLeaveLastRow.Checked;
+		_treeViewFiles.Style.LeaveLastRow = (bool)_miLeaveLastRow.Checked;
+	}
 
-			_miCustomColors.Checked = !_miCustomColors.Checked;
+	void SetCursor ()
+	{
+		_miCursor.Checked = !_miCursor.Checked;
+		_treeViewFiles.DesiredCursorVisibility = _miCursor.Checked == true ? CursorVisibility.Default : CursorVisibility.Invisible;
+	}
 
-			if (_miCustomColors.Checked == true) {
-				treeViewFiles.ColorGetter = (m) => {
-					if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
-					if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) return hidden;
-					return null;
-				};
-			} else {
-				treeViewFiles.ColorGetter = null;
-			}
-			treeViewFiles.SetNeedsDisplay ();
-		}
+	void SetMultiSelect ()
+	{
+		_miMultiSelect.Checked = !_miMultiSelect.Checked;
+		_treeViewFiles.MultiSelect = (bool)_miMultiSelect.Checked;
+	}
 
-		private void SetCheckHighlightModelTextOnly ()
-		{
-			treeViewFiles.Style.HighlightModelTextOnly = !treeViewFiles.Style.HighlightModelTextOnly;
-			_miHighlightModelTextOnly.Checked = treeViewFiles.Style.HighlightModelTextOnly;
-			treeViewFiles.SetNeedsDisplay ();
-		}
+	void SetCustomColors ()
+	{
+		var hidden = new ColorScheme {
+			Focus = new Attribute (Color.BrightRed, _treeViewFiles.ColorScheme.Focus.Background),
+			Normal = new Attribute (Color.BrightYellow, _treeViewFiles.ColorScheme.Normal.Background)
+		};
 
-		private void Quit ()
-		{
-			Application.RequestStop ();
+		_miCustomColors.Checked = !_miCustomColors.Checked;
+
+		if (_miCustomColors.Checked == true) {
+			_treeViewFiles.ColorGetter = (m) => {
+				if (m is IDirectoryInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) {
+					return hidden;
+				}
+				if (m is IFileInfo && m.Attributes.HasFlag (FileAttributes.Hidden)) {
+					return hidden;
+				}
+				return null;
+			};
+		} else {
+			_treeViewFiles.ColorGetter = null;
 		}
+		_treeViewFiles.SetNeedsDisplay ();
 	}
-}
+
+	void SetCheckHighlightModelTextOnly ()
+	{
+		_treeViewFiles.Style.HighlightModelTextOnly = !_treeViewFiles.Style.HighlightModelTextOnly;
+		_miHighlightModelTextOnly.Checked = _treeViewFiles.Style.HighlightModelTextOnly;
+		_treeViewFiles.SetNeedsDisplay ();
+	}
+
+	void Quit () => Application.RequestStop ();
+}

+ 55 - 69
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -5,12 +5,12 @@ using System.Threading.Tasks;
 using Terminal.Gui;
 using Terminal.Gui.ConsoleDrivers;
 
-namespace UICatalog.Scenarios; 
+namespace UICatalog.Scenarios;
 
 [ScenarioMetadata ("VkeyPacketSimulator", "Simulates the Virtual Key Packet")]
 [ScenarioCategory ("Mouse and Keyboard")]
 public class VkeyPacketSimulator : Scenario {
-	List<int> _keyboardStrokes = new ();
+	List<KeyCode> _keyboardStrokes = new ();
 	bool _outputStarted = false;
 	bool _wasUnknown = false;
 	static ManualResetEventSlim _stopOutput = new (false);
@@ -94,54 +94,55 @@ public class VkeyPacketSimulator : Scenario {
 
 		// Detect unknown keys and reject them.
 		tvOutput.KeyDown += (s, e) => {
-			//System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.Key}");
-			//e.Handled = true;
-			if (e == Key.Empty) {
+			//System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.KeyCode}");
+			if (e.NoAlt.NoCtrl.NoShift == KeyCode.Null) {
 				_wasUnknown = true;
+				e.Handled = true;
+				return;
 			}
-		};
 
-		tvOutput.KeyUp += (s, e) => {
 			//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
-			if (_outputStarted && _keyboardStrokes.Count > 0) {
-				//// TODO: Tig: I don't understand what this is trying to do
-				//if (!tvOutput.ProcessKeyDown (e)) {
-				//	Application.Invoke (() => {
-				//		MessageBox.Query ("Keys", $"'{KeyEventArgs.ToString (e.ConsoleDriverKey, MenuBar.ShortcutDelimiter)}' pressed!", "Ok");
-				//	});
-				//}
-				e.Handled = true;
-				_stopOutput.Set ();
+			if (_outputStarted) {
+				// If the key wasn't handled by the TextView will popup a Dialog with the keys pressed.
+				var handled = tvOutput.OnInvokingKeyBindings (e);
+				if (handled == null || handled == false) {
+					if (!tvOutput.OnProcessKeyDown (e)) {
+						Application.Invoke (() => MessageBox.Query ("Keys", $"'{Key.ToString (e.KeyCode, MenuBar.ShortcutDelimiter)}' pressed!", "Ok"));
+					}
+				}
 			}
-			//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
+			e.Handled = true;
+			_stopOutput.Set ();
 		};
 
 		Win.Add (tvOutput);
 
-		Key unknownChar = null;
+		tvInput.KeyDown += (s, e) => {
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyDown: {e.KeyCode.Key}");
+			if (e.KeyCode == Key.Empty) {
+				_wasUnknown = true;
+				e.Handled = true;
+			} else {
+				_wasUnknown = false;
+			}
+		};
 
-		tvInput.KeyUp += (s, e) => {
-			//System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.Key}");
-			//var ke = e;
+		tvInput.InvokingKeyBindings += (s, e) => {
+			var ev = e;
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyPress: {ev}");
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
 
-			if (e == Key.Empty) {
+			if (!e.IsValid) {
 				_wasUnknown = true;
 				e.Handled = true;
 				return;
 			}
-			if (_wasUnknown && _keyboardStrokes.Count == 1) {
-				_wasUnknown = false;
-			} else if (_wasUnknown && char.IsLetter ((char)e.KeyCode)) {
-				_wasUnknown = false;
-			}
-			if (_keyboardStrokes.Count == 0) {
-				AddKeyboardStrokes (e);
-			} else {
-				_keyboardStrokes.Insert (0, 0);
-			}
-			if (_wasUnknown && (int)e.KeyCode - (int)(e.KeyCode & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask)) != 0) {
-				unknownChar = e;
-			}
+
+			_keyboardStrokes.Add (e.KeyCode);
+		};
+
+		tvInput.KeyUp += (s, e) => {
+			//System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.Key}");
 			e.Handled = true;
 			if (!_wasUnknown && _keyboardStrokes.Count > 0) {
 				_outputStarted = true;
@@ -152,38 +153,31 @@ public class VkeyPacketSimulator : Scenario {
 				Task.Run (() => {
 					while (_outputStarted) {
 						try {
-							var mod = new ConsoleModifiers ();
-							if (e.KeyCode.HasFlag (KeyCode.ShiftMask)) {
-								mod |= ConsoleModifiers.Shift;
-							}
-							if (e.KeyCode.HasFlag (KeyCode.AltMask)) {
-								mod |= ConsoleModifiers.Alt;
-							}
-							if (e.KeyCode.HasFlag (KeyCode.CtrlMask)) {
-								mod |= ConsoleModifiers.Control;
-							}
-							for (int i = 0; i < _keyboardStrokes.Count; i++) {
-								var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey ((uint)_keyboardStrokes [i], mod, out _);
-								Application.Driver.SendKeys (consoleKeyInfo.KeyChar, ConsoleKey.Packet, mod.HasFlag (ConsoleModifiers.Shift),
-									mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control));
+							while (_keyboardStrokes.Count > 0) {
+								if (_keyboardStrokes [0] == KeyCode.Null) {
+									continue;
+								}
+								var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (_keyboardStrokes [0]);
+								var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
+								Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Shift),
+									consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Alt), consoleKeyInfo.Modifiers.HasFlag (ConsoleModifiers.Control));
+
+								_stopOutput.Wait ();
+								_stopOutput.Reset ();
+								_keyboardStrokes.RemoveAt (0);
+								Application.Invoke (() => {
+									tvOutput.ReadOnly = true;
+									tvInput.SetFocus ();
+								});
 							}
-							//}
+							_outputStarted = false;
+
 						} catch (Exception) {
 							Application.Invoke (() => {
 								MessageBox.ErrorQuery ("Error", "Couldn't send the keystrokes!", "Ok");
 								Application.RequestStop ();
 							});
 						}
-						_stopOutput.Wait ();
-						_stopOutput.Reset ();
-						_keyboardStrokes.RemoveAt (0);
-						if (_keyboardStrokes.Count == 0) {
-							_outputStarted = false;
-							Application.Invoke (() => {
-								tvOutput.ReadOnly = true;
-								tvInput.SetFocus ();
-							});
-						}
 					}
 					//System.Diagnostics.Debug.WriteLine ($"_outputStarted: {_outputStarted}");
 				});
@@ -214,12 +208,4 @@ public class VkeyPacketSimulator : Scenario {
 
 		Win.LayoutComplete += Win_LayoutComplete;
 	}
-
-	void AddKeyboardStrokes (Key e)
-	{
-		int keyChar = (int)e.KeyCode;
-		int mK = (int)(e.KeyCode & (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.ShiftMask));
-		keyChar &= ~mK;
-		_keyboardStrokes.Add (keyChar);
-	}
-}
+}

+ 111 - 83
UICatalog/UICatalog.cs

@@ -1,24 +1,23 @@
-global using CM = Terminal.Gui.ConfigurationManager;
 global using Attribute = Terminal.Gui.Attribute;
+global using CM = Terminal.Gui.ConfigurationManager;
 using System;
 using System.Collections.Generic;
+using System.CommandLine;
 using System.Diagnostics;
 using System.Globalization;
+using System.IO;
 using System.Linq;
+using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
 using Terminal.Gui;
-using System.IO;
-using System.Reflection;
-using System.Threading;
 using static Terminal.Gui.ConfigurationManager;
-using System.Text.Json.Serialization;
-using static Terminal.Gui.TableView;
-
 
 #nullable enable
 
-namespace UICatalog; 
+namespace UICatalog;
 
 /// <summary>
 /// UI Catalog is a comprehensive sample library for Terminal.Gui. It provides a simple UI for adding to the catalog of scenarios.
@@ -51,13 +50,20 @@ namespace UICatalog;
 /// </para>	
 /// </remarks>
 class UICatalogApp {
-	[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)] [JsonPropertyName ("UICatalog.StatusBar")]
+	[SerializableConfigurationProperty (Scope = typeof (AppScope), OmitClassName = true)]
+	[JsonPropertyName ("UICatalog.StatusBar")]
 	public static bool ShowStatusBar { get; set; } = true;
 
-	static readonly FileSystemWatcher _currentDirWatcher = new ();
-	static readonly FileSystemWatcher _homeDirWatcher = new ();
+	static readonly FileSystemWatcher _currentDirWatcher = new FileSystemWatcher ();
+	static readonly FileSystemWatcher _homeDirWatcher = new FileSystemWatcher ();
+
+	struct Options {
+		public string Driver;
+		public string Scenario;
+		/* etc. */
+	}
 
-	static void Main (string [] args)
+	static int Main (string [] args)
 	{
 		Console.OutputEncoding = Encoding.Default;
 
@@ -68,22 +74,68 @@ class UICatalogApp {
 		_scenarios = Scenario.GetScenarios ();
 		_categories = Scenario.GetAllCategories ();
 
-		if (args.Length > 0 && args.Contains ("-usc")) {
-			_useSystemConsole = true;
-			args = args.Where (val => val != "-usc").ToArray ();
+		// Process command line args
+		// "UICatalog [-driver <driver>] [scenario name]"
+		// If no driver is provided, the default driver is used.
+		var driverOption = new Option<string> (
+			"--driver",
+			"The ConsoleDriver to use."
+		).FromAmong (Application.GetDriverTypes ().Select (d => d.Name).ToArray ());
+		driverOption.AddAlias ("-d");
+		driverOption.AddAlias ("--d");
+
+		var scenarioArgument = new Argument<string> (
+			"scenario",
+			description: "The name of the scenario to run.",
+			getDefaultValue: () => "none"
+		).FromAmong (_scenarios.Select (s => s.GetName ()).Append ("none").ToArray ());
+
+		var rootCommand = new RootCommand (description: "A comprehensive sample library for Terminal.Gui") {
+			scenarioArgument,
+			driverOption
+		};
+
+		rootCommand.SetHandler ((context) => {
+			Options options = new Options () {
+				Driver = context.ParseResult.GetValueForOption (driverOption),
+				Scenario = context.ParseResult.GetValueForArgument (scenarioArgument),
+				/* etc. */
+			};
+			context.ExitCode = CommandWrapper (options);
+		});
+
+		return rootCommand.Invoke (args);
+	}
+
+	// See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery
+	static int CommandWrapper (Options options)
+	{
+		try {
+			UICatalogMain (options);
+		} catch (Exception e) {
+			Console.WriteLine (e.ToString());
+			return 1;
 		}
+		return 0;
+	}
 
+	static void UICatalogMain (Options options)
+	{
 		StartConfigFileWatcher ();
 
+		// By setting _forceDriver we ensure that if the user has specified a driver on the command line, it will be used
+		// regardless of what's in a config file.
+		Application.ForceDriver = _forceDriver = options.Driver;
+
 		// If a Scenario name has been provided on the commandline
 		// run it and exit when done.
-		if (args.Length > 0) {
+		if (options.Scenario != "none") {
 			_topLevelColorScheme = "Base";
 
-			int item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase));
+			int item = _scenarios!.FindIndex (s => s.GetName ().Equals (options.Scenario, StringComparison.OrdinalIgnoreCase));
 			_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ())!;
-			Application.UseSystemConsole = _useSystemConsole;
-			Application.Init ();
+
+			Application.Init (driverName: _forceDriver);
 			_selectedScenario.Theme = _cachedTheme;
 			_selectedScenario.TopLevelColorScheme = _topLevelColorScheme;
 			_selectedScenario.Init ();
@@ -148,14 +200,14 @@ class UICatalogApp {
 		// Setup a file system watcher for `./.tui/`
 		_currentDirWatcher.NotifyFilter = NotifyFilters.LastWrite;
 
-		var assemblyLocation = Assembly.GetExecutingAssembly ().Location;
+		string assemblyLocation = Assembly.GetExecutingAssembly ().Location;
 		string tuiDir;
 
 		if (!string.IsNullOrEmpty (assemblyLocation)) {
 			var assemblyFile = new FileInfo (assemblyLocation);
 			tuiDir = Path.Combine (assemblyFile.Directory!.FullName, ".tui");
 		} else {
-			tuiDir = Path.Combine (System.AppContext.BaseDirectory, ".tui");
+			tuiDir = Path.Combine (AppContext.BaseDirectory, ".tui");
 		}
 
 
@@ -206,11 +258,14 @@ class UICatalogApp {
 	/// <returns></returns>
 	static Scenario RunUICatalogTopLevel ()
 	{
-		Application.UseSystemConsole = _useSystemConsole;
 
 		// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
 		// a Scenario was selected. Otherwise, the user wants to quit UI Catalog.
-		Application.Init ();
+
+		// If the user specified a driver on the command line then use it,
+		// ignoring Config files.
+
+		Application.Init (driverName: _forceDriver);
 
 		if (_cachedTheme is null) {
 			_cachedTheme = Themes?.Theme;
@@ -240,7 +295,7 @@ class UICatalogApp {
 	// If set, holds the scenario the user selected
 	static Scenario? _selectedScenario = null;
 
-	static bool _useSystemConsole = false;
+	static string _forceDriver = string.Empty;
 	static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 	static bool _isFirstRunning = true;
 	static string _topLevelColorScheme = string.Empty;
@@ -264,7 +319,7 @@ class UICatalogApp {
 		// TableView works. There's no real reason not to use ListView. Because we use TableView, and TableView
 		// doesn't (currently) have CollectionNavigator support built in, we implement it here, within the app.
 		public TableView ScenarioList;
-		CollectionNavigator _scenarioCollectionNav = new ();
+		CollectionNavigator _scenarioCollectionNav = new CollectionNavigator ();
 
 		public StatusItem DriverName;
 		public StatusItem OS;
@@ -274,16 +329,15 @@ class UICatalogApp {
 			_themeMenuItems = CreateThemeMenuItems ();
 			_themeMenuBarItem = new MenuBarItem ("_Themes", _themeMenuItems);
 			MenuBar = new MenuBar (new MenuBarItem [] {
-				new ("_File", new MenuItem [] {
-					new ("_Quit", "Quit UI Catalog", RequestStop, null, null)
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_Quit", "Quit UI Catalog", RequestStop, null, null)
 				}),
 				_themeMenuBarItem,
-				new ("Diag_nostics", CreateDiagnosticMenuItems ()),
-				new ("_Help", new MenuItem [] {
-					new ("_Documentation", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"), null, null, (KeyCode)Key.F1),
-					new ("_README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, (KeyCode)Key.F2),
-					new ("_About...",
-						"About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString (), 0, false, "_Ok"), null, null, (KeyCode)Key.A.WithCtrl)
+				new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems ()),
+				new MenuBarItem ("_Help", new MenuItem [] {
+					new MenuItem ("_Documentation", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.GuiV2Docs"), null, null, (KeyCode)Key.F1),
+					new MenuItem ("_README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, (KeyCode)Key.F2),
+					new MenuItem ("_About...", "About UI Catalog", () => MessageBox.Query ("About UI Catalog", _aboutMessage!.ToString (), 0, false, "_Ok"), null, null, (KeyCode)Key.A.WithCtrl)
 				})
 			});
 
@@ -295,7 +349,7 @@ class UICatalogApp {
 			};
 
 			StatusBar.Items = new StatusItem [] {
-				new (Application.QuitKey, $"~{Application.QuitKey} to quit", () => {
+				new StatusItem (Application.QuitKey, $"~{Application.QuitKey} to quit", () => {
 					if (_selectedScenario is null) {
 						// This causes GetScenarioToRun to return null
 						_selectedScenario = null;
@@ -304,7 +358,7 @@ class UICatalogApp {
 						_selectedScenario.RequestStop ();
 					}
 				}),
-				new (Key.F10, "~F10~ Status Bar", () => {
+				new StatusItem (Key.F10, "~F10~ Status Bar", () => {
 					StatusBar.Visible = !StatusBar.Visible;
 					//ContentPane!.Height = Dim.Fill(StatusBar.Visible ? 1 : 0);
 					LayoutSubviews ();
@@ -390,8 +444,8 @@ class UICatalogApp {
 			ScenarioList.CellActivated += ScenarioView_OpenSelectedItem;
 
 			// TableView typically is a grid where nav keys are biased for moving left/right.
-			ScenarioList.KeyBindings.Add (Key.Home, Command.TopHome);
-			ScenarioList.KeyBindings.Add (Key.End, Command.BottomEnd);
+			ScenarioList.KeyBindings.Add (Key.Home, Terminal.Gui.Command.TopHome);
+			ScenarioList.KeyBindings.Add (Key.End, Terminal.Gui.Command.BottomEnd);
 
 			// Ideally, TableView.MultiSelect = false would turn off any keybindings for
 			// multi-select options. But it currently does not. UI Catalog uses Ctrl-A for
@@ -453,10 +507,7 @@ class UICatalogApp {
 			Unloaded -= UnloadedHandler;
 		}
 
-		void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a)
-		{
-			ConfigChanged ();
-		}
+		void ConfigAppliedHandler (object? sender, ConfigurationManagerEventArgs? a) => ConfigChanged ();
 
 		/// <summary>
 		/// Launches the selected scenario, setting the global _selectedScenario
@@ -516,7 +567,7 @@ class UICatalogApp {
 			miIsMenuBorderDisabled = new MenuItem {
 				Title = "Disable Menu _Border"
 			};
-			miIsMenuBorderDisabled.Shortcut = (KeyCode)((Key)miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl;
+			miIsMenuBorderDisabled.Shortcut = (KeyCode)new Key (miIsMenuBorderDisabled!.Title!.Substring (14, 1) [0]).WithAlt.WithCtrl;
 			miIsMenuBorderDisabled.CheckType |= MenuItemCheckStyle.Checked;
 			miIsMenuBorderDisabled.Action += () => {
 				miIsMenuBorderDisabled.Checked = (bool)!miIsMenuBorderDisabled.Checked!;
@@ -553,7 +604,7 @@ class UICatalogApp {
 			miIsMouseDisabled = new MenuItem {
 				Title = "_Disable Mouse"
 			};
-			miIsMouseDisabled.Shortcut = (KeyCode)((Key)miIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl;
+			miIsMouseDisabled.Shortcut = (KeyCode)new Key (miIsMouseDisabled!.Title!.Substring (1, 1) [0]).WithAlt.WithCtrl;
 			miIsMouseDisabled.CheckType |= MenuItemCheckStyle.Checked;
 			miIsMouseDisabled.Action += () => {
 				miIsMouseDisabled.Checked = Application.IsMouseDisabled = (bool)!miIsMouseDisabled.Checked!;
@@ -592,7 +643,7 @@ class UICatalogApp {
 			foreach (Enum diag in Enum.GetValues (_diagnosticFlags.GetType ())) {
 				var item = new MenuItem {
 					Title = GetDiagnosticsTitle (diag),
-					Shortcut = (KeyCode)((Key)index.ToString () [0]).WithAlt
+					Shortcut = (KeyCode)new Key (index.ToString () [0]).WithAlt
 				};
 				index++;
 				item.CheckType |= MenuItemCheckStyle.Checked;
@@ -633,24 +684,18 @@ class UICatalogApp {
 			}
 			return menuItems.ToArray ();
 
-			string GetDiagnosticsTitle (Enum diag)
-			{
-				return Enum.GetName (_diagnosticFlags.GetType (), diag) switch {
-					"Off" => OFF,
-					"FrameRuler" => FRAME_RULER,
-					"FramePadding" => FRAME_PADDING,
-					_ => ""
-				};
-			}
+			string GetDiagnosticsTitle (Enum diag) => Enum.GetName (_diagnosticFlags.GetType (), diag) switch {
+				"Off" => OFF,
+				"FrameRuler" => FRAME_RULER,
+				"FramePadding" => FRAME_PADDING,
+				_ => ""
+			};
 
-			Enum GetDiagnosticsEnumValue (string title)
-			{
-				return title switch {
-					FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler,
-					FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding,
-					_ => null!
-				};
-			}
+			Enum GetDiagnosticsEnumValue (string title) => title switch {
+				FRAME_RULER => ConsoleDriver.DiagnosticFlags.FrameRuler,
+				FRAME_PADDING => ConsoleDriver.DiagnosticFlags.FramePadding,
+				_ => null!
+			};
 
 			void SetDiagnosticsFlag (Enum diag, bool add)
 			{
@@ -685,7 +730,7 @@ class UICatalogApp {
 			foreach (var theme in Themes!) {
 				var item = new MenuItem {
 					Title = $"_{theme.Key}",
-					Shortcut = (KeyCode)((Key)((int)KeyCode.D1 + schemeCount++)).WithCtrl
+					Shortcut = (KeyCode)new Key ((KeyCode)((uint)KeyCode.D1 + schemeCount++)).WithCtrl
 				};
 				item.CheckType |= MenuItemCheckStyle.Checked;
 				item.Checked = theme.Key == _cachedTheme; // CM.Themes.Theme;
@@ -723,13 +768,13 @@ class UICatalogApp {
 
 		public void ConfigChanged ()
 		{
-			miForce16Colors!.Checked = Application.Force16Colors;
-
 			if (_topLevelColorScheme == null || !Colors.ColorSchemes.ContainsKey (_topLevelColorScheme)) {
 				_topLevelColorScheme = "Base";
 			}
 
-			_themeMenuItems = ((UICatalogTopLevel)Application.Top).CreateThemeMenuItems ();
+			_cachedTheme = Themes?.Theme;
+
+			_themeMenuItems = CreateThemeMenuItems ();
 			_themeMenuBarItem!.Children = _themeMenuItems;
 			foreach (var mi in _themeMenuItems!) {
 				if (mi is { Parent: null }) {
@@ -737,25 +782,8 @@ class UICatalogApp {
 				}
 			}
 
-			var checkedThemeMenu = _themeMenuItems?.Where (m => m?.Checked ?? false).FirstOrDefault ();
-			if (checkedThemeMenu != null) {
-				checkedThemeMenu.Checked = false;
-			}
-			checkedThemeMenu = _themeMenuItems?.Where (m => m != null && m.Title == Themes?.Theme).FirstOrDefault ();
-			if (checkedThemeMenu != null) {
-				Themes!.Theme = checkedThemeMenu.Title!;
-				checkedThemeMenu.Checked = true;
-			}
-
-			var schemeMenuItems = ((MenuBarItem)_themeMenuItems?.Where (i => i is MenuBarItem)!.FirstOrDefault ()!)!.Children;
-			foreach (var schemeMenuItem in schemeMenuItems) {
-				schemeMenuItem.Checked = (string)schemeMenuItem.Data == _topLevelColorScheme;
-			}
-
 			ColorScheme = Colors.ColorSchemes [_topLevelColorScheme];
 
-			//ContentPane.LineStyle = FrameView.DefaultBorderStyle;
-
 			MenuBar.Menus [0].Children [0].Shortcut = (KeyCode)Application.QuitKey;
 			StatusBar.Items [0].Shortcut = Application.QuitKey;
 			StatusBar.Items [0].Title = $"~{Application.QuitKey} to quit";
@@ -763,7 +791,7 @@ class UICatalogApp {
 			miIsMouseDisabled!.Checked = Application.IsMouseDisabled;
 
 			int height = ShowStatusBar ? 1 : 0; // + (MenuBar.Visible ? 1 : 0);
-			//ContentPane.Height = Dim.Fill (height);
+							    //ContentPane.Height = Dim.Fill (height);
 
 			StatusBar.Visible = ShowStatusBar;
 

+ 1 - 0
UICatalog/UICatalog.csproj

@@ -32,6 +32,7 @@
     <PackageReference Include="SixLabors.ImageSharp" Version="3.1.1" />
     <PackageReference Include="CsvHelper" Version="30.0.1" />
     <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
+    <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />

+ 37 - 27
UnitTests/Application/ApplicationTests.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Diagnostics;
+using System.IO;
 using System.Linq;
 using System.Runtime.InteropServices;
 using System.Threading;
@@ -17,7 +18,9 @@ public class ApplicationTests {
 
 	public ApplicationTests (ITestOutputHelper output)
 	{
-		this._output = output;
+		_output = output;
+		ConsoleDriver.RunningUnitTests = true;
+
 #if DEBUG_IDISPOSABLE
 		Responder.Instances.Clear ();
 		RunState.Instances.Clear ();
@@ -52,10 +55,7 @@ public class ApplicationTests {
 		Assert.NotNull (SynchronizationContext.Current);
 	}
 
-	void Shutdown ()
-	{
-		Application.Shutdown ();
-	}
+	void Shutdown () => Application.Shutdown ();
 
 	[Fact]
 	public void Init_Shutdown_Cleans_Up ()
@@ -73,10 +73,10 @@ public class ApplicationTests {
 		// Verify state is back to initial
 		//Pre_Init_State ();
 #if DEBUG_IDISPOSABLE
-			// Validate there are no outstanding Responder-based instances 
-			// after a scenario was selected to run. This proves the main UI Catalog
-			// 'app' closed cleanly.
-			Assert.Empty (Responder.Instances);
+		// Validate there are no outstanding Responder-based instances 
+		// after a scenario was selected to run. This proves the main UI Catalog
+		// 'app' closed cleanly.
+		Assert.Empty (Responder.Instances);
 #endif
 	}
 
@@ -106,10 +106,7 @@ public class ApplicationTests {
 	}
 
 	class TestToplevel : Toplevel {
-		public TestToplevel ()
-		{
-			IsOverlappedContainer = false;
-		}
+		public TestToplevel () => IsOverlappedContainer = false;
 	}
 
 	[Fact]
@@ -122,6 +119,21 @@ public class ApplicationTests {
 		Shutdown ();
 	}
 
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driverName: driverType.Name);
+		Assert.NotNull (Application.Driver);
+		Assert.Equal (driverType, Application.Driver.GetType ());
+		Shutdown ();
+	}
+
 	[Fact]
 	public void Init_Begin_End_Cleans_Up ()
 	{
@@ -140,7 +152,7 @@ public class ApplicationTests {
 		};
 		Application.NotifyNewRunState += NewRunStateFn;
 
-		Toplevel topLevel = new Toplevel ();
+		var topLevel = new Toplevel ();
 		var rs = Application.Begin (topLevel);
 		Assert.NotNull (rs);
 		Assert.NotNull (runstate);
@@ -225,7 +237,6 @@ public class ApplicationTests {
 	}
 
 	#region RunTests
-
 	[Fact]
 	public void Run_T_After_InitWithDriver_with_TopLevel_Throws ()
 	{
@@ -249,7 +260,7 @@ public class ApplicationTests {
 		Init ();
 
 		// Run<Toplevel> when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel)
-		Assert.Throws<ArgumentException> (() => Application.Run<Toplevel> (errorHandler: null, new FakeDriver ()));
+		Assert.Throws<ArgumentException> (() => Application.Run<Toplevel> (null, new FakeDriver ()));
 
 		Shutdown ();
 
@@ -281,7 +292,7 @@ public class ApplicationTests {
 	[Fact]
 	public void Run_T_After_InitNullDriver_with_TestTopLevel_Throws ()
 	{
-		Application._forceFakeConsole = true;
+		Application.ForceDriver = "FakeDriver";
 
 		Application.Init (null);
 		Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ());
@@ -324,7 +335,7 @@ public class ApplicationTests {
 	[Fact]
 	public void Run_T_NoInit_DoesNotThrow ()
 	{
-		Application._forceFakeConsole = true;
+		Application.ForceDriver = "FakeDriver";
 
 		Application.Iteration += (s, a) => {
 			Application.RequestStop ();
@@ -348,7 +359,7 @@ public class ApplicationTests {
 		};
 
 		// Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
-		Application.Run<TestToplevel> (errorHandler: null, new FakeDriver ());
+		Application.Run<TestToplevel> (null, new FakeDriver ());
 
 		Shutdown ();
 
@@ -410,7 +421,7 @@ public class ApplicationTests {
 	{
 		Init ();
 		var top = Application.Top;
-		var count = 0;
+		int count = 0;
 		top.Loaded += (s, e) => count++;
 		top.Ready += (s, e) => count++;
 		top.Unloaded += (s, e) => count++;
@@ -425,12 +436,12 @@ public class ApplicationTests {
 	public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty ()
 	{
 		Init ();
-		var count = 0;
+		int count = 0;
 		// Don't use Dialog here as it has more layout logic. Use Window instead.
 		Dialog d = null;
 		var top = Application.Top;
 		top.DrawContent += (s, a) => count++;
-		var iteration = -1;
+		int iteration = -1;
 		Application.Iteration += (s, a) => {
 			iteration++;
 			if (iteration == 0) {
@@ -439,7 +450,7 @@ public class ApplicationTests {
 				d.DrawContent += (s, a) => count++;
 				Application.Run (d);
 			} else if (iteration < 3) {
-				Application.OnMouseEvent (new (new () { X = 0, Y = 0, Flags = MouseFlags.ReportMousePosition }));
+				Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent { X = 0, Y = 0, Flags = MouseFlags.ReportMousePosition }));
 				Assert.False (top.NeedsDisplay);
 				Assert.False (top.SubViewNeedsDisplay);
 				Assert.False (top.LayoutNeeded);
@@ -521,7 +532,6 @@ public class ApplicationTests {
 	}
 
 	// TODO: Add tests for Run that test errorHandler
-
 	#endregion
 
 	#region ShutdownTests
@@ -556,7 +566,7 @@ public class ApplicationTests {
 	}
 	#endregion
 
-	[Fact, AutoInitShutdown]
+	[Fact] [AutoInitShutdown]
 	public void Begin_Sets_Application_Top_To_Console_Size ()
 	{
 		Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame);
@@ -580,7 +590,7 @@ public class ApplicationTests {
 		var t4 = new Toplevel ();
 
 		// t1, t2, t3, d, t4
-		var iterations = 5;
+		int iterations = 5;
 
 		t1.Ready += (s, e) => {
 			Assert.Equal (t1, Application.Top);
@@ -667,7 +677,7 @@ public class ApplicationTests {
 		var rs = Application.Begin (top);
 		bool firstIteration = false;
 
-		var actionCalled = 0;
+		int actionCalled = 0;
 		Application.Invoke (() => { actionCalled++; });
 		Application.MainLoop.Running = true;
 		Application.RunIteration (ref rs, ref firstIteration);

+ 470 - 474
UnitTests/Configuration/ConfigurationMangerTests.cs

@@ -7,359 +7,357 @@ using System.Text.Json;
 using Xunit;
 using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui.ConfigurationTests {
-	public class ConfigurationManagerTests {
+namespace Terminal.Gui.ConfigurationTests; 
 
-		public static readonly JsonSerializerOptions _jsonOptions = new () {
-			Converters = {
-				new AttributeJsonConverter (),
-				new ColorJsonConverter (),
-				}
-		};
-
-		[Fact ()]
-		public void DeepMemberwiseCopyTest ()
-		{
-			// Value types
-			var stringDest = "Destination";
-			var stringSrc = "Source";
-			var stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
-			Assert.Equal (stringSrc, stringCopy);
-
-			stringDest = "Destination";
-			stringSrc = "Destination";
-			stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
-			Assert.Equal (stringSrc, stringCopy);
-
-			stringDest = "Destination";
-			stringSrc = null;
-			stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
-			Assert.Equal (stringSrc, stringCopy);
-
-			stringDest = "Destination";
-			stringSrc = string.Empty;
-			stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
-			Assert.Equal (stringSrc, stringCopy);
-
-			var boolDest = true;
-			var boolSrc = false;
-			var boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
-			Assert.Equal (boolSrc, boolCopy);
-
-			boolDest = false;
-			boolSrc = true;
-			boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
-			Assert.Equal (boolSrc, boolCopy);
-
-			boolDest = true;
-			boolSrc = true;
-			boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
-			Assert.Equal (boolSrc, boolCopy);
-
-			boolDest = false;
-			boolSrc = false;
-			boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
-			Assert.Equal (boolSrc, boolCopy);
-
-			// Structs
-			var attrDest = new Attribute (Color.Black);
-			var attrSrc = new Attribute (Color.White);
-			var attrCopy = DeepMemberwiseCopy (attrSrc, attrDest);
-			Assert.Equal (attrSrc, attrCopy);
-
-			// Classes
-			var colorschemeDest = new ColorScheme () { Disabled = new Attribute (Color.Black) };
-			var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (Color.White) };
-			var colorschemeCopy = DeepMemberwiseCopy (colorschemeSrc, colorschemeDest);
-			Assert.Equal (colorschemeSrc, colorschemeCopy);
-
-			// Dictionaries
-			var dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
-			var dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) } };
-			var dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
-			Assert.Equal (dictSrc, dictCopy);
-
-			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
-			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } };
-			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
-			Assert.Equal (dictSrc, dictCopy);
-
-			// src adds an item
-			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
-			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } };
-			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
-			Assert.Equal (2, dictCopy.Count);
-			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
-			Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
-
-			// src updates only one item
-			dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) }, { "Normal", new Attribute (Color.White) } };
-			dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) } };
-			dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
-			Assert.Equal (2, dictCopy.Count);
-			Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
-			Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
-		}
-
-		//[Fact ()]
-		//public void LoadFromJsonTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void ToJsonTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void UpdateConfigurationTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void UpdateConfigurationFromFileTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void SaveHardCodedDefaultsTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadGlobalFromLibraryResourceTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadGlobalFromAppDirectoryTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadGlobalFromHomeDirectoryTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadAppFromAppResourcesTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadAppFromAppDirectoryTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadAppFromHomeDirectoryTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		//[Fact ()]
-		//public void LoadTest ()
-		//{
-		//	Assert.True (false, "This test needs an implementation");
-		//}
-
-		/// <summary>
-		/// Save the `config.json` file; this can be used to update the file in `Terminal.Gui.Resources.config.json'.
-		/// </summary>
-		/// <remarks>
-		/// IMPORTANT: For the file generated to be valid, this must be the ONLY test run. Config Properties
-		/// are all static and thus can be overwritten by other tests.</remarks>
-		[Fact]
-		public void SaveDefaults ()
-		{
-			ConfigurationManager.Initialize ();
-			ConfigurationManager.Reset ();
-
-			// Get the hard coded settings
-			ConfigurationManager.GetHardCodedDefaults ();
-
-			// Serialize to a JSON string
-			string json = ConfigurationManager.ToJson ();
-
-			// Write the JSON string to the file 
-			File.WriteAllText ("config.json", json);
-		}
-
-		[Fact]
-		public void UseWithoutResetAsserts ()
-		{
-			ConfigurationManager.Initialize ();
-			Assert.Throws<InvalidOperationException> (() => _ = ConfigurationManager.Settings);
-		}
-
-		[Fact]
-		public void Reset_Resets ()
-		{
-			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
-			ConfigurationManager.Reset ();
-			Assert.NotEmpty (ConfigurationManager.Themes);
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
+public class ConfigurationManagerTests {
+	public static readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions {
+		Converters = {
+			new AttributeJsonConverter (),
+			new ColorJsonConverter ()
 		}
+	};
+
+	[Fact ()]
+	public void DeepMemberwiseCopyTest ()
+	{
+		// Value types
+		string stringDest = "Destination";
+		string stringSrc = "Source";
+		object stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
+		Assert.Equal (stringSrc, stringCopy);
+
+		stringDest = "Destination";
+		stringSrc = "Destination";
+		stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
+		Assert.Equal (stringSrc, stringCopy);
+
+		stringDest = "Destination";
+		stringSrc = null;
+		stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
+		Assert.Equal (stringSrc, stringCopy);
+
+		stringDest = "Destination";
+		stringSrc = string.Empty;
+		stringCopy = DeepMemberwiseCopy (stringSrc, stringDest);
+		Assert.Equal (stringSrc, stringCopy);
+
+		bool boolDest = true;
+		bool boolSrc = false;
+		object boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
+		Assert.Equal (boolSrc, boolCopy);
+
+		boolDest = false;
+		boolSrc = true;
+		boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
+		Assert.Equal (boolSrc, boolCopy);
+
+		boolDest = true;
+		boolSrc = true;
+		boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
+		Assert.Equal (boolSrc, boolCopy);
+
+		boolDest = false;
+		boolSrc = false;
+		boolCopy = DeepMemberwiseCopy (boolSrc, boolDest);
+		Assert.Equal (boolSrc, boolCopy);
+
+		// Structs
+		var attrDest = new Attribute (Color.Black);
+		var attrSrc = new Attribute (Color.White);
+		object attrCopy = DeepMemberwiseCopy (attrSrc, attrDest);
+		Assert.Equal (attrSrc, attrCopy);
+
+		// Classes
+		var colorschemeDest = new ColorScheme () { Disabled = new Attribute (Color.Black) };
+		var colorschemeSrc = new ColorScheme () { Disabled = new Attribute (Color.White) };
+		object colorschemeCopy = DeepMemberwiseCopy (colorschemeSrc, colorschemeDest);
+		Assert.Equal (colorschemeSrc, colorschemeCopy);
+
+		// Dictionaries
+		var dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
+		var dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) } };
+		var dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
+		Assert.Equal (dictSrc, dictCopy);
+
+		dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
+		dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } };
+		dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
+		Assert.Equal (dictSrc, dictCopy);
+
+		// src adds an item
+		dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) } };
+		dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) }, { "Normal", new Attribute (Color.Blue) } };
+		dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
+		Assert.Equal (2, dictCopy.Count);
+		Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
+		Assert.Equal (dictSrc ["Normal"], dictCopy ["Normal"]);
+
+		// src updates only one item
+		dictDest = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.Black) }, { "Normal", new Attribute (Color.White) } };
+		dictSrc = new Dictionary<string, Attribute> () { { "Disabled", new Attribute (Color.White) } };
+		dictCopy = (Dictionary<string, Attribute>)DeepMemberwiseCopy (dictSrc, dictDest);
+		Assert.Equal (2, dictCopy.Count);
+		Assert.Equal (dictSrc ["Disabled"], dictCopy ["Disabled"]);
+		Assert.Equal (dictDest ["Normal"], dictCopy ["Normal"]);
+	}
 
-		[Fact]
-		public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same ()
-		{
-			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
-			// arrange
-			ConfigurationManager.Reset ();
-			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
-			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
-			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
-			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings.Apply ();
-
-			// assert apply worked
-			Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
-			Assert.Equal (KeyCode.F, Application.AlternateForwardKey.KeyCode);
-			Assert.Equal (KeyCode.B, Application.AlternateBackwardKey.KeyCode);
-			Assert.True (Application.IsMouseDisabled);
+	//[Fact ()]
+	//public void LoadFromJsonTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void ToJsonTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void UpdateConfigurationTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void UpdateConfigurationFromFileTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void SaveHardCodedDefaultsTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadGlobalFromLibraryResourceTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadGlobalFromAppDirectoryTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadGlobalFromHomeDirectoryTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadAppFromAppResourcesTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadAppFromAppDirectoryTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadAppFromHomeDirectoryTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	//[Fact ()]
+	//public void LoadTest ()
+	//{
+	//	Assert.True (false, "This test needs an implementation");
+	//}
+
+	/// <summary>
+	/// Save the `config.json` file; this can be used to update the file in `Terminal.Gui.Resources.config.json'.
+	/// </summary>
+	/// <remarks>
+	/// IMPORTANT: For the file generated to be valid, this must be the ONLY test run. Config Properties
+	/// are all static and thus can be overwritten by other tests.</remarks>
+	[Fact]
+	public void SaveDefaults ()
+	{
+		Initialize ();
+		Reset ();
+
+		// Get the hard coded settings
+		GetHardCodedDefaults ();
+
+		// Serialize to a JSON string
+		string json = ToJson ();
+
+		// Write the JSON string to the file 
+		File.WriteAllText ("config.json", json);
+	}
 
-			//act
-			ConfigurationManager.Reset ();
+	[Fact]
+	public void UseWithoutResetAsserts ()
+	{
+		Initialize ();
+		Assert.Throws<InvalidOperationException> (() => _ = Settings);
+	}
 
-			// assert
-			Assert.NotEmpty (ConfigurationManager.Themes);
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
-			Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
-			Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey.KeyCode);
-			Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey.KeyCode);
-			Assert.False (Application.IsMouseDisabled);
-
-			// arrange
-			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
-			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
-			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
-			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-			ConfigurationManager.Settings.Apply ();
-
-			ConfigurationManager.Locations = ConfigLocations.DefaultOnly;
-
-			// act
-			ConfigurationManager.Reset ();
-			ConfigurationManager.Load ();
+	[Fact]
+	public void Reset_Resets ()
+	{
+		Locations = ConfigLocations.DefaultOnly;
+		Reset ();
+		Assert.NotEmpty (Themes);
+		Assert.Equal ("Default", Themes.Theme);
+	}
 
-			// assert
-			Assert.NotEmpty (ConfigurationManager.Themes);
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
-			Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
-			Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey.KeyCode);
-			Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey.KeyCode);
-			Assert.False (Application.IsMouseDisabled);
+	[Fact]
+	public void Reset_and_ResetLoadWithLibraryResourcesOnly_are_same ()
+	{
+		Locations = ConfigLocations.DefaultOnly;
+		// arrange
+		Reset ();
+		Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
+		Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
+		Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
+		Settings ["Application.IsMouseDisabled"].PropertyValue = true;
+		Settings.Apply ();
+
+		// assert apply worked
+		Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
+		Assert.Equal (KeyCode.F, Application.AlternateForwardKey.KeyCode);
+		Assert.Equal (KeyCode.B, Application.AlternateBackwardKey.KeyCode);
+		Assert.True (Application.IsMouseDisabled);
+
+		//act
+		Reset ();
+
+		// assert
+		Assert.NotEmpty (Themes);
+		Assert.Equal ("Default", Themes.Theme);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
+		Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey.KeyCode);
+		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey.KeyCode);
+		Assert.False (Application.IsMouseDisabled);
+
+		// arrange
+		Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
+		Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
+		Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
+		Settings ["Application.IsMouseDisabled"].PropertyValue = true;
+		Settings.Apply ();
+
+		Locations = ConfigLocations.DefaultOnly;
+
+		// act
+		Reset ();
+		Load ();
+
+		// assert
+		Assert.NotEmpty (Themes);
+		Assert.Equal ("Default", Themes.Theme);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
+		Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey.KeyCode);
+		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey.KeyCode);
+		Assert.False (Application.IsMouseDisabled);
 
-		}
+	}
 
-		[Fact]
-		public void TestConfigProperties ()
-		{
-			ConfigurationManager.Locations = ConfigLocations.All;
-			ConfigurationManager.Reset ();
-
-			Assert.NotEmpty (ConfigurationManager.Settings);
-			// test that all ConfigProperites have our attribute
-			Assert.All (ConfigurationManager.Settings, item => Assert.NotEmpty (item.Value.PropertyInfo.CustomAttributes.Where (a => a.AttributeType == typeof (SerializableConfigurationProperty))));
-			Assert.Empty (ConfigurationManager.Settings.Where (cp => cp.Value.PropertyInfo.GetCustomAttribute (typeof (SerializableConfigurationProperty)) == null));
-
-			// Application is a static class
-			PropertyInfo pi = typeof (Application).GetProperty ("QuitKey");
-			Assert.Equal (pi, ConfigurationManager.Settings ["Application.QuitKey"].PropertyInfo);
-
-			// FrameView is not a static class and DefaultBorderStyle is Scope.Scheme
-			pi = typeof (FrameView).GetProperty ("DefaultBorderStyle");
-			Assert.False (ConfigurationManager.Settings.ContainsKey ("FrameView.DefaultBorderStyle"));
-			Assert.True (ConfigurationManager.Themes ["Default"].ContainsKey ("FrameView.DefaultBorderStyle"));
-		}
+	[Fact]
+	public void TestConfigProperties ()
+	{
+		Locations = ConfigLocations.All;
+		Reset ();
+
+		Assert.NotEmpty (Settings);
+		// test that all ConfigProperites have our attribute
+		Assert.All (Settings, item => Assert.NotEmpty (item.Value.PropertyInfo.CustomAttributes.Where (a => a.AttributeType == typeof (SerializableConfigurationProperty))));
+		Assert.Empty (Settings.Where (cp => cp.Value.PropertyInfo.GetCustomAttribute (typeof (SerializableConfigurationProperty)) == null));
+
+		// Application is a static class
+		var pi = typeof (Application).GetProperty ("QuitKey");
+		Assert.Equal (pi, Settings ["Application.QuitKey"].PropertyInfo);
+
+		// FrameView is not a static class and DefaultBorderStyle is Scope.Scheme
+		pi = typeof (FrameView).GetProperty ("DefaultBorderStyle");
+		Assert.False (Settings.ContainsKey ("FrameView.DefaultBorderStyle"));
+		Assert.True (Themes ["Default"].ContainsKey ("FrameView.DefaultBorderStyle"));
+	}
 
-		[Fact]
-		public void TestConfigPropertyOmitClassName ()
-		{
-			// Color.ColorShemes is serialzied as "ColorSchemes", not "Colors.ColorSchemes"
-			PropertyInfo pi = typeof (Colors).GetProperty ("ColorSchemes");
-			var scp = ((SerializableConfigurationProperty)pi.GetCustomAttribute (typeof (SerializableConfigurationProperty)));
-			Assert.True (scp.Scope == typeof (ThemeScope));
-			Assert.True (scp.OmitClassName);
-
-			ConfigurationManager.Reset ();
-			Assert.Equal (pi, ConfigurationManager.Themes ["Default"] ["ColorSchemes"].PropertyInfo);
-		}
+	[Fact]
+	public void TestConfigPropertyOmitClassName ()
+	{
+		// Color.ColorShemes is serialzied as "ColorSchemes", not "Colors.ColorSchemes"
+		var pi = typeof (Colors).GetProperty ("ColorSchemes");
+		var scp = (SerializableConfigurationProperty)pi.GetCustomAttribute (typeof (SerializableConfigurationProperty));
+		Assert.True (scp.Scope == typeof (ThemeScope));
+		Assert.True (scp.OmitClassName);
+
+		Reset ();
+		Assert.Equal (pi, Themes ["Default"] ["ColorSchemes"].PropertyInfo);
+	}
 
-		[Fact, AutoInitShutdown]
-		public void TestConfigurationManagerToJson ()
-		{
-			ConfigurationManager.Reset ();
-			ConfigurationManager.GetHardCodedDefaults ();
-			var stream = ConfigurationManager.ToStream ();
+	[Fact] [AutoInitShutdown]
+	public void TestConfigurationManagerToJson ()
+	{
+		Reset ();
+		GetHardCodedDefaults ();
+		var stream = ToStream ();
 
-			ConfigurationManager.Settings.Update (stream, "TestConfigurationManagerToJson");
-		}
+		Settings.Update (stream, "TestConfigurationManagerToJson");
+	}
 
-		[Fact, AutoInitShutdown (configLocation: ConfigLocations.None)]
-		public void TestConfigurationManagerInitDriver_NoLocations ()
-		{
+	[Fact] [AutoInitShutdown (configLocation: ConfigLocations.None)]
+	public void TestConfigurationManagerInitDriver_NoLocations ()
+	{
 
 
-		}
+	}
 
-		[Fact, AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
-		public void TestConfigurationManagerInitDriver ()
-		{
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
-			Assert.True (ConfigurationManager.Themes.ContainsKey ("Default"));
+	[Fact] [AutoInitShutdown (configLocation: ConfigLocations.DefaultOnly)]
+	public void TestConfigurationManagerInitDriver ()
+	{
+		Assert.Equal ("Default", Themes.Theme);
+		Assert.True (Themes.ContainsKey ("Default"));
 
-			Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
 
-			Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
-			Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
+		Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
+		Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
 
-			// Change Base
-			var json = ConfigurationManager.ToStream ();
+		// Change Base
+		var json = ToStream ();
 
-			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerInitDriver");
+		Settings.Update (json, "TestConfigurationManagerInitDriver");
 
-			var colorSchemes = ((Dictionary<string, ColorScheme>)ConfigurationManager.Themes [ConfigurationManager.Themes.Theme] ["ColorSchemes"].PropertyValue);
-			Assert.Equal (Colors.Base, colorSchemes ["Base"]);
-			Assert.Equal (Colors.TopLevel, colorSchemes ["TopLevel"]);
-			Assert.Equal (Colors.Error, colorSchemes ["Error"]);
-			Assert.Equal (Colors.Dialog, colorSchemes ["Dialog"]);
-			Assert.Equal (Colors.Menu, colorSchemes ["Menu"]);
+		var colorSchemes = (Dictionary<string, ColorScheme>)Themes [Themes.Theme] ["ColorSchemes"].PropertyValue;
+		Assert.Equal (Colors.Base, colorSchemes ["Base"]);
+		Assert.Equal (Colors.TopLevel, colorSchemes ["TopLevel"]);
+		Assert.Equal (Colors.Error, colorSchemes ["Error"]);
+		Assert.Equal (Colors.Dialog, colorSchemes ["Dialog"]);
+		Assert.Equal (Colors.Menu, colorSchemes ["Menu"]);
 
-			Colors.Base = colorSchemes ["Base"];
-			Colors.TopLevel = colorSchemes ["TopLevel"];
-			Colors.Error = colorSchemes ["Error"];
-			Colors.Dialog = colorSchemes ["Dialog"];
-			Colors.Menu = colorSchemes ["Menu"];
+		Colors.Base = colorSchemes ["Base"];
+		Colors.TopLevel = colorSchemes ["TopLevel"];
+		Colors.Error = colorSchemes ["Error"];
+		Colors.Dialog = colorSchemes ["Dialog"];
+		Colors.Menu = colorSchemes ["Menu"];
 
-			Assert.Equal (colorSchemes ["Base"], Colors.Base);
-			Assert.Equal (colorSchemes ["TopLevel"], Colors.TopLevel);
-			Assert.Equal (colorSchemes ["Error"], Colors.Error);
-			Assert.Equal (colorSchemes ["Dialog"], Colors.Dialog);
-			Assert.Equal (colorSchemes ["Menu"], Colors.Menu);
-		}
+		Assert.Equal (colorSchemes ["Base"], Colors.Base);
+		Assert.Equal (colorSchemes ["TopLevel"], Colors.TopLevel);
+		Assert.Equal (colorSchemes ["Error"], Colors.Error);
+		Assert.Equal (colorSchemes ["Dialog"], Colors.Dialog);
+		Assert.Equal (colorSchemes ["Menu"], Colors.Menu);
+	}
 
-		[Fact]
-		public void TestConfigurationManagerUpdateFromJson ()
-		{
-			// Arrange
-			string json = @"
+	[Fact]
+	public void TestConfigurationManagerUpdateFromJson ()
+	{
+		// Arrange
+		string json = @"
 {
   ""$schema"": ""https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json"",
-  ""Application.QuitKey"": {
-    ""Key"": ""Alt-Z""
-  },
+  ""Application.QuitKey"": ""Alt-Z"",
   ""Theme"": ""Default"",
   ""Themes"": [
     {
@@ -493,39 +491,39 @@ namespace Terminal.Gui.ConfigurationTests {
 }					
 			";
 
-			ConfigurationManager.Reset ();
-			ConfigurationManager.ThrowOnJsonErrors = true;
+		Reset ();
+		ThrowOnJsonErrors = true;
 
-			ConfigurationManager.Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
-			
-			Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
-			Assert.Equal (KeyCode.Z | KeyCode.AltMask, ((Key)ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue).KeyCode);
+		Settings.Update (json, "TestConfigurationManagerUpdateFromJson");
 
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
+		Assert.Equal (KeyCode.Z | KeyCode.AltMask, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
 
-			Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
-			Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
+		Assert.Equal ("Default", Themes.Theme);
 
-			var colorSchemes = (Dictionary<string, ColorScheme>)Themes.First ().Value ["ColorSchemes"].PropertyValue;
-			Assert.Equal (new Color (Color.White), colorSchemes ["Base"].Normal.Foreground);
-			Assert.Equal (new Color (Color.Blue), colorSchemes ["Base"].Normal.Background);
+		Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
+		Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
 
-			// Now re-apply
-			ConfigurationManager.Apply ();
+		var colorSchemes = (Dictionary<string, ColorScheme>)Themes.First ().Value ["ColorSchemes"].PropertyValue;
+		Assert.Equal (new Color (Color.White), colorSchemes ["Base"].Normal.Foreground);
+		Assert.Equal (new Color (Color.Blue), colorSchemes ["Base"].Normal.Background);
 
-			Assert.Equal (KeyCode.Z | KeyCode.AltMask, Application.QuitKey.KeyCode);
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
+		// Now re-apply
+		Apply ();
 
-			Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
-			Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
-		}
+		Assert.Equal (KeyCode.Z | KeyCode.AltMask, Application.QuitKey.KeyCode);
+		Assert.Equal ("Default", Themes.Theme);
 
-		[Fact, AutoInitShutdown]
-		public void TestConfigurationManagerInvalidJsonThrows ()
-		{
-			ConfigurationManager.ThrowOnJsonErrors = true;
-			// "yellow" is not a color
-			string json = @"
+		Assert.Equal (new Color (Color.White), Colors.ColorSchemes ["Base"].Normal.Foreground);
+		Assert.Equal (new Color (Color.Blue), Colors.ColorSchemes ["Base"].Normal.Background);
+	}
+
+	[Fact] [AutoInitShutdown]
+	public void TestConfigurationManagerInvalidJsonThrows ()
+	{
+		ThrowOnJsonErrors = true;
+		// "yellow" is not a color
+		string json = @"
 			{
 				""Themes"" : [
                                         {
@@ -545,11 +543,11 @@ namespace Terminal.Gui.ConfigurationTests {
 				]
 			}";
 
-			JsonException jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
-			Assert.Equal ("Unexpected color name: brown.", jsonException.Message);
+		var jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+		Assert.Equal ("Unexpected color name: brown.", jsonException.Message);
 
-			// AbNormal is not a ColorScheme attribute
-			json = @"
+		// AbNormal is not a ColorScheme attribute
+		json = @"
 			{
 				""Themes"" : [ 
                                         {
@@ -569,11 +567,11 @@ namespace Terminal.Gui.ConfigurationTests {
 				]
 			}";
 
-			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
-			Assert.Equal ("Unrecognized ColorScheme Attribute name: AbNormal.", jsonException.Message);
+		jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+		Assert.Equal ("Unrecognized ColorScheme Attribute name: AbNormal.", jsonException.Message);
 
-			// Modify hotNormal background only 
-			json = @"
+		// Modify hotNormal background only 
+		json = @"
 			{
 				""Themes"" : [ 
                                         {
@@ -592,31 +590,31 @@ namespace Terminal.Gui.ConfigurationTests {
 				]
 			}";
 
-			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
-			Assert.Equal ("Both Foreground and Background colors must be provided.", jsonException.Message);
+		jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+		Assert.Equal ("Both Foreground and Background colors must be provided.", jsonException.Message);
 
-			// Unknown proeprty
-			json = @"
+		// Unknown proeprty
+		json = @"
 			{
 				""Unknown"" : ""Not known""
 			}";
 
-			jsonException = Assert.Throws<JsonException> (() => ConfigurationManager.Settings.Update (json, "test"));
-			Assert.StartsWith ("Unknown property", jsonException.Message);
+		jsonException = Assert.Throws<JsonException> (() => Settings.Update (json, "test"));
+		Assert.StartsWith ("Unknown property", jsonException.Message);
 
-			Assert.Equal (0, ConfigurationManager.jsonErrors.Length);
+		Assert.Equal (0, jsonErrors.Length);
 
-			ConfigurationManager.ThrowOnJsonErrors = false;
-		}
+		ThrowOnJsonErrors = false;
+	}
 
-		[Fact]
-		public void TestConfigurationManagerInvalidJsonLogs ()
-		{
-			Application.Init (new FakeDriver ());
+	[Fact]
+	public void TestConfigurationManagerInvalidJsonLogs ()
+	{
+		Application.Init (new FakeDriver ());
 
-			ConfigurationManager.ThrowOnJsonErrors = false;
-			// "brown" is not a color
-			string json = @"
+		ThrowOnJsonErrors = false;
+		// "brown" is not a color
+		string json = @"
 			{
 				""Themes"" : [ 
                                         {
@@ -636,10 +634,10 @@ namespace Terminal.Gui.ConfigurationTests {
 				}
 			}";
 
-			ConfigurationManager.Settings.Update (json, "test");
+		Settings.Update (json, "test");
 
-			// AbNormal is not a ColorScheme attribute
-			json = @"
+		// AbNormal is not a ColorScheme attribute
+		json = @"
 			{
 				""Themes"" : [ 
                                         {
@@ -659,10 +657,10 @@ namespace Terminal.Gui.ConfigurationTests {
 				}
 			}";
 
-			ConfigurationManager.Settings.Update (json, "test");
+		Settings.Update (json, "test");
 
-			// Modify hotNormal background only 
-			json = @"
+		// Modify hotNormal background only 
+		json = @"
 			{
 				""Themes"" :  [ 
                                         {
@@ -681,111 +679,109 @@ namespace Terminal.Gui.ConfigurationTests {
 				}
 			}";
 
-			ConfigurationManager.Settings.Update (json, "test");
+		Settings.Update (json, "test");
 
-			ConfigurationManager.Settings.Update ("{}}", "test");
+		Settings.Update ("{}}", "test");
 
-			Assert.NotEqual (0, ConfigurationManager.jsonErrors.Length);
+		Assert.NotEqual (0, jsonErrors.Length);
 
-			Application.Shutdown ();
+		Application.Shutdown ();
 
-			ConfigurationManager.ThrowOnJsonErrors = false;
-		}
+		ThrowOnJsonErrors = false;
+	}
 
-		[Fact, AutoInitShutdown]
-		public void LoadConfigurationFromAllSources_ShouldLoadSettingsFromAllSources ()
-		{
-			//var _configFilename = "config.json";
-			//// Arrange
-			//// Create a mock of the configuration files in all sources
-			//// Home directory
-			//string homeDir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".tui");
-			//if (!Directory.Exists (homeDir)) {
-			//	Directory.CreateDirectory (homeDir);
-			//}
-			//string globalConfigFile = Path.Combine (homeDir, _configFilename);
-			//string appSpecificConfigFile = Path.Combine (homeDir, "appname.config.json");
-			//File.WriteAllText (globalConfigFile, "{\"Settings\": {\"TestSetting\":\"Global\"}}");
-			//File.WriteAllText (appSpecificConfigFile, "{\"Settings\": {\"TestSetting\":\"AppSpecific\"}}");
-
-			//// App directory
-			//string appDir = Directory.GetCurrentDirectory ();
-			//string appDirGlobalConfigFile = Path.Combine (appDir, _configFilename);
-			//string appDirAppSpecificConfigFile = Path.Combine (appDir, "appname.config.json");
-			//File.WriteAllText (appDirGlobalConfigFile, "{\"Settings\": {\"TestSetting\":\"GlobalAppDir\"}}");
-			//File.WriteAllText (appDirAppSpecificConfigFile, "{\"Settings\": {\"TestSetting\":\"AppSpecificAppDir\"}}");
-
-			//// App resources
-			//// ...
-
-			//// Act
-			//ConfigurationManager.Locations = ConfigurationManager.ConfigLocation.All;
-			//ConfigurationManager.Load ();
-
-			//// Assert
-			//// Check that the settings from the highest precedence source are loaded
-			//Assert.Equal ("AppSpecific", ConfigurationManager.Config.Settings.TestSetting);
-		}
+	[Fact] [AutoInitShutdown]
+	public void LoadConfigurationFromAllSources_ShouldLoadSettingsFromAllSources ()
+	{
+		//var _configFilename = "config.json";
+		//// Arrange
+		//// Create a mock of the configuration files in all sources
+		//// Home directory
+		//string homeDir = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".tui");
+		//if (!Directory.Exists (homeDir)) {
+		//	Directory.CreateDirectory (homeDir);
+		//}
+		//string globalConfigFile = Path.Combine (homeDir, _configFilename);
+		//string appSpecificConfigFile = Path.Combine (homeDir, "appname.config.json");
+		//File.WriteAllText (globalConfigFile, "{\"Settings\": {\"TestSetting\":\"Global\"}}");
+		//File.WriteAllText (appSpecificConfigFile, "{\"Settings\": {\"TestSetting\":\"AppSpecific\"}}");
+
+		//// App directory
+		//string appDir = Directory.GetCurrentDirectory ();
+		//string appDirGlobalConfigFile = Path.Combine (appDir, _configFilename);
+		//string appDirAppSpecificConfigFile = Path.Combine (appDir, "appname.config.json");
+		//File.WriteAllText (appDirGlobalConfigFile, "{\"Settings\": {\"TestSetting\":\"GlobalAppDir\"}}");
+		//File.WriteAllText (appDirAppSpecificConfigFile, "{\"Settings\": {\"TestSetting\":\"AppSpecificAppDir\"}}");
+
+		//// App resources
+		//// ...
+
+		//// Act
+		//ConfigurationManager.Locations = ConfigurationManager.ConfigLocation.All;
+		//ConfigurationManager.Load ();
+
+		//// Assert
+		//// Check that the settings from the highest precedence source are loaded
+		//Assert.Equal ("AppSpecific", ConfigurationManager.Config.Settings.TestSetting);
+	}
 
-		[Fact]
-		public void Load_FiresUpdated ()
+	[Fact]
+	public void Load_FiresUpdated ()
+	{
+		Reset ();
+
+		Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
+		Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
+		Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
+		Settings ["Application.IsMouseDisabled"].PropertyValue = true;
+
+		Updated += ConfigurationManager_Updated;
+		bool fired = false;
+		void ConfigurationManager_Updated (object sender, ConfigurationManagerEventArgs obj)
 		{
-			ConfigurationManager.Reset ();
+			fired = true;
+			// assert
+			Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
+			Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, ((Key)Settings ["Application.AlternateForwardKey"].PropertyValue).KeyCode);
+			Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, ((Key)Settings ["Application.AlternateBackwardKey"].PropertyValue).KeyCode);
+			Assert.False ((bool)Settings ["Application.IsMouseDisabled"].PropertyValue);
+		}
 
-			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
-			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
-			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
-			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
+		Load (true);
 
-			ConfigurationManager.Updated += ConfigurationManager_Updated;
-			bool fired = false;
-			void ConfigurationManager_Updated (object sender, ConfigurationManagerEventArgs obj)
-			{
-				fired = true;
-				// assert
-				Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, ((Key)ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue).KeyCode);
-				Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, ((Key)ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue).KeyCode);
-				Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, ((Key)ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue).KeyCode);
-				Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue);
-			}
+		// assert
+		Assert.True (fired);
 
-			ConfigurationManager.Load (true);
+		Updated -= ConfigurationManager_Updated;
+	}
 
+	[Fact]
+	public void Apply_FiresApplied ()
+	{
+		Reset ();
+		Applied += ConfigurationManager_Applied;
+		bool fired = false;
+		void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs obj)
+		{
+			fired = true;
 			// assert
-			Assert.True (fired);
-
-			ConfigurationManager.Updated -= ConfigurationManager_Updated;
+			Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
+			Assert.Equal (KeyCode.F, Application.AlternateForwardKey.KeyCode);
+			Assert.Equal (KeyCode.B, Application.AlternateBackwardKey.KeyCode);
+			Assert.True (Application.IsMouseDisabled);
 		}
 
-		[Fact]
-		public void Apply_FiresApplied ()
-		{
-			ConfigurationManager.Reset ();
-			ConfigurationManager.Applied += ConfigurationManager_Applied;
-			bool fired = false;
-			void ConfigurationManager_Applied (object sender, ConfigurationManagerEventArgs obj)
-			{
-				fired = true;
-				// assert
-				Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
-				Assert.Equal (KeyCode.F, Application.AlternateForwardKey.KeyCode);
-				Assert.Equal (KeyCode.B, Application.AlternateBackwardKey.KeyCode);
-				Assert.True (Application.IsMouseDisabled);
-			}
-
-			// act
-			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
-			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
-			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
-			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-
-			ConfigurationManager.Apply ();
+		// act
+		Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
+		Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
+		Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
+		Settings ["Application.IsMouseDisabled"].PropertyValue = true;
 
-			// assert
-			Assert.True (fired);
+		Apply ();
 
-			ConfigurationManager.Applied -= ConfigurationManager_Applied;
-		}
-	}
-}
+		// assert
+		Assert.True (fired);
 
+		Applied -= ConfigurationManager_Applied;
+	}
+}

+ 10 - 10
UnitTests/Configuration/JsonConverterTests.cs

@@ -278,16 +278,16 @@ public class KeyCodeJsonConverterTests {
 
 public class KeyJsonConverterTests {
 	[Theory]
-	[InlineData (KeyCode.A, "{\"Key\":\"a\"}")]
-	[InlineData ((KeyCode)'â', "{\"Key\":\"â\"}")]
-	[InlineData (KeyCode.A | KeyCode.ShiftMask, "{\"Key\":\"A\"}")]
-	[InlineData (KeyCode.A | KeyCode.CtrlMask, "{\"Key\":\"Ctrl+A\"}")]
-	[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "{\"Key\":\"Ctrl+Alt+A\"}")]
-	[InlineData ((KeyCode)'a' | KeyCode.AltMask | KeyCode.CtrlMask, "{\"Key\":\"Ctrl+Alt+A\"}")]
-	[InlineData ((KeyCode)'a' | KeyCode.ShiftMask, "{\"Key\":\"A\"}")]
-	[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "{\"Key\":\"Ctrl+Alt+Delete\"}")]
-	[InlineData (KeyCode.D4, "{\"Key\":\"4\"}")]
-	[InlineData (KeyCode.Esc, "{\"Key\":\"Esc\"}")]
+	[InlineData (KeyCode.A, "\"a\"")]
+	[InlineData ((KeyCode)'â', "\"â\"")]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, "\"A\"")]
+	[InlineData (KeyCode.A | KeyCode.CtrlMask, "\"Ctrl+A\"")]
+	[InlineData (KeyCode.A | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+A\"")]
+	[InlineData ((KeyCode)'a' | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+A\"")]
+	[InlineData ((KeyCode)'a' | KeyCode.ShiftMask, "\"A\"")]
+	[InlineData (KeyCode.Delete | KeyCode.AltMask | KeyCode.CtrlMask, "\"Ctrl+Alt+Delete\"")]
+	[InlineData (KeyCode.D4, "\"4\"")]
+	[InlineData (KeyCode.Esc, "\"Esc\"")]
 	public void TestKey_Serialize (KeyCode key, string expected)
 	{
 		// Arrange

+ 69 - 69
UnitTests/Configuration/SettingsScopeTests.cs

@@ -7,74 +7,74 @@ using System.Text;
 using System.Threading.Tasks;
 using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui.ConfigurationTests {
-	public class SettingsScopeTests {
-
-		[Fact]
-		public void GetHardCodedDefaults_ShouldSetProperties ()
-		{
-			ConfigurationManager.Reset ();
-
-			Assert.Equal (3, ((Dictionary<string, ThemeScope>)ConfigurationManager.Settings ["Themes"].PropertyValue).Count);
-
-			ConfigurationManager.GetHardCodedDefaults ();
-			Assert.NotEmpty (ConfigurationManager.Themes);
-			Assert.Equal ("Default", ConfigurationManager.Themes.Theme);
-
-			Assert.True (ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue is Key);
-			Assert.True (ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue is Key);
-			Assert.True (ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue is Key);
-			Assert.True (ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue is bool);
-
-			Assert.True (ConfigurationManager.Settings ["Theme"].PropertyValue is string);
-			Assert.Equal ("Default", ConfigurationManager.Settings ["Theme"].PropertyValue as string);
-
-			Assert.True (ConfigurationManager.Settings ["Themes"].PropertyValue is Dictionary<string, ThemeScope>);
-			Assert.Single (((Dictionary<string, ThemeScope>)ConfigurationManager.Settings ["Themes"].PropertyValue));
-
-		}
-
-		[Fact, AutoInitShutdown]
-		public void Apply_ShouldApplyProperties ()
-		{
-			// arrange
-			Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, ((Key)ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue).KeyCode);
-			Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, ((Key)ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue).KeyCode);
-			Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, ((Key)ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue).KeyCode);
-			Assert.False ((bool)ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue);
-
-			// act
-			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
-			ConfigurationManager.Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
-			ConfigurationManager.Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
-			ConfigurationManager.Settings ["Application.IsMouseDisabled"].PropertyValue = true;
-
-			ConfigurationManager.Settings.Apply ();
-
-			// assert
-			Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
-			Assert.Equal (KeyCode.F, Application.AlternateForwardKey.KeyCode);
-			Assert.Equal (KeyCode.B, Application.AlternateBackwardKey.KeyCode);
-			Assert.True (Application.IsMouseDisabled);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void CopyUpdatedProperitesFrom_ShouldCopyChangedPropertiesOnly ()
-		{
-			ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.End); ;
-
-			var updatedSettings = new SettingsScope ();
-
-			///Don't set Quitkey
-			updatedSettings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
-			updatedSettings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
-			updatedSettings ["Application.IsMouseDisabled"].PropertyValue = true;
-
-			ConfigurationManager.Settings.Update (updatedSettings);
-			Assert.Equal (KeyCode.End, ((Key)ConfigurationManager.Settings ["Application.QuitKey"].PropertyValue).KeyCode);
-			Assert.Equal (KeyCode.F,  ((Key)updatedSettings ["Application.AlternateForwardKey"].PropertyValue).KeyCode);
-			Assert.Equal (KeyCode.B, ((Key)updatedSettings ["Application.AlternateBackwardKey"].PropertyValue).KeyCode);
-			Assert.True ((bool)updatedSettings ["Application.IsMouseDisabled"].PropertyValue);
-		}
+namespace Terminal.Gui.ConfigurationTests; 
+
+public class SettingsScopeTests {
+	[Fact]
+	public void GetHardCodedDefaults_ShouldSetProperties ()
+	{
+		Reset ();
+
+		Assert.Equal (3, ((Dictionary<string, ThemeScope>)Settings ["Themes"].PropertyValue).Count);
+
+		GetHardCodedDefaults ();
+		Assert.NotEmpty (Themes);
+		Assert.Equal ("Default", Themes.Theme);
+
+		Assert.True (Settings ["Application.QuitKey"].PropertyValue is Key);
+		Assert.True (Settings ["Application.AlternateForwardKey"].PropertyValue is Key);
+		Assert.True (Settings ["Application.AlternateBackwardKey"].PropertyValue is Key);
+		Assert.True (Settings ["Application.IsMouseDisabled"].PropertyValue is bool);
+
+		Assert.True (Settings ["Theme"].PropertyValue is string);
+		Assert.Equal ("Default", Settings ["Theme"].PropertyValue as string);
+
+		Assert.True (Settings ["Themes"].PropertyValue is Dictionary<string, ThemeScope>);
+		Assert.Single ((Dictionary<string, ThemeScope>)Settings ["Themes"].PropertyValue);
+
+	}
+
+	[Fact] [AutoInitShutdown]
+	public void Apply_ShouldApplyProperties ()
+	{
+		// arrange
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
+		Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, ((Key)Settings ["Application.AlternateForwardKey"].PropertyValue).KeyCode);
+		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, ((Key)Settings ["Application.AlternateBackwardKey"].PropertyValue).KeyCode);
+		Assert.False ((bool)Settings ["Application.IsMouseDisabled"].PropertyValue);
+
+		// act
+		Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.Q);
+		Settings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
+		Settings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
+		Settings ["Application.IsMouseDisabled"].PropertyValue = true;
+
+		Settings.Apply ();
+
+		// assert
+		Assert.Equal (KeyCode.Q, Application.QuitKey.KeyCode);
+		Assert.Equal (KeyCode.F, Application.AlternateForwardKey.KeyCode);
+		Assert.Equal (KeyCode.B, Application.AlternateBackwardKey.KeyCode);
+		Assert.True (Application.IsMouseDisabled);
+	}
+
+	[Fact] [AutoInitShutdown]
+	public void CopyUpdatedPropertiesFrom_ShouldCopyChangedPropertiesOnly ()
+	{
+		Settings ["Application.QuitKey"].PropertyValue = new Key (KeyCode.End);
+		;
+
+		var updatedSettings = new SettingsScope ();
+
+		///Don't set Quitkey
+		updatedSettings ["Application.AlternateForwardKey"].PropertyValue = new Key (KeyCode.F);
+		updatedSettings ["Application.AlternateBackwardKey"].PropertyValue = new Key (KeyCode.B);
+		updatedSettings ["Application.IsMouseDisabled"].PropertyValue = true;
+
+		Settings.Update (updatedSettings);
+		Assert.Equal (KeyCode.End, ((Key)Settings ["Application.QuitKey"].PropertyValue).KeyCode);
+		Assert.Equal (KeyCode.F, ((Key)updatedSettings ["Application.AlternateForwardKey"].PropertyValue).KeyCode);
+		Assert.Equal (KeyCode.B, ((Key)updatedSettings ["Application.AlternateBackwardKey"].PropertyValue).KeyCode);
+		Assert.True ((bool)updatedSettings ["Application.IsMouseDisabled"].PropertyValue);
 	}
 }

+ 12 - 10
UnitTests/ConsoleDrivers/AddRuneTests.cs

@@ -7,20 +7,22 @@ using Xunit.Abstractions;
 // Alias Console to MockConsole so we don't accidentally use Console
 
 namespace Terminal.Gui.DriverTests;
+
 public class AddRuneTests {
 	readonly ITestOutputHelper _output;
 
 	public AddRuneTests (ITestOutputHelper output)
 	{
 		ConsoleDriver.RunningUnitTests = true;
-		this._output = output;
+		_output = output;
 	}
 
 	[Theory]
 	[InlineData (typeof (FakeDriver))]
 	[InlineData (typeof (NetDriver))]
-	[InlineData (typeof (CursesDriver))]
+	//[InlineData (typeof (ANSIDriver))]
 	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
 	public void AddRune (Type driverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
@@ -44,8 +46,8 @@ public class AddRuneTests {
 		driver.Move (driver.Cols, driver.Rows);
 		driver.AddRune ('a');
 
-		for (var col = 0; col < driver.Cols; col++) {
-			for (var row = 0; row < driver.Rows; row++) {
+		for (int col = 0; col < driver.Cols; col++) {
+			for (int row = 0; row < driver.Rows; row++) {
 				Assert.Equal ((Rune)' ', driver.Contents [row, col].Rune);
 			}
 		}
@@ -70,7 +72,7 @@ public class AddRuneTests {
 		Assert.Equal (2, driver.Col);
 
 		// Move to the last column of the first row
-		var lastCol = driver.Cols - 1;
+		int lastCol = driver.Cols - 1;
 		driver.Move (lastCol, 0);
 		Assert.Equal (0, driver.Row);
 		Assert.Equal (lastCol, driver.Col);
@@ -83,8 +85,8 @@ public class AddRuneTests {
 		// Add a rune; should succeed but do nothing as it's outside of Contents
 		driver.AddRune ('d');
 		Assert.Equal (lastCol + 2, driver.Col);
-		for (var col = 0; col < driver.Cols; col++) {
-			for (var row = 0; row < driver.Rows; row++) {
+		for (int col = 0; col < driver.Cols; col++) {
+			for (int row = 0; row < driver.Rows; row++) {
 				Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Rune);
 			}
 		}
@@ -99,7 +101,7 @@ public class AddRuneTests {
 		driver.Init ();
 
 		// 🍕 Slice of Pizza "\U0001F355"
-		var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out Rune rune, out int charsConsumed);
+		var operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out var rune, out int charsConsumed);
 		Assert.Equal (OperationStatus.Done, operationStatus);
 		Assert.Equal (charsConsumed, rune.Utf16SequenceLength);
 		Assert.Equal (2, rune.GetColumns ());
@@ -145,7 +147,7 @@ public class AddRuneTests {
 
 		var expected = new Rune ('ắ');
 
-		var text = "\u1eaf";
+		string text = "\u1eaf";
 		driver.AddStr (text);
 		Assert.Equal (expected, driver.Contents [0, 0].Rune);
 		Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune);
@@ -188,4 +190,4 @@ public class AddRuneTests {
 		//ắ", output);
 		driver.End ();
 	}
-}
+}

+ 121 - 104
UnitTests/ConsoleDrivers/ClipRegionTests.cs

@@ -9,108 +9,125 @@ using Xunit.Abstractions;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.DriverTests {
-	public class ClipRegionTests {
-		readonly ITestOutputHelper output;
-
-		public ClipRegionTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-		
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void IsValidLocation (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			// positive
-			Assert.True (driver.IsValidLocation (0, 0));
-			Assert.True (driver.IsValidLocation (1, 1));
-			Assert.True (driver.IsValidLocation (driver.Cols - 1, driver.Rows - 1));
-			// negative
-			Assert.False (driver.IsValidLocation (-1, 0));
-			Assert.False (driver.IsValidLocation (0, -1));
-			Assert.False (driver.IsValidLocation (-1, -1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
-
-			// Define a clip rectangle
-			driver.Clip = new Rect (5, 5, 5, 5);
-			// positive
-			Assert.True (driver.IsValidLocation (5, 5));
-			Assert.True (driver.IsValidLocation (9, 9));
-			// negative
-			Assert.False (driver.IsValidLocation (4, 5));
-			Assert.False (driver.IsValidLocation (5, 4));
-			Assert.False (driver.IsValidLocation (10, 9));
-			Assert.False (driver.IsValidLocation (9, 10));
-			Assert.False (driver.IsValidLocation (-1, 0));
-			Assert.False (driver.IsValidLocation (0, -1));
-			Assert.False (driver.IsValidLocation (-1, -1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void Clip_Set_To_Empty_AllInvalid (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			// Define a clip rectangle
-			driver.Clip = Rect.Empty;
-
-			// negative
-			Assert.False (driver.IsValidLocation (4, 5));
-			Assert.False (driver.IsValidLocation (5, 4));
-			Assert.False (driver.IsValidLocation (10, 9));
-			Assert.False (driver.IsValidLocation (9, 10));
-			Assert.False (driver.IsValidLocation (-1, 0));
-			Assert.False (driver.IsValidLocation (0, -1));
-			Assert.False (driver.IsValidLocation (-1, -1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
-			Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
-
-			Application.Shutdown ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void AddRune_Is_Clipped (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			driver.Move (0, 0);
-			driver.AddRune ('x');
-			Assert.Equal ((Rune)'x', driver.Contents [0, 0].Rune);
-
-			driver.Move (5, 5);
-			driver.AddRune ('x');
-			Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune);
-
-			// Clear the contents
-			driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), ' ');
-			Assert.Equal ((Rune)' ', driver.Contents [0, 0].Rune);
-
-			// Setup the region with a single rectangle, fill screen with 'x'
-			driver.Clip = new Rect (5, 5, 5, 5);
-			driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), 'x');
-			Assert.Equal ((Rune)' ', driver.Contents [0, 0].Rune);
-			Assert.Equal ((Rune)' ', driver.Contents [4, 9].Rune);
-			Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune);
-			Assert.Equal ((Rune)'x', driver.Contents [9, 9].Rune);
-			Assert.Equal ((Rune)' ', driver.Contents [10, 10].Rune);
-
-			Application.Shutdown ();
-		}
+namespace Terminal.Gui.DriverTests; 
+
+public class ClipRegionTests {
+	readonly ITestOutputHelper output;
+
+	public ClipRegionTests (ITestOutputHelper output)
+	{
+		ConsoleDriver.RunningUnitTests = true;
+		this.output = output;
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void IsValidLocation (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+		Application.Driver.Rows = 10;
+		Application.Driver.Cols = 10;
+
+		// positive
+		Assert.True (driver.IsValidLocation (0, 0));
+		Assert.True (driver.IsValidLocation (1, 1));
+		Assert.True (driver.IsValidLocation (driver.Cols - 1, driver.Rows - 1));
+		// negative
+		Assert.False (driver.IsValidLocation (-1, 0));
+		Assert.False (driver.IsValidLocation (0, -1));
+		Assert.False (driver.IsValidLocation (-1, -1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
+
+		// Define a clip rectangle
+		driver.Clip = new Rect (5, 5, 5, 5);
+		// positive
+		Assert.True (driver.IsValidLocation (5, 5));
+		Assert.True (driver.IsValidLocation (9, 9));
+		// negative
+		Assert.False (driver.IsValidLocation (4, 5));
+		Assert.False (driver.IsValidLocation (5, 4));
+		Assert.False (driver.IsValidLocation (10, 9));
+		Assert.False (driver.IsValidLocation (9, 10));
+		Assert.False (driver.IsValidLocation (-1, 0));
+		Assert.False (driver.IsValidLocation (0, -1));
+		Assert.False (driver.IsValidLocation (-1, -1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
+
+		Application.Shutdown ();
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void Clip_Set_To_Empty_AllInvalid (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+
+		// Define a clip rectangle
+		driver.Clip = Rect.Empty;
+
+		// negative
+		Assert.False (driver.IsValidLocation (4, 5));
+		Assert.False (driver.IsValidLocation (5, 4));
+		Assert.False (driver.IsValidLocation (10, 9));
+		Assert.False (driver.IsValidLocation (9, 10));
+		Assert.False (driver.IsValidLocation (-1, 0));
+		Assert.False (driver.IsValidLocation (0, -1));
+		Assert.False (driver.IsValidLocation (-1, -1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows - 1));
+		Assert.False (driver.IsValidLocation (driver.Cols, driver.Rows));
+
+		Application.Shutdown ();
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void AddRune_Is_Clipped (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+		Application.Driver.Rows = 25;
+		Application.Driver.Cols = 80;
+
+		driver.Move (0, 0);
+		driver.AddRune ('x');
+		Assert.Equal ((Rune)'x', driver.Contents [0, 0].Rune);
+
+		driver.Move (5, 5);
+		driver.AddRune ('x');
+		Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune);
+
+		// Clear the contents
+		driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), ' ');
+		Assert.Equal ((Rune)' ', driver.Contents [0, 0].Rune);
+
+		// Setup the region with a single rectangle, fill screen with 'x'
+		driver.Clip = new Rect (5, 5, 5, 5);
+		driver.FillRect (new Rect (0, 0, driver.Rows, driver.Cols), 'x');
+		Assert.Equal ((Rune)' ', driver.Contents [0, 0].Rune);
+		Assert.Equal ((Rune)' ', driver.Contents [4, 9].Rune);
+		Assert.Equal ((Rune)'x', driver.Contents [5, 5].Rune);
+		Assert.Equal ((Rune)'x', driver.Contents [9, 9].Rune);
+		Assert.Equal ((Rune)' ', driver.Contents [10, 10].Rune);
+
+		Application.Shutdown ();
 	}
-}
+}

+ 254 - 247
UnitTests/ConsoleDrivers/ConsoleDriverTests.cs

@@ -8,264 +8,271 @@ using Xunit.Abstractions;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.DriverTests {
-	public class ConsoleDriverTests {
-		readonly ITestOutputHelper output;
-
-		public ConsoleDriverTests (ITestOutputHelper output)
-		{
-			ConsoleDriver.RunningUnitTests = true;
-			this.output = output;
-		}
+namespace Terminal.Gui.DriverTests; 
 
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		[InlineData (typeof (NetDriver))]
-		[InlineData (typeof (CursesDriver))]
-		[InlineData (typeof (WindowsDriver))]
-		public void Init_Inits (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			var ml = driver.Init ();
-			Assert.NotNull (ml);
-			Assert.NotNull (driver.Clipboard);
-			Console.ForegroundColor = ConsoleColor.Red;
-			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
-			Console.BackgroundColor = ConsoleColor.Green;
-			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
-
-			driver.End ();
-		}
+public class ConsoleDriverTests {
+	readonly ITestOutputHelper output;
 
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		[InlineData (typeof (NetDriver))]
-		[InlineData (typeof (CursesDriver))]
-		[InlineData (typeof (WindowsDriver))]
-		public void End_Cleans_Up (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			driver.Init ();
-			driver.End ();
-		}
+	public ConsoleDriverTests (ITestOutputHelper output)
+	{
+		ConsoleDriver.RunningUnitTests = true;
+		this.output = output;
+	}
 
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void Init_Inits (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		var ml = driver.Init ();
+		Assert.NotNull (ml);
+		Assert.NotNull (driver.Clipboard);
+		Console.ForegroundColor = ConsoleColor.Red;
+		Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+		Console.BackgroundColor = ConsoleColor.Green;
+		Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
+
+		driver.End ();
+	}
 
-			var top = Application.Top;
-			var view = new View () {
-				CanFocus = true
-			};
-			var count = 0;
-			var wasKeyPressed = false;
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void End_Cleans_Up (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		driver.Init ();
+		driver.End ();
+	}
 
-			view.KeyDown += (s, e) => {
-				wasKeyPressed = true;
-			};
-			top.Add (view);
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	public void FakeDriver_Only_Sends_Keystrokes_Through_MockKeyPresses (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+
+		var top = Application.Top;
+		var view = new View () {
+			CanFocus = true
+		};
+		int count = 0;
+		bool wasKeyPressed = false;
+
+		view.KeyDown += (s, e) => {
+			wasKeyPressed = true;
+		};
+		top.Add (view);
+
+		Application.Iteration += (s, a) => {
+			count++;
+			if (count == 10) {
+				Application.RequestStop ();
+			}
+		};
 
-			Application.Iteration += (s, a) => {
-				count++;
-				if (count == 10) Application.RequestStop ();
-			};
+		Application.Run ();
 
-			Application.Run ();
+		Assert.False (wasKeyPressed);
 
-			Assert.False (wasKeyPressed);
+		// Shutdown must be called to safely clean up Application if Init has been called
+		Application.Shutdown ();
+	}
 
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	public void FakeDriver_MockKeyPresses (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+
+		string text = "MockKeyPresses";
+		var mKeys = new Stack<ConsoleKeyInfo> ();
+		foreach (char r in text.Reverse ()) {
+			var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r;
+			var cki = new ConsoleKeyInfo (r, ck, false, false, false);
+			mKeys.Push (cki);
 		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void FakeDriver_MockKeyPresses (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			var text = "MockKeyPresses";
-			var mKeys = new Stack<ConsoleKeyInfo> ();
-			foreach (var r in text.Reverse ()) {
-				var ck = char.IsLetter (r) ? (ConsoleKey)char.ToUpper (r) : (ConsoleKey)r;
-				var cki = new ConsoleKeyInfo (r, ck, false, false, false);
-				mKeys.Push (cki);
+		Console.MockKeyPresses = mKeys;
+
+		var top = Application.Top;
+		var view = new View () {
+			CanFocus = true
+		};
+		string rText = "";
+		int idx = 0;
+
+		view.KeyDown += (s, e) => {
+			Assert.Equal (text [idx], (char)e.KeyCode);
+			rText += (char)e.KeyCode;
+			Assert.Equal (rText, text.Substring (0, idx + 1));
+			e.Handled = true;
+			idx++;
+		};
+		top.Add (view);
+
+		Application.Iteration += (s, a) => {
+			if (mKeys.Count == 0) {
+				Application.RequestStop ();
 			}
-			Console.MockKeyPresses = mKeys;
-
-			var top = Application.Top;
-			var view = new View () {
-				CanFocus = true
-			};
-			var rText = "";
-			var idx = 0;
-
-			view.KeyDown += (s, e) => {
-				Assert.Equal (text [idx], (char)e.KeyCode);
-				rText += (char)e.KeyCode;
-				Assert.Equal (rText, text.Substring (0, idx + 1));
-				e.Handled = true;
-				idx++;
-			};
-			top.Add (view);
-
-			Application.Iteration += (s, a) => {
-				if (mKeys.Count == 0) Application.RequestStop ();
-			};
-
-			Application.Run ();
-
-			Assert.Equal ("MockKeyPresses", rText);
-
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
-		}
+		};
 
-		//[Theory]
-		//[InlineData (typeof (FakeDriver))]
-		//public void FakeDriver_MockKeyPresses_Press_AfterTimeOut (Type driverType)
-		//{
-		//	var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-		//	Application.Init (driver);
-
-		//	// Simulating pressing of QuitKey after a short period of time
-		//	uint quitTime = 100;
-		//	Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
-		//		// Prove the scenario is using Application.QuitKey correctly
-		//		output.WriteLine ($"  {quitTime}ms elapsed; Simulating keypresses...");
-		//		FakeConsole.PushMockKeyPress (Key.F);
-		//		FakeConsole.PushMockKeyPress (Key.U);
-		//		FakeConsole.PushMockKeyPress (Key.C);
-		//		FakeConsole.PushMockKeyPress (Key.K);
-		//		return false;
-		//	};
-		//	output.WriteLine ($"Add timeout to simulate key presses after {quitTime}ms");
-		//	_ = Application.AddTimeout (TimeSpan.FromMilliseconds (quitTime), closeCallback);
-
-		//	// If Top doesn't quit within abortTime * 5 (500ms), this will force it
-		//	uint abortTime = quitTime * 5;
-		//	Func<MainLoop, bool> forceCloseCallback = (MainLoop loop) => {
-		//		Application.RequestStop ();
-		//		Assert.Fail ($"  failed to Quit after {abortTime}ms. Force quit.");
-		//		return false;
-		//	};
-		//	output.WriteLine ($"Add timeout to force quit after {abortTime}ms");
-		//	_ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback);
-
-		//	Key key = Key.Unknown;
-			
-		//	Application.Top.KeyPress += (e) => {
-		//		key = e.Key;
-		//		output.WriteLine ($"  Application.Top.KeyPress: {key}");
-		//		e.Handled = true;
-				
-		//	};
-
-		//	int iterations = 0;
-		//	Application.Iteration += (s, a) => {
-		//		output.WriteLine ($"  iteration {++iterations}");
-
-		//		if (Console.MockKeyPresses.Count == 0) {
-		//			output.WriteLine ($"    No more MockKeyPresses; RequestStop");
-		//			Application.RequestStop ();
-		//		}
-		//	};
-
-		//	Application.Run ();
-
-		//	// Shutdown must be called to safely clean up Application if Init has been called
-		//	Application.Shutdown ();
-		//}
-		
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		[InlineData (typeof (NetDriver))]
-		[InlineData (typeof (CursesDriver))]
-		[InlineData (typeof (WindowsDriver))]
-		public void TerminalResized_Simulation (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			driver?.Init ();
-			driver.Cols = 80;
-			driver.Rows = 25;
-			
-			var wasTerminalResized = false;
-			driver.SizeChanged += (s, e) => {
-				wasTerminalResized = true;
-				Assert.Equal (120, e.Size.Width);
-				Assert.Equal (40, e.Size.Height);
-			};
-
-			Assert.Equal (80, driver.Cols);
-			Assert.Equal (25, driver.Rows);
-			Assert.False (wasTerminalResized);
-
-			driver.Cols = 120;
-			driver.Rows = 40;
-			driver.OnSizeChanged (new SizeChangedEventArgs(new Size(driver.Cols, driver.Rows)));
-			Assert.Equal (120, driver.Cols);
-			Assert.Equal (40, driver.Rows);
-			Assert.True (wasTerminalResized);
-			driver.End ();
-		}
+		Application.Run ();
+
+		Assert.Equal ("MockKeyPresses", rText);
 
-		// Disabled due to test error - Change Task.Delay to an await
-		//		[Fact, AutoInitShutdown]
-		//		public void Write_Do_Not_Change_On_ProcessKey ()
-		//		{
-		//			var win = new Window ();
-		//			Application.Begin (win);
-		//			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
-
-		//			System.Threading.Tasks.Task.Run (() => {
-		//				System.Threading.Tasks.Task.Delay (500).Wait ();
-		//				Application.Invoke (() => {
-		//					var lbl = new Label ("Hello World") { X = Pos.Center () };
-		//					var dlg = new Dialog ();
-		//					dlg.Add (lbl);
-		//					Application.Begin (dlg);
-
-		//					var expected = @"
-		//┌──────────────────┐
-		//│┌───────────────┐ │
-		//││  Hello World  │ │
-		//││               │ │
-		//││               │ │
-		//││               │ │
-		//│└───────────────┘ │
-		//└──────────────────┘
-		//";
-
-		//					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-		//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
-
-		//					Assert.True (dlg.ProcessKey (new (Key.Tab)));
-		//					dlg.Draw ();
-
-		//					expected = @"
-		//┌──────────────────┐
-		//│┌───────────────┐ │
-		//││  Hello World  │ │
-		//││               │ │
-		//││               │ │
-		//││               │ │
-		//│└───────────────┘ │
-		//└──────────────────┘
-		//";
-
-		//					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-		//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
-
-		//					win.RequestStop ();
-		//				});
-		//			});
-
-		//			Application.Run (win);
-		//			Application.Shutdown ();
-		//		}
+		// Shutdown must be called to safely clean up Application if Init has been called
+		Application.Shutdown ();
 	}
-}
+
+	//[Theory]
+	//[InlineData (typeof (FakeDriver))]
+	//public void FakeDriver_MockKeyPresses_Press_AfterTimeOut (Type driverType)
+	//{
+	//	var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+	//	Application.Init (driver);
+
+	//	// Simulating pressing of QuitKey after a short period of time
+	//	uint quitTime = 100;
+	//	Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
+	//		// Prove the scenario is using Application.QuitKey correctly
+	//		output.WriteLine ($"  {quitTime}ms elapsed; Simulating keypresses...");
+	//		FakeConsole.PushMockKeyPress (Key.F);
+	//		FakeConsole.PushMockKeyPress (Key.U);
+	//		FakeConsole.PushMockKeyPress (Key.C);
+	//		FakeConsole.PushMockKeyPress (Key.K);
+	//		return false;
+	//	};
+	//	output.WriteLine ($"Add timeout to simulate key presses after {quitTime}ms");
+	//	_ = Application.AddTimeout (TimeSpan.FromMilliseconds (quitTime), closeCallback);
+
+	//	// If Top doesn't quit within abortTime * 5 (500ms), this will force it
+	//	uint abortTime = quitTime * 5;
+	//	Func<MainLoop, bool> forceCloseCallback = (MainLoop loop) => {
+	//		Application.RequestStop ();
+	//		Assert.Fail ($"  failed to Quit after {abortTime}ms. Force quit.");
+	//		return false;
+	//	};
+	//	output.WriteLine ($"Add timeout to force quit after {abortTime}ms");
+	//	_ = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), forceCloseCallback);
+
+	//	Key key = Key.Unknown;
+
+	//	Application.Top.KeyPress += (e) => {
+	//		key = e.Key;
+	//		output.WriteLine ($"  Application.Top.KeyPress: {key}");
+	//		e.Handled = true;
+
+	//	};
+
+	//	int iterations = 0;
+	//	Application.Iteration += (s, a) => {
+	//		output.WriteLine ($"  iteration {++iterations}");
+
+	//		if (Console.MockKeyPresses.Count == 0) {
+	//			output.WriteLine ($"    No more MockKeyPresses; RequestStop");
+	//			Application.RequestStop ();
+	//		}
+	//	};
+
+	//	Application.Run ();
+
+	//	// Shutdown must be called to safely clean up Application if Init has been called
+	//	Application.Shutdown ();
+	//}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void TerminalResized_Simulation (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		driver?.Init ();
+		driver.Cols = 80;
+		driver.Rows = 25;
+
+		bool wasTerminalResized = false;
+		driver.SizeChanged += (s, e) => {
+			wasTerminalResized = true;
+			Assert.Equal (120, e.Size.Width);
+			Assert.Equal (40, e.Size.Height);
+		};
+
+		Assert.Equal (80, driver.Cols);
+		Assert.Equal (25, driver.Rows);
+		Assert.False (wasTerminalResized);
+
+		driver.Cols = 120;
+		driver.Rows = 40;
+		driver.OnSizeChanged (new SizeChangedEventArgs (new Size (driver.Cols, driver.Rows)));
+		Assert.Equal (120, driver.Cols);
+		Assert.Equal (40, driver.Rows);
+		Assert.True (wasTerminalResized);
+		driver.End ();
+	}
+
+	// Disabled due to test error - Change Task.Delay to an await
+	//		[Fact, AutoInitShutdown]
+	//		public void Write_Do_Not_Change_On_ProcessKey ()
+	//		{
+	//			var win = new Window ();
+	//			Application.Begin (win);
+	//			((FakeDriver)Application.Driver).SetBufferSize (20, 8);
+
+	//			System.Threading.Tasks.Task.Run (() => {
+	//				System.Threading.Tasks.Task.Delay (500).Wait ();
+	//				Application.Invoke (() => {
+	//					var lbl = new Label ("Hello World") { X = Pos.Center () };
+	//					var dlg = new Dialog ();
+	//					dlg.Add (lbl);
+	//					Application.Begin (dlg);
+
+	//					var expected = @"
+	//┌──────────────────┐
+	//│┌───────────────┐ │
+	//││  Hello World  │ │
+	//││               │ │
+	//││               │ │
+	//││               │ │
+	//│└───────────────┘ │
+	//└──────────────────┘
+	//";
+
+	//					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+	//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+	//					Assert.True (dlg.ProcessKey (new (Key.Tab)));
+	//					dlg.Draw ();
+
+	//					expected = @"
+	//┌──────────────────┐
+	//│┌───────────────┐ │
+	//││  Hello World  │ │
+	//││               │ │
+	//││               │ │
+	//││               │ │
+	//│└───────────────┘ │
+	//└──────────────────┘
+	//";
+
+	//					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+	//					Assert.Equal (new Rect (0, 0, 20, 8), pos);
+
+	//					win.RequestStop ();
+	//				});
+	//			});
+
+	//			Application.Run (win);
+	//			Application.Shutdown ();
+	//		}
+}

+ 415 - 148
UnitTests/ConsoleDrivers/ConsoleKeyMappingTests.cs

@@ -1,46 +1,57 @@
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using Xunit;
+using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping;
 
 namespace Terminal.Gui.ConsoleDrivers;
 public class ConsoleKeyMappingTests {
+
+#if ENABLE_VK_PACKET_NON_WINDOWS
+	// This test (and the GetConsoleKeyInfoFromKeyCode API) are bogus. They make no sense outside of
+	// the context of Windows and knowing they keyboard layout. They should be removed.
 	[Theory]
-	[InlineData ((KeyCode)'a' | KeyCode.ShiftMask, ConsoleKey.A, KeyCode.A, 'A')]
-	[InlineData ((KeyCode)'A', ConsoleKey.A, (KeyCode)'a', 'a')]
-	[InlineData ((KeyCode)'à' | KeyCode.ShiftMask, ConsoleKey.A, (KeyCode)'À', 'À')]
-	[InlineData ((KeyCode)'À', ConsoleKey.A, (KeyCode)'à', 'à')]
-	[InlineData ((KeyCode)'ü' | KeyCode.ShiftMask, ConsoleKey.U, (KeyCode)'Ü', 'Ü')]
-	[InlineData ((KeyCode)'Ü', ConsoleKey.U, (KeyCode)'ü', 'ü')]
-	[InlineData ((KeyCode)'ý' | KeyCode.ShiftMask, ConsoleKey.Y, (KeyCode)'Ý', 'Ý')]
-	[InlineData ((KeyCode)'Ý', ConsoleKey.Y, (KeyCode)'ý', 'ý')]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, ConsoleKey.A, KeyCode.A, 'A')]
+	[InlineData ((KeyCode)'a', ConsoleKey.A, (KeyCode)'a', 'a')]
+	[InlineData ((KeyCode)'À' | KeyCode.ShiftMask, ConsoleKey.A, (KeyCode)'À', 'À')]
+	[InlineData ((KeyCode)'à', ConsoleKey.A, (KeyCode)'à', 'à')]
+	[InlineData ((KeyCode)'Ü' | KeyCode.ShiftMask, ConsoleKey.U, (KeyCode)'Ü', 'Ü')]
+	[InlineData ((KeyCode)'ü', ConsoleKey.U, (KeyCode)'ü', 'ü')]
+	[InlineData ((KeyCode)'Ý' | KeyCode.ShiftMask, ConsoleKey.Y, (KeyCode)'Ý', 'Ý')]
+	[InlineData ((KeyCode)'ý', ConsoleKey.Y, (KeyCode)'ý', 'ý')]
 	[InlineData ((KeyCode)'!' | KeyCode.ShiftMask, ConsoleKey.D1, (KeyCode)'!', '!')]
+	[InlineData (KeyCode.D1 | KeyCode.ShiftMask, ConsoleKey.D1, (KeyCode)'!', '!')]
 	[InlineData (KeyCode.D1, ConsoleKey.D1, KeyCode.D1, '1')]
-	[InlineData ((KeyCode)'/' | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')]
+	[InlineData ((KeyCode)'/' | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')] // BUGBUG: This is incorrect for ENG keyboards. Shift-7 should be &.
+	[InlineData (KeyCode.D7 | KeyCode.ShiftMask, ConsoleKey.D7, (KeyCode)'/', '/')]
 	[InlineData (KeyCode.D7, ConsoleKey.D7, KeyCode.D7, '7')]
+	[InlineData ((KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, ConsoleKey.D7, (KeyCode)'{', '{')]
+	[InlineData ((KeyCode)'?' | KeyCode.ShiftMask, ConsoleKey.Oem4, (KeyCode)'?', '?')]
+	[InlineData ((KeyCode)'\'', ConsoleKey.Oem4, (KeyCode)'\'', '\'')]
 	[InlineData (KeyCode.PageDown | KeyCode.ShiftMask, ConsoleKey.PageDown, KeyCode.Null, '\0')]
 	[InlineData (KeyCode.PageDown, ConsoleKey.PageDown, KeyCode.Null, '\0')]
-
-	public void TestIfEqual (KeyCode key, ConsoleKey expectedConsoleKey, KeyCode expectedKey, char expectedChar)
+	[InlineData ((KeyCode)'q', ConsoleKey.Q, (KeyCode)'q', 'q')]
+	[InlineData (KeyCode.F2, ConsoleKey.F2, KeyCode.Null, '\0')]
+	[InlineData ((KeyCode)'英', ConsoleKey.None, (KeyCode)'英', '英')]
+	public void GetConsoleKeyInfoFromKeyCode_Tests (KeyCode keyCode, ConsoleKey expectedConsoleKey, KeyCode expectedKeyCode, char expectedKeyChar)
 	{
-		var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (key);
+		var consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (keyCode);
 		Assert.Equal (consoleKeyInfo.Key, expectedConsoleKey);
-		Assert.Equal ((char)expectedKey, expectedChar);
-		Assert.Equal (consoleKeyInfo.KeyChar, expectedChar);
+		Assert.Equal ((char)expectedKeyCode, expectedKeyChar);
+		Assert.Equal (consoleKeyInfo.KeyChar, expectedKeyChar);
 	}
 
 	static object packetLock = new object ();
 
 	/// <summary>
 	/// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'.
-	/// These are indicated with the wVirtualKeyCode of 231. When we see this code
+	/// These are indicated with the wVirtualKeyCode of 231 (VK_PACKET). When we see this code
 	/// then we need to look to the unicode character (UnicodeChar) instead of the key
 	/// when telling the rest of the framework what button was pressed. For full details
 	/// see: https://github.com/gui-cs/Terminal.Gui/issues/2008
 	/// </summary>
 	[Theory]
 	[AutoInitShutdown]
-	[ClassData (typeof (PacketTest))]
+	[MemberData (nameof (VKPacket))]
 	public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey,
 				uint initialScanCode, KeyCode expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
 	{
@@ -48,20 +59,17 @@ public class ConsoleKeyMappingTests {
 			Application._forceFakeConsole = true;
 			Application.Init ();
 
-			var modifiers = new ConsoleModifiers ();
-			if (shift) {
-				modifiers |= ConsoleModifiers.Shift;
-			}
-			if (alt) {
-				modifiers |= ConsoleModifiers.Alt;
-			}
-			if (control) {
-				modifiers |= ConsoleModifiers.Control;
-			}
-			ConsoleKeyInfo consoleKeyInfo = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode);
+			ConsoleKeyInfo originalConsoleKeyInfo = new ConsoleKeyInfo ((char)unicodeCharacter, (ConsoleKey)initialVirtualKey, shift, alt, control);
+			var encodedChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (originalConsoleKeyInfo);
+			ConsoleKeyInfo packetConsoleKeyInfo = new ConsoleKeyInfo (encodedChar, ConsoleKey.Packet, shift, alt, control);
+			ConsoleKeyInfo consoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (packetConsoleKeyInfo);
 
-			Assert.Equal ((uint)consoleKeyInfo.Key, initialVirtualKey);
+			Assert.Equal (originalConsoleKeyInfo, consoleKeyInfo);
 
+			var modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control);
+			var scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo);
+
+			Assert.Equal ((uint)consoleKeyInfo.Key, initialVirtualKey);
 
 			if (scanCode > 0 && consoleKeyInfo.KeyChar == 0) {
 				Assert.Equal (0, (double)consoleKeyInfo.KeyChar);
@@ -85,7 +93,8 @@ public class ConsoleKeyMappingTests {
 			Application.Iteration += (s, a) => {
 				iterations++;
 				if (iterations == 0) {
-					Application.Driver.SendKeys (consoleKeyInfo.KeyChar, ConsoleKey.Packet, shift, alt, control);
+					var keyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
+					Application.Driver.SendKeys (keyChar, ConsoleKey.Packet, shift, alt, control);
 				}
 			};
 			Application.Run ();
@@ -93,126 +102,384 @@ public class ConsoleKeyMappingTests {
 		}
 	}
 
-	public class PacketTest : IEnumerable, IEnumerable<object []> {
-		public IEnumerator<object []> GetEnumerator ()
-		{
-			lock (packetLock) {
-				// unicodeCharacter, shift, alt, control, initialVirtualKey, initialScanCode, expectedRemapping, expectedVirtualKey, expectedScanCode
-				yield return new object [] { 'a', false, false, false, 'A', 30, KeyCode.A, 'A', 30 };
-				yield return new object [] { 'A', true, false, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask, 'A', 30 };
-				yield return new object [] { 'A', true, true, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, 'A', 30 };
-				yield return new object [] { 'A', true, true, true, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'A', 30 };
-				yield return new object [] { 'z', false, false, false, 'Z', 44, KeyCode.Z, 'Z', 44 };
-				yield return new object [] { 'Z', true, false, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask, 'Z', 44 };
-				yield return new object [] { 'Z', true, true, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask, 'Z', 44 };
-				yield return new object [] { 'Z', true, true, true, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'Z', 44 };
-				yield return new object [] { '英', false, false, false, '\0', 0, (KeyCode)'英', '\0', 0 };
-				yield return new object [] { '英', true, false, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask, '\0', 0 };
-				yield return new object [] { '英', true, true, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask, '\0', 0 };
-				yield return new object [] { '英', true, true, true, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0', 0 };
-				yield return new object [] { '+', false, false, false, 187, 26, (KeyCode)'+', 187, 26 };
-				yield return new object [] { '*', true, false, false, 187, 26, (KeyCode)'*' | KeyCode.ShiftMask, 187, 26 };
-				yield return new object [] { '+', true, true, false, 187, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask, 187, 26 };
-				yield return new object [] { '+', true, true, true, 187, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 187, 26 };
-				yield return new object [] { '1', false, false, false, '1', 2, KeyCode.D1, '1', 2 };
-				yield return new object [] { '!', true, false, false, '1', 2, (KeyCode)'!' | KeyCode.ShiftMask, '1', 2 };
-				yield return new object [] { '1', true, true, false, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask, '1', 2 };
-				yield return new object [] { '1', true, true, true, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
-				yield return new object [] { '1', false, true, true, '1', 2, KeyCode.D1 | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
-				yield return new object [] { '2', false, false, false, '2', 3, KeyCode.D2, '2', 3 };
-				yield return new object [] { '"', true, false, false, '2', 3, (KeyCode)'"' | KeyCode.ShiftMask, '2', 3 };
-				yield return new object [] { '2', true, true, false, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask, '2', 3 };
-				yield return new object [] { '2', true, true, true, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
-				yield return new object [] { '@', false, true, true, '2', 3, (KeyCode)'@' | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
-				yield return new object [] { '3', false, false, false, '3', 4, KeyCode.D3, '3', 4 };
-				yield return new object [] { '#', true, false, false, '3', 4, (KeyCode)'#' | KeyCode.ShiftMask, '3', 4 };
-				yield return new object [] { '3', true, true, false, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask, '3', 4 };
-				yield return new object [] { '3', true, true, true, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
-				yield return new object [] { '£', false, true, true, '3', 4, (KeyCode)'£' | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
-				yield return new object [] { '4', false, false, false, '4', 5, KeyCode.D4, '4', 5 };
-				yield return new object [] { '$', true, false, false, '4', 5, (KeyCode)'$' | KeyCode.ShiftMask, '4', 5 };
-				yield return new object [] { '4', true, true, false, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask, '4', 5 };
-				yield return new object [] { '4', true, true, true, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
-				yield return new object [] { '§', false, true, true, '4', 5, (KeyCode)'§' | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
-				yield return new object [] { '5', false, false, false, '5', 6, KeyCode.D5, '5', 6 };
-				yield return new object [] { '%', true, false, false, '5', 6, (KeyCode)'%' | KeyCode.ShiftMask, '5', 6 };
-				yield return new object [] { '5', true, true, false, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask, '5', 6 };
-				yield return new object [] { '5', true, true, true, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
-				yield return new object [] { '€', false, true, true, '5', 6, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
-				yield return new object [] { '6', false, false, false, '6', 7, KeyCode.D6, '6', 7 };
-				yield return new object [] { '&', true, false, false, '6', 7, (KeyCode)'&' | KeyCode.ShiftMask, '6', 7 };
-				yield return new object [] { '6', true, true, false, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask, '6', 7 };
-				yield return new object [] { '6', true, true, true, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
-				yield return new object [] { '6', false, true, true, '6', 7, KeyCode.D6 | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
-				yield return new object [] { '7', false, false, false, '7', 8, KeyCode.D7, '7', 8 };
-				yield return new object [] { '/', true, false, false, '7', 8, (KeyCode)'/' | KeyCode.ShiftMask, '7', 8 };
-				yield return new object [] { '7', true, true, false, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask, '7', 8 };
-				yield return new object [] { '7', true, true, true, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
-				yield return new object [] { '{', false, true, true, '7', 8, (KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
-				yield return new object [] { '8', false, false, false, '8', 9, KeyCode.D8, '8', 9 };
-				yield return new object [] { '(', true, false, false, '8', 9, (KeyCode)'(' | KeyCode.ShiftMask, '8', 9 };
-				yield return new object [] { '8', true, true, false, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask, '8', 9 };
-				yield return new object [] { '8', true, true, true, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
-				yield return new object [] { '[', false, true, true, '8', 9, (KeyCode)'[' | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
-				yield return new object [] { '9', false, false, false, '9', 10, KeyCode.D9, '9', 10 };
-				yield return new object [] { ')', true, false, false, '9', 10, (KeyCode)')' | KeyCode.ShiftMask, '9', 10 };
-				yield return new object [] { '9', true, true, false, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask, '9', 10 };
-				yield return new object [] { '9', true, true, true, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
-				yield return new object [] { ']', false, true, true, '9', 10, (KeyCode)']' | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
-				yield return new object [] { '0', false, false, false, '0', 11, KeyCode.D0, '0', 11 };
-				yield return new object [] { '=', true, false, false, '0', 11, (KeyCode)'=' | KeyCode.ShiftMask, '0', 11 };
-				yield return new object [] { '0', true, true, false, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask, '0', 11 };
-				yield return new object [] { '0', true, true, true, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
-				yield return new object [] { '}', false, true, true, '0', 11, (KeyCode)'}' | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
-				yield return new object [] { '\'', false, false, false, 219, 12, (KeyCode)'\'', 219, 12 };
-				yield return new object [] { '?', true, false, false, 219, 12, (KeyCode)'?' | KeyCode.ShiftMask, 219, 12 };
-				yield return new object [] { '\'', true, true, false, 219, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask, 219, 12 };
-				yield return new object [] { '\'', true, true, true, 219, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 219, 12 };
-				yield return new object [] { '«', false, false, false, 221, 13, (KeyCode)'«', 221, 13 };
-				yield return new object [] { '»', true, false, false, 221, 13, (KeyCode)'»' | KeyCode.ShiftMask, 221, 13 };
-				yield return new object [] { '«', true, true, false, 221, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask, 221, 13 };
-				yield return new object [] { '«', true, true, true, 221, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 221, 13 };
-				yield return new object [] { 'á', false, false, false, 'A', 30, (KeyCode)'á', 'A', 30 };
-				yield return new object [] { 'Á', true, false, false, 'A', 30, (KeyCode)'Á' | KeyCode.ShiftMask, 'A', 30 };
-				yield return new object [] { 'à', false, false, false, 'A', 30, (KeyCode)'à', 'A', 30 };
-				yield return new object [] { 'À', true, false, false, 'A', 30, (KeyCode)'À' | KeyCode.ShiftMask, 'A', 30 };
-				yield return new object [] { 'é', false, false, false, 'E', 18, (KeyCode)'é', 'E', 18 };
-				yield return new object [] { 'É', true, false, false, 'E', 18, (KeyCode)'É' | KeyCode.ShiftMask, 'E', 18 };
-				yield return new object [] { 'è', false, false, false, 'E', 18, (KeyCode)'è', 'E', 18 };
-				yield return new object [] { 'È', true, false, false, 'E', 18, (KeyCode)'È' | KeyCode.ShiftMask, 'E', 18 };
-				yield return new object [] { 'í', false, false, false, 'I', 23, (KeyCode)'í', 'I', 23 };
-				yield return new object [] { 'Í', true, false, false, 'I', 23, (KeyCode)'Í' | KeyCode.ShiftMask, 'I', 23 };
-				yield return new object [] { 'ì', false, false, false, 'I', 23, (KeyCode)'ì', 'I', 23 };
-				yield return new object [] { 'Ì', true, false, false, 'I', 23, (KeyCode)'Ì' | KeyCode.ShiftMask, 'I', 23 };
-				yield return new object [] { 'ó', false, false, false, 'O', 24, (KeyCode)'ó', 'O', 24 };
-				yield return new object [] { 'Ó', true, false, false, 'O', 24, (KeyCode)'Ó' | KeyCode.ShiftMask, 'O', 24 };
-				yield return new object [] { 'ò', false, false, false, 'O', 24, (KeyCode)'ò', 'O', 24 };
-				yield return new object [] { 'Ò', true, false, false, 'O', 24, (KeyCode)'Ò' | KeyCode.ShiftMask, 'O', 24 };
-				yield return new object [] { 'ú', false, false, false, 'U', 22, (KeyCode)'ú', 'U', 22 };
-				yield return new object [] { 'Ú', true, false, false, 'U', 22, (KeyCode)'Ú' | KeyCode.ShiftMask, 'U', 22 };
-				yield return new object [] { 'ù', false, false, false, 'U', 22, (KeyCode)'ù', 'U', 22 };
-				yield return new object [] { 'Ù', true, false, false, 'U', 22, (KeyCode)'Ù' | KeyCode.ShiftMask, 'U', 22 };
-				yield return new object [] { 'ö', false, false, false, 'O', 24, (KeyCode)'ö', 'O', 24 };
-				yield return new object [] { 'Ö', true, false, false, 'O', 24, (KeyCode)'Ö' | KeyCode.ShiftMask, 'O', 24 };
-				yield return new object [] { '<', false, false, false, 226, 86, (KeyCode)'<', 226, 86 };
-				yield return new object [] { '>', true, false, false, 226, 86, (KeyCode)'>' | KeyCode.ShiftMask, 226, 86 };
-				yield return new object [] { '<', true, true, false, 226, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask, 226, 86 };
-				yield return new object [] { '<', true, true, true, 226, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 226, 86 };
-				yield return new object [] { 'ç', false, false, false, 192, 39, (KeyCode)'ç', 192, 39 };
-				yield return new object [] { 'Ç', true, false, false, 192, 39, (KeyCode)'Ç' | KeyCode.ShiftMask, 192, 39 };
-				yield return new object [] { 'ç', true, true, false, 192, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask, 192, 39 };
-				yield return new object [] { 'ç', true, true, true, 192, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 192, 39 };
-				yield return new object [] { '¨', false, true, true, 187, 26, (KeyCode)'¨' | KeyCode.AltMask | KeyCode.CtrlMask, 187, 26 };
-				yield return new object [] { KeyCode.PageUp, false, false, false, 33, 73, KeyCode.Null, 33, 73 };
-				yield return new object [] { KeyCode.PageUp, true, false, false, 33, 73, KeyCode.Null | KeyCode.ShiftMask, 33, 73 };
-				yield return new object [] { KeyCode.PageUp, true, true, false, 33, 73, KeyCode.Null | KeyCode.ShiftMask | KeyCode.AltMask, 33, 73 };
-				yield return new object [] { KeyCode.PageUp, true, true, true, 33, 73, KeyCode.Null | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 33, 73 };
-			}
+	public static IEnumerable<object []> VKPacket ()
+	{
+		lock (packetLock) {
+			// unicodeCharacter, shift, alt, control, initialVirtualKey, initialScanCode, expectedRemapping, expectedVirtualKey, expectedScanCode
+			yield return new object [] { 'a', false, false, false, 'A', 30, KeyCode.A, 'A', 30 };
+			yield return new object [] { 'A', true, false, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask, 'A', 30 };
+			yield return new object [] { 'A', true, true, false, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, 'A', 30 };
+			yield return new object [] { 'A', true, true, true, 'A', 30, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'A', 30 };
+			yield return new object [] { 'z', false, false, false, 'Z', 44, KeyCode.Z, 'Z', 44 };
+			yield return new object [] { 'Z', true, false, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask, 'Z', 44 };
+			yield return new object [] { 'Z', true, true, false, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask, 'Z', 44 };
+			yield return new object [] { 'Z', true, true, true, 'Z', 44, KeyCode.Z | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, 'Z', 44 };
+			yield return new object [] { '英', false, false, false, '\0', 0, (KeyCode)'英', '\0', 0 };
+			yield return new object [] { '英', true, false, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask, '\0', 0 };
+			yield return new object [] { '英', true, true, false, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask, '\0', 0 };
+			yield return new object [] { '英', true, true, true, '\0', 0, (KeyCode)'英' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0', 0 };
+			yield return new object [] { '+', false, false, false, VK.OEM_PLUS, 26, (KeyCode)'+', VK.OEM_PLUS, 26 };
+			yield return new object [] { '*', true, false, false, VK.OEM_PLUS, 26, (KeyCode)'*' | KeyCode.ShiftMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '+', true, true, false, VK.OEM_PLUS, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '+', true, true, true, VK.OEM_PLUS, 26, (KeyCode)'+' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '1', false, false, false, '1', 2, KeyCode.D1, '1', 2 };
+			yield return new object [] { '!', true, false, false, '1', 2, (KeyCode)'!' | KeyCode.ShiftMask, '1', 2 };
+			yield return new object [] { '1', true, true, false, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask, '1', 2 };
+			yield return new object [] { '1', true, true, true, '1', 2, KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
+			yield return new object [] { '1', false, true, true, '1', 2, KeyCode.D1 | KeyCode.AltMask | KeyCode.CtrlMask, '1', 2 };
+			yield return new object [] { '2', false, false, false, '2', 3, KeyCode.D2, '2', 3 };
+			yield return new object [] { '"', true, false, false, '2', 3, (KeyCode)'"' | KeyCode.ShiftMask, '2', 3 };
+			yield return new object [] { '2', true, true, false, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask, '2', 3 };
+			yield return new object [] { '2', true, true, true, '2', 3, KeyCode.D2 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
+			yield return new object [] { '@', false, true, true, '2', 3, (KeyCode)'@' | KeyCode.AltMask | KeyCode.CtrlMask, '2', 3 };
+			yield return new object [] { '3', false, false, false, '3', 4, KeyCode.D3, '3', 4 };
+			yield return new object [] { '#', true, false, false, '3', 4, (KeyCode)'#' | KeyCode.ShiftMask, '3', 4 };
+			yield return new object [] { '3', true, true, false, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask, '3', 4 };
+			yield return new object [] { '3', true, true, true, '3', 4, KeyCode.D3 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
+			yield return new object [] { '£', false, true, true, '3', 4, (KeyCode)'£' | KeyCode.AltMask | KeyCode.CtrlMask, '3', 4 };
+			yield return new object [] { '4', false, false, false, '4', 5, KeyCode.D4, '4', 5 };
+			yield return new object [] { '$', true, false, false, '4', 5, (KeyCode)'$' | KeyCode.ShiftMask, '4', 5 };
+			yield return new object [] { '4', true, true, false, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask, '4', 5 };
+			yield return new object [] { '4', true, true, true, '4', 5, KeyCode.D4 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
+			yield return new object [] { '§', false, true, true, '4', 5, (KeyCode)'§' | KeyCode.AltMask | KeyCode.CtrlMask, '4', 5 };
+			yield return new object [] { '5', false, false, false, '5', 6, KeyCode.D5, '5', 6 };
+			yield return new object [] { '%', true, false, false, '5', 6, (KeyCode)'%' | KeyCode.ShiftMask, '5', 6 };
+			yield return new object [] { '5', true, true, false, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask, '5', 6 };
+			yield return new object [] { '5', true, true, true, '5', 6, KeyCode.D5 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
+			yield return new object [] { '€', false, true, true, '5', 6, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, '5', 6 };
+			yield return new object [] { '6', false, false, false, '6', 7, KeyCode.D6, '6', 7 };
+			yield return new object [] { '&', true, false, false, '6', 7, (KeyCode)'&' | KeyCode.ShiftMask, '6', 7 };
+			yield return new object [] { '6', true, true, false, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask, '6', 7 };
+			yield return new object [] { '6', true, true, true, '6', 7, KeyCode.D6 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
+			yield return new object [] { '6', false, true, true, '6', 7, KeyCode.D6 | KeyCode.AltMask | KeyCode.CtrlMask, '6', 7 };
+			yield return new object [] { '7', false, false, false, '7', 8, KeyCode.D7, '7', 8 };
+			yield return new object [] { '/', true, false, false, '7', 8, (KeyCode)'/' | KeyCode.ShiftMask, '7', 8 }; // BUGBUG: This is not true for ENG keyboards. Shift-7 is &.
+			yield return new object [] { '7', true, true, false, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask, '7', 8 };
+			yield return new object [] { '7', true, true, true, '7', 8, KeyCode.D7 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
+			yield return new object [] { '{', false, true, true, '7', 8, (KeyCode)'{' | KeyCode.AltMask | KeyCode.CtrlMask, '7', 8 };
+			yield return new object [] { '8', false, false, false, '8', 9, KeyCode.D8, '8', 9 };
+			yield return new object [] { '(', true, false, false, '8', 9, (KeyCode)'(' | KeyCode.ShiftMask, '8', 9 };
+			yield return new object [] { '8', true, true, false, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask, '8', 9 };
+			yield return new object [] { '8', true, true, true, '8', 9, KeyCode.D8 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
+			yield return new object [] { '[', false, true, true, '8', 9, (KeyCode)'[' | KeyCode.AltMask | KeyCode.CtrlMask, '8', 9 };
+			yield return new object [] { '9', false, false, false, '9', 10, KeyCode.D9, '9', 10 };
+			yield return new object [] { ')', true, false, false, '9', 10, (KeyCode)')' | KeyCode.ShiftMask, '9', 10 };
+			yield return new object [] { '9', true, true, false, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask, '9', 10 };
+			yield return new object [] { '9', true, true, true, '9', 10, KeyCode.D9 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
+			yield return new object [] { ']', false, true, true, '9', 10, (KeyCode)']' | KeyCode.AltMask | KeyCode.CtrlMask, '9', 10 };
+			yield return new object [] { '0', false, false, false, '0', 11, KeyCode.D0, '0', 11 };
+			yield return new object [] { '=', true, false, false, '0', 11, (KeyCode)'=' | KeyCode.ShiftMask, '0', 11 };
+			yield return new object [] { '0', true, true, false, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask, '0', 11 };
+			yield return new object [] { '0', true, true, true, '0', 11, KeyCode.D0 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
+			yield return new object [] { '}', false, true, true, '0', 11, (KeyCode)'}' | KeyCode.AltMask | KeyCode.CtrlMask, '0', 11 };
+			yield return new object [] { '\'', false, false, false, VK.OEM_4, 12, (KeyCode)'\'', VK.OEM_4, 12 };
+			yield return new object [] { '?', true, false, false, VK.OEM_4, 12, (KeyCode)'?' | KeyCode.ShiftMask, VK.OEM_4, 12 };
+			yield return new object [] { '\'', true, true, false, VK.OEM_4, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_4, 12 };
+			yield return new object [] { '\'', true, true, true, VK.OEM_4, 12, (KeyCode)'\'' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_4, 12 };
+			yield return new object [] { '«', false, false, false, VK.OEM_6, 13, (KeyCode)'«', VK.OEM_6, 13 };
+			yield return new object [] { '»', true, false, false, VK.OEM_6, 13, (KeyCode)'»' | KeyCode.ShiftMask, VK.OEM_6, 13 };
+			yield return new object [] { '«', true, true, false, VK.OEM_6, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_6, 13 };
+			yield return new object [] { '«', true, true, true, VK.OEM_6, 13, (KeyCode)'«' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_6, 13 };
+			yield return new object [] { 'á', false, false, false, 'A', 30, (KeyCode)'á', 'A', 30 };
+			yield return new object [] { 'Á', true, false, false, 'A', 30, (KeyCode)'Á' | KeyCode.ShiftMask, 'A', 30 };
+			yield return new object [] { 'à', false, false, false, 'A', 30, (KeyCode)'à', 'A', 30 };
+			yield return new object [] { 'À', true, false, false, 'A', 30, (KeyCode)'À' | KeyCode.ShiftMask, 'A', 30 };
+			yield return new object [] { 'é', false, false, false, 'E', 18, (KeyCode)'é', 'E', 18 };
+			yield return new object [] { 'É', true, false, false, 'E', 18, (KeyCode)'É' | KeyCode.ShiftMask, 'E', 18 };
+			yield return new object [] { 'è', false, false, false, 'E', 18, (KeyCode)'è', 'E', 18 };
+			yield return new object [] { 'È', true, false, false, 'E', 18, (KeyCode)'È' | KeyCode.ShiftMask, 'E', 18 };
+			yield return new object [] { 'í', false, false, false, 'I', 23, (KeyCode)'í', 'I', 23 };
+			yield return new object [] { 'Í', true, false, false, 'I', 23, (KeyCode)'Í' | KeyCode.ShiftMask, 'I', 23 };
+			yield return new object [] { 'ì', false, false, false, 'I', 23, (KeyCode)'ì', 'I', 23 };
+			yield return new object [] { 'Ì', true, false, false, 'I', 23, (KeyCode)'Ì' | KeyCode.ShiftMask, 'I', 23 };
+			yield return new object [] { 'ó', false, false, false, 'O', 24, (KeyCode)'ó', 'O', 24 };
+			yield return new object [] { 'Ó', true, false, false, 'O', 24, (KeyCode)'Ó' | KeyCode.ShiftMask, 'O', 24 };
+			yield return new object [] { 'ò', false, false, false, 'O', 24, (KeyCode)'ò', 'O', 24 };
+			yield return new object [] { 'Ò', true, false, false, 'O', 24, (KeyCode)'Ò' | KeyCode.ShiftMask, 'O', 24 };
+			yield return new object [] { 'ú', false, false, false, 'U', 22, (KeyCode)'ú', 'U', 22 };
+			yield return new object [] { 'Ú', true, false, false, 'U', 22, (KeyCode)'Ú' | KeyCode.ShiftMask, 'U', 22 };
+			yield return new object [] { 'ù', false, false, false, 'U', 22, (KeyCode)'ù', 'U', 22 };
+			yield return new object [] { 'Ù', true, false, false, 'U', 22, (KeyCode)'Ù' | KeyCode.ShiftMask, 'U', 22 };
+			yield return new object [] { 'ö', false, false, false, 'O', 24, (KeyCode)'ö', 'O', 24 };
+			yield return new object [] { 'Ö', true, false, false, 'O', 24, (KeyCode)'Ö' | KeyCode.ShiftMask, 'O', 24 };
+			yield return new object [] { '<', false, false, false, VK.OEM_102, 86, (KeyCode)'<', VK.OEM_102, 86 };
+			yield return new object [] { '>', true, false, false, VK.OEM_102, 86, (KeyCode)'>' | KeyCode.ShiftMask, VK.OEM_102, 86 };
+			yield return new object [] { '<', true, true, false, VK.OEM_102, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_102, 86 };
+			yield return new object [] { '<', true, true, true, VK.OEM_102, 86, (KeyCode)'<' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_102, 86 };
+			yield return new object [] { 'ç', false, false, false, VK.OEM_3, 39, (KeyCode)'ç', VK.OEM_3, 39 };
+			yield return new object [] { 'Ç', true, false, false, VK.OEM_3, 39, (KeyCode)'Ç' | KeyCode.ShiftMask, VK.OEM_3, 39 };
+			yield return new object [] { 'ç', true, true, false, VK.OEM_3, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask, VK.OEM_3, 39 };
+			yield return new object [] { 'ç', true, true, true, VK.OEM_3, 39, (KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_3, 39 };
+			yield return new object [] { '¨', false, true, true, VK.OEM_PLUS, 26, (KeyCode)'¨' | KeyCode.AltMask | KeyCode.CtrlMask, VK.OEM_PLUS, 26 };
+			yield return new object [] { '\0', false, false, false, VK.PRIOR, 73, KeyCode.PageUp, VK.PRIOR, 73 };
+			yield return new object [] { '\0', true, false, false, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask, VK.PRIOR, 73 };
+			yield return new object [] { '\0', true, true, false, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask, VK.PRIOR, 73 };
+			yield return new object [] { '\0', true, true, true, VK.PRIOR, 73, KeyCode.PageUp | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, VK.PRIOR, 73 };
+			yield return new object [] { '~', false, false, false, VK.SPACE, 57, (KeyCode)'~', VK.SPACE, 57 };
+			yield return new object [] { '^', false, false, false, VK.SPACE, 57, (KeyCode)'^', VK.SPACE, 57 };
 		}
+	}
+
+	[Theory]
+	[InlineData ('a', ConsoleKey.A, 'a', ConsoleKey.A)]
+	[InlineData ('A', ConsoleKey.A, 'A', ConsoleKey.A)]
+	[InlineData ('á', ConsoleKey.A, 'á', ConsoleKey.A)]
+	[InlineData ('Á', ConsoleKey.A, 'Á', ConsoleKey.A)]
+	[InlineData ('à', ConsoleKey.A, 'à', ConsoleKey.A)]
+	[InlineData ('À', ConsoleKey.A, 'À', ConsoleKey.A)]
+	[InlineData ('5', ConsoleKey.D5, '5', ConsoleKey.D5)]
+	[InlineData ('%', ConsoleKey.D5, '%', ConsoleKey.D5)]
+	[InlineData ('€', ConsoleKey.D5, '€', ConsoleKey.D5)]
+	[InlineData ('?', ConsoleKey.Oem4, '?', ConsoleKey.Oem4)]
+	[InlineData ('\'', ConsoleKey.Oem4, '\'', ConsoleKey.Oem4)]
+	[InlineData ('q', ConsoleKey.Q, 'q', ConsoleKey.Q)]
+	[InlineData ('\0', ConsoleKey.F2, '\0', ConsoleKey.F2)]
+	[InlineData ('英', ConsoleKey.None, '英', ConsoleKey.None)]
+	[InlineData ('´', ConsoleKey.None, '´', ConsoleKey.Oem1)]
+	[InlineData ('`', ConsoleKey.None, '`', ConsoleKey.Oem1)]
+	//[InlineData ('~', ConsoleKey.None, '~', ConsoleKey.Oem2)]
+	//[InlineData ('^', ConsoleKey.None, '^', ConsoleKey.Oem2)] // BUGBUG: '^' is Shift-6 on ENG keyboard,not Oem2.
+                                                                  // For the US standard keyboard, Oem2 is the /? key
+	public void EncodeKeyCharForVKPacket_DecodeVKPacketToKConsoleKeyInfo (char keyChar, ConsoleKey consoleKey, char expectedChar, ConsoleKey expectedConsoleKey)
+	{
+		var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, false, false, false);
+		var encodedKeyChar = ConsoleKeyMapping.EncodeKeyCharForVKPacket (consoleKeyInfo);
+		var encodedConsoleKeyInfo = new ConsoleKeyInfo (encodedKeyChar, ConsoleKey.Packet, false, false, false);
+		var decodedConsoleKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (encodedConsoleKeyInfo);
+		Assert.Equal (consoleKeyInfo.Key, consoleKey);
+		Assert.Equal (expectedConsoleKey, decodedConsoleKeyInfo.Key);
+		Assert.Equal (expectedChar, decodedConsoleKeyInfo.KeyChar);
+	}
 
-		IEnumerator IEnumerable.GetEnumerator ()
-		{
-			return GetEnumerator ();
+	
+	[Theory]
+	[InlineData ((KeyCode)'a', false, ConsoleKey.A, 'a')]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, false, ConsoleKey.A, 'A')]
+	[InlineData ((KeyCode)'á', false, ConsoleKey.A, 'á')]
+	[InlineData ((KeyCode)'Á' | KeyCode.ShiftMask, false, ConsoleKey.A, 'Á')]
+	[InlineData ((KeyCode)'à', false, ConsoleKey.A, 'à')]
+	[InlineData ((KeyCode)'À' | KeyCode.ShiftMask, false, ConsoleKey.A, 'À')]
+	[InlineData (KeyCode.D5, false, ConsoleKey.D5, '5')]
+	[InlineData ((KeyCode)'%' | KeyCode.ShiftMask, false, ConsoleKey.D5, '%')]
+	//[InlineData ((KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask, false, ConsoleKey.D5, '€')] // Bogus test. This is not true on ENG keyboard layout.
+	[InlineData ((KeyCode)'?' | KeyCode.ShiftMask, false, ConsoleKey.Oem4, '?')]
+	[InlineData ((KeyCode)'\'', false, ConsoleKey.Oem4, '\'')]
+	[InlineData ((KeyCode)'q', false, ConsoleKey.Q, 'q')]
+	[InlineData (KeyCode.F2, true, ConsoleKey.F2, 'q')]
+	[InlineData ((KeyCode)'英', false, ConsoleKey.None, '英')]
+	[InlineData (KeyCode.Enter, true, ConsoleKey.Enter, '\r')]
+	public void MapKeyCodeToConsoleKey_GetKeyCharFromUnicodeChar (KeyCode keyCode, bool expectedIsConsoleKey, ConsoleKey expectedConsoleKey, char expectedConsoleKeyChar)
+	{
+		var modifiers = ConsoleKeyMapping.MapToConsoleModifiers (keyCode);
+		var consoleKey = ConsoleKeyMapping.MapKeyCodeToConsoleKey (keyCode, out bool isConsoleKey);
+		if (isConsoleKey) {
+			Assert.True (isConsoleKey == expectedIsConsoleKey);
+			Assert.Equal (expectedConsoleKey , (ConsoleKey)consoleKey);
+			Assert.Equal (expectedConsoleKeyChar, consoleKey);
+		} else {
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromUnicodeChar (consoleKey, modifiers, out consoleKey, out _, isConsoleKey);
+			Assert.True (isConsoleKey == expectedIsConsoleKey);
+			Assert.Equal (expectedConsoleKey , (ConsoleKey)consoleKey);
+			Assert.Equal (expectedConsoleKeyChar, keyChar);
 		}
 	}
+#endif
+	
+	[Theory]
+	[InlineData ('a', ConsoleKey.A, false, false, false, (KeyCode)'a')]
+	[InlineData ('A', ConsoleKey.A, true, false, false, KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('á', ConsoleKey.A, false, false, false, (KeyCode)'á')]
+	[InlineData ('Á', ConsoleKey.A, true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)]
+	[InlineData ('à', ConsoleKey.A, false, false, false, (KeyCode)'à')]
+	[InlineData ('À', ConsoleKey.A, true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)]
+	[InlineData ('5', ConsoleKey.D5, false, false, false, KeyCode.D5)]
+	[InlineData ('%', ConsoleKey.D5, true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)]
+	[InlineData ('€', ConsoleKey.D5, false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)]
+	[InlineData ('?', ConsoleKey.Oem4, true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)]
+	[InlineData ('\'', ConsoleKey.Oem4, false, false, false, (KeyCode)'\'')]
+	[InlineData ('q', ConsoleKey.Q, false, false, false, (KeyCode)'q')]
+	[InlineData ('\0', ConsoleKey.F2, false, false, false, KeyCode.F2)]
+	[InlineData ('英', ConsoleKey.None, false, false, false, (KeyCode)'英')]
+	[InlineData ('\r', ConsoleKey.Enter, false, false, false, KeyCode.Enter)]
+	public void MapConsoleKeyInfoToKeyCode_Also_Return_Modifiers (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control, KeyCode expectedKeyCode)
+	{
+		var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+		var keyCode = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
+
+		Assert.Equal (keyCode, expectedKeyCode);
+	}
+
+	[Theory]
+	[InlineData ('a', false, false, false, (KeyCode)'a')]
+	[InlineData ('A', true, false, false, KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('á', false, false, false, (KeyCode)'á')]
+	[InlineData ('Á', true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)]
+	[InlineData ('à', false, false, false, (KeyCode)'à')]
+	[InlineData ('À', true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)]
+	[InlineData ('5', false, false, false, KeyCode.D5)]
+	[InlineData ('%', true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)]
+	[InlineData ('€', false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)]
+	[InlineData ('?', true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)]
+	[InlineData ('\'', false, false, false, (KeyCode)'\'')]
+	[InlineData ('q', false, false, false, (KeyCode)'q')]
+	[InlineData ((uint)KeyCode.F2, false, false, false, KeyCode.F2)]
+	[InlineData ('英', false, false, false, (KeyCode)'英')]
+	[InlineData ('\r', false, false, false, KeyCode.Enter)]
+	[InlineData ('\n', false, false, false, (KeyCode)'\n')]
+	public void MapToKeyCodeModifiers_Tests (uint keyChar, bool shift, bool alt, bool control, KeyCode excpectedKeyCode)
+	{
+		var modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control);
+		KeyCode keyCode = (KeyCode)keyChar;
+		keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
+
+		Assert.Equal (keyCode, excpectedKeyCode);
+	}
+
+	[Theory]
+	[InlineData ('a', ConsoleKey.A, false, false, false, 30)]
+	[InlineData ('A', ConsoleKey.A, true, false, false, 30)]
+	[InlineData ('á', ConsoleKey.A, false, false, false, 30)]
+	[InlineData ('Á', ConsoleKey.A, true, false, false, 30)]
+	[InlineData ('à', ConsoleKey.A, false, false, false, 30)]
+	[InlineData ('À', ConsoleKey.A, true, false, false, 30)]
+	[InlineData ('0', ConsoleKey.D0, false, false, false, 11)]
+	[InlineData ('=', ConsoleKey.D0, true, false, false, 11)]
+	[InlineData ('}', ConsoleKey.D0, false, true, true, 11)]
+	[InlineData ('1', ConsoleKey.D1, false, false, false, 2)]
+	[InlineData ('!', ConsoleKey.D1, true, false, false, 2)]
+	[InlineData ('2', ConsoleKey.D2, false, false, false, 3)]
+	[InlineData ('"', ConsoleKey.D2, true, false, false, 3)]
+	[InlineData ('@', ConsoleKey.D2, false, true, true, 3)]
+	[InlineData ('3', ConsoleKey.D3, false, false, false, 4)]
+	[InlineData ('#', ConsoleKey.D3, true, false, false, 4)]
+	[InlineData ('£', ConsoleKey.D3, false, true, true, 4)]
+	[InlineData ('4', ConsoleKey.D4, false, false, false, 5)]
+	[InlineData ('$', ConsoleKey.D4, true, false, false, 5)]
+	[InlineData ('§', ConsoleKey.D4, false, true, true, 5)]
+	[InlineData ('5', ConsoleKey.D5, false, false, false, 6)]
+	[InlineData ('%', ConsoleKey.D5, true, false, false, 6)]
+	[InlineData ('€', ConsoleKey.D5, false, true, true, 6)]
+	[InlineData ('6', ConsoleKey.D6, false, false, false, 7)]
+	[InlineData ('&', ConsoleKey.D6, true, false, false, 7)]
+	[InlineData ('7', ConsoleKey.D7, false, false, false, 8)]
+	[InlineData ('/', ConsoleKey.D7, true, false, false, 8)]
+	[InlineData ('{', ConsoleKey.D7, false, true, true, 8)]
+	[InlineData ('8', ConsoleKey.D8, false, false, false, 9)]
+	[InlineData ('(', ConsoleKey.D8, true, false, false, 9)]
+	[InlineData ('[', ConsoleKey.D8, false, true, true, 9)]
+	[InlineData ('9', ConsoleKey.D9, false, false, false, 10)]
+	[InlineData (')', ConsoleKey.D9, true, false, false, 10)]
+	[InlineData (']', ConsoleKey.D9, false, true, true, 10)]
+	[InlineData ('´', ConsoleKey.Oem1, false, false, false, 27)]
+	[InlineData ('`', ConsoleKey.Oem1, true, false, false, 27)]
+	[InlineData ('~', ConsoleKey.Oem2, false, false, false, 43)]
+	[InlineData ('^', ConsoleKey.Oem2, true, false, false, 43)]
+	[InlineData ('ç', ConsoleKey.Oem3, false, false, false, 39)]
+	[InlineData ('Ç', ConsoleKey.Oem3, true, false, false, 39)]
+	[InlineData ('\'', ConsoleKey.Oem4, false, false, false, 12)]
+	[InlineData ('?', ConsoleKey.Oem4, true, false, false, 12)]
+	[InlineData ('\\', ConsoleKey.Oem5, false, true, true, 41)]
+	[InlineData ('|', ConsoleKey.Oem5, true, false, false, 41)]
+	[InlineData ('«', ConsoleKey.Oem6, false, true, true, 13)]
+	[InlineData ('»', ConsoleKey.Oem6, true, false, false, 13)]
+	[InlineData ('º', ConsoleKey.Oem7, false, true, true, 40)]
+	[InlineData ('ª', ConsoleKey.Oem7, true, false, false, 40)]
+	[InlineData ('+', ConsoleKey.OemPlus, false, true, true, 26)]
+	[InlineData ('*', ConsoleKey.OemPlus, true, false, false, 26)]
+	[InlineData ('¨', ConsoleKey.OemPlus, false, true, true, 26)]
+	[InlineData (',', ConsoleKey.OemComma, false, true, true, 51)]
+	[InlineData (';', ConsoleKey.OemComma, true, false, false, 51)]
+	[InlineData ('.', ConsoleKey.OemPeriod, false, true, true, 52)]
+	[InlineData (':', ConsoleKey.OemPeriod, true, false, false, 52)]
+	[InlineData ('-', ConsoleKey.OemMinus, false, true, true, 53)]
+	[InlineData ('_', ConsoleKey.OemMinus, true, false, false, 53)]
+	[InlineData ('q', ConsoleKey.Q, false, false, false, 16)]
+	[InlineData ('\0', ConsoleKey.F2, false, false, false, 60)]
+	[InlineData ('英', ConsoleKey.None, false, false, false, 0)]
+	[InlineData ('英', ConsoleKey.None, true, false, false, 0)]
+	public void GetScanCodeFromConsoleKeyInfo_Tests (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control, uint expectedScanCode)
+	{
+		var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
+		var scanCode = ConsoleKeyMapping.GetScanCodeFromConsoleKeyInfo (consoleKeyInfo);
+
+		Assert.Equal (scanCode, expectedScanCode);
+	}
+
+	[Theory]
+	[InlineData ('a', 'A', KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('z', 'Z', KeyCode.Z | KeyCode.ShiftMask)]
+	[InlineData ('á', 'Á', (KeyCode)'Á' | KeyCode.ShiftMask)]
+	[InlineData ('à', 'À', (KeyCode)'À' | KeyCode.ShiftMask)]
+	[InlineData ('ý', 'Ý', (KeyCode)'Ý' | KeyCode.ShiftMask)]
+	[InlineData ('1', '!', (KeyCode)'!' | KeyCode.ShiftMask)]
+	[InlineData ('2', '"', (KeyCode)'"' | KeyCode.ShiftMask)]
+	[InlineData ('3', '#', (KeyCode)'#' | KeyCode.ShiftMask)]
+	[InlineData ('4', '$', (KeyCode)'$' | KeyCode.ShiftMask)]
+	[InlineData ('5', '%', (KeyCode)'%' | KeyCode.ShiftMask)]
+	[InlineData ('6', '&', (KeyCode)'&' | KeyCode.ShiftMask)]
+	[InlineData ('7', '/', (KeyCode)'/' | KeyCode.ShiftMask)]
+	[InlineData ('8', '(', (KeyCode)'(' | KeyCode.ShiftMask)]
+	[InlineData ('9', ')', (KeyCode)')' | KeyCode.ShiftMask)]
+	[InlineData ('0', '=', (KeyCode)'=' | KeyCode.ShiftMask)]
+	[InlineData ('\\', '|', (KeyCode)'|' | KeyCode.ShiftMask)]
+	[InlineData ('\'', '?', (KeyCode)'?' | KeyCode.ShiftMask)]
+	[InlineData ('«', '»', (KeyCode)'»' | KeyCode.ShiftMask)]
+	[InlineData ('+', '*', (KeyCode)'*' | KeyCode.ShiftMask)]
+	[InlineData ('´', '`', (KeyCode)'`' | KeyCode.ShiftMask)]
+	[InlineData ('º', 'ª', (KeyCode)'ª' | KeyCode.ShiftMask)]
+	[InlineData ('~', '^', (KeyCode)'^' | KeyCode.ShiftMask)]
+	[InlineData ('<', '>', (KeyCode)'>' | KeyCode.ShiftMask)]
+	[InlineData (',', ';', (KeyCode)';' | KeyCode.ShiftMask)]
+	[InlineData ('.', ':', (KeyCode)':' | KeyCode.ShiftMask)]
+	[InlineData ('-', '_', (KeyCode)'_' | KeyCode.ShiftMask)]
+	public void GetKeyChar_Shifted_Char_From_UnShifted_Char (char unicodeChar, char expectedKeyChar, KeyCode excpectedKeyCode)
+	{
+		var modifiers = ConsoleKeyMapping.GetModifiers (true, false, false);
+		var keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers);
+		Assert.Equal (keyChar, expectedKeyChar);
+
+		KeyCode keyCode = (KeyCode)keyChar;
+		keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
+
+		Assert.Equal (keyCode, excpectedKeyCode);
+	}
+
+	[Theory]
+	[InlineData ('A', 'a', (KeyCode)'a')]
+	[InlineData ('Z', 'z', (KeyCode)'z')]
+	[InlineData ('Á', 'á', (KeyCode)'á')]
+	[InlineData ('À', 'à', (KeyCode)'à')]
+	[InlineData ('Ý', 'ý', (KeyCode)'ý')]
+	[InlineData ('!', '1', KeyCode.D1)]
+	[InlineData ('"', '2', KeyCode.D2)]
+	[InlineData ('#', '3', KeyCode.D3)]
+	[InlineData ('$', '4', KeyCode.D4)]
+	[InlineData ('%', '5', KeyCode.D5)]
+	[InlineData ('&', '6', KeyCode.D6)]
+	[InlineData ('/', '7', KeyCode.D7)]
+	[InlineData ('(', '8', KeyCode.D8)]
+	[InlineData (')', '9', KeyCode.D9)]
+	[InlineData ('=', '0', KeyCode.D0)]
+	[InlineData ('|', '\\', (KeyCode)'\\')]
+	[InlineData ('?', '\'', (KeyCode)'\'')]
+	[InlineData ('»', '«', (KeyCode)'«')]
+	[InlineData ('*', '+', (KeyCode)'+')]
+	[InlineData ('`', '´', (KeyCode)'´')]
+	[InlineData ('ª', 'º', (KeyCode)'º')]
+	[InlineData ('^', '~', (KeyCode)'~')]
+	[InlineData ('>', '<', (KeyCode)'<')]
+	[InlineData (';', ',', (KeyCode)',')]
+	[InlineData (':', '.', (KeyCode)'.')]
+	[InlineData ('_', '-', (KeyCode)'-')]
+	public void GetKeyChar_UnShifted_Char_From_Shifted_Char (char unicodeChar, char expectedKeyChar, KeyCode excpectedKeyCode)
+	{
+		var modifiers = ConsoleKeyMapping.GetModifiers (false, false, false);
+		var keyChar = ConsoleKeyMapping.GetKeyChar (unicodeChar, modifiers);
+		Assert.Equal (keyChar, expectedKeyChar);
+
+		KeyCode keyCode = (KeyCode)keyChar;
+		keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
+
+		Assert.Equal (keyCode, excpectedKeyCode);
+	}
 }

+ 31 - 27
UnitTests/ConsoleDrivers/ConsoleScrolllingTests.cs

@@ -8,31 +8,35 @@ using Xunit.Abstractions;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.DriverTests {
-	public class ConsoleScrollingTests {
-		readonly ITestOutputHelper output;
-
-		public ConsoleScrollingTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		public void Left_And_Top_Is_Always_Zero (Type driverType)
-		{
-			var driver = (FakeDriver)Activator.CreateInstance (driverType);
-			Application.Init (driver);
-
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			driver.SetWindowPosition (5, 5);
-			Assert.Equal (0, Console.WindowLeft);
-			Assert.Equal (0, Console.WindowTop);
-
-			Application.Shutdown ();
-		}
-		
+namespace Terminal.Gui.DriverTests; 
+
+public class ConsoleScrollingTests {
+	readonly ITestOutputHelper output;
+
+	public ConsoleScrollingTests (ITestOutputHelper output)
+	{
+		ConsoleDriver.RunningUnitTests = true;
+		this.output = output;
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	//[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	//[InlineData (typeof (WindowsDriver))]
+	//[InlineData (typeof (CursesDriver))]
+	public void Left_And_Top_Is_Always_Zero (Type driverType)
+	{
+		var driver = (FakeDriver)Activator.CreateInstance (driverType);
+		Application.Init (driver);
+
+		Assert.Equal (0, Console.WindowLeft);
+		Assert.Equal (0, Console.WindowTop);
+
+		driver.SetWindowPosition (5, 5);
+		Assert.Equal (0, Console.WindowLeft);
+		Assert.Equal (0, Console.WindowTop);
+
+		Application.Shutdown ();
 	}
-}
+}

+ 12 - 9
UnitTests/ConsoleDrivers/ContentsTests.cs

@@ -10,6 +10,7 @@ using Xunit.Abstractions;
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.DriverTests;
+
 public class ContentsTests {
 	readonly ITestOutputHelper output;
 
@@ -23,13 +24,14 @@ public class ContentsTests {
 	[Theory]
 	[InlineData (typeof (FakeDriver))]
 	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
 	//[InlineData (typeof (CursesDriver))] // TODO: Uncomment when #2796 and #2615 are fixed
 	//[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed
 	public void AddStr_Combining_Character_1st_Column (Type driverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		driver.Init ();
-		var expected = "\u0301!";
+		string expected = "\u0301!";
 		driver.AddStr ("\u0301!"); // acute accent + exclamation mark
 		TestHelpers.AssertDriverContentsAre (expected, output, driver);
 
@@ -39,6 +41,7 @@ public class ContentsTests {
 	[Theory]
 	[InlineData (typeof (FakeDriver))]
 	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
 	//[InlineData (typeof (CursesDriver))] // TODO: Uncomment when #2796 and #2615 are fixed
 	//[InlineData (typeof (WindowsDriver))] // TODO: Uncomment when #2610 is fixed
 	public void AddStr_With_Combining_Characters (Type driverType)
@@ -46,18 +49,18 @@ public class ContentsTests {
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		driver.Init ();
 
-		var acuteaccent = new System.Text.Rune (0x0301); // Combining acute accent (é)
-		var combined = "e" + acuteaccent;
-		var expected = "é";
+		var acuteaccent = new Rune (0x0301); // Combining acute accent (é)
+		string combined = "e" + acuteaccent;
+		string expected = "é";
 
 		driver.AddStr (combined);
 		TestHelpers.AssertDriverContentsAre (expected, output, driver);
 
 		// 3 char combine
 		// a + ogonek + acute = <U+0061, U+0328, U+0301> ( ą́ )
-		var ogonek = new System.Text.Rune (0x0328); // Combining ogonek (a small hook or comma shape)
+		var ogonek = new Rune (0x0328); // Combining ogonek (a small hook or comma shape)
 		combined = "a" + ogonek + acuteaccent;
-		expected = ("a" + ogonek).Normalize(NormalizationForm.FormC); // See Issue #2616
+		expected = ("a" + ogonek).Normalize (NormalizationForm.FormC); // See Issue #2616
 
 		driver.Move (0, 0);
 		driver.AddStr (combined);
@@ -93,8 +96,9 @@ public class ContentsTests {
 	[Theory]
 	[InlineData (typeof (FakeDriver))]
 	[InlineData (typeof (NetDriver))]
-	[InlineData (typeof (CursesDriver))]
+	//[InlineData (typeof (ANSIDriver))]
 	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
 	public void Move_Bad_Coordinates (Type driverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
@@ -160,5 +164,4 @@ public class ContentsTests {
 	// Refresh works correctly
 
 	// IsDirty tests
-}
-
+}

+ 64 - 64
UnitTests/ConsoleDrivers/DriverColorTests.cs

@@ -4,69 +4,69 @@ using Xunit;
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.DriverTests {
-	public class DriverColorTests {
-		public DriverColorTests ()
-		{
-			ConsoleDriver.RunningUnitTests = true;
-		}
-		
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		[InlineData (typeof (NetDriver))]
-		[InlineData (typeof (CursesDriver))]
-		[InlineData (typeof (WindowsDriver))]
-		public void SetColors_Changes_Colors (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			driver.Init ();
-
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-
-			Console.ForegroundColor = ConsoleColor.Red;
-			Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
-
-			Console.BackgroundColor = ConsoleColor.Green;
-			Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
-
-			Console.ResetColor ();
-			Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
-			Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
-
-			driver.End ();
-		}
-
-
-		[Theory]
-		[InlineData (typeof (FakeDriver), false)]
-		[InlineData (typeof (NetDriver), true)]
-		[InlineData (typeof (CursesDriver), false)]
-		[InlineData (typeof (WindowsDriver), true)] // Because we're not Windows Terminal
-		public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			driver.Init ();
-
-			Assert.Equal (expectedSetting, driver.SupportsTrueColor);
-
-			driver.End ();
-		}
-
-		[Theory]
-		[InlineData (typeof (FakeDriver))]
-		[InlineData (typeof (NetDriver))]
-		[InlineData (typeof (CursesDriver))]
-		[InlineData (typeof (WindowsDriver))]
-		public void Force16Colors_Sets (Type driverType)
-		{
-			var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
-			driver.Init ();
-
-			driver.Force16Colors = true;
-			Assert.True (driver.Force16Colors);
-
-			driver.End ();
-		}
+namespace Terminal.Gui.DriverTests; 
+
+public class DriverColorTests {
+	public DriverColorTests () => ConsoleDriver.RunningUnitTests = true;
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void SetColors_Changes_Colors (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		driver.Init ();
+
+		Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+		Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+
+		Console.ForegroundColor = ConsoleColor.Red;
+		Assert.Equal (ConsoleColor.Red, Console.ForegroundColor);
+
+		Console.BackgroundColor = ConsoleColor.Green;
+		Assert.Equal (ConsoleColor.Green, Console.BackgroundColor);
+
+		Console.ResetColor ();
+		Assert.Equal (ConsoleColor.Gray, Console.ForegroundColor);
+		Assert.Equal (ConsoleColor.Black, Console.BackgroundColor);
+
+		driver.End ();
+	}
+
+
+	[Theory]
+	[InlineData (typeof (FakeDriver), false)]
+	[InlineData (typeof (NetDriver), true)]
+	//[InlineData (typeof (ANSIDriver), true)]
+	[InlineData (typeof (WindowsDriver), true)]
+	[InlineData (typeof (CursesDriver), false)]
+	public void SupportsTrueColor_Defaults (Type driverType, bool expectedSetting)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		driver.Init ();
+
+		Assert.Equal (expectedSetting, driver.SupportsTrueColor);
+
+		driver.End ();
+	}
+
+	[Theory]
+	[InlineData (typeof (FakeDriver))]
+	[InlineData (typeof (NetDriver))]
+	//[InlineData (typeof (ANSIDriver))]
+	[InlineData (typeof (WindowsDriver))]
+	[InlineData (typeof (CursesDriver))]
+	public void Force16Colors_Sets (Type driverType)
+	{
+		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
+		driver.Init ();
+
+		driver.Force16Colors = true;
+		Assert.True (driver.Force16Colors);
+
+		driver.End ();
 	}
 }

+ 29 - 22
UnitTests/ConsoleDrivers/MainLoopDriverTests.cs

@@ -12,17 +12,14 @@ using Console = Terminal.Gui.FakeConsole;
 namespace Terminal.Gui.DriverTests;
 
 public class MainLoopDriverTests {
-
-	public MainLoopDriverTests (ITestOutputHelper output)
-	{
-		ConsoleDriver.RunningUnitTests = true;
-	}
+	public MainLoopDriverTests (ITestOutputHelper output) => ConsoleDriver.RunningUnitTests = true;
 
 	[Theory]
 	[InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_Constructs_Disposes (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
@@ -45,20 +42,21 @@ public class MainLoopDriverTests {
 		Assert.Empty (mainLoop.Timeouts);
 		Assert.False (mainLoop.Running);
 	}
-	
+
 	[Theory]
 	[InlineData (typeof (FakeDriver), typeof (FakeMainLoop))]
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_AddTimeout_ValidParameters_ReturnsToken (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
-		var callbackInvoked = false;
+		bool callbackInvoked = false;
 
-		var token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => {
+		object token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => {
 			callbackInvoked = true;
 			return false;
 		});
@@ -77,14 +75,15 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_RemoveTimeout_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
 
-		var token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
-		var result = mainLoop.RemoveTimeout (token);
+		object token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
+		bool result = mainLoop.RemoveTimeout (token);
 
 		Assert.True (result);
 		mainLoop.Dispose ();
@@ -95,13 +94,14 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_RemoveTimeout_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
 
-		var result = mainLoop.RemoveTimeout (new object ());
+		bool result = mainLoop.RemoveTimeout (new object ());
 
 		Assert.False (result);
 	}
@@ -111,12 +111,13 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_AddIdle_ValidIdleHandler_ReturnsToken (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
-		var idleHandlerInvoked = false;
+		bool idleHandlerInvoked = false;
 
 		bool IdleHandler ()
 		{
@@ -124,7 +125,7 @@ public class MainLoopDriverTests {
 			return false;
 		}
 
-		Func<bool> token = mainLoop.AddIdle (IdleHandler);
+		var token = mainLoop.AddIdle (IdleHandler);
 
 		Assert.NotNull (token);
 		Assert.False (idleHandlerInvoked); // Idle handler should not be invoked immediately
@@ -138,6 +139,7 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_RemoveIdle_ValidToken_ReturnsTrue (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
@@ -145,8 +147,8 @@ public class MainLoopDriverTests {
 		var mainLoop = new MainLoop (mainLoopDriver);
 
 		bool IdleHandler () => false;
-		Func<bool> token = mainLoop.AddIdle (IdleHandler);
-		var result = mainLoop.RemoveIdle (token);
+		var token = mainLoop.AddIdle (IdleHandler);
+		bool result = mainLoop.RemoveIdle (token);
 
 		Assert.True (result);
 		mainLoop.Dispose ();
@@ -157,13 +159,14 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_RemoveIdle_InvalidToken_ReturnsFalse (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
 
-		var result = mainLoop.RemoveIdle (() => false);
+		bool result = mainLoop.RemoveIdle (() => false);
 
 		Assert.False (result);
 		mainLoop.Dispose ();
@@ -174,14 +177,15 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_RunIteration_ValidIdleHandler_CallsIdleHandler (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
-		var idleHandlerInvoked = false;
+		bool idleHandlerInvoked = false;
 
-		Func<bool> idleHandler = () => {
+		var idleHandler = () => {
 			idleHandlerInvoked = true;
 			return false;
 		};
@@ -198,13 +202,14 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_CheckTimersAndIdleHandlers_NoTimersOrIdleHandlers_ReturnsFalse (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
 		var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, new object [] { driver });
 		var mainLoop = new MainLoop (mainLoopDriver);
 
-		var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout);
+		bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
 
 		Assert.False (result);
 		Assert.Equal (-1, waitTimeout);
@@ -216,6 +221,7 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_CheckTimersAndIdleHandlers_TimersActive_ReturnsTrue (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
@@ -223,7 +229,7 @@ public class MainLoopDriverTests {
 		var mainLoop = new MainLoop (mainLoopDriver);
 
 		mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
-		var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout);
+		bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
 
 		Assert.True (result);
 		Assert.True (waitTimeout >= 0);
@@ -235,6 +241,7 @@ public class MainLoopDriverTests {
 	[InlineData (typeof (NetDriver), typeof (NetMainLoop))]
 	[InlineData (typeof (CursesDriver), typeof (UnixMainLoop))]
 	[InlineData (typeof (WindowsDriver), typeof (WindowsMainLoop))]
+	//[InlineData (typeof (ANSIDriver), typeof (AnsiMainLoopDriver))]
 	public void MainLoop_CheckTimersAndIdleHandlers_IdleHandlersActive_ReturnsTrue (Type driverType, Type mainLoopDriverType)
 	{
 		var driver = (ConsoleDriver)Activator.CreateInstance (driverType);
@@ -242,7 +249,7 @@ public class MainLoopDriverTests {
 		var mainLoop = new MainLoop (mainLoopDriver);
 
 		mainLoop.AddIdle (() => false);
-		var result = mainLoop.CheckTimersAndIdleHandlers (out var waitTimeout);
+		bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
 
 		Assert.True (result);
 		Assert.Equal (-1, waitTimeout);
@@ -267,4 +274,4 @@ public class MainLoopDriverTests {
 	//	Assert.True (actionInvoked);
 	//	mainLoop.Dispose ();
 	//}
-}
+}

+ 220 - 36
UnitTests/Input/KeyTests.cs

@@ -27,6 +27,132 @@ public class KeyTests {
 		Assert.Equal (key, eventArgs.KeyCode);
 	}
 
+	[Theory]
+	[InlineData ('a', KeyCode.A)]
+	[InlineData ('A', KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('z', KeyCode.Z)]
+	[InlineData ('Z', KeyCode.Z | KeyCode.ShiftMask)]
+	[InlineData (' ', KeyCode.Space)]
+	[InlineData ('1', KeyCode.D1)]
+	[InlineData ('!', (KeyCode)'!')]
+	[InlineData ('\r', KeyCode.Enter)]
+	[InlineData ('\t', KeyCode.Tab)]
+	[InlineData ('\r', (KeyCode)13)]
+	[InlineData ('\n', (KeyCode)10)]
+	[InlineData ('ó', (KeyCode)'ó')]
+	[InlineData ('Ó', (KeyCode)'Ó')]
+	[InlineData ('❿', (KeyCode)'❿')]
+	[InlineData ('☑', (KeyCode)'☑')]
+	[InlineData ('英', (KeyCode)'英')]
+	[InlineData ('{', (KeyCode)'{')]
+	[InlineData ('\'', (KeyCode)'\'')]
+	[InlineData ('\xFFFF', (KeyCode)0xFFFF)]
+	[InlineData ('\x0', (KeyCode)0x0)]
+	public void Constructor_Char (char ch, KeyCode expectedKeyCode)
+	{
+		var key = new Key (ch);
+		Assert.Equal (expectedKeyCode, key.KeyCode);
+	}
+
+
+	// TryParse
+	[Theory]
+	[InlineData ("a", KeyCode.A)]
+	[InlineData ("Ctrl+A", KeyCode.A | KeyCode.CtrlMask)]
+	[InlineData ("Alt+A", KeyCode.A | KeyCode.AltMask)]
+	[InlineData ("Shift+A", KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ("A", KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ("â", (KeyCode)'â')]
+	[InlineData ("Shift+â", (KeyCode)'â' | KeyCode.ShiftMask)]
+	[InlineData ("Shift+Â", (KeyCode)'Â' | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+Shift+CursorUp", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.CursorUp)]
+	[InlineData ("Ctrl+Alt+Shift+CursorUp", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp)]
+	[InlineData ("ctrl+alt+shift+cursorup", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp)]
+	[InlineData ("CTRL+ALT+SHIFT+CURSORUP", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp)]
+	[InlineData ("Ctrl+Alt+Shift+Delete", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Delete)]
+	[InlineData ("Ctrl+Alt+Shift+Enter", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Enter)]
+	[InlineData ("Tab", KeyCode.Tab)]
+	[InlineData ("Shift+Tab", KeyCode.Tab | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+Tab", KeyCode.Tab | KeyCode.CtrlMask)]
+	[InlineData ("Alt+Tab", KeyCode.Tab | KeyCode.AltMask)]
+	[InlineData ("Ctrl+Shift+Tab", KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask)]
+	[InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)]
+	[InlineData ("", KeyCode.Null)]
+	[InlineData (" ", KeyCode.Space)]
+	[InlineData ("Space", KeyCode.Space)]
+	[InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)]
+	[InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)]
+	[InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)]
+	[InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)]
+	[InlineData ("F1", KeyCode.F1)]
+	[InlineData ("0", KeyCode.D0)]
+	[InlineData ("9", KeyCode.D9)]
+	[InlineData ("D0", KeyCode.D0)]
+	[InlineData ("65", KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ("97", KeyCode.A)]
+	[InlineData ("Shift", KeyCode.ShiftMask)]
+	[InlineData ("Ctrl", KeyCode.CtrlMask)]
+	[InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)]
+	[InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)]
+	[InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)]
+	[InlineData ("Alt-A-Ctrl", KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)]
+	public void Constructor_String_Valid (string keyString, Key expected)
+	{
+		Key key = new Key (keyString);
+		Assert.Equal (((Key)expected).ToString (), key.ToString ());
+	}
+
+	[Theory]
+	[InlineData("Barf")]
+	public void Constructor_String_Invalid_Throws (string keyString)
+	{
+		Assert.Throws<ArgumentException> (() => new Key (keyString));
+	}
+
+	[Theory]
+	[InlineData ('a', KeyCode.A)]
+	[InlineData ('A', KeyCode.A | KeyCode.ShiftMask)]
+	[InlineData ('z', KeyCode.Z)]
+	[InlineData ('Z', KeyCode.Z | KeyCode.ShiftMask)]
+	[InlineData (' ', KeyCode.Space)]
+	[InlineData ('1', KeyCode.D1)]
+	[InlineData ('!', (KeyCode)'!')]
+	[InlineData ('\r', KeyCode.Enter)]
+	[InlineData ('\t', KeyCode.Tab)]
+	[InlineData ('\r', (KeyCode)13)]
+	[InlineData ('\n', (KeyCode)10)]
+	[InlineData ('ó', (KeyCode)'ó')]
+	[InlineData ('Ó', (KeyCode)'Ó')]
+	[InlineData ('❿', (KeyCode)'❿')]
+	[InlineData ('☑', (KeyCode)'☑')]
+	[InlineData ('英', (KeyCode)'英')]
+	[InlineData ('{', (KeyCode)'{')]
+	[InlineData ('\'', (KeyCode)'\'')]
+	[InlineData ('\xFFFF', (KeyCode)0xFFFF)]
+	[InlineData ('\x0', (KeyCode)0x0)]
+	public void Cast_Char_To_Key (char ch, KeyCode expectedKeyCode)
+	{
+		var key = (Key)ch;
+		Assert.Equal (expectedKeyCode, key.KeyCode);
+	}
+
+	// string cast operators
+	[Fact]
+	public void Cast_String_To_Key ()
+	{
+		var key = (Key)"Ctrl+Q";
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, key.KeyCode);
+	}
+
+	[Fact]
+	public void Cast_Key_ToString ()
+	{
+		var str = (string)Key.Q.WithCtrl;
+		Assert.Equal ("Ctrl+Q", str);
+	}
+
 	// IsValid
 	[Theory]
 	[InlineData (KeyCode.A, true)]
@@ -37,7 +163,7 @@ public class KeyTests {
 	[InlineData (KeyCode.CtrlMask, false)]
 	[InlineData (KeyCode.AltMask, false)]
 	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, false)]
-	public void IsValid (Key key, bool expected) => Assert.Equal (expected, key.IsValid);
+	public void IsValid (KeyCode key, bool expected) => Assert.Equal (expected, ((Key)key).IsValid);
 
 	[Fact]
 	public void HandledProperty_ShouldBeFalseByDefault ()
@@ -53,15 +179,37 @@ public class KeyTests {
 	[InlineData (KeyCode.A | KeyCode.ShiftMask, KeyCode.A | KeyCode.ShiftMask)]
 	[InlineData (KeyCode.Z, (KeyCode)'z')]
 	[InlineData (KeyCode.Space, KeyCode.Space)]
-	public void Cast_KeyCode_To_Key (KeyCode cdk, Key expected)
+	public void Cast_KeyCode_To_Key (KeyCode cdk, KeyCode expected)
 	{
 		// explicit
 		var key = (Key)cdk;
-		Assert.Equal (expected.ToString (), key.ToString ());
+		Assert.Equal (((Key)expected).ToString (), key.ToString ());
 
 		// implicit
 		key = cdk;
-		Assert.Equal (expected.ToString (), key.ToString ());
+		Assert.Equal (((Key)expected).ToString (), key.ToString ());
+	}
+
+	[Fact]
+	public void Standard_Keys_Always_New ()
+	{
+		// Make two local keys, and grab Key.A, which is a reference to a singleton.
+		Key aKey1 = Key.A;
+		Key aKey2 = Key.A;
+
+		// Assert the starting state that is expected
+		Assert.False (aKey1.Handled);
+		Assert.False (aKey2.Handled);
+		Assert.False (Key.A.Handled);
+
+		// Now set Handled true on one of our local keys
+		aKey1.Handled = true;
+
+		// Assert the newly-expected case
+		// The last two assertions will fail, because we have actually modified a singleton
+		Assert.True (aKey1.Handled);
+		Assert.False (aKey2.Handled);
+		Assert.False (Key.A.Handled);
 	}
 
 	[Theory]
@@ -96,7 +244,7 @@ public class KeyTests {
 	[InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, '\0')]
 	[InlineData ((KeyCode)'a', 97)] // 97 or Key.Space | Key.A
 	[InlineData ((KeyCode)'A', 97)] // 65 or equivalent to Key.A, but A-Z are mapped to lower case by drivers
-	//[InlineData (Key.A, 97)] // 65 equivalent to (Key)'A', but A-Z are mapped to lower case by drivers
+					//[InlineData (Key.A, 97)] // 65 equivalent to (Key)'A', but A-Z are mapped to lower case by drivers
 	[InlineData (KeyCode.ShiftMask | KeyCode.A, 65)]
 	[InlineData (KeyCode.CtrlMask | KeyCode.A, '\0')]
 	[InlineData (KeyCode.AltMask | KeyCode.A, '\0')]
@@ -114,7 +262,7 @@ public class KeyTests {
 	[InlineData (KeyCode.F1, '\0')]
 	[InlineData (KeyCode.ShiftMask | KeyCode.F1, '\0')]
 	[InlineData (KeyCode.CtrlMask | KeyCode.F1, '\0')]
-	[InlineData (KeyCode.Enter, '\n')]
+	[InlineData (KeyCode.Enter, '\r')]
 	[InlineData (KeyCode.Tab, '\t')]
 	[InlineData (KeyCode.Esc, 0x1b)]
 	[InlineData (KeyCode.Space, ' ')]
@@ -123,10 +271,10 @@ public class KeyTests {
 	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, '\0')]
 	[InlineData (KeyCode.CharMask, '\0')]
 	[InlineData (KeyCode.SpecialMask, '\0')]
-	public void AsRune_ShouldReturnCorrectIntValue (KeyCode key, Rune expected)
+	public void AsRune_ShouldReturnCorrectIntValue (KeyCode key, uint expected)
 	{
 		var eventArgs = new Key (key);
-		Assert.Equal (expected, eventArgs.AsRune);
+		Assert.Equal ((Rune)expected, eventArgs.AsRune);
 	}
 
 	[Theory]
@@ -174,7 +322,7 @@ public class KeyTests {
 	}
 
 	// TODO: Create equality operator for KeyCode
-	//Assert.Equal (KeyCode.Delete, Key.Delete);
+	//Assert.Equal (KeyCode.DeleteChar, Key.Delete);
 
 	// Similar tests for IsShift and IsCtrl
 	[Fact]
@@ -191,11 +339,11 @@ public class KeyTests {
 
 	[Theory]
 	[InlineData ((KeyCode)'☑', "☑")]
-	//[InlineData ((ConsoleDriverKey)'英', "英")]
-	//[InlineData ((ConsoleDriverKey)'{', "{")]
+	[InlineData ((KeyCode)'英', "英")]
+	[InlineData ((KeyCode)'{', "{")]
 	[InlineData ((KeyCode)'\'', "\'")]
 	[InlineData ((KeyCode)'ó', "ó")]
-	[InlineData ((KeyCode)'ó' | KeyCode.ShiftMask, "Shift+ó")] // is this right???
+	[InlineData ((KeyCode)'Ó' | KeyCode.ShiftMask, "Shift+Ó")] // TODO: This is not correct, it should be Shift+ó or just Ó
 	[InlineData ((KeyCode)'Ó', "Ó")]
 	[InlineData ((KeyCode)'ç' | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt+Shift+ç")]
 	[InlineData ((KeyCode)'a', "a")] // 97 or Key.Space | Key.A
@@ -255,27 +403,33 @@ public class KeyTests {
 	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CursorUp, "Alt+Shift+CursorUp")]
 	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.CursorUp, "Ctrl+Alt+CursorUp")]
 	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp, "Ctrl+Alt+Shift+CursorUp")]
+	[InlineData (KeyCode.Space, "Space")]
 	[InlineData (KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Null")]
-	[InlineData (KeyCode.AltKey, "AltKey")]
-	[InlineData (KeyCode.CtrlKey, "CtrlKey")]
-	[InlineData (KeyCode.ShiftKey, "ShiftKey")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.Null, "Shift")]
+	[InlineData (KeyCode.CtrlMask | KeyCode.Null, "Ctrl")]
+	[InlineData (KeyCode.AltMask | KeyCode.Null, "Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.Null, "Ctrl+Shift")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.Null, "Alt+Shift")]
+	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask | KeyCode.Null, "Ctrl+Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Null, "Ctrl+Alt+Shift")]
+#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to
+	[InlineData (KeyCode.ShiftMask, "Shift")]
+	[InlineData (KeyCode.CtrlMask, "Ctrl")]
+	[InlineData (KeyCode.AltMask, "Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, "Ctrl+Shift")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, "Alt+Shift")]
+	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt")]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask, "Ctrl+Alt+Shift")]
+#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to
+	[InlineData (KeyCode.AltMask, "Alt")]
+	[InlineData (KeyCode.CtrlMask, "Ctrl")]
+	[InlineData (KeyCode.ShiftMask, "Shift")]
 	[InlineData (KeyCode.CharMask, "CharMask")]
 	[InlineData (KeyCode.SpecialMask, "Ctrl+Alt+Shift")]
+	[InlineData ((KeyCode)'+', "+")]
+	[InlineData ((KeyCode)'+' | KeyCode.ShiftMask, "Shift++")]
+	[InlineData ((KeyCode)'+' | KeyCode.CtrlMask, "Ctrl++")] 
+	[InlineData ((KeyCode)'+' | KeyCode.ShiftMask | KeyCode.CtrlMask, "Ctrl+Shift++")]
 	public void ToString_ShouldReturnFormattedString (KeyCode key, string expected) => Assert.Equal (expected, Key.ToString (key));
 
 	// TryParse
@@ -302,6 +456,10 @@ public class KeyTests {
 	[InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)]
 	[InlineData ("", KeyCode.Null)]
 	[InlineData (" ", KeyCode.Space)]
+	[InlineData ("Space", KeyCode.Space)]
+	[InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)]
+	[InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)]
+	[InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)]
 	[InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)]
 	[InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)]
 	[InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)]
@@ -311,17 +469,17 @@ public class KeyTests {
 	[InlineData ("D0", KeyCode.D0)]
 	[InlineData ("65", KeyCode.A | KeyCode.ShiftMask)]
 	[InlineData ("97", KeyCode.A)]
-	[InlineData ("Shift", KeyCode.ShiftKey)]
-	[InlineData ("Ctrl", KeyCode.CtrlKey)]
+	[InlineData ("Shift", KeyCode.ShiftMask)]
+	[InlineData ("Ctrl", KeyCode.CtrlMask)]
 	[InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)]
 	[InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)]
 	[InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)]
 	[InlineData ("Alt-A-Ctrl", KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)]
-	public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, Key expected)
+	public void TryParse_ShouldReturnTrue_WhenValidKey (string keyString, KeyCode expected)
 	{
-		Key key;
-		Assert.True (Key.TryParse (keyString, out key));
-		Assert.Equal (((Key)expected).ToString (), key.ToString ());
+		Assert.True (Key.TryParse (keyString, out Key key));
+		Key expectedKey = (Key)expected;
+		Assert.Equal (expectedKey.ToString (), key.ToString ());
 	}
 
 	[Theory]
@@ -337,4 +495,30 @@ public class KeyTests {
 	[InlineData ("0x99")]
 	[InlineData ("Ctrl-Ctrl")]
 	public void TryParse_ShouldReturnFalse_On_InvalidKey (string keyString) => Assert.False (Key.TryParse (keyString, out var _));
+	
+	[Theory]
+	[InlineData (KeyCode.ShiftMask, true, false, false)]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask, true, true, false)]
+	[InlineData (KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask, true, true, true)]
+	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, true, false, true)]
+	[InlineData (KeyCode.AltMask, false, true, false)]
+	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask, false, true, true)]
+	[InlineData (KeyCode.CtrlMask, false, false, true)]
+	public void IsShift_IsAlt_IsCtrl (KeyCode keyCode, bool isShift, bool isAlt, bool isCtrl)
+	{
+		Assert.Equal (((Key)keyCode).IsShift, isShift);
+		Assert.Equal (((Key)keyCode).IsAlt, isAlt);
+		Assert.Equal (((Key)keyCode).IsCtrl, isCtrl);
+	}
+
+	[Theory]
+	[InlineData (KeyCode.A, KeyCode.A)]
+	[InlineData (KeyCode.F1, KeyCode.F1)]
+	public void Casting_Between_Key_And_KeyCode (KeyCode keyCode, KeyCode key)
+	{
+		Assert.Equal (keyCode, (Key)key);
+		Assert.NotEqual (keyCode, ((Key)key).WithShift);
+		Assert.Equal ((uint)keyCode, (uint)(Key)key);
+		Assert.NotEqual ((uint)keyCode, (uint)((Key)key).WithShift);
+	}
 }

+ 125 - 9
UnitTests/Input/ResponderTests.cs

@@ -1,3 +1,4 @@
+using System.Collections.Generic;
 using Xunit;
 
 // Alias Console to MockConsole so we don't accidentally use Console
@@ -6,7 +7,8 @@ using Console = Terminal.Gui.FakeConsole;
 namespace Terminal.Gui.InputTests;
 
 public class ResponderTests {
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void New_Initializes ()
 	{
 		var r = new Responder ();
@@ -19,7 +21,8 @@ public class ResponderTests {
 		r.Dispose ();
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void New_Methods_Return_False ()
 	{
 		var r = new View ();
@@ -59,7 +62,8 @@ public class ResponderTests {
 	}
 
 	// Generic lifetime (IDisposable) tests
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void Dispose_Works ()
 	{
 
@@ -83,7 +87,8 @@ public class ResponderTests {
 		}
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void IsOverridden_False_IfNotOverridden ()
 	{
 		// MouseEvent IS defined on Responder but NOT overridden
@@ -106,17 +111,18 @@ public class ResponderTests {
 #endif
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void IsOverridden_True_IfOverridden ()
 	{
 		// MouseEvent is defined on Responder IS overriden on ScrollBarView (but not View)
 		Assert.True (Responder.IsOverridden (new ScrollBarView () { Text = "ScrollBarView overrides MouseEvent" }, "MouseEvent"));
 
-		//// OnKeyDown is defined on View
-		//Assert.True (Responder.IsOverridden (new View () { Text = "View overrides OnKeyDown" }, "OnKeyDown"));
+		// OnKeyDown is defined on View
+		Assert.False (Responder.IsOverridden (new View () { Text = "View overrides OnKeyDown" }, "OnKeyDown"));
 
-		//// OnKeyDown is defined on DerivedView
-		//Assert.True (Responder.IsOverridden (new DerivedView () { Text = "DerivedView overrides OnKeyDown" }, "OnKeyDown"));
+		// OnKeyDown is defined on DerivedView
+		Assert.True (Responder.IsOverridden (new DerivedView () { Text = "DerivedView overrides OnKeyDown" }, "OnKeyDown"));
 
 		// ScrollBarView overrides both MouseEvent (from Responder) and Redraw (from View)
 		Assert.True (Responder.IsOverridden (new ScrollBarView () { Text = "ScrollBarView overrides MouseEvent" }, "MouseEvent"));
@@ -129,4 +135,114 @@ public class ResponderTests {
 		Assert.Empty (Responder.Instances);
 #endif
 	}
+
+	[Fact]
+	public void Responder_Not_Notifying_Dispose ()
+	{
+		var container1 = new View () { Id = "Container1" };
+
+		var view = new View () { Id = "View" };
+		container1.Add (view);
+		Assert.Equal (container1, view.SuperView);
+
+		Assert.Single (container1.Subviews);
+
+		var container2 = new View () { Id = "Container2" };
+
+		container2.Add (view);
+		Assert.Equal (container2, view.SuperView);
+		Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
+		container1.Dispose ();
+
+		Assert.Empty (container1.Subviews);
+		Assert.NotEmpty (container2.Subviews);
+		Assert.Single (container2.Subviews);
+		Assert.Null (view.SuperView);
+
+		// Trying access disposed properties
+		Assert.True (container2.Subviews [0].WasDisposed);
+		Assert.False (container2.Subviews [0].CanFocus);
+		Assert.Null (container2.Subviews [0].Margin);
+		Assert.Null (container2.Subviews [0].Border);
+		Assert.Null (container2.Subviews [0].Padding);
+		Assert.Null (view.SuperView);
+
+		container2.Dispose ();
+
+		Assert.Empty (Responder.Instances);
+	}
+
+	[Fact]
+	public void Disposing_Event_Notify_All_Subscribers_On_The_Second_Container ()
+	{
+		var container1 = new View () { Id = "Container1" };
+
+		var view = new View () { Id = "View" };
+		container1.Add (view);
+		Assert.Equal (container1, view.SuperView);
+		Assert.Single (container1.Subviews);
+
+		var container2 = new View () { Id = "Container2" };
+		var count = 0;
+
+		view.Disposing += View_Disposing;
+		container2.Add (view);
+		Assert.Equal (container2, view.SuperView);
+
+		void View_Disposing (object sender, System.EventArgs e)
+		{
+			count++;
+			Assert.Equal (view, sender);
+			container2.Remove ((View)sender);
+		}
+
+		Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
+		container1.Dispose ();
+
+		Assert.Empty (container1.Subviews);
+		Assert.Empty (container2.Subviews);
+		Assert.Equal (1, count);
+		Assert.Null (view.SuperView);
+
+		container2.Dispose ();
+
+		Assert.Empty (Responder.Instances);
+	}
+
+	[Fact]
+	public void Disposing_Event_Notify_All_Subscribers_On_The_First_Container ()
+	{
+		var container1 = new View () { Id = "Container1" };
+		var count = 0;
+
+		var view = new View () { Id = "View" };
+		view.Disposing += View_Disposing;
+		container1.Add (view);
+		Assert.Equal (container1, view.SuperView);
+
+		void View_Disposing (object sender, System.EventArgs e)
+		{
+			count++;
+			Assert.Equal (view, sender);
+			container1.Remove ((View)sender);
+		}
+
+		Assert.Single (container1.Subviews);
+
+		var container2 = new View () { Id = "Container2" };
+
+		container2.Add (view);
+		Assert.Equal (container2, view.SuperView);
+		Assert.Equal (container1.Subviews.Count, container2.Subviews.Count);
+		container2.Dispose ();
+
+		Assert.Empty (container1.Subviews);
+		Assert.Empty (container2.Subviews);
+		Assert.Equal (1, count);
+		Assert.Null (view.SuperView);
+
+		container1.Dispose ();
+
+		Assert.Empty (Responder.Instances);
+	}
 }

+ 17 - 18
UnitTests/Text/CollectionNavigatorTests.cs

@@ -383,22 +383,21 @@ public class CollectionNavigatorTests {
 
 		Assert.Equal (-1, current = n.GetNextMatchingItem (current, "x", true));
 	}
-
-	[Fact]
-	public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys ()
-	{
-		// test all Keys
-		foreach (KeyCode key in Enum.GetValues (typeof (KeyCode))) {
-			var ke = new Key (key);
-			_output.WriteLine ($"Testing {key}");
-			if (key == KeyCode.AltMask || key == KeyCode.CtrlMask || key == KeyCode.SpecialMask) {
-				Assert.False (CollectionNavigator.IsCompatibleKey (ke));
-			} else {
-				Assert.True (CollectionNavigator.IsCompatibleKey (ke));
-			}
-		}
-
-		// test Capslock, Numlock and Scrolllock
-		Assert.True (CollectionNavigator.IsCompatibleKey (new (KeyCode.Null)));
-	}
+	
+	[Theory]
+	[InlineData (KeyCode.A, true)]
+	[InlineData (KeyCode.Z, true)]
+	[InlineData (KeyCode.D0, true)]
+	[InlineData (KeyCode.A | KeyCode.ShiftMask, true)]
+	[InlineData (KeyCode.Z | KeyCode.ShiftMask, true)]
+	[InlineData (KeyCode.Space, true)]
+
+	[InlineData (KeyCode.Z | KeyCode.CtrlMask, false)]
+	[InlineData (KeyCode.Z | KeyCode.AltMask, false)]
+	[InlineData (KeyCode.F1, false)]
+	[InlineData (KeyCode.Delete, false)]
+	[InlineData (KeyCode.Delete, false)]
+	[InlineData (KeyCode.Esc, false)]
+	[InlineData (KeyCode.ShiftMask, false)]
+	public void IsCompatibleKey_Does_Not_Allow_Alt_And_Ctrl_Keys (KeyCode keyCode, bool compatible) => Assert.Equal (compatible, CollectionNavigatorBase.IsCompatibleKey (keyCode));
 }

+ 12 - 12
UnitTests/Text/TextFormatterTests.cs

@@ -249,7 +249,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("After K_", false, -1, KeyCode.Null, true)]
 		[InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)]
 		[InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false)
+		public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
 		{
 			Rune hotKeySpecifier = (Rune)'_';
 
@@ -261,7 +261,7 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 
 		[Theory]
@@ -277,7 +277,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("After k_", false, -1, KeyCode.Null, true)]
 		[InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)]
 		[InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false)
+		public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
 		{
 			Rune hotKeySpecifier = (Rune)'_';
 
@@ -289,7 +289,7 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 
 		[Theory]
@@ -303,7 +303,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("Last _1", true, 5, (KeyCode)'1', true)]
 		[InlineData ("After 1_", false, -1, KeyCode.Null, true)]
 		[InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)]
-		public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey, bool supportFirstUpperCase = false)
+		public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
 		{
 			Rune hotKeySpecifier = (Rune)'_';
 
@@ -315,7 +315,7 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 
 		[Theory]
@@ -324,7 +324,7 @@ namespace Terminal.Gui.TextTests {
 		[InlineData ("last K", true, 5, (KeyCode)'K')]
 		[InlineData ("multiple K and R", true, 9, (KeyCode)'K')]
 		[InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
-		public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, Key expectedKey)
+		public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey)
 		{
 			var supportFirstUpperCase = true;
 
@@ -338,25 +338,25 @@ namespace Terminal.Gui.TextTests {
 			}
 			Assert.Equal (expectedResult, result);
 			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal (expectedKey, hotKey);
+			Assert.Equal ((Key)expectedKey, hotKey);
 		}
 		
 		[Theory]
-		//[InlineData ("_\"k before", false, Key.Null)] // BUGBUG: Not sure why this fails
+		[InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
 		[InlineData ("\"_k before", true, KeyCode.K)]
 		[InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
 		[InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
-		//[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (Key)'_')] // BUGBUG: Not sure why this fails
+		[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
 		[InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
 		[InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
 		[InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к)
-		public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, Key expected)
+		public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
 		{
 			var hotKeySpecifier = (Rune)'_';
 
 			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey);
 			Assert.Equal (found, result);
-			Assert.Equal (expected, hotKey);
+			Assert.Equal ((Key)expected, hotKey);
 		}
 
 		[Theory]

+ 12 - 12
UnitTests/View/HotKeyTests.cs

@@ -51,37 +51,37 @@ public class HotKeyTests {
 	[InlineData ((KeyCode)'х')]  // Cyrillic x
 	[InlineData ((KeyCode)'你')] // Chinese ni
 	[InlineData ((KeyCode)'ö')] // German o umlaut
-	public void Set_SetsKeyBindings (Key key)
+	public void Set_SetsKeyBindings (KeyCode key)
 	{
 		var view = new View ();
-		view.HotKey = key;
+		view.HotKey = (Key)key;
 		Assert.Equal (string.Empty, view.Title);
-		Assert.Equal (key, view.HotKey);
+		Assert.Equal ((Key)key, view.HotKey);
 
 		// Verify key bindings were set
 
 		// As passed
-		var commands = view.KeyBindings.GetCommands (key);
+		var commands = view.KeyBindings.GetCommands ((Key)key);
 		Assert.Contains (Command.Accept, commands);
 
-		var baseKey = key.NoShift;
+		var baseKey = ((Key)key).NoShift;
 		// If A...Z, with and without shift
 		if (baseKey.IsKeyCodeAtoZ) {
-			commands = view.KeyBindings.GetCommands (key.WithShift);
+			commands = view.KeyBindings.GetCommands (((Key)key).WithShift);
 			Assert.Contains (Command.Accept, commands);
-			commands = view.KeyBindings.GetCommands (key.NoShift);
+			commands = view.KeyBindings.GetCommands (((Key)key).NoShift);
 			Assert.Contains (Command.Accept, commands);
-			commands = view.KeyBindings.GetCommands (key.WithAlt);
+			commands = view.KeyBindings.GetCommands (((Key)key).WithAlt);
 			Assert.Contains (Command.Accept, commands);
-			commands = view.KeyBindings.GetCommands (key.NoShift.WithAlt);
+			commands = view.KeyBindings.GetCommands (((Key)key).NoShift.WithAlt);
 			Assert.Contains (Command.Accept, commands);
 		} else {
 			// Non A..Z keys should not have shift bindings
-			if (key.IsShift) {
-				commands = view.KeyBindings.GetCommands (key.NoShift);
+			if (((Key)key).IsShift) {
+				commands = view.KeyBindings.GetCommands (((Key)key).NoShift);
 				Assert.Empty (commands);
 			} else {
-				commands = view.KeyBindings.GetCommands (key.WithShift);
+				commands = view.KeyBindings.GetCommands (((Key)key).WithShift);
 				Assert.Empty (commands);
 			}
 		}

+ 337 - 345
UnitTests/View/Layout/AbsoluteLayoutTests.cs

@@ -1,350 +1,342 @@
-using System.Text;
-using System;
-using System.Collections.Generic;
-using System.Xml.Linq;
-using Xunit;
+using Xunit;
 using Xunit.Abstractions;
 //using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
 // Alias Console to MockConsole so we don't accidentally use Console
-using Console = Terminal.Gui.FakeConsole;
-
-namespace Terminal.Gui.ViewTests {
-	public class AbsoluteLayoutTests {
-		readonly ITestOutputHelper output;
-
-		public AbsoluteLayoutTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Constructor ()
-		{
-			var frame = new Rect (1, 2, 3, 4);
-			var v = new View (frame);
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			Assert.Equal (frame, v.Frame);
-			Assert.Equal (new Rect(0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-
-			v = new View (frame, "v");
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			Assert.Equal (frame, v.Frame);
-			Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-
-			v = new View (frame.X, frame.Y, "v");
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			// BUGBUG: v2 - I think the default size should be 0,0
-			Assert.Equal (new Rect(frame.X, frame.Y, 1, 1), v.Frame);
-			Assert.Equal (new Rect (0, 0, 1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-			
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_Frame ()
-		{
-			var frame = new Rect (1, 2, 3, 4);
-			var newFrame = new Rect (1, 2, 30, 40);
-
-			var v = new View (frame);
-			v.Frame = newFrame;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			Assert.Equal (newFrame, v.Frame);
-			Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-
-			v = new View (frame.X, frame.Y, "v");
-			v.Frame = newFrame;
-			Assert.Equal (newFrame, v.Frame);
-			Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-
-			newFrame = new Rect (10, 20, 30, 40);
-			v = new View (frame);
-			v.Frame = newFrame;
-			Assert.Equal (newFrame, v.Frame);
-			Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-
-			v = new View (frame.X, frame.Y, "v");
-			v.Frame = newFrame;
-			Assert.Equal (newFrame, v.Frame);
-			Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_Height_or_Width_Absolute ()
-		{
-			var frame = new Rect (1, 2, 3, 4);
-			var newFrame = new Rect (1, 2, 30, 40);
-
-			var v = new View (frame);
-			v.Height = newFrame.Height;
-			v.Width = newFrame.Width;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			Assert.Equal (newFrame, v.Frame);
-			Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Null (v.X);
-			Assert.Null (v.Y);
-			Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString());
-			Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ());
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_Height_or_Width_NotAbsolute ()
-		{
-			var v = new View (Rect.Empty);
-			v.Height = Dim.Fill ();
-			v.Width = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);  // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_Height_or_Width_Null ()
-		{
-			var v = new View (Rect.Empty);
-			v.Height = null;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_X_or_Y_Absolute ()
-		{
-			var frame = new Rect (1, 2, 3, 4);
-			var newFrame = new Rect (10, 20, 3, 4);
-
-			var v = new View (frame);
-			v.X = newFrame.X;
-			v.Y = newFrame.Y;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			Assert.Equal (newFrame, v.Frame);
-			Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-			Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
-			Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
-			Assert.Null (v.Height);
-			Assert.Null (v.Width);
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_X_or_Y_NotAbsolute ()
-		{
-			var v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_X_or_Y_Null ()
-		{
-			var v = new View (Rect.Empty);
-			v.X = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			v.X = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.Y = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.Y = Pos.Center ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			v.Y = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute ()
-		{
-			var v = new View (Rect.Empty);
-			v.X = 1;
-			v.Y = 2;
-			v.Height = 3;
-			v.Width = 4;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			v.Width = Dim.Fill ();
-			v.Height = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-			v.X = null;
-			v.Y = null;
-			v.Height = null;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			v.Width = Dim.Fill ();
-			v.Height = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-			v.X = 1;
-			v.Y = null;
-			v.Height = null;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			v.Width = Dim.Fill ();
-			v.Height = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-			v.X = null;
-			v.Y = 2;
-			v.Height = null;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			v.Width = Dim.Fill ();
-			v.Height = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-			v.X = null;
-			v.Y = null;
-			v.Height = 3;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
-			v.Dispose ();
-
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			v.Width = Dim.Fill ();
-			v.Height = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-			v.X = null;
-			v.Y = null;
-			v.Height = null;
-			v.Width = 4;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Change_X_Y_Height_Width_Null ()
-		{
-			var v = new View (Rect.Empty);
-			v.X = null;
-			v.Y = null;
-			v.Height = null;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-
-			v.Dispose ();
-			v = new View (Rect.Empty);
-			v.X = Pos.Center ();
-			v.Y = Pos.Center ();
-			v.Width = Dim.Fill ();
-			v.Height = Dim.Fill ();
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-			// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-			v.X = null;
-			v.Y = null;
-			v.Height = null;
-			v.Width = null;
-			Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
-			v.Dispose ();
-		}
-
-		[Fact, TestRespondersDisposed]
-		public void AbsoluteLayout_Layout ()
-		{
-			var superRect = new Rect (0, 0, 100, 100);
-			var super = new View (superRect, "super");
-			Assert.True (super.LayoutStyle == LayoutStyle.Absolute);
-			var v1 = new View () {
-				X = 0,
-				Y = 0,
-				Width = 10,
-				Height = 10
-			};
-			// BUGBUG: v2 - This should be LayoutStyle.Absolute
-			Assert.True (v1.LayoutStyle == LayoutStyle.Computed);
-
-			var v2 = new View () {
-				X = 10,
-				Y = 10,
-				Width = 10,
-				Height = 10
-			};
-			// BUGBUG: v2 - This should be LayoutStyle.Absolute
-			Assert.True (v1.LayoutStyle == LayoutStyle.Computed);
-
-			super.Add (v1, v2);
-			super.LayoutSubviews ();
-			Assert.Equal (new Rect (0, 0, 10, 10), v1.Frame);
-			Assert.Equal (new Rect (10, 10, 10, 10), v2.Frame);
-			super.Dispose ();
-		}
+
+namespace Terminal.Gui.ViewTests;
+
+public class AbsoluteLayoutTests {
+	readonly ITestOutputHelper _output;
+
+	public AbsoluteLayoutTests (ITestOutputHelper output) => this._output = output;
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Constructor ()
+	{
+		var frame = new Rect (1, 2, 3, 4);
+		var v = new View (frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (frame, v.Frame);
+		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+
+		v = new View (frame, "v");
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (frame, v.Frame);
+		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+
+		v = new View (frame.X, frame.Y, "v");
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		// BUGBUG: v2 - I think the default size should be 0,0
+		Assert.Equal (new Rect (frame.X, frame.Y, 1, 1), v.Frame);
+		Assert.Equal (new Rect (0, 0, 1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_Frame ()
+	{
+		var frame = new Rect (1, 2, 3, 4);
+		var newFrame = new Rect (1, 2, 30, 40);
+
+		var v = new View (frame);
+		v.Frame = newFrame;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+
+		v = new View (frame.X, frame.Y, "v");
+		v.Frame = newFrame;
+		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+
+		newFrame = new Rect (10, 20, 30, 40);
+		v = new View (frame);
+		v.Frame = newFrame;
+		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+
+		v = new View (frame.X, frame.Y, "v");
+		v.Frame = newFrame;
+		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_Height_or_Width_Absolute ()
+	{
+		var frame = new Rect (1, 2, 3, 4);
+		var newFrame = new Rect (1, 2, 30, 40);
+
+		var v = new View (frame);
+		v.Height = newFrame.Height;
+		v.Width = newFrame.Width;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Null (v.X);
+		Assert.Null (v.Y);
+		Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
+		Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ());
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_Height_or_Width_NotAbsolute ()
+	{
+		var v = new View (Rect.Empty);
+		v.Height = Dim.Fill ();
+		v.Width = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_Height_or_Width_Null ()
+	{
+		var v = new View (Rect.Empty);
+		v.Height = null;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_X_or_Y_Absolute ()
+	{
+		var frame = new Rect (1, 2, 3, 4);
+		var newFrame = new Rect (10, 20, 3, 4);
+
+		var v = new View (frame);
+		v.X = newFrame.X;
+		v.Y = newFrame.Y;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
+		Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
+		Assert.Null (v.Height);
+		Assert.Null (v.Width);
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_X_or_Y_NotAbsolute ()
+	{
+		var v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_X_or_Y_Null ()
+	{
+		var v = new View (Rect.Empty);
+		v.X = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		v.X = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.Y = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.Y = Pos.Center ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		v.Y = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute ()
+	{
+		var v = new View (Rect.Empty);
+		v.X = 1;
+		v.Y = 2;
+		v.Height = 3;
+		v.Width = 4;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		v.Width = Dim.Fill ();
+		v.Height = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
+		v.X = null;
+		v.Y = null;
+		v.Height = null;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		v.Width = Dim.Fill ();
+		v.Height = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
+		v.X = 1;
+		v.Y = null;
+		v.Height = null;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		v.Width = Dim.Fill ();
+		v.Height = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
+		v.X = null;
+		v.Y = 2;
+		v.Height = null;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		v.Width = Dim.Fill ();
+		v.Height = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
+		v.X = null;
+		v.Y = null;
+		v.Height = 3;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Dispose ();
+
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		v.Width = Dim.Fill ();
+		v.Height = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
+		v.X = null;
+		v.Y = null;
+		v.Height = null;
+		v.Width = 4;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Change_X_Y_Height_Width_Null ()
+	{
+		var v = new View (Rect.Empty);
+		v.X = null;
+		v.Y = null;
+		v.Height = null;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+		v.Dispose ();
+		v = new View (Rect.Empty);
+		v.X = Pos.Center ();
+		v.Y = Pos.Center ();
+		v.Width = Dim.Fill ();
+		v.Height = Dim.Fill ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+
+		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
+		v.X = null;
+		v.Y = null;
+		v.Height = null;
+		v.Width = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Dispose ();
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void AbsoluteLayout_Layout ()
+	{
+		var superRect = new Rect (0, 0, 100, 100);
+		var super = new View (superRect, "super");
+		Assert.True (super.LayoutStyle == LayoutStyle.Absolute);
+		var v1 = new View () {
+			X = 0,
+			Y = 0,
+			Width = 10,
+			Height = 10
+		};
+		// BUGBUG: v2 - This should be LayoutStyle.Absolute
+		Assert.True (v1.LayoutStyle == LayoutStyle.Computed);
+
+		var v2 = new View () {
+			X = 10,
+			Y = 10,
+			Width = 10,
+			Height = 10
+		};
+		// BUGBUG: v2 - This should be LayoutStyle.Absolute
+		Assert.True (v1.LayoutStyle == LayoutStyle.Computed);
+
+		super.Add (v1, v2);
+		super.LayoutSubviews ();
+		Assert.Equal (new Rect (0, 0, 10, 10), v1.Frame);
+		Assert.Equal (new Rect (10, 10, 10, 10), v2.Frame);
+		super.Dispose ();
 	}
-}
+}

+ 1 - 1
UnitTests/Views/AppendAutocompleteTests.cs

@@ -26,7 +26,7 @@ public class AppendAutocompleteTests {
 		tf.PositionCursor ();
 		TestHelpers.AssertDriverContentsAre ("", output);
 
-		tf.NewKeyDownEvent ('f');
+		tf.NewKeyDownEvent (new ('f'));
 
 		tf.Draw ();
 		tf.PositionCursor ();

+ 11 - 4
UnitTests/Views/ComboBoxTests.cs

@@ -19,6 +19,7 @@ namespace Terminal.Gui.ViewsTests {
 			var cb = new ComboBox ();
 			cb.BeginInit ();
 			cb.EndInit ();
+			cb.LayoutSubviews ();
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.Null (cb.Source);
 			Assert.False (cb.AutoSize);
@@ -28,6 +29,7 @@ namespace Terminal.Gui.ViewsTests {
 			cb = new ComboBox ("Test");
 			cb.BeginInit ();
 			cb.EndInit ();
+			cb.LayoutSubviews ();
 			Assert.Equal ("Test", cb.Text);
 			Assert.Null (cb.Source);
 			Assert.False (cb.AutoSize);
@@ -37,6 +39,7 @@ namespace Terminal.Gui.ViewsTests {
 			cb = new ComboBox (new Rect (1, 2, 10, 20), new List<string> () { "One", "Two", "Three" });
 			cb.BeginInit ();
 			cb.EndInit ();
+			cb.LayoutSubviews ();
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.NotNull (cb.Source);
 			Assert.False (cb.AutoSize);
@@ -46,6 +49,7 @@ namespace Terminal.Gui.ViewsTests {
 			cb = new ComboBox (new List<string> () { "One", "Two", "Three" });
 			cb.BeginInit ();
 			cb.EndInit ();
+			cb.LayoutSubviews ();
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.NotNull (cb.Source);
 			Assert.False (cb.AutoSize);
@@ -60,6 +64,9 @@ namespace Terminal.Gui.ViewsTests {
 			var cb = new ComboBox (new List<string> () { "One", "Two", "Three" }) {
 				SelectedItem = 1
 			};
+			cb.BeginInit ();
+			cb.EndInit ();
+			cb.LayoutSubviews ();
 			Assert.Equal ("Two", cb.Text);
 			Assert.NotNull (cb.Source);
 			Assert.False (cb.AutoSize);
@@ -215,17 +222,17 @@ Three
 			Assert.True (cb.NewKeyDownEvent (new (KeyCode.CursorDown))); // losing focus
 			Assert.False (cb.HasFocus);
 			Assert.False (cb.IsShow);
-			Assert.Equal (0, cb.SelectedItem);
+			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("One", cb.Text);
 			Application.Top.FocusFirst (); // Gets focus again
 			Assert.True (cb.HasFocus);
 			Assert.False (cb.IsShow);
-			Assert.Equal (0, cb.SelectedItem);
+			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("One", cb.Text);
 			Assert.True (cb.NewKeyDownEvent (new (KeyCode.U | KeyCode.CtrlMask)));
 			Assert.True (cb.HasFocus);
 			Assert.True (cb.IsShow);
-			Assert.Equal (0, cb.SelectedItem);
+			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("", cb.Text);
 			Assert.Equal (3, cb.Source.Count);
 		}
@@ -256,7 +263,7 @@ Three
 			Assert.Equal ("One", cb.Text);
 			cb.Text = "T";
 			Assert.True (cb.IsShow);
-			Assert.Equal (0, cb.SelectedItem);
+			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("T", cb.Text);
 			Assert.True (cb.NewKeyDownEvent (new (KeyCode.Enter)));
 			Assert.False (cb.IsShow);

+ 1 - 1
UnitTests/Views/DateFieldTests.cs

@@ -64,7 +64,7 @@ namespace Terminal.Gui.ViewsTests {
 			CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
 			DateField df = new DateField (DateTime.Parse ("12/12/1971"));
 			df.ReadOnly = true;
-			Assert.True (df.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.Delete)));
 			Assert.Equal (" 12/12/1971", df.Text);
 			df.ReadOnly = false;
 			Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));

+ 113 - 37
UnitTests/Views/SliderTests.cs

@@ -1,14 +1,29 @@
-using Xunit;
-using Terminal.Gui;
-using System;
+using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Text;
-using System.Threading.Tasks;
+using Xunit;
 
 namespace Terminal.Gui.ViewsTests;
 
 public class SliderOptionTests {
+	[Fact]
+	public void Slider_Option_Default_Constructor ()
+	{
+		var o = new SliderOption<int> ();
+		Assert.Null (o.Legend);
+		Assert.Equal (default, o.LegendAbbr);
+		Assert.Equal (default, o.Data);
+	}
+
+	[Fact]
+	public void Slider_Option_Values_Constructor ()
+	{
+		var o = new SliderOption<int> ("1 thousand", new Rune ('y'), 1000);
+		Assert.Equal ("1 thousand",   o.Legend);
+		Assert.Equal (new Rune ('y'), o.LegendAbbr);
+		Assert.Equal (1000,           o.Data);
+	}
+
 	[Fact]
 	public void OnSet_Should_Raise_SetEvent ()
 	{
@@ -53,6 +68,38 @@ public class SliderOptionTests {
 		// Assert
 		Assert.True (eventRaised);
 	}
+
+	[Fact]
+	public void SliderOption_ToString_WhenEmpty ()
+	{
+		var sliderOption = new SliderOption<object> ();
+		Assert.Equal ("{Legend=, LegendAbbr=\0, Data=}", sliderOption.ToString ());
+	}
+
+	[Fact]
+	public void SliderOption_ToString_WhenPopulated_WithInt ()
+	{
+		var sliderOption = new SliderOption<int> {
+			Legend = "Lord flibble",
+			LegendAbbr = new Rune ('l'),
+			Data = 1
+		};
+
+		Assert.Equal ("{Legend=Lord flibble, LegendAbbr=l, Data=1}", sliderOption.ToString ());
+	}
+
+
+	[Fact]
+	public void SliderOption_ToString_WhenPopulated_WithSizeF ()
+	{
+		var sliderOption = new SliderOption<SizeF> {
+			Legend = "Lord flibble",
+			LegendAbbr = new Rune ('l'),
+			Data = new SizeF (32, 11)
+		};
+
+		Assert.Equal ("{Legend=Lord flibble, LegendAbbr=l, Data={Width=32, Height=11}}", sliderOption.ToString ());
+	}
 }
 
 public class SliderEventArgsTests {
@@ -98,7 +145,6 @@ public class SliderEventArgsTests {
 	}
 }
 
-
 public class SliderTests {
 	[Fact]
 	public void Constructor_Default ()
@@ -113,9 +159,9 @@ public class SliderTests {
 		Assert.Equal (Orientation.Horizontal, slider.Orientation);
 		Assert.False (slider.AllowEmpty);
 		Assert.True (slider.ShowLegends);
-		Assert.False (slider.ShowSpacing);
+		Assert.False (slider.ShowEndSpacing);
 		Assert.Equal (SliderType.Single, slider.Type);
-		Assert.Equal (0, slider.InnerSpacing);
+		Assert.Equal (0,                 slider.InnerSpacing);
 		Assert.False (slider.AutoSize);
 		Assert.Equal (0, slider.FocusedOption);
 	}
@@ -140,7 +186,7 @@ public class SliderTests {
 	{
 		// Arrange
 		var slider = new Slider<int> ();
-		bool eventRaised = false;
+		var eventRaised = false;
 		slider.OptionsChanged += (sender, args) => eventRaised = true;
 
 		// Act
@@ -155,9 +201,9 @@ public class SliderTests {
 	{
 		// Arrange
 		var slider = new Slider<int> (new List<int> { 1, 2, 3 });
-		bool eventRaised = false;
+		var eventRaised = false;
 		slider.OptionFocused += (sender, args) => eventRaised = true;
-		int newFocusedOption = 1;
+		var newFocusedOption = 1;
 		var args = new SliderEventArgs<int> (new Dictionary<int, SliderOption<int>> (), newFocusedOption);
 
 		// Act
@@ -172,10 +218,10 @@ public class SliderTests {
 	{
 		// Arrange
 		var slider = new Slider<int> (new List<int> { 1, 2, 3 });
-		bool eventRaised = false;
-		bool cancel = false;
+		var eventRaised = false;
+		var cancel = false;
 		slider.OptionFocused += (sender, args) => eventRaised = true;
-		int newFocusedOption = 1;
+		var newFocusedOption = 1;
 
 		// Create args with cancel set to false
 		cancel = false;
@@ -188,7 +234,7 @@ public class SliderTests {
 		slider.OnOptionFocused (newFocusedOption, args);
 
 		// Assert
-		Assert.True (eventRaised); // Event should be raised
+		Assert.True (eventRaised);                             // Event should be raised
 		Assert.Equal (newFocusedOption, slider.FocusedOption); // Focused option should change
 
 		// Create args with cancel set to true
@@ -201,7 +247,7 @@ public class SliderTests {
 		slider.OnOptionFocused (2, args);
 
 		// Assert
-		Assert.True (eventRaised); // Event should be raised
+		Assert.True (eventRaised);                             // Event should be raised
 		Assert.Equal (newFocusedOption, slider.FocusedOption); // Focused option should not change
 	}
 
@@ -219,7 +265,7 @@ public class SliderTests {
 		// 1--2--3--4
 
 		// Act
-		bool result = slider.TryGetPositionByOption (option, out var position);
+		var result = slider.TryGetPositionByOption (option, out var position);
 
 		// Assert
 		Assert.True (result);
@@ -240,7 +286,7 @@ public class SliderTests {
 		slider.InnerSpacing = 2;
 
 		// Act
-		bool result = slider.TryGetPositionByOption (option, out var position);
+		var result = slider.TryGetPositionByOption (option, out var position);
 
 		// Assert
 		Assert.True (result);
@@ -253,11 +299,11 @@ public class SliderTests {
 	{
 		// Arrange
 		var slider = new Slider<int> (new List<int> { 1, 2, 3 });
-		int option = -1;
+		var option = -1;
 		var expectedPosition = (-1, -1);
 
 		// Act
-		bool result = slider.TryGetPositionByOption (option, out var position);
+		var result = slider.TryGetPositionByOption (option, out var position);
 
 		// Assert
 		Assert.False (result);
@@ -283,7 +329,7 @@ public class SliderTests {
 		// Arrange
 
 		// Act
-		bool result = slider.TryGetOptionByPosition (x, y, threshold, out int option);
+		var result = slider.TryGetOptionByPosition (x, y, threshold, out var option);
 
 		// Assert
 		Assert.True (result);
@@ -314,12 +360,9 @@ public class SliderTests {
 		// 7 |
 		// 8 |
 		// 9 4
-		slider.CalcSpacingConfig ();
-
-		// Arrange
 
 		// Act
-		bool result = slider.TryGetOptionByPosition (x, y, threshold, out int option);
+		var result = slider.TryGetOptionByPosition (x, y, threshold, out var option);
 
 		// Assert
 		Assert.True (result);
@@ -332,13 +375,13 @@ public class SliderTests {
 	{
 		// Arrange
 		var slider = new Slider<int> (new List<int> { 1, 2, 3 });
-		int x = 10;
-		int y = 10;
-		int threshold = 2;
-		int expectedOption = -1;
+		var x = 10;
+		var y = 10;
+		var threshold = 2;
+		var expectedOption = -1;
 
 		// Act
-		bool result = slider.TryGetOptionByPosition (x, y, threshold, out int option);
+		var result = slider.TryGetOptionByPosition (x, y, threshold, out var option);
 
 		// Assert
 		Assert.False (result);
@@ -353,7 +396,7 @@ public class SliderTests {
 		slider.AutoSize = true;
 
 		// Act
-		bool result = slider.MovePlus ();
+		var result = slider.MovePlus ();
 
 		// Assert
 		Assert.True (result);
@@ -369,7 +412,7 @@ public class SliderTests {
 		slider.FocusedOption = 3;
 
 		// Act
-		bool result = slider.MovePlus ();
+		var result = slider.MovePlus ();
 
 		// Assert
 		Assert.False (result);
@@ -387,7 +430,7 @@ public class SliderTests {
 
 		// Act
 		slider.FocusedOption = 2;
-		bool result = slider.Set ();
+		var result = slider.Set ();
 
 		// Assert
 		Assert.True (result);
@@ -407,14 +450,47 @@ public class SliderTests {
 		Assert.NotEmpty (slider.GetSetOptions ());
 
 		// Act
-		bool result = slider.UnSetOption (slider.FocusedOption);
+		var result = slider.UnSetOption (slider.FocusedOption);
 
 		// Assert
 		Assert.False (result);
-		Assert.NotEmpty (slider.GetSetOptions());
+		Assert.NotEmpty (slider.GetSetOptions ());
 	}
 
-	// Add more tests for different scenarios and edge cases.
-}
+	[Fact]
+	void Set_Options_Throws_If_Null ()
+	{
+		// Arrange
+		var slider = new Slider<int> ();
+
+		// Act/Assert
+		Assert.Throws<ArgumentNullException> (() => slider.Options = null);
+
+	}
+
+	[Fact]
+	void Set_Options_No_Legend_Throws ()
+	{
+		// Arrange
+		var slider = new Slider<int> ();
+
+		// Act/Assert
+		Assert.Throws<ArgumentNullException> (() => slider.Options = null);
+
+	}
 
+	// https://github.com/gui-cs/Terminal.Gui/issues/3099
+	[Fact]
+	void One_Option_Does_Not_Throw ()
+	{
+		// Arrange
+		var slider = new Slider<int> ();
+		slider.BeginInit ();
+		slider.EndInit ();
+		// Act/Assert
+		slider.Options = new List<SliderOption<int>> { new () };
 
+	}
+
+	// Add more tests for different scenarios and edge cases.
+}

+ 5 - 5
UnitTests/Views/TextFieldTests.cs

@@ -950,10 +950,10 @@ public class TextFieldTests {
 		Assert.Equal (15, tf.CursorPosition);
 		Assert.False (tf.ReadOnly);
 
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("This is a test.", tf.Text);
 		tf.CursorPosition = 0;
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("his is a test.", tf.Text);
 		tf.ReadOnly = true;
 		Assert.True (tf.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));
@@ -1114,7 +1114,7 @@ public class TextFieldTests {
 		Assert.Equal ("is is a t", tf.Text);
 		Assert.Equal (9, tf.CursorPosition);
 		Assert.True (tf.Used);
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.InsertChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Insert)));
 		Assert.Equal ("is is a t", tf.Text);
 		Assert.Equal (9, tf.CursorPosition);
 		Assert.False (tf.Used);
@@ -1137,7 +1137,7 @@ public class TextFieldTests {
 		Assert.Equal ("is is a", Clipboard.Contents);
 		tf.Text = "TAB to jump between text fields.";
 		Assert.Equal (0, tf.CursorPosition);
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("to jump between text fields.", tf.Text);
 		tf.CursorPosition = tf.Text.Length;
 		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Backspace | KeyCode.CtrlMask)));
@@ -1212,7 +1212,7 @@ public class TextFieldTests {
 		tf.CursorPosition = 2;
 		Assert.Equal (1, tf.SelectedLength);
 		Assert.Equal ("1", tf.SelectedText);
-		Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("-", newText);
 		Assert.Equal ("-1", oldText);
 		Assert.Equal ("-", tf.Text);

+ 39 - 20
UnitTests/Views/TextViewTests.cs

@@ -1007,7 +1007,7 @@ public class TextViewTests {
 				Assert.Equal ("This is the first line.", Clipboard.Contents);
 				break;
 			case 1:
-				_textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask));
+				_textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask));
 				Assert.Equal (0, _textView.CursorPosition.X);
 				Assert.Equal (0, _textView.CursorPosition.Y);
 				Assert.Equal ("This is the second line.", _textView.Text);
@@ -1085,7 +1085,7 @@ public class TextViewTests {
 		bool iterationsFinished = false;
 
 		while (!iterationsFinished) {
-			_textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask));
+			_textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask));
 			switch (iteration) {
 			case 0:
 				Assert.Equal (0, _textView.CursorPosition.X);
@@ -1180,7 +1180,7 @@ public class TextViewTests {
 		bool iterationsFinished = false;
 
 		while (!iterationsFinished) {
-			_textView.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask));
+			_textView.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask));
 			switch (iteration) {
 			case 0:
 				Assert.Equal (0, _textView.CursorPosition.X);
@@ -1541,6 +1541,9 @@ public class TextViewTests {
 	[TextViewTestsAutoInitShutdown]
 	public void TabWidth_Setting_To_Zero_Keeps_AllowsTab ()
 	{
+		Application.Top.Add (_textView);
+		Application.Begin (Application.Top);
+
 		Assert.Equal (4, _textView.TabWidth);
 		Assert.True (_textView.AllowsTab);
 		Assert.True (_textView.AllowsReturn);
@@ -1552,8 +1555,21 @@ public class TextViewTests {
 		Assert.True (_textView.Multiline);
 		_textView.NewKeyDownEvent (new (KeyCode.Tab));
 		Assert.Equal ("\tTAB to jump between text fields.", _textView.Text);
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
+TAB to jump between text field", _output);
+
+		_textView.TabWidth = 4;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
+    TAB to jump between text f", _output);
+
 		_textView.NewKeyDownEvent (new (KeyCode.Tab | KeyCode.ShiftMask));
 		Assert.Equal ("TAB to jump between text fields.", _textView.Text);
+		Assert.True (_textView.NeedsDisplay);
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
+TAB to jump between text field", _output);
 	}
 
 	[Fact]
@@ -2500,7 +2516,6 @@ line.
 	[Theory]
 	[TextViewTestsAutoInitShutdown]
 	[InlineData (KeyCode.Delete)]
-	[InlineData (KeyCode.DeleteChar)]
 	public void WordWrap_Draw_Typed_Keys_After_Text_Is_Deleted (KeyCode del)
 	{
 		Application.Top.Add (_textView);
@@ -2744,13 +2759,13 @@ Line 2.", _output);
 		Assert.Equal (0, tv.SelectedLength);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.True (tv.Selecting);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ($"This is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.False (tv.Selecting);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ($"his is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
@@ -2762,7 +2777,7 @@ Line 2.", _output);
 		Assert.True (tv.NewKeyDownEvent (new (KeyCode.End)));
 		Assert.Equal ($"is is the first line.{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (21, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Backspace)));
 		Assert.Equal ($"is is the first line{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (20, 0), tv.CursorPosition);
 		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Backspace)));
@@ -2808,7 +2823,7 @@ Line 2.", _output);
 		Assert.False (tv.Selecting);
 		Assert.Equal ("is is the first lin", Clipboard.Contents);
 		tv.CursorPosition = Point.Empty;
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask | KeyCode.ShiftMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask | KeyCode.ShiftMask)));
 		Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
@@ -2964,7 +2979,7 @@ Line 2.", _output);
 		Assert.Equal (0, tv.SelectedLength);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.False (tv.Selecting);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ($"This is the second line.{Environment.NewLine}This is the third line.first", tv.Text);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 		Assert.Equal (0, tv.SelectedLength);
@@ -3020,7 +3035,7 @@ Line 2.", _output);
 		Assert.Equal ($"{Environment.NewLine}This is the second line.{Environment.NewLine}This is the third ", tv.SelectedText);
 		Assert.True (tv.Selecting);
 		Assert.True (tv.Used);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.InsertChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Insert)));
 		Assert.False (tv.Used);
 		Assert.True (tv.AllowsTab);
 		Assert.Equal (new Point (18, 2), tv.CursorPosition);
@@ -3047,6 +3062,10 @@ Line 2.", _output);
 		Assert.False (tv.NewKeyDownEvent (Application.AlternateForwardKey));
 		Assert.False (tv.NewKeyDownEvent (new (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask)));
 		Assert.False (tv.NewKeyDownEvent (Application.AlternateBackwardKey));
+		
+		Assert.True (tv.NewKeyDownEvent (ContextMenu.DefaultKey));
+		Assert.True (tv.ContextMenu != null && tv.ContextMenu.MenuBar.Visible);
+
 	}
 
 	[Fact]
@@ -4687,7 +4706,7 @@ Line 2.", _output);
 		Assert.False (tv.IsDirty);
 		Assert.False (tv.HasHistoryChanges);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		Assert.Equal ("", tv.Text);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.Equal (1, tv.Lines);
@@ -5178,28 +5197,28 @@ Line 2.", _output);
 		var text = "First line.\nSecond line.";
 		var tv = new TextView () { Text = text };
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ($"line.{Environment.NewLine}Second line.", tv.Text);
 		Assert.Equal ("", tv.SelectedText);
 		Assert.Equal (2, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ($"{Environment.NewLine}Second line.", tv.Text);
 		Assert.Equal (2, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("Second line.", tv.Text);
 		Assert.Equal (1, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("line.", tv.Text);
 		Assert.Equal (1, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
 
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar | KeyCode.CtrlMask)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete | KeyCode.CtrlMask)));
 		Assert.Equal ("", tv.Text);
 		Assert.Equal (1, tv.Lines);
 		Assert.Equal (new Point (0, 0), tv.CursorPosition);
@@ -6393,7 +6412,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (2, 0);
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -6403,7 +6422,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (22, 0);
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -6450,7 +6469,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (2, 0);
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (2, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -6460,7 +6479,7 @@ This is the second line.
 
 		tv.CursorPosition = new Point (22, 0);
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
-		Assert.True (tv.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+		Assert.True (tv.NewKeyDownEvent (new (KeyCode.Delete)));
 		tv.Draw ();
 		Assert.Equal (new Point (22, 0), tv.CursorPosition);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"

+ 1 - 1
UnitTests/Views/TimeFieldTests.cs

@@ -61,7 +61,7 @@ namespace Terminal.Gui.ViewsTests {
 		{
 			TimeField tf = new TimeField (TimeSpan.Parse ("12:12:19"));
 			tf.ReadOnly = true;
-			Assert.True (tf.NewKeyDownEvent (new (KeyCode.DeleteChar)));
+			Assert.True (tf.NewKeyDownEvent (new (KeyCode.Delete)));
 			Assert.Equal (" 12:12:19", tf.Text);
 			tf.ReadOnly = false;
 			Assert.True (tf.NewKeyDownEvent (new (KeyCode.D | KeyCode.CtrlMask)));

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