Browse Source

Merge branch 'develop' into view-clear-fix

Tig 2 years ago
parent
commit
571a131490
58 changed files with 2784 additions and 1558 deletions
  1. 2 2
      .github/workflows/publish.yml
  2. 41 60
      Example/Example.cs
  3. 2 0
      Example/README.md
  4. 45 74
      README.md
  5. 38 12
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  6. 11 6
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  7. 32 1
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  8. 36 12
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  9. 71 8
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  10. 521 0
      Terminal.Gui/Core/ConsoleKeyMapping.cs
  11. 79 18
      Terminal.Gui/Core/Event.cs
  12. 40 20
      Terminal.Gui/Views/ContextMenu.cs
  13. 128 158
      Terminal.Gui/Views/Menu.cs
  14. 20 2
      Terminal.Gui/Views/ScrollView.cs
  15. 10 6
      Terminal.Gui/Views/TreeView.cs
  16. 4 0
      UICatalog/Properties/launchSettings.json
  17. 17 11
      UICatalog/Scenario.cs
  18. 1 4
      UICatalog/Scenarios/AllViewsTester.cs
  19. 0 1
      UICatalog/Scenarios/BordersComparisons.cs
  20. 1 11
      UICatalog/Scenarios/Clipping.cs
  21. 6 4
      UICatalog/Scenarios/CsvEditor.cs
  22. 2 4
      UICatalog/Scenarios/Editor.cs
  23. 1 1
      UICatalog/Scenarios/Keys.cs
  24. 12 9
      UICatalog/Scenarios/ListViewWithSelection.cs
  25. 1 5
      UICatalog/Scenarios/Notepad.cs
  26. 16 0
      UICatalog/Scenarios/TreeViewFileSystem.cs
  27. 251 0
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  28. 1 4
      UICatalog/Scenarios/WindowsAndFrameViews.cs
  29. 116 249
      UICatalog/UICatalog.cs
  30. 0 25
      UnitTests/AssemblyInfo.cs
  31. 13 13
      UnitTests/ButtonTests.cs
  32. 19 19
      UnitTests/CheckboxTests.cs
  33. 10 10
      UnitTests/ComboBoxTests.cs
  34. 201 19
      UnitTests/ConsoleDriverTests.cs
  35. 44 30
      UnitTests/ContextMenuTests.cs
  36. 38 38
      UnitTests/DialogTests.cs
  37. 2 2
      UnitTests/DimTests.cs
  38. 18 204
      UnitTests/GraphViewTests.cs
  39. 11 11
      UnitTests/ListViewTests.cs
  40. 335 260
      UnitTests/MenuTests.cs
  41. 6 6
      UnitTests/MessageBoxTests.cs
  42. 2 2
      UnitTests/PanelViewTests.cs
  43. 6 6
      UnitTests/PosTests.cs
  44. 3 3
      UnitTests/RadioGroupTests.cs
  45. 12 14
      UnitTests/ScenarioTests.cs
  46. 10 10
      UnitTests/ScrollBarViewTests.cs
  47. 108 1
      UnitTests/ScrollViewTests.cs
  48. 2 2
      UnitTests/StatusBarTests.cs
  49. 31 31
      UnitTests/TabViewTests.cs
  50. 28 28
      UnitTests/TableViewTests.cs
  51. 230 0
      UnitTests/TestHelpers.cs
  52. 35 35
      UnitTests/TextFormatterTests.cs
  53. 35 35
      UnitTests/TextViewTests.cs
  54. 9 9
      UnitTests/TreeViewTests.cs
  55. 60 60
      UnitTests/ViewTests.cs
  56. 3 3
      UnitTests/WizardTests.cs
  57. BIN
      docfx/images/Example.png
  58. 8 0
      docfx/overrides/Terminal_Gui_Application.md

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

@@ -16,12 +16,12 @@ jobs:
         fetch-depth: 0 #fetch-depth is needed for GitVersion
         fetch-depth: 0 #fetch-depth is needed for GitVersion
 
 
     - name: Install and calculate the new version with GitVersion 
     - name: Install and calculate the new version with GitVersion 
-      uses: gittools/actions/gitversion/[email protected]4
+      uses: gittools/actions/gitversion/[email protected]5
       with:
       with:
         versionSpec: 5.x
         versionSpec: 5.x
 
 
     - name: Determine Version
     - name: Determine Version
-      uses: gittools/actions/gitversion/[email protected]4
+      uses: gittools/actions/gitversion/[email protected]5
       id: gitversion # step id used as reference for output values
       id: gitversion # step id used as reference for output values
 
 
     - name: Display GitVersion outputs
     - name: Display GitVersion outputs

+ 41 - 60
Example/Example.cs

@@ -1,76 +1,57 @@
-// A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
-// This is the same code found in the Termiminal Gui README.md file.
+// This is a simple example application.  For the full range of functionality
+// see the UICatalog project
 
 
-using Terminal.Gui;
-using NStack;
-
-Application.Init ();
+// A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
 
 
-// Creates the top-level window to show
-var win = new Window ("Example App") {
-	X = 0,
-	Y = 1, // Leave one row for the toplevel menu
+using Terminal.Gui;
 
 
-	// By using Dim.Fill(), this Window will automatically resize without manual intervention
-	Width = Dim.Fill (),
-	Height = Dim.Fill ()
-};
+// Initialize the console
+Application.Init();
 
 
-Application.Top.Add (win);
+// Creates the top-level window with border and title
+var win = new Window("Example App (Ctrl+Q to quit)");
 
 
-// Creates a menubar, the item "New" has a help menu.
-var menu = new MenuBar (new MenuBarItem [] {
-			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_New", "Creates a new file", null),
-				new MenuItem ("_Close", "",null),
-				new MenuItem ("_Quit", "", () => { if (Quit ()) Application.Top.Running = false; })
-			}),
-			new MenuBarItem ("_Edit", new MenuItem [] {
-				new MenuItem ("_Copy", "", null),
-				new MenuItem ("C_ut", "", null),
-				new MenuItem ("_Paste", "", null)
-			})
-		});
-Application.Top.Add (menu);
+// Create input components and labels
 
 
-static bool Quit ()
+var usernameLabel = new Label("Username:");
+var usernameText = new TextField("")
 {
 {
-	var n = MessageBox.Query (50, 7, "Quit Example", "Are you sure you want to quit this example?", "Yes", "No");
-	return n == 0;
-}
+    // Position text field adjacent to label
+    X = Pos.Right(usernameLabel) + 1,
 
 
-var login = new Label ("Login: ") { X = 3, Y = 2 };
-var password = new Label ("Password: ") {
-	X = Pos.Left (login),
-	Y = Pos.Top (login) + 1
+    // Fill remaining horizontal space with a margin of 1
+    Width = Dim.Fill(1),
 };
 };
-var loginText = new TextField ("") {
-	X = Pos.Right (password),
-	Y = Pos.Top (login),
-	Width = 40
+
+var passwordLabel = new Label(0,2,"Password:");
+var passwordText = new TextField("")
+{
+    Secret = true,
+    // align with the text box above
+    X = Pos.Left(usernameText),
+    Y = 2,
+    Width = Dim.Fill(1),
 };
 };
-var passText = new TextField ("") {
-	Secret = true,
-	X = Pos.Left (loginText),
-	Y = Pos.Top (password),
-	Width = Dim.Width (loginText)
+
+// Create login button
+var btnLogin = new Button("Login")
+{
+    Y = 4,
+    // center the login button horizontally
+    X = Pos.Center(),
+    IsDefault = true,
 };
 };
 
 
-// Add the views to the main window, 
-win.Add (
-	// Using Computed Layout:
-	login, password, loginText, passText,
+// When login button is clicked display a message popup
+btnLogin.Clicked += () => MessageBox.Query("Logging In", "Login Successful", "Ok");
 
 
-	// Using Absolute Layout:
-	new CheckBox (3, 6, "Remember me"),
-	new RadioGroup (3, 8, new ustring [] { "_Personal", "_Company" }, 0),
-	new Button (3, 14, "Ok"),
-	new Button (10, 14, "Cancel"),
-	new Label (3, 18, "Press F9 or ESC plus 9 to activate the menubar")
+// Add all the views to the window
+win.Add(
+    usernameLabel, usernameText, passwordLabel, passwordText,btnLogin
 );
 );
 
 
-// Run blocks until the user quits the application
-Application.Run ();
+// Show the application
+Application.Run(win);
 
 
-// Always bracket Application.Init with .Shutdown.
-Application.Shutdown ();
+// After the application exits, release and reset console for clean shutdown
+Application.Shutdown();

+ 2 - 0
Example/README.md

@@ -4,6 +4,8 @@ This example shows how to use the Terminal.Gui library to create a simple GUI ap
 
 
 This is the same code found in the Terminal.Gui README.md file.
 This is the same code found in the Terminal.Gui README.md file.
 
 
+To explore the full range of functionality in Terminal.Gui, see the [UICatalog](../UICatalog) project
+
 See [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all Terminal.Gui samples.
 See [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all Terminal.Gui samples.
 
 
 Note, the old `demo.cs` example has been deleted because it was not a very good example. It can still be found in the [git history](https://github.com/gui-cs/Terminal.Gui/tree/v1.8.2).
 Note, the old `demo.cs` example has been deleted because it was not a very good example. It can still be found in the [git history](https://github.com/gui-cs/Terminal.Gui/tree/v1.8.2).

+ 45 - 74
README.md

@@ -61,99 +61,70 @@ See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/mas
 
 
 ## Sample Usage in C#
 ## Sample Usage in C#
 
 
+The following example shows a basic Terminal.Gui application written in C#:
+
 ```csharp
 ```csharp
 // A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
 // A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
 
 
 using Terminal.Gui;
 using Terminal.Gui;
-using NStack;
 
 
-Application.Init ();
+// Initialize the console
+Application.Init();
 
 
-// Creates the top-level window to show
-var win = new Window ("Example App") {
-	X = 0,
-	Y = 1, // Leave one row for the toplevel menu
+// Creates the top-level window with border and title
+var win = new Window("Example App (Ctrl+Q to quit)");
 
 
-	// By using Dim.Fill(), this Window will automatically resize without manual intervention
-	Width = Dim.Fill (),
-	Height = Dim.Fill ()
-};
+// Create input components and labels
 
 
-Application.Top.Add (win);
-
-// Creates a menubar, the item "New" has a help menu.
-var menu = new MenuBar (new MenuBarItem [] {
-			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_New", "Creates a new file", null),
-				new MenuItem ("_Close", "",null),
-				new MenuItem ("_Quit", "", () => { if (Quit ()) Application.Top.Running = false; })
-			}),
-			new MenuBarItem ("_Edit", new MenuItem [] {
-				new MenuItem ("_Copy", "", null),
-				new MenuItem ("C_ut", "", null),
-				new MenuItem ("_Paste", "", null)
-			})
-		});
-Application.Top.Add (menu);
-
-static bool Quit ()
+var usernameLabel = new Label("Username:");
+var usernameText = new TextField("")
 {
 {
-	var n = MessageBox.Query (50, 7, "Quit Example", "Are you sure you want to quit this example?", "Yes", "No");
-	return n == 0;
-}
-
-var login = new Label ("Login: ") { X = 3, Y = 2 };
-var password = new Label ("Password: ") {
-	X = Pos.Left (login),
-	Y = Pos.Top (login) + 1
+    // Position text field adjacent to label
+    X = Pos.Right(usernameLabel) + 1,
+
+    // Fill remaining horizontal space with a margin of 1
+    Width = Dim.Fill(1),
 };
 };
-var loginText = new TextField ("") {
-	X = Pos.Right (password),
-	Y = Pos.Top (login),
-	Width = 40
+
+var passwordLabel = new Label(0,2,"Password:");
+var passwordText = new TextField("")
+{
+    Secret = true,
+    // align with the text box above
+    X = Pos.Left(usernameText),
+    Y = 2,
+    Width = Dim.Fill(1),
 };
 };
-var passText = new TextField ("") {
-	Secret = true,
-	X = Pos.Left (loginText),
-	Y = Pos.Top (password),
-	Width = Dim.Width (loginText)
+
+// Create login button
+var btnLogin = new Button("Login")
+{
+    Y = 4,
+    // center the login button horizontally
+    X = Pos.Center(),
+    IsDefault = true,
 };
 };
 
 
-// Add the views to the main window, 
-win.Add (
-	// Using Computed Layout:
-	login, password, loginText, passText,
-
-	// Using Absolute Layout:
-	new CheckBox (3, 6, "Remember me"),
-	new RadioGroup (3, 8, new ustring [] { "_Personal", "_Company" }, 0),
-	new Button (3, 14, "Ok"),
-	new Button (10, 14, "Cancel"),
-	new Label (3, 18, "Press F9 or ESC plus 9 to activate the menubar")
+// When login button is clicked display a message popup
+btnLogin.Clicked += () => MessageBox.Query("Logging In", "Login Successful", "Ok");
+
+// Add all the views to the window
+win.Add(
+    usernameLabel, usernameText, passwordLabel, passwordText,btnLogin
 );
 );
 
 
-// Run blocks until the user quits the application
-Application.Run ();
+// Show the application
+Application.Run(win);
 
 
-// Always bracket Application.Init with .Shutdown.
-Application.Shutdown ();
+// After the application exits, release and reset console for clean shutdown
+Application.Shutdown();
 ```
 ```
 
 
-The example above shows adding views using both styles of layout supported by **Terminal.Gui**: **Absolute layout** and **[Computed layout](https://gui-cs.github.io/Terminal.Gui/articles/overview.html#layout)**.
-
-Alternatively, you can encapsulate the app behavior in a new `Window`-derived class, say `App.cs` containing the code above, and simplify your `Main` method to:
+When run the application looks as follows:
 
 
-```csharp
-using Terminal.Gui;
+![Simple Usage app](./docfx/images/Example.png)
 
 
-class Demo {
-	static void Main ()
-	{
-		Application.Run<App> ();
-		Application.Shutdown ();
-	}
-}
-```
+_Sample application running_
 
 
 ## Installing
 ## Installing
 
 
@@ -184,4 +155,4 @@ Debates on architecture and design can be found in Issues tagged with [design](h
 
 
 ## History
 ## History
 
 
-See [gui-cs](https://github.com/gui-cs/) for how this project came to be.
+See [gui-cs](https://github.com/gui-cs/) for how this project came to be.

+ 38 - 12
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -617,7 +617,7 @@ namespace Terminal.Gui {
 			return keyModifiers;
 			return keyModifiers;
 		}
 		}
 
 
-		void ProcessInput (Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+		void ProcessInput ()
 		{
 		{
 			int wch;
 			int wch;
 			var code = Curses.get_wch (out wch);
 			var code = Curses.get_wch (out wch);
@@ -787,6 +787,8 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		Action<KeyEvent> keyHandler;
 		Action<KeyEvent> keyHandler;
+		Action<KeyEvent> keyDownHandler;
+		Action<KeyEvent> keyUpHandler;
 		Action<MouseEvent> mouseHandler;
 		Action<MouseEvent> mouseHandler;
 
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
@@ -794,12 +796,14 @@ namespace Terminal.Gui {
 			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			Curses.timeout (0);
 			Curses.timeout (0);
 			this.keyHandler = keyHandler;
 			this.keyHandler = keyHandler;
+			this.keyDownHandler = keyDownHandler;
+			this.keyUpHandler = keyUpHandler;
 			this.mouseHandler = mouseHandler;
 			this.mouseHandler = mouseHandler;
 
 
 			var mLoop = mainLoop.Driver as UnixMainLoop;
 			var mLoop = mainLoop.Driver as UnixMainLoop;
 
 
 			mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
 			mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
-				ProcessInput (keyHandler, keyDownHandler, keyUpHandler, mouseHandler);
+				ProcessInput ();
 				return true;
 				return true;
 			});
 			});
 
 
@@ -1128,26 +1132,48 @@ namespace Terminal.Gui {
 			return false;
 			return false;
 		}
 		}
 
 
-		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
+		public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control)
 		{
 		{
-			Key k;
+			Key key;
 
 
-			if ((shift || alt || control)
-				&& keyChar - (int)Key.Space >= (uint)Key.A && keyChar - (int)Key.Space <= (uint)Key.Z) {
-				k = (Key)(keyChar - (uint)Key.Space);
+			if (consoleKey == ConsoleKey.Packet) {
+				ConsoleModifiers mod = new ConsoleModifiers ();
+				if (shift) {
+					mod |= ConsoleModifiers.Shift;
+				}
+				if (alt) {
+					mod |= ConsoleModifiers.Alt;
+				}
+				if (control) {
+					mod |= ConsoleModifiers.Control;
+				}
+				var kchar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyChar, mod, out uint ckey, out _);
+				key = ConsoleKeyMapping.MapConsoleKeyToKey ((ConsoleKey)ckey, out bool mappable);
+				if (mappable) {
+					key = (Key)kchar;
+				}
 			} else {
 			} else {
-				k = (Key)keyChar;
+				key = (Key)keyChar;
 			}
 			}
+
+			KeyModifiers km = new KeyModifiers ();
 			if (shift) {
 			if (shift) {
-				k |= Key.ShiftMask;
+				if (keyChar == 0) {
+					key |= Key.ShiftMask;
+				}
+				km.Shift = shift;
 			}
 			}
 			if (alt) {
 			if (alt) {
-				k |= Key.AltMask;
+				key |= Key.AltMask;
+				km.Alt = alt;
 			}
 			}
 			if (control) {
 			if (control) {
-				k |= Key.CtrlMask;
+				key |= Key.CtrlMask;
+				km.Ctrl = control;
 			}
 			}
-			keyHandler (new KeyEvent (k, MapKeyModifiers (k)));
+			keyDownHandler (new KeyEvent (key, km));
+			keyHandler (new KeyEvent (key, km));
+			keyUpHandler (new KeyEvent (key, km));
 		}
 		}
 
 
 		public override bool GetColors (int value, out Color foreground, out Color background)
 		public override bool GetColors (int value, out Color foreground, out Color background)

+ 11 - 6
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs

@@ -164,11 +164,13 @@ namespace Terminal.Gui {
 		//
 		//
 		//   T:System.IO.IOException:
 		//   T:System.IO.IOException:
 		//     An I/O error occurred.
 		//     An I/O error occurred.
+
+		static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black;
+
 		/// <summary>
 		/// <summary>
 		/// 
 		/// 
 		/// </summary>
 		/// </summary>
 		public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor;
 		public static ConsoleColor BackgroundColor { get; set; } = _defaultBackgroundColor;
-		static ConsoleColor _defaultBackgroundColor = ConsoleColor.Black;
 
 
 		//
 		//
 		// Summary:
 		// Summary:
@@ -187,11 +189,13 @@ namespace Terminal.Gui {
 		//
 		//
 		//   T:System.IO.IOException:
 		//   T:System.IO.IOException:
 		//     An I/O error occurred.
 		//     An I/O error occurred.
+
+		static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray;
+
 		/// <summary>
 		/// <summary>
 		/// 
 		/// 
 		/// </summary>
 		/// </summary>
 		public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor;
 		public static ConsoleColor ForegroundColor { get; set; } = _defaultForegroundColor;
-		static ConsoleColor _defaultForegroundColor = ConsoleColor.Gray;
 		//
 		//
 		// Summary:
 		// Summary:
 		//     Gets or sets the height of the buffer area.
 		//     Gets or sets the height of the buffer area.
@@ -541,6 +545,9 @@ namespace Terminal.Gui {
 		// Exceptions:
 		// Exceptions:
 		//   T:System.IO.IOException:
 		//   T:System.IO.IOException:
 		//     An I/O error occurred.
 		//     An I/O error occurred.
+
+		static char [,] _buffer = new char [WindowWidth, WindowHeight];
+
 		/// <summary>
 		/// <summary>
 		/// 
 		/// 
 		/// </summary>
 		/// </summary>
@@ -550,8 +557,6 @@ namespace Terminal.Gui {
 			SetCursorPosition (0, 0);
 			SetCursorPosition (0, 0);
 		}
 		}
 
 
-		static char [,] _buffer = new char [WindowWidth, WindowHeight];
-
 		//
 		//
 		// Summary:
 		// Summary:
 		//     Copies a specified source area of the screen buffer to a specified destination
 		//     Copies a specified source area of the screen buffer to a specified destination
@@ -811,9 +816,9 @@ namespace Terminal.Gui {
 		public static ConsoleKeyInfo ReadKey (bool intercept)
 		public static ConsoleKeyInfo ReadKey (bool intercept)
 		{
 		{
 			if (MockKeyPresses.Count > 0) {
 			if (MockKeyPresses.Count > 0) {
-				return MockKeyPresses.Pop();
+				return MockKeyPresses.Pop ();
 			} else {
 			} else {
-				return new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', false,false,false);
+				return new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', false, false, false);
 			}
 			}
 		}
 		}
 
 

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

@@ -256,6 +256,22 @@ namespace Terminal.Gui {
 			currentAttribute = c;
 			currentAttribute = c;
 		}
 		}
 
 
+		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+		{
+			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+				return consoleKeyInfo;
+			}
+
+			var mod = consoleKeyInfo.Modifiers;
+			var shift = (mod & ConsoleModifiers.Shift) != 0;
+			var alt = (mod & ConsoleModifiers.Alt) != 0;
+			var control = (mod & ConsoleModifiers.Control) != 0;
+
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
+
+			return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+		}
+
 		Key MapKey (ConsoleKeyInfo keyInfo)
 		Key MapKey (ConsoleKeyInfo keyInfo)
 		{
 		{
 			switch (keyInfo.Key) {
 			switch (keyInfo.Key) {
@@ -263,6 +279,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.Esc);
 				return MapKeyModifiers (keyInfo, Key.Esc);
 			case ConsoleKey.Tab:
 			case ConsoleKey.Tab:
 				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
 				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			case ConsoleKey.Clear:
+				return MapKeyModifiers (keyInfo, Key.Clear);
 			case ConsoleKey.Home:
 			case ConsoleKey.Home:
 				return MapKeyModifiers (keyInfo, Key.Home);
 				return MapKeyModifiers (keyInfo, Key.Home);
 			case ConsoleKey.End:
 			case ConsoleKey.End:
@@ -289,6 +307,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.DeleteChar);
 				return MapKeyModifiers (keyInfo, Key.DeleteChar);
 			case ConsoleKey.Insert:
 			case ConsoleKey.Insert:
 				return MapKeyModifiers (keyInfo, Key.InsertChar);
 				return MapKeyModifiers (keyInfo, Key.InsertChar);
+			case ConsoleKey.PrintScreen:
+				return MapKeyModifiers (keyInfo, Key.PrintScreen);
 
 
 			case ConsoleKey.Oem1:
 			case ConsoleKey.Oem1:
 			case ConsoleKey.Oem2:
 			case ConsoleKey.Oem2:
@@ -318,6 +338,9 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
 					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
 				}
 				}
+				if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+					return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
+				}
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 					if (keyInfo.KeyChar == 0) {
 					if (keyInfo.KeyChar == 0) {
 						return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta));
 						return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta));
@@ -335,9 +358,14 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 				}
 				}
-				if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
+				if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
 					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 				}
 				}
+				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
+						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
+					}
+				}
 				return (Key)((uint)keyInfo.KeyChar);
 				return (Key)((uint)keyInfo.KeyChar);
 			}
 			}
 			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
 			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
@@ -387,6 +415,9 @@ namespace Terminal.Gui {
 
 
 		void ProcessInput (ConsoleKeyInfo consoleKey)
 		void ProcessInput (ConsoleKeyInfo consoleKey)
 		{
 		{
+			if (consoleKey.Key == ConsoleKey.Packet) {
+				consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey);
+			}
 			keyModifiers = new KeyModifiers ();
 			keyModifiers = new KeyModifiers ();
 			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
 			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
 				keyModifiers.Shift = true;
 				keyModifiers.Shift = true;

+ 36 - 12
Terminal.Gui/ConsoleDrivers/NetDriver.cs

@@ -533,6 +533,7 @@ namespace Terminal.Gui {
 			int foundPoint = 0;
 			int foundPoint = 0;
 			string value = "";
 			string value = "";
 			var kChar = GetKeyCharArray (cki);
 			var kChar = GetKeyCharArray (cki);
+			//System.Diagnostics.Debug.WriteLine ($"kChar: {new string (kChar)}");
 			for (int i = 0; i < kChar.Length; i++) {
 			for (int i = 0; i < kChar.Length; i++) {
 				var c = kChar [i];
 				var c = kChar [i];
 				if (c == '<') {
 				if (c == '<') {
@@ -560,6 +561,8 @@ namespace Terminal.Gui {
 					//	isButtonPressed = false;
 					//	isButtonPressed = false;
 					//}
 					//}
 
 
+					//System.Diagnostics.Debug.WriteLine ($"buttonCode: {buttonCode}");
+
 					switch (buttonCode) {
 					switch (buttonCode) {
 					case 0:
 					case 0:
 					case 8:
 					case 8:
@@ -1610,6 +1613,22 @@ namespace Terminal.Gui {
 			currentAttribute = c;
 			currentAttribute = c;
 		}
 		}
 
 
+		public ConsoleKeyInfo FromVKPacketToKConsoleKeyInfo (ConsoleKeyInfo consoleKeyInfo)
+		{
+			if (consoleKeyInfo.Key != ConsoleKey.Packet) {
+				return consoleKeyInfo;
+			}
+
+			var mod = consoleKeyInfo.Modifiers;
+			var shift = (mod & ConsoleModifiers.Shift) != 0;
+			var alt = (mod & ConsoleModifiers.Alt) != 0;
+			var control = (mod & ConsoleModifiers.Control) != 0;
+
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (consoleKeyInfo.KeyChar, consoleKeyInfo.Modifiers, out uint virtualKey, out _);
+
+			return new ConsoleKeyInfo ((char)keyChar, (ConsoleKey)virtualKey, shift, alt, control);
+		}
+
 		Key MapKey (ConsoleKeyInfo keyInfo)
 		Key MapKey (ConsoleKeyInfo keyInfo)
 		{
 		{
 			MapKeyModifiers (keyInfo, (Key)keyInfo.Key);
 			MapKeyModifiers (keyInfo, (Key)keyInfo.Key);
@@ -1687,7 +1706,7 @@ namespace Terminal.Gui {
 					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 				}
 				}
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
+					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) {
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 					}
 					}
 				}
 				}
@@ -1754,14 +1773,23 @@ namespace Terminal.Gui {
 		{
 		{
 			switch (inputEvent.EventType) {
 			switch (inputEvent.EventType) {
 			case NetEvents.EventType.Key:
 			case NetEvents.EventType.Key:
+				ConsoleKeyInfo consoleKeyInfo = inputEvent.ConsoleKeyInfo;
+				if (consoleKeyInfo.Key == ConsoleKey.Packet) {
+					consoleKeyInfo = FromVKPacketToKConsoleKeyInfo (consoleKeyInfo);
+				}
 				keyModifiers = new KeyModifiers ();
 				keyModifiers = new KeyModifiers ();
-				var map = MapKey (inputEvent.ConsoleKeyInfo);
+				var map = MapKey (consoleKeyInfo);
 				if (map == (Key)0xffffffff) {
 				if (map == (Key)0xffffffff) {
 					return;
 					return;
 				}
 				}
-				keyDownHandler (new KeyEvent (map, keyModifiers));
-				keyHandler (new KeyEvent (map, keyModifiers));
-				keyUpHandler (new KeyEvent (map, keyModifiers));
+				if (map == Key.Null) {
+					keyDownHandler (new KeyEvent (map, keyModifiers));
+					keyUpHandler (new KeyEvent (map, keyModifiers));
+				} else {
+					keyDownHandler (new KeyEvent (map, keyModifiers));
+					keyHandler (new KeyEvent (map, keyModifiers));
+					keyUpHandler (new KeyEvent (map, keyModifiers));
+				}
 				break;
 				break;
 			case NetEvents.EventType.Mouse:
 			case NetEvents.EventType.Mouse:
 				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
 				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
@@ -1804,6 +1832,8 @@ namespace Terminal.Gui {
 
 
 		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
 		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
 		{
 		{
+			//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
+
 			MouseFlags mouseFlag = 0;
 			MouseFlags mouseFlag = 0;
 
 
 			if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) {
 			if ((me.ButtonState & NetEvents.MouseButtonState.Button1Pressed) != 0) {
@@ -1935,14 +1965,8 @@ namespace Terminal.Gui {
 		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
 		public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control)
 		{
 		{
 			NetEvents.InputResult input = new NetEvents.InputResult ();
 			NetEvents.InputResult input = new NetEvents.InputResult ();
-			ConsoleKey ck;
-			if (char.IsLetter (keyChar)) {
-				ck = key;
-			} else {
-				ck = (ConsoleKey)'\0';
-			}
 			input.EventType = NetEvents.EventType.Key;
 			input.EventType = NetEvents.EventType.Key;
-			input.ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, ck, shift, alt, control);
+			input.ConsoleKeyInfo = new ConsoleKeyInfo (keyChar, key, shift, alt, control);
 
 
 			try {
 			try {
 				ProcessInput (input);
 				ProcessInput (input);

+ 71 - 8
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -534,12 +534,14 @@ namespace Terminal.Gui {
 			public ConsoleKeyInfo consoleKeyInfo;
 			public ConsoleKeyInfo consoleKeyInfo;
 			public bool CapsLock;
 			public bool CapsLock;
 			public bool NumLock;
 			public bool NumLock;
+			public bool Scrolllock;
 
 
-			public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
+			public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock, bool scrolllock)
 			{
 			{
 				this.consoleKeyInfo = consoleKeyInfo;
 				this.consoleKeyInfo = consoleKeyInfo;
 				CapsLock = capslock;
 				CapsLock = capslock;
 				NumLock = numlock;
 				NumLock = numlock;
+				Scrolllock = scrolllock;
 			}
 			}
 		}
 		}
 
 
@@ -786,7 +788,26 @@ namespace Terminal.Gui {
 		{
 		{
 			switch (inputEvent.EventType) {
 			switch (inputEvent.EventType) {
 			case WindowsConsole.EventType.Key:
 			case WindowsConsole.EventType.Key:
+				var fromPacketKey = inputEvent.KeyEvent.wVirtualKeyCode == (uint)ConsoleKey.Packet;
+				if (fromPacketKey) {
+					inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
+				}
 				var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
 				var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
+				//var ke = inputEvent.KeyEvent;
+				//System.Diagnostics.Debug.WriteLine ($"fromPacketKey: {fromPacketKey}");
+				//if (ke.UnicodeChar == '\0') {
+				//	System.Diagnostics.Debug.WriteLine ("UnicodeChar: 0'\\0'");
+				//} else if (ke.UnicodeChar == 13) {
+				//	System.Diagnostics.Debug.WriteLine ("UnicodeChar: 13'\\n'");
+				//} else {
+				//	System.Diagnostics.Debug.WriteLine ($"UnicodeChar: {(uint)ke.UnicodeChar}'{ke.UnicodeChar}'");
+				//}
+				//System.Diagnostics.Debug.WriteLine ($"bKeyDown: {ke.bKeyDown}");
+				//System.Diagnostics.Debug.WriteLine ($"dwControlKeyState: {ke.dwControlKeyState}");
+				//System.Diagnostics.Debug.WriteLine ($"wRepeatCount: {ke.wRepeatCount}");
+				//System.Diagnostics.Debug.WriteLine ($"wVirtualKeyCode: {ke.wVirtualKeyCode}");
+				//System.Diagnostics.Debug.WriteLine ($"wVirtualScanCode: {ke.wVirtualScanCode}");
+
 				if (map == (Key)0xffffffff) {
 				if (map == (Key)0xffffffff) {
 					KeyEvent key = new KeyEvent ();
 					KeyEvent key = new KeyEvent ();
 
 
@@ -854,6 +875,9 @@ namespace Terminal.Gui {
 						keyUpHandler (key);
 						keyUpHandler (key);
 				} else {
 				} else {
 					if (inputEvent.KeyEvent.bKeyDown) {
 					if (inputEvent.KeyEvent.bKeyDown) {
+						// May occurs using SendKeys
+						if (keyModifiers == null)
+							keyModifiers = new KeyModifiers ();
 						// Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event
 						// Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event
 						keyDownHandler (new KeyEvent (map, keyModifiers));
 						keyDownHandler (new KeyEvent (map, keyModifiers));
 						keyHandler (new KeyEvent (map, keyModifiers));
 						keyHandler (new KeyEvent (map, keyModifiers));
@@ -861,7 +885,7 @@ namespace Terminal.Gui {
 						keyUpHandler (new KeyEvent (map, keyModifiers));
 						keyUpHandler (new KeyEvent (map, keyModifiers));
 					}
 					}
 				}
 				}
-				if (!inputEvent.KeyEvent.bKeyDown) {
+				if (!inputEvent.KeyEvent.bKeyDown && inputEvent.KeyEvent.dwControlKeyState == 0) {
 					keyModifiers = null;
 					keyModifiers = null;
 				}
 				}
 				break;
 				break;
@@ -1242,7 +1266,38 @@ namespace Terminal.Gui {
 				keyModifiers.Scrolllock = scrolllock;
 				keyModifiers.Scrolllock = scrolllock;
 
 
 			var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
 			var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
-			return new WindowsConsole.ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock);
+
+			return new WindowsConsole.ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock, scrolllock);
+		}
+
+		public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent)
+		{
+			if (keyEvent.wVirtualKeyCode != (uint)ConsoleKey.Packet) {
+				return keyEvent;
+			}
+
+			var mod = new ConsoleModifiers ();
+			if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ShiftPressed)) {
+				mod |= ConsoleModifiers.Shift;
+			}
+			if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightAltPressed) ||
+				keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftAltPressed)) {
+				mod |= ConsoleModifiers.Alt;
+			}
+			if (keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.LeftControlPressed) ||
+				keyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.RightControlPressed)) {
+				mod |= ConsoleModifiers.Control;
+			}
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (keyEvent.UnicodeChar, mod, out uint virtualKey, out uint scanCode);
+
+			return new WindowsConsole.KeyEventRecord {
+				UnicodeChar = (char)keyChar,
+				bKeyDown = keyEvent.bKeyDown,
+				dwControlKeyState = keyEvent.dwControlKeyState,
+				wRepeatCount = keyEvent.wRepeatCount,
+				wVirtualKeyCode = (ushort)virtualKey,
+				wVirtualScanCode = (ushort)scanCode
+			};
 		}
 		}
 
 
 		public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
 		public Key MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx)
@@ -1253,6 +1308,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.Esc);
 				return MapKeyModifiers (keyInfo, Key.Esc);
 			case ConsoleKey.Tab:
 			case ConsoleKey.Tab:
 				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
 				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			case ConsoleKey.Clear:
+				return MapKeyModifiers (keyInfo, Key.Clear);
 			case ConsoleKey.Home:
 			case ConsoleKey.Home:
 				return MapKeyModifiers (keyInfo, Key.Home);
 				return MapKeyModifiers (keyInfo, Key.Home);
 			case ConsoleKey.End:
 			case ConsoleKey.End:
@@ -1279,6 +1336,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.DeleteChar);
 				return MapKeyModifiers (keyInfo, Key.DeleteChar);
 			case ConsoleKey.Insert:
 			case ConsoleKey.Insert:
 				return MapKeyModifiers (keyInfo, Key.InsertChar);
 				return MapKeyModifiers (keyInfo, Key.InsertChar);
+			case ConsoleKey.PrintScreen:
+				return MapKeyModifiers (keyInfo, Key.PrintScreen);
 
 
 			case ConsoleKey.NumPad0:
 			case ConsoleKey.NumPad0:
 				return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
 				return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
@@ -1331,6 +1390,9 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
 					return (Key)(((uint)Key.AltMask) | ((uint)Key.A + delta));
 				}
 				}
+				if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+					return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
+				}
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 					if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
 					if (keyInfo.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
@@ -1347,8 +1409,11 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 					return (Key)(((uint)Key.CtrlMask) | ((uint)Key.D0 + delta));
 				}
 				}
+				if (keyInfo.Modifiers == (ConsoleModifiers.Shift | ConsoleModifiers.Alt)) {
+					return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
+				}
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
 				if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
-					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30) {
+					if (keyInfo.KeyChar == 0 || keyInfo.KeyChar == 30 || keyInfo.KeyChar == ((uint)Key.D0 + delta)) {
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.D0 + delta));
 					}
 					}
 				}
 				}
@@ -1369,7 +1434,7 @@ namespace Terminal.Gui {
 			return (Key)(0xffffffff);
 			return (Key)(0xffffffff);
 		}
 		}
 
 
-		Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
+		private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
 		{
 		{
 			Key keyMod = new Key ();
 			Key keyMod = new Key ();
 			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
 			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
@@ -1665,9 +1730,7 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			keyEvent.UnicodeChar = keyChar;
 			keyEvent.UnicodeChar = keyChar;
-			if ((shift || alt || control)
-				&& (key >= ConsoleKey.A && key <= ConsoleKey.Z
-				|| key >= ConsoleKey.D0 && key <= ConsoleKey.D9)) {
+			if ((uint)key < 255) {
 				keyEvent.wVirtualKeyCode = (ushort)key;
 				keyEvent.wVirtualKeyCode = (ushort)key;
 			} else {
 			} else {
 				keyEvent.wVirtualKeyCode = '\0';
 				keyEvent.wVirtualKeyCode = '\0';

+ 521 - 0
Terminal.Gui/Core/ConsoleKeyMapping.cs

@@ -0,0 +1,521 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Helper class to handle the scan code and virtual key from a <see cref="ConsoleKey"/>.
+	/// </summary>
+	public static class ConsoleKeyMapping {
+		private 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;
+			}
+
+			public bool Equals (ScanCodeMapping other)
+			{
+				return (this.ScanCode.Equals (other.ScanCode) &&
+					this.VirtualKey.Equals (other.VirtualKey) &&
+					this.Modifiers.Equals (other.Modifiers) &&
+					this.UnicodeChar.Equals (other.UnicodeChar));
+			}
+		}
+
+		private static ConsoleModifiers GetModifiers (uint unicodeChar, ConsoleModifiers modifiers, bool isConsoleKey)
+		{
+			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;
+			} else if ((!isConsoleKey || (isConsoleKey && (modifiers.HasFlag (ConsoleModifiers.Shift) ||
+				modifiers.HasFlag (ConsoleModifiers.Alt) || modifiers.HasFlag (ConsoleModifiers.Control)))) &&
+				unicodeChar >= 65 && unicodeChar <= 90) {
+
+				return ConsoleModifiers.Shift;
+			}
+			return 0;
+		}
+
+		private 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;
+			}
+
+			return null;
+		}
+
+		/// <summary>
+		/// Get the <see cref="ConsoleKey"/> from a <see cref="Key"/>.
+		/// </summary>
+		/// <param name="keyValue">The key value.</param>
+		/// <param name="modifiers">The modifiers keys.</param>
+		/// <param name="scanCode">The resulting scan code.</param>
+		/// <param name="outputChar">The resulting output character.</param>
+		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="outputChar"/>.</returns>
+		public static uint GetConsoleKeyFromKey (uint keyValue, ConsoleModifiers modifiers, out uint scanCode, out uint outputChar)
+		{
+			scanCode = 0;
+			outputChar = keyValue;
+			if (keyValue == 0) {
+				return 0;
+			}
+
+			uint consoleKey = MapKeyToConsoleKey (keyValue, out bool mappable);
+			if (mappable) {
+				var mod = GetModifiers (keyValue, modifiers, false);
+				var scode = GetScanCode ("UnicodeChar", keyValue, mod);
+				if (scode != null) {
+					consoleKey = scode.VirtualKey;
+					scanCode = scode.ScanCode;
+					outputChar = scode.UnicodeChar;
+				} else {
+					consoleKey = consoleKey < 0xff ? (uint)(consoleKey & 0xff | 0xff << 8) : consoleKey;
+				}
+			} else {
+				var mod = GetModifiers (keyValue, modifiers, false);
+				var scode = GetScanCode ("VirtualKey", consoleKey, mod);
+				if (scode != null) {
+					consoleKey = scode.VirtualKey;
+					scanCode = scode.ScanCode;
+					outputChar = scode.UnicodeChar;
+				}
+			}
+
+			return consoleKey;
+		}
+
+		/// <summary>
+		/// Get the output character from the <see cref="ConsoleKey"/>.
+		/// </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>
+		public 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 (decodedChar, modifiers, true);
+			scanCode = 0;
+			var scode = unicodeChar != 0 && unicodeChar >> 8 != 0xff ? GetScanCode ("VirtualKey", decodedChar, mod) : null;
+			if (scode != null) {
+				consoleKey = 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++) {
+					UnicodeCategory 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;
+						}
+					}
+				}
+			}
+
+			return keyChar;
+		}
+
+		/// <summary>
+		/// Maps a <see cref="Key"/> to a <see cref="ConsoleKey"/>.
+		/// </summary>
+		/// <param name="keyValue">The key value.</param>
+		/// <param name="isMappable">If <see langword="true"/> is mapped to a valid character, otherwise <see langword="false"/>.</param>
+		/// <returns>The <see cref="ConsoleKey"/> or the <paramref name="keyValue"/>.</returns>
+		public static uint MapKeyToConsoleKey (uint keyValue, out bool isMappable)
+		{
+			isMappable = false;
+
+			switch ((Key)keyValue) {
+			case Key.Delete:
+				return (uint)ConsoleKey.Delete;
+			case Key.CursorUp:
+				return (uint)ConsoleKey.UpArrow;
+			case Key.CursorDown:
+				return (uint)ConsoleKey.DownArrow;
+			case Key.CursorLeft:
+				return (uint)ConsoleKey.LeftArrow;
+			case Key.CursorRight:
+				return (uint)ConsoleKey.RightArrow;
+			case Key.PageUp:
+				return (uint)ConsoleKey.PageUp;
+			case Key.PageDown:
+				return (uint)ConsoleKey.PageDown;
+			case Key.Home:
+				return (uint)ConsoleKey.Home;
+			case Key.End:
+				return (uint)ConsoleKey.End;
+			case Key.InsertChar:
+				return (uint)ConsoleKey.Insert;
+			case Key.DeleteChar:
+				return (uint)ConsoleKey.Delete;
+			case Key.F1:
+				return (uint)ConsoleKey.F1;
+			case Key.F2:
+				return (uint)ConsoleKey.F2;
+			case Key.F3:
+				return (uint)ConsoleKey.F3;
+			case Key.F4:
+				return (uint)ConsoleKey.F4;
+			case Key.F5:
+				return (uint)ConsoleKey.F5;
+			case Key.F6:
+				return (uint)ConsoleKey.F6;
+			case Key.F7:
+				return (uint)ConsoleKey.F7;
+			case Key.F8:
+				return (uint)ConsoleKey.F8;
+			case Key.F9:
+				return (uint)ConsoleKey.F9;
+			case Key.F10:
+				return (uint)ConsoleKey.F10;
+			case Key.F11:
+				return (uint)ConsoleKey.F11;
+			case Key.F12:
+				return (uint)ConsoleKey.F12;
+			case Key.F13:
+				return (uint)ConsoleKey.F13;
+			case Key.F14:
+				return (uint)ConsoleKey.F14;
+			case Key.F15:
+				return (uint)ConsoleKey.F15;
+			case Key.F16:
+				return (uint)ConsoleKey.F16;
+			case Key.F17:
+				return (uint)ConsoleKey.F17;
+			case Key.F18:
+				return (uint)ConsoleKey.F18;
+			case Key.F19:
+				return (uint)ConsoleKey.F19;
+			case Key.F20:
+				return (uint)ConsoleKey.F20;
+			case Key.F21:
+				return (uint)ConsoleKey.F21;
+			case Key.F22:
+				return (uint)ConsoleKey.F22;
+			case Key.F23:
+				return (uint)ConsoleKey.F23;
+			case Key.F24:
+				return (uint)ConsoleKey.F24;
+			case Key.BackTab:
+				return (uint)ConsoleKey.Tab;
+			case Key.Unknown:
+				isMappable = true;
+				return 0;
+			}
+			isMappable = true;
+
+			return keyValue;
+		}
+
+		/// <summary>
+		/// Maps a <see cref="ConsoleKey"/> to a <see cref="Key"/>.
+		/// </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="Key"/> or the <paramref name="consoleKey"/>.</returns>
+		public static Key MapConsoleKeyToKey (ConsoleKey consoleKey, out bool isMappable)
+		{
+			isMappable = false;
+
+			switch (consoleKey) {
+			case ConsoleKey.Delete:
+				return Key.Delete;
+			case ConsoleKey.UpArrow:
+				return Key.CursorUp;
+			case ConsoleKey.DownArrow:
+				return Key.CursorDown;
+			case ConsoleKey.LeftArrow:
+				return Key.CursorLeft;
+			case ConsoleKey.RightArrow:
+				return Key.CursorRight;
+			case ConsoleKey.PageUp:
+				return Key.PageUp;
+			case ConsoleKey.PageDown:
+				return Key.PageDown;
+			case ConsoleKey.Home:
+				return Key.Home;
+			case ConsoleKey.End:
+				return Key.End;
+			case ConsoleKey.Insert:
+				return Key.InsertChar;
+			case ConsoleKey.F1:
+				return Key.F1;
+			case ConsoleKey.F2:
+				return Key.F2;
+			case ConsoleKey.F3:
+				return Key.F3;
+			case ConsoleKey.F4:
+				return Key.F4;
+			case ConsoleKey.F5:
+				return Key.F5;
+			case ConsoleKey.F6:
+				return Key.F6;
+			case ConsoleKey.F7:
+				return Key.F7;
+			case ConsoleKey.F8:
+				return Key.F8;
+			case ConsoleKey.F9:
+				return Key.F9;
+			case ConsoleKey.F10:
+				return Key.F10;
+			case ConsoleKey.F11:
+				return Key.F11;
+			case ConsoleKey.F12:
+				return Key.F12;
+			case ConsoleKey.F13:
+				return Key.F13;
+			case ConsoleKey.F14:
+				return Key.F14;
+			case ConsoleKey.F15:
+				return Key.F15;
+			case ConsoleKey.F16:
+				return Key.F16;
+			case ConsoleKey.F17:
+				return Key.F17;
+			case ConsoleKey.F18:
+				return Key.F18;
+			case ConsoleKey.F19:
+				return Key.F19;
+			case ConsoleKey.F20:
+				return Key.F20;
+			case ConsoleKey.F21:
+				return Key.F21;
+			case ConsoleKey.F22:
+				return Key.F22;
+			case ConsoleKey.F23:
+				return Key.F23;
+			case ConsoleKey.F24:
+				return Key.F24;
+			case ConsoleKey.Tab:
+				return Key.BackTab;
+			}
+			isMappable = true;
+
+			return (Key)consoleKey;
+		}
+
+		private 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 (30,65,0,97),	// A
+			new ScanCodeMapping (30,65,ConsoleModifiers.Shift,65),
+			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)
+		};
+	}
+}

+ 79 - 18
Terminal.Gui/Core/Event.cs

@@ -77,11 +77,26 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		Null = '\0',
 		Null = '\0',
 
 
+		/// <summary>
+		/// Backspace key.
+		/// </summary>
+		Backspace = 8,
+
+		/// <summary>
+		/// The key code for the user pressing the tab key (forwards tab key).
+		/// </summary>
+		Tab = 9,
+
 		/// <summary>
 		/// <summary>
 		/// The key code for the user pressing the return key.
 		/// The key code for the user pressing the return key.
 		/// </summary>
 		/// </summary>
 		Enter = '\n',
 		Enter = '\n',
 
 
+		/// <summary>
+		/// The key code for the user pressing the clear key.
+		/// </summary>
+		Clear = 12,
+
 		/// <summary>
 		/// <summary>
 		/// The key code for the user pressing the escape key
 		/// The key code for the user pressing the escape key
 		/// </summary>
 		/// </summary>
@@ -363,15 +378,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		CtrlMask = 0x40000000,
 		CtrlMask = 0x40000000,
 
 
-		/// <summary>
-		/// Backspace key.
-		/// </summary>
-		Backspace = 0x100000,
-
 		/// <summary>
 		/// <summary>
 		/// Cursor up key
 		/// Cursor up key
 		/// </summary>
 		/// </summary>
-		CursorUp,
+		CursorUp = 0x100000,
 		/// <summary>
 		/// <summary>
 		/// Cursor down key.
 		/// Cursor down key.
 		/// </summary>
 		/// </summary>
@@ -393,21 +403,33 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		PageDown,
 		PageDown,
 		/// <summary>
 		/// <summary>
-		/// Home key
+		/// Home key.
 		/// </summary>
 		/// </summary>
 		Home,
 		Home,
 		/// <summary>
 		/// <summary>
-		/// End key
+		/// End key.
 		/// </summary>
 		/// </summary>
 		End,
 		End,
+
+		/// <summary>
+		/// Insert character key.
+		/// </summary>
+		InsertChar,
+
 		/// <summary>
 		/// <summary>
-		/// Delete character key
+		/// Delete character key.
 		/// </summary>
 		/// </summary>
 		DeleteChar,
 		DeleteChar,
+
 		/// <summary>
 		/// <summary>
-		/// Insert character key
+		/// Shift-tab key (backwards tab key).
 		/// </summary>
 		/// </summary>
-		InsertChar,
+		BackTab,
+
+		/// <summary>
+		/// Print screen character key.
+		/// </summary>
+		PrintScreen,
 
 
 		/// <summary>
 		/// <summary>
 		/// F1 key.
 		/// F1 key.
@@ -457,15 +479,54 @@ namespace Terminal.Gui {
 		/// F12 key.
 		/// F12 key.
 		/// </summary>
 		/// </summary>
 		F12,
 		F12,
-
 		/// <summary>
 		/// <summary>
-		/// The key code for the user pressing the tab key (forwards tab key).
+		/// F13 key.
 		/// </summary>
 		/// </summary>
-		Tab,
+		F13,
 		/// <summary>
 		/// <summary>
-		/// Shift-tab key (backwards tab key).
+		/// F14 key.
 		/// </summary>
 		/// </summary>
-		BackTab,
+		F14,
+		/// <summary>
+		/// F15 key.
+		/// </summary>
+		F15,
+		/// <summary>
+		/// F16 key.
+		/// </summary>
+		F16,
+		/// <summary>
+		/// F17 key.
+		/// </summary>
+		F17,
+		/// <summary>
+		/// F18 key.
+		/// </summary>
+		F18,
+		/// <summary>
+		/// F19 key.
+		/// </summary>
+		F19,
+		/// <summary>
+		/// F20 key.
+		/// </summary>
+		F20,
+		/// <summary>
+		/// F21 key.
+		/// </summary>
+		F21,
+		/// <summary>
+		/// F22 key.
+		/// </summary>
+		F22,
+		/// <summary>
+		/// F23 key.
+		/// </summary>
+		F23,
+		/// <summary>
+		/// F24 key.
+		/// </summary>
+		F24,
 
 
 		/// <summary>
 		/// <summary>
 		/// A key with an unknown mapping was raised.
 		/// A key with an unknown mapping was raised.
@@ -480,7 +541,7 @@ namespace Terminal.Gui {
 		KeyModifiers keyModifiers;
 		KeyModifiers keyModifiers;
 
 
 		/// <summary>
 		/// <summary>
-		/// Symb olid definition for the key.
+		/// Symbolic definition for the key.
 		/// </summary>
 		/// </summary>
 		public Key Key;
 		public Key Key;
 
 
@@ -573,7 +634,7 @@ namespace Terminal.Gui {
 				msg += "Scrolllock-";
 				msg += "Scrolllock-";
 			}
 			}
 
 
-			msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}";
+			msg += $"{((Key)KeyValue != Key.Unknown && ((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}";
 
 
 			return msg;
 			return msg;
 		}
 		}

+ 40 - 20
Terminal.Gui/Core/ContextMenu.cs → Terminal.Gui/Views/ContextMenu.cs

@@ -2,8 +2,24 @@
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
-	/// A context menu window derived from <see cref="MenuBar"/> containing menu items
-	/// which can be opened in any position.
+	/// ContextMenu provides a pop-up menu that can be positioned anywhere within a <see cref="View"/>. 
+	/// ContextMenu is analogous to <see cref="MenuBar"/> and, once activated, works like a sub-menu 
+	/// of a <see cref="MenuBarItem"/> (but can be positioned anywhere).
+	/// <para>
+	/// By default, a ContextMenu with sub-menus is displayed in a cascading manner, where each sub-menu pops out of the ContextMenu frame
+	/// (either to the right or left, depending on where the ContextMenu is relative to the edge of the screen). By setting
+	/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-menus are
+	/// drawn within the ContextMenu frame.
+	/// </para>
+	/// <para>
+	/// ContextMenus can be activated using the Shift-F10 key (by default; use the <see cref="Key"/> to change to another key).
+	/// </para>
+	/// <para>
+	/// Callers can cause the ContextMenu to be activated on a right-mouse click (or other interaction) by calling <see cref="Show()"/>.
+	/// </para>
+	/// <para>
+	/// ContextMenus are located using screen using screen coordinates and appear above all other Views.
+	/// </para>
 	/// </summary>
 	/// </summary>
 	public sealed class ContextMenu : IDisposable {
 	public sealed class ContextMenu : IDisposable {
 		private static MenuBar menuBar;
 		private static MenuBar menuBar;
@@ -12,15 +28,15 @@ namespace Terminal.Gui {
 		private Toplevel container;
 		private Toplevel container;
 
 
 		/// <summary>
 		/// <summary>
-		/// Initialize a context menu with empty menu items.
+		/// Initializes a context menu with no menu items.
 		/// </summary>
 		/// </summary>
 		public ContextMenu () : this (0, 0, new MenuBarItem ()) { }
 		public ContextMenu () : this (0, 0, new MenuBarItem ()) { }
 
 
 		/// <summary>
 		/// <summary>
-		/// Initialize a context menu with menu items from a host <see cref="View"/>.
+		/// Initializes a context menu, with a <see cref="View"/> specifiying the parent/hose of the menu.
 		/// </summary>
 		/// </summary>
 		/// <param name="host">The host view.</param>
 		/// <param name="host">The host view.</param>
-		/// <param name="menuItems">The menu items.</param>
+		/// <param name="menuItems">The menu items for the context menu.</param>
 		public ContextMenu (View host, MenuBarItem menuItems) :
 		public ContextMenu (View host, MenuBarItem menuItems) :
 			this (host.Frame.X, host.Frame.Y, menuItems)
 			this (host.Frame.X, host.Frame.Y, menuItems)
 		{
 		{
@@ -28,10 +44,10 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Initialize a context menu with menu items.
+		/// Initializes a context menu with menu items at a specific screen location.
 		/// </summary>
 		/// </summary>
-		/// <param name="x">The left position.</param>
-		/// <param name="y">The top position.</param>
+		/// <param name="x">The left position (screen relative).</param>
+		/// <param name="y">The top position (screen relative).</param>
 		/// <param name="menuItems">The menu items.</param>
 		/// <param name="menuItems">The menu items.</param>
 		public ContextMenu (int x, int y, MenuBarItem menuItems)
 		public ContextMenu (int x, int y, MenuBarItem menuItems)
 		{
 		{
@@ -48,7 +64,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Disposes the all the context menu objects instances.
+		/// Disposes the context menu object.
 		/// </summary>
 		/// </summary>
 		public void Dispose ()
 		public void Dispose ()
 		{
 		{
@@ -65,7 +81,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Open the <see cref="MenuItems"/> menu items.
+		/// Shows (opens) the ContextMenu, displaying the <see cref="MenuItem"/>s it contains.
 		/// </summary>
 		/// </summary>
 		public void Show ()
 		public void Show ()
 		{
 		{
@@ -110,13 +126,14 @@ namespace Terminal.Gui {
 			} else if (ForceMinimumPosToZero && position.Y < 0) {
 			} else if (ForceMinimumPosToZero && position.Y < 0) {
 				position.Y = 0;
 				position.Y = 0;
 			}
 			}
-
+			
 			menuBar = new MenuBar (new [] { MenuItems }) {
 			menuBar = new MenuBar (new [] { MenuItems }) {
 				X = position.X,
 				X = position.X,
 				Y = position.Y,
 				Y = position.Y,
 				Width = 0,
 				Width = 0,
 				Height = 0,
 				Height = 0,
-				UseSubMenusSingleFrame = UseSubMenusSingleFrame
+				UseSubMenusSingleFrame = UseSubMenusSingleFrame,
+				Key = Key
 			};
 			};
 
 
 			menuBar.isContextMenuLoading = true;
 			menuBar.isContextMenuLoading = true;
@@ -138,7 +155,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Close the <see cref="MenuItems"/> menu items.
+		/// Hides (closes) the ContextMenu.
 		/// </summary>
 		/// </summary>
 		public void Hide ()
 		public void Hide ()
 		{
 		{
@@ -157,7 +174,7 @@ namespace Terminal.Gui {
 		public event Action<MouseFlags> MouseFlagsChanged;
 		public event Action<MouseFlags> MouseFlagsChanged;
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or set the menu position.
+		/// Gets or sets the menu position.
 		/// </summary>
 		/// </summary>
 		public Point Position { get; set; }
 		public Point Position { get; set; }
 
 
@@ -167,7 +184,7 @@ namespace Terminal.Gui {
 		public MenuBarItem MenuItems { get; set; }
 		public MenuBarItem MenuItems { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// The <see cref="Gui.Key"/> used to activate the context menu by keyboard.
+		/// <see cref="Gui.Key"/> specifies they keyboard key that will activate the context menu with the keyboard.
 		/// </summary>
 		/// </summary>
 		public Key Key {
 		public Key Key {
 			get => key;
 			get => key;
@@ -179,7 +196,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// The <see cref="Gui.MouseFlags"/> used to activate the context menu by mouse.
+		/// <see cref="Gui.MouseFlags"/> specifies the mouse action used to activate the context menu by mouse.
 		/// </summary>
 		/// </summary>
 		public MouseFlags MouseFlags {
 		public MouseFlags MouseFlags {
 			get => mouseFlags;
 			get => mouseFlags;
@@ -191,7 +208,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets information whether menu is showing or not.
+		/// Gets whether the ContextMenu is showing or not.
 		/// </summary>
 		/// </summary>
 		public static bool IsShow { get; private set; }
 		public static bool IsShow { get; private set; }
 
 
@@ -202,8 +219,9 @@ namespace Terminal.Gui {
 		public View Host { get; set; }
 		public View Host { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets whether forces the minimum position to zero
-		/// if the left or right position are negative.
+		/// Sets or gets whether the context menu be forced to the right, ensuring it is not clipped, if the x position 
+		/// is less than zero. The default is <see langword="true"/> which means the context menu will be forced to the right.
+		/// If set to <see langword="false"/>, the context menu will be clipped on the left if x is less than zero.
 		/// </summary>
 		/// </summary>
 		public bool ForceMinimumPosToZero { get; set; } = true;
 		public bool ForceMinimumPosToZero { get; set; } = true;
 
 
@@ -213,7 +231,9 @@ namespace Terminal.Gui {
 		public MenuBar MenuBar { get => menuBar; }
 		public MenuBar MenuBar { get => menuBar; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
+		/// Gets or sets if sub-menus will be displayed using a "single frame" menu style. If <see langword="true"/>, the ContextMenu
+		/// and any sub-menus that would normally cascade will be displayed within a single frame. If <see langword="false"/> (the default),
+		/// sub-menus will cascade using separate frames for each level of the menu hierarchy.
 		/// </summary>
 		/// </summary>
 		public bool UseSubMenusSingleFrame { get; set; }
 		public bool UseSubMenusSingleFrame { get; set; }
 	}
 	}

+ 128 - 158
Terminal.Gui/Views/Menu.cs

@@ -1,13 +1,3 @@
-//
-// Menu.cs: application menus and submenus
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-// TODO:
-//   Add accelerator support, but should also support chords (Shortcut in MenuItem)
-//   Allow menus inside menus
-
 using System;
 using System;
 using NStack;
 using NStack;
 using System.Linq;
 using System.Linq;
@@ -21,23 +11,24 @@ namespace Terminal.Gui {
 	[Flags]
 	[Flags]
 	public enum MenuItemCheckStyle {
 	public enum MenuItemCheckStyle {
 		/// <summary>
 		/// <summary>
-		/// The menu item will be shown normally, with no check indicator.
+		/// The menu item will be shown normally, with no check indicator. The default.
 		/// </summary>
 		/// </summary>
 		NoCheck = 0b_0000_0000,
 		NoCheck = 0b_0000_0000,
 
 
 		/// <summary>
 		/// <summary>
-		/// The menu item will indicate checked/un-checked state (see <see cref="Checked"/>.
+		/// The menu item will indicate checked/un-checked state (see <see cref="Checked"/>).
 		/// </summary>
 		/// </summary>
 		Checked = 0b_0000_0001,
 		Checked = 0b_0000_0001,
 
 
 		/// <summary>
 		/// <summary>
-		/// The menu item is part of a menu radio group (see <see cref="Checked"/> and will indicate selected state.
+		/// The menu item is part of a menu radio group (see <see cref="Checked"/>) and will indicate selected state.
 		/// </summary>
 		/// </summary>
 		Radio = 0b_0000_0010,
 		Radio = 0b_0000_0010,
 	};
 	};
 
 
 	/// <summary>
 	/// <summary>
-	/// A <see cref="MenuItem"/> has a title, an associated help text, and an action to execute on activation.
+	/// A <see cref="MenuItem"/> has title, an associated help text, and an action to execute on activation. 
+	/// MenuItems can also have a checked indicator (see <see cref="Checked"/>).
 	/// </summary>
 	/// </summary>
 	public class MenuItem {
 	public class MenuItem {
 		ustring title;
 		ustring title;
@@ -78,14 +69,28 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// The HotKey is used when the menu is active, the shortcut can be triggered when the menu is not active.
-		/// For example HotKey would be "N" when the File Menu is open (assuming there is a "_New" entry
-		/// if the Shortcut is set to "Control-N", this would be a global hotkey that would trigger as well
+		/// The HotKey is used to activate a <see cref="MenuItem"/> with the keyboard. HotKeys are defined by prefixing the <see cref="Title"/>
+		/// of a MenuItem with an underscore ('_'). 
+		/// <para>
+		/// Pressing Alt-Hotkey for a <see cref="MenuBarItem"/> (menu items on the menu bar) works even if the menu is not active). 
+		/// Once a menu has focus and is active, pressing just the HotKey will activate the MenuItem.
+		/// </para>
+		/// <para>
+		/// For example for a MenuBar with a "_File" MenuBarItem that contains a "_New" MenuItem, Alt-F will open the File menu.
+		/// Pressing the N key will then activate the New MenuItem.
+		/// </para>
+		/// <para>
+		/// See also <see cref="Shortcut"/> which enable global key-bindings to menu items.
+		/// </para>
 		/// </summary>
 		/// </summary>
 		public Rune HotKey;
 		public Rune HotKey;
 
 
 		/// <summary>
 		/// <summary>
-		/// This is the global setting that can be used as a global <see cref="ShortcutHelper.Shortcut"/> to invoke the action on the menu.
+		/// Shortcut defines a key binding to the MenuItem that will invoke the MenuItem's action globally for the <see cref="View"/> that is
+		/// the parent of the <see cref="MenuBar"/> or <see cref="ContextMenu"/> this <see cref="MenuItem"/>.
+		/// <para>
+		/// The <see cref="Key"/> will be drawn on the MenuItem to the right of the <see cref="Title"/> and <see cref="Help"/> text. See <see cref="ShortcutTag"/>.
+		/// </para>
 		/// </summary>
 		/// </summary>
 		public Key Shortcut {
 		public Key Shortcut {
 			get => shortcutHelper.Shortcut;
 			get => shortcutHelper.Shortcut;
@@ -97,12 +102,12 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// The keystroke combination used in the <see cref="ShortcutHelper.ShortcutTag"/> as string.
+		/// Gets the text describing the keystroke combination defined by <see cref="Shortcut"/>.
 		/// </summary>
 		/// </summary>
 		public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (shortcutHelper.Shortcut);
 		public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (shortcutHelper.Shortcut);
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the title.
+		/// Gets or sets the title of the menu item .
 		/// </summary>
 		/// </summary>
 		/// <value>The title.</value>
 		/// <value>The title.</value>
 		public ustring Title {
 		public ustring Title {
@@ -116,34 +121,46 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the help text for the menu item.
+		/// Gets or sets the help text for the menu item. The help text is drawn to the right of the <see cref="Title"/>.
 		/// </summary>
 		/// </summary>
 		/// <value>The help text.</value>
 		/// <value>The help text.</value>
 		public ustring Help { get; set; }
 		public ustring Help { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the action to be invoked when the menu is triggered
+		/// Gets or sets the action to be invoked when the menu item is triggered.
 		/// </summary>
 		/// </summary>
 		/// <value>Method to invoke.</value>
 		/// <value>Method to invoke.</value>
 		public Action Action { get; set; }
 		public Action Action { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the action to be invoked if the menu can be triggered
+		/// Gets or sets the action to be invoked to determine if the menu can be triggered. If <see cref="CanExecute"/> returns <see langword="true"/>
+		/// the menu item will be enabled. Otherwise, it will be disabled. 
 		/// </summary>
 		/// </summary>
-		/// <value>Function to determine if action is ready to be executed.</value>
+		/// <value>Function to determine if the action is can be executed or not.</value>
 		public Func<bool> CanExecute { get; set; }
 		public Func<bool> CanExecute { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Shortcut to check if the menu item is enabled
+		/// Returns <see langword="true"/> if the menu item is enabled. This method is a wrapper around <see cref="CanExecute"/>.
 		/// </summary>
 		/// </summary>
 		public bool IsEnabled ()
 		public bool IsEnabled ()
 		{
 		{
 			return CanExecute == null ? true : CanExecute ();
 			return CanExecute == null ? true : CanExecute ();
 		}
 		}
 
 
-		internal int Width => 1 + TitleLength + (Help.ConsoleWidth > 0 ? Help.ConsoleWidth + 2 : 0) +
-			(Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) +
-			(ShortcutTag.ConsoleWidth > 0 ? ShortcutTag.ConsoleWidth + 2 : 0) + 2;
+		// 
+		// ┌─────────────────────────────┐
+		// │ Quit  Quit UI Catalog  Ctrl+Q │
+		// └─────────────────────────────┘
+		// ┌─────────────────┐
+		// │ ◌ TopLevel Alt+T │
+		// └─────────────────┘
+		// TODO: Replace the `2` literals with named constants 
+		internal int Width => 1 + // space before Title
+			TitleLength +
+			2 + // space after Title - BUGBUG: This should be 1 
+			(Checked || CheckType.HasFlag (MenuItemCheckStyle.Checked) || CheckType.HasFlag (MenuItemCheckStyle.Radio) ? 2 : 0) + // check glyph + space 
+			(Help.ConsoleWidth > 0 ? 2 + Help.ConsoleWidth : 0) + // Two spaces before Help
+			(ShortcutTag.ConsoleWidth > 0 ? 2 + ShortcutTag.ConsoleWidth : 0); // Pad two spaces before shortcut tag (which are also aligned right)
 
 
 		/// <summary>
 		/// <summary>
 		/// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See <see cref="MenuItemCheckStyle"/>.
 		/// Sets or gets whether the <see cref="MenuItem"/> shows a check indicator or not. See <see cref="MenuItemCheckStyle"/>.
@@ -151,12 +168,12 @@ namespace Terminal.Gui {
 		public bool Checked { set; get; }
 		public bool Checked { set; get; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Sets or gets the type selection indicator the menu item will be displayed with.
+		/// Sets or gets the <see cref="MenuItemCheckStyle"/> of a menu item where <see cref="Checked"/> is set to <see langword="true"/>.
 		/// </summary>
 		/// </summary>
 		public MenuItemCheckStyle CheckType { get; set; }
 		public MenuItemCheckStyle CheckType { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the parent for this <see cref="MenuItem"/>.
+		/// Gets the parent for this <see cref="MenuItem"/>.
 		/// </summary>
 		/// </summary>
 		/// <value>The parent.</value>
 		/// <value>The parent.</value>
 		public MenuItem Parent { get; internal set; }
 		public MenuItem Parent { get; internal set; }
@@ -167,7 +184,7 @@ namespace Terminal.Gui {
 		internal bool IsFromSubMenu { get { return Parent != null; } }
 		internal bool IsFromSubMenu { get { return Parent != null; } }
 
 
 		/// <summary>
 		/// <summary>
-		/// Merely a debugging aid to see the interaction with main
+		/// Merely a debugging aid to see the interaction with main.
 		/// </summary>
 		/// </summary>
 		public MenuItem GetMenuItem ()
 		public MenuItem GetMenuItem ()
 		{
 		{
@@ -175,7 +192,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Merely a debugging aid to see the interaction with main
+		/// Merely a debugging aid to see the interaction with main.
 		/// </summary>
 		/// </summary>
 		public bool GetMenuBarItem ()
 		public bool GetMenuBarItem ()
 		{
 		{
@@ -213,14 +230,15 @@ namespace Terminal.Gui {
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
-	/// A <see cref="MenuBarItem"/> contains <see cref="MenuBarItem"/>s or <see cref="MenuItem"/>s.
+	/// <see cref="MenuBarItem"/> is a menu item on an app's <see cref="MenuBar"/>. 
+	/// MenuBarItems do not support <see cref="MenuItem.Shortcut"/>.
 	/// </summary>
 	/// </summary>
 	public class MenuBarItem : MenuItem {
 	public class MenuBarItem : MenuItem {
 		/// <summary>
 		/// <summary>
 		/// Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.
 		/// Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.
 		/// </summary>
 		/// </summary>
 		/// <param name="title">Title for the menu item.</param>
 		/// <param name="title">Title for the menu item.</param>
-		/// <param name="help">Help text to display.</param>
+		/// <param name="help">Help text to display. Will be displayed next to the Title surrounded by parentheses.</param>
 		/// <param name="action">Action to invoke when the menu item is activated.</param>
 		/// <param name="action">Action to invoke when the menu item is activated.</param>
 		/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
 		/// <param name="canExecute">Function to determine if the action can currently be executed.</param>
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
 		/// <param name="parent">The parent <see cref="MenuItem"/> of this if exist, otherwise is null.</param>
@@ -289,19 +307,6 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
-		//static int GetMaxTitleLength (MenuItem [] children)
-		//{
-		//	int maxLength = 0;
-		//	foreach (var item in children) {
-		//		int len = GetMenuBarItemLength (item.Title);
-		//		if (len > maxLength)
-		//			maxLength = len;
-		//		item.IsFromSubMenu = true;
-		//	}
-
-		//	return maxLength;
-		//}
-
 		void SetChildrensParent (MenuItem [] childrens)
 		void SetChildrensParent (MenuItem [] childrens)
 		{
 		{
 			foreach (var child in childrens) {
 			foreach (var child in childrens) {
@@ -363,12 +368,6 @@ namespace Terminal.Gui {
 			Title = title;
 			Title = title;
 		}
 		}
 
 
-		///// <summary>
-		///// Gets or sets the title to display.
-		///// </summary>
-		///// <value>The title.</value>
-		//public ustring Title { get; set; }
-
 		/// <summary>
 		/// <summary>
 		/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this <see cref="MenuBarItem"/>
 		/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this <see cref="MenuBarItem"/>
 		/// </summary>
 		/// </summary>
@@ -391,8 +390,8 @@ namespace Terminal.Gui {
 			}
 			}
 			int minX = x;
 			int minX = x;
 			int minY = y;
 			int minY = y;
-			int maxW = (items.Max (z => z?.Width) ?? 0) + 2;
-			int maxH = items.Length + 2;
+			int maxW = (items.Max (z => z?.Width) ?? 0) + 2; // This 2 is frame border?
+			int maxH = items.Length + 2; // This 2 is frame border?
 			if (parent != null && x + maxW > Driver.Cols) {
 			if (parent != null && x + maxW > Driver.Cols) {
 				minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
 				minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
 			}
 			}
@@ -459,6 +458,7 @@ namespace Terminal.Gui {
 			return GetNormalColor ();
 			return GetNormalColor ();
 		}
 		}
 
 
+		// Draws the Menu, within the Frame
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
@@ -477,13 +477,14 @@ namespace Terminal.Gui {
 					Move (1, i + 1);
 					Move (1, i + 1);
 
 
 				Driver.SetAttribute (DetermineColorSchemeFor (item, i));
 				Driver.SetAttribute (DetermineColorSchemeFor (item, i));
-				for (int p = Bounds.X; p < Frame.Width - 2; p++) {
+				for (int p = Bounds.X; p < Frame.Width - 2; p++) { // This - 2 is for the border
 					if (p < 0)
 					if (p < 0)
 						continue;
 						continue;
 					if (item == null)
 					if (item == null)
 						Driver.AddRune (Driver.HLine);
 						Driver.AddRune (Driver.HLine);
 					else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null)
 					else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null)
 						Driver.AddRune (Driver.LeftArrow);
 						Driver.AddRune (Driver.LeftArrow);
+					// This `- 3` is left border + right border + one row in from right
 					else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null)
 					else if (p == Frame.Width - 3 && barItems.SubMenu (barItems.Children [i]) != null)
 						Driver.AddRune (Driver.RightArrow);
 						Driver.AddRune (Driver.RightArrow);
 					else
 					else
@@ -527,6 +528,7 @@ namespace Terminal.Gui {
 							HotKeySpecifier = MenuBar.HotKeySpecifier,
 							HotKeySpecifier = MenuBar.HotKeySpecifier,
 							Text = textToDraw
 							Text = textToDraw
 						};
 						};
+						// The -3 is left/right border + one space (not sure what for)
 						tf.Draw (ViewToScreen (new Rect (2, i + 1, Frame.Width - 3, 1)),
 						tf.Draw (ViewToScreen (new Rect (2, i + 1, Frame.Width - 3, 1)),
 							i == current ? ColorScheme.Focus : GetNormalColor (),
 							i == current ? ColorScheme.Focus : GetNormalColor (),
 							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
 							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
@@ -630,7 +632,7 @@ namespace Terminal.Gui {
 					}
 					}
 				}
 				}
 			}
 			}
-			return false;
+			return host.ProcessHotKey (kb);
 		}
 		}
 
 
 		void RunSelected ()
 		void RunSelected ()
@@ -832,17 +834,27 @@ namespace Terminal.Gui {
 		}
 		}
 	}
 	}
 
 
-
-
 	/// <summary>
 	/// <summary>
-	/// Provides a menu bar with drop-down and cascading menus. 
+	///	<para>
+	/// Provides a menu bar that spans the top of a <see cref="Toplevel"/> View with drop-down and cascading menus. 
+	///	</para>
+	/// <para>
+	/// By default, any sub-sub-menus (sub-menus of the <see cref="MenuItem"/>s added to <see cref="MenuBarItem"/>s) 
+	/// are displayed in a cascading manner, where each sub-sub-menu pops out of the sub-menu frame
+	/// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
+	/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are
+	/// drawn within a single frame below the MenuBar.
+	/// </para>
 	/// </summary>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
 	///	<para>
 	///	<para>
-	///	The <see cref="MenuBar"/> appears on the first row of the terminal.
+	///	The <see cref="MenuBar"/> appears on the first row of the parent <see cref="Toplevel"/> View and uses the full width.
 	///	</para>
 	///	</para>
 	///	<para>
 	///	<para>
-	///	The <see cref="MenuBar"/> provides global hotkeys for the application.
+	///	The <see cref="MenuBar"/> provides global hotkeys for the application. See <see cref="MenuItem.HotKey"/>.
+	///	</para>
+	///	<para>
+	///	See also: <see cref="ContextMenu"/>
 	///	</para>
 	///	</para>
 	/// </remarks>
 	/// </remarks>
 	public class MenuBar : View {
 	public class MenuBar : View {
@@ -850,7 +862,7 @@ namespace Terminal.Gui {
 		internal int selectedSub;
 		internal int selectedSub;
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the array of <see cref="MenuBarItem"/>s for the menu. Only set this when the <see cref="MenuBar"/> is visible.
+		/// Gets or sets the array of <see cref="MenuBarItem"/>s for the menu. Only set this after the <see cref="MenuBar"/> is visible.
 		/// </summary>
 		/// </summary>
 		/// <value>The menu array.</value>
 		/// <value>The menu array.</value>
 		public MenuBarItem [] Menus { get; set; }
 		public MenuBarItem [] Menus { get; set; }
@@ -873,7 +885,7 @@ namespace Terminal.Gui {
 
 
 		static ustring shortcutDelimiter = "+";
 		static ustring shortcutDelimiter = "+";
 		/// <summary>
 		/// <summary>
-		/// Used for change the shortcut delimiter separator.
+		/// Sets or gets the shortcut delimiter separator. The default is "+".
 		/// </summary>
 		/// </summary>
 		public static ustring ShortcutDelimiter {
 		public static ustring ShortcutDelimiter {
 			get => shortcutDelimiter;
 			get => shortcutDelimiter;
@@ -893,6 +905,13 @@ namespace Terminal.Gui {
 
 
 		/// <summary>
 		/// <summary>
 		/// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
 		/// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
+		/// <para>
+		/// By default any sub-sub-menus (sub-menus of the main <see cref="MenuItem"/>s) are displayed in a cascading manner, 
+		/// where each sub-sub-menu pops out of the sub-menu frame
+		/// (either to the right or left, depending on where the sub-menu is relative to the edge of the screen). By setting
+		/// <see cref="UseSubMenusSingleFrame"/> to <see langword="true"/>, this behavior can be changed such that all sub-sub-menus are
+		/// drawn within a single frame below the MenuBar.
+		/// </para>		
 		/// </summary>
 		/// </summary>
 		public bool UseSubMenusSingleFrame {
 		public bool UseSubMenusSingleFrame {
 			get => useSubMenusSingleFrame;
 			get => useSubMenusSingleFrame;
@@ -905,6 +924,11 @@ namespace Terminal.Gui {
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// The <see cref="Gui.Key"/> used to activate the menu bar by keyboard.
+		/// </summary>
+		public Key Key { get; set; } = Key.F9;
+
 		/// <summary>
 		/// <summary>
 		/// Initializes a new instance of the <see cref="MenuBar"/>.
 		/// Initializes a new instance of the <see cref="MenuBar"/>.
 		/// </summary>
 		/// </summary>
@@ -1024,6 +1048,14 @@ namespace Terminal.Gui {
 			isCleaning = false;
 			isCleaning = false;
 		}
 		}
 
 
+		// The column where the MenuBar starts
+		static int xOrigin = 0;
+		// Spaces before the Title
+		static int leftPadding = 1;
+		// Spaces after the Title
+		static int rightPadding = 1;
+		// Spaces after the submenu Title, before Help
+		static int parensAroundHelp = 3;
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void Redraw (Rect bounds)
 		public override void Redraw (Rect bounds)
 		{
 		{
@@ -1033,7 +1065,7 @@ namespace Terminal.Gui {
 				Driver.AddRune (' ');
 				Driver.AddRune (' ');
 
 
 			Move (1, 0);
 			Move (1, 0);
-			int pos = 1;
+			int pos = 0;
 
 
 			for (int i = 0; i < Menus.Length; i++) {
 			for (int i = 0; i < Menus.Length; i++) {
 				var menu = Menus [i];
 				var menu = Menus [i];
@@ -1041,17 +1073,14 @@ namespace Terminal.Gui {
 				Attribute hotColor, normalColor;
 				Attribute hotColor, normalColor;
 				if (i == selected && IsMenuOpen) {
 				if (i == selected && IsMenuOpen) {
 					hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
 					hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
-					normalColor = i == selected ? ColorScheme.Focus :
-						GetNormalColor ();
-				} else if (openedByAltKey) {
+					normalColor = i == selected ? ColorScheme.Focus : GetNormalColor ();
+				} else { 
 					hotColor = ColorScheme.HotNormal;
 					hotColor = ColorScheme.HotNormal;
 					normalColor = GetNormalColor ();
 					normalColor = GetNormalColor ();
-				} else {
-					hotColor = GetNormalColor ();
-					normalColor = GetNormalColor ();
 				}
 				}
-				DrawHotString (menu.Help.IsEmpty ? $" {menu.Title}  " : $" {menu.Title}  {menu.Help}  ", hotColor, normalColor);
-				pos += 1 + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? menu.Help.ConsoleWidth + 2 : 0) + 2;
+				// Note Help on MenuBar is drawn with parens around it
+				DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} ({menu.Help}) ", hotColor, normalColor);
+				pos += leftPadding + menu.TitleLength + (menu.Help.ConsoleWidth > 0 ? leftPadding + menu.Help.ConsoleWidth + parensAroundHelp : 0) + rightPadding;
 			}
 			}
 			PositionCursor ();
 			PositionCursor ();
 		}
 		}
@@ -1066,14 +1095,10 @@ namespace Terminal.Gui {
 			for (int i = 0; i < Menus.Length; i++) {
 			for (int i = 0; i < Menus.Length; i++) {
 				if (i == selected) {
 				if (i == selected) {
 					pos++;
 					pos++;
-					if (IsMenuOpen)
-						Move (pos + 1, 0);
-					else {
-						Move (pos + 1, 0);
-					}
+					Move (pos + 1, 0);
 					return;
 					return;
 				} else {
 				} else {
-					pos += 1 + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2;
+					pos += leftPadding + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + parensAroundHelp : 0) + rightPadding;
 				}
 				}
 			}
 			}
 		}
 		}
@@ -1111,7 +1136,7 @@ namespace Terminal.Gui {
 		public event Action<MenuClosingEventArgs> MenuClosing;
 		public event Action<MenuClosingEventArgs> MenuClosing;
 
 
 		/// <summary>
 		/// <summary>
-		/// Raised when all the menu are closed.
+		/// Raised when all the menu is closed.
 		/// </summary>
 		/// </summary>
 		public event Action MenuAllClosed;
 		public event Action MenuAllClosed;
 
 
@@ -1134,7 +1159,7 @@ namespace Terminal.Gui {
 		internal bool isMenuClosing;
 		internal bool isMenuClosing;
 
 
 		/// <summary>
 		/// <summary>
-		/// True if the menu is open; otherwise false.
+		/// <see langword="true"/> if the menu is open; otherwise <see langword="true"/>.
 		/// </summary>
 		/// </summary>
 		public bool IsMenuOpen { get; protected set; }
 		public bool IsMenuOpen { get; protected set; }
 
 
@@ -1167,7 +1192,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuClosing"/>
+		/// Virtual method that will invoke the <see cref="MenuClosing"/>.
 		/// </summary>
 		/// </summary>
 		/// <param name="currentMenu">The current menu to be closed.</param>
 		/// <param name="currentMenu">The current menu to be closed.</param>
 		/// <param name="reopen">Whether the current menu will be reopen.</param>
 		/// <param name="reopen">Whether the current menu will be reopen.</param>
@@ -1180,7 +1205,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuAllClosed"/>
+		/// Virtual method that will invoke the <see cref="MenuAllClosed"/>.
 		/// </summary>
 		/// </summary>
 		public virtual void OnMenuAllClosed ()
 		public virtual void OnMenuAllClosed ()
 		{
 		{
@@ -1190,7 +1215,7 @@ namespace Terminal.Gui {
 		View lastFocused;
 		View lastFocused;
 
 
 		/// <summary>
 		/// <summary>
-		/// Get the lasted focused view before open the menu.
+		/// Gets the view that was last focused before opening the menu.
 		/// </summary>
 		/// </summary>
 		public View LastFocused { get; private set; }
 		public View LastFocused { get; private set; }
 
 
@@ -1208,6 +1233,7 @@ namespace Terminal.Gui {
 			int pos = 0;
 			int pos = 0;
 			switch (subMenu) {
 			switch (subMenu) {
 			case null:
 			case null:
+				// Open a submenu below a MenuBar
 				lastFocused = lastFocused ?? (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused);
 				lastFocused = lastFocused ?? (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused);
 				if (openSubMenu != null && !CloseMenu (false, true))
 				if (openSubMenu != null && !CloseMenu (false, true))
 					return;
 					return;
@@ -1220,8 +1246,10 @@ namespace Terminal.Gui {
 					openMenu.Dispose ();
 					openMenu.Dispose ();
 				}
 				}
 
 
+				// This positions the submenu horizontally aligned with the first character of the
+				// menu it belongs to's text
 				for (int i = 0; i < index; i++)
 				for (int i = 0; i < index; i++)
-					pos += 1 + Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + 2;
+					pos += Menus [i].TitleLength + (Menus [i].Help.ConsoleWidth > 0 ? Menus [i].Help.ConsoleWidth + 2 : 0) + leftPadding + rightPadding;
 				openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]);
 				openMenu = new Menu (this, Frame.X + pos, Frame.Y + 1, Menus [index]);
 				openCurrentMenu = openMenu;
 				openCurrentMenu = openMenu;
 				openCurrentMenu.previousSubFocused = openMenu;
 				openCurrentMenu.previousSubFocused = openMenu;
@@ -1234,6 +1262,7 @@ namespace Terminal.Gui {
 				openMenu.SetFocus ();
 				openMenu.SetFocus ();
 				break;
 				break;
 			default:
 			default:
+				// Opens a submenu next to another submenu (openSubMenu)
 				if (openSubMenu == null)
 				if (openSubMenu == null)
 					openSubMenu = new List<Menu> ();
 					openSubMenu = new List<Menu> ();
 				if (sIndex > -1) {
 				if (sIndex > -1) {
@@ -1274,7 +1303,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Opens the current Menu programatically.
+		/// Opens the Menu programatically, as though the F9 key were pressed.
 		/// </summary>
 		/// </summary>
 		public void OpenMenu ()
 		public void OpenMenu ()
 		{
 		{
@@ -1356,7 +1385,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// Closes the current Menu programatically, if open and not canceled.
+		/// Closes the Menu programmatically if open and not canceled (as though F9 were pressed).
 		/// </summary>
 		/// </summary>
 		public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false)
 		public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false)
 		{
 		{
@@ -1458,26 +1487,6 @@ namespace Terminal.Gui {
 			if (openSubMenu.Count > 0)
 			if (openSubMenu.Count > 0)
 				openCurrentMenu = openSubMenu.Last ();
 				openCurrentMenu = openSubMenu.Last ();
 
 
-			//if (openMenu.Subviews.Count == 0)
-			//	return;
-			//if (index == 0) {
-			//	//SuperView.SetFocus (previousSubFocused);
-			//	FocusPrev ();
-			//	return;
-			//}
-
-			//for (int i = openMenu.Subviews.Count - 1; i > index; i--) {
-			//	isMenuClosing = true;
-			//	if (openMenu.Subviews.Count - 1 > 0)
-			//		SuperView.SetFocus (openMenu.Subviews [i - 1]);
-			//	else
-			//		SuperView.SetFocus (openMenu);
-			//	if (openMenu != null) {
-			//		Remove (openMenu.Subviews [i]);
-			//		openMenu.Remove (openMenu.Subviews [i]);
-			//	}
-			//	RemoveSubMenu (i);
-			//}
 			isMenuClosing = false;
 			isMenuClosing = false;
 		}
 		}
 
 
@@ -1678,7 +1687,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool ProcessHotKey (KeyEvent kb)
 		public override bool ProcessHotKey (KeyEvent kb)
 		{
 		{
-			if (kb.Key == Key.F9) {
+			if (kb.Key == Key) {
 				if (!IsMenuOpen)
 				if (!IsMenuOpen)
 					OpenMenu ();
 					OpenMenu ();
 				else
 				else
@@ -1773,10 +1782,10 @@ namespace Terminal.Gui {
 			if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.Button1Clicked ||
 			if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked || me.Flags == MouseFlags.Button1Clicked ||
 				(me.Flags == MouseFlags.ReportMousePosition && selected > -1) ||
 				(me.Flags == MouseFlags.ReportMousePosition && selected > -1) ||
 				(me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && selected > -1)) {
 				(me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && selected > -1)) {
-				int pos = 1;
+				int pos = xOrigin;
 				int cx = me.X;
 				int cx = me.X;
 				for (int i = 0; i < Menus.Length; i++) {
 				for (int i = 0; i < Menus.Length; i++) {
-					if (cx >= pos && cx < pos + 1 + Menus [i].TitleLength + Menus [i].Help.ConsoleWidth + 2) {
+					if (cx >= pos && cx < pos + leftPadding + Menus [i].TitleLength + Menus [i].Help.ConsoleWidth + rightPadding) {
 						if (me.Flags == MouseFlags.Button1Clicked) {
 						if (me.Flags == MouseFlags.Button1Clicked) {
 							if (Menus [i].IsTopLevel) {
 							if (Menus [i].IsTopLevel) {
 								var menu = new Menu (this, i, 0, Menus [i]);
 								var menu = new Menu (this, i, 0, Menus [i]);
@@ -1805,7 +1814,7 @@ namespace Terminal.Gui {
 						}
 						}
 						return true;
 						return true;
 					}
 					}
-					pos += 1 + Menus [i].TitleLength + 2;
+					pos += leftPadding + Menus [i].TitleLength + rightPadding;
 				}
 				}
 			}
 			}
 			return false;
 			return false;
@@ -1878,47 +1887,6 @@ namespace Terminal.Gui {
 				handled = false;
 				handled = false;
 				return false;
 				return false;
 			}
 			}
-			//if (me.View != this && me.Flags != MouseFlags.Button1Pressed)
-			//	return true;
-			//else if (me.View != this && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
-			//	Application.UngrabMouse ();
-			//	host.CloseAllMenus ();
-			//	return true;
-			//}
-
-
-			//if (!(me.View is MenuBar) && !(me.View is Menu) && me.Flags != MouseFlags.Button1Pressed))
-			//	return false;
-
-			//if (Application.MouseGrabView != null) {
-			//	if (me.View is MenuBar || me.View is Menu) {
-			//		me.X -= me.OfX;
-			//		me.Y -= me.OfY;
-			//		me.View.MouseEvent (me);
-			//		return true;
-			//	} else if (!(me.View is MenuBar || me.View is Menu) && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
-			//		Application.UngrabMouse ();
-			//		CloseAllMenus ();
-			//	}
-			//} else if (!isMenuClosed && selected == -1 && me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
-			//	Application.GrabMouse (this);
-			//	return true;
-			//}
-
-			//if (Application.MouseGrabView != null) {
-			//	if (Application.MouseGrabView == me.View && me.View == current) {
-			//		me.X -= me.OfX;
-			//		me.Y -= me.OfY;
-			//	} else if (me.View != current && me.View is MenuBar && me.View is Menu) {
-			//		Application.UngrabMouse ();
-			//		Application.GrabMouse (me.View);
-			//	} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
-			//		Application.UngrabMouse ();
-			//		CloseMenu ();
-			//	}
-			//} else if ((!isMenuClosed && selected > -1)) {
-			//	Application.GrabMouse (current);
-			//}
 
 
 			handled = true;
 			handled = true;
 
 
@@ -1972,12 +1940,13 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public MenuBarItem NewMenuBarItem { get; set; }
 		public MenuBarItem NewMenuBarItem { get; set; }
 		/// <summary>
 		/// <summary>
-		/// Flag that allows you to cancel the opening of the menu.
+		/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
+		/// event handler, the event will be canceled. 
 		/// </summary>
 		/// </summary>
 		public bool Cancel { get; set; }
 		public bool Cancel { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Initializes a new instance of <see cref="MenuOpeningEventArgs"/>
+		/// Initializes a new instance of <see cref="MenuOpeningEventArgs"/>.
 		/// </summary>
 		/// </summary>
 		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
 		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
 		public MenuOpeningEventArgs (MenuBarItem currentMenu)
 		public MenuOpeningEventArgs (MenuBarItem currentMenu)
@@ -1996,7 +1965,7 @@ namespace Terminal.Gui {
 		public MenuBarItem CurrentMenu { get; }
 		public MenuBarItem CurrentMenu { get; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Indicates whether the current menu will be reopen.
+		/// Indicates whether the current menu will reopen.
 		/// </summary>
 		/// </summary>
 		public bool Reopen { get; }
 		public bool Reopen { get; }
 
 
@@ -2006,15 +1975,16 @@ namespace Terminal.Gui {
 		public bool IsSubMenu { get; }
 		public bool IsSubMenu { get; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Flag that allows you to cancel the opening of the menu.
+		/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
+		/// event handler, the event will be canceled. 
 		/// </summary>
 		/// </summary>
 		public bool Cancel { get; set; }
 		public bool Cancel { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Initializes a new instance of <see cref="MenuClosingEventArgs"/>
+		/// Initializes a new instance of <see cref="MenuClosingEventArgs"/>.
 		/// </summary>
 		/// </summary>
 		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
 		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
-		/// <param name="reopen">Whether the current menu will be reopen.</param>
+		/// <param name="reopen">Whether the current menu will reopen.</param>
 		/// <param name="isSubMenu">Indicates whether it is a sub-menu.</param>
 		/// <param name="isSubMenu">Indicates whether it is a sub-menu.</param>
 		public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
 		public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
 		{
 		{

+ 20 - 2
Terminal.Gui/Views/ScrollView.cs

@@ -29,7 +29,13 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </para>
 	/// </remarks>
 	/// </remarks>
 	public class ScrollView : View {
 	public class ScrollView : View {
-		View contentView = null;
+		private class ContentView : View {
+			public ContentView (Rect frame) : base (frame)
+			{
+			}
+		}
+
+		ContentView contentView;
 		ScrollBarView vertical, horizontal;
 		ScrollBarView vertical, horizontal;
 
 
 		/// <summary>
 		/// <summary>
@@ -52,7 +58,7 @@ namespace Terminal.Gui {
 
 
 		void Initialize (Rect frame)
 		void Initialize (Rect frame)
 		{
 		{
-			contentView = new View (frame);
+			contentView = new ContentView (frame);
 			vertical = new ScrollBarView (1, 0, isVertical: true) {
 			vertical = new ScrollBarView (1, 0, isVertical: true) {
 				X = Pos.AnchorEnd (1),
 				X = Pos.AnchorEnd (1),
 				Y = 0,
 				Y = 0,
@@ -177,6 +183,12 @@ namespace Terminal.Gui {
 			set {
 			set {
 				if (autoHideScrollBars != value) {
 				if (autoHideScrollBars != value) {
 					autoHideScrollBars = value;
 					autoHideScrollBars = value;
+					if (Subviews.Contains (vertical)) {
+						vertical.AutoHideScrollBars = value;
+					}
+					if (Subviews.Contains (horizontal)) {
+						horizontal.AutoHideScrollBars = value;
+					}
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
 				}
 				}
 			}
 			}
@@ -251,6 +263,8 @@ namespace Terminal.Gui {
 				SetNeedsLayout ();
 				SetNeedsLayout ();
 				if (value) {
 				if (value) {
 					base.Add (horizontal);
 					base.Add (horizontal);
+					horizontal.ShowScrollIndicator = value;
+					horizontal.AutoHideScrollBars = autoHideScrollBars;
 					horizontal.OtherScrollBarView = vertical;
 					horizontal.OtherScrollBarView = vertical;
 					horizontal.OtherScrollBarView.ShowScrollIndicator = value;
 					horizontal.OtherScrollBarView.ShowScrollIndicator = value;
 					horizontal.MouseEnter += View_MouseEnter;
 					horizontal.MouseEnter += View_MouseEnter;
@@ -290,6 +304,8 @@ namespace Terminal.Gui {
 				SetNeedsLayout ();
 				SetNeedsLayout ();
 				if (value) {
 				if (value) {
 					base.Add (vertical);
 					base.Add (vertical);
+					vertical.ShowScrollIndicator = value;
+					vertical.AutoHideScrollBars = autoHideScrollBars;
 					vertical.OtherScrollBarView = horizontal;
 					vertical.OtherScrollBarView = horizontal;
 					vertical.OtherScrollBarView.ShowScrollIndicator = value;
 					vertical.OtherScrollBarView.ShowScrollIndicator = value;
 					vertical.MouseEnter += View_MouseEnter;
 					vertical.MouseEnter += View_MouseEnter;
@@ -322,10 +338,12 @@ namespace Terminal.Gui {
 				ShowHideScrollBars ();
 				ShowHideScrollBars ();
 			} else {
 			} else {
 				if (ShowVerticalScrollIndicator) {
 				if (ShowVerticalScrollIndicator) {
+					vertical.SetRelativeLayout (Bounds);
 					vertical.Redraw (vertical.Bounds);
 					vertical.Redraw (vertical.Bounds);
 				}
 				}
 
 
 				if (ShowHorizontalScrollIndicator) {
 				if (ShowHorizontalScrollIndicator) {
+					horizontal.SetRelativeLayout (Bounds);
 					horizontal.Redraw (horizontal.Bounds);
 					horizontal.Redraw (horizontal.Bounds);
 				}
 				}
 			}
 			}

+ 10 - 6
Terminal.Gui/Views/TreeView.cs

@@ -219,19 +219,23 @@ namespace Terminal.Gui {
 		/// <value></value>
 		/// <value></value>
 		public AspectGetterDelegate<T> AspectGetter { get; set; } = (o) => o.ToString () ?? "";
 		public AspectGetterDelegate<T> AspectGetter { get; set; } = (o) => o.ToString () ?? "";
 
 
-		CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
+		CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
 
 
 		/// <summary>
 		/// <summary>
-		/// Get / Set the wished cursor when the tree is focused
+		/// Get / Set the wished cursor when the tree is focused.
+		/// Only applies when <see cref="MultiSelect"/> is true.
+		/// Defaults to <see cref="CursorVisibility.Invisible"/>
 		/// </summary>
 		/// </summary>
 		public CursorVisibility DesiredCursorVisibility {
 		public CursorVisibility DesiredCursorVisibility {
-			get => desiredCursorVisibility;
+			get { 
+				return MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
+			}
 			set {
 			set {
+				desiredCursorVisibility = value;
+
 				if (desiredCursorVisibility != value && HasFocus) {
 				if (desiredCursorVisibility != value && HasFocus) {
-					Application.Driver.SetCursorVisibility (value);
+					Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
 				}
 				}
-
-				desiredCursorVisibility = value;
 			}
 			}
 		}
 		}
 
 

+ 4 - 0
UICatalog/Properties/launchSettings.json

@@ -27,6 +27,10 @@
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "\"ProgressBar Styles\""
       "commandLineArgs": "\"ProgressBar Styles\""
     },
     },
+    "VkeyPacketSimulator": {
+      "commandName": "Project",
+      "commandLineArgs": "VkeyPacketSimulator"
+    },
     "WSL2": {
     "WSL2": {
       "commandName": "Executable",
       "commandName": "Executable",
       "executablePath": "wsl",
       "executablePath": "wsl",

+ 17 - 11
UICatalog/Scenario.cs

@@ -73,14 +73,11 @@ namespace UICatalog {
 		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> before creating any views or calling other Terminal.Gui APIs.
 		/// Overrides that do not call the base.<see cref="Run"/>, must call <see cref="Application.Init"/> before creating any views or calling other Terminal.Gui APIs.
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
-		public virtual void Init(Toplevel top, ColorScheme colorScheme)
+		public virtual void Init (Toplevel top, ColorScheme colorScheme)
 		{
 		{
 			Application.Init ();
 			Application.Init ();
 
 
-			Top = top;
-			if (Top == null) {
-				Top = Application.Top;
-			}
+			Top = top != null ? top : Application.Top;
 
 
 			Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 			Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 				X = 0,
 				X = 0,
@@ -177,7 +174,14 @@ namespace UICatalog {
 		/// <returns>list of category names</returns>
 		/// <returns>list of category names</returns>
 		public List<string> GetCategories () => ScenarioCategory.GetCategories (this.GetType ());
 		public List<string> GetCategories () => ScenarioCategory.GetCategories (this.GetType ());
 
 
-		public override string ToString () => $"{GetName (),-30}{GetDescription ()}";
+		private static int _maxScenarioNameLen = 30;
+
+		/// <summary>
+		/// Gets the Scenario Name + Description with the Description padded
+		/// based on the longest known Scenario name.
+		/// </summary>
+		/// <returns></returns>
+		public override string ToString () => $"{GetName ().PadRight(_maxScenarioNameLen)}{GetDescription ()}";
 
 
 		/// <summary>
 		/// <summary>
 		/// Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...). 
 		/// Override this to implement the <see cref="Scenario"/> setup logic (create controls, etc...). 
@@ -232,12 +236,14 @@ namespace UICatalog {
 		/// Returns an instance of each <see cref="Scenario"/> defined in the project. 
 		/// Returns an instance of each <see cref="Scenario"/> defined in the project. 
 		/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
 		/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
 		/// </summary>
 		/// </summary>
-		public static List<Type> GetDerivedClasses<T> ()
+		public static List<Scenario> GetScenarios ()
 		{
 		{
-			List<Type> objects = new List<Type> ();
-			foreach (Type type in typeof (T).Assembly.GetTypes ()
-			 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (T)))) {
-				objects.Add (type);
+			List<Scenario> objects = new List<Scenario> ();
+			foreach (Type type in typeof (Scenario).Assembly.ExportedTypes
+			 .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+				var scenario = (Scenario)Activator.CreateInstance (type);
+				objects.Add (scenario);
+				_maxScenarioNameLen = Math.Max (_maxScenarioNameLen, scenario.GetName ().Length + 1);
 			}
 			}
 			return objects;
 			return objects;
 		}
 		}

+ 1 - 4
UICatalog/Scenarios/AllViewsTester.cs

@@ -44,10 +44,7 @@ namespace UICatalog.Scenarios {
 		{
 		{
 			Application.Init ();
 			Application.Init ();
 
 
-			Top = top;
-			if (Top == null) {
-				Top = Application.Top;
-			}
+			Top = top != null ? top : Application.Top;
 
 
 			//Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 			//Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 			//	X = 0,
 			//	X = 0,

+ 0 - 1
UICatalog/Scenarios/BordersComparisons.cs

@@ -7,7 +7,6 @@ namespace UICatalog.Scenarios {
 	public class BordersComparisons : Scenario {
 	public class BordersComparisons : Scenario {
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
 		{
-			top.Dispose ();
 			Application.Init ();
 			Application.Init ();
 
 
 			top = Application.Top;
 			top = Application.Top;

+ 1 - 11
UICatalog/Scenarios/Clipping.cs

@@ -11,19 +11,9 @@ namespace UICatalog.Scenarios {
 		{
 		{
 			Application.Init ();
 			Application.Init ();
 
 
-			Top = top;
-			if (Top == null) {
-				Top = Application.Top;
-			}
+			Top = top != null ? top : Application.Top;
 
 
 			Top.ColorScheme = Colors.Base;
 			Top.ColorScheme = Colors.Base;
-			//Win = new TopLevel($"CTRL-Q to Close - Scenario: {GetName ()}") {
-			//	X = 0,
-			//	Y = 0,
-			//	Width = Dim.Fill (),
-			//	Height = Dim.Fill ()
-			//};
-			//Top.Add (Win);
 		}
 		}
 
 
 		public override void Setup ()
 		public override void Setup ()

+ 6 - 4
UICatalog/Scenarios/CsvEditor.cs

@@ -43,12 +43,14 @@ namespace UICatalog.Scenarios {
 				Height = Dim.Fill (1),
 				Height = Dim.Fill (1),
 			};
 			};
 
 
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
+			var fileMenu = new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_Open CSV", "", () => Open()),
 					new MenuItem ("_Open CSV", "", () => Open()),
 					new MenuItem ("_Save", "", () => Save()),
 					new MenuItem ("_Save", "", () => Save()),
-					new MenuItem ("_Quit", "", () => Quit()),
-				}),
+					new MenuItem ("_Quit", "Quits The App", () => Quit()),
+				});
+			//fileMenu.Help = "Help";
+			var menu = new MenuBar (new MenuBarItem [] {
+				fileMenu,
 				new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuBarItem ("_Edit", new MenuItem [] {
 					new MenuItem ("_New Column", "", () => AddColumn()),
 					new MenuItem ("_New Column", "", () => AddColumn()),
 					new MenuItem ("_New Row", "", () => AddRow()),
 					new MenuItem ("_New Row", "", () => AddRow()),

+ 2 - 4
UICatalog/Scenarios/Editor.cs

@@ -35,10 +35,7 @@ namespace UICatalog.Scenarios {
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
 		{
 			Application.Init ();
 			Application.Init ();
-			Top = top;
-			if (Top == null) {
-				Top = Application.Top;
-			}
+			Top = top != null ? top : Application.Top;
 
 
 			Win = new Window (_fileName ?? "Untitled") {
 			Win = new Window (_fileName ?? "Untitled") {
 				X = 0,
 				X = 0,
@@ -116,6 +113,7 @@ namespace UICatalog.Scenarios {
 					new MenuBarItem ("_Languages", GetSupportedCultures ())
 					new MenuBarItem ("_Languages", GetSupportedCultures ())
 				})
 				})
 			});
 			});
+
 			Top.Add (menu);
 			Top.Add (menu);
 
 
 			var statusBar = new StatusBar (new StatusItem [] {
 			var statusBar = new StatusBar (new StatusItem [] {

+ 1 - 1
UICatalog/Scenarios/Keys.cs

@@ -51,7 +51,7 @@ namespace UICatalog.Scenarios {
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
 		{
 			Application.Init ();
 			Application.Init ();
-			Top = top;
+			Top = top != null ? top : Application.Top;
 
 
 			Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 			Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") {
 				X = 0,
 				X = 0,

+ 12 - 9
UICatalog/Scenarios/ListViewWithSelection.cs

@@ -3,6 +3,7 @@ using System;
 using System.Collections;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
+using System.Text.Json.Nodes;
 using Terminal.Gui;
 using Terminal.Gui;
 using Attribute = Terminal.Gui.Attribute;
 using Attribute = Terminal.Gui.Attribute;
 
 
@@ -16,11 +17,13 @@ namespace UICatalog.Scenarios {
 		public CheckBox _allowMultipleCB;
 		public CheckBox _allowMultipleCB;
 		public ListView _listView;
 		public ListView _listView;
 
 
-		public List<Type> _scenarios = Scenario.GetDerivedClasses<Scenario>().OrderBy (t => Scenario.ScenarioMetadata.GetName (t)).ToList ();
+		public List<Scenario> _scenarios;
 
 
 		public override void Setup ()
 		public override void Setup ()
 		{
 		{
-			_customRenderCB = new CheckBox ("Render with columns") {
+			_scenarios = Scenario.GetScenarios ().OrderBy (s => s.GetName ()).ToList ();
+
+			_customRenderCB = new CheckBox ("Use custom rendering") {
 				X = 0,
 				X = 0,
 				Y = 0,
 				Y = 0,
 				Height = 1,
 				Height = 1,
@@ -137,11 +140,11 @@ namespace UICatalog.Scenarios {
 		// This is basically the same implementation used by the UICatalog main window
 		// This is basically the same implementation used by the UICatalog main window
 		internal class ScenarioListDataSource : IListDataSource {
 		internal class ScenarioListDataSource : IListDataSource {
 			int _nameColumnWidth = 30;
 			int _nameColumnWidth = 30;
-			private List<Type> scenarios;
+			private List<Scenario> scenarios;
 			BitArray marks;
 			BitArray marks;
 			int count, len;
 			int count, len;
 
 
-			public List<Type> Scenarios {
+			public List<Scenario> Scenarios {
 				get => scenarios;
 				get => scenarios;
 				set {
 				set {
 					if (value != null) {
 					if (value != null) {
@@ -163,14 +166,14 @@ namespace UICatalog.Scenarios {
 
 
 			public int Length => len;
 			public int Length => len;
 
 
-			public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
+			public ScenarioListDataSource (List<Scenario> itemList) => Scenarios = itemList;
 
 
 			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
 			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
 			{
 			{
 				container.Move (col, line);
 				container.Move (col, line);
 				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
 				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
-				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
-				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start);
+				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenarios [item].GetName ());
+				RenderUstr (driver, $"{s} ({Scenarios [item].GetDescription ()})", col, line, width, start);
 			}
 			}
 
 
 			public void SetMark (int item, bool value)
 			public void SetMark (int item, bool value)
@@ -187,8 +190,8 @@ namespace UICatalog.Scenarios {
 
 
 				int maxLength = 0;
 				int maxLength = 0;
 				for (int i = 0; i < scenarios.Count; i++) {
 				for (int i = 0; i < scenarios.Count; i++) {
-					var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i]));
-					var sc = $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}";
+					var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenarios [i].GetName ());
+					var sc = $"{s}  {Scenarios [i].GetDescription ()}";
 					var l = sc.Length;
 					var l = sc.Length;
 					if (l > maxLength) {
 					if (l > maxLength) {
 						maxLength = l;
 						maxLength = l;

+ 1 - 5
UICatalog/Scenarios/Notepad.cs

@@ -14,11 +14,7 @@ namespace UICatalog.Scenarios {
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
 		{
 			Application.Init ();
 			Application.Init ();
-
-			Top = top;
-			if (Top == null) {
-				Top = Application.Top;
-			}
+			Top = top != null ? top : Application.Top;
 			Top.ColorScheme = Colors.Base;
 			Top.ColorScheme = Colors.Base;
 		}
 		}
 
 

+ 16 - 0
UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -25,6 +25,8 @@ namespace UICatalog.Scenarios {
 		private MenuItem miFullPaths;
 		private MenuItem miFullPaths;
 		private MenuItem miLeaveLastRow;
 		private MenuItem miLeaveLastRow;
 		private MenuItem miCustomColors;
 		private MenuItem miCustomColors;
+		private MenuItem miCursor;
+		private MenuItem miMultiSelect;
 		private Terminal.Gui.Attribute green;
 		private Terminal.Gui.Attribute green;
 		private Terminal.Gui.Attribute red;
 		private Terminal.Gui.Attribute red;
 
 
@@ -54,6 +56,8 @@ namespace UICatalog.Scenarios {
 					miFullPaths = new MenuItem ("_FullPaths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 					miFullPaths = new MenuItem ("_FullPaths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 					miLeaveLastRow = new MenuItem ("_LeaveLastRow", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
 					miLeaveLastRow = new MenuItem ("_LeaveLastRow", "", () => SetLeaveLastRow()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
 					miCustomColors = new MenuItem ("C_ustomColors", "", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 					miCustomColors = new MenuItem ("C_ustomColors", "", () => SetCustomColors()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
+					miCursor = new MenuItem ("Curs_or (MultiSelect only)", "", () => SetCursor()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
+					miMultiSelect = new MenuItem ("_MultiSelect", "", () => SetMultiSelect()){Checked = true, CheckType = MenuItemCheckStyle.Checked},
 				}),
 				}),
 			});
 			});
 			Top.Add (menu);
 			Top.Add (menu);
@@ -269,6 +273,18 @@ namespace UICatalog.Scenarios {
 			miLeaveLastRow.Checked = !miLeaveLastRow.Checked;
 			miLeaveLastRow.Checked = !miLeaveLastRow.Checked;
 			treeViewFiles.Style.LeaveLastRow = miLeaveLastRow.Checked;
 			treeViewFiles.Style.LeaveLastRow = miLeaveLastRow.Checked;
 		}
 		}
+		private void SetCursor()
+		{
+			miCursor.Checked = !miCursor.Checked;
+			treeViewFiles.DesiredCursorVisibility = miCursor.Checked ? CursorVisibility.Default : CursorVisibility.Invisible;
+		}
+		private void SetMultiSelect()
+		{
+			miMultiSelect.Checked = !miMultiSelect.Checked;
+			treeViewFiles.MultiSelect = miMultiSelect.Checked;
+		}
+		
+
 		private void SetCustomColors()
 		private void SetCustomColors()
 		{
 		{
 			var yellow = new ColorScheme
 			var yellow = new ColorScheme

+ 251 - 0
UICatalog/Scenarios/VkeyPacketSimulator.cs

@@ -0,0 +1,251 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "VkeyPacketSimulator", Description: "Simulates the Virtual Key Packet")]
+	[ScenarioCategory ("Keys")]
+	public class VkeyPacketSimulator : Scenario {
+		List<int> _keyboardStrokes = new List<int> ();
+		bool _outputStarted = false;
+		bool _wasUnknown = false;
+		static ManualResetEventSlim _stopOutput = new ManualResetEventSlim (false);
+
+		public override void Setup ()
+		{
+			var label = new Label ("Input") {
+				X = Pos.Center ()
+			};
+			Win.Add (label);
+
+			var btnInput = new Button ("Select Input") {
+				X = Pos.AnchorEnd (16),
+			};
+			Win.Add (btnInput);
+
+			const string ruler = "|123456789";
+
+			var inputHorizontalRuler = new Label ("") {
+				Y = Pos.Bottom (btnInput),
+				Width = Dim.Fill (),
+				ColorScheme = Colors.Error,
+				AutoSize = false
+			};
+			Win.Add (inputHorizontalRuler);
+
+			var inputVerticalRuler = new Label ("", TextDirection.TopBottom_LeftRight) {
+				Y = Pos.Bottom (btnInput),
+				Width = 1,
+				ColorScheme = Colors.Error,
+				AutoSize = false
+			};
+			Win.Add (inputVerticalRuler);
+
+			var tvInput = new TextView {
+				X = 1,
+				Y = Pos.Bottom (inputHorizontalRuler),
+				Width = Dim.Fill (),
+				Height = Dim.Percent (50) - 1
+			};
+			Win.Add (tvInput);
+
+			label = new Label ("Output") {
+				X = Pos.Center (),
+				Y = Pos.Bottom (tvInput)
+			};
+			Win.Add (label);
+
+			var btnOutput = new Button ("Select Output") {
+				X = Pos.AnchorEnd (17),
+				Y = Pos.Top (label)
+			};
+			Win.Add (btnOutput);
+
+			var outputHorizontalRuler = new Label ("") {
+				Y = Pos.Bottom (btnOutput),
+				Width = Dim.Fill (),
+				ColorScheme = Colors.Error,
+				AutoSize = false
+			};
+			Win.Add (outputHorizontalRuler);
+
+			var outputVerticalRuler = new Label ("", TextDirection.TopBottom_LeftRight) {
+				Y = Pos.Bottom (btnOutput),
+				Width = 1,
+				Height = Dim.Fill (),
+				ColorScheme = Colors.Error,
+				AutoSize = false
+			};
+			Win.Add (outputVerticalRuler);
+
+			var tvOutput = new TextView {
+				X = 1,
+				Y = Pos.Bottom (outputHorizontalRuler),
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				ReadOnly = true
+			};
+
+			tvOutput.KeyDown += (e) => {
+				//System.Diagnostics.Debug.WriteLine ($"Output - KeyDown: {e.KeyEvent.Key}");
+				e.Handled = true;
+				if (e.KeyEvent.Key == Key.Unknown) {
+					_wasUnknown = true;
+				}
+			};
+
+			tvOutput.KeyPress += (e) => {
+				//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
+				if (_outputStarted && _keyboardStrokes.Count > 0) {
+					var ev = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+					//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress: {ev}");
+					if (!tvOutput.ProcessKey (e.KeyEvent)) {
+						Application.MainLoop.Invoke (() => {
+							MessageBox.Query ("Keys", $"'{ShortcutHelper.GetShortcutTag (ev)}' pressed!", "Ok");
+						});
+					}
+					e.Handled = true;
+					_stopOutput.Set ();
+				}
+				//System.Diagnostics.Debug.WriteLine ($"Output - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
+			};
+
+			Win.Add (tvOutput);
+
+			tvInput.KeyDown += (e) => {
+				//System.Diagnostics.Debug.WriteLine ($"Input - KeyDown: {e.KeyEvent.Key}");
+				e.Handled = true;
+				if (e.KeyEvent.Key == Key.Unknown) {
+					_wasUnknown = true;
+				}
+			};
+
+			View.KeyEventEventArgs unknownChar = null;
+
+			tvInput.KeyPress += (e) => {
+				if (e.KeyEvent.Key == (Key.Q | Key.CtrlMask)) {
+					Application.RequestStop ();
+					return;
+				}
+				if (e.KeyEvent.Key == Key.Unknown) {
+					_wasUnknown = true;
+					e.Handled = true;
+					return;
+				}
+				if (_wasUnknown && _keyboardStrokes.Count == 1) {
+					_wasUnknown = false;
+				} else if (_wasUnknown && char.IsLetter ((char)e.KeyEvent.Key)) {
+					_wasUnknown = false;
+				} else if (!_wasUnknown && _keyboardStrokes.Count > 0) {
+					e.Handled = true;
+					return;
+				}
+				if (_keyboardStrokes.Count == 0) {
+					AddKeyboardStrokes (e);
+				} else {
+					_keyboardStrokes.Insert (0, 0);
+				}
+				var ev = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+				//System.Diagnostics.Debug.WriteLine ($"Input - KeyPress: {ev}");
+				//System.Diagnostics.Debug.WriteLine ($"Input - KeyPress - _keyboardStrokes: {_keyboardStrokes.Count}");
+			};
+
+			tvInput.KeyUp += (e) => {
+				//System.Diagnostics.Debug.WriteLine ($"Input - KeyUp: {e.KeyEvent.Key}");
+				//var ke = e.KeyEvent;
+				var ke = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+				if (_wasUnknown && (int)ke - (int)(ke & (Key.AltMask | Key.CtrlMask | Key.ShiftMask)) != 0) {
+					unknownChar = e;
+				}
+				e.Handled = true;
+				if (!_wasUnknown && _keyboardStrokes.Count > 0) {
+					_outputStarted = true;
+					tvOutput.ReadOnly = false;
+					tvOutput.SetFocus ();
+					tvOutput.SetNeedsDisplay ();
+
+					Task.Run (() => {
+						while (_outputStarted) {
+							try {
+								ConsoleModifiers mod = new ConsoleModifiers ();
+								if (ke.HasFlag (Key.ShiftMask)) {
+									mod |= ConsoleModifiers.Shift;
+								}
+								if (ke.HasFlag (Key.AltMask)) {
+									mod |= ConsoleModifiers.Alt;
+								}
+								if (ke.HasFlag (Key.CtrlMask)) {
+									mod |= ConsoleModifiers.Control;
+								}
+								for (int i = 0; i < _keyboardStrokes.Count; i++) {
+									var consoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey ((uint)_keyboardStrokes [i], mod, out _, out _);
+									Application.Driver.SendKeys ((char)consoleKey, ConsoleKey.Packet, mod.HasFlag (ConsoleModifiers.Shift),
+										mod.HasFlag (ConsoleModifiers.Alt), mod.HasFlag (ConsoleModifiers.Control));
+								}
+								//}
+							} catch (Exception) {
+								Application.MainLoop.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.MainLoop.Invoke (() => {
+									tvOutput.ReadOnly = true;
+									tvInput.SetFocus ();
+								});
+							}
+						}
+						//System.Diagnostics.Debug.WriteLine ($"_outputStarted: {_outputStarted}");
+					});
+				}
+			};
+
+			btnInput.Clicked += () => {
+				if (!tvInput.HasFocus && _keyboardStrokes.Count == 0) {
+					tvInput.SetFocus ();
+				}
+			};
+
+			btnOutput.Clicked += () => {
+				if (!tvOutput.HasFocus && _keyboardStrokes.Count == 0) {
+					tvOutput.SetFocus ();
+				}
+			};
+
+			tvInput.SetFocus ();
+
+			Win.LayoutComplete += (_) => {
+				inputHorizontalRuler.Text = outputHorizontalRuler.Text = ruler.Repeat ((int)Math.Ceiling ((double)(inputHorizontalRuler.Bounds.Width) / (double)ruler.Length)) [0..(inputHorizontalRuler.Bounds.Width)];
+				inputVerticalRuler.Height = tvInput.Frame.Height + 1;
+				inputVerticalRuler.Text = ruler.Repeat ((int)Math.Ceiling ((double)(inputVerticalRuler.Bounds.Height) / (double)ruler.Length)) [0..(inputVerticalRuler.Bounds.Height)];
+				outputVerticalRuler.Text = ruler.Repeat ((int)Math.Ceiling ((double)(outputVerticalRuler.Bounds.Height) / (double)ruler.Length)) [0..(outputVerticalRuler.Bounds.Height)];
+			};
+		}
+
+		private void AddKeyboardStrokes (View.KeyEventEventArgs e)
+		{
+			var ke = e.KeyEvent;
+			var km = new KeyModifiers ();
+			if (ke.IsShift) {
+				km.Shift = true;
+			}
+			if (ke.IsAlt) {
+				km.Alt = true;
+			}
+			if (ke.IsCtrl) {
+				km.Ctrl = true;
+			}
+			var keyChar = ke.KeyValue;
+			var mK = (int)((Key)ke.KeyValue & (Key.AltMask | Key.CtrlMask | Key.ShiftMask));
+			keyChar &= ~mK;
+			_keyboardStrokes.Add (keyChar);
+		}
+	}
+}

+ 1 - 4
UICatalog/Scenarios/WindowsAndFrameViews.cs

@@ -10,10 +10,7 @@ namespace UICatalog.Scenarios {
 		{
 		{
 			Application.Init ();
 			Application.Init ();
 
 
-			Top = top;
-			if (Top == null) {
-				Top = Application.Top;
-			}
+			Top = top != null ? top : Application.Top;
 		}
 		}
 
 
 		public override void RequestStop ()
 		public override void RequestStop ()

+ 116 - 249
UICatalog/UICatalog.cs

@@ -1,6 +1,5 @@
 using NStack;
 using NStack;
 using System;
 using System;
-using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Globalization;
 using System.Globalization;
@@ -46,56 +45,75 @@ namespace UICatalog {
 	/// UI Catalog is a comprehensive sample app and scenario library for <see cref="Terminal.Gui"/>
 	/// UI Catalog is a comprehensive sample app and scenario library for <see cref="Terminal.Gui"/>
 	/// </summary>
 	/// </summary>
 	public class UICatalogApp {
 	public class UICatalogApp {
-		private static Toplevel _top;
-		private static MenuBar _menu;
 		private static int _nameColumnWidth;
 		private static int _nameColumnWidth;
 		private static FrameView _leftPane;
 		private static FrameView _leftPane;
 		private static List<string> _categories;
 		private static List<string> _categories;
 		private static ListView _categoryListView;
 		private static ListView _categoryListView;
 		private static FrameView _rightPane;
 		private static FrameView _rightPane;
-		private static List<Type> _scenarios;
+		private static List<Scenario> _scenarios;
 		private static ListView _scenarioListView;
 		private static ListView _scenarioListView;
 		private static StatusBar _statusBar;
 		private static StatusBar _statusBar;
 		private static StatusItem _capslock;
 		private static StatusItem _capslock;
 		private static StatusItem _numlock;
 		private static StatusItem _numlock;
 		private static StatusItem _scrolllock;
 		private static StatusItem _scrolllock;
-		private static int _categoryListViewItem;
-		private static int _scenarioListViewItem;
 
 
-		private static Scenario _runningScenario = null;
+		// If set, holds the scenario the user selected
+		private static Scenario _selectedScenario = null;
+		
 		private static bool _useSystemConsole = false;
 		private static bool _useSystemConsole = false;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 		private static ConsoleDriver.DiagnosticFlags _diagnosticFlags;
 		private static bool _heightAsBuffer = false;
 		private static bool _heightAsBuffer = false;
 		private static bool _isFirstRunning = true;
 		private static bool _isFirstRunning = true;
 
 
+		// When a scenario is run, the main app is killed. These items
+		// are therefore cached so that when the scenario exits the
+		// main app UI can be restored to previous state
+		private static int _cachedScenarioIndex = 0;
+		private static int _cachedCategoryIndex = 0;
+
+		private static StringBuilder _aboutMessage;
+
 		static void Main (string [] args)
 		static void Main (string [] args)
 		{
 		{
 			Console.OutputEncoding = Encoding.Default;
 			Console.OutputEncoding = Encoding.Default;
 
 
-			if (Debugger.IsAttached)
+			if (Debugger.IsAttached) {
 				CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
 				CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+			}
 
 
-			_scenarios = Scenario.GetDerivedClasses<Scenario> ().OrderBy (t => Scenario.ScenarioMetadata.GetName (t)).ToList ();
+			_scenarios = Scenario.GetScenarios ();
 
 
 			if (args.Length > 0 && args.Contains ("-usc")) {
 			if (args.Length > 0 && args.Contains ("-usc")) {
 				_useSystemConsole = true;
 				_useSystemConsole = true;
 				args = args.Where (val => val != "-usc").ToArray ();
 				args = args.Where (val => val != "-usc").ToArray ();
 			}
 			}
 			if (args.Length > 0) {
 			if (args.Length > 0) {
-				var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase));
-				_runningScenario = (Scenario)Activator.CreateInstance (_scenarios [item]);
+				var item = _scenarios.FindIndex (s => s.GetName ().Equals (args [0], StringComparison.OrdinalIgnoreCase));
+				_selectedScenario = (Scenario)Activator.CreateInstance (_scenarios [item].GetType ());
 				Application.UseSystemConsole = _useSystemConsole;
 				Application.UseSystemConsole = _useSystemConsole;
 				Application.Init ();
 				Application.Init ();
-				_runningScenario.Init (Application.Top, _baseColorScheme);
-				_runningScenario.Setup ();
-				_runningScenario.Run ();
-				_runningScenario = null;
+				_selectedScenario.Init (Application.Top, _colorScheme);
+				_selectedScenario.Setup ();
+				_selectedScenario.Run ();
+				_selectedScenario = null;
 				Application.Shutdown ();
 				Application.Shutdown ();
 				return;
 				return;
 			}
 			}
 
 
+			_aboutMessage = new StringBuilder ();
+			_aboutMessage.AppendLine (@"A comprehensive sample library for");
+			_aboutMessage.AppendLine (@"");
+			_aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
+			_aboutMessage.AppendLine (@" |__   __|                (_)           | | / ____|     (_) ");
+			_aboutMessage.AppendLine (@"    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ");
+			_aboutMessage.AppendLine (@"    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
+			_aboutMessage.AppendLine (@"    | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | ");
+			_aboutMessage.AppendLine (@"    |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
+			_aboutMessage.AppendLine (@"");
+			_aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
+
 			Scenario scenario;
 			Scenario scenario;
-			while ((scenario = GetScenarioToRun ()) != null) {
+			while ((scenario = SelectScenario ()) != null) {
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 				// Validate there are no outstanding Responder-based instances 
 				// Validate there are no outstanding Responder-based instances 
 				// after a scenario was selected to run. This proves the main UI Catalog
 				// after a scenario was selected to run. This proves the main UI Catalog
@@ -106,18 +124,12 @@ namespace UICatalog {
 				Responder.Instances.Clear ();
 				Responder.Instances.Clear ();
 #endif
 #endif
 
 
-				scenario.Init (Application.Top, _baseColorScheme);
+				scenario.Init (Application.Top, _colorScheme);
 				scenario.Setup ();
 				scenario.Setup ();
 				scenario.Run ();
 				scenario.Run ();
 
 
-				//static void LoadedHandler ()
-				//{
-				//	_rightPane.SetFocus ();
-				//	_top.Loaded -= LoadedHandler;
-				//}
-
-				//_top.Loaded += LoadedHandler;
-
+				// This call to Application.Shutdown brackets the Application.Init call
+				// made by Scenario.Init()
 				Application.Shutdown ();
 				Application.Shutdown ();
 
 
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
@@ -130,8 +142,6 @@ namespace UICatalog {
 #endif
 #endif
 			}
 			}
 
 
-			Application.Shutdown ();
-
 #if DEBUG_IDISPOSABLE
 #if DEBUG_IDISPOSABLE
 			// This proves that when the user exited the UI Catalog app
 			// This proves that when the user exited the UI Catalog app
 			// it cleaned up properly.
 			// it cleaned up properly.
@@ -143,33 +153,26 @@ namespace UICatalog {
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
-		/// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything.
+		/// Shows the UI Catalog selection UI. When the user selects a Scenario to run, the
+		/// UI Catalog main app UI is killed and the Scenario is run as though it were Application.Top. 
+		/// When the Scenario exits, this function exits.
 		/// </summary>
 		/// </summary>
 		/// <returns></returns>
 		/// <returns></returns>
-		private static Scenario GetScenarioToRun ()
+		private static Scenario SelectScenario ()
 		{
 		{
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.UseSystemConsole = _useSystemConsole;
 			Application.Init ();
 			Application.Init ();
+			if (_colorScheme == null) {
+				// `Colors` is not initilized until the ConsoleDriver is loaded by 
+				// Application.Init. Set it only the first time though so it is
+				// preserved between running multiple Scenarios
+				_colorScheme = Colors.Base;
+			}
 			Application.HeightAsBuffer = _heightAsBuffer;
 			Application.HeightAsBuffer = _heightAsBuffer;
 
 
-			// Set this here because not initialized until driver is loaded
-			_baseColorScheme = Colors.Base;
-
-			StringBuilder aboutMessage = new StringBuilder ();
-			aboutMessage.AppendLine (@"A comprehensive sample library for");
-			aboutMessage.AppendLine (@"");
-			aboutMessage.AppendLine (@"  _______                  _             _   _____       _  ");
-			aboutMessage.AppendLine (@" |__   __|                (_)           | | / ____|     (_) ");
-			aboutMessage.AppendLine (@"    | | ___ _ __ _ __ ___  _ _ __   __ _| || |  __ _   _ _  ");
-			aboutMessage.AppendLine (@"    | |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | | ");
-			aboutMessage.AppendLine (@"    | |  __/ |  | | | | | | | | | | (_| | || |__| | |_| | | ");
-			aboutMessage.AppendLine (@"    |_|\___|_|  |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_| ");
-			aboutMessage.AppendLine (@"");
-			aboutMessage.AppendLine (@"https://github.com/gui-cs/Terminal.Gui");
-
-			_menu = new MenuBar (new MenuBarItem [] {
+			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_Quit", "", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask)
+					new MenuItem ("_Quit", "Quit UI Catalog", () => Application.RequestStop(), null, null, Key.Q | Key.CtrlMask)
 				}),
 				}),
 				new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
 				new MenuBarItem ("_Color Scheme", CreateColorSchemeMenuItems()),
 				new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
 				new MenuBarItem ("Diag_nostics", CreateDiagnosticMenuItems()),
@@ -177,8 +180,8 @@ namespace UICatalog {
 					new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
 					new MenuItem ("_gui.cs API Overview", "", () => OpenUrl ("https://gui-cs.github.io/Terminal.Gui/articles/overview.html"), null, null, Key.F1),
 					new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
 					new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
 					new MenuItem ("_About...",
 					new MenuItem ("_About...",
-						"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
-				})
+						"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", _aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
+				}),
 			});
 			});
 
 
 			_leftPane = new FrameView ("Categories") {
 			_leftPane = new FrameView ("Categories") {
@@ -186,7 +189,7 @@ namespace UICatalog {
 				Y = 1, // for menu
 				Y = 1, // for menu
 				Width = 25,
 				Width = 25,
 				Height = Dim.Fill (1),
 				Height = Dim.Fill (1),
-				CanFocus = false,
+				CanFocus = true,
 				Shortcut = Key.CtrlMask | Key.C
 				Shortcut = Key.CtrlMask | Key.C
 			};
 			};
 			_leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortcutTag})";
 			_leftPane.Title = $"{_leftPane.Title} ({_leftPane.ShortcutTag})";
@@ -218,7 +221,7 @@ namespace UICatalog {
 			_rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortcutTag})";
 			_rightPane.Title = $"{_rightPane.Title} ({_rightPane.ShortcutTag})";
 			_rightPane.ShortcutAction = () => _rightPane.SetFocus ();
 			_rightPane.ShortcutAction = () => _rightPane.SetFocus ();
 
 
-			_nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length;
+			_nameColumnWidth = _scenarios.OrderByDescending (s => s.GetName ().Length).FirstOrDefault ().GetName ().Length;
 
 
 			_scenarioListView = new ListView () {
 			_scenarioListView = new ListView () {
 				X = 0,
 				X = 0,
@@ -232,9 +235,6 @@ namespace UICatalog {
 			_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
 			_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
 			_rightPane.Add (_scenarioListView);
 			_rightPane.Add (_scenarioListView);
 
 
-			_categoryListView.SelectedItem = _categoryListViewItem;
-			_categoryListView.OnSelectedChanged ();
-
 			_capslock = new StatusItem (Key.CharMask, "Caps", null);
 			_capslock = new StatusItem (Key.CharMask, "Caps", null);
 			_numlock = new StatusItem (Key.CharMask, "Num", null);
 			_numlock = new StatusItem (Key.CharMask, "Num", null);
 			_scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
 			_scrolllock = new StatusItem (Key.CharMask, "Scroll", null);
@@ -247,60 +247,73 @@ namespace UICatalog {
 				_numlock,
 				_numlock,
 				_scrolllock,
 				_scrolllock,
 				new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
 				new StatusItem(Key.Q | Key.CtrlMask, "~CTRL-Q~ Quit", () => {
-					if (_runningScenario is null){
+					if (_selectedScenario is null){
 						// This causes GetScenarioToRun to return null
 						// This causes GetScenarioToRun to return null
-						_runningScenario = null;
+						_selectedScenario = null;
 						Application.RequestStop();
 						Application.RequestStop();
 					} else {
 					} else {
-						_runningScenario.RequestStop();
+						_selectedScenario.RequestStop();
 					}
 					}
 				}),
 				}),
 				new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
 				new StatusItem(Key.F10, "~F10~ Hide/Show Status Bar", () => {
 					_statusBar.Visible = !_statusBar.Visible;
 					_statusBar.Visible = !_statusBar.Visible;
 					_leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_leftPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
 					_rightPane.Height = Dim.Fill(_statusBar.Visible ? 1 : 0);
-					_top.LayoutSubviews();
-					_top.SetChildNeedsDisplay();
+					Application.Top.LayoutSubviews();
+					Application.Top.SetChildNeedsDisplay();
 				}),
 				}),
 				new StatusItem (Key.CharMask, Application.Driver.GetType ().Name, null),
 				new StatusItem (Key.CharMask, Application.Driver.GetType ().Name, null),
 			};
 			};
 
 
-			SetColorScheme ();
-			_top = Application.Top;
-			_top.KeyDown += KeyDownHandler;
-			_top.Add (_menu);
-			_top.Add (_leftPane);
-			_top.Add (_rightPane);
-			_top.Add (_statusBar);
-
-			void TopHandler () {
-				if (_runningScenario != null) {
-					_runningScenario = null;
+			Application.Top.ColorScheme = _colorScheme;
+			Application.Top.KeyDown += KeyDownHandler;
+			Application.Top.Add (menu);
+			Application.Top.Add (_leftPane);
+			Application.Top.Add (_rightPane);
+			Application.Top.Add (_statusBar);
+
+			void TopHandler ()
+			{
+				if (_selectedScenario != null) {
+					_selectedScenario = null;
 					_isFirstRunning = false;
 					_isFirstRunning = false;
 				}
 				}
 				if (!_isFirstRunning) {
 				if (!_isFirstRunning) {
 					_rightPane.SetFocus ();
 					_rightPane.SetFocus ();
 				}
 				}
-				_top.Loaded -= TopHandler;
+				Application.Top.Loaded -= TopHandler;
+			}
+			Application.Top.Loaded += TopHandler;
+
+			// Restore previous selections
+			_categoryListView.SelectedItem = _cachedCategoryIndex;
+			_scenarioListView.SelectedItem = _cachedScenarioIndex;
+
+			// Run UI Catalog UI. When it exits, if _selectedScenario is != null then
+			// a Scenario was selected. Otherwise, the user wants to exit UI Catalog.
+			Application.Run (Application.Top);
+			Application.Shutdown ();
+
+			return _selectedScenario;
+		}
+
+
+		/// <summary>
+		/// Launches the selected scenario, setting the global _selectedScenario
+		/// </summary>
+		/// <param name="e"></param>
+		private static void _scenarioListView_OpenSelectedItem (EventArgs e)
+		{
+			if (_selectedScenario is null) {
+				// Save selected item state
+				_cachedCategoryIndex = _categoryListView.SelectedItem;
+				_cachedScenarioIndex = _scenarioListView.SelectedItem;
+				// Create new instance of scenario (even though Scenarios contains instances)
+				_selectedScenario = (Scenario)Activator.CreateInstance (_scenarioListView.Source.ToList () [_scenarioListView.SelectedItem].GetType ());
+
+				// Tell the main app to stop
+				Application.RequestStop ();
 			}
 			}
-			_top.Loaded += TopHandler;
-			// The following code was moved to the TopHandler event
-			//  because in the MainLoop.EventsPending (wait)
-			//  from the Application.RunLoop with the WindowsDriver
-			//  the OnReady event is triggered due the Focus event.
-			//  On CursesDriver and NetDriver the focus event won't be triggered
-			//  and if it's possible I don't know how to do it.
-			//void ReadyHandler ()
-			//{
-			//	if (!_isFirstRunning) {
-			//		_rightPane.SetFocus ();
-			//	}
-			//	_top.Ready -= ReadyHandler;
-			//}
-			//_top.Ready += ReadyHandler;
-
-			Application.Run (_top);
-			return _runningScenario;
 		}
 		}
 
 
 		static List<MenuItem []> CreateDiagnosticMenuItems ()
 		static List<MenuItem []> CreateDiagnosticMenuItems ()
@@ -318,7 +331,7 @@ namespace UICatalog {
 		{
 		{
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			var item = new MenuItem ();
 			var item = new MenuItem ();
-			item.Title = "_Disable/Enable Mouse";
+			item.Title = "_Disable Mouse";
 			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
 			item.Shortcut = Key.CtrlMask | Key.AltMask | (Key)item.Title.ToString ().Substring (1, 1) [0];
 			item.CheckType |= MenuItemCheckStyle.Checked;
 			item.CheckType |= MenuItemCheckStyle.Checked;
 			item.Checked = Application.IsMouseDisabled;
 			item.Checked = Application.IsMouseDisabled;
@@ -329,12 +342,13 @@ namespace UICatalog {
 
 
 			return menuItems.ToArray ();
 			return menuItems.ToArray ();
 		}
 		}
-		private static MenuItem[] CreateKeybindings()
+		private static MenuItem [] CreateKeybindings ()
 		{
 		{
 
 
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			var item = new MenuItem ();
 			var item = new MenuItem ();
-			item.Title = "Keybindings";
+			item.Title = "_Key Bindings";
+			item.Help = "Change which keys do what";
 			item.Action += () => {
 			item.Action += () => {
 				var dlg = new KeyBindingsDialog ();
 				var dlg = new KeyBindingsDialog ();
 				Application.Run (dlg);
 				Application.Run (dlg);
@@ -409,7 +423,7 @@ namespace UICatalog {
 						}
 						}
 					}
 					}
 					ConsoleDriver.Diagnostics = _diagnosticFlags;
 					ConsoleDriver.Diagnostics = _diagnosticFlags;
-					_top.SetNeedsDisplay ();
+					Application.Top.SetNeedsDisplay ();
 				};
 				};
 				menuItems.Add (item);
 				menuItems.Add (item);
 			}
 			}
@@ -461,52 +475,9 @@ namespace UICatalog {
 					break;
 					break;
 				}
 				}
 			}
 			}
-
-			//MenuItem CheckedMenuMenuItem (ustring menuItem, Action action, Func<bool> checkFunction)
-			//{
-			//	var mi = new MenuItem ();
-			//	mi.Title = menuItem;
-			//	mi.Shortcut = Key.AltMask + index.ToString () [0];
-			//	index++;
-			//	mi.CheckType |= MenuItemCheckStyle.Checked;
-			//	mi.Checked = checkFunction ();
-			//	mi.Action = () => {
-			//		action?.Invoke ();
-			//		mi.Title = menuItem;
-			//		mi.Checked = checkFunction ();
-			//	};
-			//	return mi;
-			//}
-
-			//return new MenuItem [] {
-			//	CheckedMenuMenuItem ("Use _System Console",
-			//		() => {
-			//			_useSystemConsole = !_useSystemConsole;
-			//		},
-			//		() => _useSystemConsole),
-			//	CheckedMenuMenuItem ("Diagnostics: _Frame Padding",
-			//		() => {
-			//			ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FramePadding;
-			//			_top.SetNeedsDisplay ();
-			//		},
-			//		() => (ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding),
-			//	CheckedMenuMenuItem ("Diagnostics: Frame _Ruler",
-			//		() => {
-			//			ConsoleDriver.Diagnostics ^= ConsoleDriver.DiagnosticFlags.FrameRuler;
-			//			_top.SetNeedsDisplay ();
-			//		},
-			//		() => (ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler),
-			//};
 		}
 		}
 
 
-		static void SetColorScheme ()
-		{
-			_leftPane.ColorScheme = _baseColorScheme;
-			_rightPane.ColorScheme = _baseColorScheme;
-			_top?.SetNeedsDisplay ();
-		}
-
-		static ColorScheme _baseColorScheme;
+		static ColorScheme _colorScheme;
 		static MenuItem [] CreateColorSchemeMenuItems ()
 		static MenuItem [] CreateColorSchemeMenuItems ()
 		{
 		{
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			List<MenuItem> menuItems = new List<MenuItem> ();
@@ -515,12 +486,12 @@ namespace UICatalog {
 				item.Title = $"_{sc.Key}";
 				item.Title = $"_{sc.Key}";
 				item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
 				item.Shortcut = Key.AltMask | (Key)sc.Key.Substring (0, 1) [0];
 				item.CheckType |= MenuItemCheckStyle.Radio;
 				item.CheckType |= MenuItemCheckStyle.Radio;
-				item.Checked = sc.Value == _baseColorScheme;
+				item.Checked = sc.Value == _colorScheme;
 				item.Action += () => {
 				item.Action += () => {
-					_baseColorScheme = sc.Value;
-					SetColorScheme ();
+					Application.Top.ColorScheme = _colorScheme = sc.Value;
+					Application.Top?.SetNeedsDisplay ();
 					foreach (var menuItem in menuItems) {
 					foreach (var menuItem in menuItems) {
-						menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _baseColorScheme;
+						menuItem.Checked = menuItem.Title.Equals ($"_{sc.Key}") && sc.Value == _colorScheme;
 					}
 					}
 				};
 				};
 				menuItems.Add (item);
 				menuItems.Add (item);
@@ -528,106 +499,8 @@ namespace UICatalog {
 			return menuItems.ToArray ();
 			return menuItems.ToArray ();
 		}
 		}
 
 
-		private static void _scenarioListView_OpenSelectedItem (EventArgs e)
-		{
-			if (_runningScenario is null) {
-				_scenarioListViewItem = _scenarioListView.SelectedItem;
-				var source = _scenarioListView.Source as ScenarioListDataSource;
-				_runningScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]);
-				Application.RequestStop ();
-			}
-		}
-
-		internal class ScenarioListDataSource : IListDataSource {
-			private readonly int len;
-
-			public List<Type> Scenarios { get; set; }
-
-			public bool IsMarked (int item) => false;
-
-			public int Count => Scenarios.Count;
-
-			public int Length => len;
-
-			public ScenarioListDataSource (List<Type> itemList)
-			{
-				Scenarios = itemList;
-				len = GetMaxLengthItem ();
-			}
-
-			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width, int start = 0)
-			{
-				container.Move (col, line);
-				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
-				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
-				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width, start);
-			}
-
-			public void SetMark (int item, bool value)
-			{
-			}
-
-			int GetMaxLengthItem ()
-			{
-				if (Scenarios?.Count == 0) {
-					return 0;
-				}
-
-				int maxLength = 0;
-				for (int i = 0; i < Scenarios.Count; i++) {
-					var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [i]));
-					var sc = $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [i])}";
-					var l = sc.Length;
-					if (l > maxLength) {
-						maxLength = l;
-					}
-				}
-
-				return maxLength;
-			}
-
-			// A slightly adapted method from: https://github.com/gui-cs/Terminal.Gui/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
-			private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width, int start = 0)
-			{
-				int used = 0;
-				int index = start;
-				while (index < ustr.Length) {
-					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
-					var count = Rune.ColumnWidth (rune);
-					if (used + count >= width) break;
-					driver.AddRune (rune);
-					used += count;
-					index += size;
-				}
-
-				while (used < width) {
-					driver.AddRune (' ');
-					used++;
-				}
-			}
-
-			public IList ToList ()
-			{
-				return Scenarios;
-			}
-		}
-
-		/// <summary>
-		/// When Scenarios are running we need to override the behavior of the Menu 
-		/// and Statusbar to enable Scenarios that use those (or related key input)
-		/// to not be impacted. Same as for tabs.
-		/// </summary>
-		/// <param name="ke"></param>
 		private static void KeyDownHandler (View.KeyEventEventArgs a)
 		private static void KeyDownHandler (View.KeyEventEventArgs a)
 		{
 		{
-			//if (a.KeyEvent.Key == Key.Tab || a.KeyEvent.Key == Key.BackTab) {
-			//	// BUGBUG: Work around Issue #434 by implementing our own TAB navigation
-			//	if (_top.MostFocused == _categoryListView)
-			//		_top.SetFocus (_rightPane);
-			//	else
-			//		_top.SetFocus (_leftPane);
-			//}
-
 			if (a.KeyEvent.IsCapslock) {
 			if (a.KeyEvent.IsCapslock) {
 				_capslock.Title = "Caps: On";
 				_capslock.Title = "Caps: On";
 				_statusBar.SetNeedsDisplay ();
 				_statusBar.SetNeedsDisplay ();
@@ -655,22 +528,16 @@ namespace UICatalog {
 
 
 		private static void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
 		private static void CategoryListView_SelectedChanged (ListViewItemEventArgs e)
 		{
 		{
-			if (_categoryListViewItem != _categoryListView.SelectedItem) {
-				_scenarioListViewItem = 0;
-			}
-			_categoryListViewItem = _categoryListView.SelectedItem;
-			var item = _categories [_categoryListViewItem];
-			List<Type> newlist;
-			if (_categoryListViewItem == 0) {
+			var item = _categories [e.Item];
+			List<Scenario> newlist;
+			if (e.Item == 0) {
 				// First category is "All"
 				// First category is "All"
 				newlist = _scenarios;
 				newlist = _scenarios;
 
 
 			} else {
 			} else {
-				newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList ();
+				newlist = _scenarios.Where (s => s.GetCategories ().Contains (item)).ToList ();
 			}
 			}
-			_scenarioListView.Source = new ScenarioListDataSource (newlist);
-			_scenarioListView.SelectedItem = _scenarioListViewItem;
-
+			_scenarioListView.SetSource (newlist.ToList ());
 		}
 		}
 
 
 		private static void OpenUrl (string url)
 		private static void OpenUrl (string url)

+ 0 - 25
UnitTests/AssemblyInfo.cs

@@ -7,28 +7,3 @@ using Xunit;
 // Since Application is a singleton we can't run tests in parallel
 // Since Application is a singleton we can't run tests in parallel
 [assembly: CollectionBehavior (DisableTestParallelization = true)]
 [assembly: CollectionBehavior (DisableTestParallelization = true)]
 
 
-// This class enables test functions annotated with the [AutoInitShutdown] attribute to 
-// automatically call Application.Init before called and Application.Shutdown after
-// 
-// This is necessary because a) Application is a singleton and Init/Shutdown must be called
-// as a pair, and b) all unit test functions should be atomic.
-[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
-
-	static bool _init = false;
-	public override void Before (MethodInfo methodUnderTest)
-	{
-		if (_init) {
-			throw new InvalidOperationException ("After did not run.");
-		}
-
-		Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-		_init = true;
-	}
-
-	public override void After (MethodInfo methodUnderTest)
-	{
-		Application.Shutdown ();
-		_init = false;
-	}
-}

+ 13 - 13
UnitTests/ButtonTests.cs

@@ -29,7 +29,7 @@ namespace Terminal.Gui.Views {
 			var expected = @"
 			var expected = @"
 [  ]
 [  ]
 ";
 ";
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Application.End (rs);
 			Application.End (rs);
 			btn = new Button ("ARGS", true) { Text = "Test" };
 			btn = new Button ("ARGS", true) { Text = "Test" };
@@ -47,7 +47,7 @@ namespace Terminal.Gui.Views {
 			expected = @"
 			expected = @"
 [◦ Test ◦]
 [◦ Test ◦]
 ";
 ";
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Application.End (rs);
 			Application.End (rs);
 			btn = new Button (3, 4, "Test", true);
 			btn = new Button (3, 4, "Test", true);
@@ -65,7 +65,7 @@ namespace Terminal.Gui.Views {
 			expected = @"
 			expected = @"
    [◦ Test ◦]
    [◦ Test ◦]
 ";
 ";
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Application.End (rs);
 			Application.End (rs);
 		}
 		}
@@ -235,7 +235,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 
@@ -273,7 +273,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 
@@ -310,7 +310,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -342,7 +342,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (btn.AutoSize);
 			Assert.True (btn.AutoSize);
 			btn.Text = "Say Hello 你 changed";
 			btn.Text = "Say Hello 你 changed";
@@ -356,7 +356,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -389,7 +389,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (btn.AutoSize);
 			Assert.True (btn.AutoSize);
 			btn.Text = "Say Hello 你 changed";
 			btn.Text = "Say Hello 你 changed";
@@ -403,7 +403,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -520,7 +520,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────────────────────────────┘
 └────────────────────────────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -550,7 +550,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -582,7 +582,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 	}
 	}
 }
 }

+ 19 - 19
UnitTests/CheckboxTests.cs

@@ -84,7 +84,7 @@ namespace Terminal.Gui.Views {
 √ Test
 √ Test
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 6, 1), pos);
 			Assert.Equal (new Rect (0, 0, 6, 1), pos);
 		}
 		}
 
 
@@ -123,7 +123,7 @@ namespace Terminal.Gui.Views {
 ";
 ";
 
 
 			// Positive test
 			// Positive test
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			// Also Positive test
 			// Also Positive test
@@ -139,7 +139,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.Checked = true;
 			checkBox.Checked = true;
@@ -153,7 +153,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.AutoSize = false;
 			checkBox.AutoSize = false;
@@ -169,7 +169,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.Width = 19;
 			checkBox.Width = 19;
@@ -186,7 +186,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.AutoSize = true;
 			checkBox.AutoSize = true;
@@ -200,7 +200,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 
@@ -240,7 +240,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.Checked = true;
 			checkBox.Checked = true;
@@ -253,7 +253,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 
@@ -294,7 +294,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.Checked = true;
 			checkBox.Checked = true;
@@ -307,7 +307,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 
@@ -364,7 +364,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 6), pos);
 			Assert.Equal (new Rect (0, 0, 30, 6), pos);
 
 
 			checkBox1.Checked = true;
 			checkBox1.Checked = true;
@@ -383,7 +383,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 6), pos);
 			Assert.Equal (new Rect (0, 0, 30, 6), pos);
 		}
 		}
 
 
@@ -424,7 +424,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			checkBox.Checked = true;
 			checkBox.Checked = true;
@@ -437,7 +437,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 
@@ -470,7 +470,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (checkBox.AutoSize);
 			Assert.True (checkBox.AutoSize);
 			checkBox.Text = "Check this out 你 changed";
 			checkBox.Text = "Check this out 你 changed";
@@ -484,7 +484,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -516,7 +516,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (checkBox.AutoSize);
 			Assert.True (checkBox.AutoSize);
 			checkBox.Text = "Check this out 你 changed";
 			checkBox.Text = "Check this out 你 changed";
@@ -530,7 +530,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 	}
 	}
 }
 }

+ 10 - 10
UnitTests/ComboBoxTests.cs

@@ -144,7 +144,7 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (0, cb.SelectedItem);
 			Assert.Equal (0, cb.SelectedItem);
 			Assert.Equal ("One", cb.Text);
 			Assert.Equal ("One", cb.Text);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 One      ▼
 One      ▼
 One       
 One       
 ", output);
 ", output);
@@ -154,7 +154,7 @@ One
 			Assert.Equal (1, cb.SelectedItem);
 			Assert.Equal (1, cb.SelectedItem);
 			Assert.Equal ("Two", cb.Text);
 			Assert.Equal ("Two", cb.Text);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Two      ▼
 Two      ▼
 Two       
 Two       
 ", output);
 ", output);
@@ -164,7 +164,7 @@ Two
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal ("Three", cb.Text);
 			Assert.Equal ("Three", cb.Text);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Three    ▼
 Three    ▼
 Three     
 Three     
 ", output);
 ", output);
@@ -809,7 +809,7 @@ Three
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("", cb.Text);
 			Assert.Equal ("", cb.Text);
 			cb.Redraw (cb.Bounds);
 			cb.Redraw (cb.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 One   
 One   
 Two   
 Two   
@@ -824,7 +824,7 @@ Three ", output);
 				cb.Subviews [1].GetNormalColor ()
 				cb.Subviews [1].GetNormalColor ()
 			};
 			};
 
 
-			GraphViewTests.AssertDriverColorsAre (@"
+			TestHelpers.AssertDriverColorsAre (@"
 000000
 000000
 00000
 00000
 22222
 22222
@@ -836,7 +836,7 @@ Three ", output);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("", cb.Text);
 			Assert.Equal ("", cb.Text);
 			cb.Redraw (cb.Bounds);
 			cb.Redraw (cb.Bounds);
-			GraphViewTests.AssertDriverColorsAre (@"
+			TestHelpers.AssertDriverColorsAre (@"
 000000
 000000
 22222
 22222
 00000
 00000
@@ -848,7 +848,7 @@ Three ", output);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("", cb.Text);
 			Assert.Equal ("", cb.Text);
 			cb.Redraw (cb.Bounds);
 			cb.Redraw (cb.Bounds);
-			GraphViewTests.AssertDriverColorsAre (@"
+			TestHelpers.AssertDriverColorsAre (@"
 000000
 000000
 22222
 22222
 22222
 22222
@@ -866,7 +866,7 @@ Three ", output);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal ("Three", cb.Text);
 			Assert.Equal ("Three", cb.Text);
 			cb.Redraw (cb.Bounds);
 			cb.Redraw (cb.Bounds);
-			GraphViewTests.AssertDriverColorsAre (@"
+			TestHelpers.AssertDriverColorsAre (@"
 000000
 000000
 22222
 22222
 22222
 22222
@@ -878,7 +878,7 @@ Three ", output);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal ("Three", cb.Text);
 			Assert.Equal ("Three", cb.Text);
 			cb.Redraw (cb.Bounds);
 			cb.Redraw (cb.Bounds);
-			GraphViewTests.AssertDriverColorsAre (@"
+			TestHelpers.AssertDriverColorsAre (@"
 000000
 000000
 22222
 22222
 00000
 00000
@@ -890,7 +890,7 @@ Three ", output);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal (2, cb.SelectedItem);
 			Assert.Equal ("Three", cb.Text);
 			Assert.Equal ("Three", cb.Text);
 			cb.Redraw (cb.Bounds);
 			cb.Redraw (cb.Bounds);
-			GraphViewTests.AssertDriverColorsAre (@"
+			TestHelpers.AssertDriverColorsAre (@"
 000000
 000000
 00000
 00000
 22222
 22222

+ 201 - 19
UnitTests/ConsoleDriverTests.cs

@@ -1,7 +1,7 @@
 using System;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using Terminal.Gui;
 using Terminal.Gui.Views;
 using Terminal.Gui.Views;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
@@ -228,7 +228,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 
 			void SendKeys ()
 			void SendKeys ()
 			{
 			{
-				var k = keyEnums [idxKey];
+				var k = shift && char.IsLetter ((char)keyEnums [idxKey]) && char.IsLower ((char)keyEnums [idxKey])
+					? (Key)char.ToUpper ((char)keyEnums [idxKey]) : keyEnums [idxKey];
 				var c = (char)k;
 				var c = (char)k;
 				var ck = char.IsLetter (c) ? (ConsoleKey)char.ToUpper (c) : (ConsoleKey)c;
 				var ck = char.IsLetter (c) ? (ConsoleKey)char.ToUpper (c) : (ConsoleKey)c;
 				var mk = new KeyModifiers () {
 				var mk = new KeyModifiers () {
@@ -551,7 +552,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 10), pos);
 			Assert.Equal (new Rect (0, 0, 30, 10), pos);
 		}
 		}
 
 
@@ -581,7 +582,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 └──────────────────┘
 └──────────────────┘
 ";
 ";
 
 
-					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 					Assert.Equal (new Rect (0, 0, 20, 8), pos);
 					Assert.Equal (new Rect (0, 0, 20, 8), pos);
 
 
 					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
 					Assert.True (dlg.ProcessKey (new KeyEvent (Key.Tab, new KeyModifiers ())));
@@ -598,7 +599,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 └──────────────────┘
 └──────────────────┘
 ";
 ";
 
 
-					pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+					pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 					Assert.Equal (new Rect (0, 0, 20, 8), pos);
 					Assert.Equal (new Rect (0, 0, 20, 8), pos);
 
 
 					win.RequestStop ();
 					win.RequestStop ();
@@ -608,29 +609,210 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Run (win);
 			Application.Run (win);
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
-		
+
 		[Theory]
 		[Theory]
-		[InlineData(0x0000001F, 0x241F)]
-		[InlineData(0x0000007F, 0x247F)]
-		[InlineData(0x0000009F, 0x249F)]
-		[InlineData(0x0001001A, 0x241A)]
+		[InlineData (0x0000001F, 0x241F)]
+		[InlineData (0x0000007F, 0x247F)]
+		[InlineData (0x0000009F, 0x249F)]
+		[InlineData (0x0001001A, 0x241A)]
 		public void MakePrintable_Converts_Control_Chars_To_Proper_Unicode (uint code, uint expected)
 		public void MakePrintable_Converts_Control_Chars_To_Proper_Unicode (uint code, uint expected)
 		{
 		{
-			var actual = ConsoleDriver.MakePrintable(code);
-				
+			var actual = ConsoleDriver.MakePrintable (code);
+
 			Assert.Equal (expected, actual.Value);
 			Assert.Equal (expected, actual.Value);
 		}
 		}
-		
+
 		[Theory]
 		[Theory]
-		[InlineData(0x20)]
-		[InlineData(0x7E)]
-		[InlineData(0xA0)]
-		[InlineData(0x010020)]
+		[InlineData (0x20)]
+		[InlineData (0x7E)]
+		[InlineData (0xA0)]
+		[InlineData (0x010020)]
 		public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (uint code)
 		public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (uint code)
 		{
 		{
-			var actual = ConsoleDriver.MakePrintable(code);
-				
+			var actual = ConsoleDriver.MakePrintable (code);
+
 			Assert.Equal (code, actual.Value);
 			Assert.Equal (code, actual.Value);
 		}
 		}
+
+		/// <summary>
+		/// Sometimes when using remote tools EventKeyRecord sends 'virtual keystrokes'.
+		/// These are indicated with the wVirtualKeyCode of 231. When we see this code
+		/// then we need to look to the unicode character (UnicodeChar) instead of the key
+		/// when telling the rest of the framework what button was pressed. For full details
+		/// see: https://github.com/gui-cs/Terminal.Gui/issues/2008
+		/// </summary>
+		[Theory, AutoInitShutdown]
+		[ClassData (typeof (PacketTest))]
+		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
+		{
+			ConsoleModifiers modifiers = new ConsoleModifiers ();
+			if (shift) {
+				modifiers |= ConsoleModifiers.Shift;
+			}
+			if (alt) {
+				modifiers |= ConsoleModifiers.Alt;
+			}
+			if (control) {
+				modifiers |= ConsoleModifiers.Control;
+			}
+			var mappedConsoleKey = ConsoleKeyMapping.GetConsoleKeyFromKey (unicodeCharacter, modifiers, out uint scanCode, out uint outputChar);
+
+			if ((scanCode > 0 || mappedConsoleKey == 0) && mappedConsoleKey == initialVirtualKey) {
+				Assert.Equal (mappedConsoleKey, initialVirtualKey);
+			} else {
+				Assert.Equal (mappedConsoleKey, outputChar < 0xff ? (uint)(outputChar & 0xff | 0xff << 8) : outputChar);
+			}
+			Assert.Equal (scanCode, initialScanCode);
+
+			var keyChar = ConsoleKeyMapping.GetKeyCharFromConsoleKey (mappedConsoleKey, modifiers, out uint consoleKey, out scanCode);
+
+			//if (scanCode > 0 && consoleKey == keyChar && consoleKey > 48 && consoleKey > 57 && consoleKey < 65 && consoleKey > 91) {
+			if (scanCode > 0 && keyChar == 0 && consoleKey == mappedConsoleKey) {
+				Assert.Equal (0, (double)keyChar);
+			} else {
+				Assert.Equal (keyChar, unicodeCharacter);
+			}
+			Assert.Equal (consoleKey, expectedVirtualKey);
+			Assert.Equal (scanCode, expectedScanCode);
+
+			var top = Application.Top;
+
+			top.KeyPress += (e) => {
+				var after = ShortcutHelper.GetModifiersKey (e.KeyEvent);
+				Assert.Equal (expectedRemapping, after);
+				e.Handled = true;
+				Application.RequestStop ();
+			};
+
+			var iterations = -1;
+
+			Application.Iteration += () => {
+				iterations++;
+				if (iterations == 0) {
+					Application.Driver.SendKeys ((char)mappedConsoleKey, ConsoleKey.Packet, shift, alt, control);
+				}
+			};
+
+			Application.Run ();
+			Application.Shutdown ();
+		}
+
+		public class PacketTest : IEnumerable, IEnumerable<object []> {
+			public IEnumerator<object []> GetEnumerator ()
+			{
+				yield return new object [] { 'a', false, false, false, 'A', 30, Key.a, 'A', 30 };
+				yield return new object [] { 'A', true, false, false, 'A', 30, Key.A | Key.ShiftMask, 'A', 30 };
+				yield return new object [] { 'A', true, true, false, 'A', 30, Key.A | Key.ShiftMask | Key.AltMask, 'A', 30 };
+				yield return new object [] { 'A', true, true, true, 'A', 30, Key.A | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 'A', 30 };
+				yield return new object [] { 'z', false, false, false, 'Z', 44, Key.z, 'Z', 44 };
+				yield return new object [] { 'Z', true, false, false, 'Z', 44, Key.Z | Key.ShiftMask, 'Z', 44 };
+				yield return new object [] { 'Z', true, true, false, 'Z', 44, Key.Z | Key.ShiftMask | Key.AltMask, 'Z', 44 };
+				yield return new object [] { 'Z', true, true, true, 'Z', 44, Key.Z | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 'Z', 44 };
+				yield return new object [] { '英', false, false, false, '\0', 0, (Key)'英', '\0', 0 };
+				yield return new object [] { '英', true, false, false, '\0', 0, (Key)'英' | Key.ShiftMask, '\0', 0 };
+				yield return new object [] { '英', true, true, false, '\0', 0, (Key)'英' | Key.ShiftMask | Key.AltMask, '\0', 0 };
+				yield return new object [] { '英', true, true, true, '\0', 0, (Key)'英' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '\0', 0 };
+				yield return new object [] { '+', false, false, false, 187, 26, (Key)'+', 187, 26 };
+				yield return new object [] { '*', true, false, false, 187, 26, (Key)'*' | Key.ShiftMask, 187, 26 };
+				yield return new object [] { '+', true, true, false, 187, 26, (Key)'+' | Key.ShiftMask | Key.AltMask, 187, 26 };
+				yield return new object [] { '+', true, true, true, 187, 26, (Key)'+' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 187, 26 };
+				yield return new object [] { '1', false, false, false, '1', 2, Key.D1, '1', 2 };
+				yield return new object [] { '!', true, false, false, '1', 2, (Key)'!' | Key.ShiftMask, '1', 2 };
+				yield return new object [] { '1', true, true, false, '1', 2, Key.D1 | Key.ShiftMask | Key.AltMask, '1', 2 };
+				yield return new object [] { '1', true, true, true, '1', 2, Key.D1 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '1', 2 };
+				yield return new object [] { '1', false, true, true, '1', 2, Key.D1 | Key.AltMask | Key.CtrlMask, '1', 2 };
+				yield return new object [] { '1', false, true, true, '1', 2, Key.D1 | Key.AltMask | Key.CtrlMask, '1', 2 };
+				yield return new object [] { '2', false, false, false, '2', 3, Key.D2, '2', 3 };
+				yield return new object [] { '"', true, false, false, '2', 3, (Key)'"' | Key.ShiftMask, '2', 3 };
+				yield return new object [] { '2', true, true, false, '2', 3, Key.D2 | Key.ShiftMask | Key.AltMask, '2', 3 };
+				yield return new object [] { '2', true, true, true, '2', 3, Key.D2 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '2', 3 };
+				yield return new object [] { '@', false, true, true, '2', 3, (Key)'@' | Key.AltMask | Key.CtrlMask, '2', 3 };
+				yield return new object [] { '3', false, false, false, '3', 4, Key.D3, '3', 4 };
+				yield return new object [] { '#', true, false, false, '3', 4, (Key)'#' | Key.ShiftMask, '3', 4 };
+				yield return new object [] { '3', true, true, false, '3', 4, Key.D3 | Key.ShiftMask | Key.AltMask, '3', 4 };
+				yield return new object [] { '3', true, true, true, '3', 4, Key.D3 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '3', 4 };
+				yield return new object [] { '£', false, true, true, '3', 4, (Key)'£' | Key.AltMask | Key.CtrlMask, '3', 4 };
+				yield return new object [] { '4', false, false, false, '4', 5, Key.D4, '4', 5 };
+				yield return new object [] { '$', true, false, false, '4', 5, (Key)'$' | Key.ShiftMask, '4', 5 };
+				yield return new object [] { '4', true, true, false, '4', 5, Key.D4 | Key.ShiftMask | Key.AltMask, '4', 5 };
+				yield return new object [] { '4', true, true, true, '4', 5, Key.D4 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '4', 5 };
+				yield return new object [] { '§', false, true, true, '4', 5, (Key)'§' | Key.AltMask | Key.CtrlMask, '4', 5 };
+				yield return new object [] { '5', false, false, false, '5', 6, Key.D5, '5', 6 };
+				yield return new object [] { '%', true, false, false, '5', 6, (Key)'%' | Key.ShiftMask, '5', 6 };
+				yield return new object [] { '5', true, true, false, '5', 6, Key.D5 | Key.ShiftMask | Key.AltMask, '5', 6 };
+				yield return new object [] { '5', true, true, true, '5', 6, Key.D5 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '5', 6 };
+				yield return new object [] { '€', false, true, true, '5', 6, (Key)'€' | Key.AltMask | Key.CtrlMask, '5', 6 };
+				yield return new object [] { '6', false, false, false, '6', 7, Key.D6, '6', 7 };
+				yield return new object [] { '&', true, false, false, '6', 7, (Key)'&' | Key.ShiftMask, '6', 7 };
+				yield return new object [] { '6', true, true, false, '6', 7, Key.D6 | Key.ShiftMask | Key.AltMask, '6', 7 };
+				yield return new object [] { '6', true, true, true, '6', 7, Key.D6 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '6', 7 };
+				yield return new object [] { '6', false, true, true, '6', 7, Key.D6 | Key.AltMask | Key.CtrlMask, '6', 7 };
+				yield return new object [] { '7', false, false, false, '7', 8, Key.D7, '7', 8 };
+				yield return new object [] { '/', true, false, false, '7', 8, (Key)'/' | Key.ShiftMask, '7', 8 };
+				yield return new object [] { '7', true, true, false, '7', 8, Key.D7 | Key.ShiftMask | Key.AltMask, '7', 8 };
+				yield return new object [] { '7', true, true, true, '7', 8, Key.D7 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '7', 8 };
+				yield return new object [] { '{', false, true, true, '7', 8, (Key)'{' | Key.AltMask | Key.CtrlMask, '7', 8 };
+				yield return new object [] { '8', false, false, false, '8', 9, Key.D8, '8', 9 };
+				yield return new object [] { '(', true, false, false, '8', 9, (Key)'(' | Key.ShiftMask, '8', 9 };
+				yield return new object [] { '8', true, true, false, '8', 9, Key.D8 | Key.ShiftMask | Key.AltMask, '8', 9 };
+				yield return new object [] { '8', true, true, true, '8', 9, Key.D8 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '8', 9 };
+				yield return new object [] { '[', false, true, true, '8', 9, (Key)'[' | Key.AltMask | Key.CtrlMask, '8', 9 };
+				yield return new object [] { '9', false, false, false, '9', 10, Key.D9, '9', 10 };
+				yield return new object [] { ')', true, false, false, '9', 10, (Key)')' | Key.ShiftMask, '9', 10 };
+				yield return new object [] { '9', true, true, false, '9', 10, Key.D9 | Key.ShiftMask | Key.AltMask, '9', 10 };
+				yield return new object [] { '9', true, true, true, '9', 10, Key.D9 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '9', 10 };
+				yield return new object [] { ']', false, true, true, '9', 10, (Key)']' | Key.AltMask | Key.CtrlMask, '9', 10 };
+				yield return new object [] { '0', false, false, false, '0', 11, Key.D0, '0', 11 };
+				yield return new object [] { '=', true, false, false, '0', 11, (Key)'=' | Key.ShiftMask, '0', 11 };
+				yield return new object [] { '0', true, true, false, '0', 11, Key.D0 | Key.ShiftMask | Key.AltMask, '0', 11 };
+				yield return new object [] { '0', true, true, true, '0', 11, Key.D0 | Key.ShiftMask | Key.AltMask | Key.CtrlMask, '0', 11 };
+				yield return new object [] { '}', false, true, true, '0', 11, (Key)'}' | Key.AltMask | Key.CtrlMask, '0', 11 };
+				yield return new object [] { '\'', false, false, false, 219, 12, (Key)'\'', 219, 12 };
+				yield return new object [] { '?', true, false, false, 219, 12, (Key)'?' | Key.ShiftMask, 219, 12 };
+				yield return new object [] { '\'', true, true, false, 219, 12, (Key)'\'' | Key.ShiftMask | Key.AltMask, 219, 12 };
+				yield return new object [] { '\'', true, true, true, 219, 12, (Key)'\'' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 219, 12 };
+				yield return new object [] { '«', false, false, false, 221, 13, (Key)'«', 221, 13 };
+				yield return new object [] { '»', true, false, false, 221, 13, (Key)'»' | Key.ShiftMask, 221, 13 };
+				yield return new object [] { '«', true, true, false, 221, 13, (Key)'«' | Key.ShiftMask | Key.AltMask, 221, 13 };
+				yield return new object [] { '«', true, true, true, 221, 13, (Key)'«' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 221, 13 };
+				yield return new object [] { 'á', false, false, false, 'á', 0, (Key)'á', 'A', 30 };
+				yield return new object [] { 'Á', true, false, false, 'Á', 0, (Key)'Á' | Key.ShiftMask, 'A', 30 };
+				yield return new object [] { 'à', false, false, false, 'à', 0, (Key)'à', 'A', 30 };
+				yield return new object [] { 'À', true, false, false, 'À', 0, (Key)'À' | Key.ShiftMask, 'A', 30 };
+				yield return new object [] { 'é', false, false, false, 'é', 0, (Key)'é', 'E', 18 };
+				yield return new object [] { 'É', true, false, false, 'É', 0, (Key)'É' | Key.ShiftMask, 'E', 18 };
+				yield return new object [] { 'è', false, false, false, 'è', 0, (Key)'è', 'E', 18 };
+				yield return new object [] { 'È', true, false, false, 'È', 0, (Key)'È' | Key.ShiftMask, 'E', 18 };
+				yield return new object [] { 'í', false, false, false, 'í', 0, (Key)'í', 'I', 23 };
+				yield return new object [] { 'Í', true, false, false, 'Í', 0, (Key)'Í' | Key.ShiftMask, 'I', 23 };
+				yield return new object [] { 'ì', false, false, false, 'ì', 0, (Key)'ì', 'I', 23 };
+				yield return new object [] { 'Ì', true, false, false, 'Ì', 0, (Key)'Ì' | Key.ShiftMask, 'I', 23 };
+				yield return new object [] { 'ó', false, false, false, 'ó', 0, (Key)'ó', 'O', 24 };
+				yield return new object [] { 'Ó', true, false, false, 'Ó', 0, (Key)'Ó' | Key.ShiftMask, 'O', 24 };
+				yield return new object [] { 'ò', false, false, false, 'Ó', 0, (Key)'ò', 'O', 24 };
+				yield return new object [] { 'Ò', true, false, false, 'Ò', 0, (Key)'Ò' | Key.ShiftMask, 'O', 24 };
+				yield return new object [] { 'ú', false, false, false, 'ú', 0, (Key)'ú', 'U', 22 };
+				yield return new object [] { 'Ú', true, false, false, 'Ú', 0, (Key)'Ú' | Key.ShiftMask, 'U', 22 };
+				yield return new object [] { 'ù', false, false, false, 'ù', 0, (Key)'ù', 'U', 22 };
+				yield return new object [] { 'Ù', true, false, false, 'Ù', 0, (Key)'Ù' | Key.ShiftMask, 'U', 22 };
+				yield return new object [] { 'ö', false, false, false, 'ó', 0, (Key)'ö', 'O', 24 };
+				yield return new object [] { 'Ö', true, false, false, 'Ó', 0, (Key)'Ö' | Key.ShiftMask, 'O', 24 };
+				yield return new object [] { '<', false, false, false, 226, 86, (Key)'<', 226, 86 };
+				yield return new object [] { '>', true, false, false, 226, 86, (Key)'>' | Key.ShiftMask, 226, 86 };
+				yield return new object [] { '<', true, true, false, 226, 86, (Key)'<' | Key.ShiftMask | Key.AltMask, 226, 86 };
+				yield return new object [] { '<', true, true, true, 226, 86, (Key)'<' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 226, 86 };
+				yield return new object [] { 'ç', false, false, false, 192, 39, (Key)'ç', 192, 39 };
+				yield return new object [] { 'Ç', true, false, false, 192, 39, (Key)'Ç' | Key.ShiftMask, 192, 39 };
+				yield return new object [] { 'ç', true, true, false, 192, 39, (Key)'ç' | Key.ShiftMask | Key.AltMask, 192, 39 };
+				yield return new object [] { 'ç', true, true, true, 192, 39, (Key)'ç' | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 192, 39 };
+				yield return new object [] { '¨', false, true, true, 187, 26, (Key)'¨' | Key.AltMask | Key.CtrlMask, 187, 26 };
+				yield return new object [] { (uint)Key.PageUp, false, false, false, 33, 73, Key.PageUp, 33, 73 };
+				yield return new object [] { (uint)Key.PageUp, true, false, false, 33, 73, Key.PageUp | Key.ShiftMask, 33, 73 };
+				yield return new object [] { (uint)Key.PageUp, true, true, false, 33, 73, Key.PageUp | Key.ShiftMask | Key.AltMask, 33, 73 };
+				yield return new object [] { (uint)Key.PageUp, true, true, true, 33, 73, Key.PageUp | Key.ShiftMask | Key.AltMask | Key.CtrlMask, 33, 73 };
+			}
+
+			IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
+		}
 	}
 	}
 }
 }

+ 44 - 30
UnitTests/ContextMenuTests.cs

@@ -72,7 +72,7 @@ namespace Terminal.Gui.Core {
           └──────┘
           └──────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			cm.Hide ();
 			cm.Hide ();
 			Assert.False (ContextMenu.IsShow);
 			Assert.False (ContextMenu.IsShow);
@@ -81,7 +81,7 @@ namespace Terminal.Gui.Core {
 
 
 			expected = "";
 			expected = "";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -105,7 +105,7 @@ namespace Terminal.Gui.Core {
           └──────┘
           └──────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			cm.Position = new Point (5, 10);
 			cm.Position = new Point (5, 10);
 
 
@@ -119,7 +119,7 @@ namespace Terminal.Gui.Core {
      └──────┘
      └──────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 		}
 		}
 
 
@@ -144,7 +144,7 @@ namespace Terminal.Gui.Core {
           └──────┘
           └──────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			cm.MenuItems = new MenuBarItem (new MenuItem [] {
 			cm.MenuItems = new MenuBarItem (new MenuItem [] {
 				new MenuItem ("First", "", null),
 				new MenuItem ("First", "", null),
@@ -164,7 +164,7 @@ namespace Terminal.Gui.Core {
           └─────────┘
           └─────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 		}
 		}
 
 
@@ -271,7 +271,7 @@ namespace Terminal.Gui.Core {
                                                                         └──────┘
                                                                         └──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (72, 21, 80, 4), pos);
 			Assert.Equal (new Rect (72, 21, 80, 4), pos);
 
 
 			cm.Hide ();
 			cm.Hide ();
@@ -312,7 +312,7 @@ namespace Terminal.Gui.Core {
                                                                       View    
                                                                       View    
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (70, 20, 78, 5), pos);
 			Assert.Equal (new Rect (70, 20, 78, 5), pos);
 
 
 			cm.Hide ();
 			cm.Hide ();
@@ -347,7 +347,7 @@ namespace Terminal.Gui.Core {
           └──────┘
           └──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (10, 5, 18, 5), pos);
 			Assert.Equal (new Rect (10, 5, 18, 5), pos);
 
 
 			cm.Hide ();
 			cm.Hide ();
@@ -370,7 +370,7 @@ namespace Terminal.Gui.Core {
      └──────┘
      └──────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (5, 10, 13, 7), pos);
 			Assert.Equal (new Rect (5, 10, 13, 7), pos);
 
 
 			cm.Hide ();
 			cm.Hide ();
@@ -401,7 +401,7 @@ namespace Terminal.Gui.Core {
 └────
 └────
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 1, 5, 4), pos);
 			Assert.Equal (new Rect (0, 1, 5, 4), pos);
 
 
 			cm.Hide ();
 			cm.Hide ();
@@ -431,7 +431,7 @@ namespace Terminal.Gui.Core {
 │ Two  │
 │ Two  │
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 8, 3), pos);
 			Assert.Equal (new Rect (0, 0, 8, 3), pos);
 
 
 			cm.Hide ();
 			cm.Hide ();
@@ -484,7 +484,7 @@ namespace Terminal.Gui.Core {
 └──────┘
 └──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 1, 8, 4), pos);
 			Assert.Equal (new Rect (0, 1, 8, 4), pos);
 
 
 			cm.ForceMinimumPosToZero = false;
 			cm.ForceMinimumPosToZero = false;
@@ -498,7 +498,7 @@ namespace Terminal.Gui.Core {
 ──────┘
 ──────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 0, 7, 3), pos);
 			Assert.Equal (new Rect (1, 0, 7, 3), pos);
 		}
 		}
 
 
@@ -592,7 +592,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  File   Edit                   
+ File  Edit                     
                                 
                                 
                                 
                                 
   Label: TextField              
   Label: TextField              
@@ -611,8 +611,8 @@ namespace Terminal.Gui.Core {
  F1 Help │ ^Q Quit              
  F1 Help │ ^Q Quit              
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 32, 17), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 32, 17), pos);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -656,7 +656,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (10, 5), tf.ContextMenu.Position);
 			Assert.Equal (new Point (10, 5), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  File   Edit                               
+ File  Edit                                 
 ┌ Window ──────────────────────────────────┐
 ┌ Window ──────────────────────────────────┐
 │                                          │
 │                                          │
 │                                          │
 │                                          │
@@ -675,8 +675,8 @@ namespace Terminal.Gui.Core {
  F1 Help │ ^Q Quit                          
  F1 Help │ ^Q Quit                          
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 44, 17), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 44, 17), pos);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -707,7 +707,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (-1, -2), cm.Position);
 			Assert.Equal (new Point (-1, -2), cm.Position);
 			var top = Application.Top;
 			var top = Application.Top;
 			Application.Begin (top);
 			Application.Begin (top);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two    │
 │ Two    │
@@ -726,7 +726,7 @@ namespace Terminal.Gui.Core {
 			}));
 			}));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (-1, -2), cm.Position);
 			Assert.Equal (new Point (-1, -2), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐             
 ┌────────┐             
 │ One    │             
 │ One    │             
 │ Two    │             
 │ Two    │             
@@ -747,7 +747,7 @@ namespace Terminal.Gui.Core {
 			cm.Show ();
 			cm.Show ();
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (41, -2), cm.Position);
 			Assert.Equal (new Point (41, -2), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
                               ┌────────┐
                               ┌────────┐
                               │ One    │
                               │ One    │
                               │ Two    │
                               │ Two    │
@@ -766,7 +766,7 @@ namespace Terminal.Gui.Core {
 			}));
 			}));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (41, -2), cm.Position);
 			Assert.Equal (new Point (41, -2), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
                               ┌────────┐
                               ┌────────┐
                               │ One    │
                               │ One    │
                               │ Two    │
                               │ Two    │
@@ -786,7 +786,7 @@ namespace Terminal.Gui.Core {
 			cm.Show ();
 			cm.Show ();
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (41, 9), cm.Position);
 			Assert.Equal (new Point (41, 9), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
                               ┌────────┐
                               ┌────────┐
                               │ One    │
                               │ One    │
                               │ Two    │
                               │ Two    │
@@ -805,7 +805,7 @@ namespace Terminal.Gui.Core {
 			}));
 			}));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (41, 9), cm.Position);
 			Assert.Equal (new Point (41, 9), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
                               ┌────────┐
                               ┌────────┐
                  ┌───────────┐│ One    │
                  ┌───────────┐│ One    │
                  │ SubMenu1  ││ Two    │
                  │ SubMenu1  ││ Two    │
@@ -822,7 +822,7 @@ namespace Terminal.Gui.Core {
 			cm.Show ();
 			cm.Show ();
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (41, 22), cm.Position);
 			Assert.Equal (new Point (41, 22), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
                               ┌────────┐
                               ┌────────┐
                               │ One    │
                               │ One    │
                               │ Two    │
                               │ Two    │
@@ -841,7 +841,7 @@ namespace Terminal.Gui.Core {
 			}));
 			}));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (41, 22), cm.Position);
 			Assert.Equal (new Point (41, 22), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
                  ┌───────────┐          
                  ┌───────────┐          
                  │ SubMenu1  │┌────────┐
                  │ SubMenu1  │┌────────┐
                  │ SubMenu2  ││ One    │
                  │ SubMenu2  ││ One    │
@@ -858,7 +858,7 @@ namespace Terminal.Gui.Core {
 			cm.Show ();
 			cm.Show ();
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (19, 10), cm.Position);
 			Assert.Equal (new Point (19, 10), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
         ┌────────┐
         ┌────────┐
         │ One    │
         │ One    │
         │ Two    │
         │ Two    │
@@ -877,7 +877,7 @@ namespace Terminal.Gui.Core {
 			}));
 			}));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (new Point (19, 10), cm.Position);
 			Assert.Equal (new Point (19, 10), cm.Position);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌───────────┐────┐
 ┌───────────┐────┐
 │ SubMenu1  │    │
 │ SubMenu1  │    │
 │ SubMenu2  │    │
 │ SubMenu2  │    │
@@ -888,5 +888,19 @@ namespace Terminal.Gui.Core {
 │ SubMenu7  │────┘
 │ SubMenu7  │────┘
 ", output);
 ", output);
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Key_Open_And_Close_The_ContextMenu ()
+		{
+			var tf = new TextField ();
+			var top = Application.Top;
+			top.Add (tf);
+			Application.Begin (top);
+
+			Assert.True (tf.ProcessKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ())));
+			Assert.True (tf.ContextMenu.MenuBar.IsMenuOpen);
+			Assert.True (top.Subviews [1].ProcessKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ())));
+			Assert.Null (tf.ContextMenu.MenuBar);
+		}
 	}
 	}
 }
 }

+ 38 - 38
UnitTests/DialogTests.cs

@@ -43,28 +43,28 @@ namespace Terminal.Gui.Views {
 			d.SetBufferSize (width, 3);
 			d.SetBufferSize (width, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			buttonRow = $"{d.VLine}      {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}";
 			buttonRow = $"{d.VLine}      {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			buttonRow = $"{d.VLine}      {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}";
 			buttonRow = $"{d.VLine}      {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			buttonRow = $"{d.VLine}{d.LeftBracket} {btnText} {d.RightBracket}      {d.VLine}";
 			buttonRow = $"{d.VLine}{d.LeftBracket} {btnText} {d.RightBracket}      {d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -91,28 +91,28 @@ namespace Terminal.Gui.Views {
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			buttonRow = $@"{d.VLine}{btn1}   {btn2}{d.VLine}";
 			buttonRow = $@"{d.VLine}{btn1}   {btn2}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			buttonRow = $@"{d.VLine}  {btn1} {btn2}{d.VLine}";
 			buttonRow = $@"{d.VLine}  {btn1} {btn2}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			buttonRow = $@"{d.VLine}{btn1} {btn2}  {d.VLine}";
 			buttonRow = $@"{d.VLine}{btn1} {btn2}  {d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -149,7 +149,7 @@ namespace Terminal.Gui.Views {
 			//button1.Visible = false;
 			//button1.Visible = false;
 			//Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 			//Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 			//buttonRow = $@"{d.VLine}         {btn2} {d.VLine}";
 			//buttonRow = $@"{d.VLine}         {btn2} {d.VLine}";
-			//GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			//DriverAsserts.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			//Application.End (runstate);
 			//Application.End (runstate);
 
 
 			// Justify
 			// Justify
@@ -160,21 +160,21 @@ namespace Terminal.Gui.Views {
 			button1.Visible = false;
 			button1.Visible = false;
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 			buttonRow = $@"{d.VLine}          {btn2}{d.VLine}";
 			buttonRow = $@"{d.VLine}          {btn2}{d.VLine}";
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			//// Right
 			//// Right
 			//buttonRow = $@"{d.VLine}  {btn1} {btn2}{d.VLine}";
 			//buttonRow = $@"{d.VLine}  {btn1} {btn2}{d.VLine}";
 			//Assert.Equal (width, buttonRow.Length);
 			//Assert.Equal (width, buttonRow.Length);
 			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text));
 			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text));
-			//GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			//DriverAsserts.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			//Application.End (runstate);
 			//Application.End (runstate);
 
 
 			//// Left
 			//// Left
 			//buttonRow = $@"{d.VLine}{btn1} {btn2}  {d.VLine}";
 			//buttonRow = $@"{d.VLine}{btn1} {btn2}  {d.VLine}";
 			//Assert.Equal (width, buttonRow.Length);
 			//Assert.Equal (width, buttonRow.Length);
 			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text));
 			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text));
-			//GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			//DriverAsserts.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			//Application.End (runstate);
 			//Application.End (runstate);
 		}
 		}
 
 
@@ -203,28 +203,28 @@ namespace Terminal.Gui.Views {
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			buttonRow = $@"{d.VLine}{btn1}  {btn2}  {btn3}{d.VLine}";
 			buttonRow = $@"{d.VLine}{btn1}  {btn2}  {btn3}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			buttonRow = $@"{d.VLine}  {btn1} {btn2} {btn3}{d.VLine}";
 			buttonRow = $@"{d.VLine}  {btn1} {btn2} {btn3}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			buttonRow = $@"{d.VLine}{btn1} {btn2} {btn3}  {d.VLine}";
 			buttonRow = $@"{d.VLine}{btn1} {btn2} {btn3}  {d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -256,28 +256,28 @@ namespace Terminal.Gui.Views {
 
 
 			// Default - Center
 			// Default - Center
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			buttonRow = $"{d.VLine}{btn1} {btn2}  {btn3}  {btn4}{d.VLine}";
 			buttonRow = $"{d.VLine}{btn1} {btn2}  {btn3}  {btn4}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			buttonRow = $"{d.VLine}  {btn1} {btn2} {btn3} {btn4}{d.VLine}";
 			buttonRow = $"{d.VLine}  {btn1} {btn2} {btn3} {btn4}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}  {d.VLine}";
 			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}  {d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -312,28 +312,28 @@ namespace Terminal.Gui.Views {
 
 
 			// Default - Center
 			// Default - Center
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			buttonRow = $"{d.VLine}{btn1}    {btn2}     {btn3}     {btn4}{d.VLine}";
 			buttonRow = $"{d.VLine}{btn1}    {btn2}     {btn3}     {btn4}{d.VLine}";
 			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
 			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			buttonRow = $"{d.VLine}           {btn1} {btn2} {btn3} {btn4}{d.VLine}";
 			buttonRow = $"{d.VLine}           {btn1} {btn2} {btn3} {btn4}{d.VLine}";
 			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
 			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}           {d.VLine}";
 			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}           {d.VLine}";
 			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
 			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -367,28 +367,28 @@ namespace Terminal.Gui.Views {
 
 
 			// Default - Center
 			// Default - Center
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			buttonRow = $"{d.VLine}{btn1}    {btn2}     {btn3}     {btn4}{d.VLine}";
 			buttonRow = $"{d.VLine}{btn1}    {btn2}     {btn3}     {btn4}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			buttonRow = $"{d.VLine}           {btn1} {btn2} {btn3} {btn4}{d.VLine}";
 			buttonRow = $"{d.VLine}           {btn1} {btn2} {btn3} {btn4}{d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}           {d.VLine}";
 			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}           {d.VLine}";
 			Assert.Equal (width, buttonRow.Length);
 			Assert.Equal (width, buttonRow.Length);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -409,7 +409,7 @@ namespace Terminal.Gui.Views {
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
@@ -432,7 +432,7 @@ namespace Terminal.Gui.Views {
 			d.SetBufferSize (buttonRow.Length, 3);
 			d.SetBufferSize (buttonRow.Length, 3);
 
 
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
 			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -461,56 +461,56 @@ namespace Terminal.Gui.Views {
 			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
 			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
 			runstate = Application.Begin (dlg);
 			runstate = Application.Begin (dlg);
 			var buttonRow = $"{d.VLine}    {btn1}     {d.VLine}";
 			var buttonRow = $"{d.VLine}    {btn1}     {d.VLine}";
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			// Now add a second button
 			// Now add a second button
 			buttonRow = $"{d.VLine} {btn1} {btn2} {d.VLine}";
 			buttonRow = $"{d.VLine} {btn1} {btn2} {d.VLine}";
 			dlg.AddButton (new Button (btn2Text));
 			dlg.AddButton (new Button (btn2Text));
 			bool first = false;
 			bool first = false;
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Justify
 			// Justify
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Justify };
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Justify };
 			runstate = Application.Begin (dlg);
 			runstate = Application.Begin (dlg);
 			buttonRow = $"{d.VLine}         {btn1}{d.VLine}";
 			buttonRow = $"{d.VLine}         {btn1}{d.VLine}";
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			// Now add a second button
 			// Now add a second button
 			buttonRow = $"{d.VLine}{btn1}   {btn2}{d.VLine}";
 			buttonRow = $"{d.VLine}{btn1}   {btn2}{d.VLine}";
 			dlg.AddButton (new Button (btn2Text));
 			dlg.AddButton (new Button (btn2Text));
 			first = false;
 			first = false;
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Right
 			// Right
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
 			runstate = Application.Begin (dlg);
 			runstate = Application.Begin (dlg);
 			buttonRow = $"{d.VLine}{new String (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
 			buttonRow = $"{d.VLine}{new String (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			// Now add a second button
 			// Now add a second button
 			buttonRow = $"{d.VLine}  {btn1} {btn2}{d.VLine}";
 			buttonRow = $"{d.VLine}  {btn1} {btn2}{d.VLine}";
 			dlg.AddButton (new Button (btn2Text));
 			dlg.AddButton (new Button (btn2Text));
 			first = false;
 			first = false;
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 
 
 			// Left
 			// Left
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
 			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
 			runstate = Application.Begin (dlg);
 			runstate = Application.Begin (dlg);
 			buttonRow = $"{d.VLine}{btn1}{new String (' ', width - btn1.Length - 2)}{d.VLine}";
 			buttonRow = $"{d.VLine}{btn1}{new String (' ', width - btn1.Length - 2)}{d.VLine}";
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 
 
 			// Now add a second button
 			// Now add a second button
 			buttonRow = $"{d.VLine}{btn1} {btn2}  {d.VLine}";
 			buttonRow = $"{d.VLine}{btn1} {btn2}  {d.VLine}";
 			dlg.AddButton (new Button (btn2Text));
 			dlg.AddButton (new Button (btn2Text));
 			first = false;
 			first = false;
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
 			Application.RunMainLoopIteration (ref runstate, true, ref first);
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 	}
 	}

+ 2 - 2
UnitTests/DimTests.cs

@@ -1002,7 +1002,7 @@ namespace Terminal.Gui.Core {
 			field.KeyDown += (k) => {
 			field.KeyDown += (k) => {
 				if (k.KeyEvent.Key == Key.Enter) {
 				if (k.KeyEvent.Key == Key.Enter) {
 					((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
 					((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
-					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expecteds [count], output);
+					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expecteds [count], output);
 					Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
 					Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
 
 
 					if (count < 20) {
 					if (count < 20) {
@@ -1148,7 +1148,7 @@ namespace Terminal.Gui.Core {
 			field.KeyDown += (k) => {
 			field.KeyDown += (k) => {
 				if (k.KeyEvent.Key == Key.Enter) {
 				if (k.KeyEvent.Key == Key.Enter) {
 					((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
 					((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
-					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expecteds [count], output);
+					var pos = TestHelpers.AssertDriverContentsWithFrameAre (expecteds [count], output);
 					Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
 					Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
 
 
 					if (count > 0) {
 					if (count > 0) {

+ 18 - 204
UnitTests/GraphViewTests.cs

@@ -93,192 +93,6 @@ namespace Terminal.Gui.Views {
 			return gv;
 			return gv;
 		}
 		}
 
 
-#pragma warning disable xUnit1013 // Public method should be marked as test
-		public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output)
-		{
-#pragma warning restore xUnit1013 // Public method should be marked as test
-
-			var sb = new StringBuilder ();
-			var driver = ((FakeDriver)Application.Driver);
-
-			var contents = driver.Contents;
-
-			for (int r = 0; r < driver.Rows; r++) {
-				for (int c = 0; c < driver.Cols; c++) {
-					sb.Append ((char)contents [r, c, 0]);
-				}
-				sb.AppendLine ();
-			}
-
-			var actualLook = sb.ToString ();
-
-			if (!string.Equals (expectedLook, actualLook)) {
-
-				// ignore trailing whitespace on each line
-				var trailingWhitespace = new Regex (@"\s+$", RegexOptions.Multiline);
-
-				// get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string)
-				expectedLook = trailingWhitespace.Replace (expectedLook, "").Trim ();
-				actualLook = trailingWhitespace.Replace (actualLook, "").Trim ();
-
-				// standardize line endings for the comparison
-				expectedLook = expectedLook.Replace ("\r\n", "\n");
-				actualLook = actualLook.Replace ("\r\n", "\n");
-
-				output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
-				output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
-
-				Assert.Equal (expectedLook, actualLook);
-			}
-		}
-
-		public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output)
-		{
-			var lines = new List<List<char>> ();
-			var sb = new StringBuilder ();
-			var driver = ((FakeDriver)Application.Driver);
-			var x = -1;
-			var y = -1;
-			int w = -1;
-			int h = -1;
-
-			var contents = driver.Contents;
-
-			for (int r = 0; r < driver.Rows; r++) {
-				var runes = new List<char> ();
-				for (int c = 0; c < driver.Cols; c++) {
-					var rune = (char)contents [r, c, 0];
-					if (rune != ' ') {
-						if (x == -1) {
-							x = c;
-							y = r;
-							for (int i = 0; i < c; i++) {
-								runes.InsertRange (i, new List<char> () { ' ' });
-							}
-						}
-						if (Rune.ColumnWidth (rune) > 1) {
-							c++;
-						}
-						if (c + 1 > w) {
-							w = c + 1;
-						}
-						h = r - y + 1;
-					}
-					if (x > -1) {
-						runes.Add (rune);
-					}
-				}
-				if (runes.Count > 0) {
-					lines.Add (runes);
-				}
-			}
-
-			// Remove unnecessary empty lines
-			if (lines.Count > 0) {
-				for (int r = lines.Count - 1; r > h - 1; r--) {
-					lines.RemoveAt (r);
-				}
-			}
-
-			// Remove trailing whitespace on each line
-			for (int r = 0; r < lines.Count; r++) {
-				List<char> row = lines [r];
-				for (int c = row.Count - 1; c >= 0; c--) {
-					var rune = row [c];
-					if (rune != ' ' || (row.Sum (x => Rune.ColumnWidth (x)) == w)) {
-						break;
-					}
-					row.RemoveAt (c);
-				}
-			}
-
-			// Convert char list to string
-			for (int r = 0; r < lines.Count; r++) {
-				var line = new string (lines [r].ToArray ());
-				if (r == lines.Count - 1) {
-					sb.Append (line);
-				} else {
-					sb.AppendLine (line);
-				}
-			}
-
-			var actualLook = sb.ToString ();
-
-			if (!string.Equals (expectedLook, actualLook)) {
-
-				// standardize line endings for the comparison
-				expectedLook = expectedLook.Replace ("\r\n", "\n");
-				actualLook = actualLook.Replace ("\r\n", "\n");
-
-				// Remove the first and the last line ending from the expectedLook
-				if (expectedLook.StartsWith ("\n")) {
-					expectedLook = expectedLook [1..];
-				}
-				if (expectedLook.EndsWith ("\n")) {
-					expectedLook = expectedLook [..^1];
-				}
-
-				output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
-				output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
-
-				Assert.Equal (expectedLook, actualLook);
-			}
-			return new Rect (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
-		}
-
-#pragma warning disable xUnit1013 // Public method should be marked as test
-		/// <summary>
-		/// Verifies the console was rendered using the given <paramref name="expectedColors"/> at the given locations.
-		/// Pass a bitmap of indexes into <paramref name="expectedColors"/> as <paramref name="expectedLook"/> and the
-		/// test method will verify those colors were used in the row/col of the console during rendering
-		/// </summary>
-		/// <param name="expectedLook">Numbers between 0 and 9 for each row/col of the console.  Must be valid indexes of <paramref name="expectedColors"/></param>
-		/// <param name="expectedColors"></param>
-		public static void AssertDriverColorsAre (string expectedLook, Attribute [] expectedColors)
-		{
-#pragma warning restore xUnit1013 // Public method should be marked as test
-
-			if (expectedColors.Length > 10) {
-				throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
-			}
-
-			expectedLook = expectedLook.Trim ();
-			var driver = ((FakeDriver)Application.Driver);
-
-			var contents = driver.Contents;
-
-			int r = 0;
-			foreach (var line in expectedLook.Split ('\n').Select (l => l.Trim ())) {
-
-				for (int c = 0; c < line.Length; c++) {
-
-					int val = contents [r, c, 1];
-
-					var match = expectedColors.Where (e => e.Value == val).ToList ();
-					if (match.Count == 0) {
-						throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value))})");
-					} else if (match.Count > 1) {
-						throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
-					}
-
-					var colorUsed = Array.IndexOf (expectedColors, match [0]).ToString () [0];
-					var userExpected = line [c];
-
-					if (colorUsed != userExpected) {
-						throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {DescribeColor (colorUsed)} but test expected {DescribeColor (userExpected)} (these are indexes into the expectedColors array)");
-					}
-				}
-
-				r++;
-			}
-		}
-
-		private static object DescribeColor (int userExpected)
-		{
-			var a = new Attribute (userExpected);
-			return $"{a.Foreground},{a.Background}";
-		}
-
 		#region Screen to Graph Tests
 		#region Screen to Graph Tests
 
 
 		[Fact]
 		[Fact]
@@ -833,7 +647,7 @@ namespace Terminal.Gui.Views {
  │  MM  MM  MM
  │  MM  MM  MM
  ┼──┬M──┬M──┬M──────
  ┼──┬M──┬M──┬M──────
    heytherebob  ";
    heytherebob  ";
-			GraphViewTests.AssertDriverContentsAre (looksLike, output);
+			TestHelpers.AssertDriverContentsAre (looksLike, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -1253,7 +1067,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// user scrolls up one unit of graph space
 			// user scrolls up one unit of graph space
 			gv.ScrollOffset = new PointF (0, 1f);
 			gv.ScrollOffset = new PointF (0, 1f);
@@ -1270,7 +1084,7 @@ namespace Terminal.Gui.Views {
 1┼┬┬┬┬┬┬┬┬
 1┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -1297,7 +1111,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// user scrolls up one unit of graph space
 			// user scrolls up one unit of graph space
 			gv.ScrollOffset = new PointF (0, 1f);
 			gv.ScrollOffset = new PointF (0, 1f);
@@ -1315,7 +1129,7 @@ namespace Terminal.Gui.Views {
 1┼┬┬┬┬┬┬┬┬
 1┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -1345,7 +1159,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1374,7 +1188,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -1409,7 +1223,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1444,7 +1258,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1473,7 +1287,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1515,7 +1329,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1554,7 +1368,7 @@ namespace Terminal.Gui.Views {
  0    5   
  0    5   
           
           
           ";
           ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1595,7 +1409,7 @@ namespace Terminal.Gui.Views {
    0    5
    0    5
          
          
           ";
           ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1622,7 +1436,7 @@ namespace Terminal.Gui.Views {
          
          
          
          
           ";
           ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1647,7 +1461,7 @@ namespace Terminal.Gui.Views {
          
          
          
          
           ";
           ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1679,7 +1493,7 @@ namespace Terminal.Gui.Views {
 0┼┬┬┬┬┬┬┬┬
 0┼┬┬┬┬┬┬┬┬
  0    5";
  0    5";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1726,14 +1540,14 @@ namespace Terminal.Gui.Views {
 				mount.Redraw (mount.Bounds);
 				mount.Redraw (mount.Bounds);
 
 
 				// should have the initial text
 				// should have the initial text
-				GraphViewTests.AssertDriverContentsAre ("ff", null);
+				TestHelpers.AssertDriverContentsAre ("ff", null);
 
 
 				// change the text and redraw
 				// change the text and redraw
 				lbl1.Text = "ff1234";
 				lbl1.Text = "ff1234";
 				mount.Redraw (mount.Bounds);
 				mount.Redraw (mount.Bounds);
 
 
 				// should have the new text rendered
 				// should have the new text rendered
-				GraphViewTests.AssertDriverContentsAre ("ff1234", null);
+				TestHelpers.AssertDriverContentsAre ("ff1234", null);
 
 
 
 
 			} finally {
 			} finally {

+ 11 - 11
UnitTests/ListViewTests.cs

@@ -247,7 +247,7 @@ namespace Terminal.Gui.Views {
 			Application.Refresh ();
 			Application.Refresh ();
 
 
 			Assert.Equal (0, lv.SelectedItem);
 			Assert.Equal (0, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line0     │
 │Line0     │
 │Line1     │
 │Line1     │
@@ -264,7 +264,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.ScrollDown (10));
 			Assert.True (lv.ScrollDown (10));
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (0, lv.SelectedItem);
 			Assert.Equal (0, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line10    │
 │Line10    │
 │Line11    │
 │Line11    │
@@ -281,7 +281,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.MoveDown ());
 			Assert.True (lv.MoveDown ());
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (1, lv.SelectedItem);
 			Assert.Equal (1, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line1     │
 │Line1     │
 │Line2     │
 │Line2     │
@@ -298,7 +298,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.MoveEnd ());
 			Assert.True (lv.MoveEnd ());
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (19, lv.SelectedItem);
 			Assert.Equal (19, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line19    │
 │Line19    │
 │          │
 │          │
@@ -315,7 +315,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.ScrollUp (20));
 			Assert.True (lv.ScrollUp (20));
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (19, lv.SelectedItem);
 			Assert.Equal (19, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line0     │
 │Line0     │
 │Line1     │
 │Line1     │
@@ -332,7 +332,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.MoveDown ());
 			Assert.True (lv.MoveDown ());
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (19, lv.SelectedItem);
 			Assert.Equal (19, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line10    │
 │Line10    │
 │Line11    │
 │Line11    │
@@ -349,7 +349,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.ScrollUp (20));
 			Assert.True (lv.ScrollUp (20));
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (19, lv.SelectedItem);
 			Assert.Equal (19, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line0     │
 │Line0     │
 │Line1     │
 │Line1     │
@@ -366,7 +366,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.MoveDown ());
 			Assert.True (lv.MoveDown ());
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (19, lv.SelectedItem);
 			Assert.Equal (19, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line10    │
 │Line10    │
 │Line11    │
 │Line11    │
@@ -383,7 +383,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.MoveHome ());
 			Assert.True (lv.MoveHome ());
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (0, lv.SelectedItem);
 			Assert.Equal (0, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line0     │
 │Line0     │
 │Line1     │
 │Line1     │
@@ -400,7 +400,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.ScrollDown (20));
 			Assert.True (lv.ScrollDown (20));
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (0, lv.SelectedItem);
 			Assert.Equal (0, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line19    │
 │Line19    │
 │          │
 │          │
@@ -417,7 +417,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (lv.MoveUp ());
 			Assert.True (lv.MoveUp ());
 			lv.Redraw (lv.Bounds);
 			lv.Redraw (lv.Bounds);
 			Assert.Equal (0, lv.SelectedItem);
 			Assert.Equal (0, lv.SelectedItem);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────┐
 ┌──────────┐
 │Line0     │
 │Line0     │
 │Line1     │
 │Line1     │

+ 335 - 260
UnitTests/MenuTests.cs

@@ -1,7 +1,9 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using Xunit;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
+using static Terminal.Gui.Views.MenuTests;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class MenuTests {
 	public class MenuTests {
@@ -148,7 +150,7 @@ Edit
 │ Copy   Copies the selection. │
 │ Copy   Copies the selection. │
 └──────────────────────────────┘
 └──────────────────────────────┘
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			cancelClosing = true;
 			cancelClosing = true;
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
@@ -161,7 +163,7 @@ Edit
 │ Copy   Copies the selection. │
 │ Copy   Copies the selection. │
 └──────────────────────────────┘
 └──────────────────────────────┘
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			cancelClosing = false;
 			cancelClosing = false;
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
@@ -171,7 +173,7 @@ Edit
 			expected = @"
 			expected = @"
 Edit
 Edit
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			void New () => miAction = "New";
 			void New () => miAction = "New";
 			void Copy () => miAction = "Copy";
 			void Copy () => miAction = "Copy";
@@ -606,7 +608,7 @@ Edit
 └──────┘
 └──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 1, 8, 4), pos);
 			Assert.Equal (new Rect (0, 1, 8, 4), pos);
 		}
 		}
 
 
@@ -635,7 +637,7 @@ Edit
 ──────┘
 ──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 7, 4), pos);
 			Assert.Equal (new Rect (0, 0, 7, 4), pos);
 
 
 			menu.CloseAllMenus ();
 			menu.CloseAllMenus ();
@@ -649,7 +651,7 @@ Edit
 ──────┘
 ──────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 0, 7, 3), pos);
 			Assert.Equal (new Rect (1, 0, 7, 3), pos);
 
 
 			menu.CloseAllMenus ();
 			menu.CloseAllMenus ();
@@ -665,7 +667,7 @@ Edit
 └──────
 └──────
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 1, 7, 4), pos);
 			Assert.Equal (new Rect (0, 1, 7, 4), pos);
 
 
 			menu.CloseAllMenus ();
 			menu.CloseAllMenus ();
@@ -680,7 +682,7 @@ Edit
 │ Two  
 │ Two  
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 7, 3), pos);
 			Assert.Equal (new Rect (0, 0, 7, 3), pos);
 		}
 		}
 
 
@@ -705,16 +707,15 @@ Edit
 
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -722,13 +723,12 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers                
+ Numbers                 
 ┌────────┐               
 ┌────────┐               
 │ One    │               
 │ One    │               
 │ Two   ►│┌─────────────┐
 │ Two   ►│┌─────────────┐
@@ -737,13 +737,12 @@ Edit
           └─────────────┘
           └─────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 25, 7), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null)));
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -751,17 +750,15 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -785,11 +782,11 @@ Edit
 
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () {
 			Assert.True (menu.MouseEvent (new MouseEvent () {
 				X = 1,
 				X = 1,
@@ -799,7 +796,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -807,8 +804,8 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
 				X = 1,
@@ -818,7 +815,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers                
+ Numbers                 
 ┌────────┐               
 ┌────────┐               
 │ One    │               
 │ One    │               
 │ Two   ►│┌─────────────┐
 │ Two   ►│┌─────────────┐
@@ -827,8 +824,8 @@ Edit
           └─────────────┘
           └─────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 25, 7), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 25, 7), pos);
 
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
 				X = 1,
@@ -838,7 +835,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -846,8 +843,8 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 70,
 				X = 70,
@@ -857,11 +854,11 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -887,16 +884,16 @@ Edit
 
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 
 
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -904,14 +901,14 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers      
+ Numbers       
 ┌─────────────┐
 ┌─────────────┐
 │◄    Two     │
 │◄    Two     │
 ├─────────────┤
 ├─────────────┤
@@ -920,13 +917,13 @@ Edit
 └─────────────┘
 └─────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 15, 7), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 15, 7), pos);
 
 
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -934,17 +931,17 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -970,11 +967,11 @@ Edit
 
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () {
 			Assert.True (menu.MouseEvent (new MouseEvent () {
 				X = 1,
 				X = 1,
@@ -984,7 +981,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -992,8 +989,8 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
 				X = 1,
@@ -1003,7 +1000,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers      
+ Numbers       
 ┌─────────────┐
 ┌─────────────┐
 │◄    Two     │
 │◄    Two     │
 ├─────────────┤
 ├─────────────┤
@@ -1012,8 +1009,8 @@ Edit
 └─────────────┘
 └─────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 15, 7), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 15, 7), pos);
 
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
 				X = 1,
@@ -1023,7 +1020,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -1031,8 +1028,8 @@ Edit
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 70,
 				X = 70,
@@ -1042,11 +1039,11 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+ Numbers
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -1074,11 +1071,11 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  File   Edit
+ File  Edit
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 11, 1), pos);
 
 
 			Assert.True (menu.ProcessKey (new (Key.N, null)));
 			Assert.True (menu.ProcessKey (new (Key.N, null)));
 			Application.MainLoop.MainIteration ();
 			Application.MainLoop.MainIteration ();
@@ -1088,11 +1085,11 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  File   Edit
+ File  Edit
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (1, 0, 11, 1), pos);
 
 
 			Assert.True (menu.ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.ProcessKey (new (Key.C, null)));
 			Assert.True (menu.ProcessKey (new (Key.C, null)));
@@ -1100,21 +1097,150 @@ Edit
 			Assert.True (copyAction);
 			Assert.True (copyAction);
 		}
 		}
 
 
+		// Defines the expected strings for a Menu. Currently supports 
+		//   - MenuBar with any number of MenuItems 
+		//   - Each top-level MenuItem can have a SINGLE sub-menu
+		//
+		// TODO: Enable multiple sub-menus
+		// TODO: Enable checked sub-menus
+		// TODO: Enable sub-menus with sub-menus (perhaps better to put this in a separate class with focused unit tests?)
+		//
+		// E.g: 
+		//
+		// File  Edit
+		//  New    Copy
+		public class ExpectedMenuBar : MenuBar {
+			FakeDriver d = ((FakeDriver)Application.Driver);
+
+			// Each MenuBar title has a 1 space pad on each side
+			// See `static int leftPadding` and `static int rightPadding` on line 1037 of Menu.cs
+			public string MenuBarText {
+				get {
+					string txt = string.Empty;
+					foreach (var m in Menus) {
+
+						txt += " " + m.Title.ToString () + " ";
+					}
+					return txt;
+				}
+			}
+
+			// The expected strings when the menu is closed
+			public string ClosedMenuText => MenuBarText + "\n";
+
+			// Padding for the X of the sub menu Frane
+			// Menu.cs - Line 1239 in `internal void OpenMenu` is where the Menu is created
+			string padding (int i)
+			{
+				int n = 0;
+				while (i > 0){
+					n += Menus [i-1].TitleLength + 2;
+					i--;
+				}
+				return new string (' ', n);
+			}
+
+			// Define expected menu frame
+			// "┌──────┐"
+			// "│ New  │"
+			// "└──────┘"
+			// 
+			// The width of the Frame is determined in Menu.cs line 144, where `Width` is calculated
+			//   1 space before the Title and 2 spaces after the Title/Check/Help
+			public string expectedTopRow (int i) => $"{d.ULCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.URCorner}  \n";
+			// The 3 spaces at end are a result of Menu.cs line 1062 where `pos` is calculated (` + spacesAfterTitle`)
+			public string expectedMenuItemRow (int i) => $"{d.VLine} {Menus [i].Children [0].Title}  {d.VLine}   \n";
+			public string expectedBottomRow (int i) => $"{d.LLCorner}{new String (d.HLine.ToString () [0], Menus [i].Children [0].TitleLength + 3)}{d.LRCorner}  \n";
+
+			// The fulll expected string for an open sub menu
+			public string expectedSubMenuOpen (int i) => ClosedMenuText + 
+				(Menus [i].Children.Length > 0 ?
+					padding (i) + expectedTopRow (i) +
+					padding (i) + expectedMenuItemRow (i) +
+					padding (i) + expectedBottomRow (i) 
+				: 
+				"");
+
+			public ExpectedMenuBar (MenuBarItem [] menus) : base (menus)
+			{
+			}
+		}
+
+		[Fact, AutoInitShutdown]
+		public void MenuBar_Submenus_Alignment_Correct ()
+		{
+			// Define the expected menu
+			var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("Really Long Sub Menu", "",  null)
+				}),
+				new MenuBarItem ("123", new MenuItem [] {
+					new MenuItem ("Copy", "", null)
+				}),
+				new MenuBarItem ("Format", new MenuItem [] {
+					new MenuItem ("Word Wrap", "", null)
+				}),
+				new MenuBarItem ("Help", new MenuItem [] {
+					new MenuItem ("About", "", null)
+				}),
+				new MenuBarItem ("1", new MenuItem [] {
+					new MenuItem ("2", "", null)
+				}),
+				new MenuBarItem ("3", new MenuItem [] {
+					new MenuItem ("2", "", null)
+				}),
+				new MenuBarItem ("Last one", new MenuItem [] {
+					new MenuItem ("Test", "", null)
+				})
+			});
+
+			MenuBarItem [] items = new MenuBarItem [expectedMenu.Menus.Length];
+			for (var i = 0; i < expectedMenu.Menus.Length; i++) {
+				items [i] = new MenuBarItem (expectedMenu.Menus [i].Title, new MenuItem [] {
+					new MenuItem (expectedMenu.Menus [i].Children [0].Title, "", null)
+				});
+			}
+			var menu = new MenuBar (items);
+
+			Application.Top.Add (menu);
+
+			Application.Top.Redraw (Application.Top.Bounds);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
+
+			for (var i = 0; i < expectedMenu.Menus.Length; i++) {
+				menu.OpenMenu (i);
+				Assert.True (menu.IsMenuOpen);
+				Application.Top.Redraw (Application.Top.Bounds);
+				TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (i), output);
+			}
+		}
+
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey ()
 		public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey ()
 		{
 		{
 			var newAction = false;
 			var newAction = false;
 			var copyAction = false;
 			var copyAction = false;
 
 
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_New", "", () => newAction = true)
+			// Define the expected menu
+			var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("New", "",  null)
 				}),
 				}),
-				new MenuBarItem ("_Edit", new MenuItem [] {
-					new MenuItem ("_Copy", "", () => copyAction = true)
+				new MenuBarItem ("Edit", new MenuItem [] {
+					new MenuItem ("Copy", "", null)
 				})
 				})
 			});
 			});
 
 
+			// The real menu
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_" + expectedMenu.Menus[0].Title, new MenuItem [] {
+					new MenuItem ("_" + expectedMenu.Menus[0].Children[0].Title, "",  () => newAction = true)
+				}),
+				new MenuBarItem ("_" + expectedMenu.Menus[1].Title, new MenuItem [] {
+					new MenuItem ("_" + expectedMenu.Menus[1].Children[0].Title, "",  () => copyAction = true)
+				}),
+			});
+
 			Application.Top.Add (menu);
 			Application.Top.Add (menu);
 
 
 			Assert.False (newAction);
 			Assert.False (newAction);
@@ -1123,15 +1249,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.F, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.F, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			var expected = @"
-  File   Edit
-┌──────┐     
-│ New  │     
-└──────┘     
-";
-
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null)));
 			Application.MainLoop.MainIteration ();
 			Application.MainLoop.MainIteration ();
@@ -1140,15 +1258,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.E, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.E, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   
-       ┌───────┐
-       │ Copy  │
-       └───────┘
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 16, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.C, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.C, null)));
 			Application.MainLoop.MainIteration ();
 			Application.MainLoop.MainIteration ();
@@ -1158,127 +1268,114 @@ Edit
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys ()
 		public void MenuBar_Position_And_Size_With_HotKeys_Is_The_Same_As_Without_HotKeys ()
 		{
 		{
-			// With HotKeys
+			// Define the expected menu
+			var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("12", "",  null)
+				}),
+				new MenuBarItem ("Edit", new MenuItem [] {
+					new MenuItem ("Copy", "", null)
+				})
+			});
+
+			// Test without HotKeys first
 			var menu = new MenuBar (new MenuBarItem [] {
 			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_New", "", null)
+				new MenuBarItem (expectedMenu.Menus[0].Title, new MenuItem [] {
+					new MenuItem (expectedMenu.Menus[0].Children[0].Title, "", null)
 				}),
 				}),
-				new MenuBarItem ("_Edit", new MenuItem [] {
-					new MenuItem ("_Copy", "", null)
+				new MenuBarItem (expectedMenu.Menus[1].Title, new MenuItem [] {
+					new MenuItem (expectedMenu.Menus[1].Children[0].Title, "", null)
 				})
 				})
 			});
 			});
 
 
 			Application.Top.Add (menu);
 			Application.Top.Add (menu);
 
 
+			// Open first
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			var expected = @"
-  File   Edit
-┌──────┐     
-│ New  │     
-└──────┘     
-";
-
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
+			// Open second
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   
-       ┌───────┐
-       │ Copy  │
-       └───────┘
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 16, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 
+			// Close menu
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit
-";
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			Application.Top.Remove (menu);
 
 
-			// Without HotKeys
+			// Now test WITH HotKeys
 			menu = new MenuBar (new MenuBarItem [] {
 			menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("File", new MenuItem [] {
-					new MenuItem ("New", "", null)
+				new MenuBarItem ("_" + expectedMenu.Menus[0].Title, new MenuItem [] {
+					new MenuItem ("_" + expectedMenu.Menus[0].Children[0].Title, "",  null)
+				}),
+				new MenuBarItem ("_" + expectedMenu.Menus[1].Title, new MenuItem [] {
+					new MenuItem ("_" + expectedMenu.Menus[1].Children[0].Title, "",  null)
 				}),
 				}),
-				new MenuBarItem ("Edit", new MenuItem [] {
-					new MenuItem ("Copy", "", null)
-				})
 			});
 			});
 
 
+			Application.Top.Add (menu);
+
+			// Open first
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit
-┌──────┐     
-│ New  │     
-└──────┘     
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
+			// Open second
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   
-       ┌───────┐
-       │ Copy  │
-       └───────┘
-";
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 16, 4), pos);
+			// Close menu
+			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Application.Top.Redraw (Application.Top.Bounds);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void MenuBar_ButtonPressed_Open_The_Menu_ButtonPressed_Again_Close_The_Menu ()
 		public void MenuBar_ButtonPressed_Open_The_Menu_ButtonPressed_Again_Close_The_Menu ()
 		{
 		{
-			var menu = new MenuBar (new MenuBarItem [] {
+			// Define the expected menu
+			var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] {
 				new MenuBarItem ("File", new MenuItem [] {
 				new MenuBarItem ("File", new MenuItem [] {
-					new MenuItem ("New", "", null)
+					new MenuItem ("Open", "",  null)
 				}),
 				}),
 				new MenuBarItem ("Edit", new MenuItem [] {
 				new MenuBarItem ("Edit", new MenuItem [] {
 					new MenuItem ("Copy", "", null)
 					new MenuItem ("Copy", "", null)
 				})
 				})
 			});
 			});
 
 
+			// Test without HotKeys first
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_" + expectedMenu.Menus[0].Title, new MenuItem [] {
+					new MenuItem ("_" + expectedMenu.Menus[0].Children[0].Title, "",  null)
+				}),
+				new MenuBarItem ("_" + expectedMenu.Menus[1].Title, new MenuItem [] {
+					new MenuItem ("_" + expectedMenu.Menus[1].Children[0].Title, "",  null)
+				}),
+			});
+
 			Application.Top.Add (menu);
 			Application.Top.Add (menu);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			var expected = @"
-  File   Edit
-┌──────┐     
-│ New  │     
-└──────┘     
-";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -1300,110 +1397,98 @@ Edit
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse ()
 		public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse ()
 		{
 		{
-			var menu = new MenuBar (new MenuBarItem [] {
+			// File  Edit  Format
+			//┌──────┐    ┌───────┐         
+			//│ New  │    │ Wrap  │         
+			//└──────┘    └───────┘         
+
+			// Define the expected menu
+			var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] {
 				new MenuBarItem ("File", new MenuItem [] {
 				new MenuBarItem ("File", new MenuItem [] {
-					new MenuItem ("New", "", null)
-				}),
-				new MenuBarItem ("Edit", new MenuItem [] {
+					new MenuItem ("New", "",  null)
 				}),
 				}),
+				new MenuBarItem ("Edit", new MenuItem [] {}),
 				new MenuBarItem ("Format", new MenuItem [] {
 				new MenuBarItem ("Format", new MenuItem [] {
 					new MenuItem ("Wrap", "", null)
 					new MenuItem ("Wrap", "", null)
 				})
 				})
 			});
 			});
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem (expectedMenu.Menus[0].Title, new MenuItem [] {
+					new MenuItem (expectedMenu.Menus[0].Children[0].Title, "", null)
+				}),
+				new MenuBarItem (expectedMenu.Menus[1].Title, new MenuItem [] {}),
+				new MenuBarItem (expectedMenu.Menus[2].Title, new MenuItem [] {
+					new MenuItem (expectedMenu.Menus[2].Children[0].Title, "", null)
+				})
+			});
+
 			var tf = new TextField () { Y = 2, Width = 10 };
 			var tf = new TextField () { Y = 2, Width = 10 };
 			Application.Top.Add (menu, tf);
 			Application.Top.Add (menu, tf);
-
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
+
 			Assert.True (tf.HasFocus);
 			Assert.True (tf.HasFocus);
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			var expected = @"
-  File   Edit   Format
-┌──────┐              
-│ New  │              
-└──────┘              
-";
-
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 15, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 15, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format 
-              ┌───────┐
-              │ Wrap  │
-              └───────┘
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 23, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (2), output);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-┌──────┐              
-│ New  │              
-└──────┘              
-";
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 4), pos);
 
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.IsMenuOpen);
 			Assert.True (tf.HasFocus);
 			Assert.True (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Keyboard ()
 		public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Keyboard ()
 		{
 		{
-			var menu = new MenuBar (new MenuBarItem [] {
+			var expectedMenu = new ExpectedMenuBar (new MenuBarItem [] {
 				new MenuBarItem ("File", new MenuItem [] {
 				new MenuBarItem ("File", new MenuItem [] {
 					new MenuItem ("New", "", null)
 					new MenuItem ("New", "", null)
 				}),
 				}),
-				new MenuBarItem ("Edit", new MenuItem [] {
-				}),
+				new MenuBarItem ("Edit", Array.Empty<MenuItem> ()),
 				new MenuBarItem ("Format", new MenuItem [] {
 				new MenuBarItem ("Format", new MenuItem [] {
 					new MenuItem ("Wrap", "", null)
 					new MenuItem ("Wrap", "", null)
 				})
 				})
 			});
 			});
+
+			MenuBarItem [] items = new MenuBarItem [expectedMenu.Menus.Length];
+			for (var i = 0; i < expectedMenu.Menus.Length; i++) {
+				items [i] = new MenuBarItem (expectedMenu.Menus [i].Title, expectedMenu.Menus [i].Children.Length > 0 
+					? new MenuItem [] {
+						new MenuItem (expectedMenu.Menus [i].Children [0].Title, "", null),
+					} 
+					: Array.Empty<MenuItem> ());
+			}
+			var menu = new MenuBar (items);
+			
 			var tf = new TextField () { Y = 2, Width = 10 };
 			var tf = new TextField () { Y = 2, Width = 10 };
 			Application.Top.Add (menu, tf);
 			Application.Top.Add (menu, tf);
 
 
@@ -1413,76 +1498,66 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			var expected = @"
-  File   Edit   Format
-┌──────┐              
-│ New  │              
-└──────┘              
-";
-
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen(0), output);
 
 
+			// Right - Edit has no sub menu; this tests that no sub menu shows
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 
+			// Right - Format
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format 
-              ┌───────┐
-              │ Wrap  │
-              └───────┘
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 23, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (2), output);
 
 
+			// Left - Edit
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-┌──────┐              
-│ New  │              
-└──────┘              
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+			TestHelpers.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.IsMenuOpen);
 			Assert.True (tf.HasFocus);
 			Assert.True (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
+			TestHelpers.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
+		}
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+		[Fact, AutoInitShutdown]
+		public void Key_Open_And_Close_The_MenuBar ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("New", "", null)
+				})
+			});
+			Application.Top.Add (menu);
+			Application.Begin (Application.Top);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+
+			menu.Key = Key.F10 | Key.ShiftMask;
+			Assert.False (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F10 | Key.ShiftMask, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
 		}
 		}
 	}
 	}
 }
 }

+ 6 - 6
UnitTests/MessageBoxTests.cs

@@ -29,7 +29,7 @@ namespace Terminal.Gui.Views {
 
 
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Top.Redraw (Application.Top.Bounds);
 					Application.Top.Redraw (Application.Top.Bounds);
-					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
                ┌ Title ─────────────────────────────────────────┐
                ┌ Title ─────────────────────────────────────────┐
                │                    Message                     │
                │                    Message                     │
                │                                                │
                │                                                │
@@ -71,7 +71,7 @@ namespace Terminal.Gui.Views {
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Top.Redraw (Application.Top.Bounds);
 					Application.Top.Redraw (Application.Top.Bounds);
-					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
          ┌ About UI Catalog ──────────────────────────────────────────┐
          ┌ About UI Catalog ──────────────────────────────────────────┐
          │             A comprehensive sample library for             │
          │             A comprehensive sample library for             │
          │                                                            │
          │                                                            │
@@ -110,7 +110,7 @@ namespace Terminal.Gui.Views {
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Top.Redraw (Application.Top.Bounds);
 					Application.Top.Redraw (Application.Top.Bounds);
-					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
                                     ┌─────┐
                                     ┌─────┐
                                     │Messa│
                                     │Messa│
                                     │ ge  │
                                     │ ge  │
@@ -140,7 +140,7 @@ namespace Terminal.Gui.Views {
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Top.Redraw (Application.Top.Bounds);
 					Application.Top.Redraw (Application.Top.Bounds);
-					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
                                   ┌ Title ──┐
                                   ┌ Title ──┐
                                   │ Message │
                                   │ Message │
                                   │         │
                                   │         │
@@ -170,7 +170,7 @@ namespace Terminal.Gui.Views {
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Top.Redraw (Application.Top.Bounds);
 					Application.Top.Redraw (Application.Top.Bounds);
-					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌ mywindow ────────────────────────────────────────────────────────────────────┐
 ┌ mywindow ────────────────────────────────────────────────────────────────────┐
 │ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
 │ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
 │ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
 │ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
@@ -224,7 +224,7 @@ namespace Terminal.Gui.Views {
 					Application.RequestStop ();
 					Application.RequestStop ();
 				} else if (iterations == 1) {
 				} else if (iterations == 1) {
 					Application.Top.Redraw (Application.Top.Bounds);
 					Application.Top.Redraw (Application.Top.Bounds);
-					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+					TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌ mywindow ────────────────────────────────────────────────────────────────────┐
 ┌ mywindow ────────────────────────────────────────────────────────────────────┐
 │ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │
 │ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │
 │ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │
 │ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff │

+ 2 - 2
UnitTests/PanelViewTests.cs

@@ -405,7 +405,7 @@ namespace Terminal.Gui.Views {
 └──────────────────────────────────────────────────────────────────────────────┘
 └──────────────────────────────────────────────────────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 80, 25), pos);
 			Assert.Equal (new Rect (0, 0, 80, 25), pos);
 		}
 		}
 
 
@@ -472,7 +472,7 @@ namespace Terminal.Gui.Views {
 └──────────────────────────────────────────────────────────────────────────────┘
 └──────────────────────────────────────────────────────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 80, 25), pos);
 			Assert.Equal (new Rect (0, 0, 80, 25), pos);
 		}
 		}
 	}
 	}

+ 6 - 6
UnitTests/PosTests.cs

@@ -164,7 +164,7 @@ namespace Terminal.Gui.Core {
 └──────────────────────────────────────┘
 └──────────────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -211,7 +211,7 @@ namespace Terminal.Gui.Core {
 └──────────────────────────────────────┘
 └──────────────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -247,7 +247,7 @@ namespace Terminal.Gui.Core {
 				win.Frame.Right, win.Frame.Bottom));
 				win.Frame.Right, win.Frame.Bottom));
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			var expected = @"
 			var expected = @"
-  Menu                                                                          
+ Menu                                                                           
 ┌──────────────────────────────────────────────────────────────────────────────┐
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │                                                                              │
 │                                                                              │
 │                                                                              │
 │                                                                              │
@@ -274,7 +274,7 @@ namespace Terminal.Gui.Core {
  F1 Help                                                                        
  F1 Help                                                                        
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -310,7 +310,7 @@ namespace Terminal.Gui.Core {
 				win.Frame.Right, win.Frame.Bottom));
 				win.Frame.Right, win.Frame.Bottom));
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			var expected = @"
 			var expected = @"
-  Menu                                                                          
+ Menu                                                                           
 ┌──────────────────────────────────────────────────────────────────────────────┐
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │                                                                              │
 │                                                                              │
 │                                                                              │
 │                                                                              │
@@ -337,7 +337,7 @@ namespace Terminal.Gui.Core {
  F1 Help                                                                        
  F1 Help                                                                        
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]

+ 3 - 3
UnitTests/RadioGroupTests.cs

@@ -99,7 +99,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			rg.DisplayMode = DisplayModeLayout.Horizontal;
 			rg.DisplayMode = DisplayModeLayout.Horizontal;
@@ -120,7 +120,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 
 			rg.HorizontalSpace = 4;
 			rg.HorizontalSpace = 4;
@@ -140,7 +140,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
 
 

+ 12 - 14
UnitTests/ScenarioTests.cs

@@ -11,7 +11,7 @@ using Xunit.Abstractions;
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
-namespace Terminal.Gui {
+namespace UICatalog {
 	public class ScenarioTests {
 	public class ScenarioTests {
 		readonly ITestOutputHelper output;
 		readonly ITestOutputHelper output;
 
 
@@ -49,19 +49,18 @@ namespace Terminal.Gui {
 		[Fact]
 		[Fact]
 		public void Run_All_Scenarios ()
 		public void Run_All_Scenarios ()
 		{
 		{
-			List<Type> scenarioClasses = Scenario.GetDerivedClasses<Scenario> ();
-			Assert.NotEmpty (scenarioClasses);
+			List<Scenario> scenarios = Scenario.GetScenarios ();
+			Assert.NotEmpty (scenarios);
 
 
-			foreach (var scenarioClass in scenarioClasses) {
+			foreach (var scenario in scenarios) {
 
 
-				output.WriteLine ($"Running Scenario '{scenarioClass.Name}'");
+				output.WriteLine ($"Running Scenario '{scenario}'");
 
 
 				Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
 				Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
 					Application.RequestStop ();
 					Application.RequestStop ();
 					return false;
 					return false;
 				};
 				};
 
 
-				var scenario = (Scenario)Activator.CreateInstance (scenarioClass);
 				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 
 				// Close after a short period of time
 				// Close after a short period of time
@@ -83,11 +82,11 @@ namespace Terminal.Gui {
 		[Fact]
 		[Fact]
 		public void Run_Generic ()
 		public void Run_Generic ()
 		{
 		{
-			List<Type> scenarioClasses = Scenario.GetDerivedClasses<Scenario> ();
-			Assert.NotEmpty (scenarioClasses);
+			List<Scenario> scenarios = Scenario.GetScenarios ();
+			Assert.NotEmpty (scenarios);
 
 
-			var item = scenarioClasses.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals ("Generic", StringComparison.OrdinalIgnoreCase));
-			var scenarioClass = scenarioClasses [item];
+			var item = scenarios.FindIndex (s => s.GetName ().Equals ("Generic", StringComparison.OrdinalIgnoreCase));
+			var generic = scenarios [item];
 			// Setup some fake keypresses 
 			// Setup some fake keypresses 
 			// Passing empty string will cause just a ctrl-q to be fired
 			// Passing empty string will cause just a ctrl-q to be fired
 			int stackSize = CreateInput ("");
 			int stackSize = CreateInput ("");
@@ -116,13 +115,12 @@ namespace Terminal.Gui {
 				Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
 				Assert.Equal (Key.CtrlMask | Key.Q, args.KeyEvent.Key);
 			};
 			};
 
 
-			var scenario = (Scenario)Activator.CreateInstance (scenarioClass);
-			scenario.Init (Application.Top, Colors.Base);
-			scenario.Setup ();
+			generic.Init (Application.Top, Colors.Base);
+			generic.Setup ();
 			// There is no need to call Application.Begin because Init already creates the Application.Top
 			// There is no need to call Application.Begin because Init already creates the Application.Top
 			// If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.
 			// If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.
 			//var rs = Application.Begin (Application.Top);
 			//var rs = Application.Begin (Application.Top);
-			scenario.Run ();
+			generic.Run ();
 
 
 			//Application.End (rs);
 			//Application.End (rs);
 
 

+ 10 - 10
UnitTests/ScrollBarViewTests.cs

@@ -737,7 +737,7 @@ namespace Terminal.Gui.Views {
 └───────────────────────────────────────────┘
 └───────────────────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 45, 20), pos);
 			Assert.Equal (new Rect (0, 0, 45, 20), pos);
 
 
 			textView.WordWrap = true;
 			textView.WordWrap = true;
@@ -774,7 +774,7 @@ namespace Terminal.Gui.Views {
 └────────────────────────┘
 └────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 26, 20), pos);
 			Assert.Equal (new Rect (0, 0, 26, 20), pos);
 
 
 			((FakeDriver)Application.Driver).SetBufferSize (10, 10);
 			((FakeDriver)Application.Driver).SetBufferSize (10, 10);
@@ -800,7 +800,7 @@ namespace Terminal.Gui.Views {
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 10), pos);
 			Assert.Equal (new Rect (0, 0, 10, 10), pos);
 		}
 		}
 
 
@@ -824,7 +824,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (sbv.OtherScrollBarView.ShowScrollIndicator);
 			Assert.True (sbv.OtherScrollBarView.ShowScrollIndicator);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.OtherScrollBarView.Visible);
 			Assert.True (sbv.OtherScrollBarView.Visible);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a tes▲
 This is a tes▲
 This is a tes┬
 This is a tes┬
 This is a tes┴
 This is a tes┴
@@ -842,7 +842,7 @@ This is a tes▼
 			Assert.False (sbv.Visible);
 			Assert.False (sbv.Visible);
 			Assert.False (sbv.OtherScrollBarView.Visible);
 			Assert.False (sbv.OtherScrollBarView.Visible);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a test
 This is a test
 This is a test
 This is a test
 This is a test
 This is a test
@@ -860,7 +860,7 @@ This is a test
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.OtherScrollBarView.Visible);
 			Assert.True (sbv.OtherScrollBarView.Visible);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a tes▲
 This is a tes▲
 This is a tes┬
 This is a tes┬
 This is a tes┴
 This is a tes┴
@@ -887,7 +887,7 @@ This is a tes▼
 			Assert.Null (sbv.OtherScrollBarView);
 			Assert.Null (sbv.OtherScrollBarView);
 			Assert.True (sbv.ShowScrollIndicator);
 			Assert.True (sbv.ShowScrollIndicator);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a tes▲
 This is a tes▲
 This is a tes┬
 This is a tes┬
 This is a tes┴
 This is a tes┴
@@ -901,7 +901,7 @@ This is a tes▼
 			Assert.False (sbv.ShowScrollIndicator);
 			Assert.False (sbv.ShowScrollIndicator);
 			Assert.False (sbv.Visible);
 			Assert.False (sbv.Visible);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a test
 This is a test
 This is a test
 This is a test
 This is a test
 This is a test
@@ -930,7 +930,7 @@ This is a test
 			Assert.Null (sbv.OtherScrollBarView);
 			Assert.Null (sbv.OtherScrollBarView);
 			Assert.False (sbv.ShowScrollIndicator);
 			Assert.False (sbv.ShowScrollIndicator);
 			Assert.False (sbv.Visible);
 			Assert.False (sbv.Visible);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a test[ Click Me! ]
 This is a test[ Click Me! ]
 This is a test             
 This is a test             
 This is a test             
 This is a test             
@@ -957,7 +957,7 @@ This is a test
 			Assert.False (sbv.ShowScrollIndicator);
 			Assert.False (sbv.ShowScrollIndicator);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a test[ Click Me! ]
 This is a test[ Click Me! ]
 This is a test             
 This is a test             
 This is a test             
 This is a test             

+ 108 - 1
UnitTests/ScrollViewTests.cs

@@ -4,9 +4,17 @@ using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class ScrollViewTests {
 	public class ScrollViewTests {
+		readonly ITestOutputHelper output;
+
+		public ScrollViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		[Fact]
 		public void Constructors_Defaults ()
 		public void Constructors_Defaults ()
 		{
 		{
@@ -173,5 +181,104 @@ namespace Terminal.Gui.Views {
 			Assert.False (sv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask, new KeyModifiers ())));
 			Assert.False (sv.ProcessKey (new KeyEvent (Key.End | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal (new Point (-39, -19), sv.ContentOffset);
 			Assert.Equal (new Point (-39, -19), sv.ContentOffset);
 		}
 		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoHideScrollBars_ShowHorizontalScrollIndicator_ShowVerticalScrollIndicator ()
+		{
+			var sv = new ScrollView {
+				Width = 10,
+				Height = 10
+			};
+
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			Assert.True (sv.AutoHideScrollBars);
+			Assert.False (sv.ShowHorizontalScrollIndicator);
+			Assert.False (sv.ShowVerticalScrollIndicator);
+			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
+
+			sv.AutoHideScrollBars = false;
+			sv.ShowHorizontalScrollIndicator = true;
+			sv.ShowVerticalScrollIndicator = true;
+			sv.Redraw (sv.Bounds);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+         ▲
+         ┬
+         │
+         │
+         │
+         │
+         │
+         ┴
+         ▼
+◄├─────┤► 
+", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentSize_AutoHideScrollBars_ShowHorizontalScrollIndicator_ShowVerticalScrollIndicator ()
+		{
+			var sv = new ScrollView {
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (50, 50)
+			};
+
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (50, sv.ContentSize.Width);
+			Assert.Equal (50, sv.ContentSize.Height);
+			Assert.True (sv.AutoHideScrollBars);
+			Assert.True (sv.ShowHorizontalScrollIndicator);
+			Assert.True (sv.ShowVerticalScrollIndicator);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+         ▲
+         ┬
+         ┴
+         ░
+         ░
+         ░
+         ░
+         ░
+         ▼
+◄├┤░░░░░► 
+", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ContentOffset_ContentSize_AutoHideScrollBars_ShowHorizontalScrollIndicator_ShowVerticalScrollIndicator ()
+		{
+			var sv = new ScrollView {
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (50, 50),
+				ContentOffset = new Point (25, 25)
+			};
+
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (-25, sv.ContentOffset.X);
+			Assert.Equal (-25, sv.ContentOffset.Y);
+			Assert.Equal (50, sv.ContentSize.Width);
+			Assert.Equal (50, sv.ContentSize.Height);
+			Assert.True (sv.AutoHideScrollBars);
+			Assert.True (sv.ShowHorizontalScrollIndicator);
+			Assert.True (sv.ShowVerticalScrollIndicator);
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+         ▲
+         ░
+         ░
+         ░
+         ┬
+         │
+         ┴
+         ░
+         ▼
+◄░░░├─┤░► 
+", output);
+		}
 	}
 	}
-}
+}

+ 2 - 2
UnitTests/StatusBarTests.cs

@@ -113,7 +113,7 @@ namespace Terminal.Gui.Views {
 ^O Open {Application.Driver.VLine} ^Q Quit
 ^O Open {Application.Driver.VLine} ^Q Quit
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			sb = new StatusBar (new StatusItem [] {
 			sb = new StatusBar (new StatusItem [] {
 				new StatusItem (Key.CtrlMask | Key.Q, "~CTRL-O~ Open", null),
 				new StatusItem (Key.CtrlMask | Key.Q, "~CTRL-O~ Open", null),
@@ -125,7 +125,7 @@ namespace Terminal.Gui.Views {
 CTRL-O Open {Application.Driver.VLine} CTRL-Q Quit
 CTRL-O Open {Application.Driver.VLine} CTRL-Q Quit
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]

+ 31 - 31
UnitTests/TabViewTests.cs

@@ -257,7 +257,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──┐      
 ┌──┐      
 │12│13    
 │12│13    
 │  └─────┐
 │  └─────┐
@@ -268,7 +268,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
    ┌──┐   
    ┌──┐   
  12│13│   
  12│13│   
 ┌──┘  └──┐
 ┌──┘  └──┐
@@ -282,7 +282,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌───────┐ 
 ┌───────┐ 
 │1234567│ 
 │1234567│ 
 │       └►
 │       └►
@@ -293,7 +293,7 @@ namespace Terminal.Gui.Views {
 			tv.SelectedTab = tab2;
 			tv.SelectedTab = tab2;
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──┐      
 ┌──┐      
 │13│      
 │13│      
 ◄  └─────┐
 ◄  └─────┐
@@ -307,7 +307,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌───────┐ 
 ┌───────┐ 
 │abcdefg│ 
 │abcdefg│ 
 ◄       └┐
 ◄       └┐
@@ -333,7 +333,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 │12│13    
 │12│13    
 │  └─────┐
 │  └─────┐
 │hi      │
 │hi      │
@@ -345,7 +345,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
  12│13│   
  12│13│   
 ┌──┘  └──┐
 ┌──┘  └──┐
 │hi2     │
 │hi2     │
@@ -360,7 +360,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 │1234567│ 
 │1234567│ 
 │       └►
 │       └►
 │hi      │
 │hi      │
@@ -371,7 +371,7 @@ namespace Terminal.Gui.Views {
 			tv.SelectedTab = tab2;
 			tv.SelectedTab = tab2;
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 │13│      
 │13│      
 ◄  └─────┐
 ◄  └─────┐
 │hi2     │
 │hi2     │
@@ -385,7 +385,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 │abcdefg│ 
 │abcdefg│ 
 ◄       └┐
 ◄       └┐
 │hi2     │
 │hi2     │
@@ -403,7 +403,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌─┐ 
 ┌─┐ 
 │T│ 
 │T│ 
 │ └►
 │ └►
@@ -423,7 +423,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 │T│ 
 │T│ 
 │ └►
 │ └►
 │hi│
 │hi│
@@ -441,7 +441,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌┐ 
 ┌┐ 
 ││ 
 ││ 
 │└►
 │└►
@@ -461,7 +461,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ││ 
 ││ 
 │└►
 │└►
 │h│
 │h│
@@ -487,7 +487,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi      │
 │hi      │
 │  ┌─────┘
 │  ┌─────┘
@@ -501,7 +501,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi      │
 │hi      │
 │       ┌►
 │       ┌►
@@ -512,7 +512,7 @@ namespace Terminal.Gui.Views {
 			tv.SelectedTab = tab2;
 			tv.SelectedTab = tab2;
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi2     │
 │hi2     │
 ◄  ┌─────┘
 ◄  ┌─────┘
@@ -526,7 +526,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi2     │
 │hi2     │
 ◄       ┌┘
 ◄       ┌┘
@@ -552,7 +552,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi      │
 │hi      │
 │        │
 │        │
@@ -564,7 +564,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi2     │
 │hi2     │
 │        │
 │        │
@@ -579,7 +579,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi      │
 │hi      │
 │        │
 │        │
@@ -590,7 +590,7 @@ namespace Terminal.Gui.Views {
 			tv.SelectedTab = tab2;
 			tv.SelectedTab = tab2;
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi2     │
 │hi2     │
 │        │
 │        │
@@ -604,7 +604,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────┐
 ┌────────┐
 │hi2     │
 │hi2     │
 │        │
 │        │
@@ -624,7 +624,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──┐
 ┌──┐
 │hi│
 │hi│
 │ ┌►
 │ ┌►
@@ -644,7 +644,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──┐
 ┌──┐
 │hi│
 │hi│
 │  │
 │  │
@@ -664,7 +664,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌─┐
 ┌─┐
 │h│
 │h│
 │┌►
 │┌►
@@ -684,7 +684,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌─┐
 ┌─┐
 │h│
 │h│
 │ │
 │ │
@@ -706,7 +706,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────┐              
 ┌────┐              
 │Tab0│              
 │Tab0│              
 │    └─────────────►
 │    └─────────────►
@@ -717,7 +717,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────┐    
 ┌──────────────┐    
 │Les Misérables│    
 │Les Misérables│    
 ◄              └───┐
 ◄              └───┐
@@ -741,7 +741,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────────┐
 ┌──────────────────┐
 │hi                │
 │hi                │
 │    ┌─────────────►
 │    ┌─────────────►
@@ -752,7 +752,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────────┐
 ┌──────────────────┐
 │hi2               │
 │hi2               │
 ◄              ┌───┘
 ◄              ┌───┘

+ 28 - 28
UnitTests/TableViewTests.cs

@@ -459,7 +459,7 @@ namespace Terminal.Gui.Views {
 ├─┼──────┤
 ├─┼──────┤
 │1│2     │
 │1│2     │
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -482,7 +482,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┼────┤
 ├─┼─┼────┤
 │1│2│    │
 │1│2│    │
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -506,7 +506,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│2│
 │1│2│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
@@ -670,7 +670,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│2│
 │1│2│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			string expectedColors = @"
 			string expectedColors = @"
@@ -680,7 +680,7 @@ namespace Terminal.Gui.Views {
 01000
 01000
 ";
 ";
 			
 			
-			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,				
 				tv.ColorScheme.Normal,				
 				// 1
 				// 1
@@ -714,7 +714,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│2│
 │1│2│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			string expectedColors = @"
 			string expectedColors = @"
@@ -727,7 +727,7 @@ namespace Terminal.Gui.Views {
 			var invertHotFocus = new Attribute(tv.ColorScheme.HotFocus.Background,tv.ColorScheme.HotFocus.Foreground);
 			var invertHotFocus = new Attribute(tv.ColorScheme.HotFocus.Background,tv.ColorScheme.HotFocus.Foreground);
 			var invertHotNormal = new Attribute(tv.ColorScheme.HotNormal.Background,tv.ColorScheme.HotNormal.Foreground);
 			var invertHotNormal = new Attribute(tv.ColorScheme.HotNormal.Background,tv.ColorScheme.HotNormal.Foreground);
 
 
-			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,				
 				tv.ColorScheme.Normal,				
 				// 1
 				// 1
@@ -771,7 +771,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│2│
 │1│2│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			string expectedColors = @"
 			string expectedColors = @"
@@ -781,7 +781,7 @@ namespace Terminal.Gui.Views {
 21222
 21222
 ";
 ";
 			
 			
-			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,				
 				tv.ColorScheme.Normal,				
 				// 1
 				// 1
@@ -803,7 +803,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│5│
 │1│5│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			expectedColors = @"
 			expectedColors = @"
@@ -816,7 +816,7 @@ namespace Terminal.Gui.Views {
 			// now we only see 2 colors used (the selected cell color and Normal
 			// now we only see 2 colors used (the selected cell color and Normal
 			// rowHighlight should no longer be used because the delegate returned null
 			// rowHighlight should no longer be used because the delegate returned null
 			// (now that the cell value is 5 - which does not match the conditional)
 			// (now that the cell value is 5 - which does not match the conditional)
-			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,
 				tv.ColorScheme.Normal,
 				// 1
 				// 1
@@ -864,7 +864,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│2│
 │1│2│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			string expectedColors = @"
 			string expectedColors = @"
@@ -874,7 +874,7 @@ namespace Terminal.Gui.Views {
 01020
 01020
 ";
 ";
 			
 			
-			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,				
 				tv.ColorScheme.Normal,				
 				// 1
 				// 1
@@ -896,7 +896,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┤
 ├─┼─┤
 │1│5│
 │1│5│
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			expectedColors = @"
 			expectedColors = @"
@@ -909,7 +909,7 @@ namespace Terminal.Gui.Views {
 			// now we only see 2 colors used (the selected cell color and Normal
 			// now we only see 2 colors used (the selected cell color and Normal
 			// cellHighlight should no longer be used because the delegate returned null
 			// cellHighlight should no longer be used because the delegate returned null
 			// (now that the cell value is 5 - which does not match the conditional)
 			// (now that the cell value is 5 - which does not match the conditional)
-			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+			TestHelpers.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,				
 				tv.ColorScheme.Normal,				
 				// 1
 				// 1
@@ -1005,7 +1005,7 @@ namespace Terminal.Gui.Views {
 │A│B│C│
 │A│B│C│
 │1│2│3│";
 │1│2│3│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Scroll right
 			// Scroll right
@@ -1026,7 +1026,7 @@ namespace Terminal.Gui.Views {
 │B│C│D│
 │B│C│D│
 │2│3│4│";
 │2│3│4│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1070,7 +1070,7 @@ namespace Terminal.Gui.Views {
 │A│B│C│
 │A│B│C│
 │1│2│3│";
 │1│2│3│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Scroll right
 			// Scroll right
@@ -1091,7 +1091,7 @@ namespace Terminal.Gui.Views {
 │D│E│F│
 │D│E│F│
 │4│5│6│";
 │4│5│6│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
@@ -1135,7 +1135,7 @@ namespace Terminal.Gui.Views {
 │1│2                    │
 │1│2                    │
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// get a style for the long column
 			// get a style for the long column
 			var style = tableView.Style.GetOrCreateColumnStyle(dt.Columns[2]);
 			var style = tableView.Style.GetOrCreateColumnStyle(dt.Columns[2]);
@@ -1152,7 +1152,7 @@ namespace Terminal.Gui.Views {
 │1│2│aaaaaaaaaa         │
 │1│2│aaaaaaaaaa         │
 │1│2│aaa                │
 │1│2│aaa                │
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// revert the style change
 			// revert the style change
 			style.MaxWidth = TableView.DefaultMaxCellWidth;
 			style.MaxWidth = TableView.DefaultMaxCellWidth;
@@ -1172,7 +1172,7 @@ namespace Terminal.Gui.Views {
 │1│2│aaaaaaaaaaaaa...   │
 │1│2│aaaaaaaaaaaaa...   │
 │1│2│aaa                │
 │1│2│aaa                │
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// revert style change
 			// revert style change
 			style.RepresentationGetter = null;
 			style.RepresentationGetter = null;
@@ -1197,7 +1197,7 @@ namespace Terminal.Gui.Views {
 │1│2│aaaaaaaaaaaaaaaaaaa│
 │1│2│aaaaaaaaaaaaaaaaaaa│
 │1│2│aaa                │
 │1│2│aaa                │
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Now test making the width too small for the MinAcceptableWidth
 			// Now test making the width too small for the MinAcceptableWidth
 			// the Column won't fit so should not be rendered
 			// the Column won't fit so should not be rendered
@@ -1214,7 +1214,7 @@ namespace Terminal.Gui.Views {
 │1│2    │
 │1│2    │
 
 
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// setting width to 10 leaves just enough space for the column to
 			// setting width to 10 leaves just enough space for the column to
 			// meet MinAcceptableWidth of 5.  Column width includes terminator line
 			// meet MinAcceptableWidth of 5.  Column width includes terminator line
@@ -1228,7 +1228,7 @@ namespace Terminal.Gui.Views {
 │1│2│aaaa│
 │1│2│aaaa│
 │1│2│aaa │
 │1│2│aaa │
 ";
 ";
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
@@ -1274,7 +1274,7 @@ namespace Terminal.Gui.Views {
 ├─┼─┼─►
 ├─┼─┼─►
 │1│2│3│";
 │1│2│3│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Scroll right
 			// Scroll right
@@ -1291,7 +1291,7 @@ namespace Terminal.Gui.Views {
 ◄─┼─┼─►
 ◄─┼─┼─►
 │2│3│4│";
 │2│3│4│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 
 
 			// Scroll right twice more (to end of columns)
 			// Scroll right twice more (to end of columns)
@@ -1306,7 +1306,7 @@ namespace Terminal.Gui.Views {
 ◄─┼─┼─┤
 ◄─┼─┼─┤
 │4│5│6│";
 │4│5│6│";
 
 
-			GraphViewTests.AssertDriverContentsAre (expected, output);
+			TestHelpers.AssertDriverContentsAre (expected, output);
 
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();

+ 230 - 0
UnitTests/TestHelpers.cs

@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit.Abstractions;
+using Xunit;
+using Terminal.Gui;
+using Rune = System.Rune;
+using Attribute = Terminal.Gui.Attribute;
+using System.Text.RegularExpressions;
+using System.Reflection;
+
+
+// This class enables test functions annotated with the [AutoInitShutdown] attribute to 
+// automatically call Application.Init before called and Application.Shutdown after
+// 
+// This is necessary because a) Application is a singleton and Init/Shutdown must be called
+// as a pair, and b) all unit test functions should be atomic.
+[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
+
+	static bool _init = false;
+	public override void Before (MethodInfo methodUnderTest)
+	{
+		if (_init) {
+			throw new InvalidOperationException ("After did not run.");
+		}
+
+		Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+		_init = true;
+	}
+
+	public override void After (MethodInfo methodUnderTest)
+	{
+		Application.Shutdown ();
+		_init = false;
+	}
+}
+
+class TestHelpers {
+
+
+#pragma warning disable xUnit1013 // Public method should be marked as test
+	public static void AssertDriverContentsAre (string expectedLook, ITestOutputHelper output)
+	{
+#pragma warning restore xUnit1013 // Public method should be marked as test
+
+		var sb = new StringBuilder ();
+		var driver = ((FakeDriver)Application.Driver);
+
+		var contents = driver.Contents;
+
+		for (int r = 0; r < driver.Rows; r++) {
+			for (int c = 0; c < driver.Cols; c++) {
+				sb.Append ((char)contents [r, c, 0]);
+			}
+			sb.AppendLine ();
+		}
+
+		var actualLook = sb.ToString ();
+
+		if (!string.Equals (expectedLook, actualLook)) {
+
+			// ignore trailing whitespace on each line
+			var trailingWhitespace = new Regex (@"\s+$", RegexOptions.Multiline);
+
+			// get rid of trailing whitespace on each line (and leading/trailing whitespace of start/end of full string)
+			expectedLook = trailingWhitespace.Replace (expectedLook, "").Trim ();
+			actualLook = trailingWhitespace.Replace (actualLook, "").Trim ();
+
+			// standardize line endings for the comparison
+			expectedLook = expectedLook.Replace ("\r\n", "\n");
+			actualLook = actualLook.Replace ("\r\n", "\n");
+
+			output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+			output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
+
+			Assert.Equal (expectedLook, actualLook);
+		}
+	}
+
+	public static Rect AssertDriverContentsWithFrameAre (string expectedLook, ITestOutputHelper output)
+	{
+		var lines = new List<List<char>> ();
+		var sb = new StringBuilder ();
+		var driver = ((FakeDriver)Application.Driver);
+		var x = -1;
+		var y = -1;
+		int w = -1;
+		int h = -1;
+
+		var contents = driver.Contents;
+
+		for (int r = 0; r < driver.Rows; r++) {
+			var runes = new List<char> ();
+			for (int c = 0; c < driver.Cols; c++) {
+				var rune = (char)contents [r, c, 0];
+				if (rune != ' ') {
+					if (x == -1) {
+						x = c;
+						y = r;
+						for (int i = 0; i < c; i++) {
+							runes.InsertRange (i, new List<char> () { ' ' });
+						}
+					}
+					if (Rune.ColumnWidth (rune) > 1) {
+						c++;
+					}
+					if (c + 1 > w) {
+						w = c + 1;
+					}
+					h = r - y + 1;
+				}
+				if (x > -1) {
+					runes.Add (rune);
+				}
+			}
+			if (runes.Count > 0) {
+				lines.Add (runes);
+			}
+		}
+
+		// Remove unnecessary empty lines
+		if (lines.Count > 0) {
+			for (int r = lines.Count - 1; r > h - 1; r--) {
+				lines.RemoveAt (r);
+			}
+		}
+
+		// Remove trailing whitespace on each line
+		for (int r = 0; r < lines.Count; r++) {
+			List<char> row = lines [r];
+			for (int c = row.Count - 1; c >= 0; c--) {
+				var rune = row [c];
+				if (rune != ' ' || (row.Sum (x => Rune.ColumnWidth (x)) == w)) {
+					break;
+				}
+				row.RemoveAt (c);
+			}
+		}
+
+		// Convert char list to string
+		for (int r = 0; r < lines.Count; r++) {
+			var line = new string (lines [r].ToArray ());
+			if (r == lines.Count - 1) {
+				sb.Append (line);
+			} else {
+				sb.AppendLine (line);
+			}
+		}
+
+		var actualLook = sb.ToString ();
+
+		if (!string.Equals (expectedLook, actualLook)) {
+
+			// standardize line endings for the comparison
+			expectedLook = expectedLook.Replace ("\r\n", "\n");
+			actualLook = actualLook.Replace ("\r\n", "\n");
+
+			// Remove the first and the last line ending from the expectedLook
+			if (expectedLook.StartsWith ("\n")) {
+				expectedLook = expectedLook [1..];
+			}
+			if (expectedLook.EndsWith ("\n")) {
+				expectedLook = expectedLook [..^1];
+			}
+
+			output?.WriteLine ("Expected:" + Environment.NewLine + expectedLook);
+			output?.WriteLine ("But Was:" + Environment.NewLine + actualLook);
+
+			Assert.Equal (expectedLook, actualLook);
+		}
+		return new Rect (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0);
+	}
+
+#pragma warning disable xUnit1013 // Public method should be marked as test
+	/// <summary>
+	/// Verifies the console was rendered using the given <paramref name="expectedColors"/> at the given locations.
+	/// Pass a bitmap of indexes into <paramref name="expectedColors"/> as <paramref name="expectedLook"/> and the
+	/// test method will verify those colors were used in the row/col of the console during rendering
+	/// </summary>
+	/// <param name="expectedLook">Numbers between 0 and 9 for each row/col of the console.  Must be valid indexes of <paramref name="expectedColors"/></param>
+	/// <param name="expectedColors"></param>
+	public static void AssertDriverColorsAre (string expectedLook, Attribute [] expectedColors)
+	{
+#pragma warning restore xUnit1013 // Public method should be marked as test
+
+		if (expectedColors.Length > 10) {
+			throw new ArgumentException ("This method only works for UIs that use at most 10 colors");
+		}
+
+		expectedLook = expectedLook.Trim ();
+		var driver = ((FakeDriver)Application.Driver);
+
+		var contents = driver.Contents;
+
+		int r = 0;
+		foreach (var line in expectedLook.Split ('\n').Select (l => l.Trim ())) {
+
+			for (int c = 0; c < line.Length; c++) {
+
+				int val = contents [r, c, 1];
+
+				var match = expectedColors.Where (e => e.Value == val).ToList ();
+				if (match.Count == 0) {
+					throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value))})");
+				} else if (match.Count > 1) {
+					throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
+				}
+
+				var colorUsed = Array.IndexOf (expectedColors, match [0]).ToString () [0];
+				var userExpected = line [c];
+
+				if (colorUsed != userExpected) {
+					throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {DescribeColor (colorUsed)} but test expected {DescribeColor (userExpected)} (these are indexes into the expectedColors array)");
+				}
+			}
+
+			r++;
+		}
+	}
+
+	private static object DescribeColor (int userExpected)
+	{
+		var a = new Attribute (userExpected);
+		return $"{a.Foreground},{a.Background}";
+	}
+}
+

+ 35 - 35
UnitTests/TextFormatterTests.cs

@@ -2190,7 +2190,7 @@ namespace Terminal.Gui.Core {
 └───┘
 └───┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 		}
 		}
 
 
@@ -2229,7 +2229,7 @@ namespace Terminal.Gui.Core {
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 		}
 		}
 
 
@@ -2270,7 +2270,7 @@ namespace Terminal.Gui.Core {
 └──────┘
 └──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 		}
 		}
 
 
@@ -2310,7 +2310,7 @@ namespace Terminal.Gui.Core {
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, height + 2), pos);
 		}
 		}
 
 
@@ -3059,7 +3059,7 @@ namespace Terminal.Gui.Core {
 Demo Simple Rune
 Demo Simple Rune
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 16, 1), pos);
 			Assert.Equal (new Rect (0, 0, 16, 1), pos);
 		}
 		}
 
 
@@ -3096,7 +3096,7 @@ n
 e
 e
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 1, 16), pos);
 			Assert.Equal (new Rect (0, 0, 1, 16), pos);
 		}
 		}
 
 
@@ -3114,7 +3114,7 @@ e
 デモエムポンズ
 デモエムポンズ
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 14, 1), pos);
 			Assert.Equal (new Rect (0, 0, 14, 1), pos);
 		}
 		}
 
 
@@ -3140,7 +3140,7 @@ e
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 2, 7), pos);
 			Assert.Equal (new Rect (0, 0, 2, 7), pos);
 		}
 		}
 
 
@@ -3169,7 +3169,7 @@ e
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 2, 7), pos);
 			Assert.Equal (new Rect (0, 0, 2, 7), pos);
 		}
 		}
 
 
@@ -3208,7 +3208,7 @@ e
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
 		}
 		}
 
 
@@ -3263,7 +3263,7 @@ e
 └───────┘
 └───────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 9, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, 9, height + 2), pos);
 		}
 		}
 
 
@@ -3302,7 +3302,7 @@ e
 └─────────────────────────┘
 └─────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
 			Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
 		}
 		}
 
 
@@ -3361,7 +3361,7 @@ e
 └───────────┘
 └───────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 13, height + 2), pos);
 			Assert.Equal (new Rect (0, 0, 13, height + 2), pos);
 		}
 		}
 
 
@@ -3385,7 +3385,7 @@ e
 
 
 			tf2.Draw (new Rect (new Point (0, 2), tf2Size), view.GetNormalColor (), view.ColorScheme.HotNormal);
 			tf2.Draw (new Rect (new Point (0, 2), tf2Size), view.GetNormalColor (), view.ColorScheme.HotNormal);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This view needs to be cleared before rewritten.                        
 This view needs to be cleared before rewritten.                        
 This TextFormatter (tf1) without fill will not be cleared on rewritten.
 This TextFormatter (tf1) without fill will not be cleared on rewritten.
 This TextFormatter (tf2) with fill will be cleared on rewritten.       
 This TextFormatter (tf2) with fill will be cleared on rewritten.       
@@ -3400,7 +3400,7 @@ This TextFormatter (tf2) with fill will be cleared on rewritten.
 			tf2.Text = "This TextFormatter (tf2) is rewritten.";
 			tf2.Text = "This TextFormatter (tf2) is rewritten.";
 			tf2.Draw (new Rect (new Point (0, 2), tf2Size), view.GetNormalColor (), view.ColorScheme.HotNormal);
 			tf2.Draw (new Rect (new Point (0, 2), tf2Size), view.GetNormalColor (), view.ColorScheme.HotNormal);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This view is rewritten.                                                
 This view is rewritten.                                                
 This TextFormatter (tf1) is rewritten.will not be cleared on rewritten.
 This TextFormatter (tf1) is rewritten.will not be cleared on rewritten.
 This TextFormatter (tf2) is rewritten.                                 
 This TextFormatter (tf2) is rewritten.                                 
@@ -3502,7 +3502,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3520,7 +3520,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 		}
 		}
 
 
@@ -3555,7 +3555,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3574,7 +3574,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 		}
 		}
 
 
@@ -3610,7 +3610,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3629,7 +3629,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 		}
 		}
 
 
@@ -3666,7 +3666,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3685,7 +3685,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 		}
 		}
 
 
@@ -3720,7 +3720,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3739,7 +3739,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 		}
 		}
 
 
@@ -3775,7 +3775,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3794,7 +3794,7 @@ This TextFormatter (tf2) is rewritten.
 └────────┘
 └────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 			Assert.Equal (new Rect (0, 0, 10, 4), pos);
 		}
 		}
 
 
@@ -3837,7 +3837,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3862,7 +3862,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 		}
 		}
 
 
@@ -3904,7 +3904,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3929,7 +3929,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 		}
 		}
 
 
@@ -3972,7 +3972,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -3997,7 +3997,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 		}
 		}
 
 
@@ -4039,7 +4039,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 
 
 			text = "0123456789";
 			text = "0123456789";
@@ -4064,7 +4064,7 @@ This TextFormatter (tf2) is rewritten.
 └──┘
 └──┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 			Assert.Equal (new Rect (0, 0, 4, 10), pos);
 		}
 		}
 
 

+ 35 - 35
UnitTests/TextViewTests.cs

@@ -1973,7 +1973,7 @@ namespace Terminal.Gui.Views {
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is
 This is
 the    
 the    
 first  
 first  
@@ -2000,7 +2000,7 @@ line.
 
 
 			Assert.Equal (new Point (0, 0), tv.CursorPosition);
 			Assert.Equal (new Point (0, 0), tv.CursorPosition);
 			Assert.Equal (0, tv.LeftColumn);
 			Assert.Equal (0, tv.LeftColumn);
-			GraphViewTests.AssertDriverContentsAre (@"
+			TestHelpers.AssertDriverContentsAre (@"
 aaaa
 aaaa
 ", output);
 ", output);
 
 
@@ -2008,35 +2008,35 @@ aaaa
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (0, tv.LeftColumn);
 			Assert.Equal (0, tv.LeftColumn);
-			GraphViewTests.AssertDriverContentsAre (@"
+			TestHelpers.AssertDriverContentsAre (@"
 aaa
 aaa
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (0, tv.LeftColumn);
 			Assert.Equal (0, tv.LeftColumn);
-			GraphViewTests.AssertDriverContentsAre (@"
+			TestHelpers.AssertDriverContentsAre (@"
 aa
 aa
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (0, tv.LeftColumn);
 			Assert.Equal (0, tv.LeftColumn);
-			GraphViewTests.AssertDriverContentsAre (@"
+			TestHelpers.AssertDriverContentsAre (@"
 a
 a
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (0, tv.LeftColumn);
 			Assert.Equal (0, tv.LeftColumn);
-			GraphViewTests.AssertDriverContentsAre (@"
+			TestHelpers.AssertDriverContentsAre (@"
 
 
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Application.Refresh ();
 			Application.Refresh ();
 			Assert.Equal (0, tv.LeftColumn);
 			Assert.Equal (0, tv.LeftColumn);
-			GraphViewTests.AssertDriverContentsAre (@"
+			TestHelpers.AssertDriverContentsAre (@"
 
 
 ", output);
 ", output);
 		}
 		}
@@ -2055,7 +2055,7 @@ a
 			Application.Top.Add (tv);
 			Application.Top.Add (tv);
 
 
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is  
 This is  
 the first
 the first
 line.    
 line.    
@@ -2069,7 +2069,7 @@ line.
 			tv.CursorPosition = new Point (6, 2);
 			tv.CursorPosition = new Point (6, 2);
 			Assert.Equal (new Point (5, 2), tv.CursorPosition);
 			Assert.Equal (new Point (5, 2), tv.CursorPosition);
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is  
 This is  
 the first
 the first
 line.    
 line.    
@@ -5953,7 +5953,7 @@ line.
 			Assert.False (tv.WordWrap);
 			Assert.False (tv.WordWrap);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
 			Assert.Equal (Point.Empty, cp);
 			Assert.Equal (Point.Empty, cp);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is the first line. 
 This is the first line. 
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -5963,7 +5963,7 @@ This is the second line.
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (12, 0), tv.CursorPosition);
 			Assert.Equal (new Point (12, 0), tv.CursorPosition);
 			Assert.Equal (new Point (12, 0), cp);
 			Assert.Equal (new Point (12, 0), cp);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is the first line. 
 This is the first line. 
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -5972,7 +5972,7 @@ This is the second line.
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (4, 2), tv.CursorPosition);
 			Assert.Equal (new Point (4, 2), tv.CursorPosition);
 			Assert.Equal (new Point (12, 0), cp);
 			Assert.Equal (new Point (12, 0), cp);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This 
 This 
 is   
 is   
 the  
 the  
@@ -5991,7 +5991,7 @@ line.
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (0, 3), tv.CursorPosition);
 			Assert.Equal (new Point (0, 3), tv.CursorPosition);
 			Assert.Equal (new Point (12, 0), cp);
 			Assert.Equal (new Point (12, 0), cp);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This 
 This 
 is   
 is   
 the  
 the  
@@ -6010,7 +6010,7 @@ line.
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (1, 3), tv.CursorPosition);
 			Assert.Equal (new Point (1, 3), tv.CursorPosition);
 			Assert.Equal (new Point (13, 0), cp);
 			Assert.Equal (new Point (13, 0), cp);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This 
 This 
 is   
 is   
 the  
 the  
@@ -6029,7 +6029,7 @@ line.
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (0, 3), tv.CursorPosition);
 			Assert.Equal (new Point (0, 3), tv.CursorPosition);
 			Assert.Equal (new Point (12, 0), cp);
 			Assert.Equal (new Point (12, 0), cp);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This 
 This 
 is   
 is   
 the  
 the  
@@ -6061,7 +6061,7 @@ line.
 
 
 			Assert.False (tv.WordWrap);
 			Assert.False (tv.WordWrap);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is the first line. 
 This is the first line. 
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6071,7 +6071,7 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6081,14 +6081,14 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.This is the second line.
 Ths is the first line.This is the second line.
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6118,7 +6118,7 @@ This is the second line.
 
 
 			Assert.True (tv.WordWrap);
 			Assert.True (tv.WordWrap);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is the first line. 
 This is the first line. 
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6128,7 +6128,7 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6138,14 +6138,14 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Backspace, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.This is the second line.
 Ths is the first line.This is the second line.
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6174,7 +6174,7 @@ This is the second line.
 
 
 			Assert.False (tv.WordWrap);
 			Assert.False (tv.WordWrap);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is the first line. 
 This is the first line. 
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6184,7 +6184,7 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6194,14 +6194,14 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.This is the second line.
 Ths is the first line.This is the second line.
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6231,7 +6231,7 @@ This is the second line.
 
 
 			Assert.True (tv.WordWrap);
 			Assert.True (tv.WordWrap);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
 			Assert.Equal (Point.Empty, tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is the first line. 
 This is the first line. 
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6241,7 +6241,7 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
 			Assert.Equal (new Point (2, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6251,14 +6251,14 @@ This is the second line.
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.DeleteChar, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
 			Assert.Equal (new Point (22, 0), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.This is the second line.
 Ths is the first line.This is the second line.
 ", output);
 ", output);
 
 
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
 			Assert.Equal (new Point (0, 1), tv.CursorPosition);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Ths is the first line.  
 Ths is the first line.  
 This is the second line.
 This is the second line.
 ", output);
 ", output);
@@ -6295,7 +6295,7 @@ This is the second line.
 			((FakeDriver)Application.Driver).SetBufferSize (15, 15);
 			((FakeDriver)Application.Driver).SetBufferSize (15, 15);
 			Application.Refresh ();
 			Application.Refresh ();
 			//this passes
 			//this passes
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (
 			@"
 			@"
 ┌─────────────┐
 ┌─────────────┐
 │             │
 │             │
@@ -6321,7 +6321,7 @@ This is the second line.
 			tv.InsertText ("\naaa\nbbb");
 			tv.InsertText ("\naaa\nbbb");
 			Application.Refresh ();
 			Application.Refresh ();
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (
+			TestHelpers.AssertDriverContentsWithFrameAre (
 			@"
 			@"
 ┌─────────────┐
 ┌─────────────┐
 │             │
 │             │
@@ -6365,7 +6365,7 @@ This is the second line.
 			Application.Refresh ();
 			Application.Refresh ();
 
 
 			//this passes
 			//this passes
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (
 			@"
 			@"
 ┌─────────────┐
 ┌─────────────┐
 │             │
 │             │
@@ -6391,7 +6391,7 @@ This is the second line.
 			tv.InsertText ("\r\naaa\r\nbbb");
 			tv.InsertText ("\r\naaa\r\nbbb");
 			Application.Refresh ();
 			Application.Refresh ();
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (
+			TestHelpers.AssertDriverContentsWithFrameAre (
 			@"
 			@"
 ┌─────────────┐
 ┌─────────────┐
 │             │
 │             │

+ 9 - 9
UnitTests/TreeViewTests.cs

@@ -748,7 +748,7 @@ namespace Terminal.Gui.Views {
 			tv.ColorScheme = new ColorScheme ();
 			tv.ColorScheme = new ColorScheme ();
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsAre (
+			TestHelpers.AssertDriverContentsAre (
 @"├-normal
 @"├-normal
 │ ├─pink
 │ ├─pink
 │ └─normal
 │ └─normal
@@ -766,7 +766,7 @@ namespace Terminal.Gui.Views {
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
 
 
-			GraphViewTests.AssertDriverContentsAre (
+			TestHelpers.AssertDriverContentsAre (
 @"├+normal
 @"├+normal
 └─pink
 └─pink
 ", output);
 ", output);
@@ -797,7 +797,7 @@ namespace Terminal.Gui.Views {
 			tv.ColorScheme = new ColorScheme ();
 			tv.ColorScheme = new ColorScheme ();
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
-			GraphViewTests.AssertDriverContentsAre (
+			TestHelpers.AssertDriverContentsAre (
 @"├-normal
 @"├-normal
 │ ├─pink
 │ ├─pink
 │ └─normal
 │ └─normal
@@ -814,7 +814,7 @@ namespace Terminal.Gui.Views {
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
 
 
-			GraphViewTests.AssertDriverContentsAre (
+			TestHelpers.AssertDriverContentsAre (
 @"├+normal
 @"├+normal
 └─pink
 └─pink
 ", output);
 ", output);
@@ -830,7 +830,7 @@ namespace Terminal.Gui.Views {
 			tv.Redraw (tv.Bounds);
 			tv.Redraw (tv.Bounds);
 
 
 
 
-			GraphViewTests.AssertDriverContentsAre (
+			TestHelpers.AssertDriverContentsAre (
 @"└─pink
 @"└─pink
 ", output);
 ", output);
 			Assert.Equal (-1, tv.GetObjectRow (n1));
 			Assert.Equal (-1, tv.GetObjectRow (n1));
@@ -861,14 +861,14 @@ namespace Terminal.Gui.Views {
 			tv.Redraw(tv.Bounds);
 			tv.Redraw(tv.Bounds);
 
 
 			// Normal drawing of the tree view
 			// Normal drawing of the tree view
-			GraphViewTests.AssertDriverContentsAre(
+			TestHelpers.AssertDriverContentsAre(
 @"├-normal
 @"├-normal
 │ ├─pink
 │ ├─pink
 │ └─normal
 │ └─normal
 └─pink
 └─pink
 ",output);
 ",output);
 			// Should all be the same color
 			// Should all be the same color
-			GraphViewTests.AssertDriverColorsAre(
+			TestHelpers.AssertDriverColorsAre(
 @"00000000
 @"00000000
 00000000
 00000000
 0000000000
 0000000000
@@ -892,7 +892,7 @@ namespace Terminal.Gui.Views {
 			tv.Redraw(tv.Bounds);
 			tv.Redraw(tv.Bounds);
 	
 	
 			// Same text
 			// Same text
-			GraphViewTests.AssertDriverContentsAre(
+			TestHelpers.AssertDriverContentsAre(
 @"├-normal
 @"├-normal
 │ ├─pink
 │ ├─pink
 │ └─normal
 │ └─normal
@@ -900,7 +900,7 @@ namespace Terminal.Gui.Views {
 ",output);
 ",output);
 			// but now the item (only not lines) appear
 			// but now the item (only not lines) appear
 			// in pink when they are the word "pink"
 			// in pink when they are the word "pink"
-			GraphViewTests.AssertDriverColorsAre(
+			TestHelpers.AssertDriverColorsAre(
 @"00000000
 @"00000000
 00001111
 00001111
 0000000000
 0000000000

+ 60 - 60
UnitTests/ViewTests.cs

@@ -1504,7 +1504,7 @@ Hello     X
 Y          
 Y          
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 11, 3), pos);
 			Assert.Equal (new Rect (0, 0, 11, 3), pos);
 
 
 			label.AutoSize = false;
 			label.AutoSize = false;
@@ -1519,7 +1519,7 @@ Hello     X
 Y          
 Y          
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 11, 3), pos);
 			Assert.Equal (new Rect (0, 0, 11, 3), pos);
 		}
 		}
 
 
@@ -1550,7 +1550,7 @@ o
 Y  
 Y  
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 
 
 			label.AutoSize = false;
 			label.AutoSize = false;
@@ -1573,7 +1573,7 @@ o
 Y  
 Y  
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 		}
 		}
 
 
@@ -2119,7 +2119,7 @@ Y
 └──────┘
 └──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 8, 4), pos);
 			Assert.Equal (new Rect (0, 0, 8, 4), pos);
 		}
 		}
 
 
@@ -2141,7 +2141,7 @@ Y
 └┘
 └┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 2, 2), pos);
 			Assert.Equal (new Rect (0, 0, 2, 2), pos);
 		}
 		}
 
 
@@ -2165,7 +2165,7 @@ Y
 ──────┘
 ──────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 7, 4), pos);
 			Assert.Equal (new Rect (0, 0, 7, 4), pos);
 
 
 			view.Frame = new Rect (-1, -1, 8, 4);
 			view.Frame = new Rect (-1, -1, 8, 4);
@@ -2177,7 +2177,7 @@ Y
 ──────┘
 ──────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (6, 0, 7, 3), pos);
 			Assert.Equal (new Rect (6, 0, 7, 3), pos);
 
 
 			view.Frame = new Rect (0, 0, 8, 4);
 			view.Frame = new Rect (0, 0, 8, 4);
@@ -2190,7 +2190,7 @@ Y
 └──────
 └──────
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 7, 4), pos);
 			Assert.Equal (new Rect (0, 0, 7, 4), pos);
 
 
 			view.Frame = new Rect (0, 0, 8, 4);
 			view.Frame = new Rect (0, 0, 8, 4);
@@ -2232,7 +2232,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2255,7 +2255,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2278,7 +2278,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2301,7 +2301,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2324,7 +2324,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2347,7 +2347,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2370,7 +2370,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
@@ -2393,7 +2393,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Home, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CtrlMask | Key.Home, new KeyModifiers ())));
@@ -2417,7 +2417,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
@@ -2440,7 +2440,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 
 
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
 			Assert.True (scrollView.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
@@ -2463,7 +2463,7 @@ Y
  └──────────────────┘
  └──────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 			Assert.Equal (new Rect (1, 1, 21, 14), pos);
 		}
 		}
 
 
@@ -2503,7 +2503,7 @@ Y
 └──────────────────┘
 └──────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 20, 10), pos);
 			Assert.Equal (new Rect (0, 0, 20, 10), pos);
 
 
 			view.Clear ();
 			view.Clear ();
@@ -2511,7 +2511,7 @@ Y
 			expected = @"
 			expected = @"
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (Rect.Empty, pos);
 			Assert.Equal (Rect.Empty, pos);
 		}
 		}
 
 
@@ -2551,7 +2551,7 @@ Y
 └──────────────────┘
 └──────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 20, 10), pos);
 			Assert.Equal (new Rect (0, 0, 20, 10), pos);
 
 
 			view.Clear (view.Bounds);
 			view.Clear (view.Bounds);
@@ -2559,7 +2559,7 @@ Y
 			expected = @"
 			expected = @"
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (Rect.Empty, pos);
 			Assert.Equal (Rect.Empty, pos);
 		}
 		}
 
 
@@ -2626,7 +2626,7 @@ Y
 └──────────────────────────────┘
 └──────────────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 32, 32), pos);
 			Assert.Equal (new Rect (0, 0, 32, 32), pos);
 
 
 			verticalView.Text = $"最初の行{Environment.NewLine}二行目";
 			verticalView.Text = $"最初の行{Environment.NewLine}二行目";
@@ -2667,7 +2667,7 @@ Y
 └──────────────────────────────┘
 └──────────────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 32, 32), pos);
 			Assert.Equal (new Rect (0, 0, 32, 32), pos);
 		}
 		}
 
 
@@ -2714,7 +2714,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.Text = "Hello World";
 			view.Text = "Hello World";
@@ -2751,7 +2751,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.AutoSize = true;
 			view.AutoSize = true;
@@ -2788,7 +2788,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.TextDirection = TextDirection.TopBottom_LeftRight;
 			view.TextDirection = TextDirection.TopBottom_LeftRight;
@@ -2824,7 +2824,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.AutoSize = false;
 			view.AutoSize = false;
@@ -2861,7 +2861,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.PreserveTrailingSpaces = true;
 			view.PreserveTrailingSpaces = true;
@@ -2897,7 +2897,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.PreserveTrailingSpaces = false;
 			view.PreserveTrailingSpaces = false;
@@ -2937,7 +2937,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			view.AutoSize = true;
 			view.AutoSize = true;
@@ -2973,7 +2973,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 		}
 		}
 
 
@@ -3042,7 +3042,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 
 
 			verticalView.Text = $"最初_の行二行目";
 			verticalView.Text = $"最初_の行二行目";
@@ -3080,7 +3080,7 @@ Y
 └────────────────────┘
 └────────────────────┘
 ";
 ";
 
 
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 			Assert.Equal (new Rect (0, 0, 22, 22), pos);
 		}
 		}
 
 
@@ -3113,7 +3113,7 @@ Y
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 
 			Assert.True (btn.AutoSize);
 			Assert.True (btn.AutoSize);
 			btn.Text = "Say He_llo 你 changed";
 			btn.Text = "Say He_llo 你 changed";
@@ -3127,7 +3127,7 @@ Y
 └────────────────────────────┘
 └────────────────────────────┘
 ";
 ";
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -3500,7 +3500,7 @@ Y
 			Assert.False (view.AutoSize);
 			Assert.False (view.AutoSize);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal ("Test", view.TextFormatter.Text);
 			Assert.Equal ("Test", view.TextFormatter.Text);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Test
 Test
 ", output);
 ", output);
 
 
@@ -3509,7 +3509,7 @@ Test
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 First line
 First line
 ", output);
 ", output);
 
 
@@ -3518,7 +3518,7 @@ First line
 			Assert.Equal (new Rect (0, 0, 11, 2), view.Frame);
 			Assert.Equal (new Rect (0, 0, 11, 2), view.Frame);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 First line 
 First line 
 Second line
 Second line
 ", output);
 ", output);
@@ -3528,7 +3528,7 @@ Second line
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 First line
 First line
 ", output);
 ", output);
 		}
 		}
@@ -3576,7 +3576,7 @@ First line
 			Assert.False (view.AutoSize);
 			Assert.False (view.AutoSize);
 			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
 			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
 			Assert.Equal ("Test", view.TextFormatter.Text);
 			Assert.Equal ("Test", view.TextFormatter.Text);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 T
 T
 e
 e
 s
 s
@@ -3588,7 +3588,7 @@ t
 			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
 			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 F
 F
 i
 i
 r
 r
@@ -3606,7 +3606,7 @@ e
 			Assert.Equal (new Rect (0, 0, 2, 11), view.Frame);
 			Assert.Equal (new Rect (0, 0, 2, 11), view.Frame);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 FS
 FS
 ie
 ie
 rc
 rc
@@ -3625,7 +3625,7 @@ en
 			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
 			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 F
 F
 i
 i
 r
 r
@@ -3680,7 +3680,7 @@ e
 			Assert.False (view.AutoSize);
 			Assert.False (view.AutoSize);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal ("Test 你", view.TextFormatter.Text);
 			Assert.Equal ("Test 你", view.TextFormatter.Text);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 Test 你
 Test 你
 ", output);
 ", output);
 
 
@@ -3689,7 +3689,7 @@ Test 你
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 First line
 First line
 ", output);
 ", output);
 
 
@@ -3698,7 +3698,7 @@ First line
 			Assert.Equal (new Rect (0, 0, 14, 2), view.Frame);
 			Assert.Equal (new Rect (0, 0, 14, 2), view.Frame);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 First line 你 
 First line 你 
 Second line 你
 Second line 你
 ", output);
 ", output);
@@ -3708,7 +3708,7 @@ Second line 你
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 First line
 First line
 ", output);
 ", output);
 		}
 		}
@@ -3758,7 +3758,7 @@ First line
 			// SetMinWidthHeight ensuring the minimum width for the wide char
 			// SetMinWidthHeight ensuring the minimum width for the wide char
 			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
 			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
 			Assert.Equal ("Test 你", view.TextFormatter.Text);
 			Assert.Equal ("Test 你", view.TextFormatter.Text);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 T 
 T 
 e 
 e 
 s 
 s 
@@ -3772,7 +3772,7 @@ t
 			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
 			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 F
 F
 i
 i
 r
 r
@@ -3790,7 +3790,7 @@ e
 			Assert.Equal (new Rect (0, 0, 4, 13), view.Frame);
 			Assert.Equal (new Rect (0, 0, 4, 13), view.Frame);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 F S 
 F S 
 i e 
 i e 
 r c 
 r c 
@@ -3811,7 +3811,7 @@ e n
 			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
 			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
 			Application.Refresh ();
 			Application.Refresh ();
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 F
 F
 i
 i
 r
 r
@@ -3837,7 +3837,7 @@ e
 
 
 			Assert.True (label.Visible);
 			Assert.True (label.Visible);
 			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
 			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────────────────────────┐
 ┌────────────────────────────┐
 │Testing visibility.         │
 │Testing visibility.         │
 │                            │
 │                            │
@@ -3846,7 +3846,7 @@ e
 ", output);
 ", output);
 
 
 			label.Visible = false;
 			label.Visible = false;
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌────────────────────────────┐
 ┌────────────────────────────┐
 │                            │
 │                            │
 │                            │
 │                            │
@@ -3869,7 +3869,7 @@ e
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 
 
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a tes▲
 This is a tes▲
 This is a tes┬
 This is a tes┬
 This is a tes┴
 This is a tes┴
@@ -3881,7 +3881,7 @@ This is a tes▼
 			sbv.Visible = false;
 			sbv.Visible = false;
 			Assert.False (sbv.Visible);
 			Assert.False (sbv.Visible);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a test
 This is a test
 This is a test
 This is a test
 This is a test
 This is a test
@@ -3893,7 +3893,7 @@ This is a test
 			sbv.Visible = true;
 			sbv.Visible = true;
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a tes▲
 This is a tes▲
 This is a tes┬
 This is a tes┬
 This is a tes┴
 This is a tes┴
@@ -3905,7 +3905,7 @@ This is a tes▼
 			sbv.ClearOnVisibleFalse = true;
 			sbv.ClearOnVisibleFalse = true;
 			sbv.Visible = false;
 			sbv.Visible = false;
 			Assert.False (sbv.Visible);
 			Assert.False (sbv.Visible);
-			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
 This is a tes
 This is a tes
 This is a tes
 This is a tes
 This is a tes
 This is a tes

+ 3 - 3
UnitTests/WizardTests.cs

@@ -127,7 +127,7 @@ namespace Terminal.Gui.Views {
 
 
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			var wizard = new Wizard (title) { Width = width, Height = height };
 			Application.End (Application.Begin (wizard));
 			Application.End (Application.Begin (wizard));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -164,7 +164,7 @@ namespace Terminal.Gui.Views {
 			var runstate = Application.Begin (wizard);
 			var runstate = Application.Begin (wizard);
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
 
 
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{row4}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{row4}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
 			Application.End (runstate);
 			Application.End (runstate);
 		}
 		}
 
 
@@ -231,7 +231,7 @@ namespace Terminal.Gui.Views {
 			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
 			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
 
 
 			Application.End (Application.Begin (wizard));
 			Application.End (Application.Begin (wizard));
-			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+			TestHelpers.AssertDriverContentsWithFrameAre ($"{topRow}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]

BIN
docfx/images/Example.png


+ 8 - 0
docfx/overrides/Terminal_Gui_Application.md

@@ -0,0 +1,8 @@
+---
+uid: Terminal.Gui.Application
+summary: '*You can override summary for the API here using *MARKDOWN* syntax'
+---
+
+*Please type below more information about this API:*
+
+![Sample](images/sample.png)