Selaa lähdekoodia

Merge branch 'master' of tig:migueldeicaza/gui.cs

Charlie Kindel 5 vuotta sitten
vanhempi
commit
8006fa357e

+ 1 - 0
.editorconfig

@@ -6,6 +6,7 @@ csharp_new_line_before_open_brace = methods,local_functions
 csharp_new_line_before_else = false
 csharp_new_line_before_catch = false
 csharp_new_line_before_finally = false
+end_of_line = crlf
 
 csharp_indent_case_contents = true
 csharp_indent_switch_labels = false

+ 13 - 0
.gitattributes

@@ -0,0 +1,13 @@
+# Set the default behavior for all files.
+* text=auto
+
+# Normalized and converts to 
+# native line endings on checkout.
+*.cs text
+
+# Convert to LF line endings on checkout.
+*.sh text eol=lf
+
+# Binary files.
+*.png binary
+*.jpg binary

+ 3 - 2
.gitignore

@@ -1,8 +1,9 @@
 bin
 obj
-*~
+~$*
 *.userprefs
 *~
 packages
 .vs
-*.csproj.user
+# User-specific files
+*.user

+ 85 - 32
Example/demo.cs

@@ -6,6 +6,7 @@ using System.Diagnostics;
 using System.Globalization;
 using System.Reflection;
 using NStack;
+using System.Text;
 
 static class Demo {
 	//class Box10x : View, IScrollView {
@@ -81,22 +82,26 @@ static class Demo {
 		}
 	}
 
-
 	static void ShowTextAlignments ()
 	{
-		var container = new Dialog (
-			"Text Alignments", 50, 20,
-			new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
-			new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
-
+		var container = new Window ($"Show Text Alignments") {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		container.OnKeyUp += (KeyEvent ke) => {
+			if (ke.Key == Key.Esc)
+				container.Running = false;
+		};
 
 		int i = 0;
-		string txt = "Hello world, how are you doing today";
+		string txt = "Hello world, how are you doing today?";
 		container.Add (
-				new Label(new Rect(0, 1, 40, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
-				new Label(new Rect(0, 3, 40, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
-				new Label(new Rect(0, 5, 40, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
-				new Label(new Rect(0, 7, 40, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
+				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);
@@ -408,6 +413,44 @@ static class Demo {
 	#endregion
 
 
+	#region OnKeyDown / OnKeyUp Demo
+	private static void OnKeyDownUpDemo ()
+	{
+		var container = new Dialog (
+			"OnKeyDown & OnKeyUp demo", 80, 20,
+			new Button ("Close") { Clicked = () => { Application.RequestStop (); } }) {
+			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 KeyUpDown (KeyEvent keyEvent, string updown)
+		{
+			if ((keyEvent.Key & Key.CtrlMask) != 0) {
+				list.Add ($"Key{updown,-4}: Ctrl ");
+			} else if ((keyEvent.Key & Key.AltMask) != 0) {
+				list.Add ($"Key{updown,-4}: Alt ");
+			} else {
+				list.Add ($"Key{updown,-4}: {(((uint)keyEvent.KeyValue & (uint)Key.CharMask) > 26 ? $"{(char)keyEvent.KeyValue}" : $"{keyEvent.Key}")}");
+			}
+			listView.MoveDown ();
+		}
+
+		container.OnKeyDown += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Down");
+		container.OnKeyUp += (KeyEvent keyEvent) => KeyUpDown (keyEvent, "Up");
+		Application.Run (container);
+	}
+	#endregion
+
 	public static Label ml;
 	public static MenuBar menu;
 	public static CheckBox menuKeysStyle;
@@ -418,7 +461,6 @@ static class Demo {
 			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
 
 		//Application.UseSystemConsole = true;
-		Console.WindowHeight = 35;
 
 		Application.Init ();
 
@@ -426,11 +468,13 @@ static class Demo {
 
 		//Open ();
 #if true
+		int margin = 3;
 		var win = new Window ("Hello") {
 			X = 1,
 			Y = 1,
-			Width = Dim.Fill (),
-			Height = Dim.Fill ()-1
+
+			Width = Dim.Fill () - margin,
+			Height = Dim.Fill () - margin
 		};
 #else
 		var tframe = top.Frame;
@@ -451,7 +495,7 @@ static class Demo {
 
 		menu = new MenuBar (new MenuBarItem [] {
 			new MenuBarItem ("_File", new MenuItem [] {
-				new MenuItem ("Text Editor Demo", "", () => { Editor (top); }),
+				new MenuItem ("Text _Editor Demo", "", () => { Editor (top); }),
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_Open", "", Open),
 				new MenuItem ("_Hex", "", () => ShowHex (top)),
@@ -469,18 +513,19 @@ static class Demo {
 				menuItems[3]
 			}),
 			new MenuBarItem ("_List Demos", new MenuItem [] {
-				new MenuItem ("Select Multiple Items", "", () => ListSelectionDemo (true)),
-				new MenuItem ("Select Single Item", "", () => ListSelectionDemo (false)),
+				new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)),
+				new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)),
 			}),
-			new MenuBarItem ("Assorted", new MenuItem [] {
-				new MenuItem ("Show text alignments", "", () => ShowTextAlignments ())
+			new MenuBarItem ("A_ssorted", new MenuItem [] {
+				new MenuItem ("_Show text alignments", "", () => ShowTextAlignments ()),
+				new MenuItem ("_OnKeyDown/Up", "", () => OnKeyDownUpDemo ())
 			}),
-			new MenuBarItem ("Test Menu and SubMenus", new MenuItem [] {
-				new MenuItem ("SubMenu1Item1",
+			new MenuBarItem ("_Test Menu and SubMenus", new MenuItem [] {
+				new MenuItem ("SubMenu1Item_1",
 					new MenuBarItem (new MenuItem[] {
-						new MenuItem ("SubMenu2Item1",
+						new MenuItem ("SubMenu2Item_1",
 							new MenuBarItem (new MenuItem [] {
-								new MenuItem ("SubMenu3Item1",
+								new MenuItem ("SubMenu3Item_1",
 									new MenuBarItem (new MenuItem [] { menuItems [2] })
 								)
 							})
@@ -488,6 +533,7 @@ static class Demo {
 					})
 				)
 			}),
+			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);
@@ -498,7 +544,7 @@ static class Demo {
 		ShowEntries (win);
 
 		int count = 0;
-		ml = new Label (new Rect (3, 18, 47, 1), "Mouse: ");
+		ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
 		Application.RootMouseEvent += delegate (MouseEvent me) {
 			ml.TextColor = Colors.TopLevel.Normal;
 			ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
@@ -520,24 +566,31 @@ static class Demo {
 			new StatusItem(Key.F2, "~F2~ Load", null),
 			new StatusItem(Key.F3, "~F3~ Save", null),
 			new StatusItem(Key.ControlX, "~^X~ Quit", () => { if (Quit ()) top.Running = false; }),
-		});
+		}) {
+			Parent = null,
+		};
 
 		win.Add (drag, dragText);
 #if true
-		// This currently causes a stack overflow, because it is referencing a window that has not had its size allocated yet
+		// FIXED: This currently causes a stack overflow, because it is referencing a window that has not had its size allocated yet
 
-		var bottom = new Label ("This should go on the bottom!");
+		var bottom = new Label ("This should go on the bottom of the same top-level!");
 		win.Add (bottom);
-
-		Application.OnResized = () => {
-			bottom.X = Pos.Left (win);
-			bottom.Y = Pos.Bottom (win);
+		var bottom2 = new Label ("This should go on the bottom of another top-level!");
+		top.Add (bottom2);
+
+		Application.OnLoad = () => {
+			bottom.X = win.X;
+			bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin;
+			bottom2.X = Pos.Left (win);
+			bottom2.Y = Pos.Bottom (win);
 		};
 #endif
 
+
 		top.Add (win);
 		//top.Add (menu);
-		top.Add (menu, statusBar, ml);
+		top.Add (menu, statusBar);
 		Application.Run ();
 	}
 }

+ 4 - 0
FSharpExample/.editorconfig

@@ -0,0 +1,4 @@
+[*.fs]
+indent_style = space
+indent_size = 4
+tab_width = 4

+ 5 - 1
FSharpExample/FSharpExample.fsproj

@@ -10,7 +10,11 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Terminal.Gui" Version="0.9.0" />
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Update="FSharp.Core" Version="4.7.1" />
   </ItemGroup>
 
 </Project>

+ 31 - 0
FSharpExample/FSharpExample.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30011.22
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "FSharpExample", "FSharpExample.fsproj", "{6E4DF691-FA5F-4D7C-8DBC-8656103C5CB1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui", "..\Terminal.Gui\Terminal.Gui.csproj", "{FA48E777-1308-489D-95A0-89DE46B65A93}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{6E4DF691-FA5F-4D7C-8DBC-8656103C5CB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6E4DF691-FA5F-4D7C-8DBC-8656103C5CB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6E4DF691-FA5F-4D7C-8DBC-8656103C5CB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6E4DF691-FA5F-4D7C-8DBC-8656103C5CB1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FA48E777-1308-489D-95A0-89DE46B65A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FA48E777-1308-489D-95A0-89DE46B65A93}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FA48E777-1308-489D-95A0-89DE46B65A93}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FA48E777-1308-489D-95A0-89DE46B65A93}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {A023D2E3-EF0F-4986-8E6C-323F967788B7}
+	EndGlobalSection
+EndGlobal

+ 435 - 31
FSharpExample/Program.fs

@@ -1,37 +1,441 @@
 // Learn more about F# at http://fsharp.org
 
-open System
 open Terminal.Gui
+open System
+open Mono.Terminal
+open System.Collections.Generic
+open System.Diagnostics
+open System.Globalization
+open System.Reflection
 open NStack
 
+type Demo() = class end
+    let ustr (x:string) = ustring.Make(x)
+    let mutable ml2 = Unchecked.defaultof<Label>
+    let mutable ml = Unchecked.defaultof<Label>
+    let mutable menu = Unchecked.defaultof<MenuBar>
+    let mutable menuKeysStyle = Unchecked.defaultof<CheckBox>
+    let mutable menuAutoMouseNav = Unchecked.defaultof<CheckBox>
+
+    type Box10x() =
+        inherit View()
+        member val w = 40 with get, set
+        member val h = 50 with get, set
+        member val WantCursorPosition = Unchecked.defaultof<System.Boolean> with get, set
+        new(x : int, y : int) as this =
+            (Box10x())
+            then
+            ()
+        member this.GetContentSize() =
+            new Size(this.w, this.h)
+        member this.SetCursorPosition(pos : Point) =
+            raise (new NotImplementedException())
+        override this.Redraw(region : Rect) =
+            Application.Driver.SetAttribute (Application.Current.ColorScheme.Focus)
+            do
+            let mutable (y : int) = 0
+            while (y < this.h) do
+            this.Move (0, y)
+            Application.Driver.AddStr (ustr (y.ToString()))
+            do
+            let mutable (x : int) = 0
+            while (x < this.w - (y.ToString ()).Length) do
+                if (y.ToString ()).Length < this.w
+                then Application.Driver.AddStr (ustr " ")
+                x <- x + 1
+            x
+            y <- y + 1
+            y
+            ()
+
+    type Filler() =
+        inherit View()
+        new(rect : Rect) as this =
+            (Filler ())
+            then
+            ()
+        override this.Redraw(region : Rect) =
+            Application.Driver.SetAttribute (Application.Current.ColorScheme.Focus)
+            let mutable f = this.Frame
+            do
+            let mutable (y : int) = 0
+            while (y < f.Width) do
+            this.Move (0, y)
+            do
+            let mutable (x : int) = 0
+            while (x < f.Height) do
+                let mutable (r : Rune) = Unchecked.defaultof<Rune>
+                match (x % 3) with
+                | 0 ->
+                    Application.Driver.AddRune ((Rune ((y.ToString ()).ToCharArray (0, 1)).[0]))
+                    if y > 9
+                    then Application.Driver.AddRune ((Rune ((y.ToString ()).ToCharArray (1, 1)).[0]))
+                    r <- (Rune '.')
+                | 1 ->
+                    r <- (Rune 'o')
+                | _ ->
+                    r <- (Rune 'O')
+                Application.Driver.AddRune (r)
+                x <- x + 1
+            x
+            y <- y + 1
+            y
+            ()
+
+    let ShowTextAlignments() =
+        let mutable container = new Dialog(
+            ustr "Text Alignments", 50, 20,
+            new Button (ustr "Ok", true, Clicked = Action(Application.RequestStop)),
+            new Button (ustr "Cancel", true, Clicked = Action(Application.RequestStop))
+            )
+        let mutable (i : int) = 0
+        let mutable (txt : string) = "Hello world, how are you doing today"
+        container.Add (
+            new Label (new Rect (0, 1, 40, 3), ustr ((sprintf "%O-%O" (i + 1)) txt), TextAlignment = TextAlignment.Left),
+            new Label (new Rect (0, 3, 40, 3), ustr ((sprintf "%O-%O" (i + 2)) txt), TextAlignment = TextAlignment.Right),
+            new Label (new Rect (0, 5, 40, 3), ustr ((sprintf "%O-%O" (i + 3)) txt), TextAlignment = TextAlignment.Centered),
+            new Label (new Rect (0, 7, 40, 3), ustr ((sprintf "%O-%O" (i + 4)) txt), TextAlignment = TextAlignment.Justified)
+            )
+        Application.Run (container)
+
+    let ShowEntries(container : View) =
+        let mutable scrollView = new ScrollView (new Rect (50, 10, 20, 8),
+            ContentSize = new Size (20, 50),
+            ShowVerticalScrollIndicator = true,
+            ShowHorizontalScrollIndicator = true
+            )
+        scrollView.Add (new Filler(new Rect(0, 0, 40, 40)))
+        let mutable scrollView2 = new ScrollView (new Rect (72, 10, 3, 3),
+            ContentSize = new Size (100, 100),
+            ShowVerticalScrollIndicator = true,
+            ShowHorizontalScrollIndicator = true
+            )
+        scrollView2.Add (new Box10x(0, 0))
+        let mutable progress = new ProgressBar(new Rect(68, 1, 10, 1))
+        let timer = Func<MainLoop, bool> (fun (caller) ->
+            progress.Pulse ();
+            true)
+
+        Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300.0), timer) |> ignore
+
+        let mutable login = Label (ustr "Login: ",
+            X = Pos.At(3),
+            Y = Pos.At(6)
+            )
+        let mutable password = new Label (ustr "Password: ",
+            X = Pos.Left (login),
+            Y = Pos.Bottom (login) + Pos.At(1)
+            )
+        let mutable loginText = new TextField (ustr "",
+            X = Pos.Right (password),
+            Y = Pos.Top (login),
+            Width = Dim.op_Implicit(40)
+            )
+        let mutable passText = new TextField (ustr "",
+            Secret = true,
+            X = Pos.Left (loginText),
+            Y = Pos.Top (password),
+            Width = Dim.Width (loginText)
+            )
+        let mutable tf = new Button(3, 19, ustr "Ok")
+        container.Add (login, loginText, password, passText,
+            new FrameView (new Rect (3, 10, 25, 6), ustr "Options",
+                [|new CheckBox (1, 0, ustr "Remember me");
+                new RadioGroup (1, 2, [|"_Personal"; "_Company"|])|]
+                ),
+            new ListView (new Rect(59, 6, 16, 4),
+                    [|"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, ustr "Cancel"),
+            new TimeField(3, 20, DateTime.Now),
+            new TimeField(23, 20, DateTime.Now, true),
+            new DateField(3, 22, DateTime.Now),
+            new DateField(23, 22, DateTime.Now, true),
+            progress,
+            new Label(3, 24, ustr "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
+            menuKeysStyle,
+            menuAutoMouseNav
+        )
+        container.SendSubviewToBack (tf)
+        ()
+    let NewFile() =
+        let mutable d = new Dialog (ustr "New File", 50, 20,
+                            new Button (ustr "Ok", true, Clicked = Action(Application.RequestStop)),
+                            new Button (ustr "Cancel", true, Clicked = Action(Application.RequestStop))
+        )
+        ml2 <- new Label(1, 1, ustr "Mouse Debug Line")
+        d.Add (ml2)
+        Application.Run (d)
+
+    let Editor(top : Toplevel) =
+        let mutable tframe = top.Frame
+        let mutable ntop = new Toplevel(tframe)
+        let mutable menu = new MenuBar([|new MenuBarItem(ustr "_File",
+            [|new MenuItem(ustr "_Close", "", (fun () -> Application.RequestStop ()))|]);
+            new MenuBarItem(ustr "_Edit", [|new MenuItem(ustr "_Copy", "", Unchecked.defaultof<_>);
+            new MenuItem(ustr "C_ut", "", Unchecked.defaultof<_>);
+            new MenuItem(ustr "_Paste", "", Unchecked.defaultof<_>)|])|]
+            )
+        ntop.Add (menu)
+        let mutable (fname : string) = Unchecked.defaultof<_>
+        for s in [|"/etc/passwd"; "c:\\windows\\win.ini"|] do
+            if System.IO.File.Exists (s)
+            then
+                fname <- s
+        let mutable win = new Window (ustr(if fname <> null then fname else "Untitled"),
+            X = Pos.At(0),
+            Y = Pos.At(1),
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+        )
+        ntop.Add (win)
+        let mutable text = new TextView(new Rect(0, 0, (tframe.Width - 2), (tframe.Height - 3)))
+        if fname <> Unchecked.defaultof<_>
+        then text.Text <- ustr (System.IO.File.ReadAllText (fname))
+        win.Add (text)
+        Application.Run (ntop)
+
+    let Quit() =
+        let mutable n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No")
+        n = 0
+
+    let Close() =
+        MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok")
+        |> ignore
+
+    let Open() =
+        let mutable d = new OpenDialog (ustr "Open", ustr "Open a file", AllowsMultipleSelection = true)
+        Application.Run (d)
+        if not d.Canceled
+            then MessageBox.Query (50, 7, "Selected File", (String.Join (", ", d.FilePaths)), "Ok") |> ignore
+
+    let ShowHex(top : Toplevel) =
+        let mutable tframe = top.Frame
+        let mutable ntop = new Toplevel(tframe)
+        let mutable menu = new MenuBar([|new MenuBarItem(ustr "_File",
+            [|new MenuItem(ustr "_Close", "", (fun () -> Application.RequestStop ()))|])|])
+        ntop.Add (menu)
+        let mutable win = new Window (ustr "/etc/passwd",
+            X = Pos.At(0),
+            Y = Pos.At(1),
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+            )
+        ntop.Add (win)
+        let mutable source = System.IO.File.OpenRead ("/etc/passwd")
+        let mutable hex = new HexView (source,
+            X = Pos.At(0),
+            Y = Pos.At(0),
+            Width = Dim.Fill (),
+            Height = Dim.Fill ()
+            )
+        win.Add (hex)
+        Application.Run (ntop)
+
+    type MenuItemDetails() =
+        inherit MenuItem()
+        new(title : ustring, help : string, action : Action) as this =
+            (MenuItemDetails ())
+            then
+                this.Title <- title
+                this.Help <- ustr help
+                this.Action <- action
+        static member Instance(mi : MenuItem) =
+            (mi.GetMenuItem ()) :?> MenuItemDetails
+
+    type MenuItemDelegate = delegate of MenuItemDetails -> MenuItem
+
+    let ShowMenuItem(mi : MenuItemDetails) =
+        let mutable (flags : BindingFlags) = BindingFlags.Public ||| BindingFlags.Static
+        let mutable (minfo : MethodInfo) = typeof<MenuItemDetails>.GetMethod ("Instance", flags)
+        let mutable (mid : Delegate) = Delegate.CreateDelegate (typeof<MenuItemDelegate>, minfo)
+        MessageBox.Query (70, 7, (mi.Title.ToString ()),
+            ((sprintf "%O selected. Is from submenu: %O" (mi.Title.ToString ())) (mi.GetMenuBarItem ())), "Ok")
+        |> ignore
+
+    let MenuKeysStyle_Toggled(e : EventArgs) =
+        menu.UseKeysUpDownAsKeysLeftRight <- menuKeysStyle.Checked
+
+    let MenuAutoMouseNav_Toggled(e : EventArgs) =
+        menu.WantMousePositionReports <- menuAutoMouseNav.Checked
+
+    let Copy() =
+        let mutable (textField : TextField) = menu.LastFocused :?> TextField
+        if textField <> Unchecked.defaultof<_> && textField.SelectedLength <> 0
+        then textField.Copy ()
+        ()
+
+    let Cut() =
+        let mutable (textField : TextField) = menu.LastFocused :?> TextField
+        if textField <> Unchecked.defaultof<_> && textField.SelectedLength <> 0
+        then textField.Cut ()
+        ()
+
+    let Paste() =
+        let mutable (textField : TextField) = menu.LastFocused :?> TextField
+        if textField <> Unchecked.defaultof<_>
+        then textField.Paste ()
+        ()
+
+    let Help() =
+        MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok")
+        |> ignore
+
+    let ListSelectionDemo(multiple : System.Boolean) =
+        let mutable d = new Dialog (ustr "Selection Demo", 60, 20,
+            new Button (ustr "Ok", true, Clicked = fun () -> Application.RequestStop ()),
+            new Button (ustr "Cancel", Clicked = fun () -> Application.RequestStop ())
+            )
+        let mutable animals = new List<string> ()
+        animals.AddRange([|"Alpaca"; "Llama"; "Lion"; "Shark"; "Goat"|])
+        let mutable msg = new Label (ustr "Use space bar or control-t to toggle selection",
+            X = Pos.At(1),
+            Y = Pos.At(1),
+            Width = Dim.Fill () - Dim.op_Implicit(1),
+            Height = Dim.op_Implicit(1)
+            )
+        let mutable list = new ListView (animals,
+            X = Pos.At(1),
+            Y = Pos.At(3),
+            Width = Dim.Fill () - Dim.op_Implicit(4),
+            Height = Dim.Fill () - Dim.op_Implicit(4),
+            AllowsMarking = true,
+            AllowsMultipleSelection = multiple
+            )
+        d.Add (msg, list)
+        Application.Run (d)
+        let mutable result = ""
+        do
+            let mutable (i : int) = 0
+            while (i < animals.Count) do
+            if list.Source.IsMarked (i)
+            then result <- result + animals.[i] + " "
+            i <- i + 1
+            i
+            ()
+        MessageBox.Query (60, 10, "Selected Animals", (if result = "" then "No animals selected" else result), "Ok") |> ignore
+
+    let KeyUpDown(keyEvent : KeyEvent, kl : Label, updown : string) =
+        kl.TextColor <- Colors.TopLevel.Normal
+        if keyEvent.Key &&& Key.CtrlMask <> Key.Unknown
+        then kl.Text <- ustr (sprintf "Keyboard: Ctrl Key%O" updown)
+        else
+            if keyEvent.Key &&& Key.AltMask <> Key.Unknown
+            then kl.Text <- ustr (sprintf "Keyboard: Alt Key%O" updown)
+            else kl.Text <- ustr (sprintf "Keyboard: %O Key%O" (char keyEvent.KeyValue) updown)
+
+    let OnKeyDownUpDemo() =
+        let mutable container = new Dialog(ustr "OnKeyDown & OnKeyUp demo", 50, 20,
+            new Button (ustr "Ok", true, Clicked = fun () -> Application.RequestStop ()),
+            new Button (ustr "Cancel", Clicked = fun () -> Application.RequestStop ())
+            )
+        let mutable kl = new Label(new Rect(3, 3, 40, 1), ustr "Keyboard: ")
+        container.OnKeyDown <- Action<KeyEvent>(fun (keyEvent : KeyEvent) -> KeyUpDown (keyEvent, kl, "Down"))
+        container.OnKeyUp <- Action<KeyEvent>(fun (keyEvent : KeyEvent) -> KeyUpDown (keyEvent, kl, "Up"))
+        container.Add (kl)
+        Application.Run (container)
+
+    let Main() =
+        if Debugger.IsAttached
+        then CultureInfo.DefaultThreadCurrentUICulture <- CultureInfo.GetCultureInfo ("en-US")
+        Application.Init ()
+        let mutable top = Application.Top
+        let mutable (margin : int) = 3
+        let mutable win = new Window (ustr "Hello",
+            X = Pos.At(1),
+            Y = Pos.At(1),
+
+            Width = Dim.Fill () - Dim.op_Implicit(margin),
+            Height = Dim.Fill () - Dim.op_Implicit(margin)
+            )
+        let mutable (menuItems : MenuItemDetails[]) = [|new MenuItemDetails(ustr "F_ind", "", Unchecked.defaultof<_>);
+            new MenuItemDetails(ustr "_Replace", "", Unchecked.defaultof<_>);
+            new MenuItemDetails(ustr "_Item1", "", Unchecked.defaultof<_>);
+            new MenuItemDetails(ustr "_Not From Sub Menu", "", Unchecked.defaultof<_>)|]
+        menuItems.[0].Action <- fun () -> ShowMenuItem (menuItems.[0])
+        menuItems.[1].Action <- fun () -> ShowMenuItem (menuItems.[1])
+        menuItems.[2].Action <- fun () -> ShowMenuItem (menuItems.[2])
+        menuItems.[3].Action <- fun () -> ShowMenuItem (menuItems.[3])
+        menu <-
+            new MenuBar ([|new MenuBarItem(ustr "_File",
+                [|new MenuItem (ustr "Text _Editor Demo", "", (fun () -> Editor (top)));
+                    new MenuItem (ustr "_New", "Creates new file", fun () -> NewFile());
+                    new MenuItem (ustr "_Open", "", fun () -> Open());
+                    new MenuItem (ustr "_Hex", "", (fun () -> ShowHex (top)));
+                    new MenuItem (ustr "_Close", "", (fun () -> Close()));
+                    new MenuItem (ustr "_Disabled", "", (fun () -> ()), (fun () -> false));
+                    Unchecked.defaultof<_>;
+                    new MenuItem (ustr "_Quit", "", (fun () -> if Quit() then top.Running <- false))|]);
+                new MenuBarItem (ustr "_Edit", [|new MenuItem(ustr "_Copy", "", fun () -> Copy());
+                    new MenuItem(ustr "C_ut", "", fun () -> Cut()); new MenuItem(ustr "_Paste", "", fun () -> Paste());
+                    new MenuItem(ustr "_Find and Replace", new MenuBarItem([|(menuItems.[0]);
+                    (menuItems.[1])|])); (menuItems.[3])|]);
+                new MenuBarItem(ustr "_List Demos", [|new MenuItem(ustr "Select _Multiple Items", "", (fun () -> ListSelectionDemo (true)));
+                    new MenuItem(ustr "Select _Single Item", "", (fun () -> ListSelectionDemo (false)))|]);
+                    new MenuBarItem(ustr "A_ssorted", [|new MenuItem(ustr "_Show text alignments", "", (fun () -> ShowTextAlignments ()));
+                new MenuItem(ustr "_OnKeyDown/Up", "", (fun () -> OnKeyDownUpDemo ()))|]);
+                new MenuBarItem(ustr "_Test Menu and SubMenus",
+                    [|new MenuItem(ustr "SubMenu1Item_1", new MenuBarItem([|new MenuItem(ustr "SubMenu2Item_1",
+                    new MenuBarItem([|new MenuItem(ustr "SubMenu3Item_1", new MenuBarItem([|(menuItems.[2])|]))|]))|]))|]);
+                new MenuBarItem(ustr "_About...", "Demonstrates top-level menu item",
+                    (fun () -> MessageBox.ErrorQuery (50, 7, "About Demo", "This is a demo app for gui.cs", "Ok") |> ignore))|])
+        menuKeysStyle <- new CheckBox(3, 25, ustr "UseKeysUpDownAsKeysLeftRight", true)
+        menuKeysStyle.Toggled.Add(MenuKeysStyle_Toggled)
+        menuAutoMouseNav <- new CheckBox(40, 25, ustr "UseMenuAutoNavigation", true)
+        menuAutoMouseNav.Toggled.Add(MenuAutoMouseNav_Toggled)
+        ShowEntries (win)
+        let mutable (count : int) = 0
+        ml <- new Label(new Rect(3, 17, 47, 1), ustr "Mouse: ")
+        Application.RootMouseEvent <- Action<MouseEvent> (
+                fun (me : MouseEvent) ->
+                    ml.TextColor <- Colors.TopLevel.Normal
+                    ml.Text <- ustr (
+                         (((sprintf "Mouse: (%O,%O) - %O %O" me.X) me.Y) me.Flags) (
+                            count <- count + 1
+                            count))
+                            )
+        let mutable test = new Label(3, 18, ustr "Se iniciará el análisis")
+        win.Add (test)
+        win.Add (ml)
+        let mutable drag = new Label (ustr "Drag: ", X = Pos.At(70), Y = Pos.At(24))
+        let mutable dragText = new TextField (ustr "",
+            X = Pos.Right (drag),
+            Y = Pos.Top (drag),
+            Width = Dim.op_Implicit(40)
+            )
+        let mutable statusBar = new StatusBar ([|
+            new StatusItem(Key.F1, ustr "~F1~ Help", Action(Help));
+            new StatusItem(Key.F2, ustr "~F2~ Load", null);
+            new StatusItem(Key.F3, ustr "~F3~ Save", null);
+            new StatusItem(Key.ControlX, ustr "~^X~ Quit", fun () -> if (Quit ()) then top.Running <- false)
+            |],
+            Parent = null
+            )
+        win.Add (drag, dragText)
+        let mutable bottom = new Label(ustr "This should go on the bottom of the same top-level!")
+        win.Add (bottom)
+        let mutable bottom2 = new Label(ustr "This should go on the bottom of another top-level!")
+        top.Add (bottom2)
+        Application.OnLoad <- Action (
+            fun () ->
+                bottom.X <- win.X
+                bottom.Y <- Pos.Bottom (win) - Pos.Top (win) - Pos.At(margin)
+                bottom2.X <- Pos.Left (win)
+                bottom2.Y <- Pos.Bottom (win)
+                )
+        top.Add (win)
+        top.Add (menu, statusBar)
+        Application.Run ()
 
-let ustr (x:string) = ustring.Make(x)
-
-let stop = Action Application.RequestStop
-
-let newFile() =
-    let ok = Button (ustr "Ok", true, Clicked=stop)
-    let cancel = Button (ustr "Cancel", Clicked=stop)
-    let d = Dialog (ustr ("New File"), 50, 20, ok, cancel)
-    Application.Run (d)
-
-let quit() =
-    if MessageBox.Query (50, 7, "Quit demo", "Are you sure you want to quit the demo?", "Yes", "No") = 0 then
-       Application.Top.Running <- false
-
-let buildMenu() =
-    MenuBar ([|
-        MenuBarItem (ustr ("File"), 
-            [| MenuItem(ustr("_New"), "Creates a new file", System.Action newFile);
-               MenuItem(ustr("_Quit"), null, System.Action quit)
-             |])|])
-
-[<EntryPoint>]
-let main argv =
-    Application.Init ()
-    let top = Application.Top
-    let win = Window (ustr "Hello", X=Pos.op_Implicit(0), Y=Pos.op_Implicit(1), Width=Dim.Fill(), Height=Dim.Fill())
-    top.Add (buildMenu())
-    top.Add (win)
-    Application.Run ()
-    0 // return an integer exit code
+module Demo__run =
+    [<EntryPoint>]
+    let main argv =
+        Main ()
+        0

+ 111 - 21
Terminal.Gui/Core.cs

@@ -121,6 +121,27 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		/// <summary>
+		/// Method invoked when a key is pressed.
+		/// </summary>
+		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+		/// <returns>true if the event was handled</returns>
+		public virtual bool KeyDown (KeyEvent keyEvent)
+		{
+			return false;
+		}
+
+		/// <summary>
+		/// Method invoked when a key is released.
+		/// </summary>
+		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+		/// <returns>true if the event was handled</returns>
+		public virtual bool KeyUp (KeyEvent keyEvent)
+		{
+			return false;
+		}
+
+
 		/// <summary>
 		/// Method invoked when a mouse event is generated
 		/// </summary>
@@ -1011,6 +1032,41 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		/// <summary>
+		/// Invoked when a key is pressed
+		/// </summary>
+		public Action<KeyEvent> OnKeyDown;
+
+		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+		public override bool KeyDown (KeyEvent keyEvent)
+		{
+			OnKeyDown?.Invoke (keyEvent);
+			if (subviews == null || subviews.Count == 0)
+				return false;
+			foreach (var view in subviews)
+				if (view.KeyDown (keyEvent))
+					return true;
+
+			return false;
+		}
+
+		/// <summary>
+		/// Invoked when a key is released
+		/// </summary>
+		public Action<KeyEvent> OnKeyUp;
+
+		/// <param name="keyEvent">Contains the details about the key that produced the event.</param>
+		public override bool KeyUp (KeyEvent keyEvent)
+		{
+			OnKeyUp?.Invoke (keyEvent);
+			if (subviews == null || subviews.Count == 0)
+				return false;
+			foreach (var view in subviews)
+				if (view.KeyUp (keyEvent))
+					return true;
+
+			return false;
+		}
 		/// <summary>
 		/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing.
 		/// </summary>
@@ -1192,12 +1248,12 @@ namespace Terminal.Gui {
 		}
 
 		// https://en.wikipedia.org/wiki/Topological_sorting
-		static List<View> TopologicalSort (HashSet<View> nodes, HashSet<(View, View)> edges)
+		List<View> TopologicalSort (HashSet<View> nodes, HashSet<(View From, View To)> edges)
 		{
 			var result = new List<View> ();
 
 			// Set of all nodes with no incoming edges
-			var S = new HashSet<View> (nodes.Where (n => edges.All (e => e.Item2.Equals (n) == false)));
+			var S = new HashSet<View> (nodes.Where (n => edges.All (e => e.To.Equals (n) == false)));
 
 			while (S.Any ()) {
 				//  remove a node n from S
@@ -1205,17 +1261,18 @@ namespace Terminal.Gui {
 				S.Remove (n);
 
 				// add n to tail of L
-				result.Add (n);
+				if (n != this?.SuperView)
+					result.Add (n);
 
 				// for each node m with an edge e from n to m do
-				foreach (var e in edges.Where (e => e.Item1.Equals (n)).ToList ()) {
-					var m = e.Item2;
+				foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
+					var m = e.To;
 
 					// remove edge e from the graph
 					edges.Remove (e);
 
 					// if m has no other incoming edges then
-					if (edges.All (me => me.Item2.Equals (m) == false)) {
+					if (edges.All (me => me.To.Equals (m) == false) && m != this?.SuperView) {
 						// insert m into S
 						S.Add (m);
 					}
@@ -1249,19 +1306,18 @@ namespace Terminal.Gui {
 			foreach (var v in InternalSubviews) {
 				nodes.Add (v);
 				if (v.LayoutStyle == LayoutStyle.Computed) {
-					if (v.X is Pos.PosView)
-						edges.Add ((v, (v.X as Pos.PosView).Target));
-					if (v.Y is Pos.PosView)
-						edges.Add ((v, (v.Y as Pos.PosView).Target));
-					if (v.Width is Dim.DimView)
-						edges.Add ((v, (v.Width as Dim.DimView).Target));
-					if (v.Height is Dim.DimView)
-						edges.Add ((v, (v.Height as Dim.DimView).Target));
+					if (v.X is Pos.PosView vX)
+						edges.Add ((vX.Target, v));
+					if (v.Y is Pos.PosView vY)
+						edges.Add ((vY.Target, v));
+					if (v.Width is Dim.DimView vWidth)
+						edges.Add ((vWidth.Target, v));
+					if (v.Height is Dim.DimView vHeight)
+						edges.Add ((vHeight.Target, v));
 				}
 			}
 
 			var ordered = TopologicalSort (nodes, edges);
-			ordered.Reverse ();
 			if (ordered == null)
 				throw new Exception ("There is a recursive cycle in the relative Pos/Dim in the views of " + this);
 
@@ -1269,11 +1325,15 @@ namespace Terminal.Gui {
 				if (v.LayoutStyle == LayoutStyle.Computed)
 					v.RelativeLayout (Frame);
 
-				if (this?.SuperView != v)
-					v.LayoutSubviews ();
+				v.LayoutSubviews ();
 				v.layoutNeeded = false;
 
 			}
+
+			if (SuperView == Application.Top && layoutNeeded && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
+				RelativeLayout (Frame);
+			}
+
 			layoutNeeded = false;
 		}
 
@@ -1482,7 +1542,7 @@ namespace Terminal.Gui {
 		{
 			if (this != Application.Top) {
 				EnsureVisibleBounds (this, Frame.X, Frame.Y, out int nx, out int ny);
-				if (nx != Frame.X || ny != Frame.Y) {
+				if ((nx != Frame.X || ny != Frame.Y) && LayoutStyle != LayoutStyle.Computed) {
 					X = nx;
 					Y = ny;
 				}
@@ -1490,7 +1550,7 @@ namespace Terminal.Gui {
 				foreach (var top in Subviews) {
 					if (top is Toplevel) {
 						EnsureVisibleBounds ((Toplevel)top, top.Frame.X, top.Frame.Y, out int nx, out int ny);
-						if (nx != top.Frame.X || ny != top.Frame.Y) {
+						if ((nx != top.Frame.X || ny != top.Frame.Y) && top.LayoutStyle != LayoutStyle.Computed) {
 							top.X = nx;
 							top.Y = ny;
 						}
@@ -1513,7 +1573,7 @@ namespace Terminal.Gui {
 				}
 				foreach (var view in Subviews) {
 					if (view.Frame.IntersectsWith (region)) {
-						//view.SetNeedsLayout ();
+						view.SetNeedsLayout ();
 						view.SetNeedsDisplay (view.Bounds);
 					}
 				}
@@ -1943,6 +2003,7 @@ namespace Terminal.Gui {
 
 		static void ProcessKeyEvent (KeyEvent ke)
 		{
+		
 			var chain = toplevels.ToList();
 			foreach (var topLevel in chain) {
 				if (topLevel.ProcessHotKey (ke))
@@ -1967,6 +2028,29 @@ namespace Terminal.Gui {
 			}
 		}
 
+		static void ProcessKeyDownEvent (KeyEvent ke)
+		{
+			var chain = toplevels.ToList ();
+			foreach (var topLevel in chain) {
+				if (topLevel.KeyDown (ke))
+					return;
+				if (topLevel.Modal)
+					break;
+			}
+		}
+
+		
+		static void ProcessKeyUpEvent (KeyEvent ke)
+		{
+			var chain = toplevels.ToList ();
+			foreach (var topLevel in chain) {
+				if (topLevel.KeyUp (ke))
+					return;
+				if (topLevel.Modal)
+					break;
+			}
+		}
+
 		static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
 		{
 			var startFrame = start.Frame;
@@ -2062,6 +2146,11 @@ namespace Terminal.Gui {
 			}
 		}
 
+		/// <summary>
+		/// Action that is invoked once at beginning.
+		/// </summary>
+		static public Action OnLoad;
+
 		/// <summary>
 		/// Building block API: Prepares the provided toplevel for execution.
 		/// </summary>
@@ -2092,10 +2181,11 @@ namespace Terminal.Gui {
 			}
 			toplevels.Push (toplevel);
 			Current = toplevel;
-			Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessMouseEvent);
+			Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent);
 			if (toplevel.LayoutStyle == LayoutStyle.Computed)
 				toplevel.RelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
 			toplevel.LayoutSubviews ();
+			OnLoad?.Invoke ();
 			toplevel.WillPresent ();
 			Redraw (toplevel);
 			toplevel.PositionCursor ();

+ 1 - 1
Terminal.Gui/Drivers/ConsoleDriver.cs

@@ -469,7 +469,7 @@ namespace Terminal.Gui {
 		/// <param name="mainLoop"></param>
 		/// <param name="keyHandler"></param>
 		/// <param name="mouseHandler"></param>
-		public abstract void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler);
+		public abstract void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler);
 
 		/// <summary>
 		/// Updates the screen to reflect all the changes that have been done to the display buffer

+ 2 - 1
Terminal.Gui/Drivers/CursesDriver.cs

@@ -203,8 +203,9 @@ namespace Terminal.Gui {
 				keyHandler (new KeyEvent ((Key)wch));
 		}
 
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
+		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
+			// Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			Curses.timeout (-1);
 
 			(mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => {

+ 2 - 1
Terminal.Gui/Drivers/NetDriver.cs

@@ -320,8 +320,9 @@ namespace Terminal.Gui {
 			return (Key)(0xffffffff);
 		}
 
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
+		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
+			// Note: Net doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called
 			(mainLoop.Driver as NetMainLoop).WindowsKeyPressed = delegate (ConsoleKeyInfo consoleKey) {
 				var map = MapKey (consoleKey);
 				if (map == (Key)0xffffffff)

+ 137 - 62
Terminal.Gui/Drivers/WindowsDriver.cs

@@ -26,6 +26,7 @@
 // SOFTWARE.
 //
 using System;
+using System.CodeDom;
 using System.Diagnostics;
 using System.Runtime.InteropServices;
 using System.Threading;
@@ -102,7 +103,7 @@ namespace Terminal.Gui {
 			}
 
 			if (ScreenBuffer != IntPtr.Zero)
-				CloseHandle(ScreenBuffer);
+				CloseHandle (ScreenBuffer);
 
 			ScreenBuffer = IntPtr.Zero;
 		}
@@ -358,8 +359,8 @@ namespace Terminal.Gui {
 		[DllImport ("kernel32.dll", SetLastError = true)]
 		static extern IntPtr GetStdHandle (int nStdHandle);
 
-		[DllImport("kernel32.dll", SetLastError = true)]
-		static extern bool CloseHandle(IntPtr handle);
+		[DllImport ("kernel32.dll", SetLastError = true)]
+		static extern bool CloseHandle (IntPtr handle);
 
 		[DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
 		public static extern bool ReadConsoleInput (
@@ -424,8 +425,8 @@ namespace Terminal.Gui {
 
 	internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver {
 		static bool sync;
-		ManualResetEventSlim eventReady = new ManualResetEventSlim(false);
-		ManualResetEventSlim waitForProbe = new ManualResetEventSlim(false);
+		ManualResetEventSlim eventReady = new ManualResetEventSlim (false);
+		ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false);
 		MainLoop mainLoop;
 		WindowsConsole.CharInfo [] OutputBuffer;
 		int cols, rows;
@@ -456,17 +457,17 @@ namespace Terminal.Gui {
 			Task.Run ((Action)WindowsInputHandler);
 		}
 
-		[StructLayout(LayoutKind.Sequential)]
+		[StructLayout (LayoutKind.Sequential)]
 		public struct ConsoleKeyInfoEx {
 			public ConsoleKeyInfo consoleKeyInfo;
 			public bool CapsLock;
 			public bool NumLock;
 
-			public ConsoleKeyInfoEx(ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
+			public ConsoleKeyInfoEx (ConsoleKeyInfo consoleKeyInfo, bool capslock, bool numlock)
 			{
-			this.consoleKeyInfo = consoleKeyInfo;
-			CapsLock = capslock;
-			NumLock = numlock;
+				this.consoleKeyInfo = consoleKeyInfo;
+				CapsLock = capslock;
+				NumLock = numlock;
 			}
 		}
 
@@ -476,8 +477,8 @@ namespace Terminal.Gui {
 		void WindowsInputHandler ()
 		{
 			while (true) {
-				waitForProbe.Wait();
-				waitForProbe.Reset();
+				waitForProbe.Wait ();
+				waitForProbe.Reset ();
 
 				uint numberEventsRead = 0;
 
@@ -487,7 +488,7 @@ namespace Terminal.Gui {
 				else
 					result = records;
 
-				eventReady.Set();
+				eventReady.Set ();
 			}
 		}
 
@@ -498,7 +499,9 @@ namespace Terminal.Gui {
 
 		void IMainLoopDriver.Wakeup ()
 		{
-			tokenSource.Cancel();
+			//tokenSource.Cancel ();
+			eventReady.Reset ();
+			eventReady.Set ();
 		}
 
 		bool IMainLoopDriver.EventsPending (bool wait)
@@ -517,31 +520,35 @@ namespace Terminal.Gui {
 				waitTimeout = 0;
 
 			result = null;
-			waitForProbe.Set();
+			waitForProbe.Set ();
 
 			try {
-				if(!tokenSource.IsCancellationRequested)
-					eventReady.Wait(waitTimeout, tokenSource.Token);
+				if (!tokenSource.IsCancellationRequested)
+					eventReady.Wait (waitTimeout, tokenSource.Token);
 			} catch (OperationCanceledException) {
 				return true;
 			} finally {
-				eventReady.Reset();
+				eventReady.Reset ();
 			}
 
 			if (!tokenSource.IsCancellationRequested)
 				return result != null;
 
-			tokenSource.Dispose();
-			tokenSource = new CancellationTokenSource();
+			tokenSource.Dispose ();
+			tokenSource = new CancellationTokenSource ();
 			return true;
 		}
 
 		Action<KeyEvent> keyHandler;
+		Action<KeyEvent> keyDownHandler;
+		Action<KeyEvent> keyUpHandler;
 		Action<MouseEvent> mouseHandler;
 
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
+		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 			this.keyHandler = keyHandler;
+			this.keyDownHandler = keyDownHandler;
+			this.keyUpHandler = keyUpHandler;
 			this.mouseHandler = mouseHandler;
 		}
 
@@ -554,16 +561,43 @@ namespace Terminal.Gui {
 			var inputEvent = result [0];
 			switch (inputEvent.EventType) {
 			case WindowsConsole.EventType.Key:
-				if (inputEvent.KeyEvent.bKeyDown == false)
-					return;
 				var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
-				if (inputEvent.KeyEvent.UnicodeChar == 0 && map == (Key)0xffffffff)
-					return;
-				keyHandler (new KeyEvent (map));
+				if (map == (Key)0xffffffff) {
+					KeyEvent key;
+					// Shift = VK_SHIFT = 0x10
+					// Ctrl = VK_CONTROL = 0x11
+					// Alt = VK_MENU = 0x12
+					switch (inputEvent.KeyEvent.wVirtualKeyCode) {
+					case 0x11:
+						key = new KeyEvent (Key.CtrlMask);
+						break;
+					case 0x12:
+						key = new KeyEvent (Key.AltMask);
+						break;
+					default:
+						key = new KeyEvent (Key.Unknown);
+						break;
+					}
+
+					if (inputEvent.KeyEvent.bKeyDown)
+						keyDownHandler (key);
+					else
+						keyUpHandler (key);
+				} else {
+					if (inputEvent.KeyEvent.bKeyDown) {
+						// Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event
+						keyDownHandler (new KeyEvent (map));
+						keyHandler (new KeyEvent (map));
+					} else {
+						keyUpHandler (new KeyEvent (map));
+					}
+				}
 				break;
 
 			case WindowsConsole.EventType.Mouse:
 				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
+				if (IsButtonReleased)
+					mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
 				break;
 
 			case WindowsConsole.EventType.WindowBufferSize:
@@ -571,15 +605,17 @@ namespace Terminal.Gui {
 				rows = inputEvent.WindowBufferSizeEvent.size.Y;
 				ResizeScreen ();
 				UpdateOffScreen ();
-				TerminalResized?.Invoke();
+				TerminalResized?.Invoke ();
 				break;
 			}
 			result = null;
 		}
 
 		WindowsConsole.ButtonState? LastMouseButtonPressed = null;
+		bool IsButtonPressed = false;
 		bool IsButtonReleased = false;
 		bool IsButtonDoubleClicked = false;
+		Point point;
 
 		MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
 		{
@@ -587,8 +623,8 @@ namespace Terminal.Gui {
 
 			if (IsButtonDoubleClicked) {
 				Task.Run (async () => {
-					await Task.Delay (300);
-					_ = new Action (() => IsButtonDoubleClicked = false);
+					await Task.Delay (100);
+					IsButtonDoubleClicked = false;
 				});
 			}
 
@@ -599,12 +635,13 @@ namespace Terminal.Gui {
 			// map to the correct clicked event.
 			if ((LastMouseButtonPressed != null || IsButtonReleased) && mouseEvent.ButtonState != 0) {
 				LastMouseButtonPressed = null;
+				IsButtonPressed = false;
 				IsButtonReleased = false;
 			}
 
 			if ((mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) ||
 				(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
-				mouseEvent.ButtonState != 0 && !IsButtonDoubleClicked)) {
+				mouseEvent.ButtonState != 0 && !IsButtonReleased && !IsButtonDoubleClicked)) {
 				switch (mouseEvent.ButtonState) {
 				case WindowsConsole.ButtonState.Button1Pressed:
 					mouseFlag = MouseFlags.Button1Pressed;
@@ -619,11 +656,39 @@ namespace Terminal.Gui {
 					break;
 				}
 
-				if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved)
+				if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) {
 					mouseFlag |= MouseFlags.ReportMousePosition;
+					point = new Point ();
+					IsButtonReleased = false;
+				} else {
+					point = new Point () {
+						X = mouseEvent.MousePosition.X,
+						Y = mouseEvent.MousePosition.Y
+					};
+				}
 				LastMouseButtonPressed = mouseEvent.ButtonState;
-			} else if (mouseEvent.EventFlags == 0 && LastMouseButtonPressed != null && !IsButtonReleased &&
-				!IsButtonDoubleClicked) {
+				IsButtonPressed = true;
+
+				if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) {
+					Task.Run (async () => {
+						while (IsButtonPressed) {
+							await Task.Delay (200);
+							var me = new MouseEvent () {
+								X = mouseEvent.MousePosition.X,
+								Y = mouseEvent.MousePosition.Y,
+								Flags = mouseFlag
+							};
+
+							if (IsButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
+								mouseHandler (me);
+								mainLoop.Driver.Wakeup ();
+							}
+						}
+					});
+				}
+
+			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
+				LastMouseButtonPressed != null && !IsButtonReleased && !IsButtonDoubleClicked) {
 				switch (LastMouseButtonPressed) {
 				case WindowsConsole.ButtonState.Button1Pressed:
 					mouseFlag = MouseFlags.Button1Released;
@@ -637,21 +702,30 @@ namespace Terminal.Gui {
 					mouseFlag = MouseFlags.Button4Released;
 					break;
 				}
+				IsButtonPressed = false;
 				IsButtonReleased = true;
 			} else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) &&
-				IsButtonReleased) {
-				switch (LastMouseButtonPressed) {
-				case WindowsConsole.ButtonState.Button1Pressed:
-					mouseFlag = MouseFlags.Button1Clicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button2Pressed:
-					mouseFlag = MouseFlags.Button2Clicked;
-					break;
-
-				case WindowsConsole.ButtonState.RightmostButtonPressed:
-					mouseFlag = MouseFlags.Button4Clicked;
-					break;
+				  IsButtonReleased) {
+				var p = new Point () {
+					X = mouseEvent.MousePosition.X,
+					Y = mouseEvent.MousePosition.Y
+				};
+				if (p == point) {
+					switch (LastMouseButtonPressed) {
+					case WindowsConsole.ButtonState.Button1Pressed:
+						mouseFlag = MouseFlags.Button1Clicked;
+						break;
+
+					case WindowsConsole.ButtonState.Button2Pressed:
+						mouseFlag = MouseFlags.Button2Clicked;
+						break;
+
+					case WindowsConsole.ButtonState.RightmostButtonPressed:
+						mouseFlag = MouseFlags.Button4Clicked;
+						break;
+					}
+				} else {
+					mouseFlag = 0;
 				}
 				LastMouseButtonPressed = null;
 				IsButtonReleased = false;
@@ -734,8 +808,8 @@ namespace Terminal.Gui {
 			bool capslock = (state & (WindowsConsole.ControlKeyState.CapslockOn)) != 0;
 			bool numlock = (state & (WindowsConsole.ControlKeyState.NumlockOn)) != 0;
 
-			var ConsoleKeyInfo = new ConsoleKeyInfo(keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
-			return new ConsoleKeyInfoEx(ConsoleKeyInfo, capslock, numlock);
+			var ConsoleKeyInfo = new ConsoleKeyInfo (keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
+			return new ConsoleKeyInfoEx (ConsoleKeyInfo, capslock, numlock);
 		}
 
 		public Key MapKey (ConsoleKeyInfoEx keyInfoEx)
@@ -837,6 +911,7 @@ namespace Terminal.Gui {
 
 				return (Key)((int)Key.F1 + delta);
 			}
+
 			return (Key)(0xffffffff);
 		}
 
@@ -871,7 +946,7 @@ namespace Terminal.Gui {
 			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
 			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
 			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
-			Colors.Menu.Disabled = MakeColor(ConsoleColor.DarkGray, ConsoleColor.Cyan);
+			Colors.Menu.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Cyan);
 
 			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
 			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
@@ -925,10 +1000,10 @@ namespace Terminal.Gui {
 			}
 
 			ccol++;
-			var runeWidth = Rune.ColumnWidth(rune);
+			var runeWidth = Rune.ColumnWidth (rune);
 			if (runeWidth > 1) {
 				for (int i = 1; i < runeWidth; i++) {
-					AddStr(" ");
+					AddStr (" ");
 				}
 			}
 			if (ccol == Cols) {
@@ -947,7 +1022,7 @@ namespace Terminal.Gui {
 		}
 
 		int currentAttribute;
-		CancellationTokenSource tokenSource = new CancellationTokenSource();
+		CancellationTokenSource tokenSource = new CancellationTokenSource ();
 
 		public override void SetAttribute (Attribute c)
 		{
@@ -995,39 +1070,39 @@ namespace Terminal.Gui {
 			if (damageRegion.Left == -1)
 				return;
 
-			var bufferCoords = new WindowsConsole.Coord (){
+			var bufferCoords = new WindowsConsole.Coord () {
 				X = (short)Clip.Width,
 				Y = (short)Clip.Height
 			};
 
-			var window = new WindowsConsole.SmallRect (){
+			var window = new WindowsConsole.SmallRect () {
 				Top = 0,
 				Left = 0,
 				Right = (short)Clip.Right,
 				Bottom = (short)Clip.Bottom
 			};
 
-			UpdateCursor();
+			UpdateCursor ();
 			winConsole.WriteToConsole (OutputBuffer, bufferCoords, damageRegion);
-//			System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n");
+			//			System.Diagnostics.Debugger.Log(0, "debug", $"Region={damageRegion.Right - damageRegion.Left},{damageRegion.Bottom - damageRegion.Top}\n");
 			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
 		}
 
-		public override void UpdateCursor()
+		public override void UpdateCursor ()
 		{
-			var position = new WindowsConsole.Coord(){
+			var position = new WindowsConsole.Coord () {
 				X = (short)ccol,
 				Y = (short)crow
 			};
-			winConsole.SetCursorPosition(position);
+			winConsole.SetCursorPosition (position);
 		}
 
 		public override void End ()
 		{
-			winConsole.Cleanup();
+			winConsole.Cleanup ();
 		}
 
-#region Unused
+		#region Unused
 		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
 		{
 		}
@@ -1055,7 +1130,7 @@ namespace Terminal.Gui {
 		public override void CookMouse ()
 		{
 		}
-#endregion
+		#endregion
 
 	}
 

+ 0 - 840
Terminal.Gui/Drivers/WindowsDriver.cs.orig

@@ -1,840 +0,0 @@
-//
-// WindowsDriver.cs: Windows specific driver 
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//   Nick Van Dyck ([email protected])
-//
-// Copyright (c) 2018
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-// 
-// The above copyright notice and this permission notice shall be included in all
-// copies or substantial portions of the Software.
-// 
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-// SOFTWARE.
-//
-using System;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Mono.Terminal;
-using NStack;
-
-namespace Terminal.Gui {
-
-	internal class WindowsConsole {
-		public const int STD_OUTPUT_HANDLE = -11;
-		public const int STD_INPUT_HANDLE = -10;
-		public const int STD_ERROR_HANDLE = -12;
-
-		internal IntPtr InputHandle, OutputHandle;
-		IntPtr ScreenBuffer;
-		uint originalConsoleMode;
-
-		public WindowsConsole ()
-		{
-			InputHandle = GetStdHandle (STD_INPUT_HANDLE);
-			OutputHandle = GetStdHandle (STD_OUTPUT_HANDLE);
-			originalConsoleMode = ConsoleMode;
-			var newConsoleMode = originalConsoleMode;
-			newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
-			newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
-			ConsoleMode = newConsoleMode;
-		}
-
-		public CharInfo[] OriginalStdOutChars;
-
-		public bool WriteToConsole (CharInfo[] charInfoBuffer, Coord coords, SmallRect window)
-		{
-			if (ScreenBuffer == IntPtr.Zero)
-			{
-				ScreenBuffer = CreateConsoleScreenBuffer (
-					DesiredAccess.GenericRead | DesiredAccess.GenericWrite,
-					ShareMode.FileShareRead | ShareMode.FileShareWrite,
-					IntPtr.Zero,
-					1,
-					IntPtr.Zero
-				);
-				if (ScreenBuffer == INVALID_HANDLE_VALUE){
-					var err = Marshal.GetLastWin32Error ();
-
-					if (err != 0)
-					throw new System.ComponentModel.Win32Exception(err);
-				}
-
-				if (!SetConsoleActiveScreenBuffer (ScreenBuffer)){
-					var err = Marshal.GetLastWin32Error();
-					throw new System.ComponentModel.Win32Exception(err);
-				}
-
-				OriginalStdOutChars = new CharInfo[Console.WindowHeight * Console.WindowWidth];
-
-				ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window);
-			}
-
-			return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = 0, Y = 0 }, ref window);
-		}
-
-		public bool SetCursorPosition(Coord position)
-		{
-			return SetConsoleCursorPosition (ScreenBuffer, position);
-		}
-
-		public void Cleanup ()
-		{
-			ContinueListeningForConsoleEvents = false;
-			if (!SetConsoleActiveScreenBuffer (OutputHandle)){
-				var err = Marshal.GetLastWin32Error ();
-				Console.WriteLine("Error: {0}", err);
-			}
-		}
-
-		private bool ContinueListeningForConsoleEvents = true;
-
-		public uint ConsoleMode {
-			get {
-				uint v;
-				GetConsoleMode (InputHandle, out v);
-				return v;
-			}
-
-			set {
-				SetConsoleMode (InputHandle, value);
-			}
-		}
-
-		[Flags]
-		public enum ConsoleModes : uint
-		{
-			EnableMouseInput = 16,
-			EnableQuickEditMode = 64,
-			EnableExtendedFlags = 128,
-		}
-
-		[StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
-		public struct KeyEventRecord {
-			[FieldOffset (0), MarshalAs (UnmanagedType.Bool)]
-			public bool bKeyDown;
-			[FieldOffset (4), MarshalAs (UnmanagedType.U2)]
-			public ushort wRepeatCount;
-			[FieldOffset (6), MarshalAs (UnmanagedType.U2)]
-			public ushort wVirtualKeyCode;
-			[FieldOffset (8), MarshalAs (UnmanagedType.U2)]
-			public ushort wVirtualScanCode;
-			[FieldOffset (10)]
-			public char UnicodeChar;
-			[FieldOffset (12), MarshalAs (UnmanagedType.U4)]
-			public ControlKeyState dwControlKeyState;
-		}
-
-		[Flags]
-		public enum ButtonState {
-			Button1Pressed = 1,
-			Button2Pressed = 4,
-			Button3Pressed = 8,
-			Button4Pressed = 16,
-			RightmostButtonPressed = 2,
-
-		}
-
-		[Flags]
-		public enum ControlKeyState {
-			RightAltPressed = 1,
-			LeftAltPressed = 2,
-			RightControlPressed = 4,
-			LeftControlPressed = 8,
-			ShiftPressed = 16,
-			NumlockOn = 32,
-			ScrolllockOn = 64,
-			CapslockOn = 128,
-			EnhancedKey = 256
-		}
-
-		[Flags]
-		public enum EventFlags {
-			MouseMoved = 1,
-			DoubleClick = 2,
-			MouseWheeled = 4,
-			MouseHorizontalWheeled = 8
-		}
-
-		[StructLayout (LayoutKind.Explicit)]
-		public struct MouseEventRecord {
-			[FieldOffset (0)]
-			public Coordinate MousePosition;
-			[FieldOffset (4)]
-			public ButtonState ButtonState;
-			[FieldOffset (8)]
-			public ControlKeyState ControlKeyState;
-			[FieldOffset (12)]
-			public EventFlags EventFlags;
-
-			public override string ToString ()
-			{
-				return $"[Mouse({MousePosition},{ButtonState},{ControlKeyState},{EventFlags}";
-			}
-		}
-
-		[StructLayout (LayoutKind.Sequential)]
-		public struct Coordinate {
-			public short X;
-			public short Y;
-
-			public Coordinate (short X, short Y)
-			{
-				this.X = X;
-				this.Y = Y;
-			}
-
-			public override string ToString () => $"({X},{Y})";
-		};
-
-		internal struct WindowBufferSizeRecord {
-			public Coordinate size;
-
-			public WindowBufferSizeRecord (short x, short y)
-			{
-				this.size = new Coordinate (x, y);
-			}
-
-			public override string ToString () => $"[WindowBufferSize{size}";
-		}
-
-		[StructLayout (LayoutKind.Sequential)]
-		public struct MenuEventRecord {
-			public uint dwCommandId;
-		}
-
-		[StructLayout (LayoutKind.Sequential)]
-		public struct FocusEventRecord {
-			public uint bSetFocus;
-		}
-
-		public enum EventType {
-			Focus = 0x10,
-			Key = 0x1,
-			Menu = 0x8,
-			Mouse = 2,
-			WindowBufferSize = 4
-		}
-
-		[StructLayout (LayoutKind.Explicit)]
-		public struct InputRecord {
-			[FieldOffset (0)]
-			public EventType EventType;
-			[FieldOffset (4)]
-			public KeyEventRecord KeyEvent;
-			[FieldOffset (4)]
-			public MouseEventRecord MouseEvent;
-			[FieldOffset (4)]
-			public WindowBufferSizeRecord WindowBufferSizeEvent;
-			[FieldOffset (4)]
-			public MenuEventRecord MenuEvent;
-			[FieldOffset (4)]
-			public FocusEventRecord FocusEvent;
-
-			public override string ToString ()
-			{
-				switch (EventType) {
-				case EventType.Focus:
-					return FocusEvent.ToString ();
-				case EventType.Key:
-					return KeyEvent.ToString ();
-				case EventType.Menu:
-					return MenuEvent.ToString ();
-				case EventType.Mouse:
-					return MouseEvent.ToString ();
-				case EventType.WindowBufferSize:
-					return WindowBufferSizeEvent.ToString ();
-				default:
-					return "Unknown event type: " + EventType;
-				}
-			}
-		};
-
-		[Flags]
-		enum ShareMode : uint
-		{
-			FileShareRead = 1,
-			FileShareWrite = 2,
-		}
-
-		[Flags]
-		enum DesiredAccess : uint
-		{
-			GenericRead = 2147483648,
-			GenericWrite = 1073741824,
-		}
-
-		[StructLayout(LayoutKind.Sequential)]
-		public struct ConsoleScreenBufferInfo
-		{
-			public Coord dwSize;
-			public Coord dwCursorPosition;
-			public ushort wAttributes;
-			public SmallRect srWindow;
-			public Coord dwMaximumWindowSize;
-		}
-
-		[StructLayout(LayoutKind.Sequential)]
-		public struct Coord
-		{
-			public short X;
-			public short Y;
-
-			public Coord(short X, short Y)
-			{
-				this.X = X;
-				this.Y = Y;
-			}
-		};
-
-		[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
-		public struct CharUnion
-		{
-			[FieldOffset(0)] public char UnicodeChar;
-			[FieldOffset(0)] public byte AsciiChar;
-		}
-
-		[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Unicode)]
-		public struct CharInfo
-		{
-			[FieldOffset(0)] public CharUnion Char;
-			[FieldOffset(2)] public ushort Attributes;
-		}
-
-		[StructLayout(LayoutKind.Sequential)]
-		public struct SmallRect
-		{
-			public short Left;
-			public short Top;
-			public short Right;
-			public short Bottom;
-		}
-
-		[DllImport ("kernel32.dll", SetLastError = true)]
-		static extern IntPtr GetStdHandle (int nStdHandle);
-
-		[DllImport ("kernel32.dll", EntryPoint = "ReadConsoleInputW", CharSet = CharSet.Unicode)]
-		public static extern bool ReadConsoleInput (
-			IntPtr hConsoleInput,
-			[Out] InputRecord [] lpBuffer,
-			uint nLength,
-			out uint lpNumberOfEventsRead);
-
-		[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
-		static extern bool ReadConsoleOutput(
-			IntPtr hConsoleOutput,
-			[Out] CharInfo[] lpBuffer,
-			Coord dwBufferSize,
-			Coord dwBufferCoord,
-			ref SmallRect lpReadRegion
-		);
-		
-		[DllImport("kernel32.dll", EntryPoint="WriteConsoleOutput", SetLastError=true, CharSet=CharSet.Unicode)]
-		static extern bool WriteConsoleOutput(
-			IntPtr hConsoleOutput,
-			CharInfo[] lpBuffer,
-			Coord dwBufferSize,
-			Coord dwBufferCoord,
-			ref SmallRect lpWriteRegion
-		);
-
-		[DllImport ("kernel32.dll")]
-		static extern bool SetConsoleCursorPosition(IntPtr hConsoleOutput, Coord dwCursorPosition);
-
-		[DllImport ("kernel32.dll")]
-		static extern bool GetConsoleMode (IntPtr hConsoleHandle, out uint lpMode);
-
-
-		[DllImport ("kernel32.dll")]
-		static extern bool SetConsoleMode (IntPtr hConsoleHandle, uint dwMode);
-
-		[DllImport("kernel32.dll", SetLastError = true)]
-		static extern IntPtr CreateConsoleScreenBuffer(
-			DesiredAccess dwDesiredAccess,
-			ShareMode dwShareMode,
-			IntPtr secutiryAttributes,
-			UInt32 flags,
-			IntPtr screenBufferData
-		);
-
-		internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr (-1);
-
-
-		[DllImport("kernel32.dll", SetLastError = true)]
-		static extern bool SetConsoleActiveScreenBuffer(IntPtr Handle);
-
-		[DllImport ("kernel32.dll", SetLastError = true)]
-		static extern bool GetNumberOfConsoleInputEvents (IntPtr handle, out uint lpcNumberOfEvents);
-		public uint InputEventCount {
-			get {
-				uint v;
-				GetNumberOfConsoleInputEvents (InputHandle, out v);
-				return v;
-			}
-		}
-	}
-
-	internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver {
-		static bool sync;
-		AutoResetEvent eventReady = new AutoResetEvent (false);
-		AutoResetEvent waitForProbe = new AutoResetEvent (false);
-		MainLoop mainLoop;
-		Action TerminalResized;
-		WindowsConsole.CharInfo[] OutputBuffer;
-		int cols, rows;
-		WindowsConsole winConsole;
-
-		public override int Cols => cols;
-		public override int Rows => rows;
-
-		public WindowsDriver ()
-		{
-			winConsole = new WindowsConsole();
-
-			cols = Console.WindowWidth;
-			rows = Console.WindowHeight - 1;
-
-			ResizeScreen ();
-			UpdateOffScreen ();
-
-			Task.Run ((Action)WindowsInputHandler);
-		}
-
-		// The records that we keep fetching
-		WindowsConsole.InputRecord [] result, records = new WindowsConsole.InputRecord [1];
-
-		void WindowsInputHandler () 
-		{
-			while (true) {
-				waitForProbe.WaitOne ();
-
-				uint numberEventsRead = 0;
-
-				WindowsConsole.ReadConsoleInput (winConsole.InputHandle, records, 1, out numberEventsRead);
-				if (numberEventsRead == 0)
-					result = null;
-				else
-					result = records;
-				
-				eventReady.Set ();
-			}
-		}
-
-		void IMainLoopDriver.Setup (MainLoop mainLoop)
-		{
-			this.mainLoop = mainLoop;
-		}
-
-		void IMainLoopDriver.Wakeup ()
-		{
-		}
-
-		bool IMainLoopDriver.EventsPending (bool wait)
-		{
-			long now = DateTime.UtcNow.Ticks;
-
-			int waitTimeout;
-			if (mainLoop.timeouts.Count > 0) {
-				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
-				if (waitTimeout < 0)
-					return true;
-			} else
-				waitTimeout = -1;
-
-			if (!wait)
-				waitTimeout = 0;
-
-			result = null;
-			waitForProbe.Set ();
-			eventReady.WaitOne (waitTimeout);
-			return result != null;
-		}
-
-		Action<KeyEvent> keyHandler;
-		Action<MouseEvent> mouseHandler;
-
-		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
-		{
-			this.keyHandler = keyHandler;
-			this.mouseHandler = mouseHandler;
-		}
-		
-
-		void IMainLoopDriver.MainIteration ()
-		{
-			if (result == null)
-				return;
-
-			var inputEvent = result [0];
-			switch (inputEvent.EventType) {
-			case WindowsConsole.EventType.Key:
-				if (inputEvent.KeyEvent.bKeyDown == false)
-					return;
-				var map = MapKey (ToConsoleKeyInfo (inputEvent.KeyEvent));
-				if (map == (Key)0xffffffff)
-					return;
-				keyHandler (new KeyEvent (map));
-				break;
-
-			case WindowsConsole.EventType.Mouse:
-				mouseHandler (ToDriverMouse (inputEvent.MouseEvent));
-				break;
-
-			case WindowsConsole.EventType.WindowBufferSize:
-				cols = inputEvent.WindowBufferSizeEvent.size.X;
-				rows = inputEvent.WindowBufferSizeEvent.size.Y - 1;
-				ResizeScreen ();
-				UpdateOffScreen ();
-				TerminalResized ();
-				break;
-			}
-			result = null;
-		}
-
-		private WindowsConsole.ButtonState? LastMouseButtonPressed = null;
-
-		private MouseEvent ToDriverMouse(WindowsConsole.MouseEventRecord mouseEvent)
-		{
-			MouseFlags mouseFlag = MouseFlags.AllEvents;
-
-			// The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button.
-			// This will tell when a mouse button is pressed. When the button is released this event will
-			// be fired with it's bit set to 0. So when the button is up ButtonState will be 0.
-			// To map to the correct driver events we save the last pressed mouse button so we can
-			// map to the correct clicked event.
-			if (LastMouseButtonPressed != null && mouseEvent.ButtonState != 0)
-			{
-				LastMouseButtonPressed = null;
-			}
-
-			if (mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null){
-				switch (mouseEvent.ButtonState){
-				case WindowsConsole.ButtonState.Button1Pressed:
-					mouseFlag = MouseFlags.Button1Pressed;
-					break;
-
-				case WindowsConsole.ButtonState.Button2Pressed:
-					mouseFlag = MouseFlags.Button2Pressed;
-					break;
-
-				case WindowsConsole.ButtonState.Button3Pressed:
-					mouseFlag = MouseFlags.Button3Pressed;
-					break;
-				}
-				LastMouseButtonPressed = mouseEvent.ButtonState;
-			} else if (mouseEvent.EventFlags == 0 && LastMouseButtonPressed != null){
-				switch (LastMouseButtonPressed){
-				case WindowsConsole.ButtonState.Button1Pressed:
-					mouseFlag = MouseFlags.Button1Clicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button2Pressed:
-					mouseFlag = MouseFlags.Button2Clicked;
-					break;
-
-				case WindowsConsole.ButtonState.Button3Pressed:
-					mouseFlag = MouseFlags.Button3Clicked;
-					break;
-				}
-				LastMouseButtonPressed = null;
-			} else if(mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved){
-				mouseFlag = MouseFlags.ReportMousePosition;
-			}
-
-			return new MouseEvent () {
-				X = mouseEvent.MousePosition.X,
-				Y = mouseEvent.MousePosition.Y,
-				Flags = mouseFlag
-			};
-		}
-
-		private ConsoleKeyInfo ToConsoleKeyInfo (WindowsConsole.KeyEventRecord keyEvent)
-		{
-			var state = keyEvent.dwControlKeyState;
-
-			bool shift = (state & WindowsConsole.ControlKeyState.ShiftPressed) != 0;
-			bool alt = (state & (WindowsConsole.ControlKeyState.LeftAltPressed | WindowsConsole.ControlKeyState.RightAltPressed)) != 0;
-			bool control = (state & (WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.RightControlPressed)) != 0;
-
-			return new ConsoleKeyInfo(keyEvent.UnicodeChar, (ConsoleKey)keyEvent.wVirtualKeyCode, shift, alt, control);
-		}
-
-		public Key MapKey (ConsoleKeyInfo keyInfo)
-		{
-			switch (keyInfo.Key) {
-			case ConsoleKey.Escape:
-				return Key.Esc;
-			case ConsoleKey.Tab:
-				return Key.Tab;
-			case ConsoleKey.Home:
-				return Key.Home;
-			case ConsoleKey.End:
-				return Key.End;
-			case ConsoleKey.LeftArrow:
-				return Key.CursorLeft;
-			case ConsoleKey.RightArrow:
-				return Key.CursorRight;
-			case ConsoleKey.UpArrow:
-				return Key.CursorUp;
-			case ConsoleKey.DownArrow:
-				return Key.CursorDown;
-			case ConsoleKey.PageUp:
-				return Key.PageUp;
-			case ConsoleKey.PageDown:
-				return Key.PageDown;
-			case ConsoleKey.Enter:
-				return Key.Enter;
-			case ConsoleKey.Spacebar:
-				return Key.Space;
-			case ConsoleKey.Backspace:
-				return Key.Backspace;
-			case ConsoleKey.Delete:
-				return Key.DeleteChar;
-
-			case ConsoleKey.Oem1:
-			case ConsoleKey.Oem2:
-			case ConsoleKey.Oem3:
-			case ConsoleKey.Oem4:
-			case ConsoleKey.Oem5:
-			case ConsoleKey.Oem6:
-			case ConsoleKey.Oem7:
-			case ConsoleKey.Oem8:
-			case ConsoleKey.Oem102:
-			case ConsoleKey.OemPeriod:
-			case ConsoleKey.OemComma:
-			case ConsoleKey.OemPlus:
-			case ConsoleKey.OemMinus:
-				return (Key)((uint)keyInfo.KeyChar);
-			}
-
-			var key = keyInfo.Key;
-			if (key >= ConsoleKey.A && key <= ConsoleKey.Z) {
-				var delta = key - ConsoleKey.A;
-				if (keyInfo.Modifiers == ConsoleModifiers.Control)
-					return (Key)((uint)Key.ControlA + delta);
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt)
-					return (Key)(((uint)Key.AltMask) | ((uint)'A' + delta));
-				if (keyInfo.Modifiers == ConsoleModifiers.Shift)
-					return (Key)((uint)'A' + delta);
-				else
-					return (Key)((uint)'a' + delta);
-			}
-			if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) {
-				var delta = key - ConsoleKey.D0;
-				if (keyInfo.Modifiers == ConsoleModifiers.Alt)
-					return (Key)(((uint)Key.AltMask) | ((uint)'0' + delta));
-				if (keyInfo.Modifiers == ConsoleModifiers.Shift)
-					return (Key)((uint)keyInfo.KeyChar);
-				return (Key)((uint)'0' + delta);
-			}
-			if (key >= ConsoleKey.F1 && key <= ConsoleKey.F10) {
-				var delta = key - ConsoleKey.F1;
-
-				return (Key)((int)Key.F1 + delta);
-			}
-			return (Key)(0xffffffff);
-		}
-
-		public override void Init (Action terminalResized)
-		{
-			TerminalResized = terminalResized;
-
-			Colors.Base = new ColorScheme ();
-			Colors.Dialog = new ColorScheme ();
-			Colors.Menu = new ColorScheme ();
-			Colors.Error = new ColorScheme ();
-
-			HLine = '\u2500';
-			VLine = '\u2502';
-			Stipple = '\u2592';
-			Diamond = '\u25c6';
-			ULCorner = '\u250C';
-			LLCorner = '\u2514';
-			URCorner = '\u2510';
-			LRCorner = '\u2518';
-			LeftTee = '\u251c';
-			RightTee = '\u2524';
-			TopTee = '\u22a4';
-			BottomTee = '\u22a5';
-
-			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue);
-			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
-			Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue);
-			Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
-
-			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
-			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
-			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan);
-			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
-
-			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan);
-			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
-			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan);
-
-			Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red);
-			Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
-			Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red);
-			Colors.Error.HotFocus = Colors.Error.HotNormal;
-			Console.Clear ();
-		}
-
-		void ResizeScreen ()
-		{
-			OutputBuffer = new WindowsConsole.CharInfo[Rows * Cols];
-			Clip = new Rect (0, 0, Cols, Rows);
-		}
-
-		void UpdateOffScreen ()
-		{
-			for (int row = 0; row < rows; row++)
-				for (int col = 0; col < cols; col++){
-					int position = row * cols + col;
-					OutputBuffer[position].Attributes = (ushort)MakeColor(ConsoleColor.White, ConsoleColor.Blue);
-					OutputBuffer[position].Char.UnicodeChar = ' ';
-				}
-		}
-
-		int ccol, crow;
-		public override void Move (int col, int row)
-		{
-			ccol = col;
-			crow = row;
-		}
-
-		public override void AddRune (Rune rune)
-		{
-			var position = crow * Cols + ccol;
-
-			if (Clip.Contains (ccol, crow)){
-				OutputBuffer[position].Attributes = (ushort)currentAttribute;
-				OutputBuffer[position].Char.UnicodeChar = (char)rune;
-			}
-
-			ccol++;
-			if (ccol == Cols) {
-				ccol = 0;
-				if (crow + 1 < Rows)
-					crow++;
-			}
-			if (sync)
-				UpdateScreen ();
-		}
-
-		public override void AddStr (ustring str)
-		{
-			foreach (var rune in str)
-				AddRune (rune);
-		}
-
-		int currentAttribute;
-		public override void SetAttribute (Attribute c)
-		{
-			currentAttribute = c.value;
-		}
-
-		private Attribute MakeColor (ConsoleColor f, ConsoleColor b)
-		{
-			// Encode the colors into the int value.
-			return new Attribute (){
-				value = ((int)f | (int)b << 4)
-			};
-		}
-
-		public override void Refresh()
-		{
-			var bufferCoords = new WindowsConsole.Coord (){
-				X = (short)Clip.Width,
-				Y = (short)Clip.Height
-			};
-
-			var window = new WindowsConsole.SmallRect (){
-				Top = 0,
-				Left = 0,
-				Right = (short)Clip.Right,
-				Bottom = (short)Clip.Bottom
-			};
-
-			UpdateCursor();
-			winConsole.WriteToConsole (OutputBuffer, bufferCoords, window);
-		}
-
-		public override void UpdateScreen ()
-		{
-			var bufferCoords = new WindowsConsole.Coord (){
-				X = (short)Clip.Width,
-				Y = (short)Clip.Height
-			};
-
-			var window = new WindowsConsole.SmallRect (){
-				Top = 0,
-				Left = 0,
-				Right = (short)Clip.Right,
-				Bottom = (short)Clip.Bottom
-			};
-
-			UpdateCursor();
-			winConsole.WriteToConsole (OutputBuffer, bufferCoords, window);
-		}
-
-		public override void UpdateCursor()
-		{
-			var position = new WindowsConsole.Coord(){
-				X = (short)ccol,
-				Y = (short)crow
-			};
-			winConsole.SetCursorPosition(position);
-		}
-		public override void End ()
-		{
-			winConsole.Cleanup();
-		}
-
-		#region Unused
-		public override void SetColors (ConsoleColor foreground, ConsoleColor background)
-		{
-		}
-
-		public override void SetColors (short foregroundColorId, short backgroundColorId)
-		{
-		}
-
-		public override void Suspend ()
-		{
-		}
-
-		public override void StartReportingMouseMoves ()
-		{
-		}
-
-		public override void StopReportingMouseMoves ()
-		{
-		}
-
-		public override void UncookMouse ()
-		{
-		}
-
-		public override void CookMouse ()
-		{
-		}
-		#endregion
-
-	}
-
-	
-}

+ 7 - 1
Terminal.Gui/Event.cs

@@ -178,6 +178,12 @@ namespace Terminal.Gui {
 		/// </summary>
 		AltMask = 0x80000000,
 
+		/// <summary>
+		///   When this value is set, the Key encodes the sequence Ctrl-KeyValue.
+		///   And the actual value must be extracted by removing the CtrlMask.
+		/// </summary>
+		CtrlMask = 0x40000000,
+
 		/// <summary>
 		/// Backspace key.
 		/// </summary>
@@ -297,7 +303,7 @@ namespace Terminal.Gui {
 		public bool IsAlt => (Key & Key.AltMask) != 0;
 
 		/// <summary>
-		/// Determines whether the value is a control key
+		/// Determines whether the value is a control key (and NOT just the ctrl key)
 		/// </summary>
 		/// <value><c>true</c> if is ctrl; otherwise, <c>false</c>.</value>
 		public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);

+ 17 - 18
Terminal.Gui/MonoCurses/mainloop.cs

@@ -13,10 +13,10 @@
 // distribute, sublicense, and/or sell copies of the Software, and to
 // permit persons to whom the Software is furnished to do so, subject to
 // the following conditions:
-// 
+//
 // The above copyright notice and this permission notice shall be
 // included in all copies or substantial portions of the Software.
-// 
+//
 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -138,7 +138,7 @@ namespace Mono.Terminal {
 			AddWatch (wakeupPipes [0], Condition.PollIn, ml => {
 				read (wakeupPipes [0], ignore, (IntPtr)1);
 				return true;
-			});			
+			});
 		}
 
 		/// <summary>
@@ -192,7 +192,7 @@ namespace Mono.Terminal {
 			}
 		}
 
-		bool IMainLoopDriver.EventsPending (bool wait) 
+		bool IMainLoopDriver.EventsPending (bool wait)
 		{
 			long now = DateTime.UtcNow.Ticks;
 
@@ -214,10 +214,10 @@ namespace Mono.Terminal {
 			int ic;
 			lock (mainLoop.idleHandlers)
 				ic = mainLoop.idleHandlers.Count;
-			return n > 0 || mainLoop.timeouts.Count > 0 && ((mainLoop.timeouts.Keys [0] - DateTime.UtcNow.Ticks) < 0) || ic > 0;			
+			return n > 0 || mainLoop.timeouts.Count > 0 && ((mainLoop.timeouts.Keys [0] - DateTime.UtcNow.Ticks) < 0) || ic > 0;
 		}
 
-		void IMainLoopDriver.MainIteration () 
+		void IMainLoopDriver.MainIteration ()
 		{
 			if (pollmap != null) {
 				foreach (var p in pollmap) {
@@ -231,7 +231,7 @@ namespace Mono.Terminal {
 					if (!watch.Callback (this.mainLoop))
 						descriptorWatchers.Remove (p.fd);
 				}
-			}			
+			}
 		}
 	}
 
@@ -247,7 +247,7 @@ namespace Mono.Terminal {
 		public Action<ConsoleKeyInfo> WindowsKeyPressed;
 		MainLoop mainLoop;
 
-		public NetMainLoop () 
+		public NetMainLoop ()
 		{
 		}
 
@@ -258,16 +258,16 @@ namespace Mono.Terminal {
 				windowsKeyResult = Console.ReadKey (true);
 				keyReady.Set ();
 			}
-		}		
+		}
 
 		void IMainLoopDriver.Setup (MainLoop mainLoop)
 		{
 			this.mainLoop = mainLoop;
 			Thread readThread = new Thread (WindowsKeyReader);
-			readThread.Start ();			
+			readThread.Start ();
 		}
 
-		void IMainLoopDriver.Wakeup () 
+		void IMainLoopDriver.Wakeup ()
 		{
 		}
 
@@ -298,7 +298,7 @@ namespace Mono.Terminal {
 				if (WindowsKeyPressed!= null)
 					WindowsKeyPressed (windowsKeyResult.Value);
 				windowsKeyResult = null;
-			}			
+			}
 		}
 	}
 
@@ -346,7 +346,6 @@ namespace Mono.Terminal {
 				action ();
 				return false;
 			});
-			driver.Wakeup ();
 		}
 
 		/// <summary>
@@ -373,7 +372,7 @@ namespace Mono.Terminal {
 		{
 			timeouts.Add ((DateTime.UtcNow + time).Ticks, timeout);
 		}
-		
+
 		/// <summary>
 		///   Adds a timeout to the mainloop.
 		/// </summary>
@@ -440,9 +439,9 @@ namespace Mono.Terminal {
 						idleHandlers.Add (idle);
 			}
 		}
-		
+
 		bool running;
-		
+
 		/// <summary>
 		///   Stops the mainloop.
 		/// </summary>
@@ -458,7 +457,7 @@ namespace Mono.Terminal {
 		/// <remarks>
 		///   You can use this method if you want to probe if events are pending.
 		///   Typically used if you need to flush the input queue while still
-		///   running some of your own code in your main thread. 
+		///   running some of your own code in your main thread.
 		/// </remarks>
 		public bool EventsPending (bool wait = false)
 		{
@@ -486,7 +485,7 @@ namespace Mono.Terminal {
 					RunIdle();
 			}
 		}
-		
+
 		/// <summary>
 		///   Runs the mainloop.
 		/// </summary>

+ 21 - 3
Terminal.Gui/Views/FrameView.cs

@@ -44,8 +44,22 @@ namespace Terminal.Gui {
 		{
 			var cFrame = new Rect (1, 1 , frame.Width - 2, frame.Height - 2);
 			contentView = new ContentView (cFrame);
-			base.Add (contentView);
-			Title = title;
+			Initialize ();
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="T:Terminal.Gui.Gui.FrameView"/> class with
+		/// an absolute position, a title and views.
+		/// </summary>
+		/// <param name="frame">Frame.</param>
+		/// <param name="title">Title.</param>
+		/// /// <param name="views">Views.</param>
+		public FrameView (Rect frame, ustring title, View[] views) : this (frame, title)
+		{
+			foreach (var view in views) {
+				contentView.Add (view);
+			}
+			Initialize ();
 		}
 
 		/// <summary>
@@ -61,11 +75,15 @@ namespace Terminal.Gui {
 				Width = Dim.Fill (2),
 				Height = Dim.Fill (2)
 			};
+			Initialize ();
+		}
+
+		void Initialize ()
+		{
 			base.Add (contentView);
 			Title = title;
 		}
 
-
 		void DrawFrame ()
 		{
 			DrawFrame (new Rect (0, 0, Frame.Width, Frame.Height), 0, fill: true);

+ 1105 - 998
Terminal.Gui/Views/Menu.cs

@@ -1,998 +1,1105 @@
-//
-// 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;
-using System.Collections.Generic;
-
-namespace Terminal.Gui {
-
-	/// <summary>
-	/// A menu item has a title, an associated help text, and an action to execute on activation.
-	/// </summary>
-	public class MenuItem {
-
-		/// <summary>
-		/// Initializes a new <see cref="T:Terminal.Gui.MenuItem"/>.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="help">Help text to display.</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 executred.</param>
-		public MenuItem (ustring title, string help, Action action, Func<bool> canExecute = null)
-		{
-			Title = title ?? "";
-			Help = help ?? "";
-			Action = action;
-			CanExecute = canExecute;
-			bool nextIsHot = false;
-			foreach (var x in Title) {
-				if (x == '_')
-					nextIsHot = true;
-				else {
-					if (nextIsHot) {
-						HotKey = Char.ToUpper ((char)x);
-						break;
-					}
-					nextIsHot = false;
-				}
-			}
-		}
-
-		/// <summary>
-		/// Initializes a new <see cref="T:Terminal.Gui.MenuItem"/>.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="subMenu">The menu sub-menu.</param>
-		public MenuItem (ustring title, MenuBarItem subMenu) : this (title, "", null)
-		{
-			SubMenu = subMenu;
-			IsFromSubMenu = true;
-		}
-
-		//
-		//
-
-		/// <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
-		/// </summary>
-		public Rune HotKey;
-
-		/// <summary>
-		/// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
-		/// </summary>
-		public Key ShortCut;
-
-		/// <summary>
-		/// Gets or sets the title.
-		/// </summary>
-		/// <value>The title.</value>
-		public ustring Title { get; set; }
-
-		/// <summary>
-		/// Gets or sets the help text for the menu item.
-		/// </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
-		/// </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
-		/// </summary>
-		/// <value>Function to determine if action is ready to be executed.</value>
-		public Func<bool> CanExecute { get; set; }
-
-		/// <summary>
-		/// Shortcut to check if the menu item is enabled
-		/// </summary>
-		public bool IsEnabled ()
-		{
-			return CanExecute == null ? true : CanExecute ();
-		}
-
-		internal int Width => Title.Length + Help.Length + 1 + 2;
-
-		/// <summary>
-		/// Gets or sets the parent for this MenuBarItem
-		/// </summary>
-		/// <value>The parent.</value>
-		internal MenuBarItem SubMenu { get; set; }
-		internal bool IsFromSubMenu { get; set; }
-
-		/// <summary>
-		/// Merely a debugging aid to see the interaction with main
-		/// </summary>
-		public MenuItem GetMenuItem ()
-		{
-			return this;
-		}
-
-		/// <summary>
-		/// Merely a debugging aid to see the interaction with main
-		/// </summary>
-		public bool GetMenuBarItem ()
-		{
-			return IsFromSubMenu;
-		}
-	}
-
-	/// <summary>
-	/// A menu bar item contains other menu items.
-	/// </summary>
-	public class MenuBarItem {
-		/// <summary>
-		/// Initializes a new <see cref="T:Terminal.Gui.MenuBarItem"/>.
-		/// </summary>
-		/// <param name="title">Title for the menu item.</param>
-		/// <param name="children">The items in the current menu.</param>
-		public MenuBarItem (ustring title, MenuItem [] children)
-		{
-			SetTitle (title ?? "");
-			Children = children;
-		}
-
-		/// <summary>
-		/// Initializes a new <see cref="T:Terminal.Gui.MenuBarItem"/>.
-		/// </summary>
-		/// <param name="children">The items in the current menu.</param>
-		public MenuBarItem (MenuItem[] children) : this (new string (' ', GetMaxTitleLength (children)), children)
-		{
-		}
-
-		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 SetTitle (ustring title)
-		{
-			if (title == null)
-				title = "";
-			Title = title;
-			TitleLength = GetMenuBarItemLength(Title);
-		}
-
-		static int GetMenuBarItemLength(ustring title)
-		{
-			int len = 0;
-			foreach (var ch in title) {
-				if (ch == '_')
-					continue;
-				len++;
-			}
-
-			return len;
-		}
-
-		/// <summary>
-		/// Gets or sets the title to display.
-		/// </summary>
-		/// <value>The title.</value>
-		public ustring Title { get; set; }
-
-		/// <summary>
-		/// Gets or sets the children for this MenuBarItem
-		/// </summary>
-		/// <value>The children.</value>
-		public MenuItem [] Children { get; set; }
-		internal int TitleLength { get; private set; }
-	}
-
-	class Menu : View {
-		internal MenuBarItem barItems;
-		MenuBar host;
-		internal int current;
-		internal View previousSubFocused;
-
-		static Rect MakeFrame (int x, int y, MenuItem [] items)
-		{
-			int maxW = items.Max(z => z?.Width) ?? 0;
-
-			return new Rect (x, y, maxW + 2, items.Length + 2);
-		}
-
-		public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
-		{
-			this.barItems = barItems;
-			this.host = host;
-			current = -1;
-			for (int i = 0; i < barItems.Children.Length; i++) {
-				if (barItems.Children[i] != null) {
-					current = i;
-					break;
-				}
-			}
-			ColorScheme = Colors.Menu;
-			CanFocus = true;
-			WantMousePositionReports = host.WantMousePositionReports;
-		}
-
-		internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
-		{
-			if (item != null) {
-				if (index == current) return ColorScheme.Focus;
-				if (!item.IsEnabled ()) return ColorScheme.Disabled;
-			}
-			return ColorScheme.Normal;
-		}
-
-		public override void Redraw (Rect region)
-		{
-			Driver.SetAttribute (ColorScheme.Normal);
-			DrawFrame (region, padding: 0, fill: true);
-
-			for (int i = 0; i < barItems.Children.Length; i++) {
-				var item = barItems.Children [i];
-				Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal);
-				if (item == null) {
-					Move (0, i + 1);
-					Driver.AddRune (Driver.LeftTee);
-				} else
-					Move (1, i + 1);
-
-				Driver.SetAttribute (DetermineColorSchemeFor (item, i));
-				for (int p = 0; p < Frame.Width - 2; p++)
-					if (item == null)
-						Driver.AddRune (Driver.HLine);
-					else if (p == Frame.Width - 3 && barItems.Children [i].SubMenu != null)
-						Driver.AddRune ('>');
-					else
-						Driver.AddRune (' ');
-
-				if (item == null) {
-					Move (Frame.Right - 1, i + 1);
-					Driver.AddRune (Driver.RightTee);
-					continue;
-				}
-
-				Move (2, i + 1);
-				if (!item.IsEnabled ())
-					DrawHotString (item.Title, ColorScheme.Disabled, ColorScheme.Disabled);
-				else
-					DrawHotString (item.Title,
-					       i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
-					       i == current ? ColorScheme.Focus : ColorScheme.Normal);
-
-				// The help string
-				var l = item.Help.Length;
-				Move (Frame.Width - l - 2, 1 + i);
-				Driver.AddStr (item.Help);
-			}
-			PositionCursor ();
-		}
-
-		public override void PositionCursor ()
-		{
-			if (!host.isMenuClosed)
-				Move (2, 1 + current);
-			else
-				host.PositionCursor ();
-		}
-
-		void Run (Action action)
-		{
-			if (action == null)
-				return;
-
-			Application.UngrabMouse ();
-			host.CloseAllMenus ();
-			Application.Refresh ();
-
-			Application.MainLoop.AddIdle (() => {
-				action ();
-				return false;
-			});
-		}
-
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			bool disabled;
-			switch (kb.Key) {
-			case Key.CursorUp:
-				if (current == -1)
-					break;
-				do {
-					disabled = false;
-					current--;
-					if (host.UseKeysUpDownAsKeysLeftRight) {
-						if (current == -1 && barItems.Children [current + 1].IsFromSubMenu && host.selectedSub > -1) {
-							current++;
-							host.PreviousMenu (true);
-							break;
-						}
-					}
-					if (current < 0)
-						current = barItems.Children.Length - 1;
-					var item = barItems.Children [current];
-					if (item == null || !item.IsEnabled ()) disabled = true;
-				} while (barItems.Children [current] == null || disabled);
-				SetNeedsDisplay ();
-				break;
-			case Key.CursorDown:
-				do {
-					current++;
-					disabled = false;
-					if (current == barItems.Children.Length)
-						current = 0;
-					var item = barItems.Children [current];
-					if (item == null || !item.IsEnabled ()) disabled = true;
-					if (host.UseKeysUpDownAsKeysLeftRight && barItems.Children [current]?.SubMenu != null &&
-						!disabled && !host.isMenuClosed) {
-						CheckSubMenu ();
-						break;
-					}
-					if (host.isMenuClosed)
-						host.OpenMenu (host.selected);
-				} while (barItems.Children [current] == null || disabled);
-				SetNeedsDisplay ();
-				break;
-			case Key.CursorLeft:
-				host.PreviousMenu (true);
-				break;
-			case Key.CursorRight:
-				host.NextMenu (barItems.Children [current].IsFromSubMenu ? true : false);
-				break;
-			case Key.Esc:
-				Application.UngrabMouse ();
-				host.CloseAllMenus ();
-				break;
-			case Key.Enter:
-				CheckSubMenu ();
-				Run (barItems.Children [current].Action);
-				break;
-			default:
-				// TODO: rune-ify
-				if (Char.IsLetterOrDigit ((char)kb.KeyValue)) {
-					var x = Char.ToUpper ((char)kb.KeyValue);
-
-					foreach (var item in barItems.Children) {
-						if (item == null) continue;
-						if (item.IsEnabled () && item.HotKey == x) {
-							host.CloseMenu ();
-							Run (item.Action);
-							return true;
-						}
-					}
-				}
-				break;
-			}
-			return true;
-		}
-
-		public override bool MouseEvent(MouseEvent me)
-		{
-			if (!host.handled && !host.HandleGrabView (me, this)) {
-				return false;
-			}
-			host.handled = false;
-			bool disabled;
-			if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1Released) {
-				disabled = false;
-				if (me.Y < 1)
-					return true;
-				var meY = me.Y - 1;
-				if (meY >= barItems.Children.Length)
-					return true;
-				var item = barItems.Children [meY];
-				if (item == null || !item.IsEnabled ()) disabled = true;
-				if (item != null && !disabled)
-					Run (barItems.Children [meY].Action);
-				return true;
-			} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.ReportMousePosition) {
-				disabled = false;
-				if (me.Y < 1)
-					return true;
-				if (me.Y - 1 >= barItems.Children.Length)
-					return true;
-				var item = barItems.Children [me.Y - 1];
-				if (item == null || !item.IsEnabled ()) disabled = true;
-				if (item != null && !disabled)
-					current = me.Y - 1;
-				HasFocus = true;
-				SetNeedsDisplay ();
-				CheckSubMenu ();
-				return true;
-			}
-			return false;
-		}
-
-		internal void CheckSubMenu ()
-		{
-			if (barItems.Children [current] == null)
-				return;
-			var subMenu = barItems.Children [current].SubMenu;
-			if (subMenu != null) {
-				int pos = -1;
-				if (host.openSubMenu != null)
-					pos = host.openSubMenu.FindIndex (o => o?.barItems == subMenu);
-				host.Activate (host.selected, pos, subMenu);
-			} else if (host.openSubMenu != null && !barItems.Children [current].IsFromSubMenu)
-				host.CloseMenu (false, true);
-		}
-
-		int GetSubMenuIndex (MenuBarItem subMenu)
-		{
-			int pos = -1;
-			if (this != null && Subviews.Count > 0) {
-				Menu v = null;
-				foreach (var menu in Subviews) {
-					if (((Menu)menu).barItems == subMenu)
-						v = (Menu)menu;
-				}
-				if (v != null)
-					pos = Subviews.IndexOf (v);
-			}
-
-			return pos;
-		}
-	}
-
-
-
-	/// <summary>
-	/// A menu bar for your application.
-	/// </summary>
-	public class MenuBar : View {
-		/// <summary>
-		/// The menus that were defined when the menubar was created.   This can be updated if the menu is not currently visible.
-		/// </summary>
-		/// <value>The menu array.</value>
-		public MenuBarItem [] Menus { get; set; }
-		internal int selected;
-		internal int selectedSub;
-		Action action;
-
-		/// <summary>
-		/// Used for change the navigation key style.
-		/// </summary>
-		public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true;
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="T:Terminal.Gui.MenuBar"/> class with the specified set of toplevel menu items.
-		/// </summary>
-		/// <param name="menus">Individual menu items, if one of those contains a null, then a separator is drawn.</param>
-		public MenuBar (MenuBarItem [] menus) : base ()
-		{
-			X = 0;
-			Y = 0;
-			Width = Dim.Fill ();
-			Height = 1;
-			Menus = menus;
-			CanFocus = false;
-			selected = -1;
-			selectedSub = -1;
-			ColorScheme = Colors.Menu;
-			WantMousePositionReports = true;
-			isMenuClosed = true;
-		}
-
-		public override void Redraw (Rect region)
-		{
-			Move (0, 0);
-			Driver.SetAttribute (Colors.Menu.Normal);
-			for (int i = 0; i < Frame.Width; i++)
-				Driver.AddRune (' ');
-
-			Move (1, 0);
-			int pos = 1;
-
-			for (int i = 0; i < Menus.Length; i++) {
-				var menu = Menus [i];
-				Move (pos, 0);
-				Attribute hotColor, normalColor;
-				if (i == selected) {
-					hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
-					normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
-				} else {
-					hotColor = Colors.Base.Focus;
-					normalColor = Colors.Base.Focus;
-				}
-				DrawHotString (" " + menu.Title + " " + "   ", hotColor, normalColor);
-				pos += menu.TitleLength + 3;
-			}
-			PositionCursor ();
-		}
-
-		public override void PositionCursor ()
-		{
-			int pos = 0;
-			for (int i = 0; i < Menus.Length; i++) {
-				if (i == selected) {
-					pos++;
-					if (!isMenuClosed)
-						Move (pos, 0);
-					else
-						Move (pos + 1, 0);
-					return;
-				} else {
-					if (!isMenuClosed)
-						pos += Menus [i].TitleLength + 4;
-					else
-						pos += 2 + Menus [i].TitleLength + 1;
-				}
-			}
-			Move (0, 0);
-		}
-
-		void Selected (MenuItem item)
-		{
-			// TODO: Running = false;
-			action = item.Action;
-		}
-
-		public event EventHandler OnOpenMenu;
-		public event EventHandler OnCloseMenu;
-		internal Menu openMenu;
-		Menu openCurrentMenu;
-		internal List<Menu> openSubMenu;
-		View previousFocused;
-		internal bool isMenuOpening;
-		internal bool isMenuClosing;
-		internal bool isMenuClosed;
-		public bool MenuOpen;
-		View lastFocused;
-
-		/// <summary>
-		/// Get the lasted focused view before open the menu.
-		/// </summary>
-		public View LastFocused { get; private set; }
-
-		internal void OpenMenu (int index, int sIndex = -1, MenuBarItem subMenu = null)
-		{
-			isMenuOpening = true;
-			OnOpenMenu?.Invoke (this, null);
-			int pos = 0;
-			switch (subMenu) {
-			case null:
-				lastFocused = lastFocused ?? SuperView.MostFocused;
-				if (openSubMenu != null)
-					CloseMenu (false, true);
-				if (openMenu != null)
-					SuperView.Remove (openMenu);
-
-				for (int i = 0; i < index; i++)
-					pos += Menus [i].Title.Length + 2;
-				openMenu = new Menu (this, pos, 1, Menus [index]);
-				openCurrentMenu = openMenu;
-				openCurrentMenu.previousSubFocused = openMenu;
-				SuperView.Add (openMenu);
-				SuperView.SetFocus (openMenu);
-				break;
-			default:
-				if (openSubMenu == null)
-					openSubMenu = new List<Menu> ();
-				if (sIndex > -1) {
-					RemoveSubMenu (sIndex);
-				} else {
-					var last = openSubMenu.Count > 0 ? openSubMenu.Last () : openMenu;
-					openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width, last.Frame.Top + 1 + last.current, subMenu);
-					openCurrentMenu.previousSubFocused = last.previousSubFocused;
-					openSubMenu.Add (openCurrentMenu);
-					SuperView.Add (openCurrentMenu);
-				}
-				selectedSub = openSubMenu.Count - 1;
-				SuperView?.SetFocus (openCurrentMenu);
-				break;
-			}
-			isMenuOpening = false;
-			isMenuClosed = false;
-			MenuOpen = true;
-		}
-
-		// Starts the menu from a hotkey
-		void StartMenu ()
-		{
-			if (openMenu != null)
-				return;
-			selected = 0;
-			SetNeedsDisplay ();
-
-			previousFocused = SuperView.Focused;
-			OpenMenu (selected);
-			Application.GrabMouse (this);
-		}
-
-		// Activates the menu, handles either first focus, or activating an entry when it was already active
-		// For mouse events.
-		internal void Activate (int idx, int sIdx = -1, MenuBarItem subMenu = null)
-		{
-			selected = idx;
-			selectedSub = sIdx;
-			if (openMenu == null)
-				previousFocused = SuperView.Focused;
-
-			OpenMenu (idx, sIdx, subMenu);
-			SetNeedsDisplay ();
-		}
-
-		internal void CloseMenu (bool reopen = false, bool isSubMenu = false)
-		{
-			isMenuClosing = true;
-			OnCloseMenu?.Invoke (this, null);
-			switch (isSubMenu) {
-			case false:
-				if (openMenu != null)
-					SuperView.Remove (openMenu);
-				SetNeedsDisplay ();
-				if (previousFocused != null && openMenu != null && previousFocused.ToString () != openCurrentMenu.ToString ())
-					previousFocused?.SuperView?.SetFocus (previousFocused);
-				openMenu = null;
-				if (lastFocused is Menu) {
-					lastFocused = null;
-				}
-				LastFocused = lastFocused;
-				lastFocused = null;
-				if (LastFocused != null) {
-					if (!reopen)
-						selected = -1;
-					LastFocused.SuperView?.SetFocus (LastFocused);
-				} else {
-					SuperView.SetFocus (this);
-					isMenuClosed = true;
-					PositionCursor ();
-				}
-				isMenuClosed = true;
-				break;
-
-			case true:
-				selectedSub = -1;
-				SetNeedsDisplay ();
-				RemoveAllOpensSubMenus ();
-				openCurrentMenu.previousSubFocused?.SuperView?.SetFocus (openCurrentMenu.previousSubFocused);
-				openSubMenu = null;
-				break;
-			}
-			isMenuClosing = false;
-			MenuOpen = false;
-		}
-
-		void RemoveSubMenu (int index)
-		{
-			if (openSubMenu == null)
-				return;
-			for (int i = openSubMenu.Count - 1; i > index; i--) {
-				isMenuClosing = true;
-				if (openSubMenu.Count - 1 > 0)
-					SuperView.SetFocus (openSubMenu [i - 1]);
-				else
-					SuperView.SetFocus (openMenu);
-				if (openSubMenu != null) {
-					SuperView.Remove (openSubMenu [i]);
-					openSubMenu.Remove (openSubMenu [i]);
-				}
-				RemoveSubMenu (i);
-			}
-			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;
-		}
-
-		internal void RemoveAllOpensSubMenus ()
-		{
-			if (openSubMenu != null) {
-				foreach (var item in openSubMenu) {
-					SuperView.Remove (item);
-				}
-			}
-		}
-
-		internal void CloseAllMenus ()
-		{
-			if (!isMenuOpening && !isMenuClosing) {
-				if (openSubMenu != null)
-					CloseMenu (false, true);
-				CloseMenu ();
-				if (LastFocused != null && LastFocused != this)
-					selected = -1;
-			}
-			isMenuClosed = true;
-		}
-
-		View FindDeepestMenu (View view, ref int count)
-		{
-			count = count > 0 ? count : 0;
-			foreach (var menu in view.Subviews) {
-				if (menu is Menu) {
-					count++;
-					return FindDeepestMenu ((Menu)menu, ref count);
-				}
-			}
-			return view;
-		}
-
-		internal void PreviousMenu (bool isSubMenu = false)
-		{
-			switch (isSubMenu) {
-			case false:
-				if (selected <= 0)
-					selected = Menus.Length - 1;
-				else
-					selected--;
-
-				if (selected > -1)
-					CloseMenu (true, false);
-				OpenMenu (selected);
-				break;
-			case true:
-				if (selectedSub > -1) {
-					selectedSub--;
-					RemoveSubMenu (selectedSub);
-					SetNeedsDisplay ();
-				} else
-					PreviousMenu ();
-
-				break;
-			}
-		}
-
-		internal void NextMenu (bool isSubMenu = false)
-		{
-			switch (isSubMenu) {
-			case false:
-				if (selected == -1)
-					selected = 0;
-				else if (selected + 1 == Menus.Length)
-					selected = 0;
-				else
-					selected++;
-
-				if (selected > -1)
-					CloseMenu (true);
-				OpenMenu (selected);
-				break;
-			case true:
-				if (UseKeysUpDownAsKeysLeftRight) {
-					CloseMenu (false, true);
-					NextMenu ();
-				} else {
-					if ((selectedSub == -1 || openSubMenu == null || openSubMenu?.Count == selectedSub) && openCurrentMenu.barItems.Children [openCurrentMenu.current].SubMenu == null) {
-						if (openSubMenu != null)
-							CloseMenu (false, true);
-						NextMenu ();
-					} else if (openCurrentMenu.barItems.Children [openCurrentMenu.current].SubMenu != null ||
-						!openCurrentMenu.barItems.Children [openCurrentMenu.current].IsFromSubMenu)
-						selectedSub++;
-					else
-						return;
-					SetNeedsDisplay ();
-					openCurrentMenu.CheckSubMenu ();
-				}
-				break;
-			}
-		}
-
-                internal bool FindAndOpenMenuByHotkey(KeyEvent kb)
-                {
-                    int pos = 0;
-                    var c = ((uint)kb.Key & (uint)Key.CharMask);
-	            for (int i = 0; i < Menus.Length; i++)
-                    {
-			    // TODO: this code is duplicated, hotkey should be part of the MenuBarItem
-                            var mi = Menus[i];
-                            int p = mi.Title.IndexOf('_');
-                            if (p != -1 && p + 1 < mi.Title.Length) {
-                                    if (mi.Title[p + 1] == c) {
-						Application.GrabMouse (this);
-						selected = i;
-						OpenMenu (i);
-			                    return true;
-                                    }
-                            }
-                    }
-	            return false;
-                }
-
-	        public override bool ProcessHotKey (KeyEvent kb)
-		{
-			if (kb.Key == Key.F9) {
-				StartMenu ();
-				return true;
-			}
-
-                        if (kb.IsAlt)
-                        {
-                            if (FindAndOpenMenuByHotkey(kb)) return true;
-                        }
-			var kc = kb.KeyValue;
-
-			return base.ProcessHotKey (kb);
-		}
-
-		public override bool ProcessKey (KeyEvent kb)
-		{
-			switch (kb.Key) {
-			case Key.CursorLeft:
-				selected--;
-				if (selected < 0)
-					selected = Menus.Length - 1;
-				break;
-			case Key.CursorRight:
-				selected = (selected + 1) % Menus.Length;
-				break;
-
-			case Key.Esc:
-			case Key.ControlC:
-				//TODO: Running = false;
-				CloseMenu ();
-				break;
-
-			default:
-				var key = kb.KeyValue;
-				if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
-					char c = Char.ToUpper ((char)key);
-
-					if (Menus [selected].Children == null)
-						return false;
-
-					foreach (var mi in Menus [selected].Children) {
-						int p = mi.Title.IndexOf ('_');
-						if (p != -1 && p + 1 < mi.Title.Length) {
-							if (mi.Title [p + 1] == c) {
-								Selected (mi);
-								return true;
-							}
-						}
-					}
-				}
-
-				return false;
-			}
-			SetNeedsDisplay ();
-			return true;
-		}
-
-		public override bool MouseEvent(MouseEvent me)
-		{
-			if (!handled && !HandleGrabView (me, this)) {
-				return false;
-			}
-			handled = false;
-
-			if (me.Flags == MouseFlags.Button1Clicked ||
-				(me.Flags == MouseFlags.ReportMousePosition && selected > -1)) {
- 				int pos = 1;
-				int cx = me.X;
-				for (int i = 0; i < Menus.Length; i++) {
-					if (cx > pos && me.X < pos + 1 + Menus [i].TitleLength) {
-						if (selected == i && me.Flags == MouseFlags.Button1Clicked && !isMenuClosed) {
-							Application.UngrabMouse ();
-							CloseMenu ();
-						} else if (me.Flags == MouseFlags.Button1Clicked && isMenuClosed) {
-							Activate (i);
-						}
-						else if (selected != i && selected > -1 && me.Flags == MouseFlags.ReportMousePosition) {
-							if (!isMenuClosed) {
-								CloseMenu ();
-								Activate (i);
-							}
-						} else {
-							if (!isMenuClosed)
-								Activate (i);
-						}
-						return true;
-					}
-					pos += 2 + Menus [i].TitleLength + 1;
-				}
-			}
-			return false;
-		}
-
-		internal bool handled;
-
-		internal bool HandleGrabView (MouseEvent me, View current)
-		{
-			if (Application.mouseGrabView != null) {
-				if (me.View is MenuBar || me.View is Menu) {
-					if(me.View != current) {
-						Application.UngrabMouse ();
-						Application.GrabMouse (me.View);
-						me.View.MouseEvent (me);
-					}
-				} else if (!(me.View is MenuBar || me.View is Menu) && me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
-					Application.UngrabMouse ();
-					CloseAllMenus ();
-					handled = false;
-					return false;
-				} else {
-					handled = false;
-					return false;
-				}
-			} else if (isMenuClosed && me.Flags.HasFlag (MouseFlags.Button1Clicked)) {
-				Application.GrabMouse (current);
-			} else {
-				handled = false;
-				return false;
-			}
-			//if (me.View != this && me.Flags != MouseFlags.Button1Clicked)
-			//	return true;
-			//else if (me.View != this && me.Flags == MouseFlags.Button1Clicked) {
-			//	Application.UngrabMouse ();
-			//	host.CloseAllMenus ();
-			//	return true;
-			//}
-
-
-			//if (!(me.View is MenuBar) && !(me.View is Menu) && me.Flags != MouseFlags.Button1Clicked)
-			//	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.Button1Clicked) {
-			//		Application.UngrabMouse ();
-			//		CloseAllMenus ();
-			//	}
-			//} else if (!isMenuClosed && selected == -1 && me.Flags == MouseFlags.Button1Clicked) {
-			//	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.Button1Clicked) {
-			//		Application.UngrabMouse ();
-			//		CloseMenu ();
-			//	}
-			//} else if ((!isMenuClosed && selected > -1)) {
-			//	Application.GrabMouse (current);
-			//}
-
-			handled = true;
-
-			return true;
-		}
-	}
-
-}
+//
+// 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;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// A menu item has a title, an associated help text, and an action to execute on activation.
+	/// </summary>
+	public class MenuItem {
+
+		public MenuItem ()
+		{
+			Title = "";
+			Help = "";
+		}
+
+		/// <summary>
+		/// Initializes a new <see cref="T:Terminal.Gui.MenuItem"/>.
+		/// </summary>
+		/// <param name="title">Title for the menu item.</param>
+		/// <param name="help">Help text to display.</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 executred.</param>
+		public MenuItem (ustring title, string help, Action action, Func<bool> canExecute = null)
+		{
+			Title = title ?? "";
+			Help = help ?? "";
+			Action = action;
+			CanExecute = canExecute;
+			bool nextIsHot = false;
+			foreach (var x in Title) {
+				if (x == '_')
+					nextIsHot = true;
+				else {
+					if (nextIsHot) {
+						HotKey = Char.ToUpper ((char)x);
+						break;
+					}
+					nextIsHot = false;
+				}
+			}
+		}
+
+		/// <summary>
+		/// Initializes a new <see cref="T:Terminal.Gui.MenuItem"/>.
+		/// </summary>
+		/// <param name="title">Title for the menu item.</param>
+		/// <param name="subMenu">The menu sub-menu.</param>
+		public MenuItem (ustring title, MenuBarItem subMenu) : this (title, "", null)
+		{
+			SubMenu = subMenu;
+			IsFromSubMenu = true;
+		}
+
+		//
+		//
+
+		/// <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
+		/// </summary>
+		public Rune HotKey;
+
+		/// <summary>
+		/// This is the global setting that can be used as a global shortcut to invoke the action on the menu.
+		/// </summary>
+		public Key ShortCut;
+
+		/// <summary>
+		/// Gets or sets the title.
+		/// </summary>
+		/// <value>The title.</value>
+		public ustring Title { get; set; }
+
+		/// <summary>
+		/// Gets or sets the help text for the menu item.
+		/// </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
+		/// </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
+		/// </summary>
+		/// <value>Function to determine if action is ready to be executed.</value>
+		public Func<bool> CanExecute { get; set; }
+
+		/// <summary>
+		/// Shortcut to check if the menu item is enabled
+		/// </summary>
+		public bool IsEnabled ()
+		{
+			return CanExecute == null ? true : CanExecute ();
+		}
+
+		internal int Width => Title.Length + Help.Length + 1 + 2;
+
+		/// <summary>
+		/// Gets or sets the parent for this MenuBarItem
+		/// </summary>
+		/// <value>The parent.</value>
+		internal MenuBarItem SubMenu { get; set; }
+		internal bool IsFromSubMenu { get; set; }
+
+		/// <summary>
+		/// Merely a debugging aid to see the interaction with main
+		/// </summary>
+		public MenuItem GetMenuItem ()
+		{
+			return this;
+		}
+
+		/// <summary>
+		/// Merely a debugging aid to see the interaction with main
+		/// </summary>
+		public bool GetMenuBarItem ()
+		{
+			return IsFromSubMenu;
+		}
+	}
+
+	/// <summary>
+	/// A menu bar item contains other menu items.
+	/// </summary>
+	public class MenuBarItem : MenuItem {
+		/// <summary>
+		/// Initializes a new <see cref="T:Terminal.Gui.MenuBarItem"/> as a <see cref="T:Terminal.Gui.MenuItem"/>.
+		/// </summary>
+		/// <param name="title">Title for the menu item.</param>
+		/// <param name="help">Help text to display.</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 executred.</param>
+		public MenuBarItem (ustring title, string help, Action action, Func<bool> canExecute = null) : base (title, help, action, canExecute)
+		{
+			SetTitle (title ?? "");
+			Children = null;
+		}
+
+		/// <summary>
+		/// Initializes a new <see cref="T:Terminal.Gui.MenuBarItem"/>.
+		/// </summary>
+		/// <param name="title">Title for the menu item.</param>
+		/// <param name="children">The items in the current menu.</param>
+		public MenuBarItem (ustring title, MenuItem [] children)
+		{
+			SetTitle (title ?? "");
+			Children = children;
+		}
+
+		/// <summary>
+		/// Initializes a new <see cref="T:Terminal.Gui.MenuBarItem"/>.
+		/// </summary>
+		/// <param name="children">The items in the current menu.</param>
+		public MenuBarItem (MenuItem [] children) : this (new string (' ', GetMaxTitleLength (children)), children)
+		{
+		}
+
+		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 SetTitle (ustring title)
+		{
+			if (title == null)
+				title = "";
+			Title = title;
+			TitleLength = GetMenuBarItemLength (Title);
+		}
+
+		static int GetMenuBarItemLength (ustring title)
+		{
+			int len = 0;
+			foreach (var ch in title) {
+				if (ch == '_')
+					continue;
+				len++;
+			}
+
+			return len;
+		}
+
+		/// <summary>
+		/// Gets or sets the title to display.
+		/// </summary>
+		/// <value>The title.</value>
+		//public ustring Title { get; set; }
+
+		/// <summary>
+		/// Gets or sets the children for this MenuBarItem
+		/// </summary>
+		/// <value>The children.</value>
+		public MenuItem [] Children { get; set; }
+		internal int TitleLength { get; private set; }
+
+		internal bool IsTopLevel { get => (Children == null || Children.Length == 0); }
+
+	}
+
+	class Menu : View {
+		internal MenuBarItem barItems;
+		MenuBar host;
+		internal int current;
+		internal View previousSubFocused;
+
+		static Rect MakeFrame (int x, int y, MenuItem [] items)
+		{
+			if (items == null || items.Length == 0) {
+				return new Rect ();
+			}
+			int maxW = items.Max (z => z?.Width) ?? 0;
+
+			return new Rect (x, y, maxW + 2, items.Length + 2);
+		}
+
+		public Menu (MenuBar host, int x, int y, MenuBarItem barItems) : base (MakeFrame (x, y, barItems.Children))
+		{
+			this.barItems = barItems;
+			this.host = host;
+			if (barItems.IsTopLevel) {
+				// This is a standalone MenuItem on a MenuBar
+				ColorScheme = Colors.Menu;
+				CanFocus = true;
+			} else {
+
+				current = -1;
+				for (int i = 0; i < barItems.Children.Length; i++) {
+					if (barItems.Children [i] != null) {
+						current = i;
+						break;
+					}
+				}
+				ColorScheme = Colors.Menu;
+				CanFocus = true;
+				WantMousePositionReports = host.WantMousePositionReports;
+			}
+
+		}
+
+		internal Attribute DetermineColorSchemeFor (MenuItem item, int index)
+		{
+			if (item != null) {
+				if (index == current) return ColorScheme.Focus;
+				if (!item.IsEnabled ()) return ColorScheme.Disabled;
+			}
+			return ColorScheme.Normal;
+		}
+
+		public override void Redraw (Rect region)
+		{
+			Driver.SetAttribute (ColorScheme.Normal);
+			DrawFrame (region, padding: 0, fill: true);
+
+			for (int i = 0; i < barItems.Children.Length; i++) {
+				var item = barItems.Children [i];
+				Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal);
+				if (item == null) {
+					Move (0, i + 1);
+					Driver.AddRune (Driver.LeftTee);
+				} else
+					Move (1, i + 1);
+
+				Driver.SetAttribute (DetermineColorSchemeFor (item, i));
+				for (int p = 0; p < Frame.Width - 2; p++)
+					if (item == null)
+						Driver.AddRune (Driver.HLine);
+					else if (p == Frame.Width - 3 && barItems.Children [i].SubMenu != null)
+						Driver.AddRune ('>');
+					else
+						Driver.AddRune (' ');
+
+				if (item == null) {
+					Move (Frame.Right - 1, i + 1);
+					Driver.AddRune (Driver.RightTee);
+					continue;
+				}
+
+				Move (2, i + 1);
+				if (!item.IsEnabled ())
+					DrawHotString (item.Title, ColorScheme.Disabled, ColorScheme.Disabled);
+				else
+					DrawHotString (item.Title,
+					       i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+					       i == current ? ColorScheme.Focus : ColorScheme.Normal);
+
+				// The help string
+				var l = item.Help.Length;
+				Move (Frame.Width - l - 2, 1 + i);
+				Driver.AddStr (item.Help);
+			}
+			PositionCursor ();
+		}
+
+		public override void PositionCursor ()
+		{
+			if (host == null || !host.isMenuClosed)
+				if (barItems.IsTopLevel) {
+					host.PositionCursor ();
+				} else
+					Move (2, 1 + current);
+			else
+				host.PositionCursor ();
+		}
+
+		public void Run (Action action)
+		{
+			if (action == null)
+				return;
+
+			Application.UngrabMouse ();
+			host.CloseAllMenus ();
+			Application.Refresh ();
+
+			Application.MainLoop.AddIdle (() => {
+				action ();
+				return false;
+			});
+		}
+		public override bool ProcessKey (KeyEvent kb)
+		{
+			bool disabled;
+			switch (kb.Key) {
+			case Key.CursorUp:
+				if (barItems.IsTopLevel || current == -1)
+					break;
+				do {
+					disabled = false;
+					current--;
+					if (host.UseKeysUpDownAsKeysLeftRight) {
+						if (current == -1 && barItems.Children [current + 1].IsFromSubMenu && host.selectedSub > -1) {
+							current++;
+							host.PreviousMenu (true);
+							break;
+						}
+					}
+					if (current < 0)
+						current = barItems.Children.Length - 1;
+					var item = barItems.Children [current];
+					if (item == null || !item.IsEnabled ()) disabled = true;
+				} while (barItems.Children [current] == null || disabled);
+				SetNeedsDisplay ();
+				break;
+			case Key.CursorDown:
+				if (barItems.IsTopLevel) {
+					break;
+				}
+
+				do {
+					current++;
+					disabled = false;
+					if (current == barItems.Children.Length)
+						current = 0;
+					var item = barItems.Children [current];
+					if (item == null || !item.IsEnabled ()) disabled = true;
+					if (host.UseKeysUpDownAsKeysLeftRight && barItems.Children [current]?.SubMenu != null &&
+						!disabled && !host.isMenuClosed) {
+						CheckSubMenu ();
+						break;
+					}
+					if (host.isMenuClosed)
+						host.OpenMenu (host.selected);
+				} while (barItems.Children [current] == null || disabled);
+				SetNeedsDisplay ();
+				break;
+			case Key.CursorLeft:
+				host.PreviousMenu (true);
+				break;
+			case Key.CursorRight:
+				host.NextMenu (barItems.IsTopLevel || barItems.Children [current].IsFromSubMenu ? true : false);
+				break;
+			case Key.Esc:
+				Application.UngrabMouse ();
+				host.CloseAllMenus ();
+				break;
+			case Key.Enter:
+				if (barItems.IsTopLevel) {
+					Run (barItems.Action);
+				} else {
+					CheckSubMenu ();
+					Run (barItems.Children [current].Action);
+				}
+				break;
+			default:
+				// TODO: rune-ify
+				if (barItems.Children != null && Char.IsLetterOrDigit ((char)kb.KeyValue)) {
+					var x = Char.ToUpper ((char)kb.KeyValue);
+					foreach (var item in barItems.Children) {
+						if (item == null) continue;
+						if (item.IsEnabled () && item.HotKey == x) {
+							host.CloseMenu ();
+							Run (item.Action);
+							return true;
+						}
+					}
+				}
+				break;
+			}
+			return true;
+		}
+
+		public override bool MouseEvent (MouseEvent me)
+		{
+			if (!host.handled && !host.HandleGrabView (me, this)) {
+				return false;
+			}
+			host.handled = false;
+			bool disabled;
+			if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) {
+				disabled = false;
+				if (me.Y < 1)
+					return true;
+				var meY = me.Y - 1;
+				if (meY >= barItems.Children.Length)
+					return true;
+				var item = barItems.Children [meY];
+				if (item == null || !item.IsEnabled ()) disabled = true;
+				if (item != null && !disabled)
+					Run (barItems.Children [meY].Action);
+				return true;
+			} else if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked ||
+				me.Flags == MouseFlags.ReportMousePosition) {
+				disabled = false;
+				if (me.Y < 1)
+					return true;
+				if (me.Y - 1 >= barItems.Children.Length)
+					return true;
+				var item = barItems.Children [me.Y - 1];
+				if (item == null || !item.IsEnabled ()) disabled = true;
+				if (item != null && !disabled)
+					current = me.Y - 1;
+				HasFocus = true;
+				SetNeedsDisplay ();
+				CheckSubMenu ();
+				return true;
+			}
+			return false;
+		}
+
+		internal void CheckSubMenu ()
+		{
+			if (barItems.Children [current] == null)
+				return;
+			var subMenu = barItems.Children [current].SubMenu;
+			if (subMenu != null) {
+				int pos = -1;
+				if (host.openSubMenu != null)
+					pos = host.openSubMenu.FindIndex (o => o?.barItems == subMenu);
+				host.Activate (host.selected, pos, subMenu);
+			} else if (host.openSubMenu != null && !barItems.Children [current].IsFromSubMenu)
+				host.CloseMenu (false, true);
+		}
+
+		int GetSubMenuIndex (MenuBarItem subMenu)
+		{
+			int pos = -1;
+			if (this != null && Subviews.Count > 0) {
+				Menu v = null;
+				foreach (var menu in Subviews) {
+					if (((Menu)menu).barItems == subMenu)
+						v = (Menu)menu;
+				}
+				if (v != null)
+					pos = Subviews.IndexOf (v);
+			}
+
+			return pos;
+		}
+	}
+
+
+
+	/// <summary>
+	/// A menu bar for your application.
+	/// </summary>
+	public class MenuBar : View {
+		/// <summary>
+		/// The menus that were defined when the menubar was created.   This can be updated if the menu is not currently visible.
+		/// </summary>
+		/// <value>The menu array.</value>
+		public MenuBarItem [] Menus { get; set; }
+		internal int selected;
+		internal int selectedSub;
+
+		Action action;
+
+		/// <summary>
+		/// Used for change the navigation key style.
+		/// </summary>
+		public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true;
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="T:Terminal.Gui.MenuBar"/> class with the specified set of toplevel menu items.
+		/// </summary>
+		/// <param name="menus">Individual menu items, if one of those contains a null, then a separator is drawn.</param>
+		public MenuBar (MenuBarItem [] menus) : base ()
+		{
+			X = 0;
+			Y = 0;
+			Width = Dim.Fill ();
+			Height = 1;
+			Menus = menus;
+			//CanFocus = true;
+			selected = -1;
+			selectedSub = -1;
+			ColorScheme = Colors.Menu;
+			WantMousePositionReports = true;
+			isMenuClosed = true;
+		}
+
+		public override bool KeyDown (KeyEvent keyEvent)
+		{
+			if (keyEvent.IsAlt) {
+				openedByHotKey = false;
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// Track Alt key-up events. On Windows, when a user releases Alt (without another key), the menu gets focus but doesn't open.
+		/// We mimic that behavior here. 
+		/// </summary>
+		/// <param name="keyEvent"></param>
+		/// <returns></returns>
+		public override bool KeyUp (KeyEvent keyEvent)
+		{
+			if (keyEvent.IsAlt) {
+				// User pressed Alt - this may be a precursor to a menu accellerator (e.g. Alt-F)
+				if (openMenu == null) {
+					// There's no open menu, the first menu item should be highlight. 
+					// The right way to do this is to SetFocus(MenuBar), but for some reason 
+					// that faults.
+
+					Activate (0);
+					SetNeedsDisplay ();
+				} else {
+					// There's an open menu. If this Alt key-up is a pre-cursor to an acellerator
+					// we don't want to close the menu because it'll flash.
+					// How to deal with that?
+					if (!openedByHotKey) {
+						CloseAllMenus ();
+					}
+				}
+				return true;
+			}
+			return false;
+		}
+
+		public override void Redraw (Rect region)
+		{
+			Move (0, 0);
+			Driver.SetAttribute (Colors.Menu.Normal);
+			for (int i = 0; i < Frame.Width; i++)
+				Driver.AddRune (' ');
+
+			Move (1, 0);
+			int pos = 1;
+
+			for (int i = 0; i < Menus.Length; i++) {
+				var menu = Menus [i];
+				Move (pos, 0);
+				Attribute hotColor, normalColor;
+				if (i == selected) {
+					hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal;
+					normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal;
+				} else {
+					hotColor = ColorScheme.HotNormal;
+					normalColor = ColorScheme.Normal;
+				}
+				DrawHotString ($" {menu.Title}  ", hotColor, normalColor);
+				pos += 1 + menu.TitleLength + 2;
+			}
+			PositionCursor ();
+		}
+
+		public override void PositionCursor ()
+		{
+			int pos = 0;
+			for (int i = 0; i < Menus.Length; i++) {
+				if (i == selected) {
+					pos++;
+					if (!isMenuClosed)
+						Move (pos + 1, 0);
+					else
+						Move (pos + 1, 0);
+					return;
+				} else {
+					if (!isMenuClosed)
+						pos += 1 + Menus [i].TitleLength + 2;
+					else
+						pos += 2 + Menus [i].TitleLength + 1;
+				}
+			}
+			//Move (0, 0);
+		}
+
+		void Selected (MenuItem item)
+		{
+			// TODO: Running = false;
+			action = item.Action;
+		}
+
+		public event EventHandler OnOpenMenu;
+		public event EventHandler OnCloseMenu;
+		internal Menu openMenu;
+		Menu openCurrentMenu;
+		internal List<Menu> openSubMenu;
+		View previousFocused;
+		internal bool isMenuOpening;
+		internal bool isMenuClosing;
+		internal bool isMenuClosed;
+		public bool MenuOpen;
+		View lastFocused;
+
+		/// <summary>
+		/// Get the lasted focused view before open the menu.
+		/// </summary>
+		public View LastFocused { get; private set; }
+
+		internal void OpenMenu (int index, int sIndex = -1, MenuBarItem subMenu = null)
+		{
+			isMenuOpening = true;
+			OnOpenMenu?.Invoke (this, null);
+			int pos = 0;
+			switch (subMenu) {
+			case null:
+				lastFocused = lastFocused ?? SuperView.MostFocused;
+				if (openSubMenu != null)
+					CloseMenu (false, true);
+				if (openMenu != null)
+					SuperView.Remove (openMenu);
+
+				for (int i = 0; i < index; i++)
+					pos += Menus [i].Title.Length + 2;
+				openMenu = new Menu (this, pos, 1, Menus [index]);
+				openCurrentMenu = openMenu;
+				openCurrentMenu.previousSubFocused = openMenu;
+
+				SuperView.Add (openMenu);
+				SuperView.SetFocus (openMenu);
+				break;
+			default:
+				if (openSubMenu == null)
+					openSubMenu = new List<Menu> ();
+				if (sIndex > -1) {
+					RemoveSubMenu (sIndex);
+				} else {
+					var last = openSubMenu.Count > 0 ? openSubMenu.Last () : openMenu;
+					openCurrentMenu = new Menu (this, last.Frame.Left + last.Frame.Width, last.Frame.Top + 1 + last.current, subMenu);
+					openCurrentMenu.previousSubFocused = last.previousSubFocused;
+					openSubMenu.Add (openCurrentMenu);
+					SuperView.Add (openCurrentMenu);
+				}
+				selectedSub = openSubMenu.Count - 1;
+				SuperView?.SetFocus (openCurrentMenu);
+				break;
+			}
+			isMenuOpening = false;
+			isMenuClosed = false;
+			MenuOpen = true;
+		}
+
+		// Starts the menu from a hotkey
+		void StartMenu ()
+		{
+			if (openMenu != null)
+				return;
+			selected = 0;
+			SetNeedsDisplay ();
+
+			previousFocused = SuperView.Focused;
+			OpenMenu (selected);
+			Application.GrabMouse (this);
+		}
+
+		// Activates the menu, handles either first focus, or activating an entry when it was already active
+		// For mouse events.
+		internal void Activate (int idx, int sIdx = -1, MenuBarItem subMenu = null)
+		{
+			selected = idx;
+			selectedSub = sIdx;
+			if (openMenu == null)
+				previousFocused = SuperView.Focused;
+
+			OpenMenu (idx, sIdx, subMenu);
+			SetNeedsDisplay ();
+		}
+
+		internal void CloseMenu (bool reopen = false, bool isSubMenu = false)
+		{
+			isMenuClosing = true;
+			OnCloseMenu?.Invoke (this, null);
+			switch (isSubMenu) {
+			case false:
+				if (openMenu != null)
+					SuperView.Remove (openMenu);
+				SetNeedsDisplay ();
+				if (previousFocused != null && openMenu != null && previousFocused.ToString () != openCurrentMenu.ToString ())
+					previousFocused?.SuperView?.SetFocus (previousFocused);
+				openMenu = null;
+				if (lastFocused is Menu) {
+					lastFocused = null;
+				}
+				LastFocused = lastFocused;
+				lastFocused = null;
+				if (LastFocused != null) {
+					if (!reopen)
+						selected = -1;
+					LastFocused.SuperView?.SetFocus (LastFocused);
+				} else {
+					SuperView.SetFocus (this);
+					isMenuClosed = true;
+					PositionCursor ();
+				}
+				isMenuClosed = true;
+				break;
+
+			case true:
+				selectedSub = -1;
+				SetNeedsDisplay ();
+				RemoveAllOpensSubMenus ();
+				openCurrentMenu.previousSubFocused?.SuperView?.SetFocus (openCurrentMenu.previousSubFocused);
+				openSubMenu = null;
+				break;
+			}
+			isMenuClosing = false;
+			MenuOpen = false;
+		}
+
+		void RemoveSubMenu (int index)
+		{
+			if (openSubMenu == null)
+				return;
+			for (int i = openSubMenu.Count - 1; i > index; i--) {
+				isMenuClosing = true;
+				if (openSubMenu.Count - 1 > 0)
+					SuperView.SetFocus (openSubMenu [i - 1]);
+				else
+					SuperView.SetFocus (openMenu);
+				if (openSubMenu != null) {
+					SuperView.Remove (openSubMenu [i]);
+					openSubMenu.Remove (openSubMenu [i]);
+				}
+				RemoveSubMenu (i);
+			}
+			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;
+		}
+
+		internal void RemoveAllOpensSubMenus ()
+		{
+			if (openSubMenu != null) {
+				foreach (var item in openSubMenu) {
+					SuperView.Remove (item);
+				}
+			}
+		}
+
+		internal void CloseAllMenus ()
+		{
+			if (!isMenuOpening && !isMenuClosing) {
+				if (openSubMenu != null)
+					CloseMenu (false, true);
+				CloseMenu ();
+				if (LastFocused != null && LastFocused != this)
+					selected = -1;
+			}
+			isMenuClosed = true;
+			openedByHotKey = false;
+		}
+
+		View FindDeepestMenu (View view, ref int count)
+		{
+			count = count > 0 ? count : 0;
+			foreach (var menu in view.Subviews) {
+				if (menu is Menu) {
+					count++;
+					return FindDeepestMenu ((Menu)menu, ref count);
+				}
+			}
+			return view;
+		}
+
+		internal void PreviousMenu (bool isSubMenu = false)
+		{
+			switch (isSubMenu) {
+			case false:
+				if (selected <= 0)
+					selected = Menus.Length - 1;
+				else
+					selected--;
+
+				if (selected > -1)
+					CloseMenu (true, false);
+				OpenMenu (selected);
+				break;
+			case true:
+				if (selectedSub > -1) {
+					selectedSub--;
+					RemoveSubMenu (selectedSub);
+					SetNeedsDisplay ();
+				} else
+					PreviousMenu ();
+
+				break;
+			}
+		}
+
+		internal void NextMenu (bool isSubMenu = false)
+		{
+			switch (isSubMenu) {
+			case false:
+				if (selected == -1)
+					selected = 0;
+				else if (selected + 1 == Menus.Length)
+					selected = 0;
+				else
+					selected++;
+
+				if (selected > -1)
+					CloseMenu (true);
+				OpenMenu (selected);
+				break;
+			case true:
+				if (UseKeysUpDownAsKeysLeftRight) {
+					CloseMenu (false, true);
+					NextMenu ();
+				} else {
+					if ((selectedSub == -1 || openSubMenu == null || openSubMenu?.Count == selectedSub) && openCurrentMenu.barItems.Children [openCurrentMenu.current].SubMenu == null) {
+						if (openSubMenu != null)
+							CloseMenu (false, true);
+						NextMenu ();
+					} else if (openCurrentMenu.barItems.Children [openCurrentMenu.current].SubMenu != null ||
+						!openCurrentMenu.barItems.Children [openCurrentMenu.current].IsFromSubMenu)
+						selectedSub++;
+					else
+						return;
+					SetNeedsDisplay ();
+					openCurrentMenu.CheckSubMenu ();
+				}
+				break;
+			}
+		}
+
+		bool openedByHotKey = false;
+		internal bool FindAndOpenMenuByHotkey (KeyEvent kb)
+		{
+			int pos = 0;
+			var c = ((uint)kb.Key & (uint)Key.CharMask);
+			for (int i = 0; i < Menus.Length; i++) {
+				// TODO: this code is duplicated, hotkey should be part of the MenuBarItem
+				var mi = Menus [i];
+				int p = mi.Title.IndexOf ('_');
+				if (p != -1 && p + 1 < mi.Title.Length) {
+					if (Char.ToUpperInvariant ((char)mi.Title [p + 1]) == c) {
+						if (mi.IsTopLevel) {
+							var menu = new Menu (this, i, 0, mi);
+							menu.Run (mi.Action);
+						} else {
+							openedByHotKey = true;
+							Application.GrabMouse (this);
+							selected = i;
+							OpenMenu (i);
+						}
+						return true;
+					}
+				}
+			}
+			return false;
+		}
+
+		public override bool ProcessHotKey (KeyEvent kb)
+		{
+			if (kb.Key == Key.F9) {
+				if (isMenuClosed)
+					StartMenu ();
+				else
+					CloseAllMenus ();
+				return true;
+			}
+
+			if (kb.IsAlt) {
+				if (FindAndOpenMenuByHotkey (kb)) return true;
+			}
+			var kc = kb.KeyValue;
+
+			return base.ProcessHotKey (kb);
+		}
+
+		public override bool ProcessKey (KeyEvent kb)
+		{
+			switch (kb.Key) {
+			case Key.CursorLeft:
+				selected--;
+				if (selected < 0)
+					selected = Menus.Length - 1;
+				break;
+			case Key.CursorRight:
+				selected = (selected + 1) % Menus.Length;
+				break;
+
+			case Key.Esc:
+			case Key.ControlC:
+				//TODO: Running = false;
+				CloseMenu ();
+				break;
+
+			default:
+				var key = kb.KeyValue;
+				if ((key >= 'a' && key <= 'z') || (key >= 'A' && key <= 'Z') || (key >= '0' && key <= '9')) {
+					char c = Char.ToUpper ((char)key);
+
+					if (Menus [selected].IsTopLevel)
+						return false;
+
+					foreach (var mi in Menus [selected].Children) {
+						int p = mi.Title.IndexOf ('_');
+						if (p != -1 && p + 1 < mi.Title.Length) {
+							if (mi.Title [p + 1] == c) {
+								Selected (mi);
+								return true;
+							}
+						}
+					}
+				}
+
+				return false;
+			}
+			SetNeedsDisplay ();
+			return true;
+		}
+
+		public override bool MouseEvent (MouseEvent me)
+		{
+			if (!handled && !HandleGrabView (me, this)) {
+				return false;
+			}
+			handled = false;
+
+			if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked ||
+				(me.Flags == MouseFlags.ReportMousePosition && selected > -1)) {
+				int pos = 1;
+				int cx = me.X;
+				for (int i = 0; i < Menus.Length; i++) {
+					if (cx > pos && me.X < pos + 1 + Menus [i].TitleLength) {
+						if (selected == i && (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) &&
+							!isMenuClosed) {
+							Application.UngrabMouse ();
+							if (Menus [i].IsTopLevel) {
+								var menu = new Menu (this, i, 0, Menus [i]);
+								menu.Run (Menus [i].Action);
+							} else {
+								CloseMenu ();
+							}
+						} else if ((me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) &&
+							isMenuClosed) {
+							if (Menus [i].IsTopLevel) {
+								var menu = new Menu (this, i, 0, Menus [i]);
+								menu.Run (Menus [i].Action);
+							} else {
+								Activate (i);
+							}
+						} else if (selected != i && selected > -1 && me.Flags == MouseFlags.ReportMousePosition) {
+							if (!isMenuClosed) {
+								CloseMenu ();
+								Activate (i);
+							}
+						} else {
+							if (!isMenuClosed)
+								Activate (i);
+						}
+						return true;
+					}
+					pos += 2 + Menus [i].TitleLength + 1;
+				}
+			}
+			return false;
+		}
+
+		internal bool handled;
+
+		internal bool HandleGrabView (MouseEvent me, View current)
+		{
+			if (Application.mouseGrabView != null) {
+				if (me.View is MenuBar || me.View is Menu) {
+					if (me.View != current) {
+						Application.UngrabMouse ();
+						Application.GrabMouse (me.View);
+						me.View.MouseEvent (me);
+					}
+				} else if (!(me.View is MenuBar || me.View is Menu) && (me.Flags.HasFlag (MouseFlags.Button1Pressed) ||
+					me.Flags == MouseFlags.Button1DoubleClicked)) {
+					Application.UngrabMouse ();
+					CloseAllMenus ();
+					handled = false;
+					return false;
+				} else {
+					handled = false;
+					return false;
+				}
+			} else if (isMenuClosed && (me.Flags.HasFlag (MouseFlags.Button1Pressed) || me.Flags == MouseFlags.Button1DoubleClicked)) {
+				Application.GrabMouse (current);
+			} else {
+				handled = false;
+				return false;
+			}
+			//if (me.View != this && (me.Flags != MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked))
+			//	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 ||
+			// me.Flags != MouseFlags.Button1DoubleClicked))
+			//	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;
+
+			return true;
+		}
+	}
+
+}

+ 28 - 11
Terminal.Gui/Views/ScrollView.cs

@@ -114,21 +114,19 @@ namespace Terminal.Gui {
 					var by1 = position * bh / Size;
 					var by2 = (position + bh) * bh / Size;
 
-					
 					Move (col, 0);
 					Driver.AddRune ('^');
 					Move (col, Bounds.Height - 1);
 					Driver.AddRune ('v');
 					for (int y = 0; y < bh; y++) {
 						Move (col, y+1);
-
-						if (y < by1 || y > by2)
+						if (y < by1 - 1 || y > by2)
 							special = Driver.Stipple;
 						else {
-							if (by2 - by1 == 0)
+							if (by2 - by1 == 0 && by1 < bh - 1)
 								special = Driver.Diamond;
 							else {
-								if (y == by1)
+								if (y == by1 - 1)
 									special = Driver.TopTee;
 								else if (y == by2)
 									special = Driver.BottomTee;
@@ -192,7 +190,8 @@ namespace Terminal.Gui {
 
 		public override bool MouseEvent(MouseEvent me)
 		{
-			if (me.Flags != MouseFlags.Button1Clicked)
+			if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked &&
+				!me.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
 				return false;
 
 			int location = vertical ? me.Y : me.X;
@@ -208,11 +207,23 @@ namespace Terminal.Gui {
 				if (location == 0) {
 					if (pos > 0)
 						SetPosition (pos - 1);
-				} else if (location == barsize + 1){
+				} else if (location == barsize + 1) {
 					if (pos + 1 + barsize < Size)
 						SetPosition (pos + 1);
 				} else {
-					Console.WriteLine ("TODO at ScrollBarView");
+					var b1 = pos * barsize / Size;
+					var b2 = (pos + barsize) * barsize / Size;
+
+					if (b2 == 0 && location == 1 && pos == 0 ||
+						(b2 == barsize && location == barsize) ||
+						(location > b1 && location < b2)) {
+						return true;
+					} else if (location <= barsize) {
+						if (location > 1 && location >= b2)
+							SetPosition (Math.Min (pos + barsize, Size));
+						else if (location <= b2 && pos > 0 || pos > 0)
+							SetPosition (Math.Max (pos - barsize, 0));
+					}
 				}
 			}
 
@@ -309,7 +320,7 @@ namespace Terminal.Gui {
 			set {
 				if (value == showHorizontalScrollIndicator)
 					return;
-				
+
 				showHorizontalScrollIndicator = value;
 				SetNeedsDisplay ();
 				if (value)
@@ -338,7 +349,7 @@ namespace Terminal.Gui {
 			set {
 				if (value == showVerticalScrollIndicator)
 					return;
-				
+
 				showVerticalScrollIndicator = value;
 				SetNeedsDisplay ();
 				if (value)
@@ -431,7 +442,7 @@ namespace Terminal.Gui {
 			var nx = Math.Max (-contentSize.Width, contentOffset.X - cols);
 			if (nx == contentOffset.X)
 				return false;
-			
+
 			ContentOffset = new Point (nx, contentOffset.Y);
 			return true;
 		}
@@ -461,6 +472,12 @@ namespace Terminal.Gui {
 			case Key.CursorRight:
 				return ScrollRight (1);
 
+			case Key.Home:
+				return ScrollUp (contentSize.Height);
+
+			case Key.End:
+				return ScrollDown (contentSize.Height);
+
 			}
 			return false;
 		}

+ 79 - 30
Terminal.Gui/Views/StatusBar.cs

@@ -6,27 +6,24 @@
 //
 // TODO:
 //   Add mouse support
-//   Uses internals of Application
 using System;
 using NStack;
 
-namespace Terminal.Gui
-{
+namespace Terminal.Gui {
 	/// <summary>
 	/// A statusbar item has a title, a shortcut aka hotkey, and an action to execute on activation.
 	/// Such an item is ment to be as part of the global hotkeys of the application, which are available in the current context of the screen.
-	/// The colour of the text will be changed after each ~. Having an statusbar item with a text of `~F1~ Help` will draw *F1* as shortcut and 
+	/// The colour of the text will be changed after each ~. Having an statusbar item with a text of `~F1~ Help` will draw *F1* as shortcut and
 	/// *Help* as standard text.
 	/// </summary>
-	public class StatusItem
-	{
+	public class StatusItem {
 		/// <summary>
 		/// Initializes a new <see cref="T:Terminal.Gui.StatusItem"/>.
 		/// </summary>
 		/// <param name="shortcut">Shortcut to activate the item.</param>
 		/// <param name="title">Title for the statusbar item.</param>
 		/// <param name="action">Action to invoke when the staturbar item is activated.</param>
-		public StatusItem(Key shortcut, ustring title, Action action) 
+		public StatusItem (Key shortcut, ustring title, Action action)
 		{
 			Title = title ?? "";
 			Shortcut = shortcut;
@@ -52,39 +49,91 @@ namespace Terminal.Gui
 	};
 
 	/// <summary>
-	/// A statusbar for your application.  
+	/// A statusbar for your application.
 	/// The statusbar should be context sensitive. This means, if the main menu and an open text editor are visible, the items probably shown will
-	/// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help. 
-	/// So for each context must be a new instance of a statusbar.  
+	/// be ~F1~ Help ~F2~ Save ~F3~ Load. While a dialog to ask a file to load is executed, the remaining commands will probably be ~F1~ Help.
+	/// So for each context must be a new instance of a statusbar.
 	/// </summary>
-	public class StatusBar : View
-	{
+	public class StatusBar : View {
+// After attempting to implement this, I noticed that there are hard dependencies
+// on StatusBar and MenuBars within core. They will need to be refactored for having the
+// StatusBar work at the top
+#if SNAP_TO_TOP
+		/// <summary>
+		/// The style supported by StatusBar
+		/// </summary>
+		public enum StatusBarStyle {
+			Default = 0,
+			/// <summary>
+			/// The StatusBar will snap at the the bottom line of the Parent view.
+			/// If the console window is made larger while the app is runing, the StatusBar
+			/// will continue to snap to the bottom line of the Parent, staying visible.
+			/// On consoles that support resizing of console apps (e.g. Windows Terminal and ConEmu),
+			/// if the console window is subsequently made shorter, the status bar will remain visible
+			/// as the Parent view resizes. If Parent is null, the StatusBar will snap to the bottom line
+			/// of the console window.
+			/// This is the default.
+			/// </summary>
+			SnapToBottom = Default,
+
+			/// <summary>
+			/// The StatusBar will act identically to MenuBar, snapping to the first line of the
+			/// console window.
+			/// </summary>
+			SnapToTop = 1,
+		}
+
+		public StatusBarStyle Style { get; set; } = StatusBarStyle.Default;
+#endif
+		public View Parent { get; set; }
+
 		public StatusItem [] Items { get; set; }
 
 		/// <summary>
 		/// Initializes a new instance of the <see cref="T:Terminal.Gui.StatusBar"/> class with the specified set of statusbar items.
-		/// It will be drawn in the lowest column of the terminal.
+		/// It will be drawn in the lowest line of the terminal.
 		/// </summary>
 		/// <param name="items">A list of statusbar items.</param>
-		public StatusBar(StatusItem [] items) : base()
+		public StatusBar (StatusItem [] items) : base ()
 		{
-			X = 0;
-			Y = Application.Driver.Rows - 1; // TODO: using internals of Application
 			Width = Dim.Fill ();
 			Height = 1;
 			Items = items;
 			CanFocus = false;
 			ColorScheme = Colors.Menu;
+
+			Application.OnLoad += () => {
+				X = 0;
+				Height = 1;
+#if SNAP_TO_TOP
+				switch (Style) {
+				case StatusBarStyle.SnapToTop:
+					X = 0;
+					Y = 0;
+					break;
+				case StatusBarStyle.SnapToBottom:
+#endif
+					if (Parent == null) {
+						Y = Application.Driver.Rows - 1; // TODO: using internals of Application
+					} else {
+						Y = Pos.Bottom (Parent);
+					}
+#if SNAP_TO_TOP
+					break;
+				}
+#endif
+			};
 		}
 
-		Attribute ToggleScheme(Attribute scheme)
+		Attribute ToggleScheme (Attribute scheme)
 		{
-			var result = scheme==ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal;
-			Driver.SetAttribute(result);
+			var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal;
+			Driver.SetAttribute (result);
 			return result;
 		}
 
-		public override void Redraw(Rect region) {
+		public override void Redraw (Rect region)
+		{
 			if (Frame.Y != Driver.Rows - 1) {
 				Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height);
 				Y = Driver.Rows - 1;
@@ -98,15 +147,15 @@ namespace Terminal.Gui
 
 			Move (1, 0);
 			var scheme = ColorScheme.Normal;
-			Driver.SetAttribute(scheme);
-			for(int i=0; i<Items.Length; i++) {
-				var title = Items[i].Title;
-				for(int n=0; n<title.Length; n++) {
-					if(title[n]=='~') {
-						scheme = ToggleScheme(scheme);
+			Driver.SetAttribute (scheme);
+			for (int i = 0; i < Items.Length; i++) {
+				var title = Items [i].Title;
+				for (int n = 0; n < title.Length; n++) {
+					if (title [n] == '~') {
+						scheme = ToggleScheme (scheme);
 						continue;
 					}
-					Driver.AddRune(title[n]);
+					Driver.AddRune (title [n]);
 				}
 				Driver.AddRune (' ');
 			}
@@ -114,9 +163,9 @@ namespace Terminal.Gui
 
 		public override bool ProcessHotKey (KeyEvent kb)
 		{
-			foreach(var item in Items) {
-				if(kb.Key==item.Shortcut) {
-					if( item.Action!=null ) item.Action();
+			foreach (var item in Items) {
+				if (kb.Key == item.Shortcut) {
+					if (item.Action != null) item.Action ();
 					return true;
 				}
 			}

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

@@ -43,7 +43,7 @@ namespace Terminal.Gui {
 		/// <param name="text">Initial text contents.</param>
 		public TextField (string text) : this (ustring.Make (text))
 		{
-
+			Height = 1;
 		}
 
 		/// <summary>
@@ -85,6 +85,8 @@ namespace Terminal.Gui {
 		{
 			if (Application.mouseGrabView != null && Application.mouseGrabView == this)
 				Application.UngrabMouse ();
+			if (SelectedLength != 0 && !(Application.mouseGrabView is MenuBar))
+				ClearAllSelection ();
 		}
 
 		public override Rect Frame {
@@ -473,18 +475,17 @@ namespace Terminal.Gui {
 
 		public override bool MouseEvent (MouseEvent ev)
 		{
-			if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked) && !ev.Flags.HasFlag (MouseFlags.Button1Pressed) &&
-				!ev.Flags.HasFlag (MouseFlags.ReportMousePosition))
+			if (!ev.Flags.HasFlag (MouseFlags.Button1Pressed) && !ev.Flags.HasFlag (MouseFlags.ReportMousePosition) &&
+				!ev.Flags.HasFlag (MouseFlags.Button1Released))
 				return false;
 
-			if (ev.Flags == MouseFlags.Button1Clicked) {
+			if (ev.Flags == MouseFlags.Button1Pressed) {
 				if (!HasFocus)
 					SuperView.SetFocus (this);
-				int x = PositionCursor (ev);
+				PositionCursor (ev);
 				if (isButtonReleased)
 					ClearAllSelection ();
 				isButtonReleased = true;
-				Application.UngrabMouse ();
 			} else if (ev.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
 				int x = PositionCursor (ev);
 				isButtonReleased = false;
@@ -496,6 +497,9 @@ namespace Terminal.Gui {
 				int x = PositionCursor (ev);
 				if (SelectedLength != 0)
 					ClearAllSelection ();
+			} else if (ev.Flags == MouseFlags.Button1Released) {
+				isButtonReleased = true;
+				Application.UngrabMouse ();
 			}
 
 			SetNeedsDisplay ();

+ 59 - 59
Terminal.sln

@@ -1,59 +1,59 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 2012
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}"
-EndProject
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|x86 = Debug|x86
-		Release|x86 = Release|x86
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution
-		{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.ActiveCfg = Debug|x86
-		{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|x86
-		{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|x86
-		{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|x86
-		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.Build.0 = Debug|Any CPU
-		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.ActiveCfg = Release|Any CPU
-		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.Build.0 = Release|Any CPU
-		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.ActiveCfg = Debug|x86
-		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86
-		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86
-		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86
-	EndGlobalSection
-	GlobalSection(MonoDevelopProperties) = preSolution
-		Policies = $0
-		$0.TextStylePolicy = $1
-		$1.FileWidth = 80
-		$1.scope = text/x-csharp
-		$1.TabWidth = 8
-		$1.IndentWidth = 8
-		$0.CSharpFormattingPolicy = $2
-		$2.scope = text/x-csharp
-		$2.IndentSwitchSection = False
-		$2.NewLinesForBracesInTypes = False
-		$2.NewLinesForBracesInProperties = False
-		$2.NewLinesForBracesInAccessors = False
-		$2.NewLinesForBracesInAnonymousMethods = False
-		$2.NewLinesForBracesInControlBlocks = False
-		$2.NewLinesForBracesInAnonymousTypes = False
-		$2.NewLinesForBracesInObjectCollectionArrayInitializers = False
-		$2.NewLinesForBracesInLambdaExpressionBody = False
-		$2.NewLineForElse = False
-		$2.NewLineForCatch = False
-		$2.NewLineForFinally = False
-		$2.NewLineForMembersInObjectInit = False
-		$2.NewLineForMembersInAnonymousTypes = False
-		$2.NewLineForClausesInQuery = False
-		$2.SpacingAfterMethodDeclarationName = True
-		$2.SpaceAfterMethodCallName = True
-		$2.SpaceBeforeOpenSquareBracket = True
-		$0.DotNetNamingPolicy = $3
-		$0.StandardHeader = $4
-	EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|x86 = Debug|x86
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.ActiveCfg = Debug|x86
+		{B0A602CD-E176-449D-8663-64238D54F857}.Debug|x86.Build.0 = Debug|x86
+		{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.ActiveCfg = Release|x86
+		{B0A602CD-E176-449D-8663-64238D54F857}.Release|x86.Build.0 = Release|x86
+		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Debug|x86.Build.0 = Debug|Any CPU
+		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.ActiveCfg = Release|Any CPU
+		{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}.Release|x86.Build.0 = Release|Any CPU
+		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.ActiveCfg = Debug|x86
+		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86
+		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86
+		{1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86
+	EndGlobalSection
+	GlobalSection(MonoDevelopProperties) = preSolution
+		Policies = $0
+		$0.TextStylePolicy = $1
+		$1.FileWidth = 80
+		$1.scope = text/x-csharp
+		$1.TabWidth = 8
+		$1.IndentWidth = 8
+		$0.CSharpFormattingPolicy = $2
+		$2.scope = text/x-csharp
+		$2.IndentSwitchSection = False
+		$2.NewLinesForBracesInTypes = False
+		$2.NewLinesForBracesInProperties = False
+		$2.NewLinesForBracesInAccessors = False
+		$2.NewLinesForBracesInAnonymousMethods = False
+		$2.NewLinesForBracesInControlBlocks = False
+		$2.NewLinesForBracesInAnonymousTypes = False
+		$2.NewLinesForBracesInObjectCollectionArrayInitializers = False
+		$2.NewLinesForBracesInLambdaExpressionBody = False
+		$2.NewLineForElse = False
+		$2.NewLineForCatch = False
+		$2.NewLineForFinally = False
+		$2.NewLineForMembersInObjectInit = False
+		$2.NewLineForMembersInAnonymousTypes = False
+		$2.NewLineForClausesInQuery = False
+		$2.SpacingAfterMethodDeclarationName = True
+		$2.SpaceAfterMethodCallName = True
+		$2.SpaceBeforeOpenSquareBracket = True
+		$0.DotNetNamingPolicy = $3
+		$0.StandardHeader = $4
+	EndGlobalSection
+EndGlobal