瀏覽代碼

UI catalog (#387)

* key down/up support

* line endings?

* line endings

* KeyDown/Up support

* line endings

* line endings

* Revert "Drop NuGet restore"

This reverts commit 5c7a0d05f077755943ec66e6a82db890a24cd056.

* Revert "Revert "Drop NuGet restore""

This reverts commit 2dc5fce8654ffeb6f3e570b0bdefcc6a5b6a6d2b.

* updated demo

* defined styles

* Smarter StatusBar bottom tracking.

* Prepping for https://github.com/migueldeicaza/gui.cs/issues/376

* Oops.

* Fixed StatusBar 'snap to bottom'

* line endings

* Revert "Fixed StatusBar 'snap to bottom'"

This reverts commit 9a91c957e2ed40f5b36c301eda3d107366aebb3d.

* started UICatalog project

* Initial working POC.

* Fix newlines

* merge

* textalignment demo tweaks

* textalignment demo tweaks

* Unicode Menu Scenario

* not sure why this keeps changing

* re-added project to .sln file

* re-enabled status bar

* moved scenarios to dir

* building a dim and pos demo

* terminal.sln

* progress...barely

* fixed exit

* progress with some underlying fixes to Label

* added readme

* fixes build issue

* launch

* made default colors readable on Windows

* major UI Catalog upgrade

* added more demos and updated readme

* refactored and added more tests

* added ref to Issue #437

* added OnKeyUp support to Curses and Net drivers

* more tweaks - grab PR #438 first

* Added a OpenSelectedItem event to the ListView #429

* updates

* moved KeyUpHandler out of special ESC stuff

* more tweaks & improvements

* testing top window bug

* supported OpenSelectedItem

* lots of updates

* fixed regression, fixed #444

* better button scenario

* tweaks

* add Ready event to Toplevel

* dotfx .gitignroe

* ready for ready

* updated colors based on feedback; consolodated config code

* tweaked readme

* readme

* Added Editor demonstrating TextView

* Added Editor demonstrating TextView

* added hexeditor scenario

Co-authored-by: Miguel de Icaza <[email protected]>
Co-authored-by: BDisp <[email protected]>
Charlie Kindel 5 年之前
父節點
當前提交
3d1b841efd

+ 1 - 0
Designer/Designer.csproj

@@ -8,6 +8,7 @@
     <RootNamespace>Designer</RootNamespace>
     <AssemblyName>Designer</AssemblyName>
     <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <RuntimeIdentifier>win-x86</RuntimeIdentifier>
     <TargetFrameworkProfile />
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">

+ 1 - 0
Example/Example.csproj

@@ -8,6 +8,7 @@
     <RootNamespace>Terminal</RootNamespace>
     <AssemblyName>Terminal</AssemblyName>
     <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
     <NuGetPackageImportStamp>
     </NuGetPackageImportStamp>
     <TargetFrameworkProfile />

+ 611 - 0
Example/demo.cs.orig

@@ -0,0 +1,611 @@
+using Terminal.Gui;
+using System;
+using Mono.Terminal;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Reflection;
+using NStack;
+using System.Text;
+
+static class Demo {
+	//class Box10x : View, IScrollView {
+	class Box10x : View {
+		int w = 40;
+		int h = 50;
+
+		public bool WantCursorPosition { get; set; } = false;
+
+		public Box10x (int x, int y) : base (new Rect (x, y, 20, 10))
+		{
+		}
+
+		public Size GetContentSize ()
+		{
+			return new Size (w, h);
+		}
+
+		public void SetCursorPosition (Point pos)
+		{
+			throw new NotImplementedException ();
+		}
+
+		public override void Redraw (Rect region)
+		{
+			//Point pos = new Point (region.X, region.Y);
+			Driver.SetAttribute (ColorScheme.Focus);
+
+			for (int y = 0; y < h; y++) {
+				Move (0, y);
+				Driver.AddStr (y.ToString ());
+				for (int x = 0; x < w - y.ToString ().Length; x++) {
+					//Driver.AddRune ((Rune)('0' + (x + y) % 10));
+					if (y.ToString ().Length < w)
+						Driver.AddStr (" ");
+				}
+			}
+			//Move (pos.X, pos.Y);
+		}
+	}
+
+	class Filler : View {
+		public Filler (Rect rect) : base (rect)
+		{
+		}
+
+		public override void Redraw (Rect region)
+		{
+			Driver.SetAttribute (ColorScheme.Focus);
+			var f = Frame;
+
+			for (int y = 0; y < f.Width; y++) {
+				Move (0, y);
+				for (int x = 0; x < f.Height; x++) {
+					Rune r;
+					switch (x % 3) {
+					case 0:
+						Driver.AddRune (y.ToString ().ToCharArray (0, 1) [0]);
+						if (y > 9)
+							Driver.AddRune (y.ToString ().ToCharArray (1, 1) [0]);
+						r = '.';
+						break;
+					case 1:
+						r = 'o';
+						break;
+					default:
+						r = 'O';
+						break;
+					}
+					Driver.AddRune (r);
+				}
+			}
+		}
+	}
+
+	static void ShowTextAlignments ()
+	{
+<<<<<<< HEAD
+		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;
+		};
+=======
+		var container = new Dialog (
+			"Text Alignments", 70, 20,
+			new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
+			new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
+
+>>>>>>> cb40c5c2491a559658481d20dd4b6a3343c0183f
+
+		int i = 0;
+		string txt = "Hello world, how are you doing today?";
+		container.Add (
+<<<<<<< HEAD
+				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 () }
+=======
+				new Label (new Rect (0, 1, 50, 3), $"{i+1}-{txt}") { TextAlignment = TextAlignment.Left },
+				new Label (new Rect (0, 3, 50, 3), $"{i+2}-{txt}") { TextAlignment = TextAlignment.Right },
+				new Label (new Rect (0, 5, 50, 3), $"{i+3}-{txt}") { TextAlignment = TextAlignment.Centered },
+				new Label (new Rect (0, 7, 50, 3), $"{i+4}-{txt}") { TextAlignment = TextAlignment.Justified }
+>>>>>>> cb40c5c2491a559658481d20dd4b6a3343c0183f
+			);
+
+		Application.Run (container);
+	}
+
+	static void ShowEntries (View container)
+	{
+		var scrollView = new ScrollView (new Rect (50, 10, 20, 8)) {
+			ContentSize = new Size (20, 50),
+			//ContentOffset = new Point (0, 0),
+			ShowVerticalScrollIndicator = true,
+			ShowHorizontalScrollIndicator = true
+		};
+#if false
+		scrollView.Add (new Box10x (0, 0));
+#else
+		scrollView.Add (new Filler (new Rect (0, 0, 40, 40)));
+#endif
+
+		// This is just to debug the visuals of the scrollview when small
+		var scrollView2 = new ScrollView (new Rect (72, 10, 3, 3)) {
+			ContentSize = new Size (100, 100),
+			ShowVerticalScrollIndicator = true,
+			ShowHorizontalScrollIndicator = true
+		};
+		scrollView2.Add (new Box10x (0, 0));
+		var progress = new ProgressBar (new Rect (68, 1, 10, 1));
+		bool timer (MainLoop caller)
+		{
+			progress.Pulse ();
+			return true;
+		}
+
+		Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer);
+
+
+		// A little convoluted, this is because I am using this to test the
+		// layout based on referencing elements of another view:
+
+		var login = new Label ("Login: ") { X = 3, Y = 6 };
+		var password = new Label ("Password: ") {
+			X = Pos.Left (login),
+			Y = Pos.Bottom (login) + 1
+		};
+		var loginText = new TextField ("") {
+			X = Pos.Right (password),
+			Y = Pos.Top (login),
+			Width = 40
+		};
+
+		var passText = new TextField ("") {
+			Secret = true,
+			X = Pos.Left (loginText),
+			Y = Pos.Top (password),
+			Width = Dim.Width (loginText)
+		};
+
+		var tf = new Button (3, 19, "Ok");
+		// Add some content
+		container.Add (
+			login,
+			loginText,
+			password,
+			passText,
+			new FrameView (new Rect (3, 10, 25, 6), "Options"){
+				new CheckBox (1, 0, "Remember me"),
+				new RadioGroup (1, 2, new [] { "_Personal", "_Company" }),
+			},
+			new ListView (new Rect (59, 6, 16, 4), new string [] {
+				"First row",
+				"<>",
+				"This is a very long row that should overflow what is shown",
+				"4th",
+				"There is an empty slot on the second row",
+				"Whoa",
+				"This is so cool"
+			}),
+			scrollView,
+			scrollView2,
+			tf,
+			new Button (10, 19, "Cancel"),
+			new TimeField (3, 20, DateTime.Now),
+			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, "Press F9 (on Unix, ESC+9 is an alias) to activate the menubar"),
+			menuKeysStyle,
+			menuAutoMouseNav
+
+		);
+		container.SendSubviewToBack (tf);
+	}
+
+	public static Label ml2;
+
+	static void NewFile ()
+	{
+		var d = new Dialog (
+			"New File", 50, 20,
+			new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
+			new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
+		ml2 = new Label (1, 1, "Mouse Debug Line");
+		d.Add (ml2);
+		Application.Run (d);
+	}
+
+	//
+	// Creates a nested editor
+	static void Editor (Toplevel top)
+	{
+		var tframe = top.Frame;
+		var ntop = new Toplevel (tframe);
+		var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
+			}),
+			new MenuBarItem ("_Edit", new MenuItem [] {
+				new MenuItem ("_Copy", "", null),
+				new MenuItem ("C_ut", "", null),
+				new MenuItem ("_Paste", "", null)
+			}),
+		});
+		ntop.Add (menu);
+
+		string fname = null;
+		foreach (var s in new [] { "/etc/passwd", "c:\\windows\\win.ini" })
+			if (System.IO.File.Exists (s)) {
+				fname = s;
+				break;
+			}
+
+		var win = new Window (fname ?? "Untitled") {
+			X = 0,
+			Y = 1,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		ntop.Add (win);
+
+		var text = new TextView (new Rect (0, 0, tframe.Width - 2, tframe.Height - 3));
+
+		if (fname != null)
+			text.Text = System.IO.File.ReadAllText (fname);
+		win.Add (text);
+
+		Application.Run (ntop);
+	}
+
+	static bool Quit ()
+	{
+		var n = MessageBox.Query (50, 7, "Quit Demo", "Are you sure you want to quit this demo?", "Yes", "No");
+		return n == 0;
+	}
+
+	static void Close ()
+	{
+		MessageBox.ErrorQuery (50, 7, "Error", "There is nothing to close", "Ok");
+	}
+
+	// Watch what happens when I try to introduce a newline after the first open brace
+	// it introduces a new brace instead, and does not indent.  Then watch me fight
+	// the editor as more oddities happen.
+
+	public static void Open ()
+	{
+		var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = true };
+		Application.Run (d);
+
+		if (!d.Canceled)
+			MessageBox.Query (50, 7, "Selected File", string.Join (", ", d.FilePaths), "Ok");
+	}
+
+	public static void ShowHex (Toplevel top)
+	{
+		var tframe = top.Frame;
+		var ntop = new Toplevel (tframe);
+		var menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("_Close", "", () => {Application.RequestStop ();}),
+			}),
+		});
+		ntop.Add (menu);
+
+		var win = new Window ("/etc/passwd") {
+			X = 0,
+			Y = 1,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		ntop.Add (win);
+
+		var source = System.IO.File.OpenRead ("/etc/passwd");
+		var hex = new HexView (source) {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		win.Add (hex);
+		Application.Run (ntop);
+
+	}
+
+	public class MenuItemDetails : MenuItem {
+		ustring title;
+		string help;
+		Action action;
+
+		public MenuItemDetails (ustring title, string help, Action action) : base (title, help, action)
+		{
+			this.title = title;
+			this.help = help;
+			this.action = action;
+		}
+
+		public static MenuItemDetails Instance (MenuItem mi)
+		{
+			return (MenuItemDetails)mi.GetMenuItem ();
+		}
+	}
+
+	public delegate MenuItem MenuItemDelegate (MenuItemDetails menuItem);
+
+	public static void ShowMenuItem (MenuItem mi)
+	{
+		BindingFlags flags = BindingFlags.Public | BindingFlags.Static;
+		MethodInfo minfo = typeof (MenuItemDetails).GetMethod ("Instance", flags);
+		MenuItemDelegate mid = (MenuItemDelegate)Delegate.CreateDelegate (typeof (MenuItemDelegate), minfo);
+		MessageBox.Query (70, 7, mi.Title.ToString (),
+			$"{mi.Title.ToString ()} selected. Is from submenu: {mi.GetMenuBarItem ()}", "Ok");
+	}
+
+	static void MenuKeysStyle_Toggled (object sender, EventArgs e)
+	{
+		menu.UseKeysUpDownAsKeysLeftRight = menuKeysStyle.Checked;
+	}
+
+	static void MenuAutoMouseNav_Toggled (object sender, EventArgs e)
+	{
+		menu.WantMousePositionReports = menuAutoMouseNav.Checked;
+	}
+
+
+	static void Copy ()
+	{
+		TextField textField = menu.LastFocused as TextField;
+		if (textField != null && textField.SelectedLength != 0) {
+			textField.Copy ();
+		}
+	}
+
+	static void Cut ()
+	{
+		TextField textField = menu.LastFocused as TextField;
+		if (textField != null && textField.SelectedLength != 0) {
+			textField.Cut ();
+		}
+	}
+
+	static void Paste ()
+	{
+		TextField textField = menu.LastFocused as TextField;
+		if (textField != null) {
+			textField.Paste ();
+		}
+	}
+
+	static void Help ()
+	{
+		MessageBox.Query (50, 7, "Help", "This is a small help\nBe kind.", "Ok");
+	}
+
+	#region Selection Demo
+
+	static void ListSelectionDemo (bool multiple)
+	{
+		var d = new Dialog ("Selection Demo", 60, 20,
+			new Button ("Ok", is_default: true) { Clicked = () => { Application.RequestStop (); } },
+			new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
+
+		var animals = new List<string> () { "Alpaca", "Llama", "Lion", "Shark", "Goat" };
+		var msg = new Label ("Use space bar or control-t to toggle selection") {
+			X = 1,
+			Y = 1,
+			Width = Dim.Fill () - 1,
+			Height = 1
+		};
+
+		var list = new ListView (animals) {
+			X = 1,
+			Y = 3,
+			Width = Dim.Fill () - 4,
+			Height = Dim.Fill () - 4,
+			AllowsMarking = true,
+			AllowsMultipleSelection = multiple
+		};
+		d.Add (msg, list);
+		Application.Run (d);
+
+		var result = "";
+		for (int i = 0; i < animals.Count; i++) {
+			if (list.Source.IsMarked (i)) {
+				result += animals [i] + " ";
+			}
+		}
+		MessageBox.Query (60, 10, "Selected Animals", result == "" ? "No animals selected" : result, "Ok");
+	}
+	#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;
+	public static CheckBox menuAutoMouseNav;
+	static void Main ()
+	{
+		if (Debugger.IsAttached)
+			CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+
+		//Application.UseSystemConsole = true;
+
+		Application.Init ();
+
+		var top = Application.Top;
+
+		//Open ();
+#if true
+		int margin = 3;
+		var win = new Window ("Hello") {
+			X = 1,
+			Y = 1,
+
+			Width = Dim.Fill () - margin,
+			Height = Dim.Fill () - margin
+		};
+#else
+		var tframe = top.Frame;
+
+		var win = new Window (new Rect (0, 1, tframe.Width, tframe.Height - 1), "Hello");
+#endif
+		MenuItemDetails [] menuItems = {
+			new MenuItemDetails ("F_ind", "", null),
+			new MenuItemDetails ("_Replace", "", null),
+			new MenuItemDetails ("_Item1", "", null),
+			new MenuItemDetails ("_Not From Sub Menu", "", null)
+		};
+
+		menuItems [0].Action = () => ShowMenuItem (menuItems [0]);
+		menuItems [1].Action = () => ShowMenuItem (menuItems [1]);
+		menuItems [2].Action = () => ShowMenuItem (menuItems [2]);
+		menuItems [3].Action = () => ShowMenuItem (menuItems [3]);
+
+		menu = new MenuBar (new MenuBarItem [] {
+			new MenuBarItem ("_File", new MenuItem [] {
+				new MenuItem ("Text _Editor Demo", "", () => { Editor (top); }),
+				new MenuItem ("_New", "Creates new file", NewFile),
+				new MenuItem ("_Open", "", Open),
+				new MenuItem ("_Hex", "", () => ShowHex (top)),
+				new MenuItem ("_Close", "", () => Close ()),
+				new MenuItem ("_Disabled", "", () => { }, () => false),
+				null,
+				new MenuItem ("_Quit", "", () => { if (Quit ()) top.Running = false; })
+			}),
+			new MenuBarItem ("_Edit", new MenuItem [] {
+				new MenuItem ("_Copy", "", Copy),
+				new MenuItem ("C_ut", "", Cut),
+				new MenuItem ("_Paste", "", Paste),
+				new MenuItem ("_Find and Replace",
+					new MenuBarItem (new MenuItem[] {menuItems [0], menuItems [1] })),
+				menuItems[3]
+			}),
+			new MenuBarItem ("_List Demos", new MenuItem [] {
+				new MenuItem ("Select _Multiple Items", "", () => ListSelectionDemo (true)),
+				new MenuItem ("Select _Single Item", "", () => ListSelectionDemo (false)),
+			}),
+			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 ("SubMenu1Item_1",
+					new MenuBarItem (new MenuItem[] {
+						new MenuItem ("SubMenu2Item_1",
+							new MenuBarItem (new MenuItem [] {
+								new MenuItem ("SubMenu3Item_1",
+									new MenuBarItem (new MenuItem [] { menuItems [2] })
+								)
+							})
+						)
+					})
+				)
+			}),
+			new MenuBarItem ("_About...", "Demonstrates top-level menu item", () =>  MessageBox.ErrorQuery (50, 7, "About Demo", "This is a demo app for gui.cs", "Ok")),
+		});
+
+		menuKeysStyle = new CheckBox (3, 25, "UseKeysUpDownAsKeysLeftRight", true);
+		menuKeysStyle.Toggled += MenuKeysStyle_Toggled;
+		menuAutoMouseNav = new CheckBox (40, 25, "UseMenuAutoNavigation", true);
+		menuAutoMouseNav.Toggled += MenuAutoMouseNav_Toggled;
+
+		ShowEntries (win);
+
+		int count = 0;
+		ml = new Label (new Rect (3, 17, 47, 1), "Mouse: ");
+		Application.RootMouseEvent += delegate (MouseEvent me) {
+			ml.TextColor = Colors.TopLevel.Normal;
+			ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
+		};
+
+		var test = new Label (3, 18, "Se iniciará el análisis");
+		win.Add (test);
+		win.Add (ml);
+
+		var drag = new Label ("Drag: ") { X = 70, Y = 24 };
+		var dragText = new TextField ("") {
+			X = Pos.Right (drag),
+			Y = Pos.Top (drag),
+			Width = 40
+		};
+
+		var statusBar = new StatusBar (new StatusItem [] {
+			new StatusItem(Key.F1, "~F1~ Help", () => Help()),
+			new StatusItem(Key.F2, "~F2~ Load", 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
+		// 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 of the same top-level!");
+		win.Add (bottom);
+		var bottom2 = new Label ("This should go on the bottom of another top-level!");
+		top.Add (bottom2);
+
+		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);
+		Application.Run ();
+	}
+}

+ 1 - 3
Terminal.Gui/Core.cs

@@ -1066,7 +1066,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Invoked when a character key is pressed and occurs after the key down event.
+		/// Invoked when a character key is pressed and occurs after the key up event.
 		/// </summary>
 		public Action<KeyEvent> OnKeyPress;
 
@@ -1083,7 +1083,6 @@ namespace Terminal.Gui {
 		/// <inheritdoc cref="ProcessHotKey"/>
 		public override bool ProcessHotKey (KeyEvent keyEvent)
 		{
-			OnKeyPress?.Invoke (keyEvent);
 			if (subviews == null || subviews.Count == 0)
 				return false;
 			foreach (var view in subviews)
@@ -1095,7 +1094,6 @@ namespace Terminal.Gui {
 		/// <inheritdoc cref="ProcessColdKey"/>
 		public override bool ProcessColdKey (KeyEvent keyEvent)
 		{
-			OnKeyPress?.Invoke (keyEvent);
 			if (subviews == null || subviews.Count == 0)
 				return false;
 			foreach (var view in subviews)

+ 22 - 16
Terminal.Gui/Drivers/CursesDriver.cs

@@ -52,7 +52,7 @@ namespace Terminal.Gui {
 			if (sync)
 				Application.Driver.Refresh ();
 			ccol++;
-			var runeWidth = Rune.ColumnWidth(rune);
+			var runeWidth = Rune.ColumnWidth (rune);
 			if (runeWidth > 1) {
 				for (int i = 1; i < runeWidth; i++) {
 					ccol++;
@@ -192,7 +192,7 @@ namespace Terminal.Gui {
 			};
 		}
 
-		void ProcessInput (Action<KeyEvent> keyHandler, Action<MouseEvent> mouseHandler)
+		void ProcessInput (Action<KeyEvent> keyHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
 		{
 			int wch;
 			var code = Curses.get_wch (out wch);
@@ -212,6 +212,7 @@ namespace Terminal.Gui {
 					return;
 				}
 				keyHandler (new KeyEvent (MapCursesKey (wch)));
+				keyUpHandler (new KeyEvent (MapCursesKey (wch)));
 				return;
 			}
 
@@ -219,7 +220,7 @@ namespace Terminal.Gui {
 			if (wch == 27) {
 				Curses.timeout (200);
 
-				code = Curses.get_wch (out wch);
+				code = Curses.get_wch (out int wch2);
 				if (code == Curses.KEY_CODE_YES)
 					keyHandler (new KeyEvent (Key.AltMask | MapCursesKey (wch)));
 				if (code == 0) {
@@ -227,23 +228,28 @@ namespace Terminal.Gui {
 
 					// The ESC-number handling, debatable.
 					// Simulates the AltMask itself by pressing Alt + Space.
-					if (wch == (int)Key.Space)
+					if (wch2 == (int)Key.Space)
 						key = new KeyEvent (Key.AltMask);
-					else if (wch - (int)Key.Space >= 'A' && wch - (int)Key.Space <= 'Z')
-						key = new KeyEvent ((Key)((uint)Key.AltMask + (wch - (int)Key.Space)));
-					else if (wch >= '1' && wch <= '9')
-						key = new KeyEvent ((Key)((int)Key.F1 + (wch - '0' - 1)));
-					else if (wch == '0')
+					else if (wch2 - (int)Key.Space >= 'A' && wch2 - (int)Key.Space <= 'Z')
+						key = new KeyEvent ((Key)((uint)Key.AltMask + (wch2 - (int)Key.Space)));
+					else if (wch2 >= '1' && wch <= '9')
+						key = new KeyEvent ((Key)((int)Key.F1 + (wch2 - '0' - 1)));
+					else if (wch2 == '0')
 						key = new KeyEvent (Key.F10);
-					else if (wch == 27)
-						key = new KeyEvent ((Key)wch);
+					else if (wch2 == 27)
+						key = new KeyEvent ((Key)wch2);
 					else
-						key = new KeyEvent (Key.AltMask | (Key)wch);
+						key = new KeyEvent (Key.AltMask | (Key)wch2);
 					keyHandler (key);
-				} else
+				} else {
 					keyHandler (new KeyEvent (Key.Esc));
-			} else
+				}
+			} else {
 				keyHandler (new KeyEvent ((Key)wch));
+			}
+			// Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above 
+			// will not impact KeyUp.
+			keyUpHandler (new KeyEvent ((Key)wch));
 		}
 
 		public override void PrepareToRun (MainLoop mainLoop, Action<KeyEvent> keyHandler, Action<KeyEvent> keyDownHandler, Action<KeyEvent> keyUpHandler, Action<MouseEvent> mouseHandler)
@@ -252,7 +258,7 @@ namespace Terminal.Gui {
 			Curses.timeout (-1);
 
 			(mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => {
-				ProcessInput (keyHandler, mouseHandler);
+				ProcessInput (keyHandler, keyUpHandler, mouseHandler);
 				return true;
 			});
 
@@ -314,7 +320,7 @@ namespace Terminal.Gui {
 				Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK);
 				Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN);
 				Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
-				Colors.Menu.Disabled = MakeColor(Curses.COLOR_WHITE, Curses.COLOR_CYAN);
+				Colors.Menu.Disabled = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN);
 
 				Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE);
 				Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN);

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

@@ -328,6 +328,7 @@ namespace Terminal.Gui {
 				if (map == (Key)0xffffffff)
 					return;
 				keyHandler (new KeyEvent (map));
+				keyUpHandler (new KeyEvent (map));
 			};
 		}
 

+ 71 - 50
Terminal.Gui/Drivers/WindowsDriver.cs

@@ -440,15 +440,10 @@ namespace Terminal.Gui {
 
 		public WindowsDriver ()
 		{
-			Colors.TopLevel = new ColorScheme ();
-
-			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
-			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
-			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
-			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.DarkCyan);
-
 			winConsole = new WindowsConsole ();
 
+			SetupColorsAndBorders ();
+
 			cols = Console.WindowWidth;
 			rows = Console.WindowHeight;
 			WindowsConsole.SmallRect.MakeEmpty (ref damageRegion);
@@ -459,6 +454,54 @@ namespace Terminal.Gui {
 			Task.Run ((Action)WindowsInputHandler);
 		}
 
+		private void SetupColorsAndBorders ()
+		{
+			Colors.TopLevel = new ColorScheme ();
+			Colors.Base = new ColorScheme ();
+			Colors.Dialog = new ColorScheme ();
+			Colors.Menu = new ColorScheme ();
+			Colors.Error = new ColorScheme ();
+
+			Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black);
+			Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan);
+			Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black);
+			Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan);
+
+			Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue);
+			Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
+			Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue);
+			Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray);
+
+			Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray);
+			Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black);
+			Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray);
+			Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black);
+			Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray);
+
+			Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray);
+			Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray);
+			Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray);
+			Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray);
+
+			Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White);
+			Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed);
+			Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White);
+			Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed);
+
+			HLine = '\u2500';
+			VLine = '\u2502';
+			Stipple = '\u2592';
+			Diamond = '\u25c6';
+			ULCorner = '\u250C';
+			LLCorner = '\u2514';
+			URCorner = '\u2510';
+			LRCorner = '\u2518';
+			LeftTee = '\u251c';
+			RightTee = '\u2524';
+			TopTee = '\u22a4';
+			BottomTee = '\u22a5';
+		}
+
 		[StructLayout (LayoutKind.Sequential)]
 		public struct ConsoleKeyInfoEx {
 			public ConsoleKeyInfo consoleKeyInfo;
@@ -564,11 +607,24 @@ namespace Terminal.Gui {
 			case WindowsConsole.EventType.Key:
 				var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent));
 				if (map == (Key)0xffffffff) {
-					KeyEvent key = default;
+					KeyEvent key = new KeyEvent ();
+
 					// Shift = VK_SHIFT = 0x10
 					// Ctrl = VK_CONTROL = 0x11
 					// Alt = VK_MENU = 0x12
 
+					if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) {
+						inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn;
+					}
+
+					if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) {
+						inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn;
+					}
+
+					if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) {
+						inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn;
+					}
+
 					switch (inputEvent.KeyEvent.dwControlKeyState) {
 					case WindowsConsole.ControlKeyState.RightAltPressed:
 					case WindowsConsole.ControlKeyState.RightAltPressed |
@@ -617,10 +673,10 @@ namespace Terminal.Gui {
 						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 {
+						// Key Up - Fire KeyDown Event and KeyStroke (ProcessKey) Event
+						keyHandler (new KeyEvent (map));
 						keyUpHandler (new KeyEvent (map));
 					}
 				}
@@ -918,6 +974,9 @@ namespace Terminal.Gui {
 			case ConsoleKey.OemComma:
 			case ConsoleKey.OemPlus:
 			case ConsoleKey.OemMinus:
+				if (keyInfo.KeyChar == 0)
+					return Key.Unknown;
+
 				return (Key)((uint)keyInfo.KeyChar);
 			}
 
@@ -971,48 +1030,10 @@ namespace Terminal.Gui {
 		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.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan);
-			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.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 ();
+			SetupColorsAndBorders ();
 		}
 
+		
 		void ResizeScreen ()
 		{
 			OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols];

+ 29 - 4
Terminal.Gui/Event.cs

@@ -45,7 +45,7 @@ namespace Terminal.Gui {
 		ControlSpace = 0,
 
 		/// <summary>
-	        /// The key code for the user pressing Control-A
+		/// The key code for the user pressing Control-A
 		/// </summary>
 		ControlA = 1,
 		/// <summary>
@@ -288,8 +288,7 @@ namespace Terminal.Gui {
 	/// <summary>
 	/// Describes a keyboard event.
 	/// </summary>
-	public struct KeyEvent {
-
+	public class KeyEvent {
 		/// <summary>
 		/// Symb olid definition for the key.
 		/// </summary>
@@ -321,6 +320,10 @@ namespace Terminal.Gui {
 		//public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26);
 		public bool IsCtrl => (Key & Key.CtrlMask) != 0;
 
+		public KeyEvent ()
+		{
+			Key = Key.Unknown;
+		}
 		/// <summary>
 		///   Constructs a new KeyEvent from the provided Key value - can be a rune cast into a Key value
 		/// </summary>
@@ -328,6 +331,28 @@ namespace Terminal.Gui {
 		{
 			Key = k;
 		}
+
+		public override string ToString ()
+		{
+			string msg = "";
+			var key = this.Key;
+			if ((this.Key & Key.ShiftMask) != 0) {
+				msg += "Shift-";
+			}
+			if ((this.Key & Key.CtrlMask) != 0) {
+				msg += "Ctrl-";
+			}
+			if ((this.Key & Key.AltMask) != 0) {
+				msg += "Alt-";
+			}
+
+			if (string.IsNullOrEmpty (msg)) {
+				msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}";
+			} else {
+				msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"")}";
+			}
+			return msg;
+		}
 	}
 
 	/// <summary>
@@ -486,7 +511,7 @@ namespace Terminal.Gui {
 		/// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.Gui.MouseEvent"/>.
 		/// </summary>
 		/// <returns>A <see cref="T:System.String"/> that represents the current <see cref="T:Terminal.Gui.MouseEvent"/>.</returns>
-		public override string ToString()
+		public override string ToString ()
 		{
 			return $"({X},{Y}:{Flags}";
 		}

+ 5 - 2
Terminal.Gui/Views/Label.cs

@@ -124,7 +124,7 @@ namespace Terminal.Gui {
 							for (int i = 0; i < spaces; i++)
 								s.Append (' ');
 						if (extras > 0) {
-							s.Append ('_');
+							//s.Append ('_');
 							extras--;
 						}
 					}
@@ -179,8 +179,11 @@ namespace Terminal.Gui {
 				int x;
 				switch (textAlignment) {
 				case TextAlignment.Left:
+					x = Frame.Left;
+					break;
 				case TextAlignment.Justified:
-					x = 0;
+					Recalc ();
+					x = Frame.Left;
 					break;
 				case TextAlignment.Right:
 					x = Frame.Right - str.Length;

+ 14 - 3
Terminal.Gui/Views/ListView.cs

@@ -294,6 +294,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event Action SelectedChanged;
 
+		/// <summary>
+		/// This event is raised on Enter key or Double Click to open the selected item.
+		/// </summary>
+		public event EventHandler OpenSelectedItem;
+
 		/// <summary>
 		/// Handles cursor movement for this view, passes all other events.
 		/// </summary>
@@ -325,6 +330,11 @@ namespace Terminal.Gui {
 					return true;
 				else
 					break;
+
+			case Key.Enter:
+				OpenSelectedItem?.Invoke (this, new EventArgs ());
+				break;
+
 			}
 			return base.ProcessKey (kb);
 		}
@@ -451,7 +461,7 @@ namespace Terminal.Gui {
 		///<inheritdoc cref="MouseEvent(Gui.MouseEvent)"/>
 		public override bool MouseEvent(MouseEvent me)
 		{
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked))
+			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked))
 				return false;
 
 			if (!HasFocus)
@@ -469,9 +479,10 @@ namespace Terminal.Gui {
 				SetNeedsDisplay ();
 				return true;
 			}
-			if (SelectedChanged != null)
-				SelectedChanged();
+			SelectedChanged?.Invoke ();
 			SetNeedsDisplay ();
+			if (me.Flags == MouseFlags.Button1DoubleClicked)
+				OpenSelectedItem?.Invoke (this, new EventArgs ());
 			return true;
 		}
 	}

+ 10 - 0
Terminal.sln

@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gu
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|x86 = Debug|x86
@@ -25,6 +27,14 @@ Global
 		{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
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.Build.0 = Debug|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU
+		{88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(MonoDevelopProperties) = preSolution
 		Policies = $0

+ 23 - 0
UICatalog/.editorconfig

@@ -0,0 +1,23 @@
+[*.cs]
+indent_style = tab
+indent_size = 8
+tab_width = 8
+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
+csharp_indent_labels = flush_left
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_preserve_single_line_blocks = true
+dotnet_style_require_accessibility_modifiers = never
+csharp_style_var_when_type_is_apparent = true
+csharp_prefer_braces = false
+csharp_space_before_open_square_brackets = true
+csharp_space_between_method_call_name_and_opening_parenthesis = true
+csharp_space_between_method_declaration_name_and_open_parenthesis = true

+ 9 - 0
UICatalog/.gitignore

@@ -0,0 +1,9 @@
+###############
+#    folder   #
+###############
+/**/DROP/
+/**/TEMP/
+/**/packages/
+/**/bin/
+/**/obj/
+_site

+ 271 - 0
UICatalog/Program.cs

@@ -0,0 +1,271 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog {
+	/// <summary>
+	/// Main program for the Terminal.gui UI Catalog app. This app provides a chooser that allows
+	/// for a calalog of UI demos, examples, and tests.
+	/// </summary>
+	class Program {
+		private static Toplevel _top;
+		private static MenuBar _menu;
+		private static int _nameColumnWidth;
+		private static Window _leftPane;
+		private static List<string> _categories;
+		private static ListView _categoryListView;
+		private static Window _rightPane;
+		private static List<Type> _scenarios;
+		private static ListView _scenarioListView;
+		private static StatusBar _statusBar;
+
+		private static Scenario _runningScenario = null;
+
+		static void Main (string [] args)
+		{
+			if (Debugger.IsAttached)
+				CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
+
+			_scenarios = Scenario.GetDerivedClassesCollection ().ToList ();
+
+			if (args.Length > 0) {
+				var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase));
+				_runningScenario = (Scenario)Activator.CreateInstance (_scenarios [item]);
+				Application.Init ();
+				_runningScenario.Init (Application.Top);
+				_runningScenario.Setup ();
+				_runningScenario.Run ();
+				_runningScenario = null;
+				return;
+			}
+
+			Scenario scenario = GetScenarioToRun ();
+			while (scenario != null) {
+				Application.Init ();
+				scenario.Init (Application.Top);
+				scenario.Setup ();
+				scenario.Run ();
+				scenario = GetScenarioToRun ();
+			}
+		}
+
+		/// <summary>
+		/// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs.
+		/// </summary>
+		private static void Setup ()
+		{
+			_menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_Quit", "", () => Application.RequestStop() )
+				}),
+				new MenuBarItem ("_About...", "About this app", () =>  MessageBox.Query (0, 6, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")),
+			});
+
+			_leftPane = new Window ("Categories") {
+				X = 0,
+				Y = 1, // for menu
+				Width = 25,
+				Height = Dim.Fill (),
+				CanFocus = false,
+			};
+
+
+			_categories = Scenario.GetAllCategories ();
+			_categoryListView = new ListView (_categories) {
+				X = 1,
+				Y = 0,
+				Width = Dim.Fill (0),
+				Height = Dim.Fill (2),
+				AllowsMarking = false,
+				CanFocus = true,
+			};
+			_categoryListView.OpenSelectedItem += (o, a) => {
+				_top.SetFocus (_rightPane);
+			};
+			_categoryListView.SelectedChanged += CategoryListView_SelectedChanged;
+			_leftPane.Add (_categoryListView);
+
+			_rightPane = new Window ("Scenarios") {
+				X = 25,
+				Y = 1, // for menu
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				CanFocus = false,
+
+			};
+
+			_nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length;
+
+			_scenarioListView = new ListView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (0),
+				Height = Dim.Fill (0),
+				AllowsMarking = false,
+				CanFocus = true,
+			};
+
+			//_scenarioListView.OnKeyPress += (KeyEvent ke) => {
+			//	if (_top.MostFocused == _scenarioListView && ke.Key == Key.Enter) {
+			//		_scenarioListView_OpenSelectedItem (null, null);
+			//	}
+			//};
+
+			_scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem;
+			_rightPane.Add (_scenarioListView);
+
+			_categoryListView.SelectedItem = 0;
+			CategoryListView_SelectedChanged ();
+
+			_statusBar = new StatusBar (new StatusItem [] {
+				//new StatusItem(Key.F1, "~F1~ Help", () => Help()),
+				new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => {
+					if (_runningScenario is null){
+						// This causes GetScenarioToRun to return null
+						_runningScenario = null;
+						Application.RequestStop();
+					} else {
+						_runningScenario.RequestStop();
+					}
+				}),
+			});
+		}
+
+		/// <summary>
+		/// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything.
+		/// </summary>
+		/// <returns></returns>
+		private static Scenario GetScenarioToRun ()
+		{
+			Application.Init ();
+
+			if (_menu == null) {
+				Setup ();
+			}
+
+			_top = Application.Top;
+			_top.OnKeyUp += KeyUpHandler;
+			_top.Add (_menu);
+			_top.Add (_leftPane);
+			_top.Add (_rightPane);
+			_top.Add (_statusBar);
+
+			// HACK: There is no other way to SetFocus before Application.Run. See Issue #445
+#if false
+			if (_runningScenario != null)
+				Application.Iteration += Application_Iteration;
+#else
+			_top.Ready += (o, a) => {
+				if (_runningScenario != null) {
+					_top.SetFocus (_rightPane);
+					_runningScenario = null;
+				}
+			};
+#endif
+			
+			Application.Run (_top);
+			return _runningScenario;
+		}
+
+#if false
+		private static void Application_Iteration (object sender, EventArgs e)
+		{
+			Application.Iteration -= Application_Iteration;
+			_top.SetFocus (_rightPane);
+		}
+#endif
+		private static void _scenarioListView_OpenSelectedItem (object sender, EventArgs e)
+		{
+			if (_runningScenario is null) {
+				var source = _scenarioListView.Source as ScenarioListDataSource;
+				_runningScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]);
+				Application.RequestStop ();
+			}
+		}
+
+		internal class ScenarioListDataSource : IListDataSource {
+			public List<Type> Scenarios { get; set; }
+
+			public bool IsMarked (int item) => false;//  Scenarios [item].IsMarked;
+
+			public int Count => Scenarios.Count;
+
+			public ScenarioListDataSource (List<Type> itemList) => Scenarios = itemList;
+
+			public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width)
+			{
+				container.Move (col, line);
+				// Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible
+				var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item]));
+				RenderUstr (driver, $"{s}  {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width);
+			}
+
+			public void SetMark (int item, bool value)
+			{
+			}
+
+			// A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461
+			private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width)
+			{
+				int used = 0;
+				int index = 0;
+				while (index < ustr.Length) {
+					(var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length);
+					var count = Rune.ColumnWidth (rune);
+					if (used + count >= width) break;
+					driver.AddRune (rune);
+					used += count;
+					index += size;
+				}
+
+				while (used < width) {
+					driver.AddRune (' ');
+					used++;
+				}
+			}
+		}
+
+		/// <summary>
+		/// When Scenarios are running we need to override the behavior of the Menu 
+		/// and Statusbar to enable Scenarios that use those (or related key input)
+		/// to not be impacted. Same as for tabs.
+		/// </summary>
+		/// <param name="ke"></param>
+		private static void KeyUpHandler (KeyEvent ke)
+		{
+			if (_runningScenario != null) {
+				//switch (ke.Key) {
+				//case Key.Esc:
+				//	//_runningScenario.RequestStop ();
+				//	break;
+				//case Key.Enter:
+				//	break;
+				//}
+			} else if (ke.Key == Key.Tab || ke.Key == Key.BackTab) {
+				// BUGBUG: Work around Issue #434 by implementing our own TAB navigation
+				if (_top.MostFocused == _categoryListView)
+					_top.SetFocus (_rightPane);
+				else
+					_top.SetFocus (_leftPane);
+			}
+		}
+
+		private static void CategoryListView_SelectedChanged ()
+		{
+			var item = _categories [_categoryListView.SelectedItem];
+			List<Type> newlist;
+			if (item.Equals ("All")) {
+				newlist = _scenarios;
+
+			} else {
+				newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList ();
+			}
+			_scenarioListView.Source = new ScenarioListDataSource (newlist);
+			_scenarioListView.SelectedItem = 0;
+		}
+	}
+}

+ 8 - 0
UICatalog/Properties/launchSettings.json

@@ -0,0 +1,8 @@
+{
+  "profiles": {
+    "UICatalog": {
+      "commandName": "Project",
+      "commandLineArgs": "HexEditor"
+    }
+  }
+}

+ 122 - 0
UICatalog/README.md

@@ -0,0 +1,122 @@
+# Terminal.Gui UI Catalog
+
+UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals:
+
+1. Be an easy to use showcase for Terminal.Gui concepts and features.
+2. Provide sample code that illustrates how to properly implement said concepts & features.
+3. Make it easy for contributors to add additional samples in a structured way.
+
+![screenshot](screenshot.png)
+
+## Motivation
+
+The original `demo.cs` sample app for Terminal.Gui is neither good to showcase, nor does it explain different concepts. In addition, because it is built on a single source file, it has proven to cause friction when multiple contributors are simultaneously working on different aspects of Terminal.Gui. See [Issue #368](https://github.com/migueldeicaza/Terminal.Gui/issues/368) for more background.
+
+## How To Use
+
+`Program.cs` is the main app and provides a UI for selecting and running **Scenarios**. Each **Scenario* is implemented as a class derived from `Scenario` and `Program.cs` uses reflection to dynamically build the UI.
+
+**Scenarios** are tagged with categories using the `[ScenarioCategory]` attribute. The left pane of the main screen lists the categories. Clicking on a category shows all the scenarios in that category.
+
+**Scenarios** can be run either from the **UICatalog.exe** app UI or by being specified on the command line:
+
+```
+UICatalog.exe <Scenario Name>
+```
+
+e.g.
+
+```
+UICatalog.exe Buttons
+```
+
+When a **Scenario** is run, it runs as though it were a standalone `Terminal.Gui` app. However, scaffolding is provided (in the `Scenario` base class) that (optionally) takes care of `Terminal.Gui` initialization.
+
+## Contributing by Adding Scenarios
+
+To add a new **Scenario** simply:
+
+1. Create a new `.cs` file in the `Scenarios` directory that derives from `Scenario`.
+2. Add a `[ScenarioMetaData]` attribute to the class specifying the scenario's name and description.
+3. Add one or more `[ScenarioCategory]` attributes to the class specifying which categories the sceanrio belongs to. If you don't specify a category the sceanrio will show up in "All".
+4. Implement the `Setup` override which will be called when a user selects the scenario to run.
+5. Optionally, implement the `Init` and/or `Run` overrides to provide a custom implementation.
+
+The sample below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named:
+
+```csharp
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
+	[ScenarioCategory ("Controls")]
+	class MyScenario : Scenario {
+		public override void Setup ()
+		{
+			// Put your scenario code here, e.g.
+			Win.Add (new Button ("Press me!") {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
+			});
+		}
+	}
+}
+```
+
+`Scenario` provides a `Toplevel` and `Window` the provides a canvas for the Scenario to operate. The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. 
+
+![screenshot](generic_screenshot.png)
+
+To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply set the `Top` and `Window` properties as appropriate, as seen in the `UnicodeInMenu` scenario:
+
+```csharp
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")]
+	[ScenarioCategory ("Text")]
+	[ScenarioCategory ("Controls")]
+	class UnicodeInMenu : Scenario {
+		public override void Setup ()
+		{
+			Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_Файл", new MenuItem [] {
+					new MenuItem ("_Создать", "Creates new file", null),
+					new MenuItem ("_Открыть", "", null),
+					new MenuItem ("Со_хранить", "", null),
+					new MenuItem ("_Выход", "", () => Application.RequestStop() )
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", null),
+					new MenuItem ("C_ut", "", null),
+					new MenuItem ("_Paste", "", null)
+				})
+			});
+			Top.Add (menu);
+
+			Win = new Window ($"Scenario: {GetName ()}") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+		}
+	}
+}
+```
+
+For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` assigns `Application.Top` to `Top` and creates `Win`. The `base.Run` simply calls `Application.Run(Top)`.
+
+## Contribution Guidelines
+
+- Provide a terse, descriptive name for `Scenarios`. Keep them short; the `ListView` that displays them dynamically sizes the column width and long names will make it hard for people to use.
+- Provide a clear description.
+- Comment `Scenario` code to describe to others why it's a useful `Scenario`.
+- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Try to minimize the number of new categories created.
+- Use the `Bug Rero` Category for `Scnarios` that reproduce bugs. 
+	- Include the Github Issue # in the Description.
+	- Once the bug has been fixed in `master` submit another PR to remove the `Scenario` (or modify it to provide a good regression test).
+- Tag bugs or suggestions for `UI Catalog` in the main `Terminal.Gui` Github Issues with "UICatalog: ".

+ 181 - 0
UICatalog/Scenario.cs

@@ -0,0 +1,181 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog {
+	/// <summary>
+	/// Base class for each demo/scenario. To define a new sceanrio simply
+	/// 
+	/// 1) declare a class derived from Scenario,
+	/// 2) Set Name and Description as appropriate using [ScenarioMetadata] attribute
+	/// 3) Set one or more categories with the [ScenarioCategory] attribute
+	/// 4) Implement Setup.
+	/// 5) Optionally, implement Run.
+	/// 
+	/// The Main program uses reflection to find all sceanarios and adds them to the
+	/// ListViews. Press ENTER to run the selected sceanrio. Press CTRL-Q to exit it.
+	/// </summary>
+	public class Scenario {
+		/// <summary>
+		/// The Top level for the Scenario. This should be set to `Application.Top` in most cases.
+		/// </summary>
+		public Toplevel Top { get; set; }
+
+		/// <summary>
+		/// </summary>
+		public Window Win { get; set; }
+
+		/// <summary>
+		/// Helper that provides the default Window implementation with a frame and 
+		/// label showing the name of the Scenario and logic to exit back to 
+		/// the Scenario picker UI.
+		/// Override Init to provide any `Toplevel` behavior needed.
+		/// </summary>
+		/// <param name="top"></param>
+		public virtual void Init(Toplevel top)
+		{
+			Top = top;
+			Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+		}
+
+		[System.AttributeUsage (System.AttributeTargets.Class)]
+		public class ScenarioMetadata : System.Attribute {
+			/// <summary>
+			/// Scenario Name
+			/// </summary>
+			public string Name { get; set; }
+
+			/// <summary>
+			/// Scenario Description
+			/// </summary>
+			public string Description { get; set; }
+
+			public ScenarioMetadata (string Name, string Description)
+			{
+				this.Name = Name;
+				this.Description = Description;
+			}
+
+			/// <summary>
+			/// Static helper function to get the Scenario Name given a Type
+			/// </summary>
+			/// <param name="t"></param>
+			/// <returns></returns>
+			public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name;
+
+			/// <summary>
+			/// Static helper function to get the Scenario Description given a Type
+			/// </summary>
+			/// <param name="t"></param>
+			/// <returns></returns>
+			public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description;
+		}
+
+		/// <summary>
+		/// Helper to get the Scenario Name
+		/// </summary>
+		/// <returns></returns>
+		public string GetName () => ScenarioMetadata.GetName (this.GetType ());
+
+		/// <summary>
+		/// Helper to get the Scenario Descripiton
+		/// </summary>
+		/// <returns></returns>
+		public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ());
+
+		[System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)]
+		public class ScenarioCategory : System.Attribute {
+			/// <summary>
+			/// Category Name
+			/// </summary>
+			public string Name { get; set; }
+
+			public ScenarioCategory (string Name) => this.Name = Name;
+
+			/// <summary>
+			/// Static helper function to get the Scenario Name given a Type
+			/// </summary>
+			/// <param name="t"></param>
+			/// <returns></returns>
+			public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name;
+
+			/// <summary>
+			/// Static helper function to get the Scenario Categories given a Type
+			/// </summary>
+			/// <param name="t"></param>
+			/// <returns></returns>
+			public static List<string> GetCategories (Type t) => System.Attribute.GetCustomAttributes (t)
+				.ToList ()
+				.Where (a => a is ScenarioCategory)
+				.Select (a => ((ScenarioCategory)a).Name)
+				.ToList ();
+		}
+
+		/// <summary>
+		/// Helper function to get the Categories of a Scenario
+		/// </summary>
+		/// <returns></returns>
+		public List<string> GetCategories () => ScenarioCategory.GetCategories (this.GetType ());
+
+		public override string ToString () => $"{GetName (),-30}{GetDescription ()}";
+
+		/// <summary>
+		/// Override this to implement the Scenario setup logic (create controls, etc...). 
+		/// </summary>
+		public virtual void Setup ()
+		{
+		}
+
+		/// <summary>
+		/// Runs the scenario. Override to start the scearnio using a Top level different than `Top`.
+		/// </summary>
+		public virtual void Run ()
+		{
+			Application.Run (Top);
+		}
+
+		/// <summary>
+		/// Stops the scenario. Override to implement shutdown behavior for the Scenario.
+		/// </summary>
+		public virtual void RequestStop ()
+		{
+			Application.RequestStop ();
+		}
+
+		/// <summary>
+		/// Returns a list of all Categories set by all of the scenarios defined in the project.
+		/// </summary>
+		internal static List<string> GetAllCategories ()
+		{
+			List<string> categories = new List<string> () { "All" };
+			foreach (Type type in typeof (Scenario).Assembly.GetTypes ()
+			    .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+				List<System.Attribute> attrs = System.Attribute.GetCustomAttributes (type).ToList ();
+				categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList ();
+			}
+			return categories;
+		}
+
+		/// <summary>
+		/// Returns an instance of each Scenario defined in the project. 
+		/// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class
+		/// </summary>
+		internal static List<Type> GetDerivedClassesCollection ()
+		{
+			List<Type> objects = new List<Type> ();
+			foreach (Type type in typeof (Scenario).Assembly.GetTypes ()
+			    .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) {
+				objects.Add (type);
+			}
+			return objects;
+		}
+	}
+}

+ 105 - 0
UICatalog/Scenarios/Buttons.cs

@@ -0,0 +1,105 @@
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("Layout")]
+	class Buttons : Scenario {
+		public override void Setup ()
+		{
+			// Add a label & text field so we can demo IsDefault
+			var editLabel = new Label ("TextField (to demo IsDefault):") {
+				X = 0,
+				Y = 0,
+			};
+			Win.Add (editLabel);
+			var edit = new TextField ("") {
+				X = Pos.Right (editLabel) + 1,
+				Y = Pos.Top (editLabel),
+				Width = Dim.Fill (2),
+			};
+			Win.Add (edit);
+
+			// This is the default button (IsDefault = true); if user presses ENTER in the TextField
+			// the scenario will quit
+			var defaultButton = new Button ("Quit") {
+				X = Pos.Center (),
+				// BUGBUG: Throws an exception
+				//Y= Pos.Bottom(Win),
+				Y = 20,
+				IsDefault = true,
+				Clicked = () => Application.RequestStop (),
+			};
+			Win.Add (defaultButton);
+
+			var y = 2;
+			var button = new Button (10, y, "Base Color") {
+				ColorScheme = Colors.Base,
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") 
+			};
+			Win.Add (button);
+
+			y += 2;
+			Win.Add (new Button (10, y, "Error Color") { 
+				ColorScheme = Colors.Error,
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") 
+			});
+
+			y += 2;
+			Win.Add (new Button (10, y, "Dialog Color") {
+				ColorScheme = Colors.Dialog,
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
+			});
+
+			y += 2;
+			Win.Add (new Button (10, y, "Menu Color") {
+				ColorScheme = Colors.Menu,
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
+			});
+
+			y += 2;
+			Win.Add (new Button (10, y, "TopLevel Color") {
+				ColorScheme = Colors.TopLevel,
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
+			});
+
+			y += 2;
+			Win.Add (new Button (10, y, "A super long button that will probably expose a bug in clipping or wrapping of text. Will it?") {
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
+			});
+
+			y += 2;
+			// Note the 'N' in 'Newline' will be the hotkey
+			Win.Add (new Button (10, y, "a Newline\nin the button") {
+				Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No")
+			});
+
+			y += 2;
+			// BUGBUG: Buttons don't support specifying hotkeys with _?!?
+			Win.Add (button = new Button (10, y, "Te_xt Changer") {
+			});
+			button.Clicked = () => button.Text += $"{y++}";
+
+			Win.Add (new Button ("Lets see if this will move as \"Text Changer\" grows") {
+				X = Pos.Right(button) + 10,
+				Y = y,
+			});
+
+			y += 2;
+			Win.Add (new Button (10, y, "Delete") {
+				ColorScheme = Colors.Error,
+				Clicked = () => Win.Remove (button)
+			});
+
+			y += 2;
+			Win.Add (new Button (10, y, "Change Default") {
+				Clicked = () => {
+					defaultButton.IsDefault = !defaultButton.IsDefault;
+					button.IsDefault = !button.IsDefault;
+				},
+			});
+
+
+		}
+	}
+}

+ 82 - 0
UICatalog/Scenarios/DimAndPosLayout.cs

@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog {
+	/// <summary>
+	/// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System. 
+	/// [x] - Using Dim.Fill to fill a window
+	/// [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control
+	/// [ ] - ...
+	/// </summary>
+	[ScenarioMetadata (Name: "DimAndPosLayout", Description: "Demonstrates using the Dim and Pos Layout System")]
+	[ScenarioCategory ("Layout")]
+	class DimAndPosLayout : Scenario {
+
+		public override void Setup ()
+		{
+			Top.LayoutStyle = LayoutStyle.Computed;
+			// Demonstrate using Dim to create a ruler that always measures the top-level window's width
+			// BUGBUG: Dim.Fill returns too big a value sometimes.
+			//const string rule = "|123456789";
+			//var labelRuler = new Label ("ruler") {
+			//	X = 0,
+			//	Y = 0,
+			//	Width = Dim.Fill (1),  // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does.
+			//	ColorScheme = Colors.Error
+			//};
+
+			//Application.OnResized += () => {
+			//	labelRuler.Text = rule.Repeat ((int)Math.Ceiling((double)(labelRuler.Bounds.Width) / (double)rule.Length))[0..(labelRuler.Bounds.Width)];
+			//};
+
+			//win.Add (labelRuler);
+
+			// Demonstrate using Dim to create a window that fills the parent with a margin
+			int margin = 20;
+			var subWin = new Window ($"Sub Windoww with {margin} character margin") {
+				X = margin,
+				Y = 2,
+				Width = Dim.Fill (margin),
+				Height = Dim.Fill ()
+			};
+			Win.Add (subWin);
+
+			int i = 1;
+			string txt = "Hello world, how are you doing today";
+			var labelList = new List<Label> ();
+			labelList.Add (new Label ($"Label:"));
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1, ColorScheme = Colors.Dialog });
+
+			subWin.Add (labelList.ToArray ());
+			//subWin.LayoutSubviews ();
+		}
+
+		public override void Run ()
+		{
+			base.Run ();
+		}
+	}
+
+	public static class StringExtensions {
+		public static string Repeat (this string instr, int n)
+		{
+			if (n <= 0) {
+				return null;
+			}
+
+			if (string.IsNullOrEmpty (instr) || n == 1) {
+				return instr;
+			}
+
+			return new StringBuilder (instr.Length * n)
+				.Insert (0, instr, n)
+				.ToString ();
+		}
+	}
+}

+ 150 - 0
UICatalog/Scenarios/Editor.cs

@@ -0,0 +1,150 @@
+using System;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Editor", Description: "A Terminal.Gui Text Editor via TextView")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("Text")]
+	class Editor : Scenario {
+		private string _fileName = "demo.txt";
+		private TextView _textView;
+		private bool _saved = true;
+
+
+		public override void Init (Toplevel top)
+		{
+			Top = top;
+		}
+
+		public override void Setup ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_New", "", () => New()),
+					new MenuItem ("_Open", "", () => Open()),
+					new MenuItem ("_Save", "", () => Save()),
+					null,
+					new MenuItem ("_Quit", "", () => Quit()),
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", () => Copy()),
+					new MenuItem ("C_ut", "", () => Cut()),
+					new MenuItem ("_Paste", "", () => Paste())
+				}),
+			});
+			Top.Add (menu);
+
+			var statusBar = new StatusBar (new StatusItem [] {
+				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
+				new StatusItem(Key.F3, "~F3~ Save", () => Save()),
+				new StatusItem(Key.ControlQ, "~^Q~ Quit", () => Quit()),
+			});
+			Top.Add (statusBar);
+
+			CreateDemoFile (_fileName);
+
+			Win = new Window (_fileName ?? "Untitled") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+
+			_textView = new TextView () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				
+			};
+
+			LoadFile ();
+
+			Win.Add (_textView);
+		}
+
+		private void New ()
+		{
+			Win.Title = _fileName = "Untitled";
+			throw new NotImplementedException ();
+		}
+
+		private void LoadFile ()
+		{
+			if (!_saved) {
+				MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+			}
+
+			if (_fileName != null) {
+				// BUGBUG: #452 TextView.LoadFile keeps file open and provides no way of closing it
+				//_textView.LoadFile(_fileName);
+				_textView.Text = System.IO.File.ReadAllText (_fileName);
+				Win.Title = _fileName;
+				_saved = true;
+			}
+		}
+
+		private void Paste ()
+		{
+			MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+		}
+
+		private void Cut ()
+		{
+			MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+		}
+
+		private void Copy ()
+		{
+			MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+			//if (_textView != null && _textView.SelectedLength != 0) {
+			//	_textView.Copy ();
+			//}
+		}
+
+		private void Open ()
+		{
+			var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false };
+			Application.Run (d);
+
+			if (!d.Canceled) {
+				_fileName = d.FilePaths [0];
+				LoadFile ();
+			}
+		}
+
+		private void Save ()
+		{
+			if (_fileName != null) {
+				// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r 
+				// As a result files saved on Windows and then read back will show invalid chars.
+				System.IO.File.WriteAllText (_fileName, _textView.Text.ToString());
+				_saved = true;
+			}
+		}
+
+		private void Quit ()
+		{
+			Application.RequestStop ();
+		}
+
+		private void CreateDemoFile(string fileName)
+		{
+			var sb = new StringBuilder ();
+			// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
+			sb.Append ("Hello world.\n");
+			sb.Append ("This is a test of the Emergency Broadcast System.\n");
+
+			var sw = System.IO.File.CreateText (fileName);
+			sw.Write (sb.ToString ());
+			sw.Close ();
+		}
+
+		public override void Run ()
+		{
+			Application.Run (Top);
+		}
+	}
+}

+ 17 - 0
UICatalog/Scenarios/Generic.cs

@@ -0,0 +1,17 @@
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")]
+	[ScenarioCategory ("Controls")]
+	class MyScenario : Scenario {
+		public override void Setup ()
+		{
+			// Put your scenario code here, e.g.
+			Win.Add (new Button ("Press me!") {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No")
+			});
+		}
+	}
+}

+ 154 - 0
UICatalog/Scenarios/HexEditor.cs

@@ -0,0 +1,154 @@
+using System;
+using System.IO;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "HexEditor", Description: "A Terminal.Gui binary (hex) editor via HexView")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("Text")]
+	class HexEditor : Scenario {
+		private string _fileName = "demo.bin";
+		private HexView _hexView;
+		private bool _saved = true;
+
+
+		public override void Init (Toplevel top)
+		{
+			Top = top;
+		}
+
+		public override void Setup ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_New", "", () => New()),
+					new MenuItem ("_Open", "", () => Open()),
+					new MenuItem ("_Save", "", () => Save()),
+					null,
+					new MenuItem ("_Quit", "", () => Quit()),
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", () => Copy()),
+					new MenuItem ("C_ut", "", () => Cut()),
+					new MenuItem ("_Paste", "", () => Paste())
+				}),
+			});
+			Top.Add (menu);
+
+			var statusBar = new StatusBar (new StatusItem [] {
+				//new StatusItem(Key.Enter, "~ENTER~ ApplyEdits", () => { _hexView.ApplyEdits(); }),
+				new StatusItem(Key.F2, "~F2~ Open", () => Open()),
+				new StatusItem(Key.F3, "~F3~ Save", () => Save()),
+				new StatusItem(Key.ControlQ, "~^Q~ Quit", () => Quit()),
+			});
+			Top.Add (statusBar);
+
+			CreateDemoFile (_fileName);
+
+			Win = new Window (_fileName ?? "Untitled") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+
+			_hexView = new HexView (LoadFile()) {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+			};
+
+
+			Win.Add (_hexView);
+		}
+
+		private void New ()
+		{
+			Win.Title = _fileName = "Untitled";
+			throw new NotImplementedException ();
+		}
+
+		private Stream LoadFile ()
+		{
+			MemoryStream stream = null;
+			if (!_saved) {
+				MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+			}
+
+			if (_fileName != null) {
+				var bin = System.IO.File.ReadAllBytes (_fileName);
+				stream = new MemoryStream (bin);
+				Win.Title = _fileName;
+				_saved = true;
+			}
+			return stream;
+		}
+
+		private void Paste ()
+		{
+			MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+		}
+
+		private void Cut ()
+		{
+			MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+		}
+
+		private void Copy ()
+		{
+			MessageBox.ErrorQuery (0, 10, "Not Implemented", "Functionality not yet implemented.", "Ok");
+			//if (_textView != null && _textView.SelectedLength != 0) {
+			//	_textView.Copy ();
+			//}
+		}
+
+		private void Open ()
+		{
+			var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false };
+			Application.Run (d);
+
+			if (!d.Canceled) {
+				_fileName = d.FilePaths [0];
+				_hexView.Source = LoadFile ();
+				_hexView.DisplayStart = 0;
+			}
+		}
+
+		private void Save ()
+		{
+			if (_fileName != null) {
+				using (FileStream fs = new FileStream (_fileName, FileMode.OpenOrCreate)) {
+					_hexView.ApplyEdits ();
+					_hexView.Source.CopyTo (fs);
+					fs.Flush ();
+				}
+				_saved = true;
+			}
+		}
+
+		private void Quit ()
+		{
+			Application.RequestStop ();
+		}
+
+		private void CreateDemoFile(string fileName)
+		{
+			var sb = new StringBuilder ();
+			// BUGBUG: #279 TextView does not know how to deal with \r\n, only \r
+			sb.Append ("Hello world.\n");
+			sb.Append ("This is a test of the Emergency Broadcast System.\n");
+
+			var sw = System.IO.File.CreateText (fileName);
+			sw.Write (sb.ToString ());
+			sw.Close ();
+		}
+
+		public override void Run ()
+		{
+			Application.Run (Top);
+		}
+	}
+}

+ 181 - 0
UICatalog/Scenarios/Keys.cs

@@ -0,0 +1,181 @@
+using NStack;
+using System.Collections.Generic;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Keys", Description: "Shows how to handle keyboard input")]
+	[ScenarioCategory ("Input")]
+	class Keys : Scenario {
+
+		static List<string> _processKeyList = new List<string> ();
+		static List<string> _processHotKeyList = new List<string> ();
+		static List<string> _processColdKeyList = new List<string> ();
+
+		class TestWindow : Window {
+			public TestWindow (ustring title = null) : base (title)
+			{
+			}
+
+			public TestWindow (Rect frame, ustring title = null) : base (frame, title)
+			{
+			}
+
+			public TestWindow (ustring title = null, int padding = 0) : base (title, padding)
+			{
+			}
+
+			public TestWindow (Rect frame, ustring title = null, int padding = 0) : base (frame, title, padding)
+			{
+			}
+
+			public override bool ProcessKey (KeyEvent keyEvent)
+			{
+				_processKeyList.Add (keyEvent.ToString ());
+				return base.ProcessKey (keyEvent);
+			}
+
+			public override bool ProcessHotKey (KeyEvent keyEvent)
+			{
+				_processHotKeyList.Add (keyEvent.ToString ());
+				return base.ProcessHotKey (keyEvent);
+			}
+
+			public override bool ProcessColdKey (KeyEvent keyEvent)
+			{
+				_processColdKeyList.Add (keyEvent.ToString ());
+				return base.ProcessHotKey (keyEvent);
+
+			}
+		}
+
+		public override void Init (Toplevel top)
+		{
+			Top = top;
+
+			Win = new TestWindow ($"CTRL-Q to Close - Scenario: {GetName ()}") {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+		}
+
+		public override void Setup ()
+		{
+			// Type text here: ______
+			var editLabel = new Label ("Type text here:") {
+				X = 0,
+				Y = 0,
+			};
+			Win.Add (editLabel);
+			var edit = new TextField ("") {
+				X = Pos.Right (editLabel) + 1,
+				Y = Pos.Top (editLabel),
+				Width = Dim.Fill (2),
+			};
+			Win.Add (edit);
+
+			// Last KeyPress: ______
+			var keyPressedLabel = new Label ("Last KeyPress:") {
+				X = Pos.Left (editLabel),
+				Y = Pos.Top (editLabel) + 2,
+			};
+			Win.Add (keyPressedLabel);
+			// BUGBUG: Label is not positioning right with Pos, so using TextField instead
+			var labelKeypress = new TextField ("") {
+				X = Pos.Right (keyPressedLabel) + 1,
+				Y = Pos.Top (keyPressedLabel),
+				Width = 20,
+				//TextAlignment = Terminal.Gui.TextAlignment.Left,
+				ColorScheme = Colors.Error,
+			};
+			Win.Add (labelKeypress);
+
+			Win.OnKeyPress += (KeyEvent keyEvent) => labelKeypress.Text = keyEvent.ToString ();
+
+			// Key stroke log:
+			var keyLogLabel = new Label ("Key stroke log:") {
+				X = Pos.Left (editLabel),
+				Y = Pos.Top (editLabel) + 4,
+			};
+			Win.Add (keyLogLabel);
+
+			var yOffset = (Top == Application.Top ? 1 : 6);
+			var keyStrokelist = new List<string> ();
+			var keyStrokeListView = new ListView (keyStrokelist) {
+				X = 0,
+				Y = Pos.Top (keyLogLabel) + yOffset,
+				Width = 25,
+				Height = Dim.Fill (),
+			};
+			keyStrokeListView.ColorScheme = Colors.TopLevel;
+			Win.Add (keyStrokeListView);
+
+			void KeyDownPressUp (KeyEvent keyEvent, string updown)
+			{
+				var msg = $"Key{updown,-5}: {keyEvent.ToString ()}";
+				keyStrokelist.Add (msg);
+				keyStrokeListView.MoveDown ();
+			}
+
+			Win.OnKeyDown += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Down");
+			Win.OnKeyPress += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Press");
+			Win.OnKeyUp += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Up");
+
+			// ProcessKey log:
+			// BUGBUG: Label is not positioning right with Pos, so using TextField instead
+			var processKeyLogLabel = new Label ("ProcessKey log:") {
+				X = Pos.Right (keyStrokeListView) + 1,
+				Y = Pos.Top (editLabel) + 4,
+			};
+			Win.Add (processKeyLogLabel);
+
+			yOffset = (Top == Application.Top ? 1 : 6);
+			var processKeyListView = new ListView (_processKeyList) {
+				X = Pos.Left (processKeyLogLabel),
+				Y = Pos.Top (processKeyLogLabel) + yOffset,
+				Width = 25,
+				Height = Dim.Fill (),
+			};
+			processKeyListView.ColorScheme = Colors.TopLevel;
+			Win.Add (processKeyListView);
+
+			// ProcessHotKey log:
+			// BUGBUG: Label is not positioning right with Pos, so using TextField instead
+			var processHotKeyLogLabel = new Label ("ProcessHotKey log:") {
+				X = Pos.Right (processKeyListView) + 1,
+				Y = Pos.Top (editLabel) + 4,
+			};
+			Win.Add (processHotKeyLogLabel);
+
+			yOffset = (Top == Application.Top ? 1 : 6);
+			var processHotKeyListView = new ListView (_processHotKeyList) {
+				X = Pos.Left (processHotKeyLogLabel),
+				Y = Pos.Top (processHotKeyLogLabel) + yOffset,
+				Width = 25,
+				Height = Dim.Fill (),
+			};
+			processHotKeyListView.ColorScheme = Colors.TopLevel;
+			Win.Add (processHotKeyListView);
+
+			// ProcessColdKey log:
+			// BUGBUG: Label is not positioning right with Pos, so using TextField instead
+			var processColdKeyLogLabel = new Label ("ProcessColdKey log:") {
+				X = Pos.Right (processHotKeyListView) + 1,
+				Y = Pos.Top (editLabel) + 4,
+			};
+			Win.Add (processColdKeyLogLabel);
+
+			yOffset = (Top == Application.Top ? 1 : 6);
+			var processColdKeyListView = new ListView (_processColdKeyList) {
+				X = Pos.Left (processColdKeyLogLabel),
+				Y = Pos.Top (processColdKeyLogLabel) + yOffset,
+				Width = 25,
+				Height = Dim.Fill (),
+			};
+			processColdKeyListView.ColorScheme = Colors.TopLevel;
+			Win.Add (processColdKeyListView);
+		}
+	}
+}

+ 42 - 0
UICatalog/Scenarios/MessageBoxes.cs

@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "MessageBoxes", Description: "Demonstrates how to use MessageBoxes")]
+	[ScenarioCategory ("Controls")]
+	[ScenarioCategory ("Dialogs")]
+	[ScenarioCategory ("Bug Repro")]
+	class MessageBoxes : Scenario {
+		public override void Setup ()
+		{
+			Top = new Toplevel ();
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_Quit", "", () => Application.RequestStop() )
+				}),
+				new MenuBarItem ("_Simple Query...", "A simple query message box", () =>  MessageBox.Query (0, 6, "MessageBox.Query", "Minimum size was specified", "Ok")),
+				new MenuBarItem ("_Error Query...", "A error query message box", () =>  MessageBox.ErrorQuery (0, 6, "MessageBox.Query", "Minimum size was specified", "Ok")),
+				// BUGBUG: Illustrates MessageBoxes do not deal with long text gracefully. Issue #432
+				new MenuBarItem ("_Long Text...", "Demo long text", () =>  MessageBox.Query (0, 6, "About UI Catalog", "This is a very long title. It is longer than the width of the screen. Will it Wrap? I bet  it will not wrap", "Ok")),
+			});
+			Top.Add (menu);
+
+			Win = new Window ($"Scenario: {GetName ()}") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+
+		}
+
+		public override void Run ()
+		{
+			Application.Run (Top);
+		}
+	}
+}

+ 34 - 0
UICatalog/Scenarios/Mouse.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Mouse", Description: "Demonstrates how to capture mouse events")]
+	[ScenarioCategory ("Input")]
+	class Mouse : Scenario {
+		public override void Setup () {
+			Label ml;
+			int count = 0;
+			ml = new Label (new Rect (1, 1, 50, 1), "Mouse: ");
+			Application.RootMouseEvent += delegate (MouseEvent me) {
+				ml.TextColor = Colors.TopLevel.Normal;
+				ml.Text = $"Mouse: ({me.X},{me.Y}) - {me.Flags} {count++}";
+			};
+
+
+			var test = new Label (1, 2, "Se iniciará el análisis");
+			Win.Add (test);
+			Win.Add (ml);
+
+			// I have no idea what this was intended to show off in demo.c
+			var drag = new Label ("Drag: ") { X = 1, Y = 4 };
+			var dragText = new TextField ("") {
+				X = Pos.Right (drag),
+				Y = Pos.Top (drag),
+				Width = 40
+			};
+			Win.Add (drag, dragText);
+		}
+	}
+}

+ 26 - 0
UICatalog/Scenarios/TextAlignment.cs

@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using System.Linq;
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Text Alignment", Description: "Demonstrates text alignment")]
+	[ScenarioCategory ("Text")]
+	class TextAlignment : Scenario {
+		public override void Setup ()
+		{
+			int i = 1;
+			string txt = "Hello world, how are you doing today";
+			var labelList = new List<Label> ();
+			labelList.Add (new Label ($"Label:"));
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Right, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Centered, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Justified, Width = Dim.Fill (1), X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
+			txt += "\nSecond line";
+			labelList.Add (new Label ($"{i++}-{txt}") { TextAlignment = Terminal.Gui.TextAlignment.Left, Width = Dim.Fill (1), Height = 4, X = 0, Y = Pos.Bottom (labelList.LastOrDefault ()) + 1 });
+
+			Win.Add (labelList.ToArray ());
+			Win.LayoutSubviews ();
+		}
+	}
+}

+ 40 - 0
UICatalog/Scenarios/TopLevelNoWindowBug.cs

@@ -0,0 +1,40 @@
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "TopLevelNoWindowBug", Description: "Illustrates that not having a Window causes MenuBar to misbehave. #437")]
+	[ScenarioCategory ("Bug Repro")]
+
+	class TopLevelNoWindowBug : Scenario {
+
+		public override void Run ()
+		{
+			var ntop = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_Файл", new MenuItem [] {
+					new MenuItem ("_Создать", "Creates new file", null),
+					new MenuItem ("_Открыть", "", null),
+					new MenuItem ("Со_хранить", "", null),
+					new MenuItem ("_Выход", "", () => ntop.Running = false )
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", null),
+					new MenuItem ("C_ut", "", null),
+					new MenuItem ("_Paste", "", null)
+				})
+			});
+			ntop.Add (menu);
+
+			// BUGBUG: #437 This being commmented out causes menu to mis-behave
+			var win = new Window ($"Scenario: {GetName ()}") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			ntop.Add (win);
+
+			Application.Run (ntop);
+		}
+	}
+}

+ 35 - 0
UICatalog/Scenarios/UnicodeInMenu.cs

@@ -0,0 +1,35 @@
+using Terminal.Gui;
+
+namespace UICatalog {
+	[ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")]
+	[ScenarioCategory ("Text")]
+	[ScenarioCategory ("Controls")]
+	class UnicodeInMenu : Scenario {
+		public override void Setup ()
+		{
+			Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_Файл", new MenuItem [] {
+					new MenuItem ("_Создать", "Creates new file", null),
+					new MenuItem ("_Открыть", "", null),
+					new MenuItem ("Со_хранить", "", null),
+					new MenuItem ("_Выход", "", () => Application.RequestStop() )
+				}),
+				new MenuBarItem ("_Edit", new MenuItem [] {
+					new MenuItem ("_Copy", "", null),
+					new MenuItem ("C_ut", "", null),
+					new MenuItem ("_Paste", "", null)
+				})
+			});
+			Top.Add (menu);
+
+			Win = new Window ($"Scenario: {GetName ()}") {
+				X = 0,
+				Y = 1,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Top.Add (Win);
+		}
+	}
+}

+ 13 - 0
UICatalog/UICatalog.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <StartupObject>UICatalog.Program</StartupObject>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Terminal.Gui\Terminal.Gui.csproj" />
+  </ItemGroup>
+
+</Project>

二進制
UICatalog/generic_screenshot.png


二進制
UICatalog/screenshot.png