浏览代码

Merge pull request #128 from BDisp/vkpacket-fix3

Pressing only shift with a lowercase or a digit the uppercase or symbol must be the character to display.
Thomas Nind 2 年之前
父节点
当前提交
2562ad9d95
共有 60 个文件被更改,包括 3381 次插入2046 次删除
  1. 1 1
      .github/workflows/api-docs.yml
  2. 1 1
      .github/workflows/dotnet-core.yml
  3. 3 3
      .github/workflows/publish.yml
  4. 76 0
      Example/Example.cs
  5. 0 11
      Example/Properties/launchSettings.json
  6. 7 3
      Example/README.md
  7. 0 758
      Example/demo.cs
  8. 64 55
      README.md
  9. 1 1
      ReactiveExample/ReactiveExample.csproj
  10. 0 265
      StandaloneExample/Program.cs
  11. 0 11
      StandaloneExample/Properties/launchSettings.json
  12. 0 9
      StandaloneExample/README.md
  13. 0 10
      StandaloneExample/StandaloneExample.csproj
  14. 0 25
      StandaloneExample/StandaloneExample.sln
  15. 39 12
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  16. 11 6
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeConsole.cs
  17. 46 8
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  18. 36 12
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  19. 71 25
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  20. 40 2
      Terminal.Gui/Core/Application.cs
  21. 2 1
      Terminal.Gui/Core/Clipboard/ClipboardBase.cs
  22. 5 10
      Terminal.Gui/Core/ConsoleDriver.cs
  23. 521 0
      Terminal.Gui/Core/ConsoleKeyMapping.cs
  24. 97 26
      Terminal.Gui/Core/Event.cs
  25. 5 2
      Terminal.Gui/Core/MainLoop.cs
  26. 177 174
      Terminal.Gui/Core/View.cs
  27. 3 0
      Terminal.Gui/Terminal.Gui.csproj
  28. 40 20
      Terminal.Gui/Views/ContextMenu.cs
  29. 131 161
      Terminal.Gui/Views/Menu.cs
  30. 3 3
      Terminal.Gui/Views/ScrollBarView.cs
  31. 23 14
      Terminal.Gui/Views/ScrollView.cs
  32. 8 6
      Terminal.Gui/Views/TabView.cs
  33. 3 3
      Terminal.Gui/Views/TextField.cs
  34. 2 2
      Terminal.Gui/Views/TextView.cs
  35. 10 6
      Terminal.Gui/Views/TreeView.cs
  36. 3 3
      Terminal.Gui/Windows/MessageBox.cs
  37. 1 0
      Terminal.sln
  38. 128 0
      Terminal.sln.DotSettings
  39. 18 0
      UICatalog/Properties/launchSettings.json
  40. 6 4
      UICatalog/Scenarios/CsvEditor.cs
  41. 1 0
      UICatalog/Scenarios/Editor.cs
  42. 23 38
      UICatalog/Scenarios/Notepad.cs
  43. 16 0
      UICatalog/Scenarios/TreeViewFileSystem.cs
  44. 251 0
      UICatalog/Scenarios/VkeyPacketSimulator.cs
  45. 5 4
      UICatalog/UICatalog.cs
  46. 72 9
      UnitTests/ApplicationTests.cs
  47. 190 68
      UnitTests/ConsoleDriverTests.cs
  48. 26 12
      UnitTests/ContextMenuTests.cs
  49. 305 230
      UnitTests/MenuTests.cs
  50. 104 0
      UnitTests/MessageBoxTests.cs
  51. 2 2
      UnitTests/PosTests.cs
  52. 2 2
      UnitTests/ScrollBarViewTests.cs
  53. 108 1
      UnitTests/ScrollViewTests.cs
  54. 442 20
      UnitTests/TabViewTests.cs
  55. 52 0
      UnitTests/TextFieldTests.cs
  56. 1 1
      UnitTests/UnitTests.csproj
  57. 172 2
      UnitTests/ViewTests.cs
  58. 8 0
      docfx/overrides/Terminal_Gui_Application.md
  59. 0 4
      packages.config
  60. 19 0
      testenvironments.json

+ 1 - 1
.github/workflows/api-docs.yml

@@ -13,7 +13,7 @@ jobs:
       uses: actions/checkout@v2
 
     - name: Setup .NET Core
-      uses: actions/setup-dotnet@v1
+      uses: actions/setup-dotnet@v3.0.2
       with:
         dotnet-version: 6.0.100
     

+ 1 - 1
.github/workflows/dotnet-core.yml

@@ -15,7 +15,7 @@ jobs:
     - uses: actions/checkout@v3
 
     - name: Setup .NET Core
-      uses: actions/setup-dotnet@v2
+      uses: actions/setup-dotnet@v3.0.2
       with:
         dotnet-version: 6.0.100
 

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

@@ -16,12 +16,12 @@ jobs:
         fetch-depth: 0 #fetch-depth is needed for GitVersion
 
     - name: Install and calculate the new version with GitVersion 
-      uses: gittools/actions/gitversion/[email protected]3
+      uses: gittools/actions/gitversion/[email protected]4
       with:
         versionSpec: 5.x
 
     - name: Determine Version
-      uses: gittools/actions/gitversion/[email protected]3
+      uses: gittools/actions/gitversion/[email protected]4
       id: gitversion # step id used as reference for output values
 
     - name: Display GitVersion outputs
@@ -30,7 +30,7 @@ jobs:
         echo "CommitsSinceVersionSource: ${{ steps.gitversion.outputs.CommitsSinceVersionSource }}"
 
     - name: Setup dotnet
-      uses: actions/setup-dotnet@v2
+      uses: actions/setup-dotnet@v3.0.2
       with:
         dotnet-version: 6.0.100
 

+ 76 - 0
Example/Example.cs

@@ -0,0 +1,76 @@
+// 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.
+
+using Terminal.Gui;
+using NStack;
+
+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
+
+	// By using Dim.Fill(), this Window will automatically resize without manual intervention
+	Width = Dim.Fill (),
+	Height = Dim.Fill ()
+};
+
+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 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
+};
+var loginText = new TextField ("") {
+	X = Pos.Right (password),
+	Y = Pos.Top (login),
+	Width = 40
+};
+var passText = new TextField ("") {
+	Secret = true,
+	X = Pos.Left (loginText),
+	Y = Pos.Top (password),
+	Width = Dim.Width (loginText)
+};
+
+// 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")
+);
+
+// Run blocks until the user quits the application
+Application.Run ();
+
+// Always bracket Application.Init with .Shutdown.
+Application.Shutdown ();

+ 0 - 11
Example/Properties/launchSettings.json

@@ -1,11 +0,0 @@
-{
-  "profiles": {
-    "Example": {
-      "commandName": "Project"
-    },
-    "Example:-usc": {
-      "commandName": "Project",
-      "commandLineArgs": "-usc"
-    }
-  }
-}

+ 7 - 3
Example/README.md

@@ -1,5 +1,9 @@
-# demo.cs
+# Terminal.Gui C# Example
 
-This is the original Terminal.Gui sample app. Over time we will be simplifying this sample to show just the basics of creating a Terminal.Gui app in C#. 
+This example shows how to use the Terminal.Gui library to create a simple GUI application in C#.
 
-See the Sample Code section [README.md](https://github.com/gui-cs/Terminal.Gui) for a list of all samples.
+This is the same code found in the Terminal.Gui README.md file.
+
+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).

+ 0 - 758
Example/demo.cs

@@ -1,758 +0,0 @@
-using NStack;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using Terminal.Gui;
-
-static class Demo {
-	class Box10x : View {
-		int w = 40;
-		int h = 50;
-
-		public bool WantCursorPosition { get; set; } = false;
-
-		public Box10x (int x, int y) : base (new Rect (x, y, 20, 10))
-		{
-		}
-
-		public Size GetContentSize ()
-		{
-			return new Size (w, h);
-		}
-
-		public void SetCursorPosition (Point pos)
-		{
-			throw new NotImplementedException ();
-		}
-
-		public override void Redraw (Rect bounds)
-		{
-			//Point pos = new Point (region.X, region.Y);
-			Driver.SetAttribute (ColorScheme.Focus);
-
-			for (int y = 0; y < h; y++) {
-				Move (0, y);
-				Driver.AddStr (y.ToString ());
-				for (int x = 0; x < w - y.ToString ().Length; x++) {
-					//Driver.AddRune ((Rune)('0' + (x + y) % 10));
-					if (y.ToString ().Length < w)
-						Driver.AddStr (" ");
-				}
-			}
-			//Move (pos.X, pos.Y);
-		}
-	}
-
-	class Filler : View {
-		int w = 40;
-		int h = 50;
-
-		public Filler (Rect rect) : base (rect)
-		{
-			w = rect.Width;
-			h = rect.Height;
-		}
-
-		public Size GetContentSize ()
-		{
-			return new Size (w, h);
-		}
-
-		public override void Redraw (Rect bounds)
-		{
-			Driver.SetAttribute (ColorScheme.Focus);
-			var f = Frame;
-			w = 0;
-			h = 0;
-
-			for (int y = 0; y < f.Width; y++) {
-				Move (0, y);
-				var nw = 0;
-				for (int x = 0; x < f.Height; x++) {
-					Rune r;
-					switch (x % 3) {
-					case 0:
-						var er = y.ToString ().ToCharArray (0, 1) [0];
-						nw += er.ToString ().Length;
-						Driver.AddRune (er);
-						if (y > 9) {
-							er = y.ToString ().ToCharArray (1, 1) [0];
-							nw += er.ToString ().Length;
-							Driver.AddRune (er);
-						}
-						r = '.';
-						break;
-					case 1:
-						r = 'o';
-						break;
-					default:
-						r = 'O';
-						break;
-					}
-					Driver.AddRune (r);
-					nw += Rune.RuneLen (r);
-				}
-				if (nw > w)
-					w = nw;
-				h = y + 1;
-			}
-		}
-	}
-
-	static void ShowTextAlignments ()
-	{
-		var container = new Window ("Show Text Alignments - Press Esc to return") {
-			X = 0,
-			Y = 0,
-			Width = Dim.Fill (),
-			Height = Dim.Fill ()
-		};
-		container.KeyUp += (e) => {
-			if (e.KeyEvent.Key == Key.Esc)
-				container.Running = false;
-		};
-
-		int i = 0;
-		string txt = "Hello world, how are you doing today?";
-		container.Add (
-				new Label ($"{i + 1}-{txt}") { TextAlignment = TextAlignment.Left, Y = 3, Width = Dim.Fill () },
-				new Label ($"{i + 2}-{txt}") { TextAlignment = TextAlignment.Right, Y = 5, Width = Dim.Fill () },
-				new Label ($"{i + 3}-{txt}") { TextAlignment = TextAlignment.Centered, Y = 7, Width = Dim.Fill () },
-				new Label ($"{i + 4}-{txt}") { TextAlignment = TextAlignment.Justified, Y = 9, Width = Dim.Fill () }
-			);
-
-		Application.Run (container);
-	}
-
-	static void ShowEntries (View container)
-	{
-		var scrollView = new ScrollView (new Rect (50, 10, 20, 8)) {
-			ContentSize = new Size (20, 50),
-			//ContentOffset = new Point (0, 0),
-			ShowVerticalScrollIndicator = true,
-			ShowHorizontalScrollIndicator = true
-		};
-#if false
-		scrollView.Add (new Box10x (0, 0));
-#else
-		var filler = new Filler (new Rect (0, 0, 40, 40));
-		scrollView.Add (filler);
-		scrollView.DrawContent += (r) => {
-			scrollView.ContentSize = filler.GetContentSize ();
-		};
-#endif
-
-		// This is just to debug the visuals of the scrollview when small
-		var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) {
-			ContentSize = new Size (100, 100),
-			ShowVerticalScrollIndicator = true,
-			ShowHorizontalScrollIndicator = true
-		};
-		scrollView2.Add (new Box10x (0, 0));
-		var progress = new ProgressBar (new Rect (68, 1, 10, 1));
-		bool timer (MainLoop caller)
-		{
-			progress.Pulse ();
-			return true;
-		}
-
-		Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
-
-
-		// A little convoluted, this is because I am using this to test the
-		// layout based on referencing elements of another view:
-
-		var login = new Label ("Login: ") { X = 3, Y = 6 };
-		var password = new Label ("Password: ") {
-			X = Pos.Left (login),
-			Y = Pos.Bottom (login) + 1
-		};
-		var loginText = new TextField ("") {
-			X = Pos.Right (password),
-			Y = Pos.Top (login),
-			Width = 40
-		};
-
-		var passText = new TextField ("") {
-			Secret = true,
-			X = Pos.Left (loginText),
-			Y = Pos.Top (password),
-			Width = Dim.Width (loginText)
-		};
-
-		var tf = new Button (3, 19, "Ok");
-		var frameView = new FrameView (new Rect (3, 10, 25, 6), "Options");
-		frameView.Add (new CheckBox (1, 0, "Remember me"));
-		frameView.Add (new RadioGroup (1, 2, new ustring [] { "_Personal", "_Company" }));
-		// Add some content
-		container.Add (
-			login,
-			loginText,
-			password,
-			passText,
-			frameView,
-			new ListView (new Rect (59, 6, 16, 4), new string [] {
-				"First row",
-				"<>",
-				"This is a very long row that should overflow what is shown",
-				"4th",
-				"There is an empty slot on the second row",
-				"Whoa",
-				"This is so cool"
-			}),
-			scrollView,
-			scrollView2,
-			tf,
-			new Button (10, 19, "Cancel"),
-			new TimeField (3, 20, DateTime.Now.TimeOfDay),
-			new TimeField (23, 20, DateTime.Now.TimeOfDay, true),
-			new DateField (3, 22, DateTime.Now),
-			new DateField (23, 22, DateTime.Now, true),
-			progress,
-			new Label (3, 24, "Press F9 (on Unix, ESC+9 is an alias) or Ctrl+T to activate the menubar"),
-			menuKeysStyle,
-			menuAutoMouseNav
-
-		);
-		container.SendSubviewToBack (tf);
-	}
-
-	public static Label ml2;
-
-	static void NewFile ()
-	{
-		var ok = new Button ("Ok", is_default: true);
-		ok.Clicked += () => { Application.RequestStop (); };
-		var cancel = new Button ("Cancel");
-		cancel.Clicked += () => { Application.RequestStop (); };
-		var d = new Dialog ("New File", 50, 20, ok, cancel);
-		ml2 = new Label (1, 1, "Mouse Debug Line");
-		d.Add (ml2);
-		Application.Run (d);
-	}
-
-	//
-	static void Editor ()
-	{
-		Application.Init ();
-		Application.HeightAsBuffer = heightAsBuffer;
-
-		var ntop = Application.Top;
-
-		var text = new TextView () { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill () };
-
-		string fname = GetFileName ();
-
-		var win = new Window (fname ?? "Untitled") {
-			X = 0,
-			Y = 1,
-			Width = Dim.Fill (),
-			Height = Dim.Fill ()
-		};
-		ntop.Add (win);
-
-		if (fname != null)
-			text.Text = System.IO.File.ReadAllText (fname);
-		win.Add (text);
-
-		void Paste ()
-		{
-			if (text != null) {
-				text.Paste ();
-			}
-		}
-
-		void Cut ()
-		{
-			if (text != null) {
-				text.Cut ();
-			}
-		}
-
-		void Copy ()
-		{
-			if (text != null) {
-				text.Copy ();
-			}
-		}
-
-		var menu = new MenuBar (new MenuBarItem [] {
-			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Close", "", () => { if (Quit ()) { running = MainApp; Application.RequestStop (); } }, null, null, Key.AltMask | Key.Q),
-			}),
-			new MenuBarItem ("_Edit", new MenuItem [] {
-				new MenuItem ("_Copy", "", Copy, null, null, Key.C | Key.CtrlMask),
-				new MenuItem ("C_ut", "", Cut, null, null, Key.X | Key.CtrlMask),
-				new MenuItem ("_Paste", "", Paste, null, null, Key.Y | Key.CtrlMask)
-			}),
-		});
-		ntop.Add (menu);
-
-		Application.Run (ntop);
-	}
-
-	private static string GetFileName ()
-	{
-		string fname = null;
-		foreach (var s in new [] { "/etc/passwd", "c:\\windows\\win.ini" })
-			if (System.IO.File.Exists (s)) {
-				fname = s;
-				break;
-			}
-
-		return fname;
-	}
-
-	static bool Quit ()
-	{
-		var n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
-		return n == 0;
-	}
-
-	static void Close ()
-	{
-		MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok");
-	}
-
-	// Watch what happens when I try to introduce a newline after the first open brace
-	// it introduces a new brace instead, and does not indent.  Then watch me fight
-	// the editor as more oddities happen.
-
-	public static void Open ()
-	{
-		var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true };
-		Application.Run (d);
-
-		if (!d.Canceled)
-			MessageBox.Query (50, 7, "Selected File", d.FilePaths.Count > 0 ? string.Join (", ", d.FilePaths) : d.FilePath, "Ok");
-	}
-
-	public static void ShowHex ()
-	{
-		var ntop = Application.Top;
-		var menu = new MenuBar (new MenuBarItem [] {
-			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_Close", "", () => { running = MainApp; Application.RequestStop (); }, null, null, Key.AltMask | Key.Q),
-			}),
-		});
-		ntop.Add (menu);
-
-		string fname = GetFileName ();
-		var win = new Window (fname) {
-			X = 0,
-			Y = 1,
-			Width = Dim.Fill (),
-			Height = Dim.Fill ()
-		};
-		ntop.Add (win);
-
-		var source = System.IO.File.OpenRead (fname);
-		var hex = new HexView (source) {
-			X = 0,
-			Y = 0,
-			Width = Dim.Fill (),
-			Height = Dim.Fill ()
-		};
-		win.Add (hex);
-		Application.Run (ntop);
-	}
-
-	public class MenuItemDetails : MenuItem {
-		ustring title;
-		string help;
-		Action action;
-
-		public MenuItemDetails (ustring title, string help, Action action) : base (title, help, action)
-		{
-			this.title = title;
-			this.help = help;
-			this.action = action;
-		}
-
-		public static MenuItemDetails Instance (MenuItem mi)
-		{
-			return (MenuItemDetails)mi.GetMenuItem ();
-		}
-	}
-
-	public delegate MenuItem MenuItemDelegate (MenuItemDetails menuItem);
-
-	public static void ShowMenuItem (MenuItem mi)
-	{
-		BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
-		MethodInfo minfo = typeof (MenuItemDetails).GetMethod ("Instance", flags);
-		MenuItemDelegate mid = (MenuItemDelegate)Delegate.CreateDelegate (typeof (MenuItemDelegate), minfo);
-		MessageBox.Query (70, 7, mi.Title.ToString (),
-			$"{mi.Title.ToString ()} selected. Is from submenu: {mi.GetMenuBarItem ()}", "Ok");
-	}
-
-	static void MenuKeysStyle_Toggled (bool e)
-	{
-		menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked;
-	}
-
-	static void MenuAutoMouseNav_Toggled (bool e)
-	{
-		menu.WantMousePositionReports = menuAutoMouseNav.Checked;
-	}
-
-	static void Copy ()
-	{
-		TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField;
-		if (textField != null && textField.SelectedLength != 0) {
-			textField.Copy ();
-		}
-	}
-
-	static void Cut ()
-	{
-		TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField;
-		if (textField != null && textField.SelectedLength != 0) {
-			textField.Cut ();
-		}
-	}
-
-	static void Paste ()
-	{
-		TextField textField = menu.LastFocused as TextField ?? Application.Top.MostFocused as TextField;
-		textField?.Paste ();
-	}
-
-	static void Help ()
-	{
-		MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok");
-	}
-
-	static void Load ()
-	{
-		MessageBox.Query (50, 7, "Load", "This is a small load\nBe kind.", "Ok");
-	}
-
-	static void Save ()
-	{
-		MessageBox.Query (50, 7, "Save", "This is a small save\nBe kind.", "Ok");
-	}
-
-
-	#region Selection Demo
-
-	static void ListSelectionDemo (bool multiple)
-	{
-		var ok = new Button ("Ok", is_default: true);
-		ok.Clicked += () => { Application.RequestStop (); };
-		var cancel = new Button ("Cancel");
-		cancel.Clicked += () => { Application.RequestStop (); };
-		var d = new Dialog ("Selection Demo", 60, 20, ok, cancel);
-
-		var animals = new List<string> () { "Alpaca", "Llama", "Lion", "Shark", "Goat" };
-		var msg = new Label ("Use space bar or control-t to toggle selection") {
-			X = 1,
-			Y = 1,
-			Width = Dim.Fill () - 1,
-			Height = 1
-		};
-
-		var list = new ListView (animals) {
-			X = 1,
-			Y = 3,
-			Width = Dim.Fill () - 4,
-			Height = Dim.Fill () - 4,
-			AllowsMarking = true,
-			AllowsMultipleSelection = multiple
-		};
-		d.Add (msg, list);
-		Application.Run (d);
-
-		var result = "";
-		for (int i = 0; i < animals.Count; i++) {
-			if (list.Source.IsMarked (i)) {
-				result += animals [i] + " ";
-			}
-		}
-		MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok");
-	}
-
-	static void ComboBoxDemo ()
-	{
-		//TODO: Duplicated code in ListsAndCombos.cs Consider moving to shared assembly
-		var items = new List<ustring> ();
-		foreach (var dir in new [] { "/etc", @$"{Environment.GetEnvironmentVariable ("SystemRoot")}\System32" }) {
-			if (Directory.Exists (dir)) {
-				items = Directory.GetFiles (dir).Union (Directory.GetDirectories (dir))
-					.Select (Path.GetFileName)
-					.Where (x => char.IsLetterOrDigit (x [0]))
-					.OrderBy (x => x).Select (x => ustring.Make (x)).ToList ();
-			}
-		}
-		var list = new ComboBox () { Width = Dim.Fill (), Height = Dim.Fill () };
-		list.SetSource (items);
-		list.OpenSelectedItem += (ListViewItemEventArgs text) => { Application.RequestStop (); };
-
-		var d = new Dialog () { Title = "Select source file", Width = Dim.Percent (50), Height = Dim.Percent (50) };
-		d.Add (list);
-		Application.Run (d);
-
-		MessageBox.Query (60, 10, "Selected file", list.Text.ToString () == "" ? "Nothing selected" : list.Text.ToString (), "Ok");
-	}
-	#endregion
-
-
-	#region KeyDown / KeyPress / KeyUp Demo
-	private static void OnKeyDownPressUpDemo ()
-	{
-		var close = new Button ("Close");
-		close.Clicked += () => { Application.RequestStop (); };
-		var container = new Dialog ("KeyDown & KeyPress & KeyUp demo", 80, 20, close) {
-			Width = Dim.Fill (),
-			Height = Dim.Fill (),
-		};
-
-		var list = new List<string> ();
-		var listView = new ListView (list) {
-			X = 0,
-			Y = 0,
-			Width = Dim.Fill () - 1,
-			Height = Dim.Fill () - 2,
-		};
-		listView.ColorScheme = Colors.TopLevel;
-		container.Add (listView);
-
-		void KeyDownPressUp (KeyEvent keyEvent, string updown)
-		{
-			const int ident = -5;
-			switch (updown) {
-			case "Down":
-			case "Up":
-			case "Press":
-				var msg = $"Key{updown,ident}: ";
-				if ((keyEvent.Key & Key.ShiftMask) != 0)
-					msg += "Shift ";
-				if ((keyEvent.Key & Key.CtrlMask) != 0)
-					msg += "Ctrl ";
-				if ((keyEvent.Key & Key.AltMask) != 0)
-					msg += "Alt ";
-				msg += $"{(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}";
-				//list.Add (msg);
-				list.Add (keyEvent.ToString ());
-
-				break;
-
-			default:
-				if ((keyEvent.Key & Key.ShiftMask) != 0) {
-					list.Add ($"Key{updown,ident}: Shift ");
-				} else if ((keyEvent.Key & Key.CtrlMask) != 0) {
-					list.Add ($"Key{updown,ident}: Ctrl ");
-				} else if ((keyEvent.Key & Key.AltMask) != 0) {
-					list.Add ($"Key{updown,ident}: Alt ");
-				} else {
-					list.Add ($"Key{updown,ident}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}");
-				}
-
-				break;
-			}
-			listView.MoveDown ();
-		}
-
-		container.KeyDown += (e) => KeyDownPressUp (e.KeyEvent, "Down");
-		container.KeyPress += (e) => KeyDownPressUp (e.KeyEvent, "Press");
-		container.KeyUp += (e) => KeyDownPressUp (e.KeyEvent, "Up");
-		Application.Run (container);
-	}
-	#endregion
-
-	public static Action running = MainApp;
-	static void Main (string [] args)
-	{
-		if (args.Length > 0 && args.Contains ("-usc")) {
-			Application.UseSystemConsole = true;
-		}
-
-		Console.OutputEncoding = System.Text.Encoding.Default;
-
-		while (running != null) {
-			running.Invoke ();
-		}
-		Application.Shutdown ();
-	}
-
-	public static Label ml;
-	public static MenuBar menu;
-	public static CheckBox menuKeysStyle;
-	public static CheckBox menuAutoMouseNav;
-	private static bool heightAsBuffer = false;
-	static void MainApp ()
-	{
-		if (Debugger.IsAttached)
-			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
-
-		Application.Init ();
-		Application.HeightAsBuffer = heightAsBuffer;
-		//ConsoleDriver.Diagnostics = ConsoleDriver.DiagnosticFlags.FramePadding | ConsoleDriver.DiagnosticFlags.FrameRuler;
-
-		var top = Application.Top;
-
-		//Open ();
-#if true
-		int margin = 3;
-		var win = new Window ("Hello") {
-			X = 1,
-			Y = 1,
-
-			Width = Dim.Fill () - margin,
-			Height = Dim.Fill () - margin
-		};
-#else
-		var tframe = top.Frame;
-
-		var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "Hello");
-#endif
-		MenuItemDetails [] menuItems = {
-			new MenuItemDetails ("F_ind", "", null),
-			new MenuItemDetails ("_Replace", "", null),
-			new MenuItemDetails ("_Item1", "", null),
-			new MenuItemDetails ("_Also From Sub Menu", "", null)
-		};
-
-		menuItems [0].Action = () => ShowMenuItem (menuItems [0]);
-		menuItems [1].Action = () => ShowMenuItem (menuItems [1]);
-		menuItems [2].Action = () => ShowMenuItem (menuItems [2]);
-		menuItems [3].Action = () => ShowMenuItem (menuItems [3]);
-
-		MenuItem miUseSubMenusSingleFrame = null;
-		var useSubMenusSingleFrame = false;
-		MenuItem miUseKeysUpDownAsKeysLeftRight = null;
-		var useKeysUpDownAsKeysLeftRight = false;
-
-		MenuItem miHeightAsBuffer = null;
-
-		menu = new MenuBar (new MenuBarItem [] {
-			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("Text _Editor Demo", "", () => { running = Editor; Application.RequestStop (); }, null, null, Key.AltMask | Key.CtrlMask | Key.D),
-				new MenuItem ("_New", "Creates new file", NewFile, null, null, Key.AltMask | Key.CtrlMask| Key.N),
-				new MenuItem ("_Open", "", Open, null, null, Key.AltMask | Key.CtrlMask| Key.O),
-				new MenuItem ("_Hex", "", () => { running = ShowHex; Application.RequestStop (); }, null, null, Key.AltMask | Key.CtrlMask | Key.H),
-				new MenuItem ("_Close", "", Close, null, null, Key.AltMask | Key.Q),
-				new MenuItem ("_Disabled", "", () => { }, () => false),
-				new MenuBarItem ("_SubMenu Disabled", new MenuItem [] {
-					new MenuItem ("_Disabled", "", () => { }, () => false)
-				}),
-				null,
-				new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } }, null, null, Key.CtrlMask | Key.Q)
-			}),
-			new MenuBarItem ("_Edit", new MenuItem [] {
-				new MenuItem ("_Copy", "", Copy, null, null, Key.AltMask | Key.CtrlMask | Key.C),
-				new MenuItem ("C_ut", "", Cut, null, null, Key.AltMask | Key.CtrlMask| Key.X),
-				new MenuItem ("_Paste", "", Paste, null, null, Key.AltMask | Key.CtrlMask| Key.V),
-				new MenuBarItem ("_Find and Replace",
-					new MenuItem [] { menuItems [0], menuItems [1] }),
-				menuItems[3],
-				miUseKeysUpDownAsKeysLeftRight = new MenuItem ("Use_KeysUpDownAsKeysLeftRight", "",
-				() => {
-				menu.UseKeysUpDownAsKeysLeftRight = miUseKeysUpDownAsKeysLeftRight.Checked = useKeysUpDownAsKeysLeftRight = !useKeysUpDownAsKeysLeftRight;
-					miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = menu.UseSubMenusSingleFrame;
-					}) {
-					CheckType = MenuItemCheckStyle.Checked, Checked = useKeysUpDownAsKeysLeftRight
-				},
-				miUseSubMenusSingleFrame = new MenuItem ("Use_SubMenusSingleFrame", "",
-				() => {
-				menu.UseSubMenusSingleFrame = miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = !useSubMenusSingleFrame;
-					miUseKeysUpDownAsKeysLeftRight.Checked = useKeysUpDownAsKeysLeftRight = menu.UseKeysUpDownAsKeysLeftRight;
-					}) {
-					CheckType = MenuItemCheckStyle.Checked, Checked = useSubMenusSingleFrame
-				},
-				miHeightAsBuffer = new MenuItem ("_Height As Buffer", "", () => {
-					miHeightAsBuffer.Checked = heightAsBuffer = !heightAsBuffer;
-					Application.HeightAsBuffer = heightAsBuffer;
-				}) { CheckType = MenuItemCheckStyle.Checked, Checked = heightAsBuffer }
-			}),
-			new MenuBarItem ("_List Demos", new MenuItem [] {
-				new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true), null, null, Key.AltMask + 0.ToString () [0]),
-				new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false), null, null, Key.AltMask + 1.ToString () [0]),
-				new MenuItem ("Search Single Item", "", ComboBoxDemo, null, null, Key.AltMask + 2.ToString () [0])
-			}),
-			new MenuBarItem ("A_ssorted", new MenuItem [] {
-				new MenuItem ("_Show text alignments", "", () => ShowTextAlignments (), null, null, Key.AltMask | Key.CtrlMask | Key.G),
-				new MenuItem ("_OnKeyDown/Press/Up", "", () => OnKeyDownPressUpDemo (), null, null, Key.AltMask | Key.CtrlMask | Key.K)
-			}),
-			new MenuBarItem ("_Test Menu and SubMenus", new MenuBarItem [] {
-				new MenuBarItem ("SubMenu1Item_1",  new MenuBarItem [] {
-					new MenuBarItem ("SubMenu2Item_1", new MenuBarItem [] {
-						new MenuBarItem ("SubMenu3Item_1",
-							new MenuItem [] { menuItems [2] })
-					})
-				})
-			}),
-			new MenuBarItem ("_About...", "Demonstrates top-level menu item", () =>  MessageBox.ErrorQuery (50, 7, "About Demo", "This is a demo app for gui.cs", "Ok")),
-		});
-
-		menuKeysStyle = new CheckBox (3, 25, "UseKeysUpDownAsKeysLeftRight", true);
-		menuKeysStyle.Toggled += MenuKeysStyle_Toggled;
-		menuAutoMouseNav = new CheckBox (40, 25, "UseMenuAutoNavigation", true);
-		menuAutoMouseNav.Toggled += MenuAutoMouseNav_Toggled;
-
-		ShowEntries (win);
-
-		int count = 0;
-		ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
-		Application.RootMouseEvent += delegate (MouseEvent me) {
-			ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
-		};
-
-		var test = new Label (3, 18, "Se iniciará el análisis");
-		win.Add (test);
-		win.Add (ml);
-
-		var drag = new Label ("Drag: ") { X = 70, Y = 22 };
-		var dragText = new TextField ("") {
-			X = Pos.Right (drag),
-			Y = Pos.Top (drag),
-			Width = 40
-		};
-
-		var statusBar = new StatusBar (new StatusItem [] {
-			new StatusItem(Key.F1, "~F1~ Help", () => Help()),
-			new StatusItem(Key.F2, "~F2~ Load", Load),
-			new StatusItem(Key.F3, "~F3~ Save", Save),
-			new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { if (Quit ()) { running = null; top.Running = false; } }),
-			new StatusItem(Key.Null, Application.Driver.GetType().Name, null)
-		});
-
-		win.Add (drag, dragText);
-
-		var bottom = new Label ("This should go on the bottom of the same top-level!");
-		win.Add (bottom);
-		var bottom2 = new Label ("This should go on the bottom of another top-level!");
-		top.Add (bottom2);
-
-		top.LayoutComplete += (e) => {
-			bottom.X = win.X;
-			bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin;
-			bottom2.X = Pos.Left (win);
-			bottom2.Y = Pos.Bottom (win);
-		};
-
-		win.KeyPress += Win_KeyPress;
-
-		top.Add (win);
-		//top.Add (menu);
-		top.Add (menu, statusBar);
-		Application.Run (top);
-	}
-
-	private static void Win_KeyPress (View.KeyEventEventArgs e)
-	{
-		switch (ShortcutHelper.GetModifiersKey (e.KeyEvent)) {
-		case Key.CtrlMask | Key.T:
-			if (menu.IsMenuOpen)
-				menu.CloseMenu ();
-			else
-				menu.OpenMenu ();
-			e.Handled = true;
-			break;
-		}
-	}
-}

+ 64 - 55
README.md

@@ -12,6 +12,20 @@ A toolkit for building rich console apps for .NET, .NET Core, and Mono that work
 
 ![Sample app](docfx/images/sample.gif)
 
+
+## Quick Start
+
+Paste these commands into your favorite terminal on Windows, Mac, or Linux. This will install the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.Gui.templates), create a new "Hello World" TUI app, and run it.
+
+(Press `CTRL-Q` to exit the app)
+
+```powershell
+dotnet new --install Terminal.Gui.templates
+dotnet new tui -n myproj
+cd myproj
+dotnet run
+```
+
 ## Documentation 
 
 * [Documentation Home](https://gui-cs.github.io/Terminal.Gui/index.html)
@@ -36,44 +50,43 @@ descriptors. Most classes are safe for threading.
 ## Showcase & Examples
 
 * **[UI Catalog](https://github.com/gui-cs/Terminal.Gui/tree/master/UICatalog)** - The UI Catalog project provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run --project UICatalog` to run the UI Catalog.
-* **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers.
-* **[Example (aka `demo.cs`)](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the simple demo.
-* **[Standalone Example](https://github.com/gui-cs/Terminal.Gui/tree/master/StandaloneExample)** - A trivial .NET core sample application can be found in the `StandaloneExample` directory. Run `dotnet run` in directory to test.
+* **[C# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/Example)** - Run `dotnet run` in the `Example` directory to run the C# Example.
 * **[F# Example](https://github.com/gui-cs/Terminal.Gui/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#.
-* **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools/blob/master/docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md)** - `OCGV` sends the output from a command to  an interactive table. 
+* **[Reactive Example](https://github.com/gui-cs/Terminal.Gui/tree/master/ReactiveExample)** - A sample app that shows how to use `System.Reactive` and `ReactiveUI` with `Terminal.Gui`. The app uses the MVVM architecture that may seem familiar to folks coming from WPF, Xamarin Forms, UWP, Avalonia, or Windows Forms. In this app, we implement the data bindings using ReactiveUI `WhenAnyValue` syntax and [Pharmacist](https://github.com/reactiveui/pharmacist) — a tool that converts all events in a NuGet package into observable wrappers.
+* **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools)** - `OCGV` sends the output from a command to  an interactive table. 
 * **[PoshRedisViewer](https://github.com/En3Tho/PoshRedisViewer)** - A compact Redis viewer module for PowerShell written in F# and Gui.cs
 * **[TerminalGuiDesigner](https://github.com/tznind/TerminalGuiDesigner)** - Cross platform view designer for building Terminal.Gui applications.
 
 See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured. The [Conceptual Documentation](https://gui-cs.github.io/Terminal.Gui/articles/index.html) provides insight into core concepts.
 
-## Sample Usage
-(This code uses C# 9.0 [Top-level statements](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#top-level-statements).) 
+## Sample Usage in C#
+
 ```csharp
+// A simple Terminal.Gui example in C# - using C# 9.0 Top-level statements
+
 using Terminal.Gui;
 using NStack;
 
-Application.Init();
-var top = Application.Top;
+Application.Init ();
 
 // Creates the top-level window to show
-var win = new Window("MyApp")
-{
+var win = new Window ("Example App") {
 	X = 0,
 	Y = 1, // Leave one row for the toplevel menu
 
-	// By using Dim.Fill(), it will automatically resize without manual intervention
-	Width = Dim.Fill(),
-	Height = Dim.Fill()
+	// By using Dim.Fill(), this Window will automatically resize without manual intervention
+	Width = Dim.Fill (),
+	Height = Dim.Fill ()
 };
 
-top.Add(win);
+Application.Top.Add (win);
 
 // Creates a menubar, the item "New" has a help menu.
-var menu = new MenuBar(new MenuBarItem[] {
+var menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("_New", "Creates new file", null),
+				new MenuItem ("_New", "Creates a new file", null),
 				new MenuItem ("_Close", "",null),
-				new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
+				new MenuItem ("_Quit", "", () => { if (Quit ()) Application.Top.Running = false; })
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", null),
@@ -81,49 +94,49 @@ var menu = new MenuBar(new MenuBarItem[] {
 				new MenuItem ("_Paste", "", null)
 			})
 		});
-top.Add(menu);
+Application.Top.Add (menu);
 
-static bool Quit()
+static bool Quit ()
 {
-	var n = MessageBox.Query(50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
+	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
+var login = new Label ("Login: ") { X = 3, Y = 2 };
+var password = new Label ("Password: ") {
+	X = Pos.Left (login),
+	Y = Pos.Top (login) + 1
 };
-var loginText = new TextField("")
-{
-	X = Pos.Right(password),
-	Y = Pos.Top(login),
+var loginText = new TextField ("") {
+	X = Pos.Right (password),
+	Y = Pos.Top (login),
 	Width = 40
 };
-var passText = new TextField("")
-{
+var passText = new TextField ("") {
 	Secret = true,
-	X = Pos.Left(loginText),
-	Y = Pos.Top(password),
-	Width = Dim.Width(loginText)
+	X = Pos.Left (loginText),
+	Y = Pos.Top (password),
+	Width = Dim.Width (loginText)
 };
 
-// Add some controls, 
-win.Add(
-	// The ones with my favorite layout system, Computed
+// Add the views to the main window, 
+win.Add (
+	// Using Computed Layout:
 	login, password, loginText, passText,
 
-	// The ones laid out like an australopithecus, with Absolute positions:
-	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")
+	// 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")
 );
 
-Application.Run();
-Application.Shutdown();
+// Run blocks until the user quits the application
+Application.Run ();
+
+// Always bracket Application.Init with .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)**.
@@ -154,12 +167,14 @@ To install Terminal.Gui into a .NET Core project, use the `dotnet` CLI tool with
 dotnet add package Terminal.Gui
 ```
 
-See [CONTRIBUTING.md](CONTRIBUTING.md) for instructions for downloading and forking the source.
+Or, you can use the [Terminal.Gui.Templates](https://github.com/gui-cs/Terminal.Gui.templates).
 
-## Running and Building
+## Building the Library and Running the Examples
 
 * Windows, Mac, and Linux - Build and run using the .NET SDK command line tools (`dotnet build` in the root directory). Run `UICatalog` with `dotnet run --project UICatalog`.
-* Windows - Open `Terminal.Gui.sln` with Visual Studio 2019.
+* Windows - Open `Terminal.sln` with Visual Studio 2022.
+
+See [CONTRIBUTING.md](CONTRIBUTING.md) for instructions for downloading and forking the source.
 
 ## Contributing
 
@@ -169,10 +184,4 @@ Debates on architecture and design can be found in Issues tagged with [design](h
 
 ## History
 
-This is an updated version of [gui.cs](http://tirania.org/blog/archive/2007/Apr-16.html) that Miguel wrote for [mono-curses](https://github.com/mono/mono-curses) in 2007.
-
-The original **gui.cs** was a UI toolkit in a single file and tied to curses. This version tries to be console-agnostic and instead of having a container/widget model, only uses Views (which can contain subviews) and changes the rendering model to rely on damage regions instead of burdening each view with the details.
-
-A presentation of this was part of the [Retro.NET](https://channel9.msdn.com/Events/dotnetConf/2018/S313) talk at .NET Conf 2018 [Slides](https://tirania.org/Retro.pdf)
-
-The most recent release notes can be found in the [Terminal.Gui.csproj](https://github.com/gui-cs/Terminal.Gui/blob/master/Terminal.Gui/Terminal.Gui.csproj) file.
+See [gui-cs](https://github.com/gui-cs/) for how this project came to be.

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -13,7 +13,7 @@
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="18.3.1" />
     <PackageReference Include="ReactiveUI" Version="18.3.1" />
-    <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.1.4" PrivateAssets="all" />
+    <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.2.3" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />

+ 0 - 265
StandaloneExample/Program.cs

@@ -1,265 +0,0 @@
-namespace StandaloneExample {
-	using System.Linq;
-	using Terminal.Gui;
-	using System;
-	using NStack;
-	using System.Text;
-	using Rune = System.Rune;
-
-	static class Demo {
-		class Box10x : View {
-			public Box10x (int x, int y) : base (new Rect (x, y, 10, 10))
-			{
-			}
-
-			public override void Redraw (Rect region)
-			{
-				Driver.SetAttribute (ColorScheme.Focus);
-
-				for (int y = 0; y < 10; y++) {
-					Move (0, y);
-					for (int x = 0; x < 10; x++) {
-						Driver.AddRune ((Rune)('0' + ((x + y) % 10)));
-					}
-				}
-			}
-		}
-
-		class Filler : View {
-			public Filler (Rect rect) : base (rect)
-			{
-			}
-
-			public override void Redraw (Rect region)
-			{
-				Driver.SetAttribute (ColorScheme.Focus);
-				var f = Frame;
-
-				for (int y = 0; y < f.Width; y++) {
-					Move (0, y);
-					for (int x = 0; x < f.Height; x++) {
-						var r = (x % 3) switch {
-							0 => '.',
-							1 => 'o',
-							_ => 'O',
-						};
-						Driver.AddRune (r);
-					}
-				}
-			}
-		}
-
-		static void ShowTextAlignments ()
-		{
-			var container = new Window ("Show Text Alignments - Press Esc to return") {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			container.KeyUp += (e) => {
-				if (e.KeyEvent.Key == Key.Esc)
-					container.Running = false;
-			};
-
-			const int i = 0;
-			const string txt = "Hello world, how are you doing today?";
-			container.Add (
-					new Label ($"{i + 1}-{txt}") { TextAlignment = TextAlignment.Left, Y = 3, Width = Dim.Fill () },
-					new Label ($"{i + 2}-{txt}") { TextAlignment = TextAlignment.Right, Y = 5, Width = Dim.Fill () },
-					new Label ($"{i + 3}-{txt}") { TextAlignment = TextAlignment.Centered, Y = 7, Width = Dim.Fill () },
-					new Label ($"{i + 4}-{txt}") { TextAlignment = TextAlignment.Justified, Y = 9, Width = Dim.Fill () }
-				);
-
-			Application.Run (container);
-		}
-
-		static void ShowEntries (View container)
-		{
-			scrollView = new ScrollView (new Rect (50, 10, 20, 8)) {
-				ContentSize = new Size (100, 100),
-				ContentOffset = new Point (-1, -1),
-				ShowVerticalScrollIndicator = true,
-				ShowHorizontalScrollIndicator = true
-			};
-
-			AddScrollViewChild ();
-
-			// This is just to debug the visuals of the scrollview when small
-			var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) {
-				ContentSize = new Size (100, 100),
-				ShowVerticalScrollIndicator = true,
-				ShowHorizontalScrollIndicator = true
-			};
-			scrollView2.Add (new Box10x (0, 0));
-			var progress = new ProgressBar (new Rect (68, 1, 10, 1));
-			bool timer (MainLoop _)
-			{
-				progress.Pulse ();
-				return true;
-			}
-
-			Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
-
-			// A little convoluted, this is because I am using this to test the
-			// layout based on referencing elements of another view:
-
-			var login = new Label ("Login: ") { X = 3, Y = 6 };
-			var password = new Label ("Password: ") {
-				X = Pos.Left (login),
-				Y = Pos.Bottom (login) + 1
-			};
-			var loginText = new TextField ("") {
-				X = Pos.Right (password),
-				Y = Pos.Top (login),
-				Width = 40
-			};
-			var passText = new TextField ("") {
-				Secret = true,
-				X = Pos.Left (loginText),
-				Y = Pos.Top (password),
-				Width = Dim.Width (loginText)
-			};
-
-			// Add some content
-			container.Add (
-				login,
-				loginText,
-				password,
-				passText,
-				new FrameView (new Rect (3, 10, 25, 6), "Options", new View [] {
-				new CheckBox (1, 0, "Remember me"),
-				new RadioGroup (1, 2, new ustring [] { "_Personal", "_Company" }) }
-				),
-				new ListView (new Rect (60, 6, 16, 4), new string [] {
-				"First row",
-				"<>",
-				"This is a very long row that should overflow what is shown",
-				"4th",
-				"There is an empty slot on the second row",
-				"Whoa",
-				"This is so cool"
-				}),
-				scrollView,
-				scrollView2,
-				new Button ("Ok") { X = 3, Y = 19 },
-				new Button ("Cancel") { X = 10, Y = 19 },
-				progress,
-				new Label ("Press F9 (on Unix ESC+9 is an alias) to activate the menubar") { X = 3, Y = 22 }
-			);
-		}
-
-		private static void AddScrollViewChild ()
-		{
-			if (isBox10x) {
-				scrollView.Add (new Box10x (0, 0));
-			} else {
-				scrollView.Add (new Filler (new Rect (0, 0, 40, 40)));
-			}
-			scrollView.ContentOffset = Point.Empty;
-		}
-
-		static void NewFile ()
-		{
-			var okButton = new Button ("Ok", is_default: true);
-			okButton.Clicked += () => Application.RequestStop ();
-			var cancelButton = new Button ("Cancel");
-			cancelButton.Clicked += () => Application.RequestStop ();
-
-			var d = new Dialog (
-			    "New File", 50, 20,
-			    okButton,
-			    cancelButton);
-
-			var ml2 = new Label (1, 1, "Mouse Debug Line");
-			d.Add (ml2);
-			Application.Run (d);
-		}
-
-		static bool Quit ()
-		{
-			var n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
-			return n == 0;
-		}
-
-		static void Close ()
-		{
-			MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok");
-		}
-
-		private static void ScrollViewCheck ()
-		{
-			isBox10x = miScrollViewCheck.Children [0].Checked = !miScrollViewCheck.Children [0].Checked;
-			miScrollViewCheck.Children [1].Checked = !miScrollViewCheck.Children [1].Checked;
-
-			scrollView.RemoveAll ();
-			AddScrollViewChild ();
-		}
-
-		public static Label ml;
-		private static MenuBarItem miScrollViewCheck;
-		private static bool isBox10x = true;
-		private static Window win;
-		private static ScrollView scrollView;
-
-		static void Main (string [] args)
-		{
-			if (args.Length > 0 && args.Contains ("-usc")) {
-				Application.UseSystemConsole = true;
-			}
-
-			Console.OutputEncoding = Encoding.Default;
-
-			Application.Init ();
-
-			var top = Application.Top;
-
-			win = new Window ("Hello") {
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill (),
-				Height = Dim.Fill () - 1
-			};
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("_New", "Creates new file", NewFile),
-					new MenuItem ("_Open", "", null),
-					new MenuItem ("_Close", "", () => Close ()),
-					new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
-				}),
-				new MenuBarItem ("_Edit", new MenuItem [] {
-					new MenuItem ("_Copy", "", null),
-					new MenuItem ("C_ut", "", null),
-					new MenuItem ("_Paste", "", null)
-				}),
-				new MenuBarItem ("A_ssorted", new MenuItem [] {
-					new MenuItem ("_Show text alignments", "", () => ShowTextAlignments (), null, null, Key.AltMask | Key.CtrlMask | Key.G)
-				}),
-				miScrollViewCheck = new MenuBarItem ("ScrollView", new MenuItem [] {
-					new MenuItem ("Box10x", "", () => ScrollViewCheck()) {CheckType = MenuItemCheckStyle.Radio, Checked = true },
-					new MenuItem ("Filler", "", () => ScrollViewCheck()) {CheckType = MenuItemCheckStyle.Radio }
-				})
-			});
-
-			ShowEntries (win);
-			int count = 0;
-			ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
-			Application.RootMouseEvent += (me) => ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
-
-			win.Add (ml);
-
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(Key.F1, "~F1~ Help", () => MessageBox.Query (50, 7, "Help", "Helping", "Ok")),
-				new StatusItem(Key.F2, "~F2~ Load", () => MessageBox.Query (50, 7, "Load", "Loading", "Ok")),
-				new StatusItem(Key.F3, "~F3~ Save", () => MessageBox.Query (50, 7, "Save", "Saving", "Ok")),
-				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { if (Quit ()) top.Running = false; }),
-				new StatusItem(Key.Null, Application.Driver.GetType().Name, null)
-			});
-
-			top.Add (win, menu, statusBar);
-			Application.Run ();
-
-			Application.Shutdown ();
-		}
-	}
-}

+ 0 - 11
StandaloneExample/Properties/launchSettings.json

@@ -1,11 +0,0 @@
-{
-  "profiles": {
-    "StandaloneExample": {
-      "commandName": "Project"
-    },
-    "StandaloneExample-usc": {
-      "commandName": "Project",
-      "commandLineArgs": "-usc"
-    }
-  }
-}

+ 0 - 9
StandaloneExample/README.md

@@ -1,9 +0,0 @@
-This is just a simple standalone sample that shows how to consume Terinal.Gui from a NuGet package and .NET Core project.
-
-Simply run:
-
-```
-$ dotnet run
-```
-
-To launch the application. Or use Visual Studio, open the solution and run.

+ 0 - 10
StandaloneExample/StandaloneExample.csproj

@@ -1,10 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-  <PropertyGroup>
-    <LangVersion>latest</LangVersion>
-    <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
-  </PropertyGroup>
-  <ItemGroup>
-    <PackageReference Include="Terminal.Gui" Version="*" />
-  </ItemGroup>
-</Project>

+ 0 - 25
StandaloneExample/StandaloneExample.sln

@@ -1,25 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.30011.22
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StandaloneExample", "StandaloneExample.csproj", "{8DC768EF-530D-4261-BD35-FC41E13B041E}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{8DC768EF-530D-4261-BD35-FC41E13B041E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{8DC768EF-530D-4261-BD35-FC41E13B041E}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{8DC768EF-530D-4261-BD35-FC41E13B041E}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{8DC768EF-530D-4261-BD35-FC41E13B041E}.Release|Any CPU.Build.0 = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-	GlobalSection(ExtensibilityGlobals) = postSolution
-		SolutionGuid = {C9C434AC-B7E4-43AB-834A-F9489766FDFF}
-	EndGlobalSection
-EndGlobal

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

@@ -617,7 +617,7 @@ namespace Terminal.Gui {
 			return keyModifiers;
 		}
 
-		void ProcessInput (Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
+		void ProcessInput ()
 		{
 			int wch;
 			var code = Curses.get_wch (out wch);
@@ -787,6 +787,8 @@ namespace Terminal.Gui {
 		}
 
 		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
 			Curses.timeout (0);
 			this.keyHandler = keyHandler;
+			this.keyDownHandler = keyDownHandler;
+			this.keyUpHandler = keyUpHandler;
 			this.mouseHandler = mouseHandler;
 
 			var mLoop = mainLoop.Driver as UnixMainLoop;
 
 			mLoop.AddWatch (0, UnixMainLoop.Condition.PollIn, x => {
-				ProcessInput (keyHandler, keyDownHandler, keyUpHandler, mouseHandler);
+				ProcessInput ();
 				return true;
 			});
 
@@ -1128,26 +1132,48 @@ namespace Terminal.Gui {
 			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 {
-				k = (Key)keyChar;
+				key = (Key)keyChar;
 			}
+
+			KeyModifiers km = new KeyModifiers ();
 			if (shift) {
-				k |= Key.ShiftMask;
+				if (keyChar == 0) {
+					key |= Key.ShiftMask;
+				}
+				km.Shift = shift;
 			}
 			if (alt) {
-				k |= Key.AltMask;
+				key |= Key.AltMask;
+				km.Alt = alt;
 			}
 			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)
@@ -1500,6 +1526,7 @@ namespace Terminal.Gui {
 				}
 			}) {
 				powershell.Start ();
+				powershell.WaitForExit ();
 				if (!powershell.DoubleWaitForExit ()) {
 					var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}.
 							Output: {powershell.StandardOutput.ReadToEnd ()}

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

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

+ 46 - 8
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs

@@ -256,6 +256,22 @@ namespace Terminal.Gui {
 			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)
 		{
 			switch (keyInfo.Key) {
@@ -263,6 +279,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.Esc);
 			case ConsoleKey.Tab:
 				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			case ConsoleKey.Clear:
+				return MapKeyModifiers (keyInfo, Key.Clear);
 			case ConsoleKey.Home:
 				return MapKeyModifiers (keyInfo, Key.Home);
 			case ConsoleKey.End:
@@ -289,6 +307,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.DeleteChar);
 			case ConsoleKey.Insert:
 				return MapKeyModifiers (keyInfo, Key.InsertChar);
+			case ConsoleKey.PrintScreen:
+				return MapKeyModifiers (keyInfo, Key.PrintScreen);
 
 			case ConsoleKey.Oem1:
 			case ConsoleKey.Oem2:
@@ -318,6 +338,9 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 					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.KeyChar == 0) {
 						return (Key)(((uint)Key.AltMask | (uint)Key.CtrlMask) | ((uint)Key.A + delta));
@@ -335,9 +358,14 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 					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));
 				}
+				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);
 			}
 			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F12) {
@@ -384,12 +412,14 @@ namespace Terminal.Gui {
 			return true;
 		}
 
+		Action<KeyEvent> keyDownHandler;
 		Action<KeyEvent> keyHandler;
 		Action<KeyEvent> keyUpHandler;
 		private CursorVisibility savedCursorVisibility;
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
+			this.keyDownHandler = keyDownHandler;
 			this.keyHandler = keyHandler;
 			this.keyUpHandler = keyUpHandler;
 
@@ -399,21 +429,29 @@ namespace Terminal.Gui {
 
 		void ProcessInput (ConsoleKeyInfo consoleKey)
 		{
-			keyModifiers = new KeyModifiers ();
-			var map = MapKey (consoleKey);
-			if (map == (Key)0xffffffff)
-				return;
-
-			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
-				keyModifiers.Alt = true;
+			if (consoleKey.Key == ConsoleKey.Packet) {
+				consoleKey = FromVKPacketToKConsoleKeyInfo (consoleKey);
 			}
+			keyModifiers = new KeyModifiers ();
 			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Shift)) {
 				keyModifiers.Shift = true;
 			}
+			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Alt)) {
+				keyModifiers.Alt = true;
+			}
 			if (consoleKey.Modifiers.HasFlag (ConsoleModifiers.Control)) {
 				keyModifiers.Ctrl = true;
 			}
+			var map = MapKey (consoleKey);
+			if (map == (Key)0xffffffff) {
+				if ((consoleKey.Modifiers & (ConsoleModifiers.Shift | ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) {
+					keyDownHandler (new KeyEvent (map, keyModifiers));
+					keyUpHandler (new KeyEvent (map, keyModifiers));
+				}
+				return;
+			}
 
+			keyDownHandler (new KeyEvent (map, keyModifiers));
 			keyHandler (new KeyEvent (map, keyModifiers));
 			keyUpHandler (new KeyEvent (map, keyModifiers));
 		}

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

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

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

@@ -534,12 +534,14 @@ namespace Terminal.Gui {
 			public ConsoleKeyInfo consoleKeyInfo;
 			public bool CapsLock;
 			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;
 				CapsLock = capslock;
 				NumLock = numlock;
+				Scrolllock = scrolllock;
 			}
 		}
 
@@ -786,7 +788,26 @@ namespace Terminal.Gui {
 		{
 			switch (inputEvent.EventType) {
 			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 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) {
 					KeyEvent key = new KeyEvent ();
 
@@ -854,6 +875,9 @@ namespace Terminal.Gui {
 						keyUpHandler (key);
 				} else {
 					if (inputEvent.KeyEvent.bKeyDown) {
+						// May occurs using SendKeys
+						if (keyModifiers == null)
+							keyModifiers = new KeyModifiers ();
 						// Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event
 						keyDownHandler (new KeyEvent (map, keyModifiers));
 						keyHandler (new KeyEvent (map, keyModifiers));
@@ -861,7 +885,7 @@ namespace Terminal.Gui {
 						keyUpHandler (new KeyEvent (map, keyModifiers));
 					}
 				}
-				if (!inputEvent.KeyEvent.bKeyDown) {
+				if (!inputEvent.KeyEvent.bKeyDown && inputEvent.KeyEvent.dwControlKeyState == 0) {
 					keyModifiers = null;
 				}
 				break;
@@ -1123,11 +1147,9 @@ namespace Terminal.Gui {
 				}
 
 			} else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) {
+				mouseFlag = MouseFlags.ReportMousePosition;
 				if (mouseEvent.MousePosition.X != pointMove.X || mouseEvent.MousePosition.Y != pointMove.Y) {
-					mouseFlag = MouseFlags.ReportMousePosition;
 					pointMove = new Point (mouseEvent.MousePosition.X, mouseEvent.MousePosition.Y);
-				} else {
-					mouseFlag = 0;
 				}
 			} else if (mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0) {
 				mouseFlag = 0;
@@ -1245,7 +1267,37 @@ namespace Terminal.Gui {
 
 			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)
@@ -1256,6 +1308,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.Esc);
 			case ConsoleKey.Tab:
 				return keyInfo.Modifiers == ConsoleModifiers.Shift ? Key.BackTab : Key.Tab;
+			case ConsoleKey.Clear:
+				return MapKeyModifiers (keyInfo, Key.Clear);
 			case ConsoleKey.Home:
 				return MapKeyModifiers (keyInfo, Key.Home);
 			case ConsoleKey.End:
@@ -1282,6 +1336,8 @@ namespace Terminal.Gui {
 				return MapKeyModifiers (keyInfo, Key.DeleteChar);
 			case ConsoleKey.Insert:
 				return MapKeyModifiers (keyInfo, Key.InsertChar);
+			case ConsoleKey.PrintScreen:
+				return MapKeyModifiers (keyInfo, Key.PrintScreen);
 
 			case ConsoleKey.NumPad0:
 				return keyInfoEx.NumLock ? Key.D0 : Key.InsertChar;
@@ -1334,6 +1390,9 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Alt) {
 					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.KeyChar == 0 || (keyInfo.KeyChar != 0 && keyInfo.KeyChar >= 1 && keyInfo.KeyChar <= 26)) {
 						return MapKeyModifiers (keyInfo, (Key)((uint)Key.A + delta));
@@ -1350,8 +1409,11 @@ namespace Terminal.Gui {
 				if (keyInfo.Modifiers == ConsoleModifiers.Control) {
 					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.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));
 					}
 				}
@@ -1375,7 +1437,7 @@ namespace Terminal.Gui {
 		private Key MapKeyModifiers (ConsoleKeyInfo keyInfo, Key key)
 		{
 			Key keyMod = new Key ();
-			if (CanShiftBeAdded (keyInfo))
+			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) != 0)
 				keyMod = Key.ShiftMask;
 			if ((keyInfo.Modifiers & ConsoleModifiers.Control) != 0)
 				keyMod |= Key.CtrlMask;
@@ -1385,20 +1447,6 @@ namespace Terminal.Gui {
 			return keyMod != Key.Null ? keyMod | key : key;
 		}
 
-		private bool CanShiftBeAdded (ConsoleKeyInfo keyInfo)
-		{
-			if ((keyInfo.Modifiers & ConsoleModifiers.Shift) == 0) {
-				return false;
-			}
-			if (keyInfo.Key == ConsoleKey.Packet) {
-				var ckiChar = keyInfo.KeyChar;
-				if (char.IsLetterOrDigit (ckiChar) || char.IsSymbol (ckiChar) || char.IsPunctuation (ckiChar)) {
-					return false;
-				}
-			}
-			return true;
-		}
-
 		public override void Init (Action terminalResized)
 		{
 			TerminalResized = terminalResized;
@@ -1677,9 +1725,7 @@ namespace Terminal.Gui {
 			}
 
 			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;
 			} else {
 				keyEvent.wVirtualKeyCode = '\0';

+ 40 - 2
Terminal.Gui/Core/Application.cs

@@ -581,7 +581,22 @@ namespace Terminal.Gui {
 			return top;
 		}
 
-		internal static View mouseGrabView;
+		static View mouseGrabView;
+
+		/// <summary>
+		/// The view that grabbed the mouse, to where will be routed all the mouse events.
+		/// </summary>
+		public static View MouseGrabView => mouseGrabView;
+
+		/// <summary>
+		/// Event to be invoked when a view grab the mouse.
+		/// </summary>
+		public static event Action<View> GrabbedMouse;
+
+		/// <summary>
+		/// Event to be invoked when a view ungrab the mouse.
+		/// </summary>
+		public static event Action<View> UnGrabbedMouse;
 
 		/// <summary>
 		/// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called.
@@ -592,6 +607,7 @@ namespace Terminal.Gui {
 		{
 			if (view == null)
 				return;
+			OnGrabbedMouse (view);
 			mouseGrabView = view;
 			Driver.UncookMouse ();
 		}
@@ -601,10 +617,27 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static void UngrabMouse ()
 		{
+			if (mouseGrabView == null)
+				return;
+			OnUnGrabbedMouse (mouseGrabView);
 			mouseGrabView = null;
 			Driver.CookMouse ();
 		}
 
+		static void OnGrabbedMouse (View view)
+		{
+			if (view == null)
+				return;
+			GrabbedMouse?.Invoke (view);
+		}
+
+		static void OnUnGrabbedMouse (View view)
+		{
+			if (view == null)
+				return;
+			UnGrabbedMouse?.Invoke (view);
+		}
+
 		/// <summary>
 		/// Merely a debugging aid to see the raw mouse events
 		/// </summary>
@@ -637,6 +670,11 @@ namespace Terminal.Gui {
 				me.View = view;
 			}
 			RootMouseEvent?.Invoke (me);
+
+			if (me.Handled) {
+				return;
+			}
+
 			if (mouseGrabView != null) {
 				if (view == null) {
 					UngrabMouse ();
@@ -656,7 +694,7 @@ namespace Terminal.Gui {
 					lastMouseOwnerView?.OnMouseLeave (me);
 				}
 				// System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
-				if (mouseGrabView != null && mouseGrabView.OnMouseEvent (nme)) {
+				if (mouseGrabView?.OnMouseEvent (nme) == true) {
 					return;
 				}
 			}

+ 2 - 1
Terminal.Gui/Core/Clipboard/ClipboardBase.cs

@@ -92,7 +92,8 @@ namespace Terminal.Gui {
 			try {
 				SetClipboardDataImpl (text);
 				return true;
-			} catch (Exception) {
+			} catch (Exception ex) {
+				System.Diagnostics.Debug.WriteLine ($"TrySetClipboardData: {ex.Message}");
 				return false;
 			}
 		}

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

@@ -681,11 +681,13 @@ namespace Terminal.Gui {
 		/// <param name="col">Column to move the cursor to.</param>
 		/// <param name="row">Row to move the cursor to.</param>
 		public abstract void Move (int col, int row);
+		
 		/// <summary>
 		/// Adds the specified rune to the display at the current cursor position
 		/// </summary>
 		/// <param name="rune">Rune to add.</param>
 		public abstract void AddRune (Rune rune);
+
 		/// <summary>
 		/// Ensures a Rune is not a control character and can be displayed by translating characters below 0x20
 		/// to equivalent, printable, Unicode chars.
@@ -694,21 +696,14 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		public static Rune MakePrintable (Rune c)
 		{
-			var controlChars = gethexaformat (c, 4);
-			if (controlChars <= 0x1F || (controlChars >= 0X7F && controlChars <= 0x9F)) {
+			var controlChars = c & 0xFFFF;
+			if (controlChars <= 0x1F || controlChars >= 0X7F && controlChars <= 0x9F) {
 				// ASCII (C0) control characters.
 				// C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1)
 				return new Rune (controlChars + 0x2400);
-			} else {
-				return c;
 			}
-		}
 
-		static uint gethexaformat (uint rune, int length)
-		{
-			var hex = rune.ToString ($"x{length}");
-			var hexstr = hex.Substring (hex.Length - length, length);
-			return (uint)int.Parse (hexstr, System.Globalization.NumberStyles.HexNumber);
+			return c;
 		}
 
 		/// <summary>

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

+ 97 - 26
Terminal.Gui/Core/Event.cs

@@ -77,11 +77,26 @@ namespace Terminal.Gui {
 		/// </summary>
 		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>
 		/// The key code for the user pressing the return key.
 		/// </summary>
 		Enter = '\n',
 
+		/// <summary>
+		/// The key code for the user pressing the clear key.
+		/// </summary>
+		Clear = 12,
+
 		/// <summary>
 		/// The key code for the user pressing the escape key
 		/// </summary>
@@ -363,15 +378,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		CtrlMask = 0x40000000,
 
-		/// <summary>
-		/// Backspace key.
-		/// </summary>
-		Backspace = 0x100000,
-
 		/// <summary>
 		/// Cursor up key
 		/// </summary>
-		CursorUp,
+		CursorUp = 0x100000,
 		/// <summary>
 		/// Cursor down key.
 		/// </summary>
@@ -393,21 +403,33 @@ namespace Terminal.Gui {
 		/// </summary>
 		PageDown,
 		/// <summary>
-		/// Home key
+		/// Home key.
 		/// </summary>
 		Home,
 		/// <summary>
-		/// End key
+		/// End key.
 		/// </summary>
 		End,
+
 		/// <summary>
-		/// Delete character key
+		/// Insert character key.
+		/// </summary>
+		InsertChar,
+
+		/// <summary>
+		/// Delete character key.
 		/// </summary>
 		DeleteChar,
+
 		/// <summary>
-		/// Insert character key
+		/// Shift-tab key (backwards tab key).
 		/// </summary>
-		InsertChar,
+		BackTab,
+
+		/// <summary>
+		/// Print screen character key.
+		/// </summary>
+		PrintScreen,
 
 		/// <summary>
 		/// F1 key.
@@ -457,15 +479,54 @@ namespace Terminal.Gui {
 		/// F12 key.
 		/// </summary>
 		F12,
-
 		/// <summary>
-		/// The key code for the user pressing the tab key (forwards tab key).
+		/// F13 key.
 		/// </summary>
-		Tab,
+		F13,
 		/// <summary>
-		/// Shift-tab key (backwards tab key).
+		/// F14 key.
 		/// </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>
 		/// A key with an unknown mapping was raised.
@@ -480,7 +541,7 @@ namespace Terminal.Gui {
 		KeyModifiers keyModifiers;
 
 		/// <summary>
-		/// Symb olid definition for the key.
+		/// Symbolic definition for the key.
 		/// </summary>
 		public Key Key;
 
@@ -573,7 +634,7 @@ namespace Terminal.Gui {
 				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;
 		}
@@ -706,38 +767,48 @@ namespace Terminal.Gui {
 	}
 
 	/// <summary>
-	/// Describes a mouse event
+	/// Low-level construct that conveys the details of mouse events, such
+	/// as coordinates and button state, from ConsoleDrivers up to <see cref="Application"/> and
+	/// Views.
 	/// </summary>
-	public struct MouseEvent {
+	/// <remarks>The <see cref="Application"/> class includes the <see cref="Application.RootMouseEvent"/>
+	/// Action which takes a MouseEvent argument.</remarks>
+	public class MouseEvent {
 		/// <summary>
 		/// The X (column) location for the mouse event.
 		/// </summary>
-		public int X;
+		public int X { get; set; }
 
 		/// <summary>
 		/// The Y (column) location for the mouse event.
 		/// </summary>
-		public int Y;
+		public int Y { get; set; }
 
 		/// <summary>
 		/// Flags indicating the kind of mouse event that is being posted.
 		/// </summary>
-		public MouseFlags Flags;
+		public MouseFlags Flags { get; set; }
 
 		/// <summary>
 		/// The offset X (column) location for the mouse event.
 		/// </summary>
-		public int OfX;
+		public int OfX { get; set; }
 
 		/// <summary>
 		/// The offset Y (column) location for the mouse event.
 		/// </summary>
-		public int OfY;
+		public int OfY { get; set; }
 
 		/// <summary>
 		/// The current view at the location for the mouse event.
 		/// </summary>
-		public View View;
+		public View View { get; set; }
+
+		/// <summary>
+		/// Indicates if the current mouse event has already been processed and the driver should stop notifying any other event subscriber.
+		/// Its important to set this value to true specially when updating any View's layout from inside the subscriber method.
+		/// </summary>
+		public bool Handled { get; set; }
 
 		/// <summary>
 		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="MouseEvent"/>.

+ 5 - 2
Terminal.Gui/Core/MainLoop.cs

@@ -306,9 +306,12 @@ namespace Terminal.Gui {
 
 			Driver.MainIteration ();
 
+			bool runIdle = false;
 			lock (idleHandlersLock) {
-				if (idleHandlers.Count > 0)
-					RunIdle ();
+				runIdle = idleHandlers.Count > 0;
+			}
+			if (runIdle) {
+				RunIdle ();
 			}
 		}
 

文件差异内容过多而无法显示
+ 177 - 174
Terminal.Gui/Core/View.cs


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

@@ -79,6 +79,9 @@
     <Summary>A toolkit for building rich console apps for .NET that works on Windows, Mac, and Linux/Unix.</Summary>
     <Title>Terminal.Gui - Cross Platform Terminal user interface toolkit for .NET</Title>
     <PackageReleaseNotes>
+      Release v1.8.1
+      * Fixes #2053. MessageBox.Query not wrapping correctly 
+      
       Release v1.8.0
       * Fixes #2043. Update to NStack v1.0.3
       * Fixes #2045. TrySetClipboardData test must be enclosed with a lock.

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

@@ -2,8 +2,24 @@
 
 namespace Terminal.Gui {
 	/// <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>
 	public sealed class ContextMenu : IDisposable {
 		private static MenuBar menuBar;
@@ -12,15 +28,15 @@ namespace Terminal.Gui {
 		private Toplevel container;
 
 		/// <summary>
-		/// Initialize a context menu with empty menu items.
+		/// Initializes a context menu with no menu items.
 		/// </summary>
 		public ContextMenu () : this (0, 0, new MenuBarItem ()) { }
 
 		/// <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>
 		/// <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) :
 			this (host.Frame.X, host.Frame.Y, menuItems)
 		{
@@ -28,10 +44,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Initialize a context menu with menu items.
+		/// Initializes a context menu with menu items at a specific screen location.
 		/// </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>
 		public ContextMenu (int x, int y, MenuBarItem menuItems)
 		{
@@ -48,7 +64,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Disposes the all the context menu objects instances.
+		/// Disposes the context menu object.
 		/// </summary>
 		public void Dispose ()
 		{
@@ -65,7 +81,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Open the <see cref="MenuItems"/> menu items.
+		/// Shows (opens) the ContextMenu, displaying the <see cref="MenuItem"/>s it contains.
 		/// </summary>
 		public void Show ()
 		{
@@ -110,13 +126,14 @@ namespace Terminal.Gui {
 			} else if (ForceMinimumPosToZero && position.Y < 0) {
 				position.Y = 0;
 			}
-
+			
 			menuBar = new MenuBar (new [] { MenuItems }) {
 				X = position.X,
 				Y = position.Y,
 				Width = 0,
 				Height = 0,
-				UseSubMenusSingleFrame = UseSubMenusSingleFrame
+				UseSubMenusSingleFrame = UseSubMenusSingleFrame,
+				Key = Key
 			};
 
 			menuBar.isContextMenuLoading = true;
@@ -138,7 +155,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Close the <see cref="MenuItems"/> menu items.
+		/// Hides (closes) the ContextMenu.
 		/// </summary>
 		public void Hide ()
 		{
@@ -157,7 +174,7 @@ namespace Terminal.Gui {
 		public event Action<MouseFlags> MouseFlagsChanged;
 
 		/// <summary>
-		/// Gets or set the menu position.
+		/// Gets or sets the menu position.
 		/// </summary>
 		public Point Position { get; set; }
 
@@ -167,7 +184,7 @@ namespace Terminal.Gui {
 		public MenuBarItem MenuItems { get; set; }
 
 		/// <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>
 		public Key Key {
 			get => key;
@@ -179,7 +196,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
 		public MouseFlags MouseFlags {
 			get => mouseFlags;
@@ -191,7 +208,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets information whether menu is showing or not.
+		/// Gets whether the ContextMenu is showing or not.
 		/// </summary>
 		public static bool IsShow { get; private set; }
 
@@ -202,8 +219,9 @@ namespace Terminal.Gui {
 		public View Host { get; set; }
 
 		/// <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>
 		public bool ForceMinimumPosToZero { get; set; } = true;
 
@@ -213,7 +231,9 @@ namespace Terminal.Gui {
 		public MenuBar MenuBar { get => menuBar; }
 
 		/// <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>
 		public bool UseSubMenusSingleFrame { get; set; }
 	}

+ 131 - 161
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 NStack;
 using System.Linq;
@@ -21,23 +11,24 @@ namespace Terminal.Gui {
 	[Flags]
 	public enum MenuItemCheckStyle {
 		/// <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>
 		NoCheck = 0b_0000_0000,
 
 		/// <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>
 		Checked = 0b_0000_0001,
 
 		/// <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>
 		Radio = 0b_0000_0010,
 	};
 
 	/// <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>
 	public class MenuItem {
 		ustring title;
@@ -78,14 +69,28 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
 		public Rune HotKey;
 
 		/// <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>
 		public Key Shortcut {
 			get => shortcutHelper.Shortcut;
@@ -97,12 +102,12 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
 		public ustring ShortcutTag => ShortcutHelper.GetShortcutTag (shortcutHelper.Shortcut);
 
 		/// <summary>
-		/// Gets or sets the title.
+		/// Gets or sets the title of the menu item .
 		/// </summary>
 		/// <value>The title.</value>
 		public ustring Title {
@@ -116,34 +121,46 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
 		/// <value>The help text.</value>
 		public ustring Help { get; set; }
 
 		/// <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>
 		/// <value>Method to invoke.</value>
 		public Action Action { get; set; }
 
 		/// <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>
-		/// <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; }
 
 		/// <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>
 		public bool IsEnabled ()
 		{
 			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>
 		/// 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; }
 
 		/// <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>
 		public MenuItemCheckStyle CheckType { get; set; }
 
 		/// <summary>
-		/// Gets or sets the parent for this <see cref="MenuItem"/>.
+		/// Gets the parent for this <see cref="MenuItem"/>.
 		/// </summary>
 		/// <value>The parent.</value>
 		public MenuItem Parent { get; internal set; }
@@ -167,7 +184,7 @@ namespace Terminal.Gui {
 		internal bool IsFromSubMenu { get { return Parent != null; } }
 
 		/// <summary>
-		/// Merely a debugging aid to see the interaction with main
+		/// Merely a debugging aid to see the interaction with main.
 		/// </summary>
 		public MenuItem GetMenuItem ()
 		{
@@ -175,7 +192,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Merely a debugging aid to see the interaction with main
+		/// Merely a debugging aid to see the interaction with main.
 		/// </summary>
 		public bool GetMenuBarItem ()
 		{
@@ -213,14 +230,15 @@ namespace Terminal.Gui {
 	}
 
 	/// <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>
 	public class MenuBarItem : MenuItem {
 		/// <summary>
 		/// Initializes a new <see cref="MenuBarItem"/> as a <see cref="MenuItem"/>.
 		/// </summary>
 		/// <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="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>
@@ -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)
 		{
 			foreach (var child in childrens) {
@@ -363,12 +368,6 @@ namespace Terminal.Gui {
 			Title = title;
 		}
 
-		///// <summary>
-		///// Gets or sets the title to display.
-		///// </summary>
-		///// <value>The title.</value>
-		//public ustring Title { get; set; }
-
 		/// <summary>
 		/// Gets or sets an array of <see cref="MenuItem"/> objects that are the children of this <see cref="MenuBarItem"/>
 		/// </summary>
@@ -391,8 +390,8 @@ namespace Terminal.Gui {
 			}
 			int minX = x;
 			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) {
 				minX = Math.Max (parent.Frame.Right - parent.Frame.Width - maxW, 0);
 			}
@@ -459,6 +458,7 @@ namespace Terminal.Gui {
 			return GetNormalColor ();
 		}
 
+		// Draws the Menu, within the Frame
 		public override void Redraw (Rect bounds)
 		{
 			Driver.SetAttribute (GetNormalColor ());
@@ -477,13 +477,14 @@ namespace Terminal.Gui {
 					Move (1, i + 1);
 
 				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)
 						continue;
 					if (item == null)
 						Driver.AddRune (Driver.HLine);
 					else if (i == 0 && p == 0 && host.UseSubMenusSingleFrame && item.Parent.Parent != null)
 						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)
 						Driver.AddRune (Driver.RightArrow);
 					else
@@ -527,6 +528,7 @@ namespace Terminal.Gui {
 							HotKeySpecifier = MenuBar.HotKeySpecifier,
 							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)),
 							i == current ? ColorScheme.Focus : GetNormalColor (),
 							i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
@@ -630,7 +632,7 @@ namespace Terminal.Gui {
 					}
 				}
 			}
-			return false;
+			return host.ProcessHotKey (kb);
 		}
 
 		void RunSelected ()
@@ -832,17 +834,27 @@ namespace Terminal.Gui {
 		}
 	}
 
-
-
 	/// <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>
 	/// <remarks>
 	///	<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>
-	///	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>
 	/// </remarks>
 	public class MenuBar : View {
@@ -850,7 +862,7 @@ namespace Terminal.Gui {
 		internal int selectedSub;
 
 		/// <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>
 		/// <value>The menu array.</value>
 		public MenuBarItem [] Menus { get; set; }
@@ -873,7 +885,7 @@ namespace Terminal.Gui {
 
 		static ustring shortcutDelimiter = "+";
 		/// <summary>
-		/// Used for change the shortcut delimiter separator.
+		/// Sets or gets the shortcut delimiter separator. The default is "+".
 		/// </summary>
 		public static ustring ShortcutDelimiter {
 			get => shortcutDelimiter;
@@ -893,6 +905,13 @@ namespace Terminal.Gui {
 
 		/// <summary>
 		/// 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>
 		public bool 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>
 		/// Initializes a new instance of the <see cref="MenuBar"/>.
 		/// </summary>
@@ -1024,6 +1048,14 @@ namespace Terminal.Gui {
 			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/>
 		public override void Redraw (Rect bounds)
 		{
@@ -1033,7 +1065,7 @@ namespace Terminal.Gui {
 				Driver.AddRune (' ');
 
 			Move (1, 0);
-			int pos = 1;
+			int pos = 0;
 
 			for (int i = 0; i < Menus.Length; i++) {
 				var menu = Menus [i];
@@ -1041,17 +1073,14 @@ namespace Terminal.Gui {
 				Attribute hotColor, normalColor;
 				if (i == selected && IsMenuOpen) {
 					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;
 					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 ();
 		}
@@ -1066,14 +1095,10 @@ namespace Terminal.Gui {
 			for (int i = 0; i < Menus.Length; i++) {
 				if (i == selected) {
 					pos++;
-					if (IsMenuOpen)
-						Move (pos + 1, 0);
-					else {
-						Move (pos + 1, 0);
-					}
+					Move (pos + 1, 0);
 					return;
 				} 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;
 
 		/// <summary>
-		/// Raised when all the menu are closed.
+		/// Raised when all the menu is closed.
 		/// </summary>
 		public event Action MenuAllClosed;
 
@@ -1134,7 +1159,7 @@ namespace Terminal.Gui {
 		internal bool isMenuClosing;
 
 		/// <summary>
-		/// True if the menu is open; otherwise false.
+		/// <see langword="true"/> if the menu is open; otherwise <see langword="true"/>.
 		/// </summary>
 		public bool IsMenuOpen { get; protected set; }
 
@@ -1167,7 +1192,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuClosing"/>
+		/// Virtual method that will invoke the <see cref="MenuClosing"/>.
 		/// </summary>
 		/// <param name="currentMenu">The current menu to be closed.</param>
 		/// <param name="reopen">Whether the current menu will be reopen.</param>
@@ -1180,7 +1205,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Virtual method that will invoke the <see cref="MenuAllClosed"/>
+		/// Virtual method that will invoke the <see cref="MenuAllClosed"/>.
 		/// </summary>
 		public virtual void OnMenuAllClosed ()
 		{
@@ -1190,7 +1215,7 @@ namespace Terminal.Gui {
 		View lastFocused;
 
 		/// <summary>
-		/// Get the lasted focused view before open the menu.
+		/// Gets the view that was last focused before opening the menu.
 		/// </summary>
 		public View LastFocused { get; private set; }
 
@@ -1208,6 +1233,7 @@ namespace Terminal.Gui {
 			int pos = 0;
 			switch (subMenu) {
 			case null:
+				// Open a submenu below a MenuBar
 				lastFocused = lastFocused ?? (SuperView == null ? Application.Current.MostFocused : SuperView.MostFocused);
 				if (openSubMenu != null && !CloseMenu (false, true))
 					return;
@@ -1220,8 +1246,10 @@ namespace Terminal.Gui {
 					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++)
-					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]);
 				openCurrentMenu = openMenu;
 				openCurrentMenu.previousSubFocused = openMenu;
@@ -1234,6 +1262,7 @@ namespace Terminal.Gui {
 				openMenu.SetFocus ();
 				break;
 			default:
+				// Opens a submenu next to another submenu (openSubMenu)
 				if (openSubMenu == null)
 					openSubMenu = new List<Menu> ();
 				if (sIndex > -1) {
@@ -1274,7 +1303,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Opens the current Menu programatically.
+		/// Opens the Menu programatically, as though the F9 key were pressed.
 		/// </summary>
 		public void OpenMenu ()
 		{
@@ -1356,7 +1385,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <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>
 		public bool CloseMenu (bool ignoreUseSubMenusSingleFrame = false)
 		{
@@ -1458,26 +1487,6 @@ namespace Terminal.Gui {
 			if (openSubMenu.Count > 0)
 				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;
 		}
 
@@ -1678,7 +1687,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool ProcessHotKey (KeyEvent kb)
 		{
-			if (kb.Key == Key.F9) {
+			if (kb.Key == Key) {
 				if (!IsMenuOpen)
 					OpenMenu ();
 				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 ||
 				(me.Flags == MouseFlags.ReportMousePosition && selected > -1) ||
 				(me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && selected > -1)) {
-				int pos = 1;
+				int pos = xOrigin;
 				int cx = me.X;
 				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 (Menus [i].IsTopLevel) {
 								var menu = new Menu (this, i, 0, Menus [i]);
@@ -1805,7 +1814,7 @@ namespace Terminal.Gui {
 						}
 						return true;
 					}
-					pos += 1 + Menus [i].TitleLength + 2;
+					pos += leftPadding + Menus [i].TitleLength + rightPadding;
 				}
 			}
 			return false;
@@ -1816,7 +1825,7 @@ namespace Terminal.Gui {
 
 		internal bool HandleGrabView (MouseEvent me, View current)
 		{
-			if (Application.mouseGrabView != null) {
+			if (Application.MouseGrabView != null) {
 				if (me.View is MenuBar || me.View is Menu) {
 					var mbar = GetMouseGrabViewInstance (me.View);
 					if (mbar != null) {
@@ -1878,47 +1887,6 @@ namespace Terminal.Gui {
 				handled = 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;
 
@@ -1927,7 +1895,7 @@ namespace Terminal.Gui {
 
 		MenuBar GetMouseGrabViewInstance (View view)
 		{
-			if (view == null || Application.mouseGrabView == null) {
+			if (view == null || Application.MouseGrabView == null) {
 				return null;
 			}
 
@@ -1938,7 +1906,7 @@ namespace Terminal.Gui {
 				hostView = ((Menu)view).host;
 			}
 
-			var grabView = Application.mouseGrabView;
+			var grabView = Application.MouseGrabView;
 			MenuBar hostGrabView = null;
 			if (grabView is MenuBar) {
 				hostGrabView = (MenuBar)grabView;
@@ -1972,12 +1940,13 @@ namespace Terminal.Gui {
 		/// </summary>
 		public MenuBarItem NewMenuBarItem { get; set; }
 		/// <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>
 		public bool Cancel { get; set; }
 
 		/// <summary>
-		/// Initializes a new instance of <see cref="MenuOpeningEventArgs"/>
+		/// Initializes a new instance of <see cref="MenuOpeningEventArgs"/>.
 		/// </summary>
 		/// <param name="currentMenu">The current <see cref="MenuBarItem"/> parent.</param>
 		public MenuOpeningEventArgs (MenuBarItem currentMenu)
@@ -1996,7 +1965,7 @@ namespace Terminal.Gui {
 		public MenuBarItem CurrentMenu { get; }
 
 		/// <summary>
-		/// Indicates whether the current menu will be reopen.
+		/// Indicates whether the current menu will reopen.
 		/// </summary>
 		public bool Reopen { get; }
 
@@ -2006,15 +1975,16 @@ namespace Terminal.Gui {
 		public bool IsSubMenu { get; }
 
 		/// <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>
 		public bool Cancel { get; set; }
 
 		/// <summary>
-		/// Initializes a new instance of <see cref="MenuClosingEventArgs"/>
+		/// Initializes a new instance of <see cref="MenuClosingEventArgs"/>.
 		/// </summary>
 		/// <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>
 		public MenuClosingEventArgs (MenuBarItem currentMenu, bool reopen, bool isSubMenu)
 		{

+ 3 - 3
Terminal.Gui/Views/ScrollBarView.cs

@@ -357,7 +357,7 @@ namespace Terminal.Gui {
 				} else if (otherScrollBarView != null && otherScrollBarView.contentBottomRightCorner != null) {
 					otherScrollBarView.contentBottomRightCorner.Visible = false;
 				}
-				if (Application.mouseGrabView != null && Application.mouseGrabView == this) {
+				if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
 					Application.UngrabMouse ();
 				}
 			} else if (contentBottomRightCorner != null) {
@@ -638,9 +638,9 @@ namespace Terminal.Gui {
 			var pos = Position;
 
 			if (me.Flags != MouseFlags.Button1Released
-				&& (Application.mouseGrabView == null || Application.mouseGrabView != this)) {
+				&& (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
 				Application.GrabMouse (this);
-			} else if (me.Flags == MouseFlags.Button1Released && Application.mouseGrabView != null && Application.mouseGrabView == this) {
+			} else if (me.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
 				lastLocation = -1;
 				Application.UngrabMouse ();
 				return true;

+ 23 - 14
Terminal.Gui/Views/ScrollView.cs

@@ -13,7 +13,6 @@
 
 using System;
 using System.Linq;
-using System.Reflection;
 
 namespace Terminal.Gui {
 	/// <summary>
@@ -30,7 +29,13 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </remarks>
 	public class ScrollView : View {
-		View contentView = null;
+		private class ContentView : View {
+			public ContentView (Rect frame) : base (frame)
+			{
+			}
+		}
+
+		ContentView contentView;
 		ScrollBarView vertical, horizontal;
 
 		/// <summary>
@@ -53,7 +58,7 @@ namespace Terminal.Gui {
 
 		void Initialize (Rect frame)
 		{
-			contentView = new View (frame);
+			contentView = new ContentView (frame);
 			vertical = new ScrollBarView (1, 0, isVertical: true) {
 				X = Pos.AnchorEnd (1),
 				Y = 0,
@@ -178,6 +183,12 @@ namespace Terminal.Gui {
 			set {
 				if (autoHideScrollBars != value) {
 					autoHideScrollBars = value;
+					if (Subviews.Contains (vertical)) {
+						vertical.AutoHideScrollBars = value;
+					}
+					if (Subviews.Contains (horizontal)) {
+						horizontal.AutoHideScrollBars = value;
+					}
 					SetNeedsDisplay ();
 				}
 			}
@@ -217,7 +228,7 @@ namespace Terminal.Gui {
 		/// <param name="view">The view to add to the scrollview.</param>
 		public override void Add (View view)
 		{
-			if (!IsOverridden (view)) {
+			if (!IsOverridden (view, "MouseEvent")) {
 				view.MouseEnter += View_MouseEnter;
 				view.MouseLeave += View_MouseLeave;
 			}
@@ -227,7 +238,7 @@ namespace Terminal.Gui {
 
 		void View_MouseLeave (MouseEventArgs e)
 		{
-			if (Application.mouseGrabView != null && Application.mouseGrabView != vertical && Application.mouseGrabView != horizontal) {
+			if (Application.MouseGrabView != null && Application.MouseGrabView != vertical && Application.MouseGrabView != horizontal) {
 				Application.UngrabMouse ();
 			}
 		}
@@ -237,14 +248,6 @@ namespace Terminal.Gui {
 			Application.GrabMouse (this);
 		}
 
-		bool IsOverridden (View view)
-		{
-			Type t = view.GetType ();
-			MethodInfo m = t.GetMethod ("MouseEvent");
-
-			return (m.DeclaringType == t || m.ReflectedType == t) && m.GetBaseDefinition ().DeclaringType == typeof (Responder);
-		}
-
 		/// <summary>
 		/// Gets or sets the visibility for the horizontal scroll indicator.
 		/// </summary>
@@ -260,6 +263,8 @@ namespace Terminal.Gui {
 				SetNeedsLayout ();
 				if (value) {
 					base.Add (horizontal);
+					horizontal.ShowScrollIndicator = value;
+					horizontal.AutoHideScrollBars = autoHideScrollBars;
 					horizontal.OtherScrollBarView = vertical;
 					horizontal.OtherScrollBarView.ShowScrollIndicator = value;
 					horizontal.MouseEnter += View_MouseEnter;
@@ -299,6 +304,8 @@ namespace Terminal.Gui {
 				SetNeedsLayout ();
 				if (value) {
 					base.Add (vertical);
+					vertical.ShowScrollIndicator = value;
+					vertical.AutoHideScrollBars = autoHideScrollBars;
 					vertical.OtherScrollBarView = horizontal;
 					vertical.OtherScrollBarView.ShowScrollIndicator = value;
 					vertical.MouseEnter += View_MouseEnter;
@@ -331,10 +338,12 @@ namespace Terminal.Gui {
 				ShowHideScrollBars ();
 			} else {
 				if (ShowVerticalScrollIndicator) {
+					vertical.SetRelativeLayout (Bounds);
 					vertical.Redraw (vertical.Bounds);
 				}
 
 				if (ShowHorizontalScrollIndicator) {
+					horizontal.SetRelativeLayout (Bounds);
 					horizontal.Redraw (horizontal.Bounds);
 				}
 			}
@@ -515,7 +524,7 @@ namespace Terminal.Gui {
 				vertical.MouseEvent (me);
 			} else if (me.Y == horizontal.Frame.Y && ShowHorizontalScrollIndicator) {
 				horizontal.MouseEvent (me);
-			} else if (IsOverridden (me.View)) {
+			} else if (IsOverridden (me.View, "MouseEvent")) {
 				Application.UngrabMouse ();
 			}
 			return true;

+ 8 - 6
Terminal.Gui/Views/TabView.cs

@@ -98,7 +98,7 @@ namespace Terminal.Gui {
 
 
 		/// <summary>
-		/// Initialzies a <see cref="TabView"/> class using <see cref="LayoutStyle.Computed"/> layout.
+		/// Initializes a <see cref="TabView"/> class using <see cref="LayoutStyle.Computed"/> layout.
 		/// </summary>
 		public TabView () : base ()
 		{
@@ -182,7 +182,7 @@ namespace Terminal.Gui {
 
 			if (Style.ShowBorder) {
 
-				// How muc space do we need to leave at the bottom to show the tabs
+				// How much space do we need to leave at the bottom to show the tabs
 				int spaceAtBottom = Math.Max (0, GetTabHeight (false) - 1);
 				int startAtY = Math.Max (0, GetTabHeight (true) - 1);
 
@@ -347,8 +347,10 @@ namespace Terminal.Gui {
 				var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth));
 
 				// if tab view is width <= 3 don't render any tabs
-				if (maxWidth == 0)
-					yield break;
+				if (maxWidth == 0) {
+					yield return new TabToRender (i, tab, string.Empty, Equals (SelectedTab, tab), 0);
+					break;
+				}
 
 				if (tabTextWidth > maxWidth) {
 					text = tab.Text.ToString ().Substring (0, (int)maxWidth);
@@ -412,7 +414,7 @@ namespace Terminal.Gui {
 
 			// if the currently selected tab is no longer a member of Tabs
 			if (SelectedTab == null || !Tabs.Contains (SelectedTab)) {
-				// select the tab closest to the one that disapeared
+				// select the tab closest to the one that disappeared
 				var toSelect = Math.Max (idx - 1, 0);
 
 				if (toSelect < Tabs.Count) {
@@ -657,7 +659,7 @@ namespace Terminal.Gui {
 					Driver.AddRune (Driver.LeftArrow);
 				}
 
-				// if there are mmore tabs to the right not visible
+				// if there are more tabs to the right not visible
 				if (ShouldDrawRightScrollIndicator (tabLocations)) {
 					Move (width - 1, y);
 

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

@@ -249,9 +249,9 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool OnLeave (View view)
 		{
-			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
+			if (Application.MouseGrabView != null && Application.MouseGrabView == this)
 				Application.UngrabMouse ();
-			//if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
+			//if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
 			//	ClearAllSelection ();
 
 			return base.OnLeave (view);
@@ -1049,7 +1049,7 @@ namespace Terminal.Gui {
 				int x = PositionCursor (ev);
 				isButtonReleased = false;
 				PrepareSelection (x);
-				if (Application.mouseGrabView == null) {
+				if (Application.MouseGrabView == null) {
 					Application.GrabMouse (this);
 				}
 			} else if (ev.Flags == MouseFlags.Button1Released) {

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

@@ -4328,7 +4328,7 @@ namespace Terminal.Gui {
 				}
 				lastWasKill = false;
 				columnTrack = currentColumn;
-				if (Application.mouseGrabView == null) {
+				if (Application.MouseGrabView == null) {
 					Application.GrabMouse (this);
 				}
 			} else if (ev.Flags.HasFlag (MouseFlags.Button1Released)) {
@@ -4407,7 +4407,7 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override bool OnLeave (View view)
 		{
-			if (Application.mouseGrabView != null && Application.mouseGrabView == this) {
+			if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
 				Application.UngrabMouse ();
 			}
 

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

@@ -219,19 +219,23 @@ namespace Terminal.Gui {
 		/// <value></value>
 		public AspectGetterDelegate<T> AspectGetter { get; set; } = (o) => o.ToString () ?? "";
 
-		CursorVisibility desiredCursorVisibility = CursorVisibility.Default;
+		CursorVisibility desiredCursorVisibility = CursorVisibility.Invisible;
 
 		/// <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>
 		public CursorVisibility DesiredCursorVisibility {
-			get => desiredCursorVisibility;
+			get { 
+				return MultiSelect ? desiredCursorVisibility : CursorVisibility.Invisible;
+			}
 			set {
+				desiredCursorVisibility = value;
+
 				if (desiredCursorVisibility != value && HasFocus) {
-					Application.Driver.SetCursorVisibility (value);
+					Application.Driver.SetCursorVisibility (DesiredCursorVisibility);
 				}
-
-				desiredCursorVisibility = value;
 			}
 		}
 

+ 3 - 3
Terminal.Gui/Windows/MessageBox.cs

@@ -248,9 +248,9 @@ namespace Terminal.Gui {
 			} else {
 				maxWidthLine = width;
 			}
-			int textWidth = TextFormatter.MaxWidth (message, maxWidthLine);
+			int textWidth = Math.Min (TextFormatter.MaxWidth (message, maxWidthLine), Application.Driver.Cols);
 			int textHeight = TextFormatter.MaxLines (message, textWidth); // message.Count (ustring.Make ('\n')) + 1;
-			int msgboxHeight = Math.Max (1, textHeight) + 4; // textHeight + (top + top padding + buttons + bottom)
+			int msgboxHeight = Math.Min (Math.Max (1, textHeight) + 4, Application.Driver.Rows); // textHeight + (top + top padding + buttons + bottom)
 
 			// Create button array for Dialog
 			int count = 0;
@@ -300,7 +300,7 @@ namespace Terminal.Gui {
 
 			if (width == 0 & height == 0) {
 				// Dynamically size Width
-				d.Width = Math.Max (maxWidthLine, Math.Max (title.ConsoleWidth, Math.Max (textWidth + 2, d.GetButtonsWidth ()))); // textWidth + (left + padding + padding + right)
+				d.Width = Math.Min (Math.Max (maxWidthLine, Math.Max (title.ConsoleWidth, Math.Max (textWidth + 2, d.GetButtonsWidth ()))), Application.Driver.Cols); // textWidth + (left + padding + padding + right)
 			}
 
 			// Setup actions

+ 1 - 0
Terminal.sln

@@ -20,6 +20,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		.github\workflows\dotnet-core.yml = .github\workflows\dotnet-core.yml
 		.github\workflows\publish.yml = .github\workflows\publish.yml
 		README.md = README.md
+		testenvironments.json = testenvironments.json
 	EndProjectSection
 EndProject
 Global

+ 128 - 0
Terminal.sln.DotSettings

@@ -0,0 +1,128 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ArrangeAccessorOwnerBody/@EntryIndexedValue">WARNING</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String>
+	<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=MemberCanBePrivate_002EGlobal/@EntryIndexedValue">HINT</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ACCESSOR_OWNER_BODY/@EntryValue">ExpressionBody</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ARGUMENTS_SKIP_SINGLE/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOR/@EntryValue">NotRequired</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_FOREACH/@EntryValue">NotRequired</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_IFELSE/@EntryValue">NotRequired</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_LOCK/@EntryValue">RequiredForMultiline</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_FOR_WHILE/@EntryValue">RequiredForMultiline</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/BRACES_REDUNDANT/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/CONSTRUCTOR_OR_DESTRUCTOR_BODY/@EntryValue">BlockBody</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Explicit</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_PRIVATE_MODIFIER/@EntryValue">Implicit</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/FORCE_ATTRIBUTE_STYLE/@EntryValue">Separate</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">BlockBody</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">BlockBody</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/MODIFIERS_ORDER/@EntryValue">internal volatile public private new static async protected extern sealed override virtual unsafe abstract readonly</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/NAMESPACE_BODY/@EntryValue">FileScoped</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/OBJECT_CREATION_WHEN_TYPE_EVIDENT/@EntryValue">ExplicitlyTyped</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/OBJECT_CREATION_WHEN_TYPE_NOT_EVIDENT/@EntryValue">ExplicitlyTyped</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/PARENTHESES_GROUP_NON_OBVIOUS_OPERATIONS/@EntryValue">Conditional</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/PARENTHESES_NON_OBVIOUS_OPERATIONS/@EntryValue">Shift, Bitwise, Conditional</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/PARENTHESES_REDUNDANCY_STYLE/@EntryValue">Remove</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/PARENTHESES_SAME_TYPE_OPERATIONS/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/ThisQualifier/INSTANCE_MEMBERS_QUALIFY_DECLARED_IN/@EntryValue">0</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_OWNER_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_CALLS_CHAIN/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGN_MULTILINE_STATEMENT_CONDITIONS/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ALIGNMENT_TAB_FILL_STYLE/@EntryValue">USE_TABS_ONLY</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ANONYMOUS_METHOD_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_BLOCK_STATEMENTS/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_MULTILINE_STATEMENTS/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_START_COMMENT/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AFTER_USING_LIST/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_AUTO_PROPERTY/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_FIELD/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_INVOCABLE/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_LOCAL_METHOD/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_NAMESPACE/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_PROPERTY/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_REGION/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_SINGLE_LINE_TYPE/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_AROUND_TYPE/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_SINGLE_LINE_COMMENT/@EntryValue">0</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_INSIDE_REGION/@EntryValue">0</s:Int64>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_CASE_FROM_SWITCH/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_PARS/@EntryValue">OUTSIDE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_PREPROCESSOR_REGION/@EntryValue">USUAL_INDENT</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_SIZE/@EntryValue">8</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STATEMENT_PARS/@EntryValue">OUTSIDE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_STYLE/@EntryValue">Tab</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_TYPE_CONSTRAINTS/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INITIALIZER_BRACES/@EntryValue">END_OF_LINE</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INT_ALIGN_COMMENTS/@EntryValue">False</s:Boolean>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_CODE/@EntryValue">100</s:Int64>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_BLANK_LINES_IN_DECLARATIONS/@EntryValue">100</s:Int64>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_ATTRIBUTE_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_DECLARATION_BLOCK_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EMBEDDED_BLOCK_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_ENUM_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_EXPR_MEMBER_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/KEEP_EXISTING_INITIALIZER_ARRANGEMENT/@EntryValue">True</s:Boolean>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/MAX_INITIALIZER_ELEMENTS_ON_LINE/@EntryValue">2</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/NESTED_TERNARY_STYLE/@EntryValue">SIMPLE_WRAP</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/OTHER_BRACES/@EntryValue">END_OF_LINE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_EXPR_ACCESSOR_ON_SINGLE_LINE/@EntryValue">ALWAYS</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_EXPR_METHOD_ON_SINGLE_LINE/@EntryValue">ALWAYS</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_EXPR_PROPERTY_ON_SINGLE_LINE/@EntryValue">ALWAYS</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_FIELD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_METHOD_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ACCESSOR_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ANONYMOUSMETHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_ENUM_ON_SINGLE_LINE/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_INITIALIZER_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_SIMPLE_METHOD_ON_SINGLE_LINE/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_TYPE_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/REMOVE_BLANK_LINES_NEAR_BRACES_IN_CODE/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/REMOVE_BLANK_LINES_NEAR_BRACES_IN_DECLARATIONS/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_AFTER_TYPECAST_PARENTHESES/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_ARRAY_ACCESS_BRACKETS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_ARRAY_RANK_BRACKETS/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_DEFAULT_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_EMPTY_METHOD_CALL_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_EMPTY_METHOD_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_EXTENDS_COLON/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_METHOD_CALL_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_METHOD_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_NAMEOF_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_NEW_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPE_PARAMETER_CONSTRAINT_COLON/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/SPACE_BEFORE_TYPEOF_PARENTHESES/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/TYPE_DECLARATION_BRACES/@EntryValue">END_OF_LINE</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARRAY_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_BEFORE_BINARY_OPSIGN/@EntryValue">True</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_BINARY_EXPRESSIONS/@EntryValue">WRAP_IF_LONG</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_CHAINED_METHOD_CALLS/@EntryValue">WRAP_IF_LONG</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_FOR_STMT_HEADER_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
+	<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LIMIT/@EntryValue">527</s:Int64>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_LINQ_EXPRESSIONS/@EntryValue">CHOP_ALWAYS</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_MULTIPLE_DECLARATION_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_OBJECT_AND_COLLECTION_INITIALIZER_STYLE/@EntryValue">CHOP_ALWAYS</s:String>
+	<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_TERNARY_EXPR_STYLE/@EntryValue">WRAP_IF_LONG</s:String>
+	<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">False</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/PreferExplicitDiscardDeclaration/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/PreferSeparateDeconstructedVariablesDeclaration/@EntryValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
+	<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
+	<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=CAF4ECB3AC41AE43BD233D613AC1562C/@KeyIndexDefined">True</s:Boolean>
+	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=CAF4ECB3AC41AE43BD233D613AC1562C/AbsolutePath/@EntryValue">Terminal.sln.DotSettings</s:String>
+	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=CAF4ECB3AC41AE43BD233D613AC1562C/RelativePath/@EntryValue"></s:String>
+	<s:Boolean x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=F728A143A60F2142985180A92B1C45E8/@KeyIndexDefined">True</s:Boolean>
+	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=F728A143A60F2142985180A92B1C45E8/AbsolutePath/@EntryValue">/Users/alex/Development/Terminal.Gui/Terminal.sln.DotSettings</s:String>
+	<s:String x:Key="/Default/Environment/InjectedLayers/FileInjectedLayer/=F728A143A60F2142985180A92B1C45E8/RelativePath/@EntryValue"></s:String>
+	<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileCAF4ECB3AC41AE43BD233D613AC1562C/@KeyIndexDefined">True</s:Boolean>
+	<s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileCAF4ECB3AC41AE43BD233D613AC1562C/RelativePriority/@EntryValue">1</s:Double>
+	<s:Boolean x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileF728A143A60F2142985180A92B1C45E8/@KeyIndexDefined">True</s:Boolean>
+	<s:Double x:Key="/Default/Environment/InjectedLayers/InjectedLayerCustomization/=FileF728A143A60F2142985180A92B1C45E8/RelativePriority/@EntryValue">2</s:Double>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

+ 18 - 0
UICatalog/Properties/launchSettings.json

@@ -26,6 +26,24 @@
     "Issue1719Repro": {
       "commandName": "Project",
       "commandLineArgs": "\"ProgressBar Styles\""
+    },
+    "VkeyPacketSimulator": {
+      "commandName": "Project",
+      "commandLineArgs": "VkeyPacketSimulator"
+    },
+    "WSL2": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll"
+    },
+    "WSL2 : -usc": {
+      "commandName": "Executable",
+      "executablePath": "wsl",
+      "commandLineArgs": "dotnet UICatalog.dll -usc"
+    },
+    "WSL": {
+      "commandName": "WSL2",
+      "distributionName": ""
     }
   }
 }

+ 6 - 4
UICatalog/Scenarios/CsvEditor.cs

@@ -43,12 +43,14 @@ namespace UICatalog.Scenarios {
 				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 ("_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 MenuItem ("_New Column", "", () => AddColumn()),
 					new MenuItem ("_New Row", "", () => AddRow()),

+ 1 - 0
UICatalog/Scenarios/Editor.cs

@@ -116,6 +116,7 @@ namespace UICatalog.Scenarios {
 					new MenuBarItem ("_Languages", GetSupportedCultures ())
 				})
 			});
+
 			Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {

+ 23 - 38
UICatalog/Scenarios/Notepad.cs

@@ -1,36 +1,35 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
+using System.IO;
 using Terminal.Gui;
-using static UICatalog.Scenario;
 
 namespace UICatalog.Scenarios {
 
-	[ScenarioMetadata (Name: "Notepad", Description: "Multi tab text editor uising the TabView control.")]
+	[ScenarioMetadata (Name: "Notepad", Description: "Multi-tab text editor uising the TabView control.")]
 	[ScenarioCategory ("Controls"), ScenarioCategory ("TabView")]
 	public class Notepad : Scenario {
-
 		TabView tabView;
-		Label lblStatus;
 
 		private int numbeOfNewTabs = 1;
 
-		public override void Setup ()
+		// Don't create a Window, just return the top-level view
+		public override void Init (Toplevel top, ColorScheme colorScheme)
 		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-			Top.LayoutSubviews ();
+			Application.Init ();
+
+			Top = top;
+			if (Top == null) {
+				Top = Application.Top;
+			}
+			Top.ColorScheme = Colors.Base;
+		}
 
+		public override void Setup ()
+		{
 			var menu = new MenuBar (new MenuBarItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_New", "", () => New()),
 					new MenuItem ("_Open", "", () => Open()),
 					new MenuItem ("_Save", "", () => Save()),
-					new MenuItem ("_Save As", "", () => SaveAs()),
+					new MenuItem ("Save _As", "", () => SaveAs()),
 					new MenuItem ("_Close", "", () => Close()),
 					new MenuItem ("_Quit", "", () => Quit()),
 				})
@@ -39,16 +38,17 @@ namespace UICatalog.Scenarios {
 
 			tabView = new TabView () {
 				X = 0,
-				Y = 0,
+				Y = 1,
 				Width = Dim.Fill (),
 				Height = Dim.Fill (1),
 			};
 
-			tabView.Style.ShowBorder = false;
+			tabView.Style.ShowBorder = true;
 			tabView.ApplyStyleChanges ();
 
-			Win.Add (tabView);
+			Top.Add (tabView);
 
+			var lenStatusItem = new StatusItem (Key.CharMask, "Len: ", null);
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()),
 
@@ -58,26 +58,16 @@ namespace UICatalog.Scenarios {
 
 				new StatusItem(Key.CtrlMask | Key.S, "~^S~ Save", () => Save()),
 				new StatusItem(Key.CtrlMask | Key.W, "~^W~ Close", () => Close()),
+				lenStatusItem,
 			});
 
-			Win.Add (lblStatus = new Label ("Len:") {
-				Y = Pos.Bottom (tabView),
-				Width = Dim.Fill (),
-				TextAlignment = TextAlignment.Right
-			});
-
-			tabView.SelectedTabChanged += (s, e) => UpdateStatus (e.NewTab);
+			tabView.SelectedTabChanged += (s, e) => lenStatusItem.Title = $"Len:{(e.NewTab?.View?.Text?.Length ?? 0)}";
 
 			Top.Add (statusBar);
 
 			New ();
 		}
 
-		private void UpdateStatus (TabView.Tab newTab)
-		{
-			lblStatus.Text = $"Len:{(newTab?.View?.Text?.Length ?? 0)}";
-		}
-
 		private void New ()
 		{
 			Open ("", null, $"new {numbeOfNewTabs++}");
@@ -109,12 +99,10 @@ namespace UICatalog.Scenarios {
 			// close and dispose the tab
 			tabView.RemoveTab (tab);
 			tab.View.Dispose ();
-
 		}
 
 		private void Open ()
 		{
-
 			var open = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true };
 
 			Application.Run (open);
@@ -130,7 +118,6 @@ namespace UICatalog.Scenarios {
 					Open (File.ReadAllText (path), new FileInfo (path), Path.GetFileName (path));
 				}
 			}
-
 		}
 
 		/// <summary>
@@ -140,7 +127,6 @@ namespace UICatalog.Scenarios {
 		/// <param name="fileInfo">File that was read or null if a new blank document</param>
 		private void Open (string initialText, FileInfo fileInfo, string tabName)
 		{
-
 			var textView = new TextView () {
 				X = 0,
 				Y = 0,
@@ -188,7 +174,7 @@ namespace UICatalog.Scenarios {
 			}
 
 			tab.Save ();
-
+			tabView.SetNeedsDisplay ();
 		}
 
 		public bool SaveAs ()
@@ -207,14 +193,13 @@ namespace UICatalog.Scenarios {
 			}
 
 			tab.File = new FileInfo (fd.FilePath.ToString ());
+			tab.Text = fd.FileName.ToString ();
 			tab.Save ();
 
 			return true;
 		}
 
 		private class OpenedFile : TabView.Tab {
-
-
 			public FileInfo File { get; set; }
 
 			/// <summary>

+ 16 - 0
UICatalog/Scenarios/TreeViewFileSystem.cs

@@ -25,6 +25,8 @@ namespace UICatalog.Scenarios {
 		private MenuItem miFullPaths;
 		private MenuItem miLeaveLastRow;
 		private MenuItem miCustomColors;
+		private MenuItem miCursor;
+		private MenuItem miMultiSelect;
 		private Terminal.Gui.Attribute green;
 		private Terminal.Gui.Attribute red;
 
@@ -54,6 +56,8 @@ namespace UICatalog.Scenarios {
 					miFullPaths = new MenuItem ("_FullPaths", "", () => SetFullName()){Checked = false, CheckType = MenuItemCheckStyle.Checked},
 					miLeaveLastRow = new MenuItem ("_LeaveLastRow", "", () => SetLeaveLastRow()){Checked = true, 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);
@@ -269,6 +273,18 @@ namespace UICatalog.Scenarios {
 			miLeaveLastRow.Checked = !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()
 		{
 			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);
+		}
+	}
+}

+ 5 - 4
UICatalog/UICatalog.cs

@@ -169,7 +169,7 @@ namespace UICatalog {
 
 			_menu = new MenuBar (new MenuBarItem [] {
 				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 ("Diag_nostics", CreateDiagnosticMenuItems()),
@@ -178,7 +178,7 @@ namespace UICatalog {
 					new MenuItem ("gui.cs _README", "", () => OpenUrl ("https://github.com/gui-cs/Terminal.Gui"), null, null, Key.F2),
 					new MenuItem ("_About...",
 						"About UI Catalog", () =>  MessageBox.Query ("About UI Catalog", aboutMessage.ToString(), "_Ok"), null, null, Key.CtrlMask | Key.A),
-				})
+				}),
 			});
 
 			_leftPane = new FrameView ("Categories") {
@@ -318,7 +318,7 @@ namespace UICatalog {
 		{
 			List<MenuItem> menuItems = new List<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.CheckType |= MenuItemCheckStyle.Checked;
 			item.Checked = Application.IsMouseDisabled;
@@ -334,7 +334,8 @@ namespace UICatalog {
 
 			List<MenuItem> menuItems = new List<MenuItem> ();
 			var item = new MenuItem ();
-			item.Title = "Keybindings";
+			item.Title = "_Key Bindings";
+			item.Help = "Change which keys do what";
 			item.Action += () => {
 				var dlg = new KeyBindingsDialog ();
 				Application.Run (dlg);

+ 72 - 9
UnitTests/ApplicationTests.cs

@@ -1144,7 +1144,7 @@ namespace Terminal.Gui.Core {
 			Assert.NotNull (Application.Top);
 			var rs = Application.Begin (Application.Top);
 			Assert.Equal (Application.Top, rs.Toplevel);
-			Assert.Null (Application.mouseGrabView);
+			Assert.Null (Application.MouseGrabView);
 			Assert.Null (Application.WantContinuousButtonPressedView);
 			Assert.False (Application.DebugDrawBounds);
 			Assert.False (Application.ShowChild (Application.Top));
@@ -1428,7 +1428,7 @@ namespace Terminal.Gui.Core {
 				iterations++;
 				if (iterations == 0) {
 					Assert.True (tf.HasFocus);
-					Assert.Null (Application.mouseGrabView);
+					Assert.Null (Application.MouseGrabView);
 
 					ReflectionTools.InvokePrivate (
 						typeof (Application),
@@ -1439,13 +1439,13 @@ namespace Terminal.Gui.Core {
 							Flags = MouseFlags.ReportMousePosition
 						});
 
-					Assert.Equal (sv, Application.mouseGrabView);
+					Assert.Equal (sv, Application.MouseGrabView);
 
 					MessageBox.Query ("Title", "Test", "Ok");
 
-					Assert.Null (Application.mouseGrabView);
+					Assert.Null (Application.MouseGrabView);
 				} else if (iterations == 1) {
-					Assert.Equal (sv, Application.mouseGrabView);
+					Assert.Equal (sv, Application.MouseGrabView);
 
 					ReflectionTools.InvokePrivate (
 						typeof (Application),
@@ -1456,7 +1456,7 @@ namespace Terminal.Gui.Core {
 							Flags = MouseFlags.ReportMousePosition
 						});
 
-					Assert.Null (Application.mouseGrabView);
+					Assert.Null (Application.MouseGrabView);
 
 					ReflectionTools.InvokePrivate (
 						typeof (Application),
@@ -1467,7 +1467,7 @@ namespace Terminal.Gui.Core {
 							Flags = MouseFlags.ReportMousePosition
 						});
 
-					Assert.Null (Application.mouseGrabView);
+					Assert.Null (Application.MouseGrabView);
 
 					ReflectionTools.InvokePrivate (
 						typeof (Application),
@@ -1478,11 +1478,11 @@ namespace Terminal.Gui.Core {
 							Flags = MouseFlags.Button1Pressed
 						});
 
-					Assert.Null (Application.mouseGrabView);
+					Assert.Null (Application.MouseGrabView);
 
 					Application.RequestStop ();
 				} else if (iterations == 2) {
-					Assert.Null (Application.mouseGrabView);
+					Assert.Null (Application.MouseGrabView);
 
 					Application.RequestStop ();
 				}
@@ -1490,5 +1490,68 @@ namespace Terminal.Gui.Core {
 
 			Application.Run ();
 		}
+
+		[Fact, AutoInitShutdown]
+		public void MouseGrabView_GrabbedMouse_UnGrabbedMouse ()
+		{
+			View grabView = null;
+			var count = 0;
+
+			var view1 = new View ();
+			var view2 = new View ();
+
+			Application.GrabbedMouse += Application_GrabbedMouse;
+			Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+
+			Application.GrabMouse (view1);
+			Assert.Equal (0, count);
+			Assert.Equal (grabView, view1);
+			Assert.Equal (view1, Application.MouseGrabView);
+
+			Application.UngrabMouse ();
+			Assert.Equal (1, count);
+			Assert.Equal (grabView, view1);
+			Assert.Null (Application.MouseGrabView);
+
+			Application.GrabbedMouse += Application_GrabbedMouse;
+			Application.UnGrabbedMouse += Application_UnGrabbedMouse;
+
+			Application.GrabMouse (view2);
+			Assert.Equal (1, count);
+			Assert.Equal (grabView, view2);
+			Assert.Equal (view2, Application.MouseGrabView);
+
+			Application.UngrabMouse ();
+			Assert.Equal (2, count);
+			Assert.Equal (grabView, view2);
+			Assert.Null (Application.MouseGrabView);
+
+			void Application_GrabbedMouse (View obj)
+			{
+				if (count == 0) {
+					Assert.Equal (view1, obj);
+					grabView = view1;
+				} else {
+					Assert.Equal (view2, obj);
+					grabView = view2;
+				}
+
+				Application.GrabbedMouse -= Application_GrabbedMouse;
+			}
+
+			void Application_UnGrabbedMouse (View obj)
+			{
+				if (count == 0) {
+					Assert.Equal (view1, obj);
+					Assert.Equal (grabView, obj);
+				} else {
+					Assert.Equal (view2, obj);
+					Assert.Equal (grabView, obj);
+				}
+				count++;
+
+				Application.UnGrabbedMouse -= Application_UnGrabbedMouse;
+			}
+		}
 	}
 }

+ 190 - 68
UnitTests/ConsoleDriverTests.cs

@@ -1,7 +1,7 @@
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
-using Terminal.Gui;
 using Terminal.Gui.Views;
 using Xunit;
 using Xunit.Abstractions;
@@ -228,7 +228,8 @@ namespace Terminal.Gui.ConsoleDrivers {
 
 			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 ck = char.IsLetter (c) ? (ConsoleKey)char.ToUpper (c) : (ConsoleKey)c;
 				var mk = new KeyModifiers () {
@@ -609,6 +610,30 @@ namespace Terminal.Gui.ConsoleDrivers {
 			Application.Shutdown ();
 		}
 
+		[Theory]
+		[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)
+		{
+			var actual = ConsoleDriver.MakePrintable (code);
+
+			Assert.Equal (expected, actual.Value);
+		}
+
+		[Theory]
+		[InlineData (0x20)]
+		[InlineData (0x7E)]
+		[InlineData (0xA0)]
+		[InlineData (0x010020)]
+		public void MakePrintable_Does_Not_Convert_Ansi_Chars_To_Unicode (uint code)
+		{
+			var actual = ConsoleDriver.MakePrintable (code);
+
+			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
@@ -617,80 +642,177 @@ namespace Terminal.Gui.ConsoleDrivers {
 		/// see: https://github.com/gui-cs/Terminal.Gui/issues/2008
 		/// </summary>
 		[Theory, AutoInitShutdown]
-		[InlineData ('A', false, false, false, Key.A)]
-		[InlineData ('A', true, false, false, Key.A)]
-		[InlineData ('A', true, true, false, Key.A | Key.AltMask)]
-		[InlineData ('A', true, true, true, Key.A | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('z', false, false, false, Key.z)]
-		[InlineData ('z', true, false, false, Key.z)]
-		[InlineData ('z', true, true, false, Key.z | Key.AltMask)]
-		[InlineData ('z', true, true, true, Key.z | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('英', false, false, false, (Key)'英')]
-		[InlineData ('英', true, false, false, (Key)'英')]
-		[InlineData ('英', true, true, false, (Key)'英' | Key.AltMask)]
-		[InlineData ('英', true, true, true, (Key)'英' | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('+', false, false, false, (Key)'+')]
-		[InlineData ('+', true, false, false, (Key)'+')]
-		[InlineData ('+', true, true, false, (Key)'+' | Key.AltMask)]
-		[InlineData ('+', true, true, true, (Key)'+' | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('0', false, false, false, Key.D0)]
-		[InlineData ('=', true, false, false, (Key)'=')]
-		[InlineData ('0', true, true, false, Key.D0 | Key.AltMask)]
-		[InlineData ('0', true, true, true, Key.D0 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('1', false, false, false, Key.D1)]
-		[InlineData ('!', true, false, false, (Key)'!')]
-		[InlineData ('1', true, true, false, Key.D1 | Key.AltMask)]
-		[InlineData ('1', true, true, true, Key.D1 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('2', false, false, false, Key.D2)]
-		[InlineData ('"', true, false, false, (Key)'"')]
-		[InlineData ('2', true, true, false, Key.D2 | Key.AltMask)]
-		[InlineData ('2', true, true, true, Key.D2 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('3', false, false, false, Key.D3)]
-		[InlineData ('#', true, false, false, (Key)'#')]
-		[InlineData ('3', true, true, false, Key.D3 | Key.AltMask)]
-		[InlineData ('3', true, true, true, Key.D3 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('4', false, false, false, Key.D4)]
-		[InlineData ('$', true, false, false, (Key)'$')]
-		[InlineData ('4', true, true, false, Key.D4 | Key.AltMask)]
-		[InlineData ('4', true, true, true, Key.D4 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('5', false, false, false, Key.D5)]
-		[InlineData ('%', true, false, false, (Key)'%')]
-		[InlineData ('5', true, true, false, Key.D5 | Key.AltMask)]
-		[InlineData ('5', true, true, true, Key.D5 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('6', false, false, false, Key.D6)]
-		[InlineData ('&', true, false, false, (Key)'&')]
-		[InlineData ('6', true, true, false, Key.D6 | Key.AltMask)]
-		[InlineData ('6', true, true, true, Key.D6 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('7', false, false, false, Key.D7)]
-		[InlineData ('/', true, false, false, (Key)'/')]
-		[InlineData ('7', true, true, false, Key.D7 | Key.AltMask)]
-		[InlineData ('7', true, true, true, Key.D7 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('8', false, false, false, Key.D8)]
-		[InlineData ('(', true, false, false, (Key)'(')]
-		[InlineData ('8', true, true, false, Key.D8 | Key.AltMask)]
-		[InlineData ('8', true, true, true, Key.D8 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('9', false, false, false, Key.D9)]
-		[InlineData (')', true, false, false, (Key)')')]
-		[InlineData ('9', true, true, false, Key.D9 | Key.AltMask)]
-		[InlineData ('9', true, true, true, Key.D9 | Key.AltMask | Key.CtrlMask)]
-		[InlineData ('\0', false, false, false, (Key)'\0')]
-		[InlineData ('\0', true, false, false, (Key)'\0' | Key.ShiftMask)]
-		[InlineData ('\0', true, true, false, (Key)'\0' | Key.ShiftMask | Key.AltMask)]
-		[InlineData ('\0', true, true, true, (Key)'\0' | Key.ShiftMask | Key.AltMask | Key.CtrlMask)]
-		public void TestVKPacket (char unicodeCharacter, bool shift, bool alt, bool control, Key expectedRemapping)
+		[ClassData (typeof (PacketTest))]
+		public void TestVKPacket (uint unicodeCharacter, bool shift, bool alt, bool control, uint initialVirtualKey, uint initialScanCode, Key expectedRemapping, uint expectedVirtualKey, uint expectedScanCode)
 		{
-			var before = new ConsoleKeyInfo (unicodeCharacter, ConsoleKey.Packet, shift, alt, control);
+			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 = e.KeyEvent.Key;
-				Assert.Equal (before.KeyChar, (char)after);
+				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.Begin (top);
+			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 };
+			}
 
-			Application.Driver.SendKeys (unicodeCharacter, ConsoleKey.Packet, shift, alt, control);
+			IEnumerator IEnumerable.GetEnumerator () => GetEnumerator ();
 		}
 	}
 }

+ 26 - 12
UnitTests/ContextMenuTests.cs

@@ -519,38 +519,38 @@ namespace Terminal.Gui.Core {
 
 			Application.Top.Add (menu);
 
-			Assert.Null (Application.mouseGrabView);
+			Assert.Null (Application.MouseGrabView);
 
 			cm.Show ();
 			Assert.True (ContextMenu.IsShow);
-			Assert.Equal (cm.MenuBar, Application.mouseGrabView);
+			Assert.Equal (cm.MenuBar, Application.MouseGrabView);
 			Assert.False (menu.IsMenuOpen);
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
 			Assert.False (ContextMenu.IsShow);
-			Assert.Equal (menu, Application.mouseGrabView);
+			Assert.Equal (menu, Application.MouseGrabView);
 			Assert.True (menu.IsMenuOpen);
 
 			cm.Show ();
 			Assert.True (ContextMenu.IsShow);
-			Assert.Equal (cm.MenuBar, Application.mouseGrabView);
+			Assert.Equal (cm.MenuBar, Application.MouseGrabView);
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.OnKeyDown (new KeyEvent (Key.Null, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.OnKeyUp (new KeyEvent (Key.Null, new KeyModifiers () { Alt = true })));
 			Assert.False (ContextMenu.IsShow);
-			Assert.Equal (menu, Application.mouseGrabView);
+			Assert.Equal (menu, Application.MouseGrabView);
 			Assert.True (menu.IsMenuOpen);
 
 			cm.Show ();
 			Assert.True (ContextMenu.IsShow);
-			Assert.Equal (cm.MenuBar, Application.mouseGrabView);
+			Assert.Equal (cm.MenuBar, Application.MouseGrabView);
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.MouseEvent (new MouseEvent () { X = 1, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (ContextMenu.IsShow);
-			Assert.Equal (cm.MenuBar, Application.mouseGrabView);
+			Assert.Equal (cm.MenuBar, Application.MouseGrabView);
 			Assert.False (menu.IsMenuOpen);
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Flags = MouseFlags.Button1Clicked, View = menu }));
 			Assert.False (ContextMenu.IsShow);
-			Assert.Equal (menu, Application.mouseGrabView);
+			Assert.Equal (menu, Application.MouseGrabView);
 			Assert.True (menu.IsMenuOpen);
 		}
 
@@ -592,7 +592,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  File   Edit                   
+ File  Edit                     
                                 
                                 
   Label: TextField              
@@ -612,7 +612,7 @@ namespace Terminal.Gui.Core {
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 32, 17), pos);
+			Assert.Equal (new Rect (1, 0, 32, 17), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -656,7 +656,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (10, 5), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  File   Edit                               
+ File  Edit                                 
 ┌ Window ──────────────────────────────────┐
 │                                          │
 │                                          │
@@ -676,7 +676,7 @@ namespace Terminal.Gui.Core {
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 44, 17), pos);
+			Assert.Equal (new Rect (1, 0, 44, 17), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -888,5 +888,19 @@ namespace Terminal.Gui.Core {
 │ SubMenu7  │────┘
 ", 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);
+		}
 	}
 }

+ 305 - 230
UnitTests/MenuTests.cs

@@ -1,7 +1,9 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Xunit;
 using Xunit.Abstractions;
+using static Terminal.Gui.Views.MenuTests;
 
 namespace Terminal.Gui.Views {
 	public class MenuTests {
@@ -705,16 +707,15 @@ Edit
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  Numbers
+ Numbers
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
 
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -723,12 +724,11 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers                
+ Numbers                 
 ┌────────┐               
 │ One    │               
 │ Two   ►│┌─────────────┐
@@ -738,12 +738,11 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 25, 7), pos);
 
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -752,16 +751,14 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+ Numbers
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -785,11 +782,11 @@ Edit
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  Numbers
+ Numbers
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () {
 				X = 1,
@@ -799,7 +796,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -808,7 +805,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
@@ -818,7 +815,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers                
+ Numbers                 
 ┌────────┐               
 │ One    │               
 │ Two   ►│┌─────────────┐
@@ -828,7 +825,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 25, 7), pos);
+			Assert.Equal (new Rect (1, 0, 25, 7), pos);
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
@@ -838,7 +835,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -847,7 +844,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 70,
@@ -857,11 +854,11 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+ Numbers
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -887,16 +884,16 @@ Edit
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  Numbers
+ Numbers
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -905,13 +902,13 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			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.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers      
+ Numbers       
 ┌─────────────┐
 │◄    Two     │
 ├─────────────┤
@@ -921,12 +918,12 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 15, 7), pos);
+			Assert.Equal (new Rect (1, 0, 15, 7), pos);
 
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -935,16 +932,16 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Esc, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+ Numbers
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -970,11 +967,11 @@ Edit
 
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  Numbers
+ Numbers
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () {
 				X = 1,
@@ -984,7 +981,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -993,7 +990,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
@@ -1003,7 +1000,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers      
+ Numbers       
 ┌─────────────┐
 │◄    Two     │
 ├─────────────┤
@@ -1013,7 +1010,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 15, 7), pos);
+			Assert.Equal (new Rect (1, 0, 15, 7), pos);
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 1,
@@ -1023,7 +1020,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers 
+ Numbers  
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -1032,7 +1029,7 @@ Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 10, 6), pos);
+			Assert.Equal (new Rect (1, 0, 10, 6), pos);
 
 			Assert.False (menu.MouseEvent (new MouseEvent () {
 				X = 70,
@@ -1042,11 +1039,11 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+ Numbers
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 9, 1), pos);
+			Assert.Equal (new Rect (1, 0, 8, 1), pos);
 		}
 
 		[Fact, AutoInitShutdown]
@@ -1074,11 +1071,11 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  File   Edit
+ File  Edit
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			Assert.Equal (new Rect (1, 0, 11, 1), pos);
 
 			Assert.True (menu.ProcessKey (new (Key.N, null)));
 			Application.MainLoop.MainIteration ();
@@ -1088,11 +1085,11 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  File   Edit
+ File  Edit
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			Assert.Equal (new Rect (1, 0, 11, 1), pos);
 
 			Assert.True (menu.ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.ProcessKey (new (Key.C, null)));
@@ -1100,21 +1097,150 @@ Edit
 			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);
+			GraphViewTests.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);
+				GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (i), output);
+			}
+		}
+
 		[Fact, AutoInitShutdown]
 		public void HotKey_MenuBar_ProcessHotKey_Menu_ProcessKey ()
 		{
 			var newAction = 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);
 
 			Assert.False (newAction);
@@ -1123,15 +1249,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.F, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.IsMenuOpen);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.N, null)));
 			Application.MainLoop.MainIteration ();
@@ -1140,15 +1258,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new (Key.AltMask | Key.E, new KeyModifiers () { Alt = true })));
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   
-       ┌───────┐
-       │ Copy  │
-       └───────┘
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 16, 4), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.C, null)));
 			Application.MainLoop.MainIteration ();
@@ -1158,127 +1268,114 @@ Edit
 		[Fact, AutoInitShutdown]
 		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 [] {
-				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);
 
+			// Open first
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
+			// Open second
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   
-       ┌───────┐
-       │ Copy  │
-       └───────┘
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 16, 4), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
+			// Close menu
 			Assert.True (menu.ProcessHotKey (new (Key.F9, new KeyModifiers ())));
 			Assert.False (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit
-";
+			GraphViewTests.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 [] {
-				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.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit
-┌──────┐     
-│ New  │     
-└──────┘     
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 4), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
+			// Open second
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new (Key.CursorRight, null)));
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   
-       ┌───────┐
-       │ Copy  │
-       └───────┘
-";
+			GraphViewTests.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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 		}
 
 		[Fact, AutoInitShutdown]
 		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 MenuItem ("New", "", null)
+					new MenuItem ("Open", "",  null)
 				}),
 				new MenuBarItem ("Edit", new MenuItem [] {
 					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);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.False (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 13, 1), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 		}
 
 		[Fact]
@@ -1300,110 +1397,98 @@ Edit
 		[Fact, AutoInitShutdown]
 		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 MenuItem ("New", "", null)
-				}),
-				new MenuBarItem ("Edit", new MenuItem [] {
+					new MenuItem ("New", "",  null)
 				}),
+				new MenuBarItem ("Edit", new MenuItem [] {}),
 				new MenuBarItem ("Format", new MenuItem [] {
 					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 };
 			Application.Top.Add (menu, tf);
-
 			Application.Begin (Application.Top);
+
 			Assert.True (tf.HasFocus);
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 15, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (2), output);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 
 			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-┌──────┐              
-│ New  │              
-└──────┘              
-";
+			GraphViewTests.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.False (menu.IsMenuOpen);
 			Assert.True (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.ClosedMenuText, output);
 		}
 
 		[Fact, AutoInitShutdown]
 		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 MenuItem ("New", "", null)
 				}),
-				new MenuBarItem ("Edit", new MenuItem [] {
-				}),
+				new MenuBarItem ("Edit", Array.Empty<MenuItem> ()),
 				new MenuBarItem ("Format", new MenuItem [] {
 					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 };
 			Application.Top.Add (menu, tf);
 
@@ -1413,76 +1498,66 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			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);
+			GraphViewTests.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.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
+			// Right - Format
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (2), output);
 
+			// Left - Edit
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
-
-			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (1), output);
 
 			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.False (tf.HasFocus);
 			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);
+			GraphViewTests.AssertDriverContentsAre (expectedMenu.expectedSubMenuOpen (0), output);
 
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
 			Assert.False (menu.IsMenuOpen);
 			Assert.True (tf.HasFocus);
 			Application.Top.Redraw (Application.Top.Bounds);
-			expected = @"
-  File   Edit   Format
-";
+			GraphViewTests.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);
 		}
 	}
 }

+ 104 - 0
UnitTests/MessageBoxTests.cs

@@ -154,5 +154,109 @@ namespace Terminal.Gui.Views {
 
 			Application.Run ();
 		}
+
+		[Fact, AutoInitShutdown]
+		public void MessageBox_With_A_Label_Without_Spaces ()
+		{
+			var iterations = -1;
+			Application.Begin (Application.Top);
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations == 0) {
+					MessageBox.Query ("mywindow", new string ('f', 2000), "ok");
+
+					Application.RequestStop ();
+				} else if (iterations == 1) {
+					Application.Top.Redraw (Application.Top.Bounds);
+					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌ mywindow ────────────────────────────────────────────────────────────────────┐
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff│
+│                                   [◦ ok ◦]                                   │
+└──────────────────────────────────────────────────────────────────────────────┘
+", output);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
+
+		[Fact, AutoInitShutdown]
+		public void MessageBox_With_A_Label_With_Spaces ()
+		{
+			var iterations = -1;
+			Application.Begin (Application.Top);
+
+			Application.Iteration += () => {
+				iterations++;
+
+				if (iterations == 0) {
+					var sb = new StringBuilder ();
+					for (int i = 0; i < 1000; i++)
+						sb.Append ("ff ");
+
+					MessageBox.Query ("mywindow", sb.ToString (), "ok");
+
+					Application.RequestStop ();
+				} else if (iterations == 1) {
+					Application.Top.Redraw (Application.Top.Bounds);
+					GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌ 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 │
+│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 │
+│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 │
+│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 │
+│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 │
+│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 │
+│                                   [◦ ok ◦]                                   │
+└──────────────────────────────────────────────────────────────────────────────┘
+", output);
+
+					Application.RequestStop ();
+				}
+			};
+
+			Application.Run ();
+		}
 	}
 }

+ 2 - 2
UnitTests/PosTests.cs

@@ -247,7 +247,7 @@ namespace Terminal.Gui.Core {
 				win.Frame.Right, win.Frame.Bottom));
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			var expected = @"
-  Menu                                                                          
+ Menu                                                                           
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │                                                                              │
 │                                                                              │
@@ -310,7 +310,7 @@ namespace Terminal.Gui.Core {
 				win.Frame.Right, win.Frame.Bottom));
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			var expected = @"
-  Menu                                                                          
+ Menu                                                                           
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │                                                                              │
 │                                                                              │

+ 2 - 2
UnitTests/ScrollBarViewTests.cs

@@ -947,7 +947,7 @@ This is a test
 					Flags = MouseFlags.Button1Clicked
 				});
 
-			Assert.Null (Application.mouseGrabView);
+			Assert.Null (Application.MouseGrabView);
 			Assert.True (clicked);
 
 			clicked = false;
@@ -974,7 +974,7 @@ This is a test
 					Flags = MouseFlags.Button1Clicked
 				});
 
-			Assert.Null (Application.mouseGrabView);
+			Assert.Null (Application.MouseGrabView);
 			Assert.True (clicked);
 			Assert.Equal (5, sbv.Size);
 			Assert.False (sbv.ShowScrollIndicator);

+ 108 - 1
UnitTests/ScrollViewTests.cs

@@ -4,9 +4,17 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class ScrollViewTests {
+		readonly ITestOutputHelper output;
+
+		public ScrollViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		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.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);
+			GraphViewTests.AssertDriverContentsWithFrameAre ("", output);
+
+			sv.AutoHideScrollBars = false;
+			sv.ShowHorizontalScrollIndicator = true;
+			sv.ShowVerticalScrollIndicator = true;
+			sv.Redraw (sv.Bounds);
+			GraphViewTests.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);
+			GraphViewTests.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);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+         ▲
+         ░
+         ░
+         ░
+         ┬
+         │
+         ┴
+         ░
+         ▼
+◄░░░├─┤░► 
+", output);
+		}
 	}
-}
+}

+ 442 - 20
UnitTests/TabViewTests.cs

@@ -242,7 +242,7 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact, AutoInitShutdown]
-		public void TestThinTabView_WithLongNames ()
+		public void ShowTopLine_True_TabsOnBottom_False_TestThinTabView_WithLongNames ()
 		{
 			var tv = GetTabView (out var tab1, out var tab2, false);
 			tv.Width = 10;
@@ -257,23 +257,34 @@ namespace Terminal.Gui.Views {
 
 			tv.Redraw (tv.Bounds);
 
-			GraphViewTests.AssertDriverContentsAre (@"
-┌──┐
-│12│13
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──┐      
+│12│13    
 │  └─────┐
 │hi      │
 └────────┘", output);
 
+			tv.SelectedTab = tab2;
 
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+   ┌──┐   
+ 12│13│   
+┌──┘  └──┐
+│hi2     │
+└────────┘", output);
+
+			tv.SelectedTab = tab1;
 			// Test first tab name too long
 			tab1.Text = "12345678910";
 			tab2.Text = "13";
 
 			tv.Redraw (tv.Bounds);
 
-			GraphViewTests.AssertDriverContentsAre (@"
-┌───────┐
-│1234567│
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌───────┐ 
+│1234567│ 
 │       └►
 │hi      │
 └────────┘", output);
@@ -282,9 +293,9 @@ namespace Terminal.Gui.Views {
 			tv.SelectedTab = tab2;
 			tv.Redraw (tv.Bounds);
 
-			GraphViewTests.AssertDriverContentsAre (@"   
-┌──┐
-│13│
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──┐      
+│13│      
 ◄  └─────┐
 │hi2     │
 └────────┘", output);
@@ -296,16 +307,94 @@ namespace Terminal.Gui.Views {
 
 			tv.Redraw (tv.Bounds);
 
-			GraphViewTests.AssertDriverContentsAre (@"     
-┌───────┐
-│abcdefg│
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌───────┐ 
+│abcdefg│ 
 ◄       └┐
 │hi2     │
 └────────┘", output);
 		}
 
 		[Fact, AutoInitShutdown]
-		public void TestTabView_Width4 ()
+		public void ShowTopLine_False_TabsOnBottom_False_TestThinTabView_WithLongNames ()
+		{
+			var tv = GetTabView (out var tab1, out var tab2, false);
+			tv.Width = 10;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { ShowTopLine = false };
+			tv.ApplyStyleChanges ();
+
+			// Ensures that the tab bar subview gets the bounds of the parent TabView
+			tv.LayoutSubviews ();
+
+			// Test two tab names that fit 
+			tab1.Text = "12";
+			tab2.Text = "13";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+│12│13    
+│  └─────┐
+│hi      │
+│        │
+└────────┘", output);
+
+
+			tv.SelectedTab = tab2;
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+ 12│13│   
+┌──┘  └──┐
+│hi2     │
+│        │
+└────────┘", output);
+
+			tv.SelectedTab = tab1;
+
+			// Test first tab name too long
+			tab1.Text = "12345678910";
+			tab2.Text = "13";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+│1234567│ 
+│       └►
+│hi      │
+│        │
+└────────┘", output);
+
+			//switch to tab2
+			tv.SelectedTab = tab2;
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+│13│      
+◄  └─────┐
+│hi2     │
+│        │
+└────────┘", output);
+
+
+			// now make both tabs too long
+			tab1.Text = "12345678910";
+			tab2.Text = "abcdefghijklmnopq";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+│abcdefg│ 
+◄       └┐
+│hi2     │
+│        │
+└────────┘", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width4 ()
 		{
 			var tv = GetTabView (out _, out _, false);
 			tv.Width = 4;
@@ -314,16 +403,36 @@ namespace Terminal.Gui.Views {
 
 			tv.Redraw (tv.Bounds);
 
-			GraphViewTests.AssertDriverContentsAre (@"
-┌─┐
-│T│
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌─┐ 
+│T│ 
 │ └►
 │hi│
 └──┘", output);
 		}
 
 		[Fact, AutoInitShutdown]
-		public void TestTabView_Width3 ()
+		public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width4 ()
+		{
+			var tv = GetTabView (out _, out _, false);
+			tv.Width = 4;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { ShowTopLine = false };
+			tv.ApplyStyleChanges ();
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+│T│ 
+│ └►
+│hi│
+│  │
+└──┘", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_False_TestTabView_Width3 ()
 		{
 			var tv = GetTabView (out _, out _, false);
 			tv.Width = 3;
@@ -332,12 +441,325 @@ namespace Terminal.Gui.Views {
 
 			tv.Redraw (tv.Bounds);
 
-			GraphViewTests.AssertDriverContentsAre (@"
-┌─┐
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌┐ 
+││ 
+│└►
 │h│
 └─┘", output);
 		}
 
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_False_TabsOnBottom_False_TestTabView_Width3 ()
+		{
+			var tv = GetTabView (out _, out _, false);
+			tv.Width = 3;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { ShowTopLine = false };
+			tv.ApplyStyleChanges ();
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+││ 
+│└►
+│h│
+│ │
+└─┘", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_True_TestThinTabView_WithLongNames ()
+		{
+			var tv = GetTabView (out var tab1, out var tab2, false);
+			tv.Width = 10;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+
+			// Ensures that the tab bar subview gets the bounds of the parent TabView
+			tv.LayoutSubviews ();
+
+			// Test two tab names that fit 
+			tab1.Text = "12";
+			tab2.Text = "13";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi      │
+│  ┌─────┘
+│12│13    
+└──┘      ", output);
+
+
+			// Test first tab name too long
+			tab1.Text = "12345678910";
+			tab2.Text = "13";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi      │
+│       ┌►
+│1234567│ 
+└───────┘ ", output);
+
+			//switch to tab2
+			tv.SelectedTab = tab2;
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi2     │
+◄  ┌─────┘
+│13│      
+└──┘      ", output);
+
+
+			// now make both tabs too long
+			tab1.Text = "12345678910";
+			tab2.Text = "abcdefghijklmnopq";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi2     │
+◄       ┌┘
+│abcdefg│ 
+└───────┘ ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_False_TabsOnBottom_True_TestThinTabView_WithLongNames ()
+		{
+			var tv = GetTabView (out var tab1, out var tab2, false);
+			tv.Width = 10;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { ShowTopLine = false, TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+
+			// Ensures that the tab bar subview gets the bounds of the parent TabView
+			tv.LayoutSubviews ();
+
+			// Test two tab names that fit 
+			tab1.Text = "12";
+			tab2.Text = "13";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi      │
+│        │
+│  ┌─────┘
+│12│13    ", output);
+
+
+			tv.SelectedTab = tab2;
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi2     │
+│        │
+└──┐  ┌──┘
+ 12│13│   ", output);
+
+			tv.SelectedTab = tab1;
+
+			// Test first tab name too long
+			tab1.Text = "12345678910";
+			tab2.Text = "13";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi      │
+│        │
+│       ┌►
+│1234567│ ", output);
+
+			//switch to tab2
+			tv.SelectedTab = tab2;
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi2     │
+│        │
+◄  ┌─────┘
+│13│      ", output);
+
+
+			// now make both tabs too long
+			tab1.Text = "12345678910";
+			tab2.Text = "abcdefghijklmnopq";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────────┐
+│hi2     │
+│        │
+◄       ┌┘
+│abcdefg│ ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width4 ()
+		{
+			var tv = GetTabView (out _, out _, false);
+			tv.Width = 4;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──┐
+│hi│
+│ ┌►
+│T│ 
+└─┘ ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width4 ()
+		{
+			var tv = GetTabView (out _, out _, false);
+			tv.Width = 4;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { ShowTopLine = false, TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──┐
+│hi│
+│  │
+│ ┌►
+│T│ ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_True_TestTabView_Width3 ()
+		{
+			var tv = GetTabView (out _, out _, false);
+			tv.Width = 3;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌─┐
+│h│
+│┌►
+││ 
+└┘ ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_False_TabsOnBottom_True_TestTabView_Width3 ()
+		{
+			var tv = GetTabView (out _, out _, false);
+			tv.Width = 3;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { ShowTopLine = false, TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+			tv.LayoutSubviews ();
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌─┐
+│h│
+│ │
+│┌►
+││ ", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_False_With_Unicode ()
+		{
+			var tv = GetTabView (out var tab1, out var tab2, false);
+			tv.Width = 20;
+			tv.Height = 5;
+
+			tv.LayoutSubviews ();
+
+			tab1.Text = "Tab0";
+			tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌────┐              
+│Tab0│              
+│    └─────────────►
+│hi                │
+└──────────────────┘", output);
+
+			tv.SelectedTab = tab2;
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────────┐    
+│Les Misérables│    
+◄              └───┐
+│hi2               │
+└──────────────────┘", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void ShowTopLine_True_TabsOnBottom_True_With_Unicode ()
+		{
+			var tv = GetTabView (out var tab1, out var tab2, false);
+			tv.Width = 20;
+			tv.Height = 5;
+			tv.Style = new TabView.TabStyle { TabsOnBottom = true };
+			tv.ApplyStyleChanges ();
+
+			tv.LayoutSubviews ();
+
+			tab1.Text = "Tab0";
+			tab2.Text = "Les Mise" + Char.ConvertFromUtf32 (Int32.Parse ("0301", NumberStyles.HexNumber)) + "rables";
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────────────┐
+│hi                │
+│    ┌─────────────►
+│Tab0│              
+└────┘              ", output);
+
+			tv.SelectedTab = tab2;
+
+			tv.Redraw (tv.Bounds);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+┌──────────────────┐
+│hi2               │
+◄              ┌───┘
+│Les Misérables│    
+└──────────────┘    ", output);
+		}
+
 		private void InitFakeDriver ()
 		{
 			var driver = new FakeDriver ();

+ 52 - 0
UnitTests/TextFieldTests.cs

@@ -1203,6 +1203,52 @@ namespace Terminal.Gui.Views {
 			Application.Driver.SendKeys ('j', ConsoleKey.A, false, false, false);
 			Assert.Equal ("aj", tf.Text.ToString ());
 		}
+		[Fact]
+		[AutoInitShutdown]
+		public void Test_RootMouseKeyEvent_Cancel ()
+		{
+			Application.RootMouseEvent += SuppressRightClick;
+
+			var tf = new TextField () { Width = 10 };
+			int clickCounter = 0;
+			tf.MouseClick += (m) => { clickCounter++; };
+
+			Application.Top.Add (tf);
+			Application.Begin (Application.Top);
+
+			var processMouseEventMethod = typeof (Application).GetMethod ("ProcessMouseEvent", BindingFlags.Static | BindingFlags.NonPublic)
+				?? throw new Exception ("Expected private method not found 'ProcessMouseEvent', this method was used for testing mouse behaviours");
+
+			var mouseEvent = new MouseEvent {
+				Flags = MouseFlags.Button1Clicked,
+				View = tf
+			};
+
+			processMouseEventMethod.Invoke (null, new object [] { mouseEvent });
+			Assert.Equal (1, clickCounter);
+
+			// Get a fresh instance that represents a right click.
+			// Should be ignored because of SuppressRightClick callback
+			mouseEvent = new MouseEvent {
+				Flags = MouseFlags.Button3Clicked,
+				View = tf
+			};
+			processMouseEventMethod.Invoke (null, new object [] { mouseEvent });
+			Assert.Equal (1, clickCounter);
+
+			Application.RootMouseEvent -= SuppressRightClick;
+
+			// Get a fresh instance that represents a right click.
+			// Should no longer be ignored as the callback was removed
+			mouseEvent = new MouseEvent {
+				Flags = MouseFlags.Button3Clicked,
+				View = tf
+			};
+
+			processMouseEventMethod.Invoke (null, new object [] { mouseEvent });
+			Assert.Equal (2, clickCounter);
+		}
+
 
 		private bool SuppressKey (KeyEvent arg)
 		{
@@ -1212,6 +1258,12 @@ namespace Terminal.Gui.Views {
 			return false;
 		}
 
+		private void SuppressRightClick (MouseEvent arg)
+		{
+			if (arg.Flags.HasFlag (MouseFlags.Button3Clicked))
+				arg.Handled = true;
+		}
+
 		[Fact, AutoInitShutdown]
 		public void ScrollOffset_Initialize ()
 		{

+ 1 - 1
UnitTests/UnitTests.csproj

@@ -18,7 +18,7 @@
     <DefineConstants>TRACE;DEBUG_IDISPOSABLE</DefineConstants>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
     <PackageReference Include="ReportGenerator" Version="5.1.10" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="xunit" Version="2.4.2" />

+ 172 - 2
UnitTests/ViewTests.cs

@@ -2123,6 +2123,28 @@ Y
 			Assert.Equal (new Rect (0, 0, 8, 4), pos);
 		}
 
+		[Fact, AutoInitShutdown]
+		public void DrawFrame_With_Minimum_Size ()
+		{
+			var view = new View (new Rect (0, 0, 2, 2));
+
+			view.DrawContent += (_) => view.DrawFrame (view.Bounds, 0, true);
+
+			Assert.Equal (Point.Empty, new Point (view.Frame.X, view.Frame.Y));
+			Assert.Equal (new Size (2, 2), new Size (view.Frame.Width, view.Frame.Height));
+
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			var expected = @"
+┌┐
+└┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 2, 2), pos);
+		}
+
 		[Fact, AutoInitShutdown]
 		public void DrawFrame_With_Negative_Positions ()
 		{
@@ -2452,7 +2474,7 @@ Y
 				Width = Dim.Fill (),
 				Height = Dim.Fill ()
 			};
-			view.LayoutComplete += e => {
+			view.DrawContent += e => {
 				view.DrawFrame (view.Bounds);
 				var savedClip = Application.Driver.Clip;
 				Application.Driver.Clip = new Rect (1, 1, view.Bounds.Width - 2, view.Bounds.Height - 2);
@@ -2500,7 +2522,7 @@ Y
 				Width = Dim.Fill (),
 				Height = Dim.Fill ()
 			};
-			view.LayoutComplete += e => {
+			view.DrawContent += e => {
 				view.DrawFrame (view.Bounds);
 				var savedClip = Application.Driver.Clip;
 				Application.Driver.Clip = new Rect (1, 1, view.Bounds.Width - 2, view.Bounds.Height - 2);
@@ -3892,5 +3914,153 @@ This is a tes
 This is a tes
 ", output);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void DrawContentComplete_Event_Is_Always_Called ()
+		{
+			var viewCalled = false;
+			var tvCalled = false;
+
+			var view = new View ("View") { Width = 10, Height = 10 };
+			view.DrawContentComplete += (e) => viewCalled = true;
+			var tv = new TextView () { Y = 11, Width = 10, Height = 10 };
+			tv.DrawContentComplete += (e) => tvCalled = true;
+
+			Application.Top.Add (view, tv);
+			Application.Begin (Application.Top);
+
+			Assert.True (viewCalled);
+			Assert.True (tvCalled);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void KeyDown_And_KeyUp_Events_Must_Called_Before_OnKeyDown_And_OnKeyUp ()
+		{
+			var keyDown = false;
+			var keyPress = false;
+			var keyUp = false;
+
+			var view = new DerivedView ();
+			view.KeyDown += (e) => {
+				Assert.Equal (Key.a, e.KeyEvent.Key);
+				Assert.False (keyDown);
+				Assert.False (view.IsKeyDown);
+				e.Handled = true;
+				keyDown = true;
+			};
+			view.KeyPress += (e) => {
+				Assert.Equal (Key.a, e.KeyEvent.Key);
+				Assert.False (keyPress);
+				Assert.False (view.IsKeyPress);
+				e.Handled = true;
+				keyPress = true;
+			};
+			view.KeyUp += (e) => {
+				Assert.Equal (Key.a, e.KeyEvent.Key);
+				Assert.False (keyUp);
+				Assert.False (view.IsKeyUp);
+				e.Handled = true;
+				keyUp = true;
+			};
+
+			Application.Top.Add (view);
+
+			Console.MockKeyPresses.Push (new ConsoleKeyInfo ('a', ConsoleKey.A, false, false, false));
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Assert.True (view.CanFocus);
+
+			Application.Run ();
+			Application.Shutdown ();
+
+			Assert.True (keyDown);
+			Assert.True (keyPress);
+			Assert.True (keyUp);
+			Assert.False (view.IsKeyDown);
+			Assert.False (view.IsKeyPress);
+			Assert.False (view.IsKeyUp);
+		}
+
+		public class DerivedView : View {
+			public DerivedView ()
+			{
+				CanFocus = true;
+			}
+
+			public bool IsKeyDown { get; set; }
+			public bool IsKeyPress { get; set; }
+			public bool IsKeyUp { get; set; }
+
+			public override bool OnKeyDown (KeyEvent keyEvent)
+			{
+				IsKeyDown = true;
+				return true;
+			}
+
+			public override bool ProcessKey (KeyEvent keyEvent)
+			{
+				IsKeyPress = true;
+				return true;
+			}
+
+			public override bool OnKeyUp (KeyEvent keyEvent)
+			{
+				IsKeyUp = true;
+				return true;
+			}
+		}
+
+		[Theory, AutoInitShutdown]
+		[InlineData (true, false, false)]
+		[InlineData (true, true, false)]
+		[InlineData (true, true, true)]
+		public void KeyDown_And_KeyUp_Events_With_Only_Key_Modifiers (bool shift, bool alt, bool control)
+		{
+			var keyDown = false;
+			var keyPress = false;
+			var keyUp = false;
+
+			var view = new DerivedView ();
+			view.KeyDown += (e) => {
+				Assert.Equal (-1, e.KeyEvent.KeyValue);
+				Assert.Equal (shift, e.KeyEvent.IsShift);
+				Assert.Equal (alt, e.KeyEvent.IsAlt);
+				Assert.Equal (control, e.KeyEvent.IsCtrl);
+				Assert.False (keyDown);
+				Assert.False (view.IsKeyDown);
+				keyDown = true;
+			};
+			view.KeyPress += (e) => {
+				keyPress = true;
+			};
+			view.KeyUp += (e) => {
+				Assert.Equal (-1, e.KeyEvent.KeyValue);
+				Assert.Equal (shift, e.KeyEvent.IsShift);
+				Assert.Equal (alt, e.KeyEvent.IsAlt);
+				Assert.Equal (control, e.KeyEvent.IsCtrl);
+				Assert.False (keyUp);
+				Assert.False (view.IsKeyUp);
+				keyUp = true;
+			};
+
+			Application.Top.Add (view);
+
+			Console.MockKeyPresses.Push (new ConsoleKeyInfo ('\0', (ConsoleKey)'\0', shift, alt, control));
+
+			Application.Iteration += () => Application.RequestStop ();
+
+			Assert.True (view.CanFocus);
+
+			Application.Run ();
+			Application.Shutdown ();
+
+			Assert.True (keyDown);
+			Assert.False (keyPress);
+			Assert.True (keyUp);
+			Assert.True (view.IsKeyDown);
+			Assert.False (view.IsKeyPress);
+			Assert.True (view.IsKeyUp);
+		}
 	}
 }

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

+ 0 - 4
packages.config

@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<packages>
-  <package id="NStack.Core" version="0.14.0" targetFramework="net472" />
-</packages>

+ 19 - 0
testenvironments.json

@@ -0,0 +1,19 @@
+{
+	// Remote Testing (experimental preview).
+	// Here is some documentation https://learn.microsoft.com/en-us/visualstudio/test/remote-testing?view=vs-2022.
+	// Here a screen shot of the VS2022 where are the Test Explorer https://user-images.githubusercontent.com/13117724/196798350-5a6f94d3-b6cd-424e-b4e8-a9b507dc057a.png.
+	// Ignore "Could not find 'mono' host" error because unit tests don't use the .NET Framework.
+	"version": "1",
+	"environments": [
+		{
+			"name": "WSL-Ubuntu",
+			"type": "wsl",
+			"wslDistribution": "Ubuntu"
+		},
+		{
+			"name": "WSL-Debian",
+			"type": "wsl",
+			"wslDistribution": "Debian"
+		}
+	]
+}

部分文件因为文件数量过多而无法显示