浏览代码

Merge branch 'develop' into update_workflows

Tig Kindel 3 年之前
父节点
当前提交
804cf897c5
共有 100 个文件被更改,包括 10817 次插入1347 次删除
  1. 16 1
      Example/demo.cs
  2. 40 36
      README.md
  3. 1 1
      ReactiveExample/ReactiveExample.csproj
  4. 36 30
      Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
  5. 2 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs
  6. 7 2
      Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs
  7. 2 2
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
  8. 21 6
      Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs
  9. 7 12
      Terminal.Gui/ConsoleDrivers/NetDriver.cs
  10. 36 24
      Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
  11. 118 57
      Terminal.Gui/Core/Application.cs
  12. 58 44
      Terminal.Gui/Core/Border.cs
  13. 9 2
      Terminal.Gui/Core/ConsoleDriver.cs
  14. 3 1
      Terminal.Gui/Core/ContextMenu.cs
  15. 32 5
      Terminal.Gui/Core/MainLoop.cs
  16. 97 15
      Terminal.Gui/Core/PosDim.cs
  17. 76 31
      Terminal.Gui/Core/TextFormatter.cs
  18. 83 56
      Terminal.Gui/Core/Toplevel.cs
  19. 334 111
      Terminal.Gui/Core/View.cs
  20. 92 17
      Terminal.Gui/Core/Window.cs
  21. 27 0
      Terminal.Gui/Resources/Strings.Designer.cs
  22. 89 60
      Terminal.Gui/Resources/Strings.fr-FR.resx
  23. 9 0
      Terminal.Gui/Resources/Strings.ja-JP.resx
  24. 89 60
      Terminal.Gui/Resources/Strings.pt-PT.resx
  25. 89 60
      Terminal.Gui/Resources/Strings.resx
  26. 22 76
      Terminal.Gui/Views/Button.cs
  27. 38 48
      Terminal.Gui/Views/Checkbox.cs
  28. 2 2
      Terminal.Gui/Views/ComboBox.cs
  29. 17 14
      Terminal.Gui/Views/FrameView.cs
  30. 22 4
      Terminal.Gui/Views/Label.cs
  31. 55 23
      Terminal.Gui/Views/Menu.cs
  32. 4 4
      Terminal.Gui/Views/PanelView.cs
  33. 21 15
      Terminal.Gui/Views/RadioGroup.cs
  34. 1 1
      Terminal.Gui/Views/ScrollBarView.cs
  35. 4 2
      Terminal.Gui/Views/TabView.cs
  36. 86 14
      Terminal.Gui/Views/TableView.cs
  37. 3 0
      Terminal.Gui/Views/TextField.cs
  38. 137 37
      Terminal.Gui/Views/TextView.cs
  39. 100 8
      Terminal.Gui/Windows/Dialog.cs
  40. 9 3
      Terminal.Gui/Windows/MessageBox.cs
  41. 869 0
      Terminal.Gui/Windows/Wizard.cs
  42. 16 0
      UICatalog/Properties/launchSettings.json
  43. 32 0
      UICatalog/Scenarios/AutoSizeAndDirectionText.cs
  44. 1 1
      UICatalog/Scenarios/BackgroundWorkerCollection.cs
  45. 22 5
      UICatalog/Scenarios/CsvEditor.cs
  46. 85 21
      UICatalog/Scenarios/Dialogs.cs
  47. 36 24
      UICatalog/Scenarios/Editor.cs
  48. 4 0
      UICatalog/Scenarios/LabelsAsButtons.cs
  49. 4 2
      UICatalog/Scenarios/Scrolling.cs
  50. 311 3
      UICatalog/Scenarios/TableEditor.cs
  51. 2 2
      UICatalog/Scenarios/TextAlignments.cs
  52. 1 1
      UICatalog/Scenarios/TextAlignmentsAndDirection.cs
  53. 3 3
      UICatalog/Scenarios/TextFormatterDemo.cs
  54. 106 0
      UICatalog/Scenarios/WizardAsView.cs
  55. 292 0
      UICatalog/Scenarios/Wizards.cs
  56. 34 32
      UnitTests/ApplicationTests.cs
  57. 1 1
      UnitTests/AssemblyInfo.cs
  58. 335 17
      UnitTests/ButtonTests.cs
  59. 461 8
      UnitTests/CheckboxTests.cs
  60. 45 7
      UnitTests/ComboBoxTests.cs
  61. 2 2
      UnitTests/ConsoleDriverTests.cs
  62. 15 15
      UnitTests/ContextMenuTests.cs
  63. 517 0
      UnitTests/DialogTests.cs
  64. 545 23
      UnitTests/DimTests.cs
  65. 26 4
      UnitTests/GraphViewTests.cs
  66. 337 39
      UnitTests/MenuTests.cs
  67. 9 5
      UnitTests/PanelViewTests.cs
  68. 241 20
      UnitTests/PosTests.cs
  69. 82 22
      UnitTests/RadioGroupTests.cs
  70. 24 56
      UnitTests/ScenarioTests.cs
  71. 139 1
      UnitTests/ScrollBarViewTests.cs
  72. 10 8
      UnitTests/ScrollViewTests.cs
  73. 1 1
      UnitTests/StatusBarTests.cs
  74. 8 12
      UnitTests/TabViewTests.cs
  75. 465 10
      UnitTests/TableViewTests.cs
  76. 28 10
      UnitTests/TextFieldTests.cs
  77. 145 34
      UnitTests/TextFormatterTests.cs
  78. 64 4
      UnitTests/TextViewTests.cs
  79. 1 0
      UnitTests/ToplevelTests.cs
  80. 1519 68
      UnitTests/ViewTests.cs
  81. 157 0
      UnitTests/WindowTests.cs
  82. 610 0
      UnitTests/WizardTests.cs
  83. 11 0
      docfx/_exported_templates/README.md
  84. 258 0
      docfx/_exported_templates/default/ManagedReference.common.js
  85. 15 0
      docfx/_exported_templates/default/ManagedReference.extension.js
  86. 40 0
      docfx/_exported_templates/default/ManagedReference.html.primary.js
  87. 13 0
      docfx/_exported_templates/default/ManagedReference.html.primary.tmpl
  88. 290 0
      docfx/_exported_templates/default/RestApi.common.js
  89. 15 0
      docfx/_exported_templates/default/RestApi.extension.js
  90. 25 0
      docfx/_exported_templates/default/RestApi.html.primary.js
  91. 3 0
      docfx/_exported_templates/default/RestApi.html.primary.tmpl
  92. 318 0
      docfx/_exported_templates/default/UniversalReference.common.js
  93. 15 0
      docfx/_exported_templates/default/UniversalReference.extension.js
  94. 28 0
      docfx/_exported_templates/default/UniversalReference.html.primary.js
  95. 12 0
      docfx/_exported_templates/default/UniversalReference.html.primary.tmpl
  96. 237 0
      docfx/_exported_templates/default/common.js
  97. 15 0
      docfx/_exported_templates/default/conceptual.extension.js
  98. 19 0
      docfx/_exported_templates/default/conceptual.html.primary.js
  99. 4 0
      docfx/_exported_templates/default/conceptual.html.primary.tmpl
  100. 二进制
      docfx/_exported_templates/default/favicon.ico

+ 16 - 1
Example/demo.cs

@@ -624,6 +624,8 @@ static class Demo {
 
 		MenuItem miUseSubMenusSingleFrame = null;
 		var useSubMenusSingleFrame = false;
+		MenuItem miUseKeysUpDownAsKeysLeftRight = null;
+		var useKeysUpDownAsKeysLeftRight = false;
 
 		MenuItem miHeightAsBuffer = null;
 
@@ -635,6 +637,9 @@ static class Demo {
 				new MenuItem ("_Hex", "", () => { running = ShowHex; Application.RequestStop (); }, null, null, Key.AltMask | Key.CtrlMask | Key.H),
 				new MenuItem ("_Close", "", Close, null, null, Key.AltMask | Key.Q),
 				new MenuItem ("_Disabled", "", () => { }, () => false),
+				new MenuBarItem ("_SubMenu Disabled", new MenuItem [] {
+					new MenuItem ("_Disabled", "", () => { }, () => false)
+				}),
 				null,
 				new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } }, null, null, Key.CtrlMask | Key.Q)
 			}),
@@ -645,8 +650,18 @@ static class Demo {
 				new MenuBarItem ("_Find and Replace",
 					new MenuItem [] { menuItems [0], menuItems [1] }),
 				menuItems[3],
+				miUseKeysUpDownAsKeysLeftRight = new MenuItem ("Use_KeysUpDownAsKeysLeftRight", "",
+				() => {
+				menu.UseKeysUpDownAsKeysLeftRight = miUseKeysUpDownAsKeysLeftRight.Checked = useKeysUpDownAsKeysLeftRight = !useKeysUpDownAsKeysLeftRight;
+					miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = menu.UseSubMenusSingleFrame;
+					}) {
+					CheckType = MenuItemCheckStyle.Checked, Checked = useKeysUpDownAsKeysLeftRight
+				},
 				miUseSubMenusSingleFrame = new MenuItem ("Use_SubMenusSingleFrame", "",
-				() => menu.UseSubMenusSingleFrame = miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = !useSubMenusSingleFrame) {
+				() => {
+				menu.UseSubMenusSingleFrame = miUseSubMenusSingleFrame.Checked = useSubMenusSingleFrame = !useSubMenusSingleFrame;
+					miUseKeysUpDownAsKeysLeftRight.Checked = useKeysUpDownAsKeysLeftRight = menu.UseKeysUpDownAsKeysLeftRight;
+					}) {
 					CheckType = MenuItemCheckStyle.Checked, Checked = useSubMenusSingleFrame
 				},
 				miHeightAsBuffer = new MenuItem ("_Height As Buffer", "", () => {

+ 40 - 36
README.md

@@ -10,47 +10,50 @@
 
 A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.
 
-![Sample app](https://raw.githubusercontent.com/migueldeicaza/gui.cs/master/docfx/sample.gif)
+![Sample app](https://raw.githubusercontent.com/migueldeicaza/gui.cs/master/docfx/images/sample.gif)
 
 ## Controls and Views
 
 *Terminal.Gui* provides a rich set of views and controls for building terminal user interfaces:
 
-* [Button](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Button.html) 
-* [CheckBox](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.CheckBox.html)
-* [ColorPicker](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ColorPicker.html)
-* [ComboBox](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ComboBox.html)
-* [Dialog](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Dialog.html)
-  * [OpenDialog](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.OpenDialog.html)
-  * [SaveDialog](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.SaveDialog.html)
-* [FrameView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.FrameView.html)
-* [GraphView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.GraphView.html)
-* [Hex viewer/editor](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.HexView.html)
-* [Label](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Label.html)
-* [ListView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ListView.html)
-* [Menu](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MenuBar.html)
-* [MessageBox](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MessageBox.html)
-* [ProgressBar](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ProgressBar.html)
-* [Radio buttons](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.RadioGroup.html)
-* [TableView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TableView.html)
-* [Time & Date Fields](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TimeField.html)
-* [TextField](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextField.html)
-* [TextValidateField](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextValidateField.html)
-* [TextView (Text Editor)](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextView.html)
-* [TreeView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TreeView.html)
-* [ScrollView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ScrollView.html)
-* [ScrollBarView](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ScrollBarView.html)
-* [StatusBar](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.StatusBar.html)
-* [Window](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Window.html)
+* [Button](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Button.html) - A View that provides an item that invokes an System.Action when activated by the user.
+* [CheckBox](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.CheckBox.html) - Shows an on/off toggle that the user can set.
+* [ColorPicker](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.ColorPicker.html) - Enables to user to pick a color.
+* [ComboBox](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.ComboBox.html) - Provides a drop-down list of items the user can select from.
+* [Dialog](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Dialog.html) - A pop-up Window that contains one or more Buttons.
+  * [OpenDialog](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.OpenDialog.html) - A Dialog providing an interactive pop-up Window for users to select files or directories.
+  * [SaveDialog](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.SaveDialog.html) - A Dialog providing an interactive pop-up Window for users to save files.
+* [FrameView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.FrameView.html) - A container View that draws a frame around its contents. Similar to a GroupBox in Windows.
+* [GraphView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.GraphView.html) - A View for rendering graphs (bar, scatter etc).
+* [Hex viewer/editor](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.HexView.html) - A hex viewer and editor that operates over a file stream. 
+* [Label](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Label.html) - Displays a string at a given position and supports multiple lines.
+* [ListView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.ListView.html) - Displays a scrollable list of data where each item can be activated to perform an action.
+* [MenuBar](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MenuBar.html) - Provides a menu bar with drop-down and cascading menus.
+* [MessageBox](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MessageBox.html) - Displays a modal (pup-up) message to the user, with a title, a message and a series of options that the user can choose from. 
+* [ProgressBar](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.ProgressBar.html) - Displays a progress Bar indicating progress of an activity.
+* [RadioGroup](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.RadioGroup.html) - Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time
+* [ScrollView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.ScrollView.html) - Present a window into a virtual space where subviews are added. Similar to the iOS UIScrollView.
+* [ScrollBarView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.ScrollBarView.html) - display a 1-character scrollbar, either horizontal or vertical.
+* [StatusBar](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.StatusBar.html) - A View that snaps to the bottom of a Toplevel displaying set of status items. Includes support for global app keyboard shortcuts.
+* [TableView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TableView.html) - A View for tabular data based on a System.Data.DataTable. 
+* [TimeField](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TimeField.html) & [DateField](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TimeField.html) - Enables structured editing of dates and times.
+* [TextField](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TextField.html) - Provides a single-line text entry.
+* [TextValidateField](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TextValidateField.html) - Text field that validates input through a ITextValidateProvider.
+* [TextView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TextView.html)- A multi-line text editing View supporting word-wrap, auto-complete, context menus, undo/redo, and clipboard operations, 
+* [TopLevel](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Toplevel.html) - The base class for modal/pop-up Windows.
+* [TreeView](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.TreeView.html) - A hierarchical tree view with expandable branches. Branch objects are dynamically determined when expanded using a user defined ITreeBuilder.
+* [View](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.View.html) - The base class for all views on the screen and represents a visible element that can render itself and contains zero or more nested views.
+* [Window](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Window.html) - A Toplevel view that draws a border around its Frame with a title at the top.
+* [Wizard](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Wizard.html) - Provides navigation and a user interface to collect related data across multiple steps.
 
 ### Features
 
 * **Cross Platform** - Windows, Mac, and Linux. Terminal drivers for Curses, [Windows Console](https://github.com/migueldeicaza/gui.cs/issues/27), and the .NET Console mean apps will work well on both color and monochrome terminals. 
 * **Keyboard and Mouse Input** - Both keyboard and mouse input are supported, including support for drag & drop.
-* **[Flexible Layout](https://migueldeicaza.github.io/gui.cs/articles/overview.html#layout)** - Supports both *Absolute layout* and an innovative *Computed Layout* system. *Computed Layout* makes it easy to layout controls relative to each other and enables dynamic terminal UIs.
-* **Clipboard support** - Cut, Copy, and Paste of text provided through the [`Clipboard`](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Clipboard.html) class.
-* **[Arbitrary Views](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.View.html)** - All visible UI elements are subclasses of the `View` class, and these in turn can contain an arbitrary number of sub-views.
-* **Advanced App Features** - The [Mainloop](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file
+* **[Flexible Layout](https://gui-cs.github.io/gui.cs/articles/overview.html#layout)** - Supports both *Absolute layout* and an innovative *Computed Layout* system. *Computed Layout* makes it easy to layout controls relative to each other and enables dynamic terminal UIs.
+* **Clipboard support** - Cut, Copy, and Paste of text provided through the [`Clipboard`](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.Clipboard.html) class.
+* **[Arbitrary Views](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.View.html)** - All visible UI elements are subclasses of the `View` class, and these in turn can contain an arbitrary number of sub-views.
+* **Advanced App Features** - The [Mainloop](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file
 descriptors. Most classes are safe for threading.
 * **Reactive Extensions** - Use [reactive extensions](https://github.com/dotnet/reactive) and benefit from increased code readability, and the ability to apply the MVVM pattern and [ReactiveUI](https://www.reactiveui.net/) data bindings. See the [source code](https://github.com/migueldeicaza/gui.cs/tree/master/ReactiveExample) of a sample app in order to learn how to achieve this.
 
@@ -85,14 +88,15 @@ You can force the use of `System.Console` on Unix as well; see `Core.cs`.
 * **[F# Example](https://github.com/migueldeicaza/gui.cs/tree/master/FSharpExample)** - An example showing how to build a Terminal.Gui app using F#.
 * **[PowerShell's `Out-ConsoleGridView`](https://github.com/PowerShell/GraphicalTools/blob/master/docs/Microsoft.PowerShell.ConsoleGuiTools/Out-ConsoleGridView.md)** - `OCGV` sends the output from a command to  an interactive table. 
 * **[PoshRedisViewer](https://github.com/En3Tho/PoshRedisViewer)** - A compact Redis viewer module for PowerShell written in F# and Gui.cs
+* **[TerminalGuiDesigner](https://github.com/tznind/TerminalGuiDesigner)** - Cross platform view designer for building Terminal.Gui applications.
 
 ## Documentation
 
-* [Overview](https://migueldeicaza.github.io/gui.cs/articles/overview.html)
-* [Conceptual Documentation](https://migueldeicaza.github.io/gui.cs/articles/index.html)
-* [API Documentation](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.html)
+* [Overview](https://gui-cs.github.io/gui.cs/articles/overview.html)
+* [Conceptual Documentation](https://gui-cs.github.io/gui.cs/articles/index.html)
+* [API Documentation](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui/Terminal.Gui.html)
 
-See the [`Terminal.Gui/` README](https://github.com/migueldeicaza/gui.cs/tree/master/Terminal.Gui) for an overview of how the library is structured. The [Conceptual Documentation](https://migueldeicaza.github.io/gui.cs/articles/index.html) provides insight into core concepts.
+See the [`Terminal.Gui/` README](https://github.com/migueldeicaza/gui.cs/tree/master/Terminal.Gui) for an overview of how the library is structured. The [Conceptual Documentation](https://gui-cs.github.io/gui.cs/articles/index.html) provides insight into core concepts.
 
 ### Sample Usage
 (This code uses C# 9.0 [Top-level statements](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#top-level-statements).) 
@@ -174,7 +178,7 @@ Application.Run();
 Application.Shutdown();
 ```
 
-The example above shows adding views using both styles of layout supported by **Terminal.Gui**: **Absolute layout** and **[Computed layout](https://migueldeicaza.github.io/gui.cs/articles/overview.html#layout)**.
+The example above shows adding views using both styles of layout supported by **Terminal.Gui**: **Absolute layout** and **[Computed layout](https://gui-cs.github.io/gui.cs/articles/overview.html#layout)**.
 
 Alternatively, you can encapsulate the app behavior in a new `Window`-derived class, say `App.cs` containing the code above, and simplify your `Main` method to:
 

+ 1 - 1
ReactiveExample/ReactiveExample.csproj

@@ -12,7 +12,7 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
-    <PackageReference Include="ReactiveUI" Version="18.0.10" />
+    <PackageReference Include="ReactiveUI" Version="18.2.5" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.1.4" PrivateAssets="all" />
   </ItemGroup>
   <ItemGroup>

+ 36 - 30
Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

@@ -30,7 +30,7 @@ namespace Terminal.Gui {
 		IClipboard clipboard;
 		int [,,] contents;
 
-		internal override int [,,] Contents => contents;
+		public override int [,,] Contents => contents;
 
 		// Current row, and current col, tracked by Move/AddRune only
 		int ccol, crow;
@@ -119,8 +119,13 @@ namespace Terminal.Gui {
 			Curses.raw ();
 			Curses.noecho ();
 			Curses.refresh ();
+			ProcessWinChange ();
+		}
+
+		private void ProcessWinChange ()
+		{
 			if (Curses.CheckWinChange ()) {
-				Clip = new Rect (0, 0, Cols, Rows);
+				ResizeScreen ();
 				UpdateOffScreen ();
 				TerminalResized?.Invoke ();
 			}
@@ -382,18 +387,16 @@ namespace Terminal.Gui {
 
 				if (cev.ButtonState == Curses.Event.ReportMousePosition) {
 					mouseFlag = MapCursesButton ((Curses.Event)lastMouseButtonPressed) | MouseFlags.ReportMousePosition;
-					point = new Point ();
 					cancelButtonClicked = true;
-				} else {
-					point = new Point () {
-						X = cev.X,
-						Y = cev.Y
-					};
 				}
+				point = new Point () {
+					X = cev.X,
+					Y = cev.Y
+				};
 
 				if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) {
 					Application.MainLoop.AddIdle (() => {
-						Task.Run (async () => await ProcessContinuousButtonPressedAsync (cev, mouseFlag));
+						Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag));
 						return false;
 					});
 				}
@@ -480,18 +483,17 @@ namespace Terminal.Gui {
 			return mf;
 		}
 
-		async Task ProcessContinuousButtonPressedAsync (Curses.MouseEvent cev, MouseFlags mouseFlag)
+		async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
 		{
-			await Task.Delay (200);
-			while (isButtonPressed && lastMouseButtonPressed != null) {
+			while (isButtonPressed) {
 				await Task.Delay (100);
 				var me = new MouseEvent () {
-					X = cev.X,
-					Y = cev.Y,
+					X = point.X,
+					Y = point.Y,
 					Flags = mouseFlag
 				};
 
-				var view = Application.wantContinuousButtonPressedView;
+				var view = Application.WantContinuousButtonPressedView;
 				if (view == null)
 					break;
 				if (isButtonPressed && lastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) {
@@ -628,10 +630,7 @@ namespace Terminal.Gui {
 
 			if (code == Curses.KEY_CODE_YES) {
 				if (wch == Curses.KeyResize) {
-					if (Curses.CheckWinChange ()) {
-						TerminalResized?.Invoke ();
-						return;
-					}
+					ProcessWinChange ();
 				}
 				if (wch == Curses.KeyMouse) {
 					Curses.getmouse (out Curses.MouseEvent ev);
@@ -805,11 +804,7 @@ namespace Terminal.Gui {
 			});
 
 			mLoop.WinChanged += () => {
-				if (Curses.CheckWinChange ()) {
-					Clip = new Rect (0, 0, Cols, Rows);
-					UpdateOffScreen ();
-					TerminalResized?.Invoke ();
-				}
+				ProcessWinChange ();
 			};
 		}
 
@@ -826,7 +821,7 @@ namespace Terminal.Gui {
 
 				window = Curses.initscr ();
 			} catch (Exception e) {
-				Console.WriteLine ("Curses failed to initialize, the exception is: " + e);
+				throw new Exception ($"Curses failed to initialize, the exception is: {e.Message}");
 			}
 
 			// Ensures that all procedures are performed at some previous closing.
@@ -874,6 +869,9 @@ namespace Terminal.Gui {
 			if (reportableMouseEvents.HasFlag (Curses.Event.ReportMousePosition))
 				StartReportingMouseMoves ();
 
+			ResizeScreen ();
+			UpdateOffScreen ();
+
 			//HLine = Curses.ACS_HLINE;
 			//VLine = Curses.ACS_VLINE;
 			//Stipple = Curses.ACS_CKBOARD;
@@ -896,8 +894,7 @@ namespace Terminal.Gui {
 			Colors.Dialog = new ColorScheme ();
 			Colors.Menu = new ColorScheme ();
 			Colors.Error = new ColorScheme ();
-			Clip = new Rect (0, 0, Cols, Rows);
-			UpdateOffScreen ();
+
 			if (Curses.HasColors) {
 				Curses.StartColor ();
 				Curses.UseDefaultColors ();
@@ -965,6 +962,13 @@ namespace Terminal.Gui {
 			}
 		}
 
+		public override void ResizeScreen ()
+		{
+			Clip = new Rect (0, 0, Cols, Rows);
+			Console.Out.Write ("\x1b[3J");
+			Console.Out.Flush ();
+		}
+
 		public override void UpdateOffScreen ()
 		{
 			contents = new int [Rows, Cols, 3];
@@ -982,6 +986,9 @@ namespace Terminal.Gui {
 
 		public static bool Is_WSL_Platform ()
 		{
+			if (new CursesClipboard ().IsSupported) {
+				return false;
+			}
 			var result = BashRunner.Run ("uname -a", runCurses: false);
 			if (result.Contains ("microsoft") && result.Contains ("WSL")) {
 				return true;
@@ -1357,6 +1364,7 @@ namespace Terminal.Gui {
 						FileName = "bash",
 						Arguments = arguments,
 						RedirectStandardInput = true,
+						RedirectStandardError = true,
 						UseShellExecute = false,
 						CreateNoWindow = false
 					}
@@ -1495,14 +1503,13 @@ namespace Terminal.Gui {
 					RedirectStandardOutput = true,
 					FileName = "powershell.exe",
 					Arguments = "-noprofile -command \"Get-Clipboard\"",
-					UseShellExecute = Application.Driver is CursesDriver,
+					UseShellExecute = false,
 					CreateNoWindow = true
 				}
 			}) {
 				powershell.Start ();
 				var result = powershell.StandardOutput.ReadToEnd ();
 				powershell.StandardOutput.Close ();
-				powershell.WaitForExit ();
 				if (!powershell.DoubleWaitForExit ()) {
 					var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}.
 							Output: {powershell.StandardOutput.ReadToEnd ()}
@@ -1529,7 +1536,6 @@ namespace Terminal.Gui {
 				}
 			}) {
 				powershell.Start ();
-				powershell.WaitForExit ();
 				if (!powershell.DoubleWaitForExit ()) {
 					var timeoutError = $@"Process timed out. Command line: bash {powershell.StartInfo.Arguments}.
 							Output: {powershell.StandardOutput.ReadToEnd ()}

+ 2 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/UnixMainLoop.cs

@@ -174,12 +174,12 @@ namespace Terminal.Gui {
 
 		bool IMainLoopDriver.EventsPending (bool wait)
 		{
+			UpdatePollMap ();
+
 			if (CheckTimers (wait, out var pollTimeout)) {
 				return true;
 			}
 
-			UpdatePollMap ();
-
 			var n = poll (pollmap, (uint)pollmap.Length, pollTimeout);
 
 			if (n == KEY_RESIZE) {

+ 7 - 2
Terminal.Gui/ConsoleDrivers/CursesDriver/UnmanagedLibrary.cs

@@ -117,7 +117,7 @@ namespace Unix.Terminal {
 			}
 
 			if (this.handle == IntPtr.Zero) {
-				throw new IOException (string.Format ("Error loading native library \"{0}\"", this.libraryPath));
+				throw new IOException ($"Error loading native library \"{string.Join (", ", libraryPathAlternatives)}\"");
 			}
 		}
 
@@ -186,7 +186,12 @@ namespace Unix.Terminal {
 					return Mono.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
 				}
 				if (IsNetCore) {
-					return CoreCLR.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+					try {
+						return CoreCLR.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
+					} catch (Exception) {
+
+						IsNetCore = false;
+					}
 				}
 				return Linux.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
 			}

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

@@ -35,7 +35,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Assists with testing, the format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
 		/// </summary>
-		internal override int [,,] Contents => contents;
+		public override int [,,] Contents => contents;
 
 		//void UpdateOffscreen ()
 		//{
@@ -523,7 +523,7 @@ namespace Terminal.Gui {
 			TerminalResized?.Invoke ();
 		}
 
-		void ResizeScreen ()
+		public override void ResizeScreen ()
 		{
 			if (!HeightAsBuffer) {
 				if (FakeConsole.WindowHeight > 0) {

+ 21 - 6
Terminal.Gui/ConsoleDrivers/FakeDriver/FakeMainLoop.cs

@@ -58,24 +58,39 @@ namespace Terminal.Gui {
 		}
 
 		bool IMainLoopDriver.EventsPending (bool wait)
+		{
+			keyResult = null;
+			waitForProbe.Set ();
+
+			if (CheckTimers (wait, out var waitTimeout)) {
+				return true;
+			}
+
+			keyReady.WaitOne (waitTimeout);
+			return keyResult.HasValue;
+		}
+
+		bool CheckTimers (bool wait, out int waitTimeout)
 		{
 			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
+			} else {
 				waitTimeout = -1;
+			}
 
 			if (!wait)
 				waitTimeout = 0;
 
-			keyResult = null;
-			waitForProbe.Set ();
-			keyReady.WaitOne (waitTimeout);
-			return keyResult.HasValue;
+			int ic;
+			lock (mainLoop.idleHandlers) {
+				ic = mainLoop.idleHandlers.Count;
+			}
+
+			return ic > 0;
 		}
 
 		void IMainLoopDriver.MainIteration ()

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

@@ -947,7 +947,7 @@ namespace Terminal.Gui {
 			await Task.Delay (200);
 			while (isButtonPressed) {
 				await Task.Delay (100);
-				var view = Application.wantContinuousButtonPressedView;
+				var view = Application.WantContinuousButtonPressedView;
 				if (view == null) {
 					break;
 				}
@@ -1187,7 +1187,7 @@ namespace Terminal.Gui {
 		public NetWinVTConsole NetWinConsole { get; }
 		public bool IsWinPlatform { get; }
 		public override IClipboard Clipboard { get; }
-		internal override int [,,] Contents => contents;
+		public override int [,,] Contents => contents;
 
 		int largestWindowHeight;
 
@@ -1381,7 +1381,7 @@ namespace Terminal.Gui {
 			Clear ();
 		}
 
-		void ResizeScreen ()
+		public override void ResizeScreen ()
 		{
 			if (!HeightAsBuffer) {
 				if (Console.WindowHeight > 0) {
@@ -1423,6 +1423,8 @@ namespace Terminal.Gui {
 				}
 			}
 			Clip = new Rect (0, 0, Cols, Rows);
+			Console.Out.Write ("\x1b[3J");
+			Console.Out.Flush ();
 		}
 
 		public override void UpdateOffScreen ()
@@ -1442,8 +1444,6 @@ namespace Terminal.Gui {
 						}
 					}
 				} catch (IndexOutOfRangeException) { }
-
-				winChanging = false;
 			}
 		}
 
@@ -1462,7 +1462,7 @@ namespace Terminal.Gui {
 
 		public override void UpdateScreen ()
 		{
-			if (winChanging || Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
+			if (Console.WindowHeight == 0 || contents.Length != Rows * Cols * 3
 				|| (!HeightAsBuffer && Rows != Console.WindowHeight)
 				|| (HeightAsBuffer && Rows != largestWindowHeight)) {
 				return;
@@ -1815,11 +1815,8 @@ namespace Terminal.Gui {
 			}
 		}
 
-		bool winChanging;
-
 		void ChangeWin ()
 		{
-			winChanging = true;
 			const int Min_WindowWidth = 14;
 			Size size = new Size ();
 			if (!HeightAsBuffer) {
@@ -1836,9 +1833,7 @@ namespace Terminal.Gui {
 			rows = size.Height;
 			ResizeScreen ();
 			UpdateOffScreen ();
-			if (!winChanging) {
-				TerminalResized.Invoke ();
-			}
+			TerminalResized?.Invoke ();
 		}
 
 		MouseEvent ToDriverMouse (NetEvents.MouseEvent me)

+ 36 - 24
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -732,7 +732,7 @@ namespace Terminal.Gui {
 		public override int Top => top;
 		public override bool HeightAsBuffer { get; set; }
 		public override IClipboard Clipboard => clipboard;
-		internal override int [,,] Contents => contents;
+		public override int [,,] Contents => contents;
 
 		public WindowsConsole WinConsole { get; private set; }
 
@@ -876,7 +876,6 @@ namespace Terminal.Gui {
 							Y = me.Y,
 							Flags = ProcessButtonClick (inputEvent.MouseEvent)
 						});
-					processButtonClick = false;
 				}
 				break;
 
@@ -901,7 +900,8 @@ namespace Terminal.Gui {
 		bool isButtonPressed = false;
 		bool isButtonReleased = false;
 		bool isButtonDoubleClicked = false;
-		Point point;
+		Point? point;
+		Point pointMove;
 		//int buttonPressedCount;
 		bool isOneFingerDoubleClicked = false;
 		bool processButtonClick;
@@ -910,7 +910,8 @@ namespace Terminal.Gui {
 		{
 			MouseFlags mouseFlag = MouseFlags.AllEvents;
 
-			//System.Diagnostics.Debug.WriteLine ($"ButtonState: {mouseEvent.ButtonState};EventFlags: {mouseEvent.EventFlags}");
+			//System.Diagnostics.Debug.WriteLine (
+			//	$"X:{mouseEvent.MousePosition.X};Y:{mouseEvent.MousePosition.Y};ButtonState:{mouseEvent.ButtonState};EventFlags:{mouseEvent.EventFlags}");
 
 			if (isButtonDoubleClicked || isOneFingerDoubleClicked) {
 				Application.MainLoop.AddIdle (() => {
@@ -996,7 +997,7 @@ namespace Terminal.Gui {
 
 			//}
 			if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) ||
-				 (lastMouseButtonPressed == null && mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved &&
+				 (lastMouseButtonPressed == null && mouseEvent.EventFlags.HasFlag (WindowsConsole.EventFlags.MouseMoved) &&
 				 mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) {
 				switch (mouseEvent.ButtonState) {
 				case WindowsConsole.ButtonState.Button1Pressed:
@@ -1012,23 +1013,20 @@ namespace Terminal.Gui {
 					break;
 				}
 
+				if (point == null)
+					point = p;
+
 				if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) {
 					mouseFlag |= MouseFlags.ReportMousePosition;
-					point = new Point ();
 					isButtonReleased = false;
 					processButtonClick = false;
-				} else {
-					point = new Point () {
-						X = mouseEvent.MousePosition.X,
-						Y = mouseEvent.MousePosition.Y
-					};
 				}
 				lastMouseButtonPressed = mouseEvent.ButtonState;
 				isButtonPressed = true;
 
 				if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) {
 					Application.MainLoop.AddIdle (() => {
-						Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseEvent, mouseFlag));
+						Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseFlag));
 						return false;
 					});
 				}
@@ -1050,8 +1048,10 @@ namespace Terminal.Gui {
 				}
 				isButtonPressed = false;
 				isButtonReleased = true;
-				if (point.X == mouseEvent.MousePosition.X && point.Y == mouseEvent.MousePosition.Y) {
+				if (point != null && (((Point)point).X == mouseEvent.MousePosition.X && ((Point)point).Y == mouseEvent.MousePosition.Y)) {
 					processButtonClick = true;
+				} else {
+					point = null;
 				}
 			} else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved
 				&& !isOneFingerDoubleClicked && isButtonReleased && p == point) {
@@ -1123,9 +1123,9 @@ namespace Terminal.Gui {
 				}
 
 			} else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) {
-				if (mouseEvent.MousePosition.X != point.X || mouseEvent.MousePosition.Y != point.Y) {
+				if (mouseEvent.MousePosition.X != pointMove.X || mouseEvent.MousePosition.Y != pointMove.Y) {
 					mouseFlag = MouseFlags.ReportMousePosition;
-					point = new Point ();
+					pointMove = new Point ();
 				} else {
 					mouseFlag = 0;
 				}
@@ -1135,6 +1135,9 @@ namespace Terminal.Gui {
 
 			mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag);
 
+			//System.Diagnostics.Debug.WriteLine (
+			//	$"point.X:{(point != null ? ((Point)point).X : -1)};point.Y:{(point != null ? ((Point)point).Y : -1)}");
+
 			return new MouseEvent () {
 				X = mouseEvent.MousePosition.X,
 				Y = mouseEvent.MousePosition.Y,
@@ -1164,6 +1167,8 @@ namespace Terminal.Gui {
 			};
 			lastMouseButtonPressed = null;
 			isButtonReleased = false;
+			processButtonClick = false;
+			point = null;
 			return mouseFlag;
 		}
 
@@ -1175,18 +1180,17 @@ namespace Terminal.Gui {
 			//buttonPressedCount = 0;
 		}
 
-		async Task ProcessContinuousButtonPressedAsync (WindowsConsole.MouseEventRecord mouseEvent, MouseFlags mouseFlag)
+		async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
 		{
-			await Task.Delay (200);
 			while (isButtonPressed) {
 				await Task.Delay (100);
 				var me = new MouseEvent () {
-					X = mouseEvent.MousePosition.X,
-					Y = mouseEvent.MousePosition.Y,
+					X = pointMove.X,
+					Y = pointMove.Y,
 					Flags = mouseFlag
 				};
 
-				var view = Application.wantContinuousButtonPressedView;
+				var view = Application.WantContinuousButtonPressedView;
 				if (view == null) {
 					break;
 				}
@@ -1430,7 +1434,7 @@ namespace Terminal.Gui {
 			Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White);
 		}
 
-		void ResizeScreen ()
+		public override void ResizeScreen ()
 		{
 			OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols];
 			Clip = new Rect (0, 0, Cols, Rows);
@@ -1441,6 +1445,8 @@ namespace Terminal.Gui {
 				Right = (short)Cols
 			};
 			WinConsole.ForceRefreshCursorVisibility ();
+			Console.Out.Write ("\x1b[3J");
+			Console.Out.Flush ();
 		}
 
 		public override void UpdateOffScreen ()
@@ -1576,6 +1582,12 @@ namespace Terminal.Gui {
 			if (damageRegion.Left == -1)
 				return;
 
+			if (!HeightAsBuffer) {
+				var windowSize = WinConsole.GetConsoleBufferWindow (out _);
+				if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows))
+					return;
+			}
+
 			var bufferCoords = new WindowsConsole.Coord () {
 				X = (short)Clip.Width,
 				Y = (short)Clip.Height
@@ -1840,13 +1852,13 @@ namespace Terminal.Gui {
 
 		bool IMainLoopDriver.EventsPending (bool wait)
 		{
+			waitForProbe.Set ();
+			winChange.Set ();
+
 			if (CheckTimers (wait, out var waitTimeout)) {
 				return true;
 			}
 
-			waitForProbe.Set ();
-			winChange.Set ();
-
 			try {
 				if (!tokenSource.IsCancellationRequested) {
 					eventReady.Wait (waitTimeout, tokenSource.Token);

+ 118 - 57
Terminal.Gui/Core/Application.cs

@@ -105,6 +105,11 @@ namespace Terminal.Gui {
 		/// <value>The current.</value>
 		public static Toplevel Current { get; private set; }
 
+		/// <summary>
+		/// The current <see cref="View"/> object that wants continuous mouse button pressed events.
+		/// </summary>
+		public static View WantContinuousButtonPressedView { get; private set; }
+
 		/// <summary>
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// </summary>
@@ -210,6 +215,24 @@ namespace Terminal.Gui {
 		/// </summary>
 		public static bool IsMouseDisabled { get; set; }
 
+		/// <summary>
+		/// Set to true to cause the RunLoop method to exit after the first iterations.
+		/// Set to false (the default) to cause the RunLoop to continue running until Application.RequestStop() is called.
+		/// </summary>
+		public static bool ExitRunLoopAfterFirstIteration { get; set; } = false;
+
+		/// <summary>
+		/// Notify that a new <see cref="RunState"/> token was created,
+		/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
+		/// </summary>
+		public static event Action<RunState> NotifyNewRunState;
+
+		/// <summary>
+		/// Notify that a existent <see cref="RunState"/> token is stopping,
+		/// used if <see cref="ExitRunLoopAfterFirstIteration"/> is true.
+		/// </summary>
+		public static event Action<Toplevel> NotifyStopRunState;
+
 		/// <summary>
 		///   This event is raised on each iteration of the <see cref="MainLoop"/> 
 		/// </summary>
@@ -297,12 +320,12 @@ namespace Terminal.Gui {
 			}
 
 			// Used only for start debugging on Unix.
-//#if DEBUG
-//			while (!System.Diagnostics.Debugger.IsAttached) {
-//				System.Threading.Thread.Sleep (100);
-//			}
-//			System.Diagnostics.Debugger.Break ();
-//#endif
+			//#if DEBUG
+			//			while (!System.Diagnostics.Debugger.IsAttached) {
+			//				System.Threading.Thread.Sleep (100);
+			//			}
+			//			System.Diagnostics.Debugger.Break ();
+			//#endif
 
 			// Reset all class variables (Application is a singleton).
 			ResetState ();
@@ -352,7 +375,10 @@ namespace Terminal.Gui {
 			{
 				Toplevel = view;
 			}
-			internal Toplevel Toplevel;
+			/// <summary>
+			/// The <see cref="Toplevel"/> belong to this <see cref="RunState"/>.
+			/// </summary>
+			public Toplevel Toplevel { get; internal set; }
 
 			/// <summary>
 			/// Releases alTop = l resource used by the <see cref="Application.RunState"/> object.
@@ -385,7 +411,7 @@ namespace Terminal.Gui {
 
 		static void ProcessKeyEvent (KeyEvent ke)
 		{
-			if(RootKeyEvent?.Invoke(ke) ?? false) {
+			if (RootKeyEvent?.Invoke (ke) ?? false) {
 				return;
 			}
 
@@ -580,9 +606,8 @@ namespace Terminal.Gui {
 		/// </para>
 		/// <para>Return true to suppress the KeyPress event</para>
 		/// </summary>
-		public static Func<KeyEvent,bool> RootKeyEvent;
+		public static Func<KeyEvent, bool> RootKeyEvent;
 
-		internal static View wantContinuousButtonPressedView;
 		static View lastMouseOwnerView;
 
 		static void ProcessMouseEvent (MouseEvent me)
@@ -594,9 +619,9 @@ namespace Terminal.Gui {
 			var view = FindDeepestView (Current, me.X, me.Y, out int rx, out int ry);
 
 			if (view != null && view.WantContinuousButtonPressed)
-				wantContinuousButtonPressedView = view;
+				WantContinuousButtonPressedView = view;
 			else
-				wantContinuousButtonPressedView = null;
+				WantContinuousButtonPressedView = null;
 			if (view != null) {
 				me.View = view;
 			}
@@ -655,9 +680,9 @@ namespace Terminal.Gui {
 					return;
 
 				if (view.WantContinuousButtonPressed)
-					wantContinuousButtonPressedView = view;
+					WantContinuousButtonPressedView = view;
 				else
-					wantContinuousButtonPressedView = null;
+					WantContinuousButtonPressedView = null;
 
 				// Should we bubbled up the event, if it is not handled?
 				view.OnMouseEvent (nme);
@@ -868,6 +893,8 @@ namespace Terminal.Gui {
 			RootMouseEvent = null;
 			RootKeyEvent = null;
 			Resized = null;
+			NotifyNewRunState = null;
+			NotifyStopRunState = null;
 			_initialized = false;
 			mouseGrabView = null;
 
@@ -952,51 +979,65 @@ namespace Terminal.Gui {
 
 			bool firstIteration = true;
 			for (state.Toplevel.Running = true; state.Toplevel.Running;) {
-				if (MainLoop.EventsPending (wait)) {
-					// Notify Toplevel it's ready
-					if (firstIteration) {
-						state.Toplevel.OnReady ();
-					}
-					firstIteration = false;
-
-					MainLoop.MainIteration ();
-					Iteration?.Invoke ();
-
-					EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-					if ((state.Toplevel != Current && Current?.Modal == true)
-						|| (state.Toplevel != Current && Current?.Modal == false)) {
-						MdiTop?.OnDeactivate (state.Toplevel);
-						state.Toplevel = Current;
-						MdiTop?.OnActivate (state.Toplevel);
-						Top.SetChildNeedsDisplay ();
-						Refresh ();
-					}
-					if (Driver.EnsureCursorVisibility ()) {
-						state.Toplevel.SetNeedsDisplay ();
-					}
-				} else if (!wait) {
+				if (ExitRunLoopAfterFirstIteration && !firstIteration)
 					return;
+				RunMainLoopIteration (ref state, wait, ref firstIteration);
+			}
+		}
+
+		/// <summary>
+		/// Run one iteration of the MainLoop.
+		/// </summary>
+		/// <param name="state">The state returned by the Begin method.</param>
+		/// <param name="wait">If will execute the runloop waiting for events.</param>
+		/// <param name="firstIteration">If it's the first run loop iteration.</param>
+		public static void RunMainLoopIteration (ref RunState state, bool wait, ref bool firstIteration)
+		{
+			if (MainLoop.EventsPending (wait)) {
+				// Notify Toplevel it's ready
+				if (firstIteration) {
+					state.Toplevel.OnReady ();
 				}
-				if (state.Toplevel != Top
-					&& (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
-					Top.Redraw (Top.Bounds);
-					state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
+
+				MainLoop.MainIteration ();
+				Iteration?.Invoke ();
+
+				EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
+				if ((state.Toplevel != Current && Current?.Modal == true)
+					|| (state.Toplevel != Current && Current?.Modal == false)) {
+					MdiTop?.OnDeactivate (state.Toplevel);
+					state.Toplevel = Current;
+					MdiTop?.OnActivate (state.Toplevel);
+					Top.SetChildNeedsDisplay ();
+					Refresh ();
 				}
-				if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded
-					|| MdiChildNeedsDisplay ()) {
-					state.Toplevel.Redraw (state.Toplevel.Bounds);
-					if (DebugDrawBounds) {
-						DrawBounds (state.Toplevel);
-					}
-					state.Toplevel.PositionCursor ();
-					Driver.Refresh ();
-				} else {
-					Driver.UpdateCursor ();
+				if (Driver.EnsureCursorVisibility ()) {
+					state.Toplevel.SetNeedsDisplay ();
 				}
-				if (state.Toplevel != Top && !state.Toplevel.Modal
-					&& (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
-					Top.Redraw (Top.Bounds);
+			} else if (!wait) {
+				return;
+			}
+			firstIteration = false;
+
+			if (state.Toplevel != Top
+				&& (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
+				Top.Redraw (Top.Bounds);
+				state.Toplevel.SetNeedsDisplay (state.Toplevel.Bounds);
+			}
+			if (!state.Toplevel.NeedDisplay.IsEmpty || state.Toplevel.ChildNeedsDisplay || state.Toplevel.LayoutNeeded
+				|| MdiChildNeedsDisplay ()) {
+				state.Toplevel.Redraw (state.Toplevel.Bounds);
+				if (DebugDrawBounds) {
+					DrawBounds (state.Toplevel);
 				}
+				state.Toplevel.PositionCursor ();
+				Driver.Refresh ();
+			} else {
+				Driver.UpdateCursor ();
+			}
+			if (state.Toplevel != Top && !state.Toplevel.Modal
+				&& (!Top.NeedDisplay.IsEmpty || Top.ChildNeedsDisplay || Top.LayoutNeeded)) {
+				Top.Redraw (Top.Bounds);
 			}
 		}
 
@@ -1058,8 +1099,12 @@ namespace Terminal.Gui {
 		{
 			if (_initialized && Driver != null) {
 				var top = new T ();
-				if (top.GetType ().BaseType != typeof (Toplevel)) {
-					throw new ArgumentException (top.GetType ().BaseType.Name);
+				var type = top.GetType ().BaseType;
+				while (type != typeof (Toplevel) && type != typeof (object)) {
+					type = type.BaseType;
+				}
+				if (type != typeof (Toplevel)) {
+					throw new ArgumentException ($"{top.GetType ().Name} must be derived from TopLevel");
 				}
 				Run (top, errorHandler);
 			} else {
@@ -1107,7 +1152,12 @@ namespace Terminal.Gui {
 				resume = false;
 				var runToken = Begin (view);
 				RunLoop (runToken);
-				End (runToken);
+				if (!ExitRunLoopAfterFirstIteration)
+					End (runToken);
+				else
+					// If ExitRunLoopAfterFirstIteration is true then the user must deal his disposing when it ends
+					// by using NotifyStopRunState event.
+					NotifyNewRunState?.Invoke (runToken);
 #if !DEBUG
 				}
 				catch (Exception error)
@@ -1158,7 +1208,9 @@ namespace Terminal.Gui {
 					return;
 				}
 				Current.Running = false;
+				OnNotifyStopRunState (Current);
 				top.Running = false;
+				OnNotifyStopRunState (top);
 			} else if ((MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
 				&& Current?.Running == true && !top.Running)
 				|| (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
@@ -1169,11 +1221,13 @@ namespace Terminal.Gui {
 				&& Current?.Modal == true && top.Modal) {
 				// The Current and the top are both modal so needed to set the Current.Running to false too.
 				Current.Running = false;
+				OnNotifyStopRunState (Current);
 			} else if (MdiTop != null && Current == top && MdiTop?.Running == true && Current?.Running == true && top.Running
 				&& Current?.Modal == true && top.Modal) {
 				// The MdiTop was requested to stop inside a modal toplevel which is the Current and top,
 				// both are the same, so needed to set the Current.Running to false too.
 				Current.Running = false;
+				OnNotifyStopRunState (Current);
 			} else {
 				Toplevel currentTop;
 				if (top == Current || (Current?.Modal == true && !top.Modal)) {
@@ -1190,9 +1244,16 @@ namespace Terminal.Gui {
 					return;
 				}
 				currentTop.Running = false;
+				OnNotifyStopRunState (currentTop);
 			}
 		}
 
+		static void OnNotifyStopRunState (Toplevel top)
+		{
+			if (ExitRunLoopAfterFirstIteration)
+				NotifyStopRunState?.Invoke (top);
+		}
+
 		/// <summary>
 		/// Event arguments for the <see cref="Application.Resized"/> event.
 		/// </summary>

+ 58 - 44
Terminal.Gui/Core/Border.cs

@@ -173,26 +173,30 @@ namespace Terminal.Gui {
 				} else {
 					Border = border;
 				}
+				AdjustContentView (frame);
 			}
 
 			void AdjustContentView (Rect frame)
 			{
 				var borderLength = Border.DrawMarginFrame ? 1 : 0;
 				var sumPadding = Border.GetSumThickness ();
+				var wp = new Point ();
 				var wb = new Size ();
 				if (frame == Rect.Empty) {
+					wp.X = borderLength + sumPadding.Left;
+					wp.Y = borderLength + sumPadding.Top;
 					wb.Width = borderLength + sumPadding.Right;
 					wb.Height = borderLength + sumPadding.Bottom;
 					if (Border.Child == null) {
 						Border.Child = new ChildContentView (this) {
-							X = borderLength + sumPadding.Left,
-							Y = borderLength + sumPadding.Top,
+							X = wp.X,
+							Y = wp.Y,
 							Width = Dim.Fill (wb.Width),
 							Height = Dim.Fill (wb.Height)
 						};
 					} else {
-						Border.Child.X = borderLength + sumPadding.Left;
-						Border.Child.Y = borderLength + sumPadding.Top;
+						Border.Child.X = wp.X;
+						Border.Child.Y = wp.Y;
 						Border.Child.Width = Dim.Fill (wb.Width);
 						Border.Child.Height = Dim.Fill (wb.Height);
 					}
@@ -206,7 +210,8 @@ namespace Terminal.Gui {
 						Border.Child.Frame = cFrame;
 					}
 				}
-				base.Add (Border.Child);
+				if (Subviews?.Count == 0)
+					base.Add (Border.Child);
 				Border.ChildContainer = this;
 			}
 
@@ -248,7 +253,7 @@ namespace Terminal.Gui {
 			{
 				if (!NeedDisplay.IsEmpty) {
 					Driver.SetAttribute (GetNormalColor ());
-					Border.DrawContent ();
+					Clear ();
 				}
 				var savedClip = Border.Child.ClipToBounds ();
 				Border.Child.Redraw (Border.Child.Bounds);
@@ -257,10 +262,13 @@ namespace Terminal.Gui {
 				ClearLayoutNeeded ();
 				ClearNeedsDisplay ();
 
-				if (Border.BorderStyle != BorderStyle.None) {
-					Driver.SetAttribute (GetNormalColor ());
-					Border.DrawTitle (this, this.Frame);
-				}
+				Driver.SetAttribute (GetNormalColor ());
+				Border.DrawContent (this, false);
+				if (HasFocus)
+					Driver.SetAttribute (ColorScheme.HotNormal);
+				if (Border.DrawMarginFrame)
+					Border.DrawTitle (this, Frame);
+				Driver.SetAttribute (GetNormalColor ());
 
 				// Checks if there are any SuperView view which intersect with this window.
 				if (SuperView != null) {
@@ -540,24 +548,26 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 
 			// Draw margin frame
-			if (Parent?.Border != null) {
-				var sumPadding = GetSumThickness ();
-				borderRect = new Rect () {
-					X = scrRect.X + sumPadding.Left,
-					Y = scrRect.Y + sumPadding.Top,
-					Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0),
-					Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0)
-				};
-			} else {
-				borderRect = new Rect () {
-					X = borderRect.X + padding.Left,
-					Y = borderRect.Y + padding.Top,
-					Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0),
-					Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0)
-				};
-			}
-			if (borderRect.Width > 0 && borderRect.Height > 0) {
-				driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
+			if (DrawMarginFrame) {
+				if (Parent?.Border != null) {
+					var sumPadding = GetSumThickness ();
+					borderRect = new Rect () {
+						X = scrRect.X + sumPadding.Left,
+						Y = scrRect.Y + sumPadding.Top,
+						Width = Math.Max (scrRect.Width - sumPadding.Right - sumPadding.Left, 0),
+						Height = Math.Max (scrRect.Height - sumPadding.Bottom - sumPadding.Top, 0)
+					};
+				} else {
+					borderRect = new Rect () {
+						X = borderRect.X + padding.Left,
+						Y = borderRect.Y + padding.Top,
+						Width = Math.Max (borderRect.Width - padding.Right - padding.Left, 0),
+						Height = Math.Max (borderRect.Height - padding.Bottom - padding.Top, 0)
+					};
+				}
+				if (borderRect.Width > 0 && borderRect.Height > 0) {
+					driver.DrawWindowFrame (borderRect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill: true, this);
+				}
 			}
 		}
 
@@ -658,14 +668,16 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 
 			// Draw the MarginFrame
-			var rect = new Rect () {
-				X = frame.X - drawMarginFrame,
-				Y = frame.Y - drawMarginFrame,
-				Width = frame.Width + (2 * drawMarginFrame),
-				Height = frame.Height + (2 * drawMarginFrame)
-			};
-			if (rect.Width > 0 && rect.Height > 0) {
-				driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
+			if (DrawMarginFrame) {
+				var rect = new Rect () {
+					X = frame.X - drawMarginFrame,
+					Y = frame.Y - drawMarginFrame,
+					Width = frame.Width + (2 * drawMarginFrame),
+					Height = frame.Height + (2 * drawMarginFrame)
+				};
+				if (rect.Width > 0 && rect.Height > 0) {
+					driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
+				}
 			}
 
 			if (Effect3D) {
@@ -810,14 +822,16 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 
 			// Draw the MarginFrame
-			var rect = new Rect () {
-				X = frame.X + sumThickness.Left,
-				Y = frame.Y + sumThickness.Top,
-				Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0),
-				Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
-			};
-			if (rect.Width > 0 && rect.Height > 0) {
-				driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
+			if (DrawMarginFrame) {
+				var rect = new Rect () {
+					X = frame.X + sumThickness.Left,
+					Y = frame.Y + sumThickness.Top,
+					Width = Math.Max (frame.Width - sumThickness.Right - sumThickness.Left, 0),
+					Height = Math.Max (frame.Height - sumThickness.Bottom - sumThickness.Top, 0)
+				};
+				if (rect.Width > 0 && rect.Height > 0) {
+					driver.DrawWindowFrame (rect, 1, 1, 1, 1, BorderStyle != BorderStyle.None, fill, this);
+				}
 			}
 
 			if (Effect3D) {

+ 9 - 2
Terminal.Gui/Core/ConsoleDriver.cs

@@ -665,8 +665,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		public abstract bool HeightAsBuffer { get; set; }
 
-		// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
-		internal abstract int [,,] Contents { get; }
+		/// <summary>
+		/// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag
+		/// </summary>
+		public virtual int [,,] Contents { get; }
 
 		/// <summary>
 		/// Initializes the driver
@@ -769,6 +771,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public abstract void End ();
 
+		/// <summary>
+		/// Resizes the clip area when the screen is resized.
+		/// </summary>
+		public abstract void ResizeScreen ();
+
 		/// <summary>
 		/// Reset and recreate the contents and the driver buffer.
 		/// </summary>

+ 3 - 1
Terminal.Gui/Core/ContextMenu.cs

@@ -47,7 +47,9 @@ namespace Terminal.Gui {
 			Dispose ();
 		}
 
-		/// <inheritdoc/>
+		/// <summary>
+		/// Disposes the all the context menu objects instances.
+		/// </summary>
 		public void Dispose ()
 		{
 			if (IsShow) {

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

@@ -45,8 +45,17 @@ namespace Terminal.Gui {
 	///   does not seem to be a way of supporting this on Windows.
 	/// </remarks>
 	public class MainLoop {
-		internal class Timeout {
+		/// <summary>
+		/// Provides data for timers running manipulation.
+		/// </summary>
+		public sealed class Timeout {
+			/// <summary>
+			/// Time to wait before invoke the callback.
+			/// </summary>
 			public TimeSpan Span;
+			/// <summary>
+			/// The function that will be invoked.
+			/// </summary>
 			public Func<MainLoop, bool> Callback;
 		}
 
@@ -54,12 +63,30 @@ namespace Terminal.Gui {
 		object timeoutsLockToken = new object ();
 		internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
 
+		/// <summary>
+		/// Gets the list of all timeouts sorted by the <see cref="TimeSpan"/> time ticks./>.
+		/// A shorter limit time can be added at the end, but it will be called before an
+		///  earlier addition that has a longer limit time.
+		/// </summary>
+		public SortedList<long, Timeout> Timeouts => timeouts;
+
+		/// <summary>
+		/// Gets the list of all idle handlers.
+		/// </summary>
+		public List<Func<bool>> IdleHandlers => idleHandlers;
+
 		/// <summary>
 		/// The current IMainLoopDriver in use.
 		/// </summary>
 		/// <value>The driver.</value>
 		public IMainLoopDriver Driver { get; }
 
+		/// <summary>
+		/// Invoked when a new timeout is added to be used on the case
+		/// if <see cref="Application.ExitRunLoopAfterFirstIteration"/> is true,
+		/// </summary>
+		public event Action<long> TimeoutAdded;
+
 		/// <summary>
 		///  Creates a new Mainloop. 
 		/// </summary>
@@ -120,7 +147,8 @@ namespace Terminal.Gui {
 		{
 			lock (timeoutsLockToken) {
 				var k = (DateTime.UtcNow + time).Ticks;
-				timeouts.Add (NudgeToUniqueKey(k), timeout);
+				timeouts.Add (NudgeToUniqueKey (k), timeout);
+				TimeoutAdded?.Invoke (k);
 			}
 		}
 
@@ -188,11 +216,10 @@ namespace Terminal.Gui {
 						AddTimeout (timeout.Span, timeout);
 				} else {
 					lock (timeoutsLockToken) {
-						timeouts.Add (NudgeToUniqueKey(k), timeout);
+						timeouts.Add (NudgeToUniqueKey (k), timeout);
 					}
 				}
 			}
-			
 		}
 
 		/// <summary>
@@ -203,7 +230,7 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		private long NudgeToUniqueKey (long k)
 		{
-			lock(timeoutsLockToken) {
+			lock (timeoutsLockToken) {
 				while (timeouts.ContainsKey (k)) {
 					k++;
 				}

+ 97 - 15
Terminal.Gui/Core/PosDim.cs

@@ -36,6 +36,40 @@ namespace Terminal.Gui {
 			return 0;
 		}
 
+		// Helper class to provide dynamic value by the execution of a function that returns an integer.
+		internal class PosFunc : Pos {
+			Func<int> function;
+
+			public PosFunc (Func<int> n)
+			{
+				this.function = n;
+			}
+
+			internal override int Anchor (int width)
+			{
+				return function ();
+			}
+
+			public override string ToString ()
+			{
+				return $"Pos.PosFunc({function ()})";
+			}
+
+			public override int GetHashCode () => function.GetHashCode ();
+
+			public override bool Equals (object other) => other is PosFunc f && f.function () == function ();
+		}
+
+		/// <summary>
+		/// Creates a "PosFunc" from the specified function.
+		/// </summary>
+		/// <param name="function">The function to be executed.</param>
+		/// <returns>The <see cref="Pos"/> returned from the function.</returns>
+		public static Pos Function (Func<int> function)
+		{
+			return new PosFunc (function);
+		}
+
 		internal class PosFactor : Pos {
 			float factor;
 
@@ -53,6 +87,10 @@ namespace Terminal.Gui {
 			{
 				return $"Pos.Factor({factor})";
 			}
+
+			public override int GetHashCode () => factor.GetHashCode ();
+
+			public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
 		}
 
 		/// <summary>
@@ -182,7 +220,6 @@ namespace Terminal.Gui {
 			public override int GetHashCode () => n.GetHashCode ();
 
 			public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
-
 		}
 
 		/// <summary>
@@ -310,6 +347,10 @@ namespace Terminal.Gui {
 				}
 				return $"Pos.View(side={tside}, target={Target.ToString ()})";
 			}
+
+			public override int GetHashCode () => Target.GetHashCode ();
+
+			public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
 		}
 
 		/// <summary>
@@ -353,6 +394,16 @@ namespace Terminal.Gui {
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		public static Pos Bottom (View view) => new PosCombine (true, new PosView (view, 3), new Pos.PosAbsolute (0));
+
+		/// <summary>Serves as the default hash function. </summary>
+		/// <returns>A hash code for the current object.</returns>
+		public override int GetHashCode () => Anchor (0).GetHashCode ();
+
+		/// <summary>Determines whether the specified object is equal to the current object.</summary>
+		/// <param name="other">The object to compare with the current object. </param>
+		/// <returns>
+		///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
+		public override bool Equals (object other) => other is Pos abs && abs == this;
 	}
 
 	/// <summary>
@@ -375,6 +426,40 @@ namespace Terminal.Gui {
 			return 0;
 		}
 
+		// Helper class to provide dynamic value by the execution of a function that returns an integer.
+		internal class DimFunc : Dim {
+			Func<int> function;
+
+			public DimFunc (Func<int> n)
+			{
+				this.function = n;
+			}
+
+			internal override int Anchor (int width)
+			{
+				return function ();
+			}
+
+			public override string ToString ()
+			{
+				return $"Dim.DimFunc({function ()})";
+			}
+
+			public override int GetHashCode () => function.GetHashCode ();
+
+			public override bool Equals (object other) => other is DimFunc f && f.function () == function ();
+		}
+
+		/// <summary>
+		/// Creates a "DimFunc" from the specified function.
+		/// </summary>
+		/// <param name="function">The function to be executed.</param>
+		/// <returns>The <see cref="Dim"/> returned from the function.</returns>
+		public static Dim Function (Func<int> function)
+		{
+			return new DimFunc (function);
+		}
+
 		internal class DimFactor : Dim {
 			float factor;
 			bool remaining;
@@ -403,7 +488,6 @@ namespace Terminal.Gui {
 			public override int GetHashCode () => factor.GetHashCode ();
 
 			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
-
 		}
 
 		/// <summary>
@@ -449,7 +533,6 @@ namespace Terminal.Gui {
 			public override int GetHashCode () => n.GetHashCode ();
 
 			public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
-
 		}
 
 		internal class DimFill : Dim {
@@ -590,6 +673,16 @@ namespace Terminal.Gui {
 				this.side = side;
 			}
 
+			internal override int Anchor (int width)
+			{
+				switch (side) {
+				case 0: return Target.Frame.Height;
+				case 1: return Target.Frame.Width;
+				default:
+					return 0;
+				}
+			}
+
 			public override string ToString ()
 			{
 				string tside;
@@ -601,20 +694,9 @@ namespace Terminal.Gui {
 				return $"DimView(side={tside}, target={Target.ToString ()})";
 			}
 
-			internal override int Anchor (int width)
-			{
-				switch (side) {
-				case 0: return Target.Frame.Height;
-				case 1: return Target.Frame.Width;
-				default:
-					return 0;
-				}
-			}
-
 			public override int GetHashCode () => Target.GetHashCode ();
 
 			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
-
 		}
 		/// <summary>
 		/// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
@@ -632,7 +714,7 @@ namespace Terminal.Gui {
 
 		/// <summary>Serves as the default hash function. </summary>
 		/// <returns>A hash code for the current object.</returns>
-		public override int GetHashCode () => GetHashCode ();
+		public override int GetHashCode () => Anchor (0).GetHashCode ();
 
 		/// <summary>Determines whether the specified object is equal to the current object.</summary>
 		/// <param name="other">The object to compare with the current object. </param>

+ 76 - 31
Terminal.Gui/Core/TextFormatter.cs

@@ -136,7 +136,7 @@ namespace Terminal.Gui {
 			set {
 				text = value;
 
-				if (text.RuneCount > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != text.ConsoleWidth)) {
+				if (text != null && text.RuneCount > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != text.ConsoleWidth)) {
 					// Provide a default size (width = length of longest line, height = 1)
 					// TODO: It might makes more sense for the default to be width = length of first line?
 					Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
@@ -154,9 +154,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		public bool AutoSize { get; set; }
 
-		// TODO: Add Vertical Text Alignment
 		/// <summary>
-		/// Controls the horizontal text-alignment property. 
+		/// Gets or sets a flag that determines whether <see cref="Text"/> will have trailing spaces preserved
+		/// or not when <see cref="WordWrap"/> is enabled. If `true` any trailing spaces will be trimmed when
+		/// either the <see cref="Text"/> property is changed or when <see cref="WordWrap"/> is set to `true`.
+		/// The default is `false`.
+		/// </summary>
+		public bool PreserveTrailingSpaces { get; set; }
+
+		/// <summary>
+		/// Controls the horizontal text-alignment property.
 		/// </summary>
 		/// <value>The text alignment.</value>
 		public TextAlignment Alignment {
@@ -309,7 +316,7 @@ namespace Terminal.Gui {
 		public List<ustring> Lines {
 			get {
 				// With this check, we protect against subclasses with overrides of Text
-				if (ustring.IsNullOrEmpty (Text)) {
+				if (ustring.IsNullOrEmpty (Text) || Size.IsEmpty) {
 					lines = new List<ustring> ();
 					lines.Add (ustring.Empty);
 					NeedsFormat = false;
@@ -323,19 +330,20 @@ namespace Terminal.Gui {
 						shown_text = RemoveHotKeySpecifier (Text, hotKeyPos, HotKeySpecifier);
 						shown_text = ReplaceHotKeyWithTag (shown_text, hotKeyPos);
 					}
-					if (Size.IsEmpty) {
-						throw new InvalidOperationException ("Size must be set before accessing Lines");
-					}
 
 					if (IsVerticalDirection (textDirection)) {
-						lines = Format (shown_text, Size.Height, textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > 1,
-							false, 0, textDirection);
-						if (!AutoSize && lines.Count > Size.Width) {
-							lines.RemoveRange (Size.Width, lines.Count - Size.Width);
+						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1);
+						lines = Format (shown_text, Size.Height, textVerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth,
+							PreserveTrailingSpaces, 0, textDirection);
+						if (!AutoSize) {
+							colsWidth = GetMaxColsForWidth (lines, Size.Width);
+							if (lines.Count > colsWidth) {
+								lines.RemoveRange (colsWidth, lines.Count - colsWidth);
+							}
 						}
 					} else {
 						lines = Format (shown_text, Size.Width, textAlignment == TextAlignment.Justified, Size.Height > 1,
-							false, 0, textDirection);
+							PreserveTrailingSpaces, 0, textDirection);
 						if (!AutoSize && lines.Count > Size.Height) {
 							lines.RemoveRange (Size.Height, lines.Count - Size.Height);
 						}
@@ -465,7 +473,7 @@ namespace Terminal.Gui {
 			var runes = StripCRLF (text).ToRuneList ();
 			if (!preserveTrailingSpaces) {
 				if (IsHorizontalDirection (textDirection)) {
-					while ((end = start + GetMaxLengthForWidth (runes.GetRange (start, runes.Count - start), width)) < runes.Count) {
+					while ((end = start + Math.Max (GetMaxLengthForWidth (runes.GetRange (start, runes.Count - start), width), 1)) < runes.Count) {
 						while (runes [end] != ' ' && end > start)
 							end--;
 						if (end == start)
@@ -491,16 +499,25 @@ namespace Terminal.Gui {
 				}
 			} else {
 				while ((end = start) < runes.Count) {
-					end = GetNextWhiteSpace (start, width);
+					end = GetNextWhiteSpace (start, width, out bool incomplete);
+					if (end == 0 && incomplete) {
+						start = text.RuneCount;
+						break;
+					}
 					lines.Add (ustring.Make (runes.GetRange (start, end - start)));
 					start = end;
+					if (incomplete) {
+						start = text.RuneCount;
+						break;
+					}
 				}
 			}
 
-			int GetNextWhiteSpace (int from, int cWidth, int cLength = 0)
+			int GetNextWhiteSpace (int from, int cWidth, out bool incomplete, int cLength = 0)
 			{
 				var to = from;
 				var length = cLength;
+				incomplete = false;
 
 				while (length < cWidth && to < runes.Count) {
 					var rune = runes [to];
@@ -509,13 +526,19 @@ namespace Terminal.Gui {
 					} else {
 						length++;
 					}
+					if (length > cWidth) {
+						if (to >= runes.Count || (length > 1 && cWidth <= 1)) {
+							incomplete = true;
+						}
+						return to;
+					}
 					if (rune == ' ') {
 						if (length == cWidth) {
 							return to + 1;
 						} else if (length > cWidth) {
 							return to;
 						} else {
-							return GetNextWhiteSpace (to + 1, cWidth, length);
+							return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
 						}
 					} else if (rune == '\t') {
 						length += tabWidth + 1;
@@ -524,7 +547,7 @@ namespace Terminal.Gui {
 						} else if (length > cWidth && tabWidth > cWidth) {
 							return to;
 						} else {
-							return GetNextWhiteSpace (to + 1, cWidth, length);
+							return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
 						}
 					}
 					to++;
@@ -576,11 +599,15 @@ namespace Terminal.Gui {
 			var runes = text.ToRuneList ();
 			int slen = runes.Count;
 			if (slen > width) {
-				return ustring.Make (runes.GetRange (0, GetMaxLengthForWidth (text, width)));
+				if (IsHorizontalDirection (textDirection)) {
+					return ustring.Make (runes.GetRange (0, GetMaxLengthForWidth (text, width)));
+				} else {
+					return ustring.Make (runes.GetRange (0, width));
+				}
 			} else {
 				if (justify) {
 					return Justify (text, width, ' ', textDirection);
-				} else if (GetTextWidth (text) > width && IsHorizontalDirection (textDirection)) {
+				} else if (IsHorizontalDirection (textDirection) && GetTextWidth (text) > width) {
 					return ustring.Make (runes.GetRange (0, GetMaxLengthForWidth (text, width)));
 				}
 				return text;
@@ -687,9 +714,6 @@ namespace Terminal.Gui {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
 			}
-			if (preserveTrailingSpaces && !wordWrap) {
-				throw new ArgumentException ("if 'preserveTrailingSpaces' is true, then 'wordWrap' must be true either.");
-			}
 			List<ustring> lineResult = new List<ustring> ();
 
 			if (ustring.IsNullOrEmpty (text) || width == 0) {
@@ -846,6 +870,28 @@ namespace Terminal.Gui {
 			return runeIdx;
 		}
 
+		/// <summary>
+		/// Gets the index position from the list based on the <paramref name="width"/>.
+		/// </summary>
+		/// <param name="lines">The lines.</param>
+		/// <param name="width">The width.</param>
+		/// <returns>The index of the list that fit the width.</returns>
+		public static int GetMaxColsForWidth (List<ustring> lines, int width)
+		{
+			var runesLength = 0;
+			var lineIdx = 0;
+			for (; lineIdx < lines.Count; lineIdx++) {
+				var runes = lines [lineIdx].ToRuneList ();
+				var maxRruneWidth = runes.Count > 0
+					? runes.Max (r => Math.Max (Rune.ColumnWidth (r), 1)) : 1;
+				if (runesLength + maxRruneWidth > width) {
+					break;
+				}
+				runesLength += maxRruneWidth;
+			}
+			return lineIdx;
+		}
+
 		/// <summary>
 		///  Calculates the rectangle required to hold text, assuming no word wrapping.
 		/// </summary>
@@ -889,7 +935,7 @@ namespace Terminal.Gui {
 				w = mw;
 				h = ml;
 			} else {
-				int vw = 0;
+				int vw = 1, cw = 1;
 				int vh = 0;
 
 				int rows = 0;
@@ -900,14 +946,13 @@ namespace Terminal.Gui {
 							vh = rows;
 						}
 						rows = 0;
+						cw = 1;
 					} else if (rune != '\r') {
 						rows++;
 						var rw = Rune.ColumnWidth (rune);
-						if (rw < 0) {
-							rw++;
-						}
-						if (rw > vw) {
-							vw = rw;
+						if (cw < rw) {
+							cw = rw;
+							vw++;
 						}
 					}
 				}
@@ -1064,10 +1109,10 @@ namespace Terminal.Gui {
 				break;
 			}
 
-			for (int line = 0; line < linesFormated.Count; line++) {
-				var isVertical = IsVerticalDirection (textDirection);
+			var isVertical = IsVerticalDirection (textDirection);
 
-				if ((isVertical && (line > bounds.Width)) || (!isVertical && (line > bounds.Height)))
+			for (int line = 0; line < linesFormated.Count; line++) {
+				if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height))
 					continue;
 
 				var runes = lines [line].ToRunes ();

+ 83 - 56
Terminal.Gui/Core/Toplevel.cs

@@ -1,23 +1,18 @@
-//
-// Toplevel.cs: Toplevel views can be modally executed
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-using System;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Linq;
 
 namespace Terminal.Gui {
 	/// <summary>
-	/// Toplevel views can be modally executed.
+	/// Toplevel views can be modally executed. They are used for both an application's main view (filling the entire screeN and
+	/// for pop-up views such as <see cref="Dialog"/>, <see cref="MessageBox"/>, and <see cref="Wizard"/>.
 	/// </summary>
 	/// <remarks>
 	///   <para>
 	///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>. 
 	///     They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has 
-	///     been called (which sets the <see cref="Toplevel.Running"/> property to false). 
+	///     been called (which sets the <see cref="Toplevel.Running"/> property to <c>false</c>). 
 	///   </para>
 	///   <para>
 	///     A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
@@ -49,66 +44,72 @@ namespace Terminal.Gui {
 		public bool Running { get; set; }
 
 		/// <summary>
-		/// Fired once the Toplevel's <see cref="Application.RunState"/> has begin loaded.
-		/// A Loaded event handler is a good place to finalize initialization before calling `<see cref="Application.RunLoop(Application.RunState, bool)"/>.
+		/// Invoked when the Toplevel <see cref="Application.RunState"/> has begin loaded.
+		/// A Loaded event handler is a good place to finalize initialization before calling 
+		/// <see cref="Application.RunLoop(Application.RunState, bool)"/>.
 		/// </summary>
 		public event Action Loaded;
 
 		/// <summary>
-		/// Fired once the Toplevel's <see cref="MainLoop"/> has started it's first iteration.
+		/// Invoked when the Toplevel <see cref="MainLoop"/> has started it's first iteration.
 		/// Subscribe to this event to perform tasks when the <see cref="Toplevel"/> has been laid out and focus has been set.
-		/// changes. A Ready event handler is a good place to finalize initialization after calling `<see cref="Application.Run(Func{Exception, bool})"/>(topLevel)`.
+		/// changes. 
+		/// <para>A Ready event handler is a good place to finalize initialization after calling 
+		/// <see cref="Application.Run(Func{Exception, bool})"/> on this Toplevel.</para>
 		/// </summary>
 		public event Action Ready;
 
 		/// <summary>
-		/// Fired once the Toplevel's <see cref="Application.RunState"/> has begin unloaded.
-		/// A Unloaded event handler is a good place to disposing after calling `<see cref="Application.End(Application.RunState)"/>.
+		/// Invoked when the Toplevel <see cref="Application.RunState"/> has been unloaded.
+		/// A Unloaded event handler is a good place to dispose objects after calling <see cref="Application.End(Application.RunState)"/>.
 		/// </summary>
 		public event Action Unloaded;
 
 		/// <summary>
-		/// Invoked once the Toplevel's <see cref="Application.RunState"/> becomes the <see cref="Application.Current"/>.
+		/// Invoked when the Toplevel <see cref="Application.RunState"/> becomes the <see cref="Application.Current"/> Toplevel.
 		/// </summary>
 		public event Action<Toplevel> Activate;
 
 		/// <summary>
-		/// Invoked once the Toplevel's <see cref="Application.RunState"/> ceases to be the <see cref="Application.Current"/>.
+		/// Invoked when the Toplevel<see cref="Application.RunState"/> ceases to be the <see cref="Application.Current"/> Toplevel.
 		/// </summary>
 		public event Action<Toplevel> Deactivate;
 
 		/// <summary>
-		/// Invoked once the child Toplevel's <see cref="Application.RunState"/> is closed from the <see cref="Application.End(View)"/>
+		/// Invoked when a child of the Toplevel <see cref="Application.RunState"/> is closed by  
+		/// <see cref="Application.End(View)"/>.
 		/// </summary>
 		public event Action<Toplevel> ChildClosed;
 
 		/// <summary>
-		/// Invoked once the last child Toplevel's <see cref="Application.RunState"/> is closed from the <see cref="Application.End(View)"/>
+		/// Invoked when the last child of the Toplevel <see cref="Application.RunState"/> is closed from 
+		/// by <see cref="Application.End(View)"/>.
 		/// </summary>
 		public event Action AllChildClosed;
 
 		/// <summary>
-		/// Invoked once the Toplevel's <see cref="Application.RunState"/> is being closing from the <see cref="Application.RequestStop(Toplevel)"/>
+		/// Invoked when the Toplevel's <see cref="Application.RunState"/> is being closed by  
+		/// <see cref="Application.RequestStop(Toplevel)"/>.
 		/// </summary>
 		public event Action<ToplevelClosingEventArgs> Closing;
 
 		/// <summary>
-		/// Invoked once the Toplevel's <see cref="Application.RunState"/> is closed from the <see cref="Application.End(View)"/>
+		/// Invoked when the Toplevel's <see cref="Application.RunState"/> is closed by <see cref="Application.End(View)"/>.
 		/// </summary>
 		public event Action<Toplevel> Closed;
 
 		/// <summary>
-		/// Invoked once the child Toplevel's <see cref="Application.RunState"/> has begin loaded.
+		/// Invoked when a child Toplevel's <see cref="Application.RunState"/> has been loaded.
 		/// </summary>
 		public event Action<Toplevel> ChildLoaded;
 
 		/// <summary>
-		/// Invoked once the child Toplevel's <see cref="Application.RunState"/> has begin unloaded.
+		/// Invoked when a cjhild Toplevel's <see cref="Application.RunState"/> has been unloaded.
 		/// </summary>
 		public event Action<Toplevel> ChildUnloaded;
 
 		/// <summary>
-		/// Invoked when the terminal was resized. The new <see cref="Size"/> of the terminal is provided.
+		/// Invoked when the terminal has been resized. The new <see cref="Size"/> of the terminal is provided.
 		/// </summary>
 		public event Action<Size> Resized;
 
@@ -162,18 +163,25 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> is redraws for the first time.
+		/// Called from <see cref="Application.Begin(Toplevel)"/> before the <see cref="Toplevel"/> redraws for the first time. 
 		/// </summary>
-		internal virtual void OnLoaded ()
+		virtual public void OnLoaded ()
 		{
+			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+				tl.OnLoaded ();
+			}
 			Loaded?.Invoke ();
 		}
 
 		/// <summary>
-		/// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered it's first iteration of the loop.
+		/// Called from <see cref="Application.RunLoop"/> after the <see cref="Toplevel"/> has entered the 
+		/// first iteration of the loop.
 		/// </summary>
 		internal virtual void OnReady ()
 		{
+			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+				tl.OnReady ();
+			}
 			Ready?.Invoke ();
 		}
 
@@ -182,11 +190,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		internal virtual void OnUnloaded ()
 		{
+			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+				tl.OnUnloaded ();
+			}
 			Unloaded?.Invoke ();
 		}
 
 		/// <summary>
-		/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified absolute layout.
+		/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified <see cref="LayoutStyle.Absolute"/> layout.
 		/// </summary>
 		/// <param name="frame">A superview-relative rectangle specifying the location and size for the new Toplevel</param>
 		public Toplevel (Rect frame) : base (frame)
@@ -195,7 +206,8 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Initializes a new instance of the <see cref="Toplevel"/> class with <see cref="LayoutStyle.Computed"/> layout, defaulting to full screen.
+		/// Initializes a new instance of the <see cref="Toplevel"/> class with <see cref="LayoutStyle.Computed"/> layout, 
+		/// defaulting to full screen.
 		/// </summary>
 		public Toplevel () : base ()
 		{
@@ -291,7 +303,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
 		/// </summary>
-		/// <returns>The create.</returns>
+		/// <returns>The created Toplevel.</returns>
 		public static Toplevel Create ()
 		{
 			return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
@@ -306,19 +318,38 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Determines whether the <see cref="Toplevel"/> is modal or not.
-		/// Causes <see cref="ProcessKey(KeyEvent)"/> to propagate keys upwards
-		/// by default unless set to <see langword="true"/>.
+		/// Determines whether the <see cref="Toplevel"/> is modal or not. 
+		/// If set to <c>false</c> (the default):
+		/// 
+		/// <list type="bullet">
+		///   <item>
+		///		<description><see cref="ProcessKey(KeyEvent)"/> events will propagate keys upwards.</description>
+		///   </item>
+		///   <item>
+		///		<description>The Toplevel will act as an embedded view (not a modal/pop-up).</description>
+		///   </item>
+		/// </list>
+		///
+		/// If set to <c>true</c>:
+		/// 
+		/// <list type="bullet">
+		///   <item>
+		///		<description><see cref="ProcessKey(KeyEvent)"/> events will NOT propogate keys upwards.</description>
+		///	  </item>
+		///   <item>
+		///		<description>The Toplevel will and look like a modal (pop-up) (e.g. see <see cref="Dialog"/>.</description>
+		///   </item>
+		/// </list>
 		/// </summary>
 		public bool Modal { get; set; }
 
 		/// <summary>
-		/// Gets or sets the menu for this Toplevel
+		/// Gets or sets the menu for this Toplevel.
 		/// </summary>
 		public virtual MenuBar MenuBar { get; set; }
 
 		/// <summary>
-		/// Gets or sets the status bar for this Toplevel
+		/// Gets or sets the status bar for this Toplevel.
 		/// </summary>
 		public virtual StatusBar StatusBar { get; set; }
 
@@ -647,7 +678,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Virtual method which allow to be overridden to implement specific positions for inherited <see cref="Toplevel"/>.
+		/// Virtual method enabling implementation of specific positions for inherited <see cref="Toplevel"/> views.
 		/// </summary>
 		/// <param name="top">The toplevel.</param>
 		public virtual void PositionToplevel (Toplevel top)
@@ -734,20 +765,12 @@ namespace Terminal.Gui {
 			return false;
 		}
 
-		//
-		// FIXED:It does not look like the event is raised on clicked-drag
-		// need to figure that out.
-		//
 		internal static Point? dragPosition;
 		Point start;
 
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent mouseEvent)
 		{
-			// FIXED:The code is currently disabled, because the
-			// Driver.UncookMouse does not seem to have an effect if there is
-			// a pending mouse event activated.
-
 			if (!CanFocus) {
 				return true;
 			}
@@ -800,7 +823,6 @@ namespace Terminal.Gui {
 					}
 					//System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}");
 
-					// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
 					SetNeedsDisplay ();
 					return true;
 				}
@@ -817,8 +839,8 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Invoked by <see cref="Application.Begin"/> as part of the <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> after
-		/// the views have been laid out, and before the views are drawn for the first time.
+		/// Invoked by <see cref="Application.Begin"/> as part of  <see cref="Application.Run(Toplevel, Func{Exception, bool})"/> 
+		/// after the views have been laid out, and before the views are drawn for the first time.
 		/// </summary>
 		public virtual void WillPresent ()
 		{
@@ -842,7 +864,8 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Stops running this <see cref="Toplevel"/>.
+		/// Stops and closes this <see cref="Toplevel"/>. If this Toplevel is the top-most Toplevel, 
+		/// <see cref="Application.RequestStop(Toplevel)"/> will be called, causing the application to exit.
 		/// </summary>
 		public virtual void RequestStop ()
 		{
@@ -880,7 +903,8 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Stops running the <paramref name="top"/> <see cref="Toplevel"/>.
+		/// Stops and closes the <see cref="Toplevel"/> specified by <paramref name="top"/>. If <paramref name="top"/> is the top-most Toplevel, 
+		/// <see cref="Application.RequestStop(Toplevel)"/> will be called, causing the application to exit.
 		/// </summary>
 		/// <param name="top">The toplevel to request stop.</param>
 		public virtual void RequestStop (Toplevel top)
@@ -908,7 +932,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets the current visible toplevel Mdi child that match the arguments pattern.
+		/// Gets the current visible Toplevel Mdi child that matches the arguments pattern.
 		/// </summary>
 		/// <param name="type">The type.</param>
 		/// <param name="exclude">The strings to exclude.</param>
@@ -933,10 +957,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Shows the Mdi child indicated by the <paramref name="top"/> setting as <see cref="Application.Current"/>.
+		/// Shows the Mdi child indicated by <paramref name="top"/>, setting it as <see cref="Application.Current"/>.
 		/// </summary>
-		/// <param name="top">The toplevel.</param>
-		/// <returns><see langword="true"/> if the toplevel can be showed.<see langword="false"/> otherwise.</returns>
+		/// <param name="top">The Toplevel.</param>
+		/// <returns><c>true</c> if the toplevel can be shown or <c>false</c> if not.</returns>
 		public virtual bool ShowChild (Toplevel top = null)
 		{
 			if (Application.MdiTop != null) {
@@ -947,7 +971,8 @@ namespace Terminal.Gui {
 	}
 
 	/// <summary>
-	/// Implements the <see cref="IEqualityComparer{T}"/> to comparing two <see cref="Toplevel"/> used by <see cref="StackExtensions"/>.
+	/// Implements the <see cref="IEqualityComparer{T}"/> for comparing two <see cref="Toplevel"/>s
+	/// used by <see cref="StackExtensions"/>.
 	/// </summary>
 	public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
 		/// <summary>Determines whether the specified objects are equal.</summary>
@@ -970,7 +995,8 @@ namespace Terminal.Gui {
 		/// <summary>Returns a hash code for the specified object.</summary>
 		/// <param name="obj">The <see cref="Toplevel" /> for which a hash code is to be returned.</param>
 		/// <returns>A hash code for the specified object.</returns>
-		/// <exception cref="ArgumentNullException">The type of <paramref name="obj" /> is a reference type and <paramref name="obj" /> is <see langword="null" />.</exception>
+		/// <exception cref="ArgumentNullException">The type of <paramref name="obj" /> 
+		/// is a reference type and <paramref name="obj" /> is <see langword="null" />.</exception>
 		public int GetHashCode (Toplevel obj)
 		{
 			if (obj == null)
@@ -985,7 +1011,8 @@ namespace Terminal.Gui {
 	}
 
 	/// <summary>
-	/// Implements the <see cref="IComparer{T}"/> to sort the <see cref="Toplevel"/> from the <see cref="Application.MdiChildes"/> if needed.
+	/// Implements the <see cref="IComparer{T}"/> to sort the <see cref="Toplevel"/> 
+	/// from the <see cref="Application.MdiChildes"/> if needed.
 	/// </summary>
 	public sealed class ToplevelComparer : IComparer<Toplevel> {
 		/// <summary>Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other.</summary>

+ 334 - 111
Terminal.Gui/Core/View.cs

@@ -181,15 +181,30 @@ namespace Terminal.Gui {
 		/// </summary>
 		public event Action<Key> HotKeyChanged;
 
+		Key hotKey = Key.Null;
+
 		/// <summary>
 		/// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire.
 		/// </summary>
-		public virtual Key HotKey { get => TextFormatter.HotKey; set => TextFormatter.HotKey = value; }
+		public virtual Key HotKey {
+			get => hotKey;
+			set {
+				if (hotKey != value) {
+					hotKey = TextFormatter.HotKey = (value == Key.Unknown ? Key.Null : value);
+				}
+			}
+		}
 
 		/// <summary>
 		/// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. 
 		/// </summary>
-		public virtual Rune HotKeySpecifier { get => TextFormatter.HotKeySpecifier; set => TextFormatter.HotKeySpecifier = value; }
+		public virtual Rune HotKeySpecifier {
+			get => TextFormatter.HotKeySpecifier;
+			set {
+				TextFormatter.HotKeySpecifier = value;
+				SetHotKey ();
+			}
+		}
 
 		/// <summary>
 		/// This is the global setting that can be used as a global shortcut to invoke an action if provided.
@@ -445,8 +460,8 @@ namespace Terminal.Gui {
 					SuperView.SetNeedsDisplay (frame);
 					SuperView.SetNeedsDisplay (value);
 				}
-				frame = value;
-
+				frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
+				TextFormatter.Size = GetBoundsTextFormatterSize ();
 				SetNeedsLayout ();
 				SetNeedsDisplay (frame);
 			}
@@ -513,17 +528,13 @@ namespace Terminal.Gui {
 		public Pos X {
 			get => x;
 			set {
-				if (!ValidatePosDim (x, value)) {
+				if (ForceValidatePosDim && !ValidatePosDim (x, value)) {
 					throw new ArgumentException ();
 				}
 
 				x = value;
-				SetNeedsLayout ();
-				if (x is Pos.PosAbsolute) {
-					frame = new Rect (x.Anchor (0), frame.Y, frame.Width, frame.Height);
-				}
-				TextFormatter.Size = frame.Size;
-				SetNeedsDisplay (frame);
+
+				ProcessResizeView ();
 			}
 		}
 
@@ -537,20 +548,15 @@ namespace Terminal.Gui {
 		public Pos Y {
 			get => y;
 			set {
-				if (!ValidatePosDim (y, value)) {
+				if (ForceValidatePosDim && !ValidatePosDim (y, value)) {
 					throw new ArgumentException ();
 				}
 
 				y = value;
-				SetNeedsLayout ();
-				if (y is Pos.PosAbsolute) {
-					frame = new Rect (frame.X, y.Anchor (0), frame.Width, frame.Height);
-				}
-				TextFormatter.Size = frame.Size;
-				SetNeedsDisplay (frame);
+
+				ProcessResizeView ();
 			}
 		}
-
 		Dim width, height;
 
 		/// <summary>
@@ -563,20 +569,20 @@ namespace Terminal.Gui {
 		public Dim Width {
 			get => width;
 			set {
-				if (!ValidatePosDim (width, value)) {
-					throw new ArgumentException ();
+				if (ForceValidatePosDim && !ValidatePosDim (width, value)) {
+					throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width));
 				}
 
 				width = value;
-				if (autoSize && value.Anchor (0) != TextFormatter.Size.Width) {
-					autoSize = false;
-				}
-				SetNeedsLayout ();
-				if (width is Dim.DimAbsolute) {
-					frame = new Rect (frame.X, frame.Y, width.Anchor (0), frame.Height);
+
+				if (ForceValidatePosDim) {
+					var isValidNewAutSize = autoSize && IsValidAutoSizeWidth (width);
+
+					if (IsAdded && autoSize && !isValidNewAutSize) {
+						throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
+					}
 				}
-				TextFormatter.Size = frame.Size;
-				SetNeedsDisplay (frame);
+				ProcessResizeView ();
 			}
 		}
 
@@ -588,23 +594,29 @@ namespace Terminal.Gui {
 		public Dim Height {
 			get => height;
 			set {
-				if (!ValidatePosDim (height, value)) {
-					throw new ArgumentException ();
+				if (ForceValidatePosDim && !ValidatePosDim (height, value)) {
+					throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height));
 				}
 
 				height = value;
-				if (autoSize && value.Anchor (0) != TextFormatter.Size.Height) {
-					autoSize = false;
-				}
-				SetNeedsLayout ();
-				if (height is Dim.DimAbsolute) {
-					frame = new Rect (frame.X, frame.Y, frame.Width, height.Anchor (0));
+
+				if (ForceValidatePosDim) {
+					var isValidNewAutSize = autoSize && IsValidAutoSizeHeight (height);
+
+					if (IsAdded && autoSize && !isValidNewAutSize) {
+						throw new InvalidOperationException ("Must set AutoSize to false before set the Height.");
+					}
 				}
-				TextFormatter.Size = frame.Size;
-				SetNeedsDisplay (frame);
+				ProcessResizeView ();
 			}
 		}
 
+		/// <summary>
+		/// Forces validation with <see cref="LayoutStyle.Computed"/> layout
+		///  to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
+		/// </summary>
+		public bool ForceValidatePosDim { get; set; }
+
 		bool ValidatePosDim (object oldvalue, object newValue)
 		{
 			if (!IsInitialized || layoutStyle == LayoutStyle.Absolute || oldvalue == null || oldvalue.GetType () == newValue.GetType () || this is Toplevel) {
@@ -618,6 +630,50 @@ namespace Terminal.Gui {
 			return false;
 		}
 
+		/// <summary>
+		/// Verifies if the minimum width or height can be sets in the view.
+		/// </summary>
+		/// <param name="size">The size.</param>
+		/// <returns><see langword="true"/> if the size can be set, <see langword="false"/>otherwise.</returns>
+		public bool GetMinWidthHeight (out Size size)
+		{
+			size = Size.Empty;
+
+			if (!AutoSize && !ustring.IsNullOrEmpty (TextFormatter.Text)) {
+				switch (TextFormatter.IsVerticalDirection (TextDirection)) {
+				case true:
+					var colWidth = TextFormatter.GetSumMaxCharWidth (new List<ustring> { TextFormatter.Text }, 0, 1);
+					if (frame.Width < colWidth && (Width == null || (Bounds.Width >= 0 && Width is Dim.DimAbsolute
+						&& Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth))) {
+						size = new Size (colWidth, Bounds.Height);
+						return true;
+					}
+					break;
+				default:
+					if (frame.Height < 1 && (Height == null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0))) {
+						size = new Size (Bounds.Width, 1);
+						return true;
+					}
+					break;
+				}
+			}
+			return false;
+		}
+
+		/// <summary>
+		/// Sets the minimum width or height if the view can be resized.
+		/// </summary>
+		/// <returns><see langword="true"/> if the size can be set, <see langword="false"/>otherwise.</returns>
+		public bool SetMinWidthHeight ()
+		{
+			if (GetMinWidthHeight (out Size size)) {
+				Bounds = new Rect (Bounds.Location, size);
+				TextFormatter.Size = GetBoundsTextFormatterSize ();
+				return true;
+			}
+			return false;
+		}
+
 		/// <summary>
 		/// Gets or sets the <see cref="Terminal.Gui.TextFormatter"/> which can be handled differently by any derived class.
 		/// </summary>
@@ -724,7 +780,7 @@ namespace Terminal.Gui {
 		}
 
 		void Initialize (ustring text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed,
-			TextDirection direction = TextDirection.LeftRight_TopBottom, Border border = null)
+		    TextDirection direction = TextDirection.LeftRight_TopBottom, Border border = null)
 		{
 			TextFormatter = new TextFormatter ();
 			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
@@ -745,14 +801,45 @@ namespace Terminal.Gui {
 			} else {
 				r = rect;
 			}
-			x = Pos.At (r.X);
-			y = Pos.At (r.Y);
-			Width = r.Width;
-			Height = r.Height;
-
 			Frame = r;
 
 			Text = text;
+			UpdateTextFormatterText ();
+			ProcessResizeView ();
+		}
+
+		/// <summary>
+		/// Can be overridden if the <see cref="TextFormatter.Text"/> has
+		///  different format than the default.
+		/// </summary>
+		protected virtual void UpdateTextFormatterText ()
+		{
+			TextFormatter.Text = text;
+		}
+
+		/// <summary>
+		/// Can be overridden if the view resize behavior is
+		///  different than the default.
+		/// </summary>
+		protected virtual void ProcessResizeView ()
+		{
+			var _x = x is Pos.PosAbsolute ? x.Anchor (0) : frame.X;
+			var _y = y is Pos.PosAbsolute ? y.Anchor (0) : frame.Y;
+
+			if (AutoSize) {
+				var s = GetAutoSize ();
+				var w = width is Dim.DimAbsolute && width.Anchor (0) > s.Width ? width.Anchor (0) : s.Width;
+				var h = height is Dim.DimAbsolute && height.Anchor (0) > s.Height ? height.Anchor (0) : s.Height;
+				frame = new Rect (new Point (_x, _y), new Size (w, h));
+			} else {
+				var w = width is Dim.DimAbsolute ? width.Anchor (0) : frame.Width;
+				var h = height is Dim.DimAbsolute ? height.Anchor (0) : frame.Height;
+				frame = new Rect (new Point (_x, _y), new Size (w, h));
+				SetMinWidthHeight ();
+			}
+			TextFormatter.Size = GetBoundsTextFormatterSize ();
+			SetNeedsLayout ();
+			SetNeedsDisplay ();
 		}
 
 		private void TextFormatter_HotKeyChanged (Key obj)
@@ -1279,6 +1366,12 @@ namespace Terminal.Gui {
 		/// <param name="view">The subview being added.</param>
 		public virtual void OnAdded (View view)
 		{
+			view.IsAdded = true;
+			view.x = view.x ?? view.frame.X;
+			view.y = view.y ?? view.frame.Y;
+			view.width = view.width ?? view.frame.Width;
+			view.height = view.height ?? view.frame.Height;
+
 			view.Added?.Invoke (this);
 		}
 
@@ -1288,6 +1381,7 @@ namespace Terminal.Gui {
 		/// <param name="view">The subview being removed.</param>
 		public virtual void OnRemoved (View view)
 		{
+			view.IsAdded = false;
 			view.Removed?.Invoke (this);
 		}
 
@@ -1429,8 +1523,8 @@ namespace Terminal.Gui {
 				containerBounds.Width = Math.Min (containerBounds.Width, Driver.Clip.Width);
 				containerBounds.Height = Math.Min (containerBounds.Height, Driver.Clip.Height);
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
-					HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
-					containerBounds);
+				    HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled,
+				    containerBounds);
 			}
 
 			// Invoke DrawContentEvent
@@ -2021,47 +2115,64 @@ namespace Terminal.Gui {
 		internal void SetRelativeLayout (Rect hostFrame)
 		{
 			int w, h, _x, _y;
+			var s = Size.Empty;
+
+			if (AutoSize) {
+				s = GetAutoSize ();
+			}
 
 			if (x is Pos.PosCenter) {
-				if (width == null)
-					w = hostFrame.Width;
-				else
+				if (width == null) {
+					w = AutoSize ? s.Width : hostFrame.Width;
+				} else {
 					w = width.Anchor (hostFrame.Width);
+					w = AutoSize && s.Width > w ? s.Width : w;
+				}
 				_x = x.Anchor (hostFrame.Width - w);
 			} else {
 				if (x == null)
 					_x = 0;
 				else
 					_x = x.Anchor (hostFrame.Width);
-				if (width == null)
-					w = hostFrame.Width;
-				else if (width is Dim.DimFactor && !((Dim.DimFactor)width).IsFromRemaining ())
+				if (width == null) {
+					w = AutoSize ? s.Width : hostFrame.Width;
+				} else if (width is Dim.DimFactor && !((Dim.DimFactor)width).IsFromRemaining ()) {
 					w = width.Anchor (hostFrame.Width);
-				else
+					w = AutoSize && s.Width > w ? s.Width : w;
+				} else {
 					w = Math.Max (width.Anchor (hostFrame.Width - _x), 0);
+					w = AutoSize && s.Width > w ? s.Width : w;
+				}
 			}
 
 			if (y is Pos.PosCenter) {
-				if (height == null)
-					h = hostFrame.Height;
-				else
+				if (height == null) {
+					h = AutoSize ? s.Height : hostFrame.Height;
+				} else {
 					h = height.Anchor (hostFrame.Height);
+					h = AutoSize && s.Height > h ? s.Height : h;
+				}
 				_y = y.Anchor (hostFrame.Height - h);
 			} else {
 				if (y == null)
 					_y = 0;
 				else
 					_y = y.Anchor (hostFrame.Height);
-				if (height == null)
-					h = hostFrame.Height;
-				else if (height is Dim.DimFactor && !((Dim.DimFactor)height).IsFromRemaining ())
+				if (height == null) {
+					h = AutoSize ? s.Height : hostFrame.Height;
+				} else if (height is Dim.DimFactor && !((Dim.DimFactor)height).IsFromRemaining ()) {
 					h = height.Anchor (hostFrame.Height);
-				else
+					h = AutoSize && s.Height > h ? s.Height : h;
+				} else {
 					h = Math.Max (height.Anchor (hostFrame.Height - _y), 0);
+					h = AutoSize && s.Height > h ? s.Height : h;
+				}
 			}
 			var r = new Rect (_x, _y, w, h);
 			if (Frame != r) {
 				Frame = new Rect (_x, _y, w, h);
+				if (!SetMinWidthHeight ())
+					TextFormatter.Size = GetBoundsTextFormatterSize ();
 			}
 		}
 
@@ -2177,7 +2288,7 @@ namespace Terminal.Gui {
 			Rect oldBounds = Bounds;
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 
-			TextFormatter.Size = Bounds.Size;
+			TextFormatter.Size = GetBoundsTextFormatterSize ();
 
 
 			// Sort out the dependencies of the X, Y, Width, Height properties
@@ -2250,7 +2361,7 @@ namespace Terminal.Gui {
 			}
 
 			if (SuperView != null && SuperView == Application.Top && LayoutNeeded
-				&& ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
+			    && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
 				SetRelativeLayout (SuperView.Frame);
 			}
 
@@ -2259,6 +2370,8 @@ namespace Terminal.Gui {
 			OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
 		}
 
+		ustring text;
+
 		/// <summary>
 		///   The text displayed by the <see cref="View"/>.
 		/// </summary>
@@ -2278,27 +2391,22 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </remarks>
 		public virtual ustring Text {
-			get => TextFormatter.Text;
+			get => text;
 			set {
-				TextFormatter.Text = value;
-				var prevSize = frame.Size;
-				var canResize = ResizeView (autoSize);
-				if (canResize && TextFormatter.Size != Bounds.Size) {
-					Bounds = new Rect (new Point (Bounds.X, Bounds.Y), TextFormatter.Size);
-				} else if (!canResize && TextFormatter.Size != Bounds.Size) {
-					TextFormatter.Size = Bounds.Size;
-				}
-				SetNeedsLayout ();
-				SetNeedsDisplay (new Rect (new Point (0, 0),
-					new Size (Math.Max (frame.Width, prevSize.Width), Math.Max (frame.Height, prevSize.Height))));
+				text = value;
+				SetHotKey ();
+				UpdateTextFormatterText ();
+				ProcessResizeView ();
 			}
 		}
 
 		/// <summary>
-		/// Used by <see cref="Text"/> to resize the view's <see cref="Bounds"/> with the <see cref="TextFormatter.Size"/>.
-		/// Setting <see cref="AutoSize"/> to true only work if the <see cref="Width"/> and <see cref="Height"/> are null or
-		///   <see cref="LayoutStyle.Absolute"/> values and doesn't work with <see cref="LayoutStyle.Computed"/> layout,
-		///   to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
+		/// Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>.
+		/// The default is `false`. Set to `true` to turn on AutoSize. If <see cref="AutoSize"/> is `true` the <see cref="Width"/>
+		/// and <see cref="Height"/> will always be used if the text size is lower. If the text size is higher the bounds will
+		/// be resized to fit it.
+		/// In addition, if <see cref="ForceValidatePosDim"/> is `true` the new values of <see cref="Width"/> and
+		/// <see cref="Height"/> must be of the same types of the existing one to avoid breaking the <see cref="Dim"/> settings.
 		/// </summary>
 		public virtual bool AutoSize {
 			get => autoSize;
@@ -2308,8 +2416,24 @@ namespace Terminal.Gui {
 				if (autoSize != v) {
 					autoSize = v;
 					TextFormatter.NeedsFormat = true;
-					SetNeedsLayout ();
-					SetNeedsDisplay ();
+					UpdateTextFormatterText ();
+					ProcessResizeView ();
+				}
+			}
+		}
+
+		/// <summary>
+		/// Gets or sets a flag that determines whether <see cref="TextFormatter.Text"/> will have trailing spaces preserved
+		/// or not when <see cref="TextFormatter.WordWrap"/> is enabled. If `true` any trailing spaces will be trimmed when
+		/// either the <see cref="Text"/> property is changed or when <see cref="TextFormatter.WordWrap"/> is set to `true`.
+		/// The default is `false`.
+		/// </summary>
+		public virtual bool PreserveTrailingSpaces {
+			get => TextFormatter.PreserveTrailingSpaces;
+			set {
+				if (TextFormatter.PreserveTrailingSpaces != value) {
+					TextFormatter.PreserveTrailingSpaces = value;
+					TextFormatter.NeedsFormat = true;
 				}
 			}
 		}
@@ -2322,7 +2446,8 @@ namespace Terminal.Gui {
 			get => TextFormatter.Alignment;
 			set {
 				TextFormatter.Alignment = value;
-				SetNeedsDisplay ();
+				UpdateTextFormatterText ();
+				ProcessResizeView ();
 			}
 		}
 
@@ -2346,14 +2471,23 @@ namespace Terminal.Gui {
 			get => TextFormatter.Direction;
 			set {
 				if (TextFormatter.Direction != value) {
+					var isValidOldAutSize = autoSize && IsValidAutoSize (out Size autSize);
+					var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
+					    != TextFormatter.IsHorizontalDirection (value);
+
 					TextFormatter.Direction = value;
-					if (AutoSize) {
-						ResizeView (true);
-					} else if (IsInitialized) {
-						var b = new Rect (Bounds.X, Bounds.Y, Bounds.Height, Bounds.Width);
-						SetWidthHeight (b);
+					UpdateTextFormatterText ();
+
+					if ((!ForceValidatePosDim && directionChanged && AutoSize)
+					    || (ForceValidatePosDim && directionChanged && AutoSize && isValidOldAutSize)) {
+						ProcessResizeView ();
+					} else if (directionChanged && IsAdded) {
+						SetWidthHeight (Bounds.Size);
+						SetMinWidthHeight ();
+					} else {
+						SetMinWidthHeight ();
 					}
-					TextFormatter.Size = Bounds.Size;
+					TextFormatter.Size = GetBoundsTextFormatterSize ();
 					SetNeedsDisplay ();
 				}
 			}
@@ -2365,6 +2499,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		public virtual bool IsInitialized { get; set; }
 
+		/// <summary>
+		/// Gets information if the view was already added to the <see cref="SuperView"/>.
+		/// </summary>
+		public bool IsAdded { get; private set; }
+
 		bool oldEnabled;
 
 		/// <inheritdoc/>
@@ -2431,32 +2570,38 @@ namespace Terminal.Gui {
 			return $"{GetType ().Name}({Id})({Frame})";
 		}
 
+		void SetHotKey ()
+		{
+			TextFormatter.FindHotKey (text, HotKeySpecifier, true, out _, out Key hk);
+			if (hotKey != hk) {
+				HotKey = hk;
+			}
+		}
+
 		bool ResizeView (bool autoSize)
 		{
 			if (!autoSize) {
 				return false;
 			}
 
-			var aSize = autoSize;
-			Rect nBounds = TextFormatter.CalcRect (Bounds.X, Bounds.Y, Text, TextFormatter.Direction);
-			if (TextFormatter.Size != nBounds.Size) {
-				TextFormatter.Size = nBounds.Size;
-			}
-			if ((TextFormatter.Size != Bounds.Size || TextFormatter.Size != nBounds.Size)
-				&& (((width == null || width is Dim.DimAbsolute) && (Bounds.Width == 0
-				|| autoSize && Bounds.Width != nBounds.Width))
-				|| ((height == null || height is Dim.DimAbsolute) && (Bounds.Height == 0
-				|| autoSize && Bounds.Height != nBounds.Height)))) {
-				aSize = SetWidthHeight (nBounds);
+			var aSize = true;
+			var nBoundsSize = GetAutoSize ();
+			if (nBoundsSize != Bounds.Size) {
+				if (ForceValidatePosDim) {
+					aSize = SetWidthHeight (nBoundsSize);
+				} else {
+					Bounds = new Rect (Bounds.X, Bounds.Y, nBoundsSize.Width, nBoundsSize.Height);
+				}
 			}
+			TextFormatter.Size = GetBoundsTextFormatterSize ();
 			return aSize;
 		}
 
-		bool SetWidthHeight (Rect nBounds)
+		bool SetWidthHeight (Size nBounds)
 		{
 			bool aSize = false;
-			var canSizeW = SetWidth (nBounds.Width, out int rW);
-			var canSizeH = SetHeight (nBounds.Height, out int rH);
+			var canSizeW = SetWidth (nBounds.Width - GetHotKeySpecifierLength (), out int rW);
+			var canSizeH = SetHeight (nBounds.Height - GetHotKeySpecifierLength (false), out int rH);
 			if (canSizeW) {
 				aSize = true;
 				width = rW;
@@ -2467,12 +2612,90 @@ namespace Terminal.Gui {
 			}
 			if (aSize) {
 				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
-				TextFormatter.Size = Bounds.Size;
+				TextFormatter.Size = GetBoundsTextFormatterSize ();
 			}
 
 			return aSize;
 		}
 
+		/// <summary>
+		/// Gets the size to fit all text if <see cref="AutoSize"/> is true.
+		/// </summary>
+		/// <returns>The <see cref="Size"/></returns>
+		public Size GetAutoSize ()
+		{
+			var rect = TextFormatter.CalcRect (Bounds.X, Bounds.Y, TextFormatter.Text, TextFormatter.Direction);
+			return new Size (rect.Size.Width - GetHotKeySpecifierLength (),
+			    rect.Size.Height - GetHotKeySpecifierLength (false));
+		}
+
+		bool IsValidAutoSize (out Size autoSize)
+		{
+			var rect = TextFormatter.CalcRect (frame.X, frame.Y, TextFormatter.Text, TextDirection);
+			autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (),
+			    rect.Size.Height - GetHotKeySpecifierLength (false));
+			return !(ForceValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute))
+			    || frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength ()
+			    || frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
+		}
+
+		bool IsValidAutoSizeWidth (Dim width)
+		{
+			var rect = TextFormatter.CalcRect (frame.X, frame.Y, TextFormatter.Text, TextDirection);
+			var dimValue = width.Anchor (0);
+			return !(ForceValidatePosDim && (!(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width
+			    - GetHotKeySpecifierLength ());
+		}
+
+		bool IsValidAutoSizeHeight (Dim height)
+		{
+			var rect = TextFormatter.CalcRect (frame.X, frame.Y, TextFormatter.Text, TextDirection);
+			var dimValue = height.Anchor (0);
+			return !(ForceValidatePosDim && (!(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height
+			    - GetHotKeySpecifierLength (false));
+		}
+
+		/// <summary>
+		/// Get the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> length.
+		/// </summary>
+		/// <param name="isWidth"><c>true</c>if is the width (default)<c>false</c>if is the height.</param>
+		/// <returns>The length of the <see cref="TextFormatter.HotKeySpecifier"/>.</returns>
+		public int GetHotKeySpecifierLength (bool isWidth = true)
+		{
+			if (isWidth) {
+				return TextFormatter.IsHorizontalDirection (TextDirection) &&
+				    TextFormatter.Text?.Contains (HotKeySpecifier) == true
+				    ? Math.Max (Rune.ColumnWidth (HotKeySpecifier), 0) : 0;
+			} else {
+				return TextFormatter.IsVerticalDirection (TextDirection) &&
+				    TextFormatter.Text?.Contains (HotKeySpecifier) == true
+				    ? Math.Max (Rune.ColumnWidth (HotKeySpecifier), 0) : 0;
+			}
+		}
+
+		/// <summary>
+		/// Gets the bounds size from a <see cref="TextFormatter.Size"/>.
+		/// </summary>
+		/// <returns>The bounds size minus the <see cref="TextFormatter.HotKeySpecifier"/> length.</returns>
+		public Size GetTextFormatterBoundsSize ()
+		{
+			return new Size (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
+			    TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
+		}
+
+		/// <summary>
+		/// Gets the text formatter size from a <see cref="Bounds"/> size.
+		/// </summary>
+		/// <returns>The text formatter size more the <see cref="TextFormatter.HotKeySpecifier"/> length.</returns>
+		public Size GetBoundsTextFormatterSize ()
+		{
+			if (TextFormatter.Text == null)
+				return Bounds.Size;
+
+			return new Size (frame.Size.Width + GetHotKeySpecifierLength (),
+			    frame.Size.Height + GetHotKeySpecifierLength (false));
+		}
+
 		/// <summary>
 		/// Specifies the event arguments for <see cref="MouseEvent"/>
 		/// </summary>
@@ -2655,7 +2878,7 @@ namespace Terminal.Gui {
 			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
 				w = Width.Anchor (w);
-				canSetWidth = false;
+				canSetWidth = !ForceValidatePosDim;
 			} else if (Width is Dim.DimFactor factor) {
 				// Tries to get the SuperView width otherwise the view width.
 				var sw = SuperView != null ? SuperView.Frame.Width : w;
@@ -2663,7 +2886,7 @@ namespace Terminal.Gui {
 					sw -= Frame.X;
 				}
 				w = Width.Anchor (sw);
-				canSetWidth = false;
+				canSetWidth = !ForceValidatePosDim;
 			} else {
 				canSetWidth = true;
 			}
@@ -2679,7 +2902,7 @@ namespace Terminal.Gui {
 			if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) {
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
 				h = Height.Anchor (h);
-				canSetHeight = false;
+				canSetHeight = !ForceValidatePosDim;
 			} else if (Height is Dim.DimFactor factor) {
 				// Tries to get the SuperView height otherwise the view height.
 				var sh = SuperView != null ? SuperView.Frame.Height : h;
@@ -2687,7 +2910,7 @@ namespace Terminal.Gui {
 					sh -= Frame.Y;
 				}
 				h = Height.Anchor (sh);
-				canSetHeight = false;
+				canSetHeight = !ForceValidatePosDim;
 			} else {
 				canSetHeight = true;
 			}
@@ -2725,8 +2948,8 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c> if the width can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool GetCurrentWidth (out int currentWidth)
 		{
-			SetRelativeLayout (SuperView == null ? Frame : SuperView.Frame);
-			currentWidth = Frame.Width;
+			SetRelativeLayout (SuperView == null ? frame : SuperView.frame);
+			currentWidth = frame.Width;
 
 			return CanSetWidth (0, out _);
 		}
@@ -2738,8 +2961,8 @@ namespace Terminal.Gui {
 		/// <returns><c>true</c> if the height can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool GetCurrentHeight (out int currentHeight)
 		{
-			SetRelativeLayout (SuperView == null ? Frame : SuperView.Frame);
-			currentHeight = Frame.Height;
+			SetRelativeLayout (SuperView == null ? frame : SuperView.frame);
+			currentHeight = frame.Height;
 
 			return CanSetHeight (0, out _);
 		}

+ 92 - 17
Terminal.Gui/Core/Window.cs

@@ -9,6 +9,7 @@
 //  - FrameView Does not support IEnumerable
 // Any udpates done here should probably be done in FrameView as well; TODO: Merge these classes
 
+using System;
 using System.Collections;
 using NStack;
 
@@ -22,7 +23,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	public class Window : Toplevel {
 		View contentView;
-		ustring title;
+		ustring title = ustring.Empty;
 
 		/// <summary>
 		/// The title to be displayed for this window.
@@ -31,7 +32,11 @@ namespace Terminal.Gui {
 		public ustring Title {
 			get => title;
 			set {
-				title = value;
+				if (!OnTitleChanging (title, value)) {
+					var old = title;
+					title = value;
+					OnTitleChanged (old, title);
+				}
 				SetNeedsDisplay ();
 			}
 		}
@@ -70,7 +75,6 @@ namespace Terminal.Gui {
 			AdjustContentView (frame);
 		}
 
-
 		/// <summary>
 		/// ContentView is an internal implementation detail of Window. It is used to host Views added with <see cref="Add(View)"/>. 
 		/// Its ONLY reason for being is to provide a simple way for Window to expose to those SubViews that the Window's Bounds 
@@ -170,6 +174,7 @@ namespace Terminal.Gui {
 		{
 			CanFocus = true;
 			ColorScheme = Colors.Base;
+			if (title == null) title = ustring.Empty;
 			Title = title;
 			if (border == null) {
 				Border = new Border () {
@@ -180,26 +185,30 @@ namespace Terminal.Gui {
 			} else {
 				Border = border;
 			}
+			AdjustContentView (frame);
 		}
 
 		void AdjustContentView (Rect frame)
 		{
 			var borderLength = Border.DrawMarginFrame ? 1 : 0;
 			var sumPadding = Border.GetSumThickness ();
+			var wp = new Point ();
 			var wb = new Size ();
 			if (frame == Rect.Empty) {
+				wp.X = borderLength + sumPadding.Left;
+				wp.Y = borderLength + sumPadding.Top;
 				wb.Width = borderLength + sumPadding.Right;
 				wb.Height = borderLength + sumPadding.Bottom;
 				if (contentView == null) {
 					contentView = new ContentView (this) {
-						X = borderLength + sumPadding.Left,
-						Y = borderLength + sumPadding.Top,
+						X = wp.X,
+						Y = wp.Y,
 						Width = Dim.Fill (wb.Width),
 						Height = Dim.Fill (wb.Height)
 					};
 				} else {
-					contentView.X = borderLength + sumPadding.Left;
-					contentView.Y = borderLength + sumPadding.Top;
+					contentView.X = wp.X;
+					contentView.Y = wp.Y;
 					contentView.Width = Dim.Fill (wb.Width);
 					contentView.Height = Dim.Fill (wb.Height);
 				}
@@ -213,7 +222,8 @@ namespace Terminal.Gui {
 					contentView.Frame = cFrame;
 				}
 			}
-			base.Add (contentView);
+			if (Subviews?.Count == 0)
+				base.Add (contentView);
 			Border.Child = contentView;
 		}
 
@@ -283,15 +293,15 @@ namespace Terminal.Gui {
 
 			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
-			if (Border.BorderStyle != BorderStyle.None) {
-				Driver.SetAttribute (GetNormalColor ());
-				//Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength,
-				//	Border.BorderStyle != BorderStyle.None, fill: true, Border.BorderStyle);
-				Border.DrawContent (this, false);
-				if (HasFocus)
-					Driver.SetAttribute (ColorScheme.HotNormal);
+
+			Driver.SetAttribute (GetNormalColor ());
+			//Driver.DrawWindowFrame (scrRect, padding.Left + borderLength, padding.Top + borderLength, padding.Right + borderLength, padding.Bottom + borderLength,
+			//	Border.BorderStyle != BorderStyle.None, fill: true, Border.BorderStyle);
+			Border.DrawContent (this, false);
+			if (HasFocus)
+				Driver.SetAttribute (ColorScheme.HotNormal);
+			if (Border.DrawMarginFrame)
 				Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
-			}
 			Driver.SetAttribute (GetNormalColor ());
 
 			// Checks if there are any SuperView view which intersect with this window.
@@ -314,7 +324,7 @@ namespace Terminal.Gui {
 		///   The text displayed by the <see cref="Label"/>.
 		/// </summary>
 		public override ustring Text {
-			get => contentView.Text;
+			get => contentView?.Text;
 			set {
 				base.Text = value;
 				if (contentView != null) {
@@ -333,5 +343,70 @@ namespace Terminal.Gui {
 				base.TextAlignment = contentView.TextAlignment = value;
 			}
 		}
+
+		/// <summary>
+		/// An <see cref="EventArgs"/> which allows passing a cancelable new <see cref="Title"/> value event.
+		/// </summary>
+		public class TitleEventArgs : EventArgs {
+			/// <summary>
+			/// The new Window Title.
+			/// </summary>
+			public ustring NewTitle { get; set; }
+
+			/// <summary>
+			/// The old Window Title.
+			/// </summary>
+			public ustring OldTitle { get; set; }
+
+			/// <summary>
+			/// Flag which allows cancelling the Title change.
+			/// </summary>
+			public bool Cancel { get; set; }
+
+			/// <summary>
+			/// Initializes a new instance of <see cref="TitleEventArgs"/>
+			/// </summary>
+			/// <param name="oldTitle">The <see cref="Window.Title"/> that is/has been replaced.</param>
+			/// <param name="newTitle">The new <see cref="Window.Title"/> to be replaced.</param>
+			public TitleEventArgs (ustring oldTitle, ustring newTitle)
+			{
+				OldTitle = oldTitle;
+				NewTitle = newTitle;
+			}
+		}
+		/// <summary>
+		/// Called before the <see cref="Window.Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be cancelled.
+		/// </summary>
+		/// <param name="oldTitle">The <see cref="Window.Title"/> that is/has been replaced.</param>
+		/// <param name="newTitle">The new <see cref="Window.Title"/> to be replaced.</param>
+		/// <returns>`true` if an event handler cancelled the Title change.</returns>
+		public virtual bool OnTitleChanging (ustring oldTitle, ustring newTitle)
+		{
+			var args = new TitleEventArgs (oldTitle, newTitle);
+			TitleChanging?.Invoke (args);
+			return args.Cancel;
+		}
+
+		/// <summary>
+		/// Event fired when the <see cref="Window.Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to 
+		/// `true` to cancel the Title change.
+		/// </summary>
+		public event Action<TitleEventArgs> TitleChanging;
+
+		/// <summary>
+		/// Called when the <see cref="Window.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
+		/// </summary>
+		/// <param name="oldTitle">The <see cref="Window.Title"/> that is/has been replaced.</param>
+		/// <param name="newTitle">The new <see cref="Window.Title"/> to be replaced.</param>
+		public virtual void OnTitleChanged (ustring oldTitle, ustring newTitle)
+		{
+			var args = new TitleEventArgs (oldTitle, newTitle);
+			TitleChanged?.Invoke (args);
+		}
+
+		/// <summary>
+		/// Event fired after the <see cref="Window.Title"/> has been changed. 
+		/// </summary>
+		public event Action<TitleEventArgs> TitleChanged;
 	}
 }

+ 27 - 0
Terminal.Gui/Resources/Strings.Designer.cs

@@ -185,5 +185,32 @@ namespace Terminal.Gui.Resources {
                 return ResourceManager.GetString("fdSelectMixed", resourceCulture);
             }
         }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _Back.
+        /// </summary>
+        internal static string wzBack {
+            get {
+                return ResourceManager.GetString("wzBack", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Fi_nish.
+        /// </summary>
+        internal static string wzFinish {
+            get {
+                return ResourceManager.GetString("wzFinish", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to _Next....
+        /// </summary>
+        internal static string wzNext {
+            get {
+                return ResourceManager.GetString("wzNext", resourceCulture);
+            }
+        }
     }
 }

+ 89 - 60
Terminal.Gui/Resources/Strings.fr-FR.resx

@@ -1,76 +1,96 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
   <!-- 
-		Microsoft ResX Schema
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
 
-		Version 1.3
-
-		The primary goals of this format is to allow a simple XML format 
-		that is mostly human readable. The generation and parsing of the 
-		various data types are done through the TypeConverter classes 
-		associated with the data types.
-
-		Example:
-
-		... ado.net/XML headers & schema ...
-		<resheader name="resmimetype">text/microsoft-resx</resheader>
-		<resheader name="version">1.3</resheader>
-		<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
-		<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
-		<data name="Name1">this is my long string</data>
-		<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
-		<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
-			[base64 mime encoded serialized .NET Framework object]
-		</data>
-		<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-			[base64 mime encoded string representing a byte array form of the .NET Framework object]
-		</data>
-
-		There are any number of "resheader" rows that contain simple 
-		name/value pairs.
-
-		Each data row contains a name, and value. The row also contains a 
-		type or mimetype. Type corresponds to a .NET class that support 
-		text/value conversion through the TypeConverter architecture. 
-		Classes that don't support this are serialized and stored with the 
-		mimetype set.
-
-		The mimetype is used for serialized objects, and tells the 
-		ResXResourceReader how to depersist the object. This is currently not 
-		extensible. For a given mimetype the value must be set accordingly:
-
-		Note - application/x-microsoft.net.object.binary.base64 is the format 
-		that the ResXResourceWriter will generate, however the reader can 
-		read any of the formats listed below.
-
-		mimetype: application/x-microsoft.net.object.binary.base64
-		value   : The object must be serialized with 
-			: System.Serialization.Formatters.Binary.BinaryFormatter
-			: and then encoded with base64 encoding.
-
-		mimetype: application/x-microsoft.net.object.soap.base64
-		value   : The object must be serialized with 
-			: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
-			: and then encoded with base64 encoding.
-
-		mimetype: application/x-microsoft.net.object.bytearray.base64
-		value   : The object must be serialized into a byte array 
-			: using a System.ComponentModel.TypeConverter
-			: and then encoded with base64 encoding.
-	-->
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
     <xsd:element name="root" msdata:IsDataSet="true">
       <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
           <xsd:element name="data">
             <xsd:complexType>
               <xsd:sequence>
                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
               </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
           </xsd:element>
           <xsd:element name="resheader">
@@ -89,13 +109,13 @@
     <value>text/microsoft-resx</value>
   </resheader>
   <resheader name="version">
-    <value>1.3</value>
+    <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <data name="ctxCopy" xml:space="preserve">
     <value>_Copier</value>
@@ -139,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
     <value>Sélection _mixte</value>
   </data>
+  <data name="wzBack" xml:space="preserve">
+    <value>_Précédent</value>
+  </data>
+  <data name="wzFinish" xml:space="preserve">
+    <value>Fi_nir</value>
+  </data>
+  <data name="wzNext" xml:space="preserve">
+    <value>Prochai_n...</value>
+  </data>
 </root>

+ 9 - 0
Terminal.Gui/Resources/Strings.ja-JP.resx

@@ -159,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
     <value>混在選択</value>
   </data>
+  <data name="wzBack" xml:space="preserve">
+    <value>戻る</value>
+  </data>
+  <data name="wzFinish" xml:space="preserve">
+    <value>終える</value>
+  </data>
+  <data name="wzNext" xml:space="preserve">
+    <value>次に</value>
+  </data>
 </root>

+ 89 - 60
Terminal.Gui/Resources/Strings.pt-PT.resx

@@ -1,76 +1,96 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
   <!-- 
-		Microsoft ResX Schema
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
 
-		Version 1.3
-
-		The primary goals of this format is to allow a simple XML format 
-		that is mostly human readable. The generation and parsing of the 
-		various data types are done through the TypeConverter classes 
-		associated with the data types.
-
-		Example:
-
-		... ado.net/XML headers & schema ...
-		<resheader name="resmimetype">text/microsoft-resx</resheader>
-		<resheader name="version">1.3</resheader>
-		<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
-		<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
-		<data name="Name1">this is my long string</data>
-		<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
-		<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
-			[base64 mime encoded serialized .NET Framework object]
-		</data>
-		<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-			[base64 mime encoded string representing a byte array form of the .NET Framework object]
-		</data>
-
-		There are any number of "resheader" rows that contain simple 
-		name/value pairs.
-
-		Each data row contains a name, and value. The row also contains a 
-		type or mimetype. Type corresponds to a .NET class that support 
-		text/value conversion through the TypeConverter architecture. 
-		Classes that don't support this are serialized and stored with the 
-		mimetype set.
-
-		The mimetype is used for serialized objects, and tells the 
-		ResXResourceReader how to depersist the object. This is currently not 
-		extensible. For a given mimetype the value must be set accordingly:
-
-		Note - application/x-microsoft.net.object.binary.base64 is the format 
-		that the ResXResourceWriter will generate, however the reader can 
-		read any of the formats listed below.
-
-		mimetype: application/x-microsoft.net.object.binary.base64
-		value   : The object must be serialized with 
-			: System.Serialization.Formatters.Binary.BinaryFormatter
-			: and then encoded with base64 encoding.
-
-		mimetype: application/x-microsoft.net.object.soap.base64
-		value   : The object must be serialized with 
-			: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
-			: and then encoded with base64 encoding.
-
-		mimetype: application/x-microsoft.net.object.bytearray.base64
-		value   : The object must be serialized into a byte array 
-			: using a System.ComponentModel.TypeConverter
-			: and then encoded with base64 encoding.
-	-->
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
     <xsd:element name="root" msdata:IsDataSet="true">
       <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
           <xsd:element name="data">
             <xsd:complexType>
               <xsd:sequence>
                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
               </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
           </xsd:element>
           <xsd:element name="resheader">
@@ -89,13 +109,13 @@
     <value>text/microsoft-resx</value>
   </resheader>
   <resheader name="version">
-    <value>1.3</value>
+    <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <data name="ctxCopy" xml:space="preserve">
     <value>_Copiar</value>
@@ -139,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
     <value>Selecione misto</value>
   </data>
+  <data name="wzBack" xml:space="preserve">
+    <value>_Voltar</value>
+  </data>
+  <data name="wzFinish" xml:space="preserve">
+    <value>Acabam_ento</value>
+  </data>
+  <data name="wzNext" xml:space="preserve">
+    <value>S_eguir</value>
+  </data>
 </root>

+ 89 - 60
Terminal.Gui/Resources/Strings.resx

@@ -1,76 +1,96 @@
 <?xml version="1.0" encoding="utf-8"?>
 <root>
   <!-- 
-		Microsoft ResX Schema
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
 
-		Version 1.3
-
-		The primary goals of this format is to allow a simple XML format 
-		that is mostly human readable. The generation and parsing of the 
-		various data types are done through the TypeConverter classes 
-		associated with the data types.
-
-		Example:
-
-		... ado.net/XML headers & schema ...
-		<resheader name="resmimetype">text/microsoft-resx</resheader>
-		<resheader name="version">1.3</resheader>
-		<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
-		<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
-		<data name="Name1">this is my long string</data>
-		<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
-		<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
-			[base64 mime encoded serialized .NET Framework object]
-		</data>
-		<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
-			[base64 mime encoded string representing a byte array form of the .NET Framework object]
-		</data>
-
-		There are any number of "resheader" rows that contain simple 
-		name/value pairs.
-
-		Each data row contains a name, and value. The row also contains a 
-		type or mimetype. Type corresponds to a .NET class that support 
-		text/value conversion through the TypeConverter architecture. 
-		Classes that don't support this are serialized and stored with the 
-		mimetype set.
-
-		The mimetype is used for serialized objects, and tells the 
-		ResXResourceReader how to depersist the object. This is currently not 
-		extensible. For a given mimetype the value must be set accordingly:
-
-		Note - application/x-microsoft.net.object.binary.base64 is the format 
-		that the ResXResourceWriter will generate, however the reader can 
-		read any of the formats listed below.
-
-		mimetype: application/x-microsoft.net.object.binary.base64
-		value   : The object must be serialized with 
-			: System.Serialization.Formatters.Binary.BinaryFormatter
-			: and then encoded with base64 encoding.
-
-		mimetype: application/x-microsoft.net.object.soap.base64
-		value   : The object must be serialized with 
-			: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
-			: and then encoded with base64 encoding.
-
-		mimetype: application/x-microsoft.net.object.bytearray.base64
-		value   : The object must be serialized into a byte array 
-			: using a System.ComponentModel.TypeConverter
-			: and then encoded with base64 encoding.
-	-->
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
     <xsd:element name="root" msdata:IsDataSet="true">
       <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
           <xsd:element name="data">
             <xsd:complexType>
               <xsd:sequence>
                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
               </xsd:sequence>
-              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
           </xsd:element>
           <xsd:element name="resheader">
@@ -89,13 +109,13 @@
     <value>text/microsoft-resx</value>
   </resheader>
   <resheader name="version">
-    <value>1.3</value>
+    <value>2.0</value>
   </resheader>
   <resheader name="reader">
-    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <resheader name="writer">
-    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <data name="ctxSelectAll" xml:space="preserve">
     <value>_Select All</value>
@@ -139,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
     <value>Select Mixed</value>
   </data>
+  <data name="wzBack" xml:space="preserve">
+    <value>_Back</value>
+  </data>
+  <data name="wzFinish" xml:space="preserve">
+    <value>Fi_nish</value>
+  </data>
+  <data name="wzNext" xml:space="preserve">
+    <value>_Next...</value>
+  </data>
 </root>

+ 22 - 76
Terminal.Gui/Views/Button.cs

@@ -31,8 +31,11 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </remarks>
 	public class Button : View {
-		ustring text;
 		bool is_default;
+		Rune _leftBracket;
+		Rune _rightBracket;
+		Rune _leftDefault;
+		Rune _rightDefault;
 
 		/// <summary>
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
@@ -92,16 +95,10 @@ namespace Terminal.Gui {
 			Initialize (text, is_default);
 		}
 
-		Rune _leftBracket;
-		Rune _rightBracket;
-		Rune _leftDefault;
-		Rune _rightDefault;
-		private Key hotKey = Key.Null;
-		private Rune hotKeySpecifier;
-
 		void Initialize (ustring text, bool is_default)
 		{
 			TextAlignment = TextAlignment.Centered;
+			VerticalTextAlignment = VerticalTextAlignment.Middle;
 
 			HotKeySpecifier = new Rune ('_');
 
@@ -111,9 +108,11 @@ namespace Terminal.Gui {
 			_rightDefault = new Rune (Driver != null ? Driver.RightDefaultIndicator : '>');
 
 			CanFocus = true;
+			AutoSize = true;
 			this.is_default = is_default;
-			this.text = text ?? string.Empty;
-			Update ();
+			Text = text ?? string.Empty;
+			UpdateTextFormatterText ();
+			ProcessResizeView ();
 
 			// Things this view knows how to do
 			AddCommand (Command.Accept, () => AcceptKey ());
@@ -126,21 +125,6 @@ namespace Terminal.Gui {
 			}
 		}
 
-		/// <inheritdoc/>>
-		public override ustring Text {
-			get {
-				return text;
-			}
-			set {
-				text = value;
-				TextFormatter.FindHotKey (text, HotKeySpecifier, true, out _, out Key hk);
-				if (hotKey != hk) {
-					HotKey = hk;
-				}
-				Update ();
-			}
-		}
-
 		/// <summary>
 		/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
 		/// </summary>
@@ -149,76 +133,38 @@ namespace Terminal.Gui {
 			get => is_default;
 			set {
 				is_default = value;
-				Update ();
+				UpdateTextFormatterText ();
+				ProcessResizeView ();
 			}
 		}
 
 		/// <inheritdoc/>
 		public override Key HotKey {
-			get => hotKey;
+			get => base.HotKey;
 			set {
-				if (hotKey != value) {
+				if (base.HotKey != value) {
 					var v = value == Key.Unknown ? Key.Null : value;
-					if (ContainsKeyBinding (Key.Space | hotKey)) {
+					if (base.HotKey != Key.Null && ContainsKeyBinding (Key.Space | base.HotKey)) {
 						if (v == Key.Null) {
-							ClearKeybinding (Key.Space | hotKey);
+							ClearKeybinding (Key.Space | base.HotKey);
 						} else {
-							ReplaceKeyBinding (Key.Space | hotKey, Key.Space | v);
+							ReplaceKeyBinding (Key.Space | base.HotKey, Key.Space | v);
 						}
 					} else if (v != Key.Null) {
 						AddKeyBinding (Key.Space | v, Command.Accept);
 					}
-					hotKey = v;
+					base.HotKey = TextFormatter.HotKey = v;
 				}
 			}
 		}
 
 		/// <inheritdoc/>
-		public override Rune HotKeySpecifier {
-			get => hotKeySpecifier;
-			set {
-				hotKeySpecifier = TextFormatter.HotKeySpecifier = value;
-			}
-		}
-
-		/// <inheritdoc/>
-		public override bool AutoSize {
-			get => base.AutoSize;
-			set {
-				base.AutoSize = value;
-				Update ();
-			}
-		}
-
-		internal void Update ()
+		protected override void UpdateTextFormatterText ()
 		{
 			if (IsDefault)
-				TextFormatter.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
+				TextFormatter.Text = ustring.Make (_leftBracket) + ustring.Make (_leftDefault) + " " + Text + " " + ustring.Make (_rightDefault) + ustring.Make (_rightBracket);
 			else
-				TextFormatter.Text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket);
-
-			int w = TextFormatter.Size.Width - (TextFormatter.Text.Contains (HotKeySpecifier) ? 1 : 0);
-			GetCurrentWidth (out int cWidth);
-			var canSetWidth = SetWidth (w, out int rWidth);
-			if (canSetWidth && (cWidth < rWidth || AutoSize)) {
-				Width = rWidth;
-				w = rWidth;
-			} else if (!canSetWidth || !AutoSize) {
-				w = cWidth;
-			}
-			var layout = LayoutStyle;
-			bool layoutChanged = false;
-			if (!(Height is Dim.DimAbsolute)) {
-				// The height is always equal to 1 and must be Dim.DimAbsolute.
-				layoutChanged = true;
-				LayoutStyle = LayoutStyle.Absolute;
-			}
-			Height = 1;
-			if (layoutChanged) {
-				LayoutStyle = layout;
-			}
-			Frame = new Rect (Frame.Location, new Size (w, 1));
-			SetNeedsDisplay ();
+				TextFormatter.Text = ustring.Make (_leftBracket) + " " + Text + " " + ustring.Make (_rightBracket);
 		}
 
 		///<inheritdoc/>
@@ -321,9 +267,9 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		public override void PositionCursor ()
 		{
-			if (HotKey == Key.Unknown && text != "") {
+			if (HotKey == Key.Unknown && Text != "") {
 				for (int i = 0; i < TextFormatter.Text.RuneCount; i++) {
-					if (TextFormatter.Text [i] == text [0]) {
+					if (TextFormatter.Text [i] == Text [0]) {
 						Move (i, 0);
 						return;
 					}

+ 38 - 48
Terminal.Gui/Views/Checkbox.cs

@@ -13,9 +13,9 @@ namespace Terminal.Gui {
 	/// The <see cref="CheckBox"/> <see cref="View"/> shows an on/off toggle that the user can set
 	/// </summary>
 	public class CheckBox : View {
-		ustring text;
-		int hot_pos = -1;
-		Rune hot_key;
+		Rune charChecked;
+		Rune charUnChecked;
+		bool @checked;
 
 		/// <summary>
 		///   Toggled event, raised when the <see cref="CheckBox"/>  is toggled.
@@ -68,18 +68,22 @@ namespace Terminal.Gui {
 		///   The size of <see cref="CheckBox"/> is computed based on the
 		///   text length. 
 		/// </remarks>
-		public CheckBox (int x, int y, ustring s, bool is_checked) : base (new Rect (x, y, s.Length + 4, 1))
+		public CheckBox (int x, int y, ustring s, bool is_checked) : base (new Rect (x, y, s.Length, 1))
 		{
 			Initialize (s, is_checked);
 		}
 
 		void Initialize (ustring s, bool is_checked)
 		{
+			charChecked = new Rune (Driver != null ? Driver.Checked : '√');
+			charUnChecked = new Rune (Driver != null ? Driver.UnChecked : '╴');
 			Checked = is_checked;
-			Text = s;
+			HotKeySpecifier = new Rune ('_');
 			CanFocus = true;
-			Height = 1;
-			Width = s.RuneCount + 4;
+			AutoSize = true;
+			Text = s;
+			UpdateTextFormatterText ();
+			ProcessResizeView ();
 
 			// Things this view knows how to do
 			AddCommand (Command.ToggleChecked, () => ToggleChecked ());
@@ -89,52 +93,38 @@ namespace Terminal.Gui {
 			AddKeyBinding (Key.Space, Command.ToggleChecked);
 		}
 
-		/// <summary>
-		///    The state of the <see cref="CheckBox"/>
-		/// </summary>
-		public bool Checked { get; set; }
-
-		/// <summary>
-		///   The text displayed by this <see cref="CheckBox"/>
-		/// </summary>
-		public new ustring Text {
-			get {
-				return text;
+		/// <inheritdoc/>
+		protected override void UpdateTextFormatterText ()
+		{
+			switch (TextAlignment) {
+			case TextAlignment.Left:
+			case TextAlignment.Centered:
+			case TextAlignment.Justified:
+				TextFormatter.Text = ustring.Make (Checked ? charChecked : charUnChecked) + " " + GetFormatterText ();
+				break;
+			case TextAlignment.Right:
+				TextFormatter.Text = GetFormatterText () + " " + ustring.Make (Checked ? charChecked : charUnChecked);
+				break;
 			}
+		}
 
-			set {
-				text = value;
-
-				int i = 0;
-				hot_pos = -1;
-				hot_key = (char)0;
-				foreach (Rune c in text) {
-					//if (Rune.IsUpper (c)) {
-					if (c == '_') {
-						hot_key = text [i + 1];
-						HotKey = (Key)(char)hot_key.ToString ().ToUpper () [0];
-						text = text.ToString ().Replace ("_", "");
-						hot_pos = i;
-						break;
-					}
-					i++;
-				}
+		ustring GetFormatterText ()
+		{
+			if (AutoSize || ustring.IsNullOrEmpty (Text) || Frame.Width <= 2) {
+				return Text;
 			}
+			return Text.RuneSubstring (0, Math.Min (Frame.Width - 2, Text.RuneCount));
 		}
 
-		///<inheritdoc/>
-		public override void Redraw (Rect bounds)
-		{
-			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
-			Move (0, 0);
-			Driver.AddRune (Checked ? Driver.Checked : Driver.UnChecked);
-			Driver.AddRune (' ');
-			Move (2, 0);
-			Driver.AddStr (Text);
-			if (hot_pos != -1) {
-				Move (2 + hot_pos, 0);
-				Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled);
-				Driver.AddRune (hot_key);
+		/// <summary>
+		///    The state of the <see cref="CheckBox"/>
+		/// </summary>
+		public bool Checked {
+			get => @checked;
+			set {
+				@checked = value;
+				UpdateTextFormatterText ();
+				ProcessResizeView ();
 			}
 		}
 

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

@@ -12,7 +12,7 @@ using NStack;
 
 namespace Terminal.Gui {
 	/// <summary>
-	/// ComboBox control
+	/// Provides a drop-down list of items the user can select from.
 	/// </summary>
 	public class ComboBox : View {
 
@@ -109,7 +109,7 @@ namespace Terminal.Gui {
 
 		private void Initialize ()
 		{
-			if (Bounds.Height < minimumHeight && Height is Dim.DimAbsolute) {
+			if (Bounds.Height < minimumHeight && (Height == null || Height is Dim.DimAbsolute)) {
 				Height = minimumHeight;
 			}
 

+ 17 - 14
Terminal.Gui/Views/FrameView.cs

@@ -108,7 +108,8 @@ namespace Terminal.Gui {
 
 		void Initialize (Rect frame, ustring title, View [] views = null, Border border = null)
 		{
-			this.title = title;
+			if (title == null) title = ustring.Empty;
+			this.Title = title;
 			if (border == null) {
 				Border = new Border () {
 					BorderStyle = BorderStyle.Single
@@ -123,20 +124,23 @@ namespace Terminal.Gui {
 		{
 			var borderLength = Border.DrawMarginFrame ? 1 : 0;
 			var sumPadding = Border.GetSumThickness ();
+			var wp = new Point ();
 			var wb = new Size ();
 			if (frame == Rect.Empty) {
+				wp.X = borderLength + sumPadding.Left;
+				wp.Y = borderLength + sumPadding.Top;
 				wb.Width = borderLength + sumPadding.Right;
 				wb.Height = borderLength + sumPadding.Bottom;
 				if (contentView == null) {
 					contentView = new ContentView () {
-						X = borderLength + sumPadding.Left,
-						Y = borderLength + sumPadding.Top,
+						X = wp.X,
+						Y = wp.Y,
 						Width = Dim.Fill (wb.Width),
 						Height = Dim.Fill (wb.Height)
 					};
 				} else {
-					contentView.X = borderLength + sumPadding.Left;
-					contentView.Y = borderLength + sumPadding.Top;
+					contentView.X = wp.X;
+					contentView.Y = wp.Y;
 					contentView.Width = Dim.Fill (wb.Width);
 					contentView.Height = Dim.Fill (wb.Height);
 				}
@@ -224,15 +228,14 @@ namespace Terminal.Gui {
 			Driver.Clip = savedClip;
 
 			ClearNeedsDisplay ();
-			if (Border.BorderStyle != BorderStyle.None) {
-				Driver.SetAttribute (GetNormalColor ());
-				//Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
-				Border.DrawContent (this, false);
-				if (HasFocus)
-					Driver.SetAttribute (ColorScheme.HotNormal);
-				//Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding);
+
+			Driver.SetAttribute (GetNormalColor ());
+			//Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false);
+			Border.DrawContent (this, false);
+			if (HasFocus)
+				Driver.SetAttribute (ColorScheme.HotNormal);
+			if (Border.DrawMarginFrame)
 				Driver.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
-			}
 			Driver.SetAttribute (GetNormalColor ());
 		}
 
@@ -240,7 +243,7 @@ namespace Terminal.Gui {
 		///   The text displayed by the <see cref="Label"/>.
 		/// </summary>
 		public override ustring Text {
-			get => contentView.Text;
+			get => contentView?.Text;
 			set {
 				base.Text = value;
 				if (contentView != null) {

+ 22 - 4
Terminal.Gui/Views/Label.cs

@@ -6,9 +6,6 @@
 //
 
 using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
 using NStack;
 
 namespace Terminal.Gui {
@@ -110,7 +107,7 @@ namespace Terminal.Gui {
 					SetNeedsDisplay ();
 				}
 
-				Clicked?.Invoke ();
+				OnClicked ();
 				return true;
 			}
 			return false;
@@ -123,5 +120,26 @@ namespace Terminal.Gui {
 
 			return base.OnEnter (view);
 		}
+
+		///<inheritdoc/>
+		public override bool ProcessHotKey (KeyEvent ke)
+		{
+			if (ke.Key == (Key.AltMask | HotKey)) {
+				if (!HasFocus) {
+					SetFocus ();
+				}
+				OnClicked ();
+				return true;
+			}
+			return base.ProcessHotKey (ke);
+		}
+
+		/// <summary>
+		/// Virtual method to invoke the <see cref="Clicked"/> event.
+		/// </summary>
+		public virtual void OnClicked ()
+		{
+			Clicked?.Invoke ();
+		}
 	}
 }

+ 55 - 23
Terminal.Gui/Views/Menu.cs

@@ -359,7 +359,7 @@ namespace Terminal.Gui {
 		void SetTitle (ustring title)
 		{
 			if (title == null)
-				title = "";
+				title = ustring.Empty;
 			Title = title;
 		}
 
@@ -421,9 +421,12 @@ namespace Terminal.Gui {
 			AddCommand (Command.LineDown, () => MoveDown ());
 			AddCommand (Command.Left, () => { this.host.PreviousMenu (true); return true; });
 			AddCommand (Command.Right, () => {
-				this.host.NextMenu (this.barItems.IsTopLevel || (this.barItems.Children != null
-					&& current > -1 && current < this.barItems.Children.Length && this.barItems.Children [current].IsFromSubMenu),
-					current > -1 && host.UseSubMenusSingleFrame && this.barItems.SubMenu (this.barItems.Children [current]) != null);
+				this.host.NextMenu (!this.barItems.IsTopLevel || (this.barItems.Children != null
+					&& this.barItems.Children.Length > 0 && current > -1
+					&& current < this.barItems.Children.Length && this.barItems.Children [current].IsFromSubMenu),
+					this.barItems.Children != null && this.barItems.Children.Length > 0 && current > -1
+					&& host.UseSubMenusSingleFrame && this.barItems.SubMenu (this.barItems.Children [current]) != null);
+
 				return true;
 			});
 			AddCommand (Command.Cancel, () => { CloseAllMenus (); return true; });
@@ -654,7 +657,7 @@ namespace Terminal.Gui {
 				if (current >= barItems.Children.Length) {
 					current = 0;
 				}
-				if (this != host.openCurrentMenu && barItems.Children [current].IsFromSubMenu && host.selectedSub > -1) {
+				if (this != host.openCurrentMenu && barItems.Children [current]?.IsFromSubMenu == true && host.selectedSub > -1) {
 					host.PreviousMenu (true);
 					host.SelectEnabledItem (barItems.Children, current, out current);
 					host.openCurrentMenu = this;
@@ -823,7 +826,7 @@ namespace Terminal.Gui {
 
 
 	/// <summary>
-	/// The MenuBar provides a menu for Terminal.Gui applications. 
+	/// Provides a menu bar with drop-down and cascading menus. 
 	/// </summary>
 	/// <remarks>
 	///	<para>
@@ -843,10 +846,21 @@ namespace Terminal.Gui {
 		/// <value>The menu array.</value>
 		public MenuBarItem [] Menus { get; set; }
 
+		private bool useKeysUpDownAsKeysLeftRight = false;
+
 		/// <summary>
 		/// Used for change the navigation key style.
 		/// </summary>
-		public bool UseKeysUpDownAsKeysLeftRight { get; set; } = true;
+		public bool UseKeysUpDownAsKeysLeftRight {
+			get => useKeysUpDownAsKeysLeftRight;
+			set {
+				useKeysUpDownAsKeysLeftRight = value;
+				if (value && UseSubMenusSingleFrame) {
+					UseSubMenusSingleFrame = false;
+					SetNeedsDisplay ();
+				}
+			}
+		}
 
 		static ustring shortcutDelimiter = "+";
 		/// <summary>
@@ -866,10 +880,21 @@ namespace Terminal.Gui {
 		/// </summary>
 		new public static Rune HotKeySpecifier => '_';
 
+		private bool useSubMenusSingleFrame;
+
 		/// <summary>
 		/// Gets or sets if the sub-menus must be displayed in a single or multiple frames.
 		/// </summary>
-		public bool UseSubMenusSingleFrame { get; set; }
+		public bool UseSubMenusSingleFrame {
+			get => useSubMenusSingleFrame;
+			set {
+				useSubMenusSingleFrame = value;
+				if (value && UseKeysUpDownAsKeysLeftRight) {
+					useKeysUpDownAsKeysLeftRight = false;
+					SetNeedsDisplay ();
+				}
+			}
+		}
 
 		/// <summary>
 		/// Initializes a new instance of the <see cref="MenuBar"/>.
@@ -1122,8 +1147,12 @@ namespace Terminal.Gui {
 		public virtual void OnMenuOpened ()
 		{
 			MenuItem mi = null;
-			if (openCurrentMenu.barItems.Children != null) {
+			if (openCurrentMenu.barItems.Children != null && openCurrentMenu?.current > -1) {
 				mi = openCurrentMenu.barItems.Children [openCurrentMenu.current];
+			} else if (openCurrentMenu.barItems.IsTopLevel) {
+				mi = openCurrentMenu.barItems;
+			} else {
+				mi = openMenu.barItems.Children [openMenu.current];
 			}
 			MenuOpened?.Invoke (mi);
 		}
@@ -1270,11 +1299,6 @@ namespace Terminal.Gui {
 				previousFocused = SuperView == null ? Application.Current.Focused : SuperView.Focused;
 
 			OpenMenu (idx, sIdx, subMenu);
-			if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)
-				&& subMenu == null && !CloseMenu (false)) {
-
-				return;
-			}
 			SetNeedsDisplay ();
 		}
 
@@ -1395,7 +1419,9 @@ namespace Terminal.Gui {
 
 		void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false)
 		{
-			if (openSubMenu == null || (UseSubMenusSingleFrame && !ignoreUseSubMenusSingleFrame && openSubMenu.Count > 0))
+			if (openSubMenu == null || (UseSubMenusSingleFrame
+				&& !ignoreUseSubMenusSingleFrame && openSubMenu.Count == 0))
+
 				return;
 			for (int i = openSubMenu.Count - 1; i > index; i--) {
 				isMenuClosing = true;
@@ -1503,9 +1529,6 @@ namespace Terminal.Gui {
 				OpenMenu (selected);
 				if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current, false)) {
 					openCurrentMenu.current = 0;
-					if (!SelectEnabledItem (openCurrentMenu.barItems.Children, openCurrentMenu.current, out openCurrentMenu.current)) {
-						CloseMenu (ignoreUseSubMenusSingleFrame);
-					}
 				}
 				break;
 			case true:
@@ -1542,18 +1565,27 @@ namespace Terminal.Gui {
 						NextMenu (false, ignoreUseSubMenusSingleFrame);
 					}
 				} else {
-					var subMenu = openCurrentMenu.barItems.SubMenu (openCurrentMenu.barItems.Children [openCurrentMenu.current]);
+					var subMenu = openCurrentMenu.current > -1 && openCurrentMenu.barItems.Children.Length > 0
+						? openCurrentMenu.barItems.SubMenu (openCurrentMenu.barItems.Children [openCurrentMenu.current])
+						: null;
 					if ((selectedSub == -1 || openSubMenu == null || openSubMenu?.Count == selectedSub) && subMenu == null) {
 						if (openSubMenu != null && !CloseMenu (false, true))
 							return;
 						NextMenu (false, ignoreUseSubMenusSingleFrame);
-					} else if (subMenu != null ||
-						!openCurrentMenu.barItems.Children [openCurrentMenu.current].IsFromSubMenu)
+					} else if (subMenu != null || (openCurrentMenu.current > -1
+						&& !openCurrentMenu.barItems.Children [openCurrentMenu.current].IsFromSubMenu)) {
 						selectedSub++;
-					else
+						openCurrentMenu.CheckSubMenu ();
+					} else {
+						if (CloseMenu (false, true, ignoreUseSubMenusSingleFrame)) {
+							NextMenu (false, ignoreUseSubMenusSingleFrame);
+						}
 						return;
+					}
+
 					SetNeedsDisplay ();
-					openCurrentMenu.CheckSubMenu ();
+					if (UseKeysUpDownAsKeysLeftRight)
+						openCurrentMenu.CheckSubMenu ();
 				}
 				break;
 			}

+ 4 - 4
Terminal.Gui/Views/PanelView.cs

@@ -68,10 +68,10 @@ namespace Terminal.Gui {
 				}
 				child = value;
 				savedChild = new SavedPosDim () {
-					X = child?.X,
-					Y = child?.Y,
-					Width = child?.Width,
-					Height = child?.Height
+					X = child?.X ?? child?.Frame.X,
+					Y = child?.Y ?? child?.Frame.Y,
+					Width = child?.Width ?? child?.Frame.Width,
+					Height = child?.Height ?? child?.Frame.Height
 				};
 				if (child == null) {
 					Visible = false;

+ 21 - 15
Terminal.Gui/Views/RadioGroup.cs

@@ -5,7 +5,7 @@ using System.Linq;
 
 namespace Terminal.Gui {
 	/// <summary>
-	/// <see cref="RadioGroup"/> shows a group of radio labels, only one of those can be selected at a given time
+	/// Displays a group of labels each with a selected indicator. Only one of those can be selected at a given time.
 	/// </summary>
 	public class RadioGroup : View {
 		int selected = -1;
@@ -26,7 +26,7 @@ namespace Terminal.Gui {
 		/// <param name="selected">The index of the item to be selected, the value is clamped to the number of items.</param>
 		public RadioGroup (ustring [] radioLabels, int selected = 0) : base ()
 		{
-			Initialize (radioLabels, selected);
+			Initialize (Rect.Empty, radioLabels, selected);
 		}
 
 		/// <summary>
@@ -37,7 +37,7 @@ namespace Terminal.Gui {
 		/// <param name="selected">The index of item to be selected, the value is clamped to the number of items.</param>
 		public RadioGroup (Rect rect, ustring [] radioLabels, int selected = 0) : base (rect)
 		{
-			Initialize (radioLabels, selected);
+			Initialize (rect, radioLabels, selected);
 		}
 
 		/// <summary>
@@ -52,7 +52,7 @@ namespace Terminal.Gui {
 			this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
 		{ }
 
-		void Initialize (ustring [] radioLabels, int selected)
+		void Initialize (Rect rect, ustring [] radioLabels, int selected)
 		{
 			if (radioLabels == null) {
 				this.radioLabels = new List<ustring> ();
@@ -61,7 +61,11 @@ namespace Terminal.Gui {
 			}
 
 			this.selected = selected;
-			SetWidthHeight (this.radioLabels);
+			if (rect == Rect.Empty) {
+				SetWidthHeight (this.radioLabels);
+			} else {
+				Frame = rect;
+			}
 			CanFocus = true;
 
 			// Things this view knows how to do
@@ -102,6 +106,7 @@ namespace Terminal.Gui {
 				if (horizontalSpace != value && displayMode == DisplayModeLayout.Horizontal) {
 					horizontalSpace = value;
 					SetWidthHeight (radioLabels);
+					UpdateTextFormatterText ();
 					SetNeedsDisplay ();
 				}
 			}
@@ -112,7 +117,7 @@ namespace Terminal.Gui {
 			switch (displayMode) {
 			case DisplayModeLayout.Vertical:
 				var r = MakeRect (0, 0, radioLabels);
-				if (LayoutStyle == LayoutStyle.Computed) {
+				if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
 					Width = r.Width;
 					Height = radioLabels.Count;
 				} else {
@@ -126,9 +131,11 @@ namespace Terminal.Gui {
 					length += item.length;
 				}
 				var hr = new Rect (0, 0, length, 1);
-				if (LayoutStyle == LayoutStyle.Computed) {
+				if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
 					Width = hr.Width;
 					Height = 1;
+				} else {
+					Frame = new Rect (Frame.Location, new Size (hr.Width, radioLabels.Count));
 				}
 				break;
 			}
@@ -136,18 +143,17 @@ namespace Terminal.Gui {
 
 		static Rect MakeRect (int x, int y, List<ustring> radioLabels)
 		{
-			int width = 0;
-
 			if (radioLabels == null) {
-				return new Rect (x, y, width, 0);
+				return new Rect (x, y, 0, 0);
 			}
 
+			int width = 0;
+
 			foreach (var s in radioLabels)
-				width = Math.Max (s.RuneCount + 3, width);
+				width = Math.Max (s.ConsoleWidth + 3, width);
 			return new Rect (x, y, width, radioLabels.Count);
 		}
 
-
 		List<ustring> radioLabels = new List<ustring> ();
 
 		/// <summary>
@@ -176,7 +182,7 @@ namespace Terminal.Gui {
 				int length = 0;
 				for (int i = 0; i < radioLabels.Count; i++) {
 					start += length;
-					length = radioLabels [i].RuneCount + horizontalSpace;
+					length = radioLabels [i].ConsoleWidth + 2 + (i < radioLabels.Count - 1 ? horizontalSpace : 0);
 					horizontal.Add ((start, length));
 				}
 			}
@@ -188,7 +194,7 @@ namespace Terminal.Gui {
 		//	for (int i = 0; i < radioLabels.Count; i++) {
 		//		Move(0, i);
 		//		Driver.SetAttribute(ColorScheme.Normal);
-		//		Driver.AddStr(ustring.Make(new string (' ', radioLabels[i].RuneCount + 4)));
+		//		Driver.AddStr(ustring.Make(new string (' ', radioLabels[i].ConsoleWidth + 4)));
 		//	}
 		//	if (newRadioLabels.Count != radioLabels.Count) {
 		//		SetWidthHeight(newRadioLabels);
@@ -210,7 +216,7 @@ namespace Terminal.Gui {
 					break;
 				}
 				Driver.SetAttribute (GetNormalColor ());
-				Driver.AddStr (ustring.Make (new Rune [] { (i == selected ? Driver.Selected : Driver.UnSelected), ' ' }));
+				Driver.AddStr (ustring.Make (new Rune [] { i == selected ? Driver.Selected : Driver.UnSelected, ' ' }));
 				DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme);
 			}
 		}

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

@@ -380,7 +380,7 @@ namespace Terminal.Gui {
 		{
 			int barsize = scrollBarView.vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
 
-			if (barsize == 0 || barsize > scrollBarView.size) {
+			if (barsize == 0 || barsize >= scrollBarView.size) {
 				if (scrollBarView.showScrollIndicator) {
 					scrollBarView.ShowScrollIndicator = false;
 				}

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

@@ -193,7 +193,9 @@ namespace Terminal.Gui {
 			if (Tabs.Any ()) {
 				tabsBar.Redraw (tabsBar.Bounds);
 				contentView.SetNeedsDisplay ();
+				var savedClip = contentView.ClipToBounds ();
 				contentView.Redraw (contentView.Bounds);
+				Driver.Clip = savedClip;
 			}
 		}
 
@@ -338,11 +340,11 @@ namespace Terminal.Gui {
 				var tabTextWidth = tab.Text.Sum (c => Rune.ColumnWidth (c));
 
 				string text = tab.Text.ToString ();
-				
+
 				// The maximum number of characters to use for the tab name as specified
 				// by the user (MaxTabTextWidth).  But not more than the width of the view
 				// or we won't even be able to render a single tab!
-				var maxWidth = Math.Max(0,Math.Min(bounds.Width-3, MaxTabTextWidth));
+				var maxWidth = Math.Max (0, Math.Min (bounds.Width - 3, MaxTabTextWidth));
 
 				// if tab view is width <= 3 don't render any tabs
 				if (maxWidth == 0)

+ 86 - 14
Terminal.Gui/Views/TableView.cs

@@ -68,6 +68,12 @@ namespace Terminal.Gui {
 		/// </summary>
 		public const int DefaultMaxCellWidth = 100;
 
+
+		/// <summary>
+		/// The default minimum cell width for <see cref="ColumnStyle.MinAcceptableWidth"/>
+		/// </summary>
+		public const int DefaultMinAcceptableWidth = 100;
+
 		/// <summary>
 		/// The data table to render in the view.  Setting this property automatically updates and redraws the control.
 		/// </summary>
@@ -480,6 +486,8 @@ namespace Terminal.Gui {
 		}
 		private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender)
 		{
+			var focused = HasFocus;
+
 			var rowScheme = (Style.RowColorGetter?.Invoke (
 				new RowColorGetterArgs(Table,rowToRender))) ?? ColorScheme;
 
@@ -489,8 +497,18 @@ namespace Terminal.Gui {
 
 			//start by clearing the entire line
 			Move (0, row);
-			Driver.SetAttribute (FullRowSelect && IsSelected (0, rowToRender) ? rowScheme.HotFocus
-				: Enabled ? rowScheme.Normal : rowScheme.Disabled);
+
+			Attribute color;
+
+			if(FullRowSelect && IsSelected (0, rowToRender)) {
+				color = focused ? rowScheme.HotFocus : rowScheme.HotNormal;
+			}
+			else 
+			{
+				color = Enabled ? rowScheme.Normal : rowScheme.Disabled;
+			}
+
+			Driver.SetAttribute (color);
 			Driver.AddStr (new string (' ', Bounds.Width));
 
 			// Render cells for each visible header for the current row
@@ -530,7 +548,12 @@ namespace Terminal.Gui {
 					scheme = rowScheme;
 				}
 
-				var cellColor = isSelectedCell ? scheme.HotFocus : Enabled ? scheme.Normal : scheme.Disabled;
+				Attribute cellColor;
+				if (isSelectedCell) {
+					cellColor = focused ? scheme.HotFocus : scheme.HotNormal;
+				} else {
+					cellColor = Enabled ? scheme.Normal : scheme.Disabled;
+				}
 
 				var render = TruncateOrPad (val, representation, current.Width, colStyle);
 
@@ -541,8 +564,14 @@ namespace Terminal.Gui {
 								
 				// Reset color scheme to normal for drawing separators if we drew text with custom scheme
 				if (scheme != rowScheme) {
-					Driver.SetAttribute (isSelectedCell ? rowScheme.HotFocus
-						: Enabled ? rowScheme.Normal : rowScheme.Disabled);
+
+					if(isSelectedCell) {
+						color = focused ? rowScheme.HotFocus : rowScheme.HotNormal;
+					}
+					else {
+						color = Enabled ? rowScheme.Normal : rowScheme.Disabled;
+					}
+					Driver.SetAttribute (color);
 				}
 
 				// If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell
@@ -840,11 +869,11 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		private TableSelection CreateTableSelection (int pt1X, int pt1Y, int pt2X, int pt2Y)
 		{
-			var top = Math.Min (pt1Y, pt2Y);
-			var bot = Math.Max (pt1Y, pt2Y);
+			var top = Math.Max(Math.Min (pt1Y, pt2Y), 0);
+			var bot = Math.Max(Math.Max (pt1Y, pt2Y), 0);
 
-			var left = Math.Min (pt1X, pt2X);
-			var right = Math.Max (pt1X, pt2X);
+			var left = Math.Max(Math.Min (pt1X, pt2X), 0);
+			var right = Math.Max(Math.Max (pt1X, pt2X), 0);
 
 			// Rect class is inclusive of Top Left but exclusive of Bottom Right so extend by 1
 			return new TableSelection (new Point (pt1X, pt1Y), new Rect (left, top, right - left + 1, bot - top + 1));
@@ -1214,11 +1243,40 @@ namespace Terminal.Gui {
 				int colWidth;
 
 				// is there enough space for this column (and it's data)?
-				usedSpace += colWidth = CalculateMaxCellWidth (col, rowsToRender, colStyle) + padding;
+				colWidth = CalculateMaxCellWidth (col, rowsToRender, colStyle) + padding;
 
-				// no (don't render it) unless its the only column we are render (that must be one massively wide column!)
-				if (!first && usedSpace > availableHorizontalSpace)
-					yield break;
+				// there is not enough space for this columns 
+				// visible content
+				if (usedSpace + colWidth > availableHorizontalSpace)
+				{
+					bool showColumn = false;
+
+					// if this column accepts flexible width rendering and
+					// is therefore happy rendering into less space
+					if ( colStyle != null && colStyle.MinAcceptableWidth > 0 &&
+						// is there enough space to meet the MinAcceptableWidth
+						(availableHorizontalSpace - usedSpace) >= colStyle.MinAcceptableWidth)
+					{
+						// show column and use use whatever space is 
+						// left for rendering it
+						showColumn = true;
+						colWidth = availableHorizontalSpace - usedSpace;
+					}
+
+					// If its the only column we are able to render then
+					// accept it anyway (that must be one massively wide column!)
+					if (first)
+					{
+						showColumn = true;
+					}
+
+					// no special exceptions and we are out of space
+					// so stop accepting new columns for the render area
+					if(!showColumn)
+						break;
+				}
+
+				usedSpace += colWidth;
 
 				// there is space
 				yield return new ColumnToRender (col, startingIdxForCurrentHeader,
@@ -1351,10 +1409,24 @@ namespace Terminal.Gui {
 			public int MaxWidth { get; set; } = TableView.DefaultMaxCellWidth;
 
 			/// <summary>
-			/// Set the minimum width of the column in characters.  This value will be ignored if more than the tables <see cref="TableView.MaxCellWidth"/> or the <see cref="MaxWidth"/>
+			/// Set the minimum width of the column in characters.  Setting this will ensure that
+			/// even when a column has short content/header it still fills a given width of the control.
+			/// 
+			/// <para>This value will be ignored if more than the tables <see cref="TableView.MaxCellWidth"/> 
+			/// or the <see cref="MaxWidth"/>
+			/// </para>
+			/// <remarks>
+			/// For setting a flexible column width (down to a lower limit) use <see cref="MinAcceptableWidth"/>
+			/// instead
+			/// </remarks>
 			/// </summary>
 			public int MinWidth { get; set; }
 
+			/// <summary>
+			/// Enables flexible sizing of this column based on available screen space to render into.
+			/// </summary>
+			public int MinAcceptableWidth { get; set; } = DefaultMinAcceptableWidth;
+
 			/// <summary>
 			/// Returns the alignment for the cell based on <paramref name="cellValue"/> and <see cref="AlignmentGetter"/>/<see cref="Alignment"/>
 			/// </summary>

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

@@ -474,6 +474,9 @@ namespace Terminal.Gui {
 
 		void Adjust ()
 		{
+			if (!IsAdded)
+				return;
+
 			int offB = OffSetBackground ();
 			if (point < first) {
 				first = point;

+ 137 - 37
Terminal.Gui/Views/TextView.cs

@@ -28,6 +28,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Text;
 using System.Threading;
 using NStack;
@@ -1371,6 +1372,8 @@ namespace Terminal.Gui {
 
 		private void HistoryText_ChangeText (HistoryText.HistoryTextItem obj)
 		{
+			SetWrapModel ();
+
 			var startLine = obj.CursorPosition.Y;
 
 			if (obj.RemovedOnAdded != null) {
@@ -1402,6 +1405,9 @@ namespace Terminal.Gui {
 			}
 
 			CursorPosition = obj.FinalCursorPosition;
+
+			UpdateWrapModel ();
+
 			Adjust ();
 		}
 
@@ -1659,8 +1665,10 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets a value indicating whether pressing the TAB key in a <see cref="TextView"/>
-		/// types a TAB character in the view instead of moving the focus to the next view in the tab order.
+		/// Gets or sets whether the <see cref="TextView"/> inserts a tab character into the text or ignores 
+		/// tab input. If set to `false` and the user presses the tab key (or shift-tab) the focus will move to the
+		/// next view (or previous with shift-tab). The default is `true`; if the user presses the tab key, a tab 
+		/// character will be inserted into the text.
 		/// </summary>
 		public bool AllowsTab {
 			get => allowsTab;
@@ -2019,6 +2027,8 @@ namespace Terminal.Gui {
 		//
 		void ClearRegion ()
 		{
+			SetWrapModel ();
+
 			long start, end;
 			long currentEncoded = ((long)(uint)currentRow << 32) | (uint)currentColumn;
 			GetEncodedRegionBounds (out start, out end);
@@ -2045,6 +2055,8 @@ namespace Terminal.Gui {
 
 				historyText.Add (new List<List<Rune>> (removedLines), CursorPosition, HistoryText.LineStatus.Removed);
 
+				UpdateWrapModel ();
+
 				return;
 			}
 
@@ -2067,6 +2079,8 @@ namespace Terminal.Gui {
 			historyText.Add (new List<List<Rune>> (removedLines), CursorPosition,
 				HistoryText.LineStatus.Removed);
 
+			UpdateWrapModel ();
+
 			SetNeedsDisplay ();
 		}
 
@@ -2209,12 +2223,19 @@ namespace Terminal.Gui {
 			}
 		}
 
+		string currentCaller;
+
 		/// <summary>
 		/// Restore from original model.
 		/// </summary>
-		void SetWrapModel ()
+		void SetWrapModel ([CallerMemberName] string caller = null)
 		{
+			if (currentCaller != null)
+				return;
+
 			if (wordWrap) {
+				currentCaller = caller;
+
 				currentColumn = wrapManager.GetModelColFromWrappedLines (currentRow, currentColumn);
 				currentRow = wrapManager.GetModelLineFromWrappedLines (currentRow);
 				selectionStartColumn = wrapManager.GetModelColFromWrappedLines (selectionStartRow, selectionStartColumn);
@@ -2226,9 +2247,14 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// Update the original model.
 		/// </summary>
-		void UpdateWrapModel ()
+		void UpdateWrapModel ([CallerMemberName] string caller = null)
 		{
+			if (currentCaller != null && currentCaller != caller)
+				return;
+
 			if (wordWrap) {
+				currentCaller = null;
+
 				wrapManager.UpdateModel (model, out int nRow, out int nCol,
 					out int nStartRow, out int nStartCol,
 					currentRow, currentColumn,
@@ -2239,6 +2265,8 @@ namespace Terminal.Gui {
 				selectionStartColumn = nStartCol;
 				wrapNeeded = true;
 			}
+			if (currentCaller != null)
+				throw new InvalidOperationException ($"WordWrap settings was changed after the {currentCaller} call.");
 		}
 
 		///<inheritdoc/>
@@ -2369,20 +2397,9 @@ namespace Terminal.Gui {
 				}
 				line.Insert (Math.Min (currentColumn, line.Count), rune);
 			}
-			if (wordWrap) {
-				if (Used) {
-					wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune);
-				} else {
-					wrapNeeded = wrapManager.RemoveAt (currentRow, currentColumn);
-					wrapNeeded = wrapManager.Insert (currentRow, currentColumn, rune);
-				}
-				if (wrapNeeded) {
-					SetNeedsDisplay ();
-				}
-			}
 			var prow = currentRow - topRow;
 			if (!wrapNeeded) {
-				SetNeedsDisplay (new Rect (0, prow, Frame.Width, prow + 1));
+				SetNeedsDisplay (new Rect (0, prow, Math.Max (Frame.Width, 0), Math.Max (prow + 1, 0)));
 			}
 		}
 
@@ -2422,6 +2439,8 @@ namespace Terminal.Gui {
 				return;
 			}
 
+			SetWrapModel ();
+
 			var line = GetCurrentLine ();
 
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (line) }, CursorPosition);
@@ -2442,6 +2461,9 @@ namespace Terminal.Gui {
 				} else {
 					SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Math.Max (currentRow - topRow + 1, 0)));
 				}
+
+				UpdateWrapModel ();
+
 				return;
 			}
 
@@ -2484,6 +2506,8 @@ namespace Terminal.Gui {
 
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (line) }, CursorPosition,
 				HistoryText.LineStatus.Replaced);
+
+			UpdateWrapModel ();
 		}
 
 		// The column we are tracking, or -1 if we are not tracking any column
@@ -2612,7 +2636,7 @@ namespace Terminal.Gui {
 
 		void RedoChanges ()
 		{
-			if (ReadOnly || wordWrap)
+			if (ReadOnly)
 				return;
 
 			historyText.Redo ();
@@ -2620,7 +2644,7 @@ namespace Terminal.Gui {
 
 		void UndoChanges ()
 		{
-			if (ReadOnly || wordWrap)
+			if (ReadOnly)
 				return;
 
 			historyText.Undo ();
@@ -2934,10 +2958,12 @@ namespace Terminal.Gui {
 		{
 			ResetColumnTrack ();
 
-			if (!AllowsTab) {
-				return false;
+			if (!AllowsTab || isReadOnly) {
+				return ProcessMovePreviousView ();
 			}
 			if (currentColumn > 0) {
+				SetWrapModel ();
+
 				var currentLine = GetCurrentLine ();
 				if (currentLine.Count > 0 && currentLine [currentColumn - 1] == '\t') {
 
@@ -2949,6 +2975,8 @@ namespace Terminal.Gui {
 					historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 						HistoryText.LineStatus.Replaced);
 				}
+
+				UpdateWrapModel ();
 			}
 			DoNeededAction ();
 			return true;
@@ -2958,8 +2986,8 @@ namespace Terminal.Gui {
 		{
 			ResetColumnTrack ();
 
-			if (!AllowsTab) {
-				return false;
+			if (!AllowsTab || isReadOnly) {
+				return ProcessMoveNextView ();
 			}
 			InsertText (new KeyEvent ((Key)'\t', null));
 			DoNeededAction ();
@@ -2977,11 +3005,11 @@ namespace Terminal.Gui {
 		{
 			ResetColumnTrack ();
 
-			if (!AllowsReturn) {
+			if (!AllowsReturn || isReadOnly) {
 				return false;
 			}
-			if (isReadOnly)
-				return true;
+
+			SetWrapModel ();
 
 			var currentLine = GetCurrentLine ();
 
@@ -3003,10 +3031,6 @@ namespace Terminal.Gui {
 
 			historyText.Add (addedLines, CursorPosition, HistoryText.LineStatus.Added);
 
-			if (wordWrap) {
-				wrapManager.AddLine (currentRow, currentColumn);
-				wrapNeeded = true;
-			}
 			currentRow++;
 
 			bool fullNeedsDisplay = false;
@@ -3029,6 +3053,8 @@ namespace Terminal.Gui {
 			else
 				SetNeedsDisplay (new Rect (0, currentRow - topRow, 2, Frame.Height));
 
+			UpdateWrapModel ();
+
 			DoNeededAction ();
 			return true;
 		}
@@ -3037,6 +3063,9 @@ namespace Terminal.Gui {
 		{
 			if (isReadOnly)
 				return;
+
+			SetWrapModel ();
+
 			var currentLine = GetCurrentLine ();
 
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition);
@@ -3047,6 +3076,8 @@ namespace Terminal.Gui {
 				historyText.ReplaceLast (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
 
+				UpdateWrapModel ();
+
 				return;
 			}
 			var newPos = WordBackward (currentColumn, currentRow);
@@ -3070,6 +3101,8 @@ namespace Terminal.Gui {
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 				HistoryText.LineStatus.Replaced);
 
+			UpdateWrapModel ();
+
 			if (wrapNeeded) {
 				SetNeedsDisplay ();
 			} else {
@@ -3082,6 +3115,9 @@ namespace Terminal.Gui {
 		{
 			if (isReadOnly)
 				return;
+
+			SetWrapModel ();
+
 			var currentLine = GetCurrentLine ();
 
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition);
@@ -3092,6 +3128,8 @@ namespace Terminal.Gui {
 				historyText.ReplaceLast (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
 
+				UpdateWrapModel ();
+
 				return;
 			}
 			var newPos = WordForward (currentColumn, currentRow);
@@ -3110,6 +3148,8 @@ namespace Terminal.Gui {
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 				HistoryText.LineStatus.Replaced);
 
+			UpdateWrapModel ();
+
 			if (wrapNeeded) {
 				SetNeedsDisplay ();
 			} else {
@@ -3149,9 +3189,13 @@ namespace Terminal.Gui {
 				return;
 			}
 
+			SetWrapModel ();
+
 			var currentLine = GetCurrentLine ();
 			var setLastWasKill = true;
 			if (currentLine.Count > 0 && currentColumn == 0) {
+				UpdateWrapModel ();
+
 				DeleteTextBackwards ();
 				return;
 			}
@@ -3203,7 +3247,12 @@ namespace Terminal.Gui {
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 				HistoryText.LineStatus.Replaced);
 
-			SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+			UpdateWrapModel ();
+
+			if (wrapNeeded)
+				SetNeedsDisplay ();
+			else
+				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
 			lastWasKill = setLastWasKill;
 			DoNeededAction ();
 		}
@@ -3217,9 +3266,13 @@ namespace Terminal.Gui {
 				return;
 			}
 
+			SetWrapModel ();
+
 			var currentLine = GetCurrentLine ();
 			var setLastWasKill = true;
 			if (currentLine.Count > 0 && currentColumn == currentLine.Count) {
+				UpdateWrapModel ();
+
 				DeleteTextForwards ();
 				return;
 			}
@@ -3265,7 +3318,12 @@ namespace Terminal.Gui {
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 				HistoryText.LineStatus.Replaced);
 
-			SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
+			UpdateWrapModel ();
+
+			if (wrapNeeded)
+				SetNeedsDisplay ();
+			else
+				SetNeedsDisplay (new Rect (0, currentRow - topRow, Frame.Width, Frame.Height));
 			lastWasKill = setLastWasKill;
 			DoNeededAction ();
 		}
@@ -3293,6 +3351,9 @@ namespace Terminal.Gui {
 		{
 			if (isReadOnly)
 				return;
+
+			SetWrapModel ();
+
 			if (selecting) {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 					HistoryText.LineStatus.Original);
@@ -3304,11 +3365,18 @@ namespace Terminal.Gui {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
 
+				UpdateWrapModel ();
+
 				return;
 			}
 			if (DeleteTextForwards ()) {
+				UpdateWrapModel ();
+
 				return;
 			}
+
+			UpdateWrapModel ();
+
 			DoNeededAction ();
 		}
 
@@ -3319,6 +3387,9 @@ namespace Terminal.Gui {
 		{
 			if (isReadOnly)
 				return;
+
+			SetWrapModel ();
+
 			if (selecting) {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 					HistoryText.LineStatus.Original);
@@ -3330,11 +3401,18 @@ namespace Terminal.Gui {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition,
 					HistoryText.LineStatus.Replaced);
 
+				UpdateWrapModel ();
+
 				return;
 			}
 			if (DeleteTextBackwards ()) {
+				UpdateWrapModel ();
+
 				return;
 			}
+
+			UpdateWrapModel ();
+
 			DoNeededAction ();
 		}
 
@@ -3443,7 +3521,7 @@ namespace Terminal.Gui {
 			if (isReadOnly)
 				return true;
 
-			var curPos = CursorPosition;
+			SetWrapModel ();
 
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition);
 
@@ -3465,6 +3543,8 @@ namespace Terminal.Gui {
 			historyText.Add (new List<List<Rune>> () { new List<Rune> (GetCurrentLine ()) }, CursorPosition,
 				HistoryText.LineStatus.Replaced);
 
+			UpdateWrapModel ();
+
 			return true;
 		}
 
@@ -3517,10 +3597,15 @@ namespace Terminal.Gui {
 
 		bool DeleteTextForwards ()
 		{
+			SetWrapModel ();
+
 			var currentLine = GetCurrentLine ();
 			if (currentColumn == currentLine.Count) {
-				if (currentRow + 1 == model.Count)
+				if (currentRow + 1 == model.Count) {
+					UpdateWrapModel ();
+
 					return true;
+				}
 
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
 
@@ -3541,8 +3626,12 @@ namespace Terminal.Gui {
 				if (wordWrap && wrapManager.RemoveLine (currentRow, currentColumn, out _)) {
 					wrapNeeded = true;
 				}
-				var sr = currentRow - topRow;
-				SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
+				if (wrapNeeded) {
+					SetNeedsDisplay ();
+				} else {
+					var sr = currentRow - topRow;
+					SetNeedsDisplay (new Rect (0, sr, Frame.Width, sr + 1));
+				}
 			} else {
 				historyText.Add (new List<List<Rune>> () { new List<Rune> (currentLine) }, CursorPosition);
 
@@ -3554,15 +3643,24 @@ namespace Terminal.Gui {
 				if (wordWrap && wrapManager.RemoveAt (currentRow, currentColumn)) {
 					wrapNeeded = true;
 				}
-				var r = currentRow - topRow;
-				SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1));
+
+				if (wrapNeeded) {
+					SetNeedsDisplay ();
+				} else {
+					var r = currentRow - topRow;
+					SetNeedsDisplay (new Rect (currentColumn - leftColumn, r, Frame.Width, r + 1));
+				}
 			}
 
+			UpdateWrapModel ();
+
 			return false;
 		}
 
 		bool DeleteTextBackwards ()
 		{
+			SetWrapModel ();
+
 			if (currentColumn > 0) {
 				// Delete backwards 
 				var currentLine = GetCurrentLine ();
@@ -3619,6 +3717,8 @@ namespace Terminal.Gui {
 				SetNeedsDisplay ();
 			}
 
+			UpdateWrapModel ();
+
 			return false;
 		}
 

+ 100 - 8
Terminal.Gui/Windows/Dialog.cs

@@ -109,24 +109,115 @@ namespace Terminal.Gui {
 			LayoutSubviews ();
 		}
 
+		// Get the width of all buttons, not including any spacing
 		internal int GetButtonsWidth ()
 		{
 			if (buttons.Count == 0) {
 				return 0;
 			}
-			return buttons.Select (b => b.Bounds.Width).Sum () + buttons.Count - 1;
+			return buttons.Select (b => b.Bounds.Width).Sum ();
 		}
+		/// <summary>
+		/// Determines the horizontal alignment of the Dialog buttons.
+		/// </summary>
+		public enum ButtonAlignments {
+			/// <summary>
+			/// Center-aligns the buttons (the default).
+			/// </summary>
+			Center = 0,
+
+			/// <summary>
+			/// Justifies the buttons
+			/// </summary>
+			Justify,
+
+			/// <summary>
+			/// Left-aligns the buttons
+			/// </summary>
+			Left,
+
+			/// <summary>
+			/// Right-aligns the buttons
+			/// </summary>
+			Right
+		}
+
+		private ButtonAlignments buttonAlignment = Dialog.ButtonAlignments.Center;
+
+		/// <summary>
+		/// Determines how the <see cref="Dialog"/> <see cref="Button"/>s are aligned along the 
+		/// bottom of the dialog. 
+		/// </summary>
+		public ButtonAlignments ButtonAlignment { get => buttonAlignment; set => buttonAlignment = value; }
 
 		void LayoutStartedHandler ()
 		{
+			if (buttons.Count == 0 || !IsInitialized) return;
+
+			int shiftLeft = 0;
+
 			int buttonsWidth = GetButtonsWidth ();
+			switch (ButtonAlignment) {
+			case ButtonAlignments.Center:
+				// Center Buttons
+				shiftLeft = Math.Max ((Bounds.Width - buttonsWidth - buttons.Count - 2) / 2 + 1, 0);
+				for (int i = buttons.Count - 1; i >= 0; i--) {
+					Button button = buttons [i];
+					shiftLeft += button.Frame.Width + (i == buttons.Count - 1 ? 0 : 1);
+					button.X = Pos.AnchorEnd (shiftLeft);
+					button.Y = Pos.AnchorEnd (1);
+				}
+				break;
+
+			case ButtonAlignments.Justify:
+				// Justify Buttons
+				// leftmost and rightmost buttons are hard against edges. The rest are evenly spaced.
+
+				var spacing = (int)Math.Ceiling ((double)(Bounds.Width - buttonsWidth - 2) / (buttons.Count - 1));
+				for (int i = buttons.Count - 1; i >= 0; i--) {
+					Button button = buttons [i];
+					if (i == buttons.Count - 1) {
+						shiftLeft += button.Frame.Width;
+						button.X = Pos.AnchorEnd (shiftLeft);
+					} else {
+						if (i == 0) {
+							// first (leftmost) button - always hard flush left
+							var left = Bounds.Width - ((Border.DrawMarginFrame ? 2 : 0) + Border.BorderThickness.Left + Border.BorderThickness.Right);
+							button.X = Pos.AnchorEnd (left);
+						} else {
+							shiftLeft += button.Frame.Width + (spacing);
+							button.X = Pos.AnchorEnd (shiftLeft);
+						}
+					}
+					button.Y = Pos.AnchorEnd (1);
+				}
+				break;
+
+			case ButtonAlignments.Left:
+				// Left Align Buttons
+				var prevButton = buttons [0];
+				prevButton.X = 0;
+				prevButton.Y = Pos.AnchorEnd (1);
+				for (int i = 1; i < buttons.Count; i++) {
+					Button button = buttons [i];
+					button.X = Pos.Right (prevButton) + 1;
+					button.Y = Pos.AnchorEnd (1);
+					prevButton = button;
+				}
+				break;
 
-			int shiftLeft = Math.Max ((Bounds.Width - buttonsWidth) / 2 - 2, 0);
-			for (int i = buttons.Count - 1; i >= 0; i--) {
-				Button button = buttons [i];
-				shiftLeft += button.Frame.Width + 1;
-				button.X = Pos.AnchorEnd (shiftLeft);
-				button.Y = Pos.AnchorEnd (1);
+			case ButtonAlignments.Right:
+				// Right align buttons
+				shiftLeft = buttons [buttons.Count - 1].Frame.Width;
+				buttons [buttons.Count - 1].X = Pos.AnchorEnd (shiftLeft);
+				buttons [buttons.Count - 1].Y = Pos.AnchorEnd (1);
+				for (int i = buttons.Count - 2; i >= 0; i--) {
+					Button button = buttons [i];
+					shiftLeft += button.Frame.Width + 1;
+					button.X = Pos.AnchorEnd (shiftLeft);
+					button.Y = Pos.AnchorEnd (1);
+				}
+				break;
 			}
 		}
 
@@ -135,10 +226,11 @@ namespace Terminal.Gui {
 		{
 			switch (kb.Key) {
 			case Key.Esc:
-				Running = false;
+				Application.RequestStop (this);
 				return true;
 			}
 			return base.ProcessKey (kb);
 		}
+
 	}
 }

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

@@ -292,12 +292,12 @@ namespace Terminal.Gui {
 			d.Width = msgboxWidth;
 
 			// Setup actions
-			int clicked = -1;
+			Clicked = -1;
 			for (int n = 0; n < buttonList.Count; n++) {
 				int buttonId = n;
 				var b = buttonList [n];
 				b.Clicked += () => {
-					clicked = buttonId;
+					Clicked = buttonId;
 					Application.RequestStop ();
 				};
 				if (b.IsDefault) {
@@ -307,7 +307,13 @@ namespace Terminal.Gui {
 
 			// Run the modal; do not shutdown the mainloop driver when done
 			Application.Run (d);
-			return clicked;
+			return Clicked;
 		}
+
+		/// <summary>
+		/// The index of the selected button, or -1 if the user pressed ESC to close the dialog.
+		/// This is useful for web based console where by default there is no SynchronizationContext or TaskScheduler.
+		/// </summary>
+		public static int Clicked { get; private set; } = -1;
 	}
 }

+ 869 - 0
Terminal.Gui/Windows/Wizard.cs

@@ -0,0 +1,869 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NStack;
+using Terminal.Gui.Resources;
+
+namespace Terminal.Gui {
+
+	/// <summary>
+	/// Provides navigation and a user interface (UI) to collect related data across multiple steps. Each step (<see cref="WizardStep"/>) can host 
+	/// arbitrary <see cref="View"/>s, much like a <see cref="Dialog"/>. Each step also has a pane for help text. Along the
+	/// bottom of the Wizard view are customizable buttons enabling the user to navigate forward and backward through the Wizard. 
+	/// </summary>
+	/// <remarks>
+	/// The Wizard can be displayed either as a modal (pop-up) <see cref="Window"/> (like <see cref="Dialog"/>) or as an embedded <see cref="View"/>. 
+	/// 
+	/// By default, <see cref="Wizard.Modal"/> is <c>true</c>. In this case launch the Wizard with <c>Application.Run(wizard)</c>. 
+	/// 
+	/// See <see cref="Wizard.Modal"/> for more details.
+	/// </remarks>
+	/// <example>
+	/// <code>
+	/// using Terminal.Gui;
+	/// using NStack;
+	/// 
+	/// Application.Init();
+	/// 
+	/// var wizard = new Wizard ($"Setup Wizard");
+	/// 
+	/// // Add 1st step
+	/// var firstStep = new Wizard.WizardStep ("End User License Agreement");
+	/// wizard.AddStep(firstStep);
+	/// firstStep.NextButtonText = "Accept!";
+	/// firstStep.HelpText = "This is the End User License Agreement.";
+	/// 
+	/// // Add 2nd step
+	/// var secondStep = new Wizard.WizardStep ("Second Step");
+	/// wizard.AddStep(secondStep);
+	/// secondStep.HelpText = "This is the help text for the Second Step.";
+	/// var lbl = new Label ("Name:") { AutoSize = true };
+	/// secondStep.Add(lbl);
+	/// 
+	/// var name = new TextField () { X = Pos.Right (lbl) + 1, Width = Dim.Fill () - 1 };
+	/// secondStep.Add(name);
+	/// 
+	/// wizard.Finished += (args) =>
+	/// {
+	///     MessageBox.Query("Wizard", $"Finished. The Name entered is '{name.Text}'", "Ok");
+	///     Application.RequestStop();
+	/// };
+	/// 
+	/// Application.Top.Add (wizard);
+	/// Application.Run ();
+	/// Application.Shutdown ();
+	/// </code>
+	/// </example>
+	public class Wizard : Dialog {
+		/// <summary>
+		/// Represents a basic step that is displayed in a <see cref="Wizard"/>. The <see cref="WizardStep"/> view is divided horizontally in two. On the left is the
+		/// content view where <see cref="View"/>s can be added,  On the right is the help for the step.
+		/// Set <see cref="WizardStep.HelpText"/> to set the help text. If the help text is empty the help pane will not
+		/// be shown. 
+		/// 
+		/// If there are no Views added to the WizardStep the <see cref="HelpText"/> (if not empty) will fill the wizard step. 
+		/// </summary>
+		/// <remarks>
+		/// If <see cref="Button"/>s are added, do not set <see cref="Button.IsDefault"/> to true as this will conflict
+		/// with the Next button of the Wizard.
+		/// 
+		/// Subscribe to the <see cref="View.VisibleChanged"/> event to be notified when the step is active; see also: <see cref="Wizard.StepChanged"/>.
+		/// 
+		/// To enable or disable a step from being shown to the user, set <see cref="View.Enabled"/>.
+		/// 
+		/// </remarks>
+		public class WizardStep : FrameView {
+			/// <summary>
+			/// The title of the <see cref="WizardStep"/>. 
+			/// </summary>
+			/// <remarks>The Title is only displayed when the <see cref="Wizard"/> is used as a modal pop-up (see <see cref="Wizard.Modal"/>.</remarks>
+			public new ustring Title {
+				get => title;
+				set {
+					if (!OnTitleChanging (title, value)) {
+						var old = title;
+						title = value;
+						OnTitleChanged (old, title);
+					}
+					base.Title = value;
+					SetNeedsDisplay ();
+				}
+			}
+
+			private ustring title = ustring.Empty;
+
+			/// <summary>
+			/// An <see cref="EventArgs"/> which allows passing a cancelable new <see cref="Title"/> value event.
+			/// </summary>
+			public class TitleEventArgs : EventArgs {
+				/// <summary>
+				/// The new Window Title.
+				/// </summary>
+				public ustring NewTitle { get; set; }
+
+				/// <summary>
+				/// The old Window Title.
+				/// </summary>
+				public ustring OldTitle { get; set; }
+
+				/// <summary>
+				/// Flag which allows cancelling the Title change.
+				/// </summary>
+				public bool Cancel { get; set; }
+
+				/// <summary>
+				/// Initializes a new instance of <see cref="TitleEventArgs"/>
+				/// </summary>
+				/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
+				/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
+				public TitleEventArgs (ustring oldTitle, ustring newTitle)
+				{
+					OldTitle = oldTitle;
+					NewTitle = newTitle;
+				}
+			}
+
+			/// <summary>
+			/// Called before the <see cref="Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be cancelled.
+			/// </summary>
+			/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
+			/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
+			/// <returns><c>true</c> if an event handler cancelled the Title change.</returns>
+			public virtual bool OnTitleChanging (ustring oldTitle, ustring newTitle)
+			{
+				var args = new TitleEventArgs (oldTitle, newTitle);
+				TitleChanging?.Invoke (args);
+				return args.Cancel;
+			}
+
+			/// <summary>
+			/// Event fired when the <see cref="Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to 
+			/// <c>true</c> to cancel the Title change.
+			/// </summary>
+			public event Action<TitleEventArgs> TitleChanging;
+
+			/// <summary>
+			/// Called when the <see cref="Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
+			/// </summary>
+			/// <param name="oldTitle">The <see cref="Title"/> that is/has been replaced.</param>
+			/// <param name="newTitle">The new <see cref="Title"/> to be replaced.</param>
+			public virtual void OnTitleChanged (ustring oldTitle, ustring newTitle)
+			{
+				var args = new TitleEventArgs (oldTitle, newTitle);
+				TitleChanged?.Invoke (args);
+			}
+
+			/// <summary>
+			/// Event fired after the <see cref="Title"/> has been changed. 
+			/// </summary>
+			public event Action<TitleEventArgs> TitleChanged;
+
+			// The contentView works like the ContentView in FrameView.
+			private View contentView = new View ();
+
+			/// <summary>
+			/// Sets or gets help text for the <see cref="WizardStep"/>.If <see cref="WizardStep.HelpText"/> is empty
+			/// the help pane will not be visible and the content will fill the entire WizardStep.
+			/// </summary>
+			/// <remarks>The help text is displayed using a read-only <see cref="TextView"/>.</remarks>
+			public ustring HelpText {
+				get => helpTextView.Text;
+				set {
+					helpTextView.Text = value;
+					ShowHide ();
+					SetNeedsDisplay ();
+				}
+			}
+			private TextView helpTextView = new TextView ();
+
+			/// <summary>
+			/// Sets or gets the text for the back button. The back button will only be visible on 
+			/// steps after the first step.
+			/// </summary>
+			/// <remarks>The default text is "Back"</remarks>
+			public ustring BackButtonText { get; set; } = ustring.Empty;
+
+			/// <summary>
+			/// Sets or gets the text for the next/finish button.
+			/// </summary>
+			/// <remarks>The default text is "Next..." if the Pane is not the last pane. Otherwise it is "Finish"</remarks>
+			public ustring NextButtonText { get; set; } = ustring.Empty;
+
+			/// <summary>
+			/// Initializes a new instance of the <see cref="Wizard"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+			/// </summary>
+			/// <param name="title">Title for the Step. Will be appended to the containing Wizard's title as 
+			/// "Wizard Title - Wizard Step Title" when this step is active.</param>
+			/// <remarks>
+			/// </remarks>
+			public WizardStep (ustring title)
+			{
+				this.Title = title; // this.Title holds just the "Wizard Title"; base.Title holds "Wizard Title - Step Title"
+				this.Border.BorderStyle = BorderStyle.Rounded;
+
+				base.Add (contentView);
+
+				helpTextView.ColorScheme = Colors.TopLevel;
+				helpTextView.ReadOnly = true;
+				helpTextView.WordWrap = true;
+				base.Add (helpTextView);
+
+				ShowHide ();
+
+				var scrollBar = new ScrollBarView (helpTextView, true);
+
+				scrollBar.ChangedPosition += () => {
+					helpTextView.TopRow = scrollBar.Position;
+					if (helpTextView.TopRow != scrollBar.Position) {
+						scrollBar.Position = helpTextView.TopRow;
+					}
+					helpTextView.SetNeedsDisplay ();
+				};
+
+				scrollBar.OtherScrollBarView.ChangedPosition += () => {
+					helpTextView.LeftColumn = scrollBar.OtherScrollBarView.Position;
+					if (helpTextView.LeftColumn != scrollBar.OtherScrollBarView.Position) {
+						scrollBar.OtherScrollBarView.Position = helpTextView.LeftColumn;
+					}
+					helpTextView.SetNeedsDisplay ();
+				};
+
+				scrollBar.VisibleChanged += () => {
+					if (scrollBar.Visible && helpTextView.RightOffset == 0) {
+						helpTextView.RightOffset = 1;
+					} else if (!scrollBar.Visible && helpTextView.RightOffset == 1) {
+						helpTextView.RightOffset = 0;
+					}
+				};
+
+				scrollBar.OtherScrollBarView.VisibleChanged += () => {
+					if (scrollBar.OtherScrollBarView.Visible && helpTextView.BottomOffset == 0) {
+						helpTextView.BottomOffset = 1;
+					} else if (!scrollBar.OtherScrollBarView.Visible && helpTextView.BottomOffset == 1) {
+						helpTextView.BottomOffset = 0;
+					}
+				};
+
+				helpTextView.DrawContent += (e) => {
+					scrollBar.Size = helpTextView.Lines;
+					scrollBar.Position = helpTextView.TopRow;
+					if (scrollBar.OtherScrollBarView != null) {
+						scrollBar.OtherScrollBarView.Size = helpTextView.Maxlength;
+						scrollBar.OtherScrollBarView.Position = helpTextView.LeftColumn;
+					}
+					scrollBar.LayoutSubviews ();
+					scrollBar.Refresh ();
+				};
+				base.Add (scrollBar);
+			}
+
+			/// <summary>
+			/// Does the work to show and hide the contentView and helpView as appropriate
+			/// </summary>
+			internal void ShowHide ()
+			{
+				contentView.Height = Dim.Fill ();
+				helpTextView.Height = Dim.Fill ();
+				helpTextView.Width = Dim.Fill ();
+
+				if (contentView.InternalSubviews?.Count > 0) {
+					if (helpTextView.Text.Length > 0) {
+						contentView.Width = Dim.Percent (70);
+						helpTextView.X = Pos.Right (contentView);
+						helpTextView.Width = Dim.Fill ();
+
+					} else {
+						contentView.Width = Dim.Percent (100);
+					}
+				} else {
+					if (helpTextView.Text.Length > 0) {
+						helpTextView.X = 0;
+					} else {
+						// Error - no pane shown
+					}
+
+				}
+				contentView.Visible = contentView.InternalSubviews?.Count > 0;
+				helpTextView.Visible = helpTextView.Text.Length > 0;
+			}
+
+			/// <summary>
+			/// Add the specified <see cref="View"/> to the <see cref="WizardStep"/>. 
+			/// </summary>
+			/// <param name="view"><see cref="View"/> to add to this container</param>
+			public override void Add (View view)
+			{
+				contentView.Add (view);
+				if (view.CanFocus)
+					CanFocus = true;
+				ShowHide ();
+			}
+
+			/// <summary>
+			///   Removes a <see cref="View"/> from <see cref="WizardStep"/>.
+			/// </summary>
+			/// <remarks>
+			/// </remarks>
+			public override void Remove (View view)
+			{
+				if (view == null)
+					return;
+
+				SetNeedsDisplay ();
+				var touched = view.Frame;
+				contentView.Remove (view);
+
+				if (contentView.InternalSubviews.Count < 1)
+					this.CanFocus = false;
+				ShowHide ();
+			}
+
+			/// <summary>
+			///   Removes all <see cref="View"/>s from the <see cref="WizardStep"/>.
+			/// </summary>
+			/// <remarks>
+			/// </remarks>
+			public override void RemoveAll ()
+			{
+				contentView.RemoveAll ();
+				ShowHide ();
+			}
+
+		} // end of WizardStep class
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Wizard"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+		/// </summary>
+		/// <remarks>
+		/// The Wizard will be vertically and horizontally centered in the container.
+		/// After initialization use <c>X</c>, <c>Y</c>, <c>Width</c>, and <c>Height</c> change size and position.
+		/// </remarks>
+		public Wizard () : this (ustring.Empty)
+		{
+		}
+
+		/// <summary>
+		/// Initializes a new instance of the <see cref="Wizard"/> class using <see cref="LayoutStyle.Computed"/> positioning.
+		/// </summary>
+		/// <param name="title">Sets the <see cref="Title"/> for the Wizard.</param>
+		/// <remarks>
+		/// The Wizard will be vertically and horizontally centered in the container.
+		/// After initialization use <c>X</c>, <c>Y</c>, <c>Width</c>, and <c>Height</c> change size and position.
+		/// </remarks>
+		public Wizard (ustring title) : base (title)
+		{
+			wizardTitle = title;
+			// Using Justify causes the Back and Next buttons to be hard justified against
+			// the left and right edge
+			ButtonAlignment = ButtonAlignments.Justify;
+			this.Border.BorderStyle = BorderStyle.Double;
+			this.Border.Padding = new Thickness (0);
+
+			//// Add a horiz separator
+			//var separator = new LineView (Graphs.Orientation.Horizontal) {
+			//	Y = Pos.AnchorEnd (2)
+			//};
+			//Add (separator);
+
+			// BUGBUG: Space is to work around https://github.com/migueldeicaza/gui.cs/issues/1812
+			backBtn = new Button (Strings.wzBack) { AutoSize = true };
+			AddButton (backBtn);
+
+			nextfinishBtn = new Button (Strings.wzFinish) { AutoSize = true };
+			nextfinishBtn.IsDefault = true;
+			AddButton (nextfinishBtn);
+
+			backBtn.Clicked += BackBtn_Clicked;
+			nextfinishBtn.Clicked += NextfinishBtn_Clicked;
+
+			Loaded += Wizard_Loaded;
+			Closing += Wizard_Closing;
+
+			if (Modal) {
+				ClearKeybinding (Command.QuitToplevel);
+				AddKeyBinding (Key.Esc, Command.QuitToplevel);
+			}
+
+		}
+
+		private void Wizard_Loaded ()
+		{
+			CurrentStep = GetFirstStep (); // gets the first step if CurrentStep == null
+		}
+
+		private bool finishedPressed = false;
+
+		private void Wizard_Closing (ToplevelClosingEventArgs obj)
+		{
+			if (!finishedPressed) {
+				var args = new WizardButtonEventArgs ();
+				Cancelled?.Invoke (args);
+			}
+		}
+
+		private void NextfinishBtn_Clicked ()
+		{
+			if (CurrentStep == GetLastStep ()) {
+				var args = new WizardButtonEventArgs ();
+				Finished?.Invoke (args);
+				if (!args.Cancel) {
+					finishedPressed = true;
+					if (IsCurrentTop) {
+						Application.RequestStop (this);
+					} else {
+						// Wizard was created as a non-modal (just added to another View). 
+						// Do nothing
+					}
+				}
+			} else {
+				var args = new WizardButtonEventArgs ();
+				MovingNext?.Invoke (args);
+				if (!args.Cancel) {
+					GoNext ();
+				}
+			}
+		}
+
+		/// <summary>
+		/// <see cref="Wizard"/> is derived from <see cref="Dialog"/> and Dialog causes <c>Esc</c> to call
+		/// <see cref="Application.RequestStop(Toplevel)"/>, closing the Dialog. Wizard overrides <see cref="Responder.ProcessKey(KeyEvent)"/>
+		/// to instead fire the <see cref="Cancelled"/> event when Wizard is being used as a non-modal (see <see cref="Wizard.Modal"/>.
+		/// See <see cref="Responder.ProcessKey(KeyEvent)"/> for more.
+		/// </summary>
+		/// <param name="kb"></param>
+		/// <returns></returns>
+		public override bool ProcessKey (KeyEvent kb)
+		{
+			if (!Modal) {
+				switch (kb.Key) {
+				case Key.Esc:
+					var args = new WizardButtonEventArgs ();
+					Cancelled?.Invoke (args);
+					return false;
+				}
+			}
+			return base.ProcessKey (kb);
+		}
+
+		/// <summary>
+		/// Causes the wizad to move to the next enabled step (or last step if <see cref="CurrentStep"/> is not set). 
+		/// If there is no previous step, does nothing.
+		/// </summary>
+		public void GoNext ()
+		{
+			var nextStep = GetNextStep ();
+			if (nextStep != null) {
+				GoToStep (nextStep);
+			}
+		}
+
+		/// <summary>
+		/// Returns the next enabled <see cref="WizardStep"/> after the current step. Takes into account steps which
+		/// are disabled. If <see cref="CurrentStep"/> is <c>null</c> returns the first enabled step.
+		/// </summary>
+		/// <returns>The next step after the current step, if there is one; otherwise returns <c>null</c>, which 
+		/// indicates either there are no enabled steps or the current step is the last enabled step.</returns>
+		public WizardStep GetNextStep ()
+		{
+			LinkedListNode<WizardStep> step = null;
+			if (CurrentStep == null) {
+				// Get first step, assume it is next
+				step = steps.First;
+			} else {
+				// Get the step after current
+				step = steps.Find (CurrentStep);
+				if (step != null) {
+					step = step.Next;
+				}
+			}
+
+			// step now points to the potential next step
+			while (step != null) {
+				if (step.Value.Enabled) {
+					return step.Value;
+				}
+				step = step.Next;
+			}
+			return null;
+		}
+
+		private void BackBtn_Clicked ()
+		{
+			var args = new WizardButtonEventArgs ();
+			MovingBack?.Invoke (args);
+			if (!args.Cancel) {
+				GoBack ();
+			}
+		}
+
+		/// <summary>
+		/// Causes the wizad to move to the previous enabled step (or first step if <see cref="CurrentStep"/> is not set). 
+		/// If there is no previous step, does nothing.
+		/// </summary>
+		public void GoBack ()
+		{
+			var previous = GetPreviousStep ();
+			if (previous != null) {
+				GoToStep (previous);
+			}
+		}
+
+		/// <summary>
+		/// Returns the first enabled <see cref="WizardStep"/> before the current step. Takes into account steps which
+		/// are disabled. If <see cref="CurrentStep"/> is <c>null</c> returns the last enabled step.
+		/// </summary>
+		/// <returns>The first step ahead of the current step, if there is one; otherwise returns <c>null</c>, which 
+		/// indicates either there are no enabled steps or the current step is the first enabled step.</returns>
+		public WizardStep GetPreviousStep ()
+		{
+			LinkedListNode<WizardStep> step = null;
+			if (CurrentStep == null) {
+				// Get last step, assume it is previous
+				step = steps.Last;
+			} else {
+				// Get the step before current
+				step = steps.Find (CurrentStep);
+				if (step != null) {
+					step = step.Previous;
+				}
+			}
+
+			// step now points to the potential previous step
+			while (step != null) {
+				if (step.Value.Enabled) {
+					return step.Value;
+				}
+				step = step.Previous;
+			}
+			return null;
+		}
+
+		/// <summary>
+		/// Returns the first enabled step in the Wizard
+		/// </summary>
+		/// <returns>The last enabled step</returns>
+		public WizardStep GetFirstStep ()
+		{
+			return steps.FirstOrDefault (s => s.Enabled);
+		}
+
+		/// <summary>
+		/// Returns the last enabled step in the Wizard
+		/// </summary>
+		/// <returns>The last enabled step</returns>
+		public WizardStep GetLastStep ()
+		{
+			return steps.LastOrDefault (s => s.Enabled);
+		}
+
+		private LinkedList<WizardStep> steps = new LinkedList<WizardStep> ();
+		private WizardStep currentStep = null;
+
+		/// <summary>
+		/// If the <see cref="CurrentStep"/> is not the first step in the wizard, this button causes
+		/// the <see cref="MovingBack"/> event to be fired and the wizard moves to the previous step. 
+		/// </summary>
+		/// <remarks>
+		/// Use the <see cref="MovingBack"></see> event to be notified when the user attempts to go back.
+		/// </remarks>
+		public Button BackButton { get => backBtn; }
+		private Button backBtn;
+
+		/// <summary>
+		/// If the <see cref="CurrentStep"/> is the last step in the wizard, this button causes
+		/// the <see cref="Finished"/> event to be fired and the wizard to close. If the step is not the last step,
+		/// the <see cref="MovingNext"/> event will be fired and the wizard will move next step. 
+		/// </summary>
+		/// <remarks>
+		/// Use the <see cref="MovingNext"></see> and <see cref="Finished"></see> events to be notified 
+		/// when the user attempts go to the next step or finish the wizard.
+		/// </remarks>
+		public Button NextFinishButton { get => nextfinishBtn; }
+		private Button nextfinishBtn;
+
+		/// <summary>
+		/// Adds a step to the wizard. The Next and Back buttons navigate through the added steps in the
+		/// order they were added.
+		/// </summary>
+		/// <param name="newStep"></param>
+		/// <remarks>The "Next..." button of the last step added will read "Finish" (unless changed from default).</remarks>
+		public void AddStep (WizardStep newStep)
+		{
+			SizeStep (newStep);
+
+			newStep.EnabledChanged += UpdateButtonsAndTitle;
+			newStep.TitleChanged += (args) => UpdateButtonsAndTitle ();
+			steps.AddLast (newStep);
+			this.Add (newStep);
+			UpdateButtonsAndTitle ();
+		}
+
+		/// <summary>
+		/// The title of the Wizard, shown at the top of the Wizard with " - currentStep.Title" appended.
+		/// </summary>
+		/// <remarks>
+		/// The Title is only displayed when the <see cref="Wizard"/> <see cref="Wizard.Modal"/> is set to <c>false</c>.
+		/// </remarks>
+		public new ustring Title {
+			get {
+				// The base (Dialog) Title holds the full title ("Wizard Title - Step Title")
+				return base.Title;
+			}
+			set {
+				wizardTitle = value;
+				base.Title = $"{wizardTitle}{(steps.Count > 0 && currentStep != null ? " - " + currentStep.Title : string.Empty)}";
+			}
+		}
+		private ustring wizardTitle = ustring.Empty;
+
+		/// <summary>	
+		/// <see cref="EventArgs"/> for <see cref="WizardStep"/> transition events.
+		/// </summary>
+		public class WizardButtonEventArgs : EventArgs {
+			/// <summary>
+			/// Set to true to cancel the transition to the next step.
+			/// </summary>
+			public bool Cancel { get; set; }
+
+			/// <summary>
+			/// Initializes a new instance of <see cref="WizardButtonEventArgs"/>
+			/// </summary>
+			public WizardButtonEventArgs ()
+			{
+				Cancel = false;
+			}
+		}
+
+		/// <summary>
+		/// Raised when the Back button in the <see cref="Wizard"/> is clicked. The Back button is always
+		/// the first button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any.
+		/// </summary>
+		public event Action<WizardButtonEventArgs> MovingBack;
+
+		/// <summary>
+		/// Raised when the Next/Finish button in the <see cref="Wizard"/> is clicked (or the user presses Enter). 
+		/// The Next/Finish button is always the last button in the array of Buttons passed to the <see cref="Wizard"/> constructor, 
+		/// if any. This event is only raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
+		/// (otherwise the <see cref="Finished"/> event is raised).
+		/// </summary>
+		public event Action<WizardButtonEventArgs> MovingNext;
+
+		/// <summary>
+		/// Raised when the Next/Finish button in the <see cref="Wizard"/> is clicked. The Next/Finish button is always
+		/// the last button in the array of Buttons passed to the <see cref="Wizard"/> constructor, if any. This event is only
+		/// raised if the <see cref="CurrentStep"/> is the last Step in the Wizard flow 
+		/// (otherwise the <see cref="Finished"/> event is raised).
+		/// </summary>
+		public event Action<WizardButtonEventArgs> Finished;
+
+		/// <summary>
+		/// Raised when the user has cancelled the <see cref="Wizard"/> by pressin the Esc key. 
+		/// To prevent a modal (<see cref="Wizard.Modal"/> is <c>true</c>) Wizard from
+		/// closing, cancel the event by setting <see cref="WizardButtonEventArgs.Cancel"/> to 
+		/// <c>true</c> before returning from the event handler.
+		/// </summary>
+		public event Action<WizardButtonEventArgs> Cancelled;
+
+		/// <summary>
+		/// <see cref="EventArgs"/> for <see cref="WizardStep"/> events.
+		/// </summary>
+		public class StepChangeEventArgs : EventArgs {
+			/// <summary>
+			/// The current (or previous) <see cref="WizardStep"/>.
+			/// </summary>
+			public WizardStep OldStep { get; }
+
+			/// <summary>
+			/// The <see cref="WizardStep"/> the <see cref="Wizard"/> is changing to or has changed to.
+			/// </summary>
+			public WizardStep NewStep { get; }
+
+			/// <summary>
+			/// Event handlers can set to true before returning to cancel the step transition.
+			/// </summary>
+			public bool Cancel { get; set; }
+
+			/// <summary>
+			/// Initializes a new instance of <see cref="StepChangeEventArgs"/>
+			/// </summary>
+			/// <param name="oldStep">The current <see cref="WizardStep"/>.</param>
+			/// <param name="newStep">The new <see cref="WizardStep"/>.</param>
+			public StepChangeEventArgs (WizardStep oldStep, WizardStep newStep)
+			{
+				OldStep = oldStep;
+				NewStep = newStep;
+				Cancel = false;
+			}
+		}
+
+		/// <summary>
+		/// This event is raised when the current <see cref="CurrentStep"/>) is about to change. Use <see cref="StepChangeEventArgs.Cancel"/> 
+		/// to abort the transition.
+		/// </summary>
+		public event Action<StepChangeEventArgs> StepChanging;
+
+		/// <summary>
+		/// This event is raised after the <see cref="Wizard"/> has changed the <see cref="CurrentStep"/>. 
+		/// </summary>
+		public event Action<StepChangeEventArgs> StepChanged;
+
+		/// <summary>
+		/// Gets or sets the currently active <see cref="WizardStep"/>.
+		/// </summary>
+		public WizardStep CurrentStep {
+			get => currentStep;
+			set {
+				GoToStep (value);
+			}
+		}
+
+		/// <summary>
+		/// Called when the <see cref="Wizard"/> is about to transition to another <see cref="WizardStep"/>. Fires the <see cref="StepChanging"/> event. 
+		/// </summary>
+		/// <param name="oldStep">The step the Wizard is about to change from</param>
+		/// <param name="newStep">The step the Wizard is about to change to</param>
+		/// <returns>True if the change is to be cancelled.</returns>
+		public virtual bool OnStepChanging (WizardStep oldStep, WizardStep newStep)
+		{
+			var args = new StepChangeEventArgs (oldStep, newStep);
+			StepChanging?.Invoke (args);
+			return args.Cancel;
+		}
+
+		/// <summary>
+		/// Called when the <see cref="Wizard"/> has completed transition to a new <see cref="WizardStep"/>. Fires the <see cref="StepChanged"/> event. 
+		/// </summary>
+		/// <param name="oldStep">The step the Wizard changed from</param>
+		/// <param name="newStep">The step the Wizard has changed to</param>
+		/// <returns>True if the change is to be cancelled.</returns>
+		public virtual bool OnStepChanged (WizardStep oldStep, WizardStep newStep)
+		{
+			var args = new StepChangeEventArgs (oldStep, newStep);
+			StepChanged?.Invoke (args);
+			return args.Cancel;
+		}
+
+		/// <summary>
+		/// Changes to the specified <see cref="WizardStep"/>.
+		/// </summary>
+		/// <param name="newStep">The step to go to.</param>
+		/// <returns>True if the transition to the step succeeded. False if the step was not found or the operation was cancelled.</returns>
+		public bool GoToStep (WizardStep newStep)
+		{
+			if (OnStepChanging (currentStep, newStep) || (newStep != null && !newStep.Enabled)) {
+				return false;
+			}
+
+			// Hide all but the new step
+			foreach (WizardStep step in steps) {
+				step.Visible = (step == newStep);
+				step.ShowHide ();
+			}
+
+			var oldStep = currentStep;
+			currentStep = newStep;
+
+			UpdateButtonsAndTitle ();
+
+			// Set focus to the nav buttons
+			if (backBtn.HasFocus) {
+				backBtn.SetFocus ();
+			} else {
+				nextfinishBtn.SetFocus ();
+			}
+
+			if (OnStepChanged (oldStep, currentStep)) {
+				// For correctness we do this, but it's meaningless because there's nothing to cancel
+				return false;
+			}
+
+			return true;
+		}
+
+		private void UpdateButtonsAndTitle ()
+		{
+			if (CurrentStep == null) return;
+
+			base.Title = $"{wizardTitle}{(steps.Count > 0 ? " - " + CurrentStep.Title : string.Empty)}";
+
+			// Configure the Back button
+			backBtn.Text = CurrentStep.BackButtonText != ustring.Empty ? CurrentStep.BackButtonText : Strings.wzBack; // "_Back";
+			backBtn.Visible = (CurrentStep != GetFirstStep ());
+
+			// Configure the Next/Finished button
+			if (CurrentStep == GetLastStep ()) {
+				nextfinishBtn.Text = CurrentStep.NextButtonText != ustring.Empty ? CurrentStep.NextButtonText : Strings.wzFinish; // "Fi_nish";
+			} else {
+				nextfinishBtn.Text = CurrentStep.NextButtonText != ustring.Empty ? CurrentStep.NextButtonText : Strings.wzNext; // "_Next...";
+			}
+
+			SizeStep (CurrentStep);
+
+			SetNeedsLayout ();
+			LayoutSubviews ();
+			Redraw (Bounds);
+		}
+
+		private void SizeStep (WizardStep step)
+		{
+			if (Modal) {
+				// If we're modal, then we expand the WizardStep so that the top and side 
+				// borders and not visible. The bottom border is the separator above the buttons.
+				step.X = step.Y = -1;
+				step.Height = Dim.Fill (1); // for button frame
+				step.Width = Dim.Fill (-1);
+			} else {
+				// If we're not a modal, then we show the border around the WizardStep
+				step.X = step.Y = 0;
+				step.Height = Dim.Fill (1); // for button frame
+				step.Width = Dim.Fill (0);
+			}
+		}
+
+		/// <summary>
+		/// Determines whether the <see cref="Wizard"/> is displayed as modal pop-up or not.
+		/// 
+		/// The default is <c>true</c>. The Wizard will be shown with a frame with <see cref="Title"/> and will behave like
+		/// any <see cref="Toplevel"/> window.
+		/// 
+		/// If set to <c>false</c> the Wizard will have no frame and will behave like any embedded <see cref="View"/>.
+		/// 
+		/// To use Wizard as an embedded View 
+		/// <list type="number">
+		/// <item><description>Set <see cref="Modal"/> to <c>false</c>.</description></item>
+		/// <item><description>Add the Wizard to a containing view with <see cref="View.Add(View)"/>.</description></item>
+		/// </list>
+		/// 
+		/// If a non-Modal Wizard is added to the application after <see cref="Application.Run(Func{Exception, bool})"/> has been called
+		/// the first step must be explicitly set by setting <see cref="CurrentStep"/> to <see cref="GetNextStep()"/>:
+		/// <code>
+		///    wizard.CurrentStep = wizard.GetNextStep();
+		/// </code>
+		/// </summary>
+		public new bool Modal {
+			get => base.Modal;
+			set {
+				base.Modal = value;
+				foreach (var step in steps) {
+					SizeStep (step);
+				}
+				if (base.Modal) {
+					ColorScheme = Colors.Dialog;
+					Border.BorderStyle = BorderStyle.Rounded;
+					Border.Effect3D = true;
+					Border.DrawMarginFrame = true;
+				} else {
+					if (SuperView != null) {
+						ColorScheme = SuperView.ColorScheme;
+					} else {
+						ColorScheme = Colors.Base;
+					}
+					CanFocus = true;
+					Border.Effect3D = false;
+					Border.BorderStyle = BorderStyle.None;
+					Border.DrawMarginFrame = false;
+				}
+			}
+		}
+	}
+}

+ 16 - 0
UICatalog/Properties/launchSettings.json

@@ -6,6 +6,22 @@
     "UICatalog : -usc": {
       "commandName": "Project",
       "commandLineArgs": "-usc"
+    },
+    "Wizards": {
+      "commandName": "Project",
+      "commandLineArgs": "Wizards"
+    },
+    "Dialogs": {
+      "commandName": "Project",
+      "commandLineArgs": "Dialogs"
+    },
+    "Buttons": {
+      "commandName": "Project",
+      "commandLineArgs": "Buttons"
+    },
+    "WizardAsView": {
+      "commandName": "Project",
+      "commandLineArgs": "WizardAsView"
     }
   }
 }

+ 32 - 0
UICatalog/Scenarios/AutoSizeAndDirectionText.cs

@@ -7,11 +7,14 @@ namespace UICatalog.Scenarios {
 		public override void Setup ()
 		{
 			var text = "Hello World";
+			var wideText = "Hello World 你";
 			var color = Colors.Dialog;
 
 			var labelH = new Label (text, TextDirection.LeftRight_TopBottom) {
 				X = 1,
 				Y = 1,
+				Width = 11,
+				Height = 1,
 				ColorScheme = color
 			};
 			Win.Add (labelH);
@@ -19,6 +22,8 @@ namespace UICatalog.Scenarios {
 			var labelV = new Label (text, TextDirection.TopBottom_LeftRight) {
 				X = 70,
 				Y = 1,
+				Width = 1,
+				Height = 11,
 				ColorScheme = color
 			};
 			Win.Add (labelV);
@@ -59,6 +64,33 @@ namespace UICatalog.Scenarios {
 			ckbAutoSize.Toggled += (_) => labelH.AutoSize = labelV.AutoSize = ckbAutoSize.Checked;
 			Win.Add (ckbAutoSize);
 
+			var ckbPreserveTrailingSpaces = new CheckBox ("Preserve Trailing Spaces") {
+				X = Pos.Center (),
+				Y = Pos.Center () + 7,
+				Checked = labelH.PreserveTrailingSpaces = labelV.PreserveTrailingSpaces
+			};
+			ckbPreserveTrailingSpaces.Toggled += (_) =>
+					labelH.PreserveTrailingSpaces = labelV.PreserveTrailingSpaces = ckbPreserveTrailingSpaces.Checked;
+			Win.Add (ckbPreserveTrailingSpaces);
+
+			var ckbWideText = new CheckBox ("Use wide runes") {
+				X = Pos.Center (),
+				Y = Pos.Center () + 9
+			};
+			ckbWideText.Toggled += (_) => {
+				if (ckbWideText.Checked) {
+					labelH.Text = labelV.Text = editText.Text = wideText;
+					labelH.Width = 14;
+					labelV.Height = 13;
+				} else {
+					labelH.Text = labelV.Text = editText.Text = text;
+					labelH.Width = 11;
+					labelV.Width = 1;
+					labelV.Height = 11;
+				}
+			};
+			Win.Add (ckbWideText);
+
 			Win.KeyUp += (_) =>
 				labelH.Text = labelV.Text = text = editText.Text.ToString ();
 		}

+ 1 - 1
UICatalog/Scenarios/BackgroundWorkerCollection.cs

@@ -286,7 +286,7 @@ namespace UICatalog.Scenarios {
 			public void WriteLog (string msg)
 			{
 				log.Add (msg);
-				listLog.MoveEnd ();
+				listLog.MoveDown ();
 			}
 		}
 

+ 22 - 5
UICatalog/Scenarios/CsvEditor.cs

@@ -8,6 +8,7 @@ using System.Globalization;
 using System.IO;
 using System.Text;
 using NStack;
+using System.Text.RegularExpressions;
 
 namespace UICatalog.Scenarios {
 
@@ -26,7 +27,7 @@ namespace UICatalog.Scenarios {
 		private MenuItem miLeft;
 		private MenuItem miRight;
 		private MenuItem miCentered;
-		private Label selectedCellLabel;
+		private TextField selectedCellLabel;
 
 		public override void Setup ()
 		{
@@ -78,14 +79,14 @@ namespace UICatalog.Scenarios {
 
 			Win.Add (tableView);
 
-			selectedCellLabel = new Label(){
+			selectedCellLabel = new TextField(){
 				X = 0,
 				Y = Pos.Bottom(tableView),
 				Text = "0,0",
 				Width = Dim.Fill(),
-				TextAlignment = TextAlignment.Right
-				
+				TextAlignment = TextAlignment.Right				
 			};
+			selectedCellLabel.TextChanged += SelectedCellLabel_TextChanged;
 
 			Win.Add(selectedCellLabel);
 
@@ -96,10 +97,26 @@ namespace UICatalog.Scenarios {
 			SetupScrollBar();
 		}
 
+		private void SelectedCellLabel_TextChanged (ustring last)
+		{
+			// if user is in the text control and editing the selected cell
+			if (!selectedCellLabel.HasFocus)
+				return;
+			
+			// change selected cell to the one the user has typed into the box
+			var match = Regex.Match (selectedCellLabel.Text.ToString(), "^(\\d+),(\\d+)$");
+			if(match.Success) {
+
+				tableView.SelectedColumn = int.Parse (match.Groups [1].Value);
+				tableView.SelectedRow = int.Parse (match.Groups [2].Value);
+			}
+		}
 
 		private void OnSelectedCellChanged (TableView.SelectedCellChangedEventArgs e)
 		{
-			selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}";
+			// only update the text box if the user is not manually editing it
+			if (!selectedCellLabel.HasFocus)
+				selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}";
 			
 			if(tableView.Table == null || tableView.SelectedColumn == -1)
 				return;

+ 85 - 21
UICatalog/Scenarios/Dialogs.cs

@@ -9,13 +9,13 @@ namespace UICatalog.Scenarios {
 	[ScenarioMetadata (Name: "Dialogs", Description: "Demonstrates how to the Dialog class")]
 	[ScenarioCategory ("Dialogs")]
 	public class Dialogs : Scenario {
+		static int CODE_POINT = '你'; // We know this is a wide char
 		public override void Setup ()
 		{
 			var frame = new FrameView ("Dialog Options") {
 				X = Pos.Center (),
-				Y = 1,
-				Width = Dim.Percent (75),
-				Height = 10
+				Y = 0,
+				Width = Dim.Percent (75)
 			};
 			Win.Add (frame);
 
@@ -92,10 +92,30 @@ namespace UICatalog.Scenarios {
 			};
 			frame.Add (numButtonsEdit);
 
+			var glyphsNotWords = new CheckBox ($"Add {Char.ConvertFromUtf32(CODE_POINT)} to button text to stress wide char support", false) {
+				X = Pos.Left (numButtonsEdit),
+				Y = Pos.Bottom (label),
+				TextAlignment = Terminal.Gui.TextAlignment.Right,
+			};
+			frame.Add (glyphsNotWords);
+
+
+			label = new Label ("Button Style:") {
+				X = 0,
+				Y = Pos.Bottom (glyphsNotWords),
+				TextAlignment = Terminal.Gui.TextAlignment.Right
+			};
+			frame.Add (label);
+			var styleRadioGroup = new RadioGroup (new ustring [] { "Center", "Justify", "Left", "Right" }) {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+			};
+			frame.Add (styleRadioGroup);
+
 			void Top_Loaded ()
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit)
-					+ Dim.Height (numButtonsEdit) + 2;
+					+ Dim.Height (numButtonsEdit) + Dim.Height (styleRadioGroup) + Dim.Height(glyphsNotWords) + 2;
 				Top.Loaded -= Top_Loaded;
 			}
 			Top.Loaded += Top_Loaded;
@@ -114,8 +134,11 @@ namespace UICatalog.Scenarios {
 				Height = 1,
 				ColorScheme = Colors.Error,
 			};
+			// glyphsNotWords
+			// false:var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
+			// true: var btnText = new [] { "0", "\u2780", "➁", "\u2783", "\u2784", "\u2785", "\u2786", "\u2787", "\u2788", "\u2789" };
+			// \u2781 is ➁ dingbats \ufb70 is	
 
-			//var btnText = new [] { "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine" };
 			var showDialogButton = new Button ("Show Dialog") {
 				X = Pos.Center (),
 				Y = Pos.Bottom (frame) + 2,
@@ -123,51 +146,92 @@ namespace UICatalog.Scenarios {
 			};
 			showDialogButton.Clicked += () => {
 				try {
-					int width = int.Parse (widthEdit.Text.ToString ());
-					int height = int.Parse (heightEdit.Text.ToString ());
-					int numButtons = int.Parse (numButtonsEdit.Text.ToString ());
+					Dialog dialog = null;
+
+					int width = 0;
+					int.TryParse (widthEdit.Text.ToString (), out width);
+					int height = 0;
+					int.TryParse (heightEdit.Text.ToString (), out height);
+					int numButtons = 3;
+					int.TryParse (numButtonsEdit.Text.ToString (), out numButtons);
 
 					var buttons = new List<Button> ();
 					var clicked = -1;
 					for (int i = 0; i < numButtons; i++) {
-						var buttonId = i;
-						//var button = new Button (btnText [buttonId % 10],
-						//	is_default: buttonId == 0);
-						var button = new Button (NumberToWords.Convert (buttonId),
-							is_default: buttonId == 0);
+						int buttonId = i;
+						Button button = null;
+						if (glyphsNotWords.Checked) {
+							buttonId = i;
+							button = new Button (NumberToWords.Convert (buttonId) + " " + Char.ConvertFromUtf32 (buttonId + CODE_POINT),
+								is_default: buttonId == 0);
+						} else {
+							button = new Button (NumberToWords.Convert (buttonId),
+							       is_default: buttonId == 0);
+						}
 						button.Clicked += () => {
 							clicked = buttonId;
 							Application.RequestStop ();
 						};
 						buttons.Add (button);
 					}
+					//if (buttons.Count > 1) {
+					//	buttons [1].Text = "Accept";
+					//	buttons [1].IsDefault = true;
+					//	buttons [0].Visible = false;
+					//	buttons [0].Text = "_Back";
+					//	buttons [0].IsDefault = false;
+					//}
 
 					// This tests dynamically adding buttons; ensuring the dialog resizes if needed and 
 					// the buttons are laid out correctly
-					var dialog = new Dialog (titleEdit.Text, width, height,
-						buttons.ToArray ());
+					dialog = new Dialog (titleEdit.Text, width, height,
+						buttons.ToArray ()) {
+						ButtonAlignment = (Dialog.ButtonAlignments)styleRadioGroup.SelectedItem
+					};
+
 					var add = new Button ("Add a button") {
 						X = Pos.Center (),
 						Y = Pos.Center ()
 					};
 					add.Clicked += () => {
 						var buttonId = buttons.Count;
-						//var button = new Button (btnText [buttonId % 10],
-						//	is_default: buttonId == 0);
-						var button = new Button (NumberToWords.Convert (buttonId),
-							is_default: buttonId == 0);
+						Button button;
+						if (glyphsNotWords.Checked) {
+							button = new Button (NumberToWords.Convert (buttonId) + " " + Char.ConvertFromUtf32 (buttonId + CODE_POINT),
+								is_default: buttonId == 0);
+						} else {
+							button = new Button (NumberToWords.Convert (buttonId),
+								is_default: buttonId == 0);
+						}
 						button.Clicked += () => {
 							clicked = buttonId;
 							Application.RequestStop ();
+
 						};
 						buttons.Add (button);
 						dialog.AddButton (button);
-						button.TabIndex = buttons [buttons.Count - 2].TabIndex + 1;
+						if (buttons.Count > 1) {
+							button.TabIndex = buttons [buttons.Count - 2].TabIndex + 1;
+						}
 					};
 					dialog.Add (add);
 
+					var addChar = new Button ($"Add a {Char.ConvertFromUtf32(CODE_POINT)} to each button") {
+						X = Pos.Center (),
+						Y = Pos.Center () + 1
+					};
+					addChar.Clicked += () => {
+						foreach (var button in buttons) {
+							button.Text += Char.ConvertFromUtf32 (CODE_POINT);
+						}
+						dialog.LayoutSubviews ();
+					};
+					dialog.Closed += (args) => {
+						buttonPressedLabel.Text = $"{clicked}";
+					};
+					dialog.Add (addChar);
+
 					Application.Run (dialog);
-					buttonPressedLabel.Text = $"{clicked}";
 
 				} catch (FormatException) {
 					buttonPressedLabel.Text = "Invalid Options";

+ 36 - 24
UICatalog/Scenarios/Editor.cs

@@ -94,7 +94,8 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem ("Forma_t", new MenuItem [] {
 					CreateWrapChecked (),
 					CreateAutocomplete(),
-					CreateAllowsTabChecked ()
+					CreateAllowsTabChecked (),
+					CreateReadOnlyChecked ()
 				}),
 				new MenuBarItem ("_Responder", new MenuItem [] {
 					CreateCanFocusChecked (),
@@ -572,6 +573,18 @@ namespace UICatalog.Scenarios {
 			return item;
 		}
 
+		private MenuItem CreateReadOnlyChecked ()
+		{
+			var item = new MenuItem {
+				Title = "Read Only"
+			};
+			item.CheckType |= MenuItemCheckStyle.Checked;
+			item.Checked = _textView.ReadOnly;
+			item.Action += () => _textView.ReadOnly = item.Checked = !item.Checked;
+
+			return item;
+		}
+
 		private MenuItem CreateCanFocusChecked ()
 		{
 			var item = new MenuItem {
@@ -748,12 +761,7 @@ namespace UICatalog.Scenarios {
 
 		private View FindTab ()
 		{
-			var d = new View () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
+			var d = new View ();
 			d.DrawContent += (e) => {
 				foreach (var v in d.Subviews) {
 					v.SetNeedsDisplay ();
@@ -762,10 +770,11 @@ namespace UICatalog.Scenarios {
 
 			var lblWidth = "Replace:".Length;
 
-			var label = new Label (0, 1, "Find:") {
+			var label = new Label ("Find:") {
+				Y = 1,
 				Width = lblWidth,
 				TextAlignment = TextAlignment.Right,
-				LayoutStyle = LayoutStyle.Computed
+				AutoSize = false
 			};
 			d.Add (label);
 
@@ -784,7 +793,8 @@ namespace UICatalog.Scenarios {
 				Width = 20,
 				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered,
-				IsDefault = true
+				IsDefault = true,
+				AutoSize = false
 			};
 			btnFindNext.Clicked += () => FindNext ();
 			d.Add (btnFindNext);
@@ -794,7 +804,8 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Top (btnFindNext) + 1,
 				Width = 20,
 				Enabled = !txtToFind.Text.IsEmpty,
-				TextAlignment = TextAlignment.Centered
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
 			};
 			btnFindPrevious.Clicked += () => FindPrevious ();
 			d.Add (btnFindPrevious);
@@ -810,7 +821,8 @@ namespace UICatalog.Scenarios {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (btnFindPrevious) + 2,
 				Width = 20,
-				TextAlignment = TextAlignment.Centered
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
 			};
 			btnCancel.Clicked += () => {
 				DisposeWinDialog ();
@@ -841,12 +853,7 @@ namespace UICatalog.Scenarios {
 
 		private View ReplaceTab ()
 		{
-			var d = new View () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
+			var d = new View ();
 			d.DrawContent += (e) => {
 				foreach (var v in d.Subviews) {
 					v.SetNeedsDisplay ();
@@ -855,10 +862,11 @@ namespace UICatalog.Scenarios {
 
 			var lblWidth = "Replace:".Length;
 
-			var label = new Label (0, 1, "Find:") {
+			var label = new Label ("Find:") {
+				Y = 1,
 				Width = lblWidth,
 				TextAlignment = TextAlignment.Right,
-				LayoutStyle = LayoutStyle.Computed
+				AutoSize = false
 			};
 			d.Add (label);
 
@@ -877,7 +885,8 @@ namespace UICatalog.Scenarios {
 				Width = 20,
 				Enabled = !txtToFind.Text.IsEmpty,
 				TextAlignment = TextAlignment.Centered,
-				IsDefault = true
+				IsDefault = true,
+				AutoSize = false
 			};
 			btnFindNext.Clicked += () => ReplaceNext ();
 			d.Add (btnFindNext);
@@ -904,7 +913,8 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Top (btnFindNext) + 1,
 				Width = 20,
 				Enabled = !txtToFind.Text.IsEmpty,
-				TextAlignment = TextAlignment.Centered
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
 			};
 			btnFindPrevious.Clicked += () => ReplacePrevious ();
 			d.Add (btnFindPrevious);
@@ -914,7 +924,8 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Top (btnFindPrevious) + 1,
 				Width = 20,
 				Enabled = !txtToFind.Text.IsEmpty,
-				TextAlignment = TextAlignment.Centered
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
 			};
 			btnReplaceAll.Clicked += () => ReplaceAll ();
 			d.Add (btnReplaceAll);
@@ -931,7 +942,8 @@ namespace UICatalog.Scenarios {
 				X = Pos.Right (txtToFind) + 1,
 				Y = Pos.Top (btnReplaceAll) + 1,
 				Width = 20,
-				TextAlignment = TextAlignment.Centered
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
 			};
 			btnCancel.Clicked += () => {
 				DisposeWinDialog ();

+ 4 - 0
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -90,6 +90,8 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Bottom (Label) + 1,
 				HotKeySpecifier = (System.Rune)'_',
 				CanFocus = true,
+				TextAlignment = TextAlignment.Centered,
+				VerticalTextAlignment = VerticalTextAlignment.Middle
 			});
 			Label.Clicked += () => MessageBox.Query ("Message", "Question?", "Yes", "No");
 
@@ -159,6 +161,7 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Error,
 				HotKeySpecifier = (System.Rune)'_',
 				CanFocus = true,
+				AutoSize = false
 			};
 			sizeBtn.Clicked += () => {
 				sizeBtn.Width = sizeBtn.Frame.Width + 5;
@@ -190,6 +193,7 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Error,
 				HotKeySpecifier = (System.Rune)'_',
 				CanFocus = true,
+				AutoSize = false
 			};
 			sizeBtnA.Clicked += () => {
 				sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);

+ 4 - 2
UICatalog/Scenarios/Scrolling.cs

@@ -129,7 +129,8 @@ namespace UICatalog.Scenarios {
 				Y = 0,
 				Width = Dim.Fill (),  // FIXED: I don't think this should be needed; DimFill() should respect container's frame. X does.
 				Height = 2,
-				ColorScheme = Colors.Error
+				ColorScheme = Colors.Error,
+				AutoSize = false
 			};
 			scrollView.Add (horizontalRuler);
 			const string vrule = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n";
@@ -139,7 +140,8 @@ namespace UICatalog.Scenarios {
 				Y = 0,
 				Width = 1,
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Error
+				ColorScheme = Colors.Error,
+				AutoSize = false
 			};
 			scrollView.Add (verticalRuler);
 

+ 311 - 3
UICatalog/Scenarios/TableEditor.cs

@@ -4,6 +4,7 @@ using System.Data;
 using Terminal.Gui;
 using System.Linq;
 using System.Globalization;
+using static Terminal.Gui.TableView;
 
 namespace UICatalog.Scenarios {
 
@@ -24,6 +25,7 @@ namespace UICatalog.Scenarios {
 		private MenuItem miCellLines;
 		private MenuItem miFullRowSelect;
 		private MenuItem miExpandLastColumn;
+		private MenuItem miSmoothScrolling;
 		private MenuItem miAlternatingColors;
 		private MenuItem miCursor;
 
@@ -49,6 +51,7 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_OpenBigExample", "", () => OpenExample(true)),
 					new MenuItem ("_OpenSmallExample", "", () => OpenExample(false)),
+					new MenuItem ("OpenCharacter_Map","",()=>OpenUnicodeMap()),
 					new MenuItem ("_CloseExample", "", () => CloseExample()),
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
@@ -61,14 +64,23 @@ namespace UICatalog.Scenarios {
 					miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked },
 					miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked },
 					miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn()){Checked = tableView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked },
+					miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling()){Checked = tableView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked },
 					new MenuItem ("_AllLines", "", () => ToggleAllCellLines()),
 					new MenuItem ("_NoLines", "", () => ToggleNoCellLines()),
 					miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked},
 					miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked},
 					new MenuItem ("_ClearColumnStyles", "", () => ClearColumnStyles()),
 				}),
+				new MenuBarItem ("_Column", new MenuItem [] {
+					new MenuItem ("_Set Max Width", "", SetMaxWidth),
+					new MenuItem ("_Set Min Width", "", SetMinWidth),
+					new MenuItem ("_Set MinAcceptableWidth", "",SetMinAcceptableWidth),
+					new MenuItem ("_Set All MinAcceptableWidth=1", "",SetMinAcceptableWidthToOne),
+				}),
 			});
-			Top.Add (menu);
+		
+
+		Top.Add (menu);
 
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)),
@@ -91,7 +103,7 @@ namespace UICatalog.Scenarios {
 
 			Win.Add(selectedCellLabel);
 
-			tableView.SelectedCellChanged += (e)=>{selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}";};
+			tableView.SelectedCellChanged += (e) => { selectedCellLabel.Text = $"{tableView.SelectedRow},{tableView.SelectedColumn}"; };
 			tableView.CellActivated += EditCurrentCell;
 			tableView.KeyPress += TableViewKeyPress;
 
@@ -120,6 +132,85 @@ namespace UICatalog.Scenarios {
 			};
 		}
 
+
+		private DataColumn GetColumn ()
+		{
+			if (tableView.Table == null)
+				return null;
+
+			if (tableView.SelectedColumn < 0 || tableView.SelectedColumn > tableView.Table.Columns.Count)
+				return null;
+
+			return tableView.Table.Columns [tableView.SelectedColumn];
+		}
+
+		private void SetMinAcceptableWidthToOne ()
+		{
+			foreach (DataColumn c in tableView.Table.Columns) 
+			{
+				var style = tableView.Style.GetOrCreateColumnStyle (c);
+				style.MinAcceptableWidth = 1;
+			}
+		}
+		private void SetMinAcceptableWidth ()
+		{
+			var col = GetColumn ();
+			RunColumnWidthDialog (col, "MinAcceptableWidth", (s,v)=>s.MinAcceptableWidth = v,(s)=>s.MinAcceptableWidth);
+		}
+
+		private void SetMinWidth ()
+		{
+			var col = GetColumn ();
+			RunColumnWidthDialog (col, "MinWidth", (s, v) => s.MinWidth = v, (s) => s.MinWidth);
+		}
+
+		private void SetMaxWidth ()
+		{
+			var col = GetColumn ();
+			RunColumnWidthDialog (col, "MaxWidth", (s, v) => s.MaxWidth = v, (s) => s.MaxWidth);
+		}
+
+		private void RunColumnWidthDialog (DataColumn col, string prompt, Action<ColumnStyle,int> setter,Func<ColumnStyle,int> getter)
+		{
+			var accepted = false;
+			var ok = new Button ("Ok", is_default: true);
+			ok.Clicked += () => { accepted = true; Application.RequestStop (); };
+			var cancel = new Button ("Cancel");
+			cancel.Clicked += () => { Application.RequestStop (); };
+			var d = new Dialog (prompt, 60, 20, ok, cancel);
+
+			var style = tableView.Style.GetOrCreateColumnStyle (col);
+
+			var lbl = new Label () {
+				X = 0,
+				Y = 1,
+				Text = col.ColumnName
+			};
+
+			var tf = new TextField () {
+				Text = getter(style).ToString (),
+				X = 0,
+				Y = 2,
+				Width = Dim.Fill ()
+			};
+
+			d.Add (lbl, tf);
+			tf.SetFocus ();
+
+			Application.Run (d);
+
+			if (accepted) {
+
+				try {
+					setter (style, int.Parse (tf.Text.ToString()));
+				} catch (Exception ex) {
+					MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok");
+				}
+
+				tableView.Update ();
+			}
+		}
+
 		private void SetupScrollBar ()
 		{
 			var _scrollBar = new ScrollBarView (tableView, true);
@@ -227,6 +318,14 @@ namespace UICatalog.Scenarios {
 
 			tableView.Update();
 
+		}
+		private void ToggleSmoothScrolling()
+		{
+			miSmoothScrolling.Checked = !miSmoothScrolling.Checked;
+			tableView.Style.SmoothHorizontalScrolling = miSmoothScrolling.Checked;
+
+			tableView.Update ();
+
 		}
 		private void ToggleCellLines()
 		{
@@ -301,6 +400,207 @@ namespace UICatalog.Scenarios {
 			SetDemoTableStyles();
 		}
 
+		private void OpenUnicodeMap()
+		{
+			tableView.Table = BuildUnicodeMap ();
+			tableView.Update ();
+		}
+
+		private DataTable BuildUnicodeMap ()
+		{
+			var dt = new DataTable ();
+
+			// add cols called 0 to 9
+			for (int i = 0; i < 10;i++) {
+
+				var col = dt.Columns.Add (i.ToString (), typeof (uint));
+				var style = tableView.Style.GetOrCreateColumnStyle (col);
+				style.RepresentationGetter = (o) => new Rune ((uint)o).ToString ();
+			}
+
+			// add cols called a to z
+			for (int i = 'a'; i < 'a'+26; i++) {
+				
+				var col =dt.Columns.Add (((char)i).ToString (), typeof (uint));
+				var style = tableView.Style.GetOrCreateColumnStyle (col);
+				style.RepresentationGetter = (o) => new Rune ((uint)o).ToString ();
+			}
+
+			// now add table contents
+			List<uint> runes = new List<uint> ();
+
+			foreach(var range in Ranges) {
+				for(uint i=range.Start;i<=range.End;i++) {
+					runes.Add (i);
+				}
+			}
+
+			DataRow dr = null;
+
+			for(int i = 0; i<runes.Count;i++) {
+				if(dr == null || i% dt.Columns.Count == 0) {
+					dr = dt.Rows.Add ();
+				}
+				dr [i % dt.Columns.Count] = runes [i].ToString();
+			}
+
+			return dt;
+		}
+		class UnicodeRange {
+			public uint Start;
+			public uint End;
+			public string Category;
+			public UnicodeRange (uint start, uint end, string category)
+			{
+				this.Start = start;
+				this.End = end;
+				this.Category = category;
+			}
+		}
+
+		List<UnicodeRange> Ranges = new List<UnicodeRange> {
+			new UnicodeRange (0x0000, 0x001F, "ASCII Control Characters"),
+			new UnicodeRange (0x0080, 0x009F, "C0 Control Characters"),
+			new UnicodeRange(0x1100, 0x11ff,"Hangul Jamo"),	// This is where wide chars tend to start
+			new UnicodeRange(0x20A0, 0x20CF,"Currency Symbols"),
+			new UnicodeRange(0x2100, 0x214F,"Letterlike Symbols"),
+			new UnicodeRange(0x2190, 0x21ff,"Arrows" ),
+			new UnicodeRange(0x2200, 0x22ff,"Mathematical symbols"),
+			new UnicodeRange(0x2300, 0x23ff,"Miscellaneous Technical"),
+			new UnicodeRange(0x2500, 0x25ff,"Box Drawing & Geometric Shapes"),
+			new UnicodeRange(0x2600, 0x26ff,"Miscellaneous Symbols"),
+			new UnicodeRange(0x2700, 0x27ff,"Dingbats"),
+			new UnicodeRange(0x2800, 0x28ff,"Braille"),
+			new UnicodeRange(0x2b00, 0x2bff,"Miscellaneous Symbols and Arrows"),
+			new UnicodeRange(0xFB00, 0xFb4f,"Alphabetic Presentation Forms"),
+			new UnicodeRange(0x12400, 0x1240f,"Cuneiform Numbers and Punctuation"),
+			new UnicodeRange(0x1FA00, 0x1FA0f,"Chess Symbols"),
+			new UnicodeRange((uint)(CharMap.MaxCodePointVal - 16), (uint)CharMap.MaxCodePointVal,"End"),
+
+			new UnicodeRange (0x0020 ,0x007F        ,"Basic Latin"),
+			new UnicodeRange (0x00A0 ,0x00FF        ,"Latin-1 Supplement"),
+			new UnicodeRange (0x0100 ,0x017F        ,"Latin Extended-A"),
+			new UnicodeRange (0x0180 ,0x024F        ,"Latin Extended-B"),
+			new UnicodeRange (0x0250 ,0x02AF        ,"IPA Extensions"),
+			new UnicodeRange (0x02B0 ,0x02FF        ,"Spacing Modifier Letters"),
+			new UnicodeRange (0x0300 ,0x036F        ,"Combining Diacritical Marks"),
+			new UnicodeRange (0x0370 ,0x03FF        ,"Greek and Coptic"),
+			new UnicodeRange (0x0400 ,0x04FF        ,"Cyrillic"),
+			new UnicodeRange (0x0500 ,0x052F        ,"Cyrillic Supplementary"),
+			new UnicodeRange (0x0530 ,0x058F        ,"Armenian"),
+			new UnicodeRange (0x0590 ,0x05FF        ,"Hebrew"),
+			new UnicodeRange (0x0600 ,0x06FF        ,"Arabic"),
+			new UnicodeRange (0x0700 ,0x074F        ,"Syriac"),
+			new UnicodeRange (0x0780 ,0x07BF        ,"Thaana"),
+			new UnicodeRange (0x0900 ,0x097F        ,"Devanagari"),
+			new UnicodeRange (0x0980 ,0x09FF        ,"Bengali"),
+			new UnicodeRange (0x0A00 ,0x0A7F        ,"Gurmukhi"),
+			new UnicodeRange (0x0A80 ,0x0AFF        ,"Gujarati"),
+			new UnicodeRange (0x0B00 ,0x0B7F        ,"Oriya"),
+			new UnicodeRange (0x0B80 ,0x0BFF        ,"Tamil"),
+			new UnicodeRange (0x0C00 ,0x0C7F        ,"Telugu"),
+			new UnicodeRange (0x0C80 ,0x0CFF        ,"Kannada"),
+			new UnicodeRange (0x0D00 ,0x0D7F        ,"Malayalam"),
+			new UnicodeRange (0x0D80 ,0x0DFF        ,"Sinhala"),
+			new UnicodeRange (0x0E00 ,0x0E7F        ,"Thai"),
+			new UnicodeRange (0x0E80 ,0x0EFF        ,"Lao"),
+			new UnicodeRange (0x0F00 ,0x0FFF        ,"Tibetan"),
+			new UnicodeRange (0x1000 ,0x109F        ,"Myanmar"),
+			new UnicodeRange (0x10A0 ,0x10FF        ,"Georgian"),
+			new UnicodeRange (0x1100 ,0x11FF        ,"Hangul Jamo"),
+			new UnicodeRange (0x1200 ,0x137F        ,"Ethiopic"),
+			new UnicodeRange (0x13A0 ,0x13FF        ,"Cherokee"),
+			new UnicodeRange (0x1400 ,0x167F        ,"Unified Canadian Aboriginal Syllabics"),
+			new UnicodeRange (0x1680 ,0x169F        ,"Ogham"),
+			new UnicodeRange (0x16A0 ,0x16FF        ,"Runic"),
+			new UnicodeRange (0x1700 ,0x171F        ,"Tagalog"),
+			new UnicodeRange (0x1720 ,0x173F        ,"Hanunoo"),
+			new UnicodeRange (0x1740 ,0x175F        ,"Buhid"),
+			new UnicodeRange (0x1760 ,0x177F        ,"Tagbanwa"),
+			new UnicodeRange (0x1780 ,0x17FF        ,"Khmer"),
+			new UnicodeRange (0x1800 ,0x18AF        ,"Mongolian"),
+			new UnicodeRange (0x1900 ,0x194F        ,"Limbu"),
+			new UnicodeRange (0x1950 ,0x197F        ,"Tai Le"),
+			new UnicodeRange (0x19E0 ,0x19FF        ,"Khmer Symbols"),
+			new UnicodeRange (0x1D00 ,0x1D7F        ,"Phonetic Extensions"),
+			new UnicodeRange (0x1E00 ,0x1EFF        ,"Latin Extended Additional"),
+			new UnicodeRange (0x1F00 ,0x1FFF        ,"Greek Extended"),
+			new UnicodeRange (0x2000 ,0x206F        ,"General Punctuation"),
+			new UnicodeRange (0x2070 ,0x209F        ,"Superscripts and Subscripts"),
+			new UnicodeRange (0x20A0 ,0x20CF        ,"Currency Symbols"),
+			new UnicodeRange (0x20D0 ,0x20FF        ,"Combining Diacritical Marks for Symbols"),
+			new UnicodeRange (0x2100 ,0x214F        ,"Letterlike Symbols"),
+			new UnicodeRange (0x2150 ,0x218F        ,"Number Forms"),
+			new UnicodeRange (0x2190 ,0x21FF        ,"Arrows"),
+			new UnicodeRange (0x2200 ,0x22FF        ,"Mathematical Operators"),
+			new UnicodeRange (0x2300 ,0x23FF        ,"Miscellaneous Technical"),
+			new UnicodeRange (0x2400 ,0x243F        ,"Control Pictures"),
+			new UnicodeRange (0x2440 ,0x245F        ,"Optical Character Recognition"),
+			new UnicodeRange (0x2460 ,0x24FF        ,"Enclosed Alphanumerics"),
+			new UnicodeRange (0x2500 ,0x257F        ,"Box Drawing"),
+			new UnicodeRange (0x2580 ,0x259F        ,"Block Elements"),
+			new UnicodeRange (0x25A0 ,0x25FF        ,"Geometric Shapes"),
+			new UnicodeRange (0x2600 ,0x26FF        ,"Miscellaneous Symbols"),
+			new UnicodeRange (0x2700 ,0x27BF        ,"Dingbats"),
+			new UnicodeRange (0x27C0 ,0x27EF        ,"Miscellaneous Mathematical Symbols-A"),
+			new UnicodeRange (0x27F0 ,0x27FF        ,"Supplemental Arrows-A"),
+			new UnicodeRange (0x2800 ,0x28FF        ,"Braille Patterns"),
+			new UnicodeRange (0x2900 ,0x297F        ,"Supplemental Arrows-B"),
+			new UnicodeRange (0x2980 ,0x29FF        ,"Miscellaneous Mathematical Symbols-B"),
+			new UnicodeRange (0x2A00 ,0x2AFF        ,"Supplemental Mathematical Operators"),
+			new UnicodeRange (0x2B00 ,0x2BFF        ,"Miscellaneous Symbols and Arrows"),
+			new UnicodeRange (0x2E80 ,0x2EFF        ,"CJK Radicals Supplement"),
+			new UnicodeRange (0x2F00 ,0x2FDF        ,"Kangxi Radicals"),
+			new UnicodeRange (0x2FF0 ,0x2FFF        ,"Ideographic Description Characters"),
+			new UnicodeRange (0x3000 ,0x303F        ,"CJK Symbols and Punctuation"),
+			new UnicodeRange (0x3040 ,0x309F        ,"Hiragana"),
+			new UnicodeRange (0x30A0 ,0x30FF        ,"Katakana"),
+			new UnicodeRange (0x3100 ,0x312F        ,"Bopomofo"),
+			new UnicodeRange (0x3130 ,0x318F        ,"Hangul Compatibility Jamo"),
+			new UnicodeRange (0x3190 ,0x319F        ,"Kanbun"),
+			new UnicodeRange (0x31A0 ,0x31BF        ,"Bopomofo Extended"),
+			new UnicodeRange (0x31F0 ,0x31FF        ,"Katakana Phonetic Extensions"),
+			new UnicodeRange (0x3200 ,0x32FF        ,"Enclosed CJK Letters and Months"),
+			new UnicodeRange (0x3300 ,0x33FF        ,"CJK Compatibility"),
+			new UnicodeRange (0x3400 ,0x4DBF        ,"CJK Unified Ideographs Extension A"),
+			new UnicodeRange (0x4DC0 ,0x4DFF        ,"Yijing Hexagram Symbols"),
+			new UnicodeRange (0x4E00 ,0x9FFF        ,"CJK Unified Ideographs"),
+			new UnicodeRange (0xA000 ,0xA48F        ,"Yi Syllables"),
+			new UnicodeRange (0xA490 ,0xA4CF        ,"Yi Radicals"),
+			new UnicodeRange (0xAC00 ,0xD7AF        ,"Hangul Syllables"),
+			new UnicodeRange (0xD800 ,0xDB7F        ,"High Surrogates"),
+			new UnicodeRange (0xDB80 ,0xDBFF        ,"High Private Use Surrogates"),
+			new UnicodeRange (0xDC00 ,0xDFFF        ,"Low Surrogates"),
+			new UnicodeRange (0xE000 ,0xF8FF        ,"Private Use Area"),
+			new UnicodeRange (0xF900 ,0xFAFF        ,"CJK Compatibility Ideographs"),
+			new UnicodeRange (0xFB00 ,0xFB4F        ,"Alphabetic Presentation Forms"),
+			new UnicodeRange (0xFB50 ,0xFDFF        ,"Arabic Presentation Forms-A"),
+			new UnicodeRange (0xFE00 ,0xFE0F        ,"Variation Selectors"),
+			new UnicodeRange (0xFE20 ,0xFE2F        ,"Combining Half Marks"),
+			new UnicodeRange (0xFE30 ,0xFE4F        ,"CJK Compatibility Forms"),
+			new UnicodeRange (0xFE50 ,0xFE6F        ,"Small Form Variants"),
+			new UnicodeRange (0xFE70 ,0xFEFF        ,"Arabic Presentation Forms-B"),
+			new UnicodeRange (0xFF00 ,0xFFEF        ,"Halfwidth and Fullwidth Forms"),
+			new UnicodeRange (0xFFF0 ,0xFFFF        ,"Specials"),
+			new UnicodeRange (0x10000, 0x1007F   ,"Linear B Syllabary"),
+			new UnicodeRange (0x10080, 0x100FF   ,"Linear B Ideograms"),
+			new UnicodeRange (0x10100, 0x1013F   ,"Aegean Numbers"),
+			new UnicodeRange (0x10300, 0x1032F   ,"Old Italic"),
+			new UnicodeRange (0x10330, 0x1034F   ,"Gothic"),
+			new UnicodeRange (0x10380, 0x1039F   ,"Ugaritic"),
+			new UnicodeRange (0x10400, 0x1044F   ,"Deseret"),
+			new UnicodeRange (0x10450, 0x1047F   ,"Shavian"),
+			new UnicodeRange (0x10480, 0x104AF   ,"Osmanya"),
+			new UnicodeRange (0x10800, 0x1083F   ,"Cypriot Syllabary"),
+			new UnicodeRange (0x1D000, 0x1D0FF   ,"Byzantine Musical Symbols"),
+			new UnicodeRange (0x1D100, 0x1D1FF   ,"Musical Symbols"),
+			new UnicodeRange (0x1D300, 0x1D35F   ,"Tai Xuan Jing Symbols"),
+			new UnicodeRange (0x1D400, 0x1D7FF   ,"Mathematical Alphanumeric Symbols"),
+			new UnicodeRange (0x1F600, 0x1F532   ,"Emojis Symbols"),
+			new UnicodeRange (0x20000, 0x2A6DF   ,"CJK Unified Ideographs Extension B"),
+			new UnicodeRange (0x2F800, 0x2FA1F   ,"CJK Compatibility Ideographs Supplement"),
+			new UnicodeRange (0xE0000, 0xE007F   ,"Tags"),
+		};
 		private void SetDemoTableStyles ()
 		{
 			var alignMid = new TableView.ColumnStyle () {
@@ -353,6 +653,9 @@ namespace UICatalog.Scenarios {
 		{
 			if(e.Table == null)
 				return;
+			var o = e.Table.Rows [e.Row] [e.Col];
+
+			var title = o is uint u ? GetUnicodeCategory(u) + $"(0x{o:X4})" : "Enter new value";
 
 			var oldValue = e.Table.Rows[e.Row][e.Col].ToString();
 			bool okPressed = false;
@@ -361,7 +664,7 @@ namespace UICatalog.Scenarios {
 			ok.Clicked += () => { okPressed = true; Application.RequestStop (); };
 			var cancel = new Button ("Cancel");
 			cancel.Clicked += () => { Application.RequestStop (); };
-			var d = new Dialog ("Enter new value", 60, 20, ok, cancel);
+			var d = new Dialog (title, 60, 20, ok, cancel);
 
 			var lbl = new Label() {
 				X = 0,
@@ -395,6 +698,11 @@ namespace UICatalog.Scenarios {
 			}
 		}
 
+		private string GetUnicodeCategory (uint u)
+		{
+			return Ranges.FirstOrDefault (r => u >= r.Start && u <= r.End)?.Category ?? "Unknown";
+		}
+
 		/// <summary>
 		/// Generates a new demo <see cref="DataTable"/> with the given number of <paramref name="cols"/> (min 5) and <paramref name="rows"/>
 		/// </summary>

+ 2 - 2
UICatalog/Scenarios/TextAlignments.cs

@@ -22,8 +22,8 @@ namespace UICatalog.Scenarios {
 			var multiLineHeight = 5;
 
 			foreach (var alignment in alignments) {
-				singleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, X = 1, Width = Dim.Fill (1), Height = 1, ColorScheme = Colors.Dialog };
-				multipleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, X = 1, Width = Dim.Fill (1), Height = multiLineHeight, ColorScheme = Colors.Dialog };
+				singleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, X = 1, Width = Dim.Fill (1), Height = 1, ColorScheme = Colors.Dialog, AutoSize = false };
+				multipleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, X = 1, Width = Dim.Fill (1), Height = multiLineHeight, ColorScheme = Colors.Dialog, AutoSize = false };
 			}
 
 			// Add a label & text field so we can demo IsDefault

+ 1 - 1
UICatalog/Scenarios/TextAlignmentsAndDirection.cs

@@ -60,7 +60,7 @@ namespace UICatalog.Scenarios {
 
 			// Multi-Line
 
-			var container = new View () { X = 0, Y = Pos.Bottom (txtLabelHJ), Width = Dim.Fill (31), Height = Dim.Fill (7), ColorScheme = color2 };
+			var container = new View () { X = 0, Y = Pos.Bottom (txtLabelHJ), Width = Dim.Fill (31), Height = Dim.Fill (6), ColorScheme = color2 };
 
 			var txtLabelTL = new Label (txt) { X = 1 /*                    */, Y = 1, Width = Dim.Percent (100f / 3f), Height = Dim.Percent (100f / 3f), TextAlignment = TextAlignment.Left, VerticalTextAlignment = VerticalTextAlignment.Top, ColorScheme = color1 };
 			var txtLabelTC = new Label (txt) { X = Pos.Right (txtLabelTL) + 2, Y = 1, Width = Dim.Percent (100f / 3f), Height = Dim.Percent (100f / 3f), TextAlignment = TextAlignment.Centered, VerticalTextAlignment = VerticalTextAlignment.Top, ColorScheme = color1 };

+ 3 - 3
UICatalog/Scenarios/TextFormatterDemo.cs

@@ -26,7 +26,7 @@ namespace UICatalog.Scenarios {
 			string text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line.";
 			string unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου.";
 
-			Label blockText = new Label () { ColorScheme = Colors.TopLevel, X = 0, Y = 0, Height = 10, Width = Dim.Fill (0) };
+			Label blockText = new Label () { ColorScheme = Colors.TopLevel, X = 0, Y = 0, Height = 10, Width = Dim.Fill (0), AutoSize = false };
 
 			var block = new StringBuilder ();
 			block.AppendLine ("  ▄████  █    ██  ██▓      ▄████▄    ██████ ");
@@ -56,8 +56,8 @@ namespace UICatalog.Scenarios {
 			var multiLineHeight = 5;
 
 			foreach (var alignment in alignments) {
-				singleLines [(int)alignment] = new Label (text) { TextAlignment = alignment, X = 0, Width = Dim.Fill (), Height = 1, ColorScheme = Colors.Dialog };
-				multipleLines [(int)alignment] = new Label (text) { TextAlignment = alignment, X = 0, Width = Dim.Fill (), Height = multiLineHeight, ColorScheme = Colors.Dialog };
+				singleLines [(int)alignment] = new Label (text) { TextAlignment = alignment, X = 0, Width = Dim.Fill (), Height = 1, ColorScheme = Colors.Dialog, AutoSize = false };
+				multipleLines [(int)alignment] = new Label (text) { TextAlignment = alignment, X = 0, Width = Dim.Fill (), Height = multiLineHeight, ColorScheme = Colors.Dialog, AutoSize = false };
 			}
 
 			var label = new Label ($"Demonstrating single-line (should clip):") { Y = Pos.Bottom (unicodeCheckBox) + 1 };

+ 106 - 0
UICatalog/Scenarios/WizardAsView.cs

@@ -0,0 +1,106 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "WizardAsView", Description: "Shows using the Wizard class in an non-modal way")]
+	[ScenarioCategory ("Wizards")]
+	public class WizardAsView : Scenario {
+
+		public override void Init (Toplevel top, ColorScheme colorScheme)
+		{
+			Top = Application.Top;
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuItem ("_Restart Configuration...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to reset the Wizard and start over?", "Ok", "Cancel")),
+					new MenuItem ("Re_boot Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to reboot the server start over?", "Ok", "Cancel")),
+					new MenuItem ("_Shutdown Server...", "", () => MessageBox.Query ("Wizaard", "Are you sure you want to cancel setup and shutdown?", "Ok", "Cancel")),
+				})
+			});
+			Top.Add (menu);
+
+			// No need for a Title because the border is disabled
+			var wizard = new Wizard () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+			};
+
+			// Set Mdoal to false to cause the Wizard class to render without a frame and
+			// behave like an non-modal View (vs. a modal/pop-up Window).
+			wizard.Modal = false;
+
+			wizard.MovingBack += (args) => {
+				//args.Cancel = true;
+				//actionLabel.Text = "Moving Back";
+			};
+
+			wizard.MovingNext += (args) => {
+				//args.Cancel = true;
+				//actionLabel.Text = "Moving Next";
+			};
+
+			wizard.Finished += (args) => {
+				//args.Cancel = true;
+				MessageBox.Query ("Setup Wizard", "Finished", "Ok");
+				Application.RequestStop ();
+			};
+
+			wizard.Cancelled += (args) => {
+				var btn = MessageBox.Query ("Setup Wizard", "Are you sure you want to cancel?", "Yes", "No");
+				args.Cancel = btn == 1;
+				if (btn == 0) {
+					Application.RequestStop ();
+				}
+			};
+
+			// Add 1st step
+			var firstStep = new Wizard.WizardStep ("End User License Agreement");
+			wizard.AddStep (firstStep);
+			firstStep.NextButtonText = "Accept!";
+			firstStep.HelpText = "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA.";
+
+			// Add 2nd step
+			var secondStep = new Wizard.WizardStep ("Second Step");
+			wizard.AddStep (secondStep);
+			secondStep.HelpText = "This is the help text for the Second Step.\n\nPress the button to change the Title.\n\nIf First Name is empty the step will prevent moving to the next step.";
+
+			var buttonLbl = new Label () { Text = "Second Step Button: ", X = 0, Y = 0 };
+			var button = new Button () {
+				Text = "Press Me to Rename Step",
+				X = Pos.Right (buttonLbl),
+				Y = Pos.Top (buttonLbl)
+			};
+			button.Clicked += () => {
+				secondStep.Title = "2nd Step";
+				MessageBox.Query ("Wizard Scenario", "This Wizard Step's title was changed to '2nd Step'", "Ok");
+			};
+			secondStep.Add (buttonLbl, button);
+			var lbl = new Label () { Text = "First Name: ", X = Pos.Left (buttonLbl), Y = Pos.Bottom (buttonLbl) };
+			var firstNameField = new TextField () { Text = "Number", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
+			secondStep.Add (lbl, firstNameField);
+			lbl = new Label () { Text = "Last Name:  ", X = Pos.Left (buttonLbl), Y = Pos.Bottom (lbl) };
+			var lastNameField = new TextField () { Text = "Six", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
+			secondStep.Add (lbl, lastNameField);
+
+			// Add last step
+			var lastStep = new Wizard.WizardStep ("The last step");
+			wizard.AddStep (lastStep);
+			lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing Esc will cancel.";
+
+			Top.Add (wizard);
+			Application.Run (Top);
+		}
+
+		public override void Run ()
+		{
+			// Do nothing in the override because we call Application.Run above
+			// (just to make it clear how the Top is being run and not the Wizard).
+		}
+	}
+}

+ 292 - 0
UICatalog/Scenarios/Wizards.cs

@@ -0,0 +1,292 @@
+using NStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios {
+	[ScenarioMetadata (Name: "Wizards", Description: "Demonstrates the Wizard class")]
+	[ScenarioCategory ("Dialogs"), ScenarioCategory ("Top Level Windows"), ScenarioCategory ("Wizards")]
+	public class Wizards : Scenario {
+		public override void Setup ()
+		{
+			var frame = new FrameView ("Wizard Options") {
+				X = Pos.Center (),
+				Y = 0,
+				Width = Dim.Percent (75),
+				ColorScheme = Colors.Base,
+			};
+			Win.Add (frame);
+
+			var label = new Label ("Width:") {
+				X = 0,
+				Y = 0,
+				Width = 15,
+				Height = 1,
+				TextAlignment = Terminal.Gui.TextAlignment.Right,
+				AutoSize = false
+			};
+			frame.Add (label);
+			var widthEdit = new TextField ("80") {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = 5,
+				Height = 1
+			};
+			frame.Add (widthEdit);
+
+			label = new Label ("Height:") {
+				X = 0,
+				Y = Pos.Bottom (label),
+				Width = Dim.Width (label),
+				Height = 1,
+				TextAlignment = Terminal.Gui.TextAlignment.Right,
+				AutoSize = false
+			};
+			frame.Add (label);
+			var heightEdit = new TextField ("20") {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = 5,
+				Height = 1
+			};
+			frame.Add (heightEdit);
+
+			label = new Label ("Title:") {
+				X = 0,
+				Y = Pos.Bottom (label),
+				Width = Dim.Width (label),
+				Height = 1,
+				TextAlignment = Terminal.Gui.TextAlignment.Right,
+				AutoSize = false
+			};
+			frame.Add (label);
+			var titleEdit = new TextField ("Gandolf") {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = Dim.Fill (),
+				Height = 1
+			};
+			frame.Add (titleEdit);
+
+			void Top_Loaded ()
+			{
+				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit) + 2;
+				Top.Loaded -= Top_Loaded;
+			}
+			Top.Loaded += Top_Loaded;
+
+			label = new Label ("Action:") {
+				X = Pos.Center (),
+				Y = Pos.AnchorEnd (1),
+				TextAlignment = Terminal.Gui.TextAlignment.Right,
+			};
+			Win.Add (label);
+
+			var actionLabel = new Label ("") {
+				X = Pos.Right (label),
+				Y = Pos.AnchorEnd (1),
+				ColorScheme = Colors.Error,
+			};
+			Win.Add (actionLabel);
+
+			var showWizardButton = new Button ("Show Wizard") {
+				X = Pos.Center (),
+				Y = Pos.Bottom (frame) + 2,
+				IsDefault = true,
+			};
+
+			showWizardButton.Clicked += () => {
+				try {
+					int width = 0;
+					int.TryParse (widthEdit.Text.ToString (), out width);
+					int height = 0;
+					int.TryParse (heightEdit.Text.ToString (), out height);
+
+					if (width < 1 || height < 1) {
+						MessageBox.ErrorQuery ("Nope", "Height and width must be greater than 0 (much bigger)", "Ok");
+						return;
+					}
+
+					actionLabel.Text = ustring.Empty;
+
+					var wizard = new Wizard (titleEdit.Text) {
+						Width = width,
+						Height = height
+					};
+
+					wizard.MovingBack += (args) => {
+						//args.Cancel = true;
+						actionLabel.Text = "Moving Back";
+					};
+
+					wizard.MovingNext += (args) => {
+						//args.Cancel = true;
+						actionLabel.Text = "Moving Next";
+					};
+
+					wizard.Finished += (args) => {
+						//args.Cancel = true;
+						actionLabel.Text = "Finished";
+					};
+
+					wizard.Cancelled += (args) => {
+						//args.Cancel = true;
+						actionLabel.Text = "Cancelled";
+					};
+
+					// Add 1st step
+					var firstStep = new Wizard.WizardStep ("End User License Agreement");
+					firstStep.NextButtonText = "Accept!";
+					firstStep.HelpText = "This is the End User License Agreement.\n\n\n\n\n\nThis is a test of the emergency broadcast system. This is a test of the emergency broadcast system.\nThis is a test of the emergency broadcast system.\n\n\nThis is a test of the emergency broadcast system.\n\nThis is a test of the emergency broadcast system.\n\n\n\nThe end of the EULA.";
+					wizard.AddStep (firstStep);
+
+					// Add 2nd step
+					var secondStep = new Wizard.WizardStep ("Second Step");
+					wizard.AddStep (secondStep);
+					secondStep.HelpText = "This is the help text for the Second Step.\n\nPress the button to change the Title.\n\nIf First Name is empty the step will prevent moving to the next step.";
+
+					var buttonLbl = new Label () { Text = "Second Step Button: ", X = 1, Y = 1 };
+					var button = new Button () {
+						Text = "Press Me to Rename Step",
+						X = Pos.Right (buttonLbl),
+						Y = Pos.Top (buttonLbl)
+					};
+					button.Clicked += () => {
+						secondStep.Title = "2nd Step";
+						MessageBox.Query ("Wizard Scenario", "This Wizard Step's title was changed to '2nd Step'");
+					};
+					secondStep.Add (buttonLbl, button);
+					var lbl = new Label () { Text = "First Name: ", X = 1, Y = Pos.Bottom (buttonLbl) };
+					var firstNameField = new TextField () { Text = "Number", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
+					secondStep.Add (lbl, firstNameField);
+					lbl = new Label () { Text = "Last Name:  ", X = 1, Y = Pos.Bottom (lbl) };
+					var lastNameField = new TextField () { Text = "Six", Width = 30, X = Pos.Right (lbl), Y = Pos.Top (lbl) };
+					secondStep.Add (lbl, lastNameField);
+					var thirdStepEnabledCeckBox = new CheckBox () { Text = "Enable Step _3", Checked = false, X = Pos.Left (lastNameField), Y = Pos.Bottom (lastNameField) };
+					secondStep.Add (thirdStepEnabledCeckBox);
+
+					// Add a frame 
+					var frame = new FrameView ($"A Broken Frame (by Depeche Mode)") {
+						X = 0,
+						Y = Pos.Bottom (thirdStepEnabledCeckBox) + 2,
+						Width = Dim.Fill (),
+						Height = 4
+					};
+					frame.Add (new TextField ("This is a TextField inside of the frame."));
+					secondStep.Add (frame);
+					wizard.StepChanging += (args) => {
+						if (args.OldStep == secondStep && firstNameField.Text.IsEmpty) {
+							args.Cancel = true;
+							var btn = MessageBox.ErrorQuery ("Second Step", "You must enter a First Name to continue", "Ok");
+						}
+					};
+
+					// Add 3rd (optional) step
+					var thirdStep = new Wizard.WizardStep ("Third Step (Optional)");
+					wizard.AddStep (thirdStep);
+					thirdStep.HelpText = "This is step is optional (WizardStep.Enabled = false). Enable it with the checkbox in Step 2.";
+					var step3Label = new Label () {
+						Text = "This step is optional.",
+						X = 0,
+						Y = 0
+					};
+					thirdStep.Add (step3Label);
+					var progLbl = new Label () { Text = "Third Step ProgressBar: ", X = 1, Y = 10 };
+					var progressBar = new ProgressBar () {
+						X = Pos.Right (progLbl),
+						Y = Pos.Top (progLbl),
+						Width = 40,
+						Fraction = 0.42F
+					};
+					thirdStep.Add (progLbl, progressBar);
+					thirdStep.Enabled = thirdStepEnabledCeckBox.Checked;
+					thirdStepEnabledCeckBox.Toggled += (args) => {
+						thirdStep.Enabled = thirdStepEnabledCeckBox.Checked;
+					};
+
+					// Add 4th step
+					var fourthStep = new Wizard.WizardStep ("Step Four");
+					wizard.AddStep (fourthStep);
+					var someText = new TextView () {
+						Text = "This step (Step Four) shows how to show/hide the Help pane. The step contains this TextView (but it's hard to tell it's a TextView because of Issue #1800).",
+						X = 0,
+						Y = 0,
+						Width = Dim.Fill (),
+						Height = Dim.Fill (1),
+						WordWrap = true,
+						AllowsTab = false,
+					};
+					var help = "This is helpful.";
+					fourthStep.Add (someText);
+					var hideHelpBtn = new Button () {
+						Text = "Press me to show/hide help",
+						X = Pos.Center (),
+						Y = Pos.AnchorEnd (1)
+					};
+					hideHelpBtn.Clicked += () => {
+						if (fourthStep.HelpText.Length > 0) {
+							fourthStep.HelpText = ustring.Empty;
+						} else {
+							fourthStep.HelpText = help;
+						}
+					};
+					fourthStep.Add (hideHelpBtn);
+					fourthStep.NextButtonText = "Go To Last Step";
+					var scrollBar = new ScrollBarView (someText, true);
+
+					scrollBar.ChangedPosition += () => {
+						someText.TopRow = scrollBar.Position;
+						if (someText.TopRow != scrollBar.Position) {
+							scrollBar.Position = someText.TopRow;
+						}
+						someText.SetNeedsDisplay ();
+					};
+
+					scrollBar.VisibleChanged += () => {
+						if (scrollBar.Visible && someText.RightOffset == 0) {
+							someText.RightOffset = 1;
+						} else if (!scrollBar.Visible && someText.RightOffset == 1) {
+							someText.RightOffset = 0;
+						}
+					};
+
+					someText.DrawContent += (e) => {
+						scrollBar.Size = someText.Lines;
+						scrollBar.Position = someText.TopRow;
+						if (scrollBar.OtherScrollBarView != null) {
+							scrollBar.OtherScrollBarView.Size = someText.Maxlength;
+							scrollBar.OtherScrollBarView.Position = someText.LeftColumn;
+						}
+						scrollBar.LayoutSubviews ();
+						scrollBar.Refresh ();
+					};
+					fourthStep.Add (scrollBar);
+
+					// Add last step
+					var lastStep = new Wizard.WizardStep ("The last step");
+					wizard.AddStep (lastStep);
+					lastStep.HelpText = "The wizard is complete!\n\nPress the Finish button to continue.\n\nPressing ESC will cancel the wizard.";
+					var finalFinalStepEnabledCeckBox = new CheckBox () { Text = "Enable _Final Final Step", Checked = false, X = 0, Y = 1 };
+					lastStep.Add (finalFinalStepEnabledCeckBox);
+
+					// Add an optional FINAL last step
+					var finalFinalStep = new Wizard.WizardStep ("The VERY last step");
+					wizard.AddStep (finalFinalStep);
+					finalFinalStep.HelpText = "This step only shows if it was enabled on the other last step.";
+					finalFinalStep.Enabled = thirdStepEnabledCeckBox.Checked;
+					finalFinalStepEnabledCeckBox.Toggled += (args) => {
+						finalFinalStep.Enabled = finalFinalStepEnabledCeckBox.Checked;
+					};
+
+					Application.Run (wizard);
+
+				} catch (FormatException) {
+					actionLabel.Text = "Invalid Options";
+				}
+			};
+			Win.Add (showWizardButton);
+		}
+	}
+}

+ 34 - 32
UnitTests/ApplicationTests.cs

@@ -1144,7 +1144,7 @@ namespace Terminal.Gui.Core {
 			var rs = Application.Begin (Application.Top);
 			Assert.Equal (Application.Top, rs.Toplevel);
 			Assert.Null (Application.mouseGrabView);
-			Assert.Null (Application.wantContinuousButtonPressedView);
+			Assert.Null (Application.WantContinuousButtonPressedView);
 			Assert.False (Application.DebugDrawBounds);
 			Assert.False (Application.ShowChild (Application.Top));
 			Application.End (Application.Top);
@@ -1293,45 +1293,47 @@ namespace Terminal.Gui.Core {
 			int numberOfTimeoutsPerThread = 100;
 
 
-			// start lots of threads
-			for (int i = 0; i < numberOfThreads; i++) {
-				
-				var myi = i;
+			lock (Application.Top) {
+				// start lots of threads
+				for (int i = 0; i < numberOfThreads; i++) {
 
-				Task.Run (() => {
-					Task.Delay (100).Wait ();
+					var myi = i;
 
-					// each thread registers lots of 1s timeouts
-					for(int j=0;j< numberOfTimeoutsPerThread; j++) {
+					Task.Run (() => {
+						Task.Delay (100).Wait ();
 
-						Application.MainLoop.AddTimeout (TimeSpan.FromSeconds(1), (s) => {
+						// each thread registers lots of 1s timeouts
+						for (int j = 0; j < numberOfTimeoutsPerThread; j++) {
 
-							// each timeout delegate increments delegatesRun count by 1 every second
-							Interlocked.Increment (ref delegatesRun);
-							return true; 
-						});
-					}
-					 
-					// if this is the first Thread created
-					if (myi == 0) {
+							Application.MainLoop.AddTimeout (TimeSpan.FromSeconds (1), (s) => {
 
-						// let the timeouts run for a bit
-						Task.Delay (5000).Wait ();
+								// each timeout delegate increments delegatesRun count by 1 every second
+								Interlocked.Increment (ref delegatesRun);
+								return true;
+							});
+						}
 
-						// then tell the application to quuit
-						Application.MainLoop.Invoke (() => Application.RequestStop ());
-					}
-				});
-			}
+						// if this is the first Thread created
+						if (myi == 0) {
 
-			// blocks here until the RequestStop is processed at the end of the test
-			Application.Run ();
+							// let the timeouts run for a bit
+							Task.Delay (5000).Wait ();
 
-			// undershoot a bit to be on the safe side.  The 5000 ms wait allows the timeouts to run
-			// a lot but all those timeout delegates could end up going slowly on a slow machine perhaps
-			// so the final number of delegatesRun might vary by computer.  So for this assert we say
-			// that it should have run at least 2 seconds worth of delegates
-			Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
+							// then tell the application to quit
+							Application.MainLoop.Invoke (() => Application.RequestStop ());
+						}
+					});
+				}
+
+				// blocks here until the RequestStop is processed at the end of the test
+				Application.Run ();
+
+				// undershoot a bit to be on the safe side.  The 5000 ms wait allows the timeouts to run
+				// a lot but all those timeout delegates could end up going slowly on a slow machine perhaps
+				// so the final number of delegatesRun might vary by computer.  So for this assert we say
+				// that it should have run at least 2 seconds worth of delegates
+				Assert.True (delegatesRun >= numberOfThreads * numberOfTimeoutsPerThread * 2);
+			}
 		}
 	}
 }

+ 1 - 1
UnitTests/AssemblyInfo.cs

@@ -13,7 +13,7 @@ using Xunit;
 // This is necessary because a) Application is a singleton and Init/Shutdown must be called
 // as a pair, and b) all unit test functions should be atomic.
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-public class AutoInitShutdown : Xunit.Sdk.BeforeAfterTestAttribute {
+public class AutoInitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 
 	static bool _init = false;
 	public override void Before (MethodInfo methodUnderTest)

+ 335 - 17
UnitTests/ButtonTests.cs

@@ -17,47 +17,57 @@ namespace Terminal.Gui.Views {
 			var btn = new Button ();
 			Assert.Equal (string.Empty, btn.Text);
 			Application.Top.Add (btn);
-			btn.Redraw (btn.Bounds);
-			Assert.Equal ("[  ]", GetContents (btn.Bounds.Width));
+			var rs = Application.Begin (Application.Top);
+
+			Assert.Equal ("[  ]", btn.TextFormatter.Text);
 			Assert.False (btn.IsDefault);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.True (btn.CanFocus);
 			Assert.Equal (new Rect (0, 0, 4, 1), btn.Frame);
 			Assert.Equal (Key.Null, btn.HotKey);
+			var expected = @"
+[  ]
+";
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 
-			btn = new Button ("ARGS", true) {Text="Test"};
+			Application.End (rs);
+			btn = new Button ("ARGS", true) { Text = "Test" };
 			Assert.Equal ("Test", btn.Text);
 			Application.Top.Add (btn);
-			btn.Redraw (btn.Bounds);
-			Assert.Equal ("[◦ Test ◦]", GetContents (btn.Bounds.Width));
+			rs = Application.Begin (Application.Top);
+
+			Assert.Equal ("[◦ Test ◦]", btn.TextFormatter.Text);
 			Assert.True (btn.IsDefault);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.True (btn.CanFocus);
 			Assert.Equal (new Rect (0, 0, 10, 1), btn.Frame);
 			Assert.Equal (Key.T, btn.HotKey);
+			expected = @"
+[◦ Test ◦]
+";
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 
+			Application.End (rs);
 			btn = new Button (3, 4, "Test", true);
 			Assert.Equal ("Test", btn.Text);
 			Application.Top.Add (btn);
-			btn.Redraw (btn.Bounds);
-			Assert.Equal ("[◦ Test ◦]", GetContents (btn.Bounds.Width));
+			rs = Application.Begin (Application.Top);
+
+			Assert.Equal ("[◦ Test ◦]", btn.TextFormatter.Text);
 			Assert.True (btn.IsDefault);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.True (btn.CanFocus);
 			Assert.Equal (new Rect (3, 4, 10, 1), btn.Frame);
 			Assert.Equal (Key.T, btn.HotKey);
-		}
+			expected = @"
+   [◦ Test ◦]
+";
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 
-		private string GetContents (int width)
-		{
-			string output = "";
-			for (int i = 0; i < width; i++) {
-				output += (char)Application.Driver.Contents [0, i, 0];
-			}
-			return output;
+			Application.End (rs);
 		}
 
 		[Fact]
@@ -166,7 +176,7 @@ namespace Terminal.Gui.Views {
 		[Fact]
 		public void TestAssignTextToButton ()
 		{
-			View b = new Button () {Text="heya"};
+			View b = new Button () { Text = "heya" };
 			Assert.Equal ("heya", b.Text);
 			Assert.True (b.TextFormatter.Text.Contains ("heya"));
 			b.Text = "heyb";
@@ -228,7 +238,7 @@ namespace Terminal.Gui.Views {
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
-		
+
 		[Fact, AutoInitShutdown]
 		public void Update_Parameterless_Only_On_Or_After_Initialize ()
 		{
@@ -266,5 +276,313 @@ namespace Terminal.Gui.Views {
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Stays_True_With_EmptyText ()
+		{
+			var btn = new Button () {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				AutoSize = true
+			};
+
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (btn);
+			Application.Top.Add (win);
+
+			Assert.True (btn.AutoSize);
+
+			btn.Text = "Say Hello 你";
+
+			Assert.True (btn.AutoSize);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│      [ Say Hello 你 ]      │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Stays_True_Center ()
+		{
+			var btn = new Button () {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				Text = "Say Hello 你"
+			};
+
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (btn);
+			Application.Top.Add (win);
+
+			Assert.True (btn.AutoSize);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│      [ Say Hello 你 ]      │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+
+			Assert.True (btn.AutoSize);
+			btn.Text = "Say Hello 你 changed";
+			Assert.True (btn.AutoSize);
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│  [ Say Hello 你 changed ]  │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Stays_True_AnchorEnd ()
+		{
+			var btn = new Button () {
+				Y = Pos.Center (),
+				Text = "Say Hello 你",
+				AutoSize = true
+			};
+			btn.X = Pos.AnchorEnd () - Pos.Function (() => TextFormatter.GetTextWidth (btn.TextFormatter.Text));
+
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (btn);
+			Application.Top.Add (win);
+
+			Assert.True (btn.AutoSize);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│            [ Say Hello 你 ]│
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+
+			Assert.True (btn.AutoSize);
+			btn.Text = "Say Hello 你 changed";
+			Assert.True (btn.AutoSize);
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│    [ Say Hello 你 changed ]│
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_False_With_Fixed_Width ()
+		{
+			var tab = new View ();
+
+			var lblWidth = 8;
+
+			var label = new Label ("Find:") {
+				Y = 1,
+				Width = lblWidth,
+				TextAlignment = TextAlignment.Right,
+				AutoSize = false
+			};
+			tab.Add (label);
+
+			var txtToFind = new TextField ("Testing buttons.") {
+				X = Pos.Right (label) + 1,
+				Y = Pos.Top (label),
+				Width = 20
+			};
+			tab.Add (txtToFind);
+
+			var btnFindNext = new Button ("Find _Next") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (label),
+				Width = 20,
+				Enabled = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered,
+				IsDefault = true,
+				AutoSize = false
+			};
+			tab.Add (btnFindNext);
+
+			var btnFindPrevious = new Button ("Find _Previous") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnFindNext) + 1,
+				Width = 20,
+				Enabled = !txtToFind.Text.IsEmpty,
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
+			};
+			tab.Add (btnFindPrevious);
+
+			var btnCancel = new Button ("Cancel") {
+				X = Pos.Right (txtToFind) + 1,
+				Y = Pos.Top (btnFindPrevious) + 2,
+				Width = 20,
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false
+			};
+			tab.Add (btnCancel);
+
+			var ckbMatchCase = new CheckBox ("Match c_ase") {
+				X = 0,
+				Y = Pos.Top (txtToFind) + 2,
+				Checked = true
+			};
+			tab.Add (ckbMatchCase);
+
+			var ckbMatchWholeWord = new CheckBox ("Match _whole word") {
+				X = 0,
+				Y = Pos.Top (ckbMatchCase) + 1,
+				Checked = false
+			};
+			tab.Add (ckbMatchWholeWord);
+
+			var tabView = new TabView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			tabView.AddTab (new TabView.Tab ("Find", tab), true);
+
+			var win = new Window ("Find") {
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+
+			tab.Width = label.Width + txtToFind.Width + btnFindNext.Width + 2;
+			tab.Height = btnFindNext.Height + btnFindPrevious.Height + btnCancel.Height + 4;
+
+			win.Add (tabView);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (54, 11);
+
+			Assert.Equal (new Rect (0, 0, 54, 11), win.Frame);
+			Assert.Equal (new Rect (0, 0, 52, 9), tabView.Frame);
+			Assert.Equal (new Rect (0, 0, 50, 7), tab.Frame);
+			Assert.Equal (new Rect (0, 1, 8, 1), label.Frame);
+			Assert.Equal (new Rect (9, 1, 20, 1), txtToFind.Frame);
+
+			Assert.Equal (0, txtToFind.ScrollOffset);
+			Assert.Equal (16, txtToFind.CursorPosition);
+
+			Assert.Equal (new Rect (30, 1, 20, 1), btnFindNext.Frame);
+			Assert.Equal (new Rect (30, 2, 20, 1), btnFindPrevious.Frame);
+			Assert.Equal (new Rect (30, 4, 20, 1), btnCancel.Frame);
+			Assert.Equal (new Rect (0, 3, 12, 1), ckbMatchCase.Frame);
+			Assert.Equal (new Rect (0, 4, 18, 1), ckbMatchWholeWord.Frame);
+			var expected = @"
+┌ Find ──────────────────────────────────────────────┐
+│┌────┐                                              │
+││Find│                                              │
+││    └─────────────────────────────────────────────┐│
+││                                                  ││
+││   Find: Testing buttons.       [◦ Find Next ◦]   ││
+││                               [ Find Previous ]  ││
+││√ Match case                                      ││
+││╴ Match whole word                 [ Cancel ]     ││
+│└──────────────────────────────────────────────────┘│
+└────────────────────────────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Pos_Center_Layout_AutoSize_True ()
+		{
+			var button = new Button ("Process keys") {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				IsDefault = true
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			win.Add (button);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			Assert.True (button.AutoSize);
+			Assert.Equal (new Rect (5, 1, 18, 1), button.Frame);
+			var expected = @"
+┌────────────────────────────┐
+│                            │
+│     [◦ Process keys ◦]     │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Pos_Center_Layout_AutoSize_False ()
+		{
+			var button = new Button ("Process keys") {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				Width = 20,
+				IsDefault = true,
+				AutoSize = false
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			win.Add (button);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			Assert.False (button.AutoSize);
+			Assert.Equal (new Rect (4, 1, 20, 1), button.Frame);
+			var expected = @"
+┌────────────────────────────┐
+│                            │
+│     [◦ Process keys ◦]     │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
 	}
 }

+ 461 - 8
UnitTests/CheckboxTests.cs

@@ -4,35 +4,51 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class CheckboxTests {
+		readonly ITestOutputHelper output;
+
+		public CheckboxTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Constructors_Defaults ()
 		{
 			var ckb = new CheckBox ();
+			Assert.True (ckb.AutoSize);
 			Assert.False (ckb.Checked);
 			Assert.Equal (string.Empty, ckb.Text);
+			Assert.Equal ("╴ ", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
-			Assert.Equal (new Rect (0, 0, 4, 1), ckb.Frame);
+			Assert.Equal (new Rect (0, 0, 2, 1), ckb.Frame);
 
 			ckb = new CheckBox ("Test", true);
+			Assert.True (ckb.AutoSize);
 			Assert.True (ckb.Checked);
 			Assert.Equal ("Test", ckb.Text);
+			Assert.Equal ("√ Test", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
-			Assert.Equal (new Rect (0, 0, 8, 1), ckb.Frame);
+			Assert.Equal (new Rect (0, 0, 6, 1), ckb.Frame);
 
 			ckb = new CheckBox (1, 2, "Test");
+			Assert.True (ckb.AutoSize);
 			Assert.False (ckb.Checked);
 			Assert.Equal ("Test", ckb.Text);
+			Assert.Equal ("╴ Test", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
-			Assert.Equal (new Rect (1, 2, 8, 1), ckb.Frame);
+			Assert.Equal (new Rect (1, 2, 6, 1), ckb.Frame);
 
 			ckb = new CheckBox (3, 4, "Test", true);
+			Assert.True (ckb.AutoSize);
 			Assert.True (ckb.Checked);
 			Assert.Equal ("Test", ckb.Text);
+			Assert.Equal ("√ Test", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
-			Assert.Equal (new Rect (3, 4, 8, 1), ckb.Frame);
+			Assert.Equal (new Rect (3, 4, 6, 1), ckb.Frame);
 		}
 
 		[Fact]
@@ -40,17 +56,19 @@ namespace Terminal.Gui.Views {
 		public void KeyBindings_Command ()
 		{
 			var isChecked = false;
-			CheckBox ckb = new CheckBox ("Test");
+			CheckBox ckb = new CheckBox ();
 			ckb.Toggled += (e) => isChecked = true;
 			Application.Top.Add (ckb);
 			Application.Begin (Application.Top);
 
 			Assert.Equal (Key.Null, ckb.HotKey);
+			ckb.Text = "Test";
+			Assert.Equal (Key.T, ckb.HotKey);
 			Assert.False (ckb.ProcessHotKey (new KeyEvent (Key.T, new KeyModifiers ())));
 			Assert.False (isChecked);
-			ckb.Text = "_Test";
-			Assert.Equal (Key.T, ckb.HotKey);
-			Assert.True (ckb.ProcessHotKey (new KeyEvent (Key.T | Key.AltMask, new KeyModifiers () { Alt = true })));
+			ckb.Text = "T_est";
+			Assert.Equal (Key.E, ckb.HotKey);
+			Assert.True (ckb.ProcessHotKey (new KeyEvent (Key.E | Key.AltMask, new KeyModifiers () { Alt = true })));
 			Assert.True (isChecked);
 			isChecked = false;
 			Assert.True (ckb.ProcessKey (new KeyEvent ((Key)' ', new KeyModifiers ())));
@@ -58,6 +76,441 @@ namespace Terminal.Gui.Views {
 			isChecked = false;
 			Assert.True (ckb.ProcessKey (new KeyEvent (Key.Space, new KeyModifiers ())));
 			Assert.True (isChecked);
+			Assert.True (ckb.AutoSize);
+
+			Application.Refresh ();
+
+			var expected = @"
+√ Test
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 6, 1), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_StaysVisible ()
+		{
+			var checkBox = new CheckBox () {
+				X = 1,
+				Y = Pos.Center (),
+				Text = "Check this out 你"
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Assert.False (checkBox.IsInitialized);
+
+			var runstate = Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+
+			Assert.True (checkBox.IsInitialized);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			Assert.Equal ("Check this out 你", checkBox.Text);
+			Assert.Equal ("╴ Check this out 你", checkBox.TextFormatter.Text);
+			Assert.True (checkBox.AutoSize);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ ╴ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			// Positive test
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			// Also Positive test
+			checkBox.AutoSize = true;
+			bool first = false;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ ╴ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Checked = true;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ √ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.AutoSize = false;
+			// It isn't auto-size so the height is guaranteed by the SetMinWidthHeight
+			checkBox.Text = "Check this out 你 changed";
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ √ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Width = 19;
+			// It isn't auto-size so the height is guaranteed by the SetMinWidthHeight
+			checkBox.Text = "Check this out 你 changed";
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			Assert.False (checkBox.AutoSize);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ √ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.AutoSize = true;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame);
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ √ Check this out 你 changed│
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TextAlignment_Left ()
+		{
+			var checkBox = new CheckBox () {
+				X = 1,
+				Y = Pos.Center (),
+				Text = "Check this out 你",
+				AutoSize = false,
+				Width = 25
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+
+			Assert.Equal (TextAlignment.Left, checkBox.TextAlignment);
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
+			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
+			Assert.Equal ("Check this out 你", checkBox.Text);
+			Assert.Equal ("╴ Check this out 你", checkBox.TextFormatter.Text);
+			Assert.False (checkBox.AutoSize);
+
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ ╴ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Checked = true;
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ √ Check this out 你        │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TextAlignment_Centered ()
+		{
+			var checkBox = new CheckBox () {
+				X = 1,
+				Y = Pos.Center (),
+				Text = "Check this out 你",
+				TextAlignment = TextAlignment.Centered,
+				AutoSize = false,
+				Width = 25
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+
+			Assert.Equal (TextAlignment.Centered, checkBox.TextAlignment);
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
+			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
+			Assert.Equal ("Check this out 你", checkBox.Text);
+			Assert.Equal ("╴ Check this out 你", checkBox.TextFormatter.Text);
+			Assert.False (checkBox.AutoSize);
+
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│    ╴ Check this out 你     │
+│                            │
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Checked = true;
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│    √ Check this out 你     │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TextAlignment_Justified ()
+		{
+			var checkBox = new CheckBox () {
+				X = 1,
+				Y = Pos.Center (),
+				Text = "Check this out 你",
+				TextAlignment = TextAlignment.Justified,
+				AutoSize = false,
+				Width = 25
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+
+			Assert.Equal (TextAlignment.Justified, checkBox.TextAlignment);
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
+			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
+			Assert.Equal ("Check this out 你", checkBox.Text);
+			Assert.Equal ("╴ Check this out 你", checkBox.TextFormatter.Text);
+			Assert.False (checkBox.AutoSize);
+
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ ╴  Check  this  out  你    │
+│                            │
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Checked = true;
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
+			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ √  Check  this  out  你    │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TextAlignment_Right ()
+		{
+			var checkBox = new CheckBox () {
+				X = 1,
+				Y = Pos.Center (),
+				Text = "Check this out 你",
+				TextAlignment = TextAlignment.Right,
+				AutoSize = false,
+				Width = 25
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+
+			Assert.Equal (TextAlignment.Right, checkBox.TextAlignment);
+			Assert.Equal (new Rect (1, 1, 25, 1), checkBox.Frame);
+			Assert.Equal (new Size (25, 1), checkBox.TextFormatter.Size);
+			Assert.Equal ("Check this out 你", checkBox.Text);
+			Assert.Equal ("Check this out 你 ╴", checkBox.TextFormatter.Text);
+			Assert.False (checkBox.AutoSize);
+
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│       Check this out 你 ╴  │
+│                            │
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.Checked = true;
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│       Check this out 你 √  │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Stays_True_AnchorEnd_Without_HotKeySpecifier ()
+		{
+			var checkBox = new CheckBox () {
+				Y = Pos.Center (),
+				Text = "Check this out 你"
+			};
+			checkBox.X = Pos.AnchorEnd () - Pos.Function (() => checkBox.GetTextFormatterBoundsSize ().Width);
+
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Assert.True (checkBox.AutoSize);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│         ╴ Check this out 你│
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+
+			Assert.True (checkBox.AutoSize);
+			checkBox.Text = "Check this out 你 changed";
+			Assert.True (checkBox.AutoSize);
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ ╴ Check this out 你 changed│
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Stays_True_AnchorEnd_With_HotKeySpecifier ()
+		{
+			var checkBox = new CheckBox () {
+				Y = Pos.Center (),
+				Text = "C_heck this out 你"
+			};
+			checkBox.X = Pos.AnchorEnd () - Pos.Function (() => checkBox.GetTextFormatterBoundsSize ().Width);
+
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (checkBox);
+			Application.Top.Add (win);
+
+			Assert.True (checkBox.AutoSize);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│         ╴ Check this out 你│
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+
+			Assert.True (checkBox.AutoSize);
+			checkBox.Text = "Check this out 你 changed";
+			Assert.True (checkBox.AutoSize);
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│ ╴ Check this out 你 changed│
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 		}
 	}
 }

+ 45 - 7
UnitTests/ComboBoxTests.cs

@@ -1,25 +1,38 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using Terminal.Gui;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class ComboBoxTests {
+		ITestOutputHelper output;
+
+		public ComboBoxTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Constructors_Defaults ()
 		{
 			var cb = new ComboBox ();
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.Null (cb.Source);
+			Assert.False (cb.AutoSize);
+			Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame);
 
 			cb = new ComboBox ("Test");
 			Assert.Equal ("Test", cb.Text);
 			Assert.Null (cb.Source);
+			Assert.False (cb.AutoSize);
+			Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame);
 
 			cb = new ComboBox (new Rect (1, 2, 10, 20), new List<string> () { "One", "Two", "Three" });
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.NotNull (cb.Source);
+			Assert.False (cb.AutoSize);
+			Assert.Equal (new Rect (1, 2, 10, 20), cb.Frame);
 		}
 
 		[Fact]
@@ -44,12 +57,12 @@ namespace Terminal.Gui.Views {
 		public void KeyBindings_Command ()
 		{
 			List<string> source = new List<string> () { "One", "Two", "Three" };
-			ComboBox cb = new ComboBox ();
+			ComboBox cb = new ComboBox () { Width = 10 };
 			cb.SetSource (source);
 			Application.Top.Add (cb);
 			Application.Top.FocusFirst ();
 			Assert.Equal (-1, cb.SelectedItem);
-			Assert.Equal(string.Empty,cb.Text);
+			Assert.Equal (string.Empty, cb.Text);
 			var opened = false;
 			cb.OpenSelectedItem += (_) => opened = true;
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
@@ -63,7 +76,7 @@ namespace Terminal.Gui.Views {
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); // with no source also expand empty
 			Assert.True (cb.IsShow);
 			Assert.Equal (-1, cb.SelectedItem);
-			cb.SetSource(source);
+			cb.SetSource (source);
 			cb.Text = "";
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); // collapse
 			Assert.False (cb.IsShow);
@@ -106,8 +119,33 @@ namespace Terminal.Gui.Views {
 			Assert.True (cb.IsShow);
 			Assert.Equal (0, cb.SelectedItem);
 			Assert.Equal ("One", cb.Text);
+			Application.Begin (Application.Top);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+One      ▼
+One       
+", output);
+
+			Assert.True (cb.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ())));
+			Assert.True (cb.IsShow);
+			Assert.Equal (1, cb.SelectedItem);
+			Assert.Equal ("Two", cb.Text);
+			Application.Begin (Application.Top);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Two      ▼
+Two       
+", output);
+
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ())));
 			Assert.True (cb.IsShow);
+			Assert.Equal (2, cb.SelectedItem);
+			Assert.Equal ("Three", cb.Text);
+			Application.Begin (Application.Top);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Three    ▼
+Three     
+", output);
+			Assert.True (cb.ProcessKey (new KeyEvent (Key.PageUp, new KeyModifiers ())));
+			Assert.True (cb.IsShow);
 			Assert.Equal (1, cb.SelectedItem);
 			Assert.Equal ("Two", cb.Text);
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.PageUp, new KeyModifiers ())));
@@ -167,10 +205,10 @@ namespace Terminal.Gui.Views {
 			var cb = new ComboBox ();
 			Application.Top.Add (cb);
 			Application.Top.FocusFirst ();
-			Assert.Null(cb.Source);
+			Assert.Null (cb.Source);
 			Assert.Equal (-1, cb.SelectedItem);
 			var source = new List<string> ();
-			cb.SetSource(source);
+			cb.SetSource (source);
 			Assert.NotNull (cb.Source);
 			Assert.Equal (0, cb.Source.Count);
 			Assert.Equal (-1, cb.SelectedItem);
@@ -197,7 +235,7 @@ namespace Terminal.Gui.Views {
 			Assert.False (cb.IsShow);
 			Assert.Equal (1, cb.SelectedItem); // retains last accept selected item
 			Assert.Equal ("", cb.Text); // clear text
-			cb.SetSource(new List<string> ());
+			cb.SetSource (new List<string> ());
 			Assert.Equal (0, cb.Source.Count);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("", cb.Text);

+ 2 - 2
UnitTests/ConsoleDriverTests.cs

@@ -576,7 +576,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 ││  Hello World  │ │
 ││               │ │
 ││               │ │
-││     [ Ok ]    │ │
+││    [ Ok ]     │ │
 │└───────────────┘ │
 └──────────────────┘
 ";
@@ -593,7 +593,7 @@ namespace Terminal.Gui.ConsoleDrivers {
 ││  Hello World  │ │
 ││               │ │
 ││               │ │
-││     [ Ok ]    │ │
+││    [ Ok ]     │ │
 │└───────────────┘ │
 └──────────────────┘
 ";

+ 15 - 15
UnitTests/ContextMenuTests.cs

@@ -309,7 +309,7 @@ namespace Terminal.Gui.Core {
                                                                       │ One  │
                                                                       │ Two  │
                                                                       └──────┘
-                                                                      View
+                                                                      View    
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -340,7 +340,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (10, 5), cm.Position);
 
 			var expected = @"
-          View
+          View    
           ┌──────┐
           │ One  │
           │ Two  │
@@ -361,9 +361,9 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (5, 12), cm.Position);
 
 			expected = @"
-     View
-
-
+     View    
+             
+             
      ┌──────┐
      │ One  │
      │ Two  │
@@ -592,10 +592,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  File   Edit
-
-
-  Label: TextField
+  File   Edit                         
+                                      
+                                      
+  Label: TextField                    
          ┌───────────────────────────┐
          │ Select All         Ctrl+T │
          │ Delete All   Ctrl+Shift+D │
@@ -605,10 +605,10 @@ namespace Terminal.Gui.Core {
          │ Undo               Ctrl+Z │
          │ Redo               Ctrl+Y │
          └───────────────────────────┘
-
-
-
- F1 Help │ ^Q Quit
+                                      
+                                      
+                                      
+ F1 Help │ ^Q Quit                    
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -657,7 +657,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (10, 5), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
-  File   Edit
+  File   Edit                               
 ┌ Window ──────────────────────────────────┐
 │                                          │
 │                                          │
@@ -673,7 +673,7 @@ namespace Terminal.Gui.Core {
 │         │ Redo               Ctrl+Y │    │
 │         └───────────────────────────┘    │
 └──────────────────────────────────────────┘
- F1 Help │ ^Q Quit
+ F1 Help │ ^Q Quit                          
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);

+ 517 - 0
UnitTests/DialogTests.cs

@@ -0,0 +1,517 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Xunit;
+using System.Globalization;
+using Xunit.Abstractions;
+using NStack;
+
+namespace Terminal.Gui.Views {
+
+	public class DialogTests {
+		readonly ITestOutputHelper output;
+
+		public DialogTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		private (Application.RunState, Dialog) RunButtonTestDialog (string title, int width, Dialog.ButtonAlignments align, params Button [] btns)
+		{
+			var dlg = new Dialog (title, width, 3, btns) { ButtonAlignment = align };
+			return (Application.Begin (dlg), dlg);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_One ()
+		{
+			var d = ((FakeDriver)Application.Driver);
+			Application.RunState runstate = null;
+
+			var title = "1234";
+			// E.g "|[ ok ]|"
+			var btnText = "ok";
+			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+
+			d.SetBufferSize (width, 3);
+
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			buttonRow = $"{d.VLine}      {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btnText));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			buttonRow = $"{d.VLine}      {d.LeftBracket} {btnText} {d.RightBracket}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btnText));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			buttonRow = $"{d.VLine}{d.LeftBracket} {btnText} {d.RightBracket}      {d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btnText));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_Two ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			// E.g "|[ yes ][ no ]|"
+			var btn1Text = "yes";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "no";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+
+			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			buttonRow = $@"{d.VLine}{btn1}   {btn2}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			buttonRow = $@"{d.VLine}  {btn1} {btn2}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			buttonRow = $@"{d.VLine}{btn1} {btn2}  {d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_Two_Hidden ()
+		{
+			Application.RunState runstate = null;
+			bool firstIteration = false;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			// E.g "|[ yes ][ no ]|"
+			var btn1Text = "yes";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "no";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+
+			var buttonRow = $@"{d.VLine} {btn1} {btn2} {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			Dialog dlg = null;
+			Button button1, button2;
+
+			//// Default (Center)
+			//button1 = new Button (btn1Text);
+			//button2 = new Button (btn2Text);
+			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, button1, button2);
+			//button1.Visible = false;
+			//Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+			//buttonRow = $@"{d.VLine}         {btn2} {d.VLine}";
+			//GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			//Application.End (runstate);
+
+			// Justify
+			Assert.Equal (width, buttonRow.Length);
+			button1 = new Button (btn1Text);
+			button2 = new Button (btn2Text);
+			(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, button1, button2);
+			button1.Visible = false;
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+			buttonRow = $@"{d.VLine}          {btn2}{d.VLine}";
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			//// Right
+			//buttonRow = $@"{d.VLine}  {btn1} {btn2}{d.VLine}";
+			//Assert.Equal (width, buttonRow.Length);
+			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text));
+			//GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			//Application.End (runstate);
+
+			//// Left
+			//buttonRow = $@"{d.VLine}{btn1} {btn2}  {d.VLine}";
+			//Assert.Equal (width, buttonRow.Length);
+			//(runstate, dlg) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text));
+			//GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			//Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_Three ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			// E.g "|[ yes ][ no ][ maybe ]|"
+			var btn1Text = "yes";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "no";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+			var btn3Text = "maybe";
+			var btn3 = $"{d.LeftBracket} {btn3Text} {d.RightBracket}";
+
+			var buttonRow = $@"{d.VLine} {btn1} {btn2} {btn3} {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			buttonRow = $@"{d.VLine}{btn1}  {btn2}  {btn3}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			buttonRow = $@"{d.VLine}  {btn1} {btn2} {btn3}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			buttonRow = $@"{d.VLine}{btn1} {btn2} {btn3}  {d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_Four ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+
+			// E.g "|[ yes ][ no ][ maybe ]|"
+			var btn1Text = "yes";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "no";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+			var btn3Text = "maybe";
+			var btn3 = $"{d.LeftBracket} {btn3Text} {d.RightBracket}";
+			var btn4Text = "never";
+			var btn4 = $"{d.LeftBracket} {btn4Text} {d.RightBracket}";
+
+			var buttonRow = $"{d.VLine} {btn1} {btn2} {btn3} {btn4} {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			// Default - Center
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			buttonRow = $"{d.VLine}{btn1} {btn2}  {btn3}  {btn4}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			buttonRow = $"{d.VLine}  {btn1} {btn2} {btn3} {btn4}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}  {d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_Four_Wider ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+
+			// E.g "|[ yes ][ no ][ maybe ]|"
+			var btn1Text = "yes";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "no";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+			var btn3Text = "你你你你你"; // This is a wide char
+			var btn3 = $"{d.LeftBracket} {btn3Text} {d.RightBracket}";
+			// Requires a Nerd Font
+			var btn4Text = "\uE36E\uE36F\uE370\uE371\uE372\uE373";
+			var btn4 = $"{d.LeftBracket} {btn4Text} {d.RightBracket}";
+
+			// Note extra spaces to make dialog even wider
+			//                         12345                           123456
+			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
+			var width = ustring.Make (buttonRow).ConsoleWidth;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			d.SetBufferSize (width, 3);
+
+			// Default - Center
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			buttonRow = $"{d.VLine}{btn1}    {btn2}     {btn3}     {btn4}{d.VLine}";
+			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			buttonRow = $"{d.VLine}           {btn1} {btn2} {btn3} {btn4}{d.VLine}";
+			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}           {d.VLine}";
+			Assert.Equal (width, ustring.Make (buttonRow).ConsoleWidth);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ButtonAlignment_Four_WideOdd ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+
+			// E.g "|[ yes ][ no ][ maybe ]|"
+			var btn1Text = "really long button 1";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "really long button 2";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+			var btn3Text = "really long button 3";
+			var btn3 = $"{d.LeftBracket} {btn3Text} {d.RightBracket}";
+			var btn4Text = "really long button 44"; // 44 is intentional to make length different than rest
+			var btn4 = $"{d.LeftBracket} {btn4Text} {d.RightBracket}";
+
+			// Note extra spaces to make dialog even wider
+			//                         12345                          123456
+			var buttonRow = $"{d.VLine}     {btn1} {btn2} {btn3} {btn4}      {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			// Default - Center
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			buttonRow = $"{d.VLine}{btn1}    {btn2}     {btn3}     {btn4}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Justify, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			buttonRow = $"{d.VLine}           {btn1} {btn2} {btn3} {btn4}{d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Right, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			buttonRow = $"{d.VLine}{btn1} {btn2} {btn3} {btn4}           {d.VLine}";
+			Assert.Equal (width, buttonRow.Length);
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Left, new Button (btn1Text), new Button (btn2Text), new Button (btn3Text), new Button (btn4Text));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Zero_Buttons_Works ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+
+			var buttonRow = $"{d.VLine}        {d.VLine}";
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, null);
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void One_Button_Works ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			var btnText = "ok";
+			var buttonRow = $"{d.VLine}   {d.LeftBracket} {btnText} {d.RightBracket}   {d.VLine}";
+
+			var width = buttonRow.Length;
+			var topRow = $"┌ {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}┐";
+			var bottomRow = $"└{new String (d.HLine.ToString () [0], width - 2)}┘";
+			d.SetBufferSize (buttonRow.Length, 3);
+
+			(runstate, var _) = RunButtonTestDialog (title, width, Dialog.ButtonAlignments.Center, new Button (btnText));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void Add_Button_Works ()
+		{
+			Application.RunState runstate = null;
+
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			var btn1Text = "yes";
+			var btn1 = $"{d.LeftBracket} {btn1Text} {d.RightBracket}";
+			var btn2Text = "no";
+			var btn2 = $"{d.LeftBracket} {btn2Text} {d.RightBracket}";
+
+			// We test with one button first, but do this to get the width right for 2
+			var width = $@"{d.VLine} {btn1} {btn2} {d.VLine}".Length;
+			d.SetBufferSize (width, 3);
+
+			var topRow = $"{d.ULCorner} {title} {new String (d.HLine.ToString () [0], width - title.Length - 4)}{d.URCorner}";
+			var bottomRow = $"{d.LLCorner}{new String (d.HLine.ToString () [0], width - 2)}{d.LRCorner}";
+
+			// Default (center)
+			var dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Center };
+			runstate = Application.Begin (dlg);
+			var buttonRow = $"{d.VLine}    {btn1}     {d.VLine}";
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+
+			// Now add a second button
+			buttonRow = $"{d.VLine} {btn1} {btn2} {d.VLine}";
+			dlg.AddButton (new Button (btn2Text));
+			bool first = false;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Justify
+			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Justify };
+			runstate = Application.Begin (dlg);
+			buttonRow = $"{d.VLine}         {btn1}{d.VLine}";
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+
+			// Now add a second button
+			buttonRow = $"{d.VLine}{btn1}   {btn2}{d.VLine}";
+			dlg.AddButton (new Button (btn2Text));
+			first = false;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Right
+			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Right };
+			runstate = Application.Begin (dlg);
+			buttonRow = $"{d.VLine}{new String (' ', width - btn1.Length - 2)}{btn1}{d.VLine}";
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+
+			// Now add a second button
+			buttonRow = $"{d.VLine}  {btn1} {btn2}{d.VLine}";
+			dlg.AddButton (new Button (btn2Text));
+			first = false;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+
+			// Left
+			dlg = new Dialog (title, width, 3, new Button (btn1Text)) { ButtonAlignment = Dialog.ButtonAlignments.Left };
+			runstate = Application.Begin (dlg);
+			buttonRow = $"{d.VLine}{btn1}{new String (' ', width - btn1.Length - 2)}{d.VLine}";
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+
+			// Now add a second button
+			buttonRow = $"{d.VLine}{btn1} {btn2}  {d.VLine}";
+			dlg.AddButton (new Button (btn2Text));
+			first = false;
+			Application.RunMainLoopIteration (ref runstate, true, ref first);
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+	}
+}

+ 545 - 23
UnitTests/DimTests.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
@@ -6,15 +6,20 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
+using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.Core {
 	public class DimTests {
-		public DimTests ()
+		readonly ITestOutputHelper output;
+
+		public DimTests (ITestOutputHelper output)
 		{
+			this.output = output;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			// Change current culture
 			CultureInfo culture = CultureInfo.CreateSpecificCulture ("en-US");
@@ -245,7 +250,7 @@ namespace Terminal.Gui.Core {
 		}
 
 		[Fact]
-		public void Dim_Validation_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type ()
+		public void ForceValidatePosDim_True_Dim_Validation_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type ()
 		{
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
@@ -257,7 +262,8 @@ namespace Terminal.Gui.Core {
 			};
 			var v = new View ("v") {
 				Width = Dim.Width (w) - 2,
-				Height = Dim.Percent (10)
+				Height = Dim.Percent (10),
+				ForceValidatePosDim = true
 			};
 
 			w.Add (v);
@@ -268,6 +274,13 @@ namespace Terminal.Gui.Core {
 				Assert.Equal (2, w.Height = 2);
 				Assert.Throws<ArgumentException> (() => v.Width = 2);
 				Assert.Throws<ArgumentException> (() => v.Height = 2);
+				v.ForceValidatePosDim = false;
+				var exception = Record.Exception (() => v.Width = 2);
+				Assert.Null (exception);
+				Assert.Equal (2, v.Width);
+				exception = Record.Exception (() => v.Height = 2);
+				Assert.Null (exception);
+				Assert.Equal (2, v.Height);
 			};
 
 			Application.Iteration += () => Application.RequestStop ();
@@ -357,39 +370,51 @@ namespace Terminal.Gui.Core {
 			};
 
 			var v1 = new Button ("v1") {
+				AutoSize = false,
 				X = Pos.X (f1) + 2,
 				Y = Pos.Bottom (f1) + 2,
 				Width = Dim.Width (f1) - 2,
-				Height = Dim.Fill () - 2
+				Height = Dim.Fill () - 2,
+				ForceValidatePosDim = true
 			};
 
 			var v2 = new Button ("v2") {
+				AutoSize = false,
 				X = Pos.X (f2) + 2,
 				Y = Pos.Bottom (f2) + 2,
 				Width = Dim.Width (f2) - 2,
-				Height = Dim.Fill () - 2
+				Height = Dim.Fill () - 2,
+				ForceValidatePosDim = true
 			};
 
 			var v3 = new Button ("v3") {
+				AutoSize = false,
 				Width = Dim.Percent (10),
-				Height = Dim.Percent (10)
+				Height = Dim.Percent (10),
+				ForceValidatePosDim = true
 			};
 
 			var v4 = new Button ("v4") {
+				AutoSize = false,
 				Width = Dim.Sized (50),
-				Height = Dim.Sized (50)
+				Height = Dim.Sized (50),
+				ForceValidatePosDim = true
 			};
 
 			var v5 = new Button ("v5") {
+				AutoSize = false,
 				Width = Dim.Width (v1) - Dim.Width (v3),
-				Height = Dim.Height (v1) - Dim.Height (v3)
+				Height = Dim.Height (v1) - Dim.Height (v3),
+				ForceValidatePosDim = true
 			};
 
 			var v6 = new Button ("v6") {
+				AutoSize = false,
 				X = Pos.X (f2),
 				Y = Pos.Bottom (f2) + 2,
 				Width = Dim.Percent (20, true),
-				Height = Dim.Percent (20, true)
+				Height = Dim.Percent (20, true),
+				ForceValidatePosDim = true
 			};
 
 			w.Add (f1, f2, v1, v2, v3, v4, v5, v6);
@@ -416,7 +441,6 @@ namespace Terminal.Gui.Core {
 				Assert.Equal (47, v1.Frame.Width); // 49-2=47
 				Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89
 
-
 				Assert.Equal ("Dim.Combine(DimView(side=Width, target=FrameView()({X=49,Y=0,Width=49,Height=5}))-Dim.Absolute(2))", v2.Width.ToString ());
 				Assert.Equal ("Dim.Combine(Dim.Fill(margin=0)-Dim.Absolute(2))", v2.Height.ToString ());
 				Assert.Equal (47, v2.Frame.Width); // 49-2=47
@@ -466,26 +490,28 @@ namespace Terminal.Gui.Core {
 
 				v1.Text = "Button1";
 				Assert.Equal ("Dim.Combine(DimView(side=Width, target=FrameView()({X=0,Y=0,Width=99,Height=5}))-Dim.Absolute(2))", v1.Width.ToString ());
-				Assert.Equal ("Dim.Absolute(1)", v1.Height.ToString ());
+				Assert.Equal ("Dim.Combine(Dim.Fill(margin=0)-Dim.Absolute(2))", v1.Height.ToString ());
 				Assert.Equal (97, v1.Frame.Width); // 99-2=97
-				Assert.Equal (1, v1.Frame.Height); // 1 because is Dim.DimAbsolute
+				Assert.Equal (189, v1.Frame.Height); // 198-2-7=189
 
 				v2.Text = "Button2";
 				Assert.Equal ("Dim.Combine(DimView(side=Width, target=FrameView()({X=99,Y=0,Width=99,Height=5}))-Dim.Absolute(2))", v2.Width.ToString ());
-				Assert.Equal ("Dim.Absolute(1)", v2.Height.ToString ());
+				Assert.Equal ("Dim.Combine(Dim.Fill(margin=0)-Dim.Absolute(2))", v2.Height.ToString ());
 				Assert.Equal (97, v2.Frame.Width); // 99-2=97
-				Assert.Equal (1, v2.Frame.Height); // 1 because is Dim.DimAbsolute
+				Assert.Equal (189, v2.Frame.Height); // 198-2-7=189
 
 				v3.Text = "Button3";
 				Assert.Equal ("Dim.Factor(factor=0.1, remaining=False)", v3.Width.ToString ());
-				Assert.Equal ("Dim.Absolute(1)", v3.Height.ToString ());
+				Assert.Equal ("Dim.Factor(factor=0.1, remaining=False)", v3.Height.ToString ());
 				Assert.Equal (19, v3.Frame.Width); // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width
-				Assert.Equal (1, v3.Frame.Height); // 1 because is Dim.DimAbsolute
+				Assert.Equal (19, v3.Frame.Height); // 199*10%=19
 
 				v4.Text = "Button4";
 				v4.AutoSize = false;
 				Assert.Equal ("Dim.Absolute(50)", v4.Width.ToString ());
-				Assert.Equal ("Dim.Absolute(1)", v4.Height.ToString ());
+				Assert.Equal ("Dim.Absolute(50)", v4.Height.ToString ());
+				Assert.Equal (50, v4.Frame.Width);
+				Assert.Equal (50, v4.Frame.Height);
 				v4.AutoSize = true;
 				Assert.Equal ("Dim.Absolute(11)", v4.Width.ToString ());
 				Assert.Equal ("Dim.Absolute(1)", v4.Height.ToString ());
@@ -493,16 +519,16 @@ namespace Terminal.Gui.Core {
 				Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute
 
 				v5.Text = "Button5";
-				Assert.Equal ("Dim.Combine(DimView(side=Width, target=Button()({X=2,Y=7,Width=97,Height=1}))-DimView(side=Width, target=Button()({X=0,Y=0,Width=19,Height=1})))", v5.Width.ToString ());
-				Assert.Equal ("Dim.Absolute(1)", v5.Height.ToString ());
+				Assert.Equal ("Dim.Combine(DimView(side=Width, target=Button()({X=2,Y=7,Width=97,Height=189}))-DimView(side=Width, target=Button()({X=0,Y=0,Width=19,Height=19})))", v5.Width.ToString ());
+				Assert.Equal ("Dim.Combine(DimView(side=Height, target=Button()({X=2,Y=7,Width=97,Height=189}))-DimView(side=Height, target=Button()({X=0,Y=0,Width=19,Height=19})))", v5.Height.ToString ());
 				Assert.Equal (78, v5.Frame.Width); // 97-19=78
-				Assert.Equal (1, v5.Frame.Height); // 1 because is Dim.DimAbsolute
+				Assert.Equal (170, v5.Frame.Height); // 189-19=170
 
 				v6.Text = "Button6";
 				Assert.Equal ("Dim.Factor(factor=0.2, remaining=True)", v6.Width.ToString ());
-				Assert.Equal ("Dim.Absolute(1)", v6.Height.ToString ());
+				Assert.Equal ("Dim.Factor(factor=0.2, remaining=True)", v6.Height.ToString ());
 				Assert.Equal (19, v6.Frame.Width); // 99*20%=19
-				Assert.Equal (1, v6.Frame.Height); // 1 because is Dim.DimAbsolute
+				Assert.Equal (38, v6.Frame.Height); // 198-7*20=38
 			};
 
 			Application.Iteration += () => Application.RequestStop ();
@@ -641,6 +667,391 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 		}
 
+		private string [] expecteds = new string [21] {
+@"
+┌────────────────────┐
+│View with long text │
+│                    │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 0             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 1             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 2             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 3             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 4             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 5             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 6             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 7             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 8             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 9             │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 10            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 11            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 12            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 13            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 14            │
+│Label 14            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 14            │
+│Label 15            │
+│Label 15            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 14            │
+│Label 15            │
+│Label 16            │
+│Label 16            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 14            │
+│Label 15            │
+│Label 16            │
+│Label 17            │
+│Label 17            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 14            │
+│Label 15            │
+│Label 16            │
+│Label 17            │
+│Label 18            │
+│Label 18            │
+└────────────────────┘",
+@"
+┌────────────────────┐
+│View with long text │
+│Label 0             │
+│Label 1             │
+│Label 2             │
+│Label 3             │
+│Label 4             │
+│Label 5             │
+│Label 6             │
+│Label 7             │
+│Label 8             │
+│Label 9             │
+│Label 10            │
+│Label 11            │
+│Label 12            │
+│Label 13            │
+│Label 14            │
+│Label 15            │
+│Label 16            │
+│Label 17            │
+│Label 18            │
+│Label 19            │
+│Label 19            │
+└────────────────────┘",
+};
+
+		[Fact]
+		public void Dim_Add_Operator_With_Text ()
+		{
+
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = Application.Top;
+
+			// Although view height is zero the text it's draw due the SetMinWidthHeight method
+			var view = new View ("View with long text") { X = 0, Y = 0, Width = 20, Height = 0 };
+			var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 };
+			var count = 0;
+			var listLabels = new List<Label> ();
+
+			field.KeyDown += (k) => {
+				if (k.KeyEvent.Key == Key.Enter) {
+					((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
+					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expecteds [count], output);
+					Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
+
+					if (count < 20) {
+						field.Text = $"Label {count}";
+						var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 10 };
+						view.Add (label);
+						Assert.Equal ($"Label {count}", label.Text);
+						Assert.Equal ($"Pos.Absolute({count + 1})", label.Y.ToString ());
+						listLabels.Add (label);
+						if (count == 0) {
+							Assert.Equal ($"Dim.Absolute({count})", view.Height.ToString ());
+							view.Height += 2;
+						} else {
+							Assert.Equal ($"Dim.Absolute({count + 1})", view.Height.ToString ());
+							view.Height += 1;
+						}
+						count++;
+					}
+					Assert.Equal ($"Dim.Absolute({count + 1})", view.Height.ToString ());
+				}
+			};
+
+			Application.Iteration += () => {
+				while (count < 21) {
+					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
+					if (count == 20) {
+						field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
+						break;
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			var win = new Window ();
+			win.Add (view);
+			win.Add (field);
+
+			top.Add (win);
+
+			Application.Run (top);
+
+			Assert.Equal (20, count);
+			Assert.Equal (count, listLabels.Count);
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
 		[Fact]
 		public void Dim_Subtract_Operator ()
 		{
@@ -701,6 +1112,88 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 		}
 
+		[Fact]
+		public void Dim_Subtract_Operator_With_Text ()
+		{
+
+			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+			var top = Application.Top;
+
+			// Although view height is zero the text it's draw due the SetMinWidthHeight method
+			var view = new View ("View with long text") { X = 0, Y = 0, Width = 20, Height = 0 };
+			var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 };
+			var count = 20;
+			var listLabels = new List<Label> ();
+
+			for (int i = 0; i < count; i++) {
+				field.Text = $"Label {i}";
+				var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 10 };
+				view.Add (label);
+				Assert.Equal ($"Label {i}", label.Text);
+				Assert.Equal ($"Pos.Absolute({i + 1})", label.Y.ToString ());
+				listLabels.Add (label);
+
+				if (i == 0) {
+					Assert.Equal ($"Dim.Absolute({i})", view.Height.ToString ());
+					view.Height += 2;
+					Assert.Equal ($"Dim.Absolute({i + 2})", view.Height.ToString ());
+				} else {
+					Assert.Equal ($"Dim.Absolute({i + 1})", view.Height.ToString ());
+					view.Height += 1;
+					Assert.Equal ($"Dim.Absolute({i + 2})", view.Height.ToString ());
+				}
+			}
+
+			field.KeyDown += (k) => {
+				if (k.KeyEvent.Key == Key.Enter) {
+					((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
+					var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expecteds [count], output);
+					Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
+
+					if (count > 0) {
+						Assert.Equal ($"Label {count - 1}", listLabels [count - 1].Text);
+						view.Remove (listLabels [count - 1]);
+						listLabels.RemoveAt (count - 1);
+						Assert.Equal ($"Dim.Absolute({count + 1})", view.Height.ToString ());
+						view.Height -= 1;
+						count--;
+						if (listLabels.Count > 0)
+							field.Text = listLabels [count - 1].Text;
+						else
+							field.Text = NStack.ustring.Empty;
+					}
+					Assert.Equal ($"Dim.Absolute({count + 1})", view.Height.ToString ());
+				}
+			};
+
+			Application.Iteration += () => {
+				while (count > -1) {
+					field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
+					if (count == 0) {
+						field.OnKeyDown (new KeyEvent (Key.Enter, new KeyModifiers ()));
+						break;
+					}
+				}
+
+				Application.RequestStop ();
+			};
+
+			var win = new Window ();
+			win.Add (view);
+			win.Add (field);
+
+			top.Add (win);
+
+			Application.Run (top);
+
+			Assert.Equal (0, count);
+			Assert.Equal (count, listLabels.Count);
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
 		[Fact]
 		public void Internal_Tests ()
 		{
@@ -724,5 +1217,34 @@ namespace Terminal.Gui.Core {
 			var dimViewWidth = new Dim.DimView (view, 1);
 			Assert.Equal (20, dimViewWidth.Anchor (0));
 		}
+
+		[Fact]
+		public void Function_SetsValue ()
+		{
+			var text = "Test";
+			var dim = Dim.Function (() => text.Length);
+			Assert.Equal ("Dim.DimFunc(4)", dim.ToString ());
+
+			text = "New Test";
+			Assert.Equal ("Dim.DimFunc(8)", dim.ToString ());
+
+			text = "";
+			Assert.Equal ("Dim.DimFunc(0)", dim.ToString ());
+		}
+
+		[Fact]
+		public void Function_Equal ()
+		{
+			var f1 = () => 0;
+			var f2 = () => 0;
+
+			var dim1 = Dim.Function (f1);
+			var dim2 = Dim.Function (f2);
+			Assert.Equal (dim1, dim2);
+
+			f2 = () => 1;
+			dim2 = Dim.Function (f2);
+			Assert.NotEqual (dim1, dim2);
+		}
 	}
 }

+ 26 - 4
UnitTests/GraphViewTests.cs

@@ -52,12 +52,27 @@ namespace Terminal.Gui.Views {
 
 	public class GraphViewTests {
 
+		static string LastInitFakeDriver;
 
 		public static FakeDriver InitFakeDriver ()
 		{
 			var driver = new FakeDriver ();
-			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			try {
+				Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+			} catch (InvalidOperationException) {
+
+				// close it so that we don't get a thousand of these errors in a row
+				Application.Shutdown ();
+
+				// but still report a failure and name the test that didn't shut down.  Note
+				// that the test that didn't shutdown won't be the one currently running it will
+				// be the last one
+				throw new Exception ("A test did not call shutdown correctly.  Test stack trace was:" + LastInitFakeDriver);
+			}
+			
 			driver.Init (() => { });
+
+			LastInitFakeDriver = Environment.StackTrace;
 			return driver;
 		}
 
@@ -169,7 +184,8 @@ namespace Terminal.Gui.Views {
 			for (int r = 0; r < lines.Count; r++) {
 				List<char> row = lines [r];
 				for (int c = row.Count - 1; c >= 0; c--) {
-					if (row [c] != ' ') {
+					var rune = row [c];
+					if (rune != ' ' || (row.Sum (x => Rune.ColumnWidth (x)) == w)) {
 						break;
 					}
 					row.RemoveAt (c);
@@ -240,7 +256,7 @@ namespace Terminal.Gui.Views {
 
 					var match = expectedColors.Where (e => e.Value == val).ToList ();
 					if (match.Count == 0) {
-						throw new Exception ($"Unexpected color {val} was used at row {r} and col {c}.  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value))})");
+						throw new Exception ($"Unexpected color {DescribeColor (val)} was used at row {r} and col {c} (indexes start at 0).  Color value was {val} (expected colors were {string.Join (",", expectedColors.Select (c => c.Value))})");
 					} else if (match.Count > 1) {
 						throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 					}
@@ -249,7 +265,7 @@ namespace Terminal.Gui.Views {
 					var userExpected = line [c];
 
 					if (colorUsed != userExpected) {
-						throw new Exception ($"Colors used did not match expected at row {r} and col {c}.  Color index used was {colorUsed} but test expected {userExpected} (these are indexes into the expectedColors array)");
+						throw new Exception ($"Colors used did not match expected at row {r} and col {c} (indexes start at 0).  Color index used was {DescribeColor (colorUsed)} but test expected {DescribeColor (userExpected)} (these are indexes into the expectedColors array)");
 					}
 				}
 
@@ -257,6 +273,12 @@ namespace Terminal.Gui.Views {
 			}
 		}
 
+		private static object DescribeColor (int userExpected)
+		{
+			var a = new Attribute (userExpected);
+			return $"{a.Foreground},{a.Background}";
+		}
+
 		#region Screen to Graph Tests
 
 		[Fact]

+ 337 - 39
UnitTests/MenuTests.cs

@@ -177,6 +177,99 @@ Edit
 			void Copy () => miAction = "Copy";
 		}
 
+		[Fact, AutoInitShutdown]
+		public void MenuOpened_On_Disabled_MenuItem ()
+		{
+			MenuItem miCurrent = null;
+			Menu mCurrent = null;
+
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("_File", new MenuItem [] {
+					new MenuBarItem ("_New", new MenuItem [] {
+						new MenuItem ("_New doc", "Creates new doc.", null, () => false)
+					}),
+					null,
+					new MenuItem ("_Save", "Saves the file.", null, null)
+				})
+			});
+			menu.MenuOpened += (e) => {
+				miCurrent = e;
+				mCurrent = menu.openMenu;
+			};
+			menu.UseKeysUpDownAsKeysLeftRight = true;
+			Application.Top.Add (menu);
+
+			// open the menu
+			Assert.True (menu.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1Pressed,
+				View = menu
+			}));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", miCurrent.Parent.Title);
+			Assert.Equal ("_New", miCurrent.Title);
+
+			Assert.True (mCurrent.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 1,
+				Flags = MouseFlags.ReportMousePosition,
+				View = mCurrent
+			}));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", miCurrent.Parent.Title);
+			Assert.Equal ("_New", miCurrent.Title);
+
+			Assert.True (mCurrent.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 2,
+				Flags = MouseFlags.ReportMousePosition,
+				View = mCurrent
+			}));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", miCurrent.Parent.Title);
+			Assert.Equal ("_New", miCurrent.Title);
+
+			Assert.True (mCurrent.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 3,
+				Flags = MouseFlags.ReportMousePosition,
+				View = mCurrent
+			}));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", miCurrent.Parent.Title);
+			Assert.Equal ("_Save", miCurrent.Title);
+
+			// close the menu
+			Assert.True (menu.MouseEvent (new MouseEvent () {
+				X = 1,
+				Y = 0,
+				Flags = MouseFlags.Button1Pressed,
+				View = menu
+			}));
+			Assert.False (menu.IsMenuOpen);
+
+			// open the menu
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_New", miCurrent.Parent.Title);
+			Assert.Equal ("_New doc", miCurrent.Title);
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorDown, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", miCurrent.Parent.Title);
+			Assert.Equal ("_Save", miCurrent.Title);
+
+			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorUp, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.Equal ("_File", miCurrent.Parent.Title);
+			Assert.Equal ("_New", miCurrent.Title);
+
+			// close the menu
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+		}
+
 		[Fact]
 		[AutoInitShutdown]
 		public void MouseEvent_Test ()
@@ -293,6 +386,7 @@ Edit
 				miCurrent = null;
 				mCurrent = null;
 			};
+			menu.UseKeysUpDownAsKeysLeftRight = true;
 			Application.Top.Add (menu);
 			Application.Begin (Application.Top);
 
@@ -304,7 +398,7 @@ Edit
 			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
-			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Assert.Equal ("_About", GetCurrentMenuTitle ());
 
 			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
@@ -363,7 +457,7 @@ Edit
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
-			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Assert.Equal ("_About", GetCurrentMenuTitle ());
 
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
@@ -393,7 +487,7 @@ Edit
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
-			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Assert.Equal ("_About", GetCurrentMenuTitle ());
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.False (menu.IsMenuOpen);
 			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
@@ -566,8 +660,8 @@ Edit
 
 			expected = @"
 ┌──────
-│ One
-│ Two
+│ One  
+│ Two  
 └──────
 ";
 
@@ -582,8 +676,8 @@ Edit
 
 			expected = @"
 ┌──────
-│ One
-│ Two
+│ One  
+│ Two  
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -603,7 +697,7 @@ Edit
 					new MenuItem ("Three", "", null),
 				})
 			});
-
+			menu.UseKeysUpDownAsKeysLeftRight = true;
 			Application.Top.Add (menu);
 
 			Assert.Equal (Point.Empty, new Point (menu.Frame.X, menu.Frame.Y));
@@ -620,7 +714,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -634,9 +728,9 @@ Edit
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
-┌────────┐
-│ One    │
+  Numbers                
+┌────────┐               
+│ One    │               
 │ Two   ►│┌─────────────┐
 │ Three  ││ Sub-Menu 1  │
 └────────┘│ Sub-Menu 2  │
@@ -649,7 +743,7 @@ Edit
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -705,7 +799,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -724,9 +818,9 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
-┌────────┐
-│ One    │
+  Numbers                
+┌────────┐               
+│ One    │               
 │ Two   ►│┌─────────────┐
 │ Three  ││ Sub-Menu 1  │
 └────────┘│ Sub-Menu 2  │
@@ -744,7 +838,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -802,7 +896,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -817,7 +911,7 @@ Edit
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers      
 ┌─────────────┐
 │◄    Two     │
 ├─────────────┤
@@ -832,7 +926,7 @@ Edit
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -890,7 +984,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -909,7 +1003,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers      
 ┌─────────────┐
 │◄    Two     │
 ├─────────────┤
@@ -929,7 +1023,7 @@ Edit
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 │ One    │
 │ Two   ►│
@@ -1031,9 +1125,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1047,7 +1141,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  File   Edit
+  File   Edit   
        ┌───────┐
        │ Copy  │
        └───────┘
@@ -1081,9 +1175,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1093,7 +1187,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  File   Edit
+  File   Edit   
        ┌───────┐
        │ Copy  │
        └───────┘
@@ -1127,9 +1221,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1139,7 +1233,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
-  File   Edit
+  File   Edit   
        ┌───────┐
        │ Copy  │
        └───────┘
@@ -1168,9 +1262,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1186,5 +1280,209 @@ Edit
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (2, 0, 13, 1), pos);
 		}
+
+		[Fact]
+		public void UseKeysUpDownAsKeysLeftRight_And_UseSubMenusSingleFrame_Cannot_Be_Both_True ()
+		{
+			var menu = new MenuBar ();
+			Assert.False (menu.UseKeysUpDownAsKeysLeftRight);
+			Assert.False (menu.UseSubMenusSingleFrame);
+
+			menu.UseKeysUpDownAsKeysLeftRight = true;
+			Assert.True (menu.UseKeysUpDownAsKeysLeftRight);
+			Assert.False (menu.UseSubMenusSingleFrame);
+
+			menu.UseSubMenusSingleFrame = true;
+			Assert.False (menu.UseKeysUpDownAsKeysLeftRight);
+			Assert.True (menu.UseSubMenusSingleFrame);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Mouse ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("New", "", null)
+				}),
+				new MenuBarItem ("Edit", new MenuItem [] {
+				}),
+				new MenuBarItem ("Format", new MenuItem [] {
+					new MenuItem ("Wrap", "", null)
+				})
+			});
+			var tf = new TextField () { Y = 2, Width = 10 };
+			Application.Top.Add (menu, tf);
+
+			Application.Begin (Application.Top);
+			Assert.True (tf.HasFocus);
+			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			var expected = @"
+  File   Edit   Format
+┌──────┐              
+│ New  │              
+└──────┘              
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+
+			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+
+			Assert.True (menu.MouseEvent (new MouseEvent () { X = 15, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format 
+              ┌───────┐
+              │ Wrap  │
+              └───────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 23, 4), pos);
+
+			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+
+			Assert.True (menu.MouseEvent (new MouseEvent () { X = 1, Y = 0, Flags = MouseFlags.ReportMousePosition, View = menu }));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+┌──────┐              
+│ New  │              
+└──────┘              
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+
+			Assert.True (menu.MouseEvent (new MouseEvent () { X = 8, Y = 0, Flags = MouseFlags.Button1Pressed, View = menu }));
+			Assert.False (menu.IsMenuOpen);
+			Assert.True (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Parent_MenuItem_Stay_Focused_If_Child_MenuItem_Is_Empty_By_Keyboard ()
+		{
+			var menu = new MenuBar (new MenuBarItem [] {
+				new MenuBarItem ("File", new MenuItem [] {
+					new MenuItem ("New", "", null)
+				}),
+				new MenuBarItem ("Edit", new MenuItem [] {
+				}),
+				new MenuBarItem ("Format", new MenuItem [] {
+					new MenuItem ("Wrap", "", null)
+				})
+			});
+			var tf = new TextField () { Y = 2, Width = 10 };
+			Application.Top.Add (menu, tf);
+
+			Application.Begin (Application.Top);
+			Assert.True (tf.HasFocus);
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			var expected = @"
+  File   Edit   Format
+┌──────┐              
+│ New  │              
+└──────┘              
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+
+			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+
+			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format 
+              ┌───────┐
+              │ Wrap  │
+              └───────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 23, 4), pos);
+
+			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+
+			Assert.True (menu.openMenu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
+			Assert.True (menu.IsMenuOpen);
+			Assert.False (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+┌──────┐              
+│ New  │              
+└──────┘              
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 4), pos);
+
+			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, new KeyModifiers ())));
+			Assert.False (menu.IsMenuOpen);
+			Assert.True (tf.HasFocus);
+			Application.Top.Redraw (Application.Top.Bounds);
+			expected = @"
+  File   Edit   Format
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (2, 0, 22, 1), pos);
+		}
 	}
 }

+ 9 - 5
UnitTests/PanelViewTests.cs

@@ -345,12 +345,13 @@ namespace Terminal.Gui.Views {
 		{
 			var top = Application.Top;
 			var win = new Window ();
-			var label = new Label ("Hello World") {
-				Width = 24,
-				Height = 13,
+			var label = new Label () {
 				ColorScheme = Colors.TopLevel,
 				Text = "This is a test\nwith a \nPanelView",
-				TextAlignment = TextAlignment.Centered
+				TextAlignment = TextAlignment.Centered,
+				Width = 24,
+				Height = 13,
+				AutoSize = false
 			};
 			var pv = new PanelView (label) {
 				Width = 24,
@@ -370,6 +371,7 @@ namespace Terminal.Gui.Views {
 
 			Application.Begin (top);
 
+			Assert.False (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 24, 13), label.Frame);
 			Assert.Equal (new Rect (0, 0, 34, 23), pv.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
@@ -412,7 +414,7 @@ namespace Terminal.Gui.Views {
 		{
 			var top = Application.Top;
 			var win = new Window ();
-			var label = new Label ("Hello World") {
+			var label = new Label () {
 				ColorScheme = Colors.TopLevel,
 				Text = "This is a test\nwith a \nPanelView",
 				TextAlignment = TextAlignment.Centered
@@ -435,6 +437,8 @@ namespace Terminal.Gui.Views {
 
 			Application.Begin (top);
 
+			Assert.True (label.AutoSize);
+			Assert.False (pv.UsePanelFrame);
 			Assert.Equal (new Rect (0, 0, 14, 3), label.Frame);
 			Assert.Equal (new Rect (0, 0, 24, 13), pv.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);

+ 241 - 20
UnitTests/PosTests.cs

@@ -1,17 +1,26 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.IO;
 using System.Linq;
 using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
+using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
 namespace Terminal.Gui.Core {
 	public class PosTests {
+		readonly ITestOutputHelper output;
+
+		public PosTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void New_Works ()
 		{
@@ -70,11 +79,11 @@ namespace Terminal.Gui.Core {
 
 			Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
-			Assert.Equal (new Rect (1, 1, 78, 23), win.Subviews[0].Frame);
-			Assert.Equal ("ContentView()({X=1,Y=1,Width=78,Height=23})", win.Subviews [0].ToString());
+			Assert.Equal (new Rect (1, 1, 78, 23), win.Subviews [0].Frame);
+			Assert.Equal ("ContentView()({X=1,Y=1,Width=78,Height=23})", win.Subviews [0].ToString ());
 			Assert.Equal (new Rect (1, 1, 79, 24), new Rect (
-				win.Subviews[0].Frame.Left, win.Subviews [0].Frame.Top,
-				win.Subviews [0].Frame.Right, win.Subviews[0].Frame.Bottom));
+				win.Subviews [0].Frame.Left, win.Subviews [0].Frame.Top,
+				win.Subviews [0].Frame.Right, win.Subviews [0].Frame.Bottom));
 			Assert.Equal (new Rect (68, 22, 10, 1), tv.Frame);
 		}
 
@@ -123,7 +132,7 @@ namespace Terminal.Gui.Core {
 				ColorScheme = Colors.Menu,
 				Width = Dim.Fill (),
 				X = Pos.Center (),
-				Y = Pos.Bottom (win) - 4  // two lines top border more two lines above border
+				Y = Pos.Bottom (win) - 3  // two lines top and bottom borders more one line above the bottom border
 			};
 
 			win.Add (label);
@@ -131,15 +140,78 @@ namespace Terminal.Gui.Core {
 			var top = Application.Top;
 			top.Add (win);
 			Application.Begin (top);
+			((FakeDriver)Application.Driver).SetBufferSize (40, 10);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 40, 10), top.Frame);
+			Assert.Equal (new Rect (0, 0, 40, 10), win.Frame);
+			Assert.Equal (new Rect (1, 1, 38, 8), win.Subviews [0].Frame);
+			Assert.Equal ("ContentView()({X=1,Y=1,Width=38,Height=8})", win.Subviews [0].ToString ());
+			Assert.Equal (new Rect (0, 0, 40, 10), new Rect (
+				win.Frame.Left, win.Frame.Top,
+				win.Frame.Right, win.Frame.Bottom));
+			Assert.Equal (new Rect (0, 7, 38, 1), label.Frame);
+			var expected = @"
+┌──────────────────────────────────────┐
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│    This should be the last line.     │
+└──────────────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
 
-			Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
-			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
-			Assert.Equal (new Rect (1, 1, 78, 23), win.Subviews [0].Frame);
-			Assert.Equal ("ContentView()({X=1,Y=1,Width=78,Height=23})", win.Subviews [0].ToString ());
-			Assert.Equal (new Rect (0, 0, 80, 25), new Rect (
+		[Fact]
+		[AutoInitShutdown]
+		public void AnchorEnd_Better_Than_Bottom_Equal_Inside_Window ()
+		{
+			var win = new Window ();
+
+			var label = new Label ("This should be the last line.") {
+				TextAlignment = Terminal.Gui.TextAlignment.Centered,
+				ColorScheme = Colors.Menu,
+				Width = Dim.Fill (),
+				X = Pos.Center (),
+				Y = Pos.AnchorEnd (1)
+			};
+
+			win.Add (label);
+
+			var top = Application.Top;
+			top.Add (win);
+			Application.Begin (top);
+			((FakeDriver)Application.Driver).SetBufferSize (40, 10);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (29, label.Text.Length);
+			Assert.Equal (new Rect (0, 0, 40, 10), top.Frame);
+			Assert.Equal (new Rect (0, 0, 40, 10), win.Frame);
+			Assert.Equal (new Rect (1, 1, 38, 8), win.Subviews [0].Frame);
+			Assert.Equal ("ContentView()({X=1,Y=1,Width=38,Height=8})", win.Subviews [0].ToString ());
+			Assert.Equal (new Rect (0, 0, 40, 10), new Rect (
 				win.Frame.Left, win.Frame.Top,
 				win.Frame.Right, win.Frame.Bottom));
-			Assert.Equal (new Rect (0, 21, 78, 1), label.Frame);
+			Assert.Equal (new Rect (0, 7, 38, 1), label.Frame);
+			var expected = @"
+┌──────────────────────────────────────┐
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│    This should be the last line.     │
+└──────────────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 		}
 
 		[Fact]
@@ -153,17 +225,81 @@ namespace Terminal.Gui.Core {
 				ColorScheme = Colors.Menu,
 				Width = Dim.Fill (),
 				X = Pos.Center (),
-				Y = Pos.Bottom (win) - 4  // two lines top border more two lines above border
+				Y = Pos.Bottom (win) - 4  // two lines top and bottom borders more two lines above border
 			};
 
 			win.Add (label);
 
-			var menu = new MenuBar ();
-			var status = new StatusBar ();
+			var menu = new MenuBar (new MenuBarItem [] { new ("Menu", "", null) });
+			var status = new StatusBar (new StatusItem [] { new (Key.F1, "~F1~ Help", null) });
+			var top = Application.Top;
+			top.Add (win, menu, status);
+			Application.Begin (top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
+			Assert.Equal (new Rect (0, 0, 80, 1), menu.Frame);
+			Assert.Equal (new Rect (0, 24, 80, 1), status.Frame);
+			Assert.Equal (new Rect (0, 1, 80, 23), win.Frame);
+			Assert.Equal (new Rect (1, 1, 78, 21), win.Subviews [0].Frame);
+			Assert.Equal (new Rect (0, 1, 80, 24), new Rect (
+				win.Frame.Left, win.Frame.Top,
+				win.Frame.Right, win.Frame.Bottom));
+			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
+			var expected = @"
+  Menu                                                                          
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                        This should be the last line.                         │
+└──────────────────────────────────────────────────────────────────────────────┘
+ F1 Help                                                                        
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void AnchorEnd_Better_Than_Bottom_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
+		{
+			var win = new Window ();
+
+			var label = new Label ("This should be the last line.") {
+				TextAlignment = Terminal.Gui.TextAlignment.Centered,
+				ColorScheme = Colors.Menu,
+				Width = Dim.Fill (),
+				X = Pos.Center (),
+				Y = Pos.AnchorEnd (1)
+			};
+
+			win.Add (label);
+
+			var menu = new MenuBar (new MenuBarItem [] { new ("Menu", "", null) });
+			var status = new StatusBar (new StatusItem [] { new (Key.F1, "~F1~ Help", null) });
 			var top = Application.Top;
 			top.Add (win, menu, status);
 			Application.Begin (top);
 
+			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 1), menu.Frame);
 			Assert.Equal (new Rect (0, 24, 80, 1), status.Frame);
@@ -173,6 +309,35 @@ namespace Terminal.Gui.Core {
 				win.Frame.Left, win.Frame.Top,
 				win.Frame.Right, win.Frame.Bottom));
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
+			var expected = @"
+  Menu                                                                          
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                        This should be the last line.                         │
+└──────────────────────────────────────────────────────────────────────────────┘
+ F1 Help                                                                        
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 		}
 
 		[Fact]
@@ -488,11 +653,37 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		public void Percent_Equal ()
 		{
-			var n1 = 0;
-			var n2 = 0;
+			float n1 = 0;
+			float n2 = 0;
 			var pos1 = Pos.Percent (n1);
 			var pos2 = Pos.Percent (n2);
-			// BUGBUG: Pos.Percent should support equality 
+			Assert.Equal (pos1, pos2);
+
+			n1 = n2 = 1;
+			pos1 = Pos.Percent (n1);
+			pos2 = Pos.Percent (n2);
+			Assert.Equal (pos1, pos2);
+
+			n1 = n2 = 0.5f;
+			pos1 = Pos.Percent (n1);
+			pos2 = Pos.Percent (n2);
+			Assert.Equal (pos1, pos2);
+
+			n1 = n2 = 100f;
+			pos1 = Pos.Percent (n1);
+			pos2 = Pos.Percent (n2);
+			Assert.Equal (pos1, pos2);
+
+			n1 = 0;
+			n2 = 1;
+			pos1 = Pos.Percent (n1);
+			pos2 = Pos.Percent (n2);
+			Assert.NotEqual (pos1, pos2);
+
+			n1 = 0.5f;
+			n2 = 1.5f;
+			pos1 = Pos.Percent (n1);
+			pos2 = Pos.Percent (n2);
 			Assert.NotEqual (pos1, pos2);
 		}
 
@@ -507,7 +698,7 @@ namespace Terminal.Gui.Core {
 		}
 
 		[Fact]
-		public void Pos_Validation_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type ()
+		public void ForceValidatePosDim_True_Pos_Validation_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type ()
 		{
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
@@ -519,7 +710,8 @@ namespace Terminal.Gui.Core {
 			};
 			var v = new View ("v") {
 				X = Pos.Center (),
-				Y = Pos.Percent (10)
+				Y = Pos.Percent (10),
+				ForceValidatePosDim = true
 			};
 
 			w.Add (v);
@@ -811,5 +1003,34 @@ namespace Terminal.Gui.Core {
 			var posViewBottom = new Pos.PosView (view, 3);
 			Assert.Equal (11, posViewBottom.Anchor (0));
 		}
+
+		[Fact]
+		public void Function_SetsValue ()
+		{
+			var text = "Test";
+			var pos = Pos.Function (() => text.Length);
+			Assert.Equal ("Pos.PosFunc(4)", pos.ToString ());
+
+			text = "New Test";
+			Assert.Equal ("Pos.PosFunc(8)", pos.ToString ());
+
+			text = "";
+			Assert.Equal ("Pos.PosFunc(0)", pos.ToString ());
+		}
+
+		[Fact]
+		public void Function_Equal ()
+		{
+			var f1 = () => 0;
+			var f2 = () => 0;
+
+			var pos1 = Pos.Function (f1);
+			var pos2 = Pos.Function (f2);
+			Assert.Equal (pos1, pos2);
+
+			f2 = () => 1;
+			pos2 = Pos.Function (f2);
+			Assert.NotEqual (pos1, pos2);
+		}
 	}
 }

+ 82 - 22
UnitTests/RadioGroupTests.cs

@@ -4,46 +4,60 @@ using System.Linq;
 using System.Text;
 using System.Threading.Tasks;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class RadioGroupTests {
+		readonly ITestOutputHelper output;
+
+		public RadioGroupTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		public void Constructors_Defaults ()
 		{
 			var rg = new RadioGroup ();
 			Assert.True (rg.CanFocus);
 			Assert.Empty (rg.RadioLabels);
-			Assert.Equal (0, rg.X);
-			Assert.Equal (0, rg.Y);
-			Assert.Equal (0, rg.Width);
-			Assert.Equal (0, rg.Height);
+			Assert.Null (rg.X);
+			Assert.Null (rg.Y);
+			Assert.Null (rg.Width);
+			Assert.Null (rg.Height);
+			Assert.Equal (Rect.Empty, rg.Frame);
 			Assert.Equal (0, rg.SelectedItem);
 
 			rg = new RadioGroup (new NStack.ustring [] { "Test" });
 			Assert.True (rg.CanFocus);
 			Assert.Single (rg.RadioLabels);
-			Assert.Equal (0, rg.X);
-			Assert.Equal (0, rg.Y);
-			Assert.Equal (7, rg.Width);
-			Assert.Equal (1, rg.Height);
+			Assert.Null (rg.X);
+			Assert.Null (rg.Y);
+			Assert.Null (rg.Width);
+			Assert.Null (rg.Height);
+			Assert.Equal (new Rect (0, 0, 7, 1), rg.Frame);
 			Assert.Equal (0, rg.SelectedItem);
 
 			rg = new RadioGroup (new Rect (1, 2, 20, 5), new NStack.ustring [] { "Test" });
 			Assert.True (rg.CanFocus);
 			Assert.Single (rg.RadioLabels);
-			Assert.Equal (1, rg.X);
-			Assert.Equal (2, rg.Y);
-			Assert.Equal (20, rg.Width);
-			Assert.Equal (5, rg.Height);
+			Assert.Equal (LayoutStyle.Absolute, rg.LayoutStyle);
+			Assert.Null (rg.X);
+			Assert.Null (rg.Y);
+			Assert.Null (rg.Width);
+			Assert.Null (rg.Height);
+			Assert.Equal (new Rect (1, 2, 20, 5), rg.Frame);
 			Assert.Equal (0, rg.SelectedItem);
 
 			rg = new RadioGroup (1, 2, new NStack.ustring [] { "Test" });
 			Assert.True (rg.CanFocus);
 			Assert.Single (rg.RadioLabels);
-			Assert.Equal (1, rg.X);
-			Assert.Equal (2, rg.Y);
-			Assert.Equal (7, rg.Width);
-			Assert.Equal (1, rg.Height);
+			Assert.Equal (LayoutStyle.Absolute, rg.LayoutStyle);
+			Assert.Null (rg.X);
+			Assert.Null (rg.Y);
+			Assert.Null (rg.Width);
+			Assert.Null (rg.Height);
+			Assert.Equal (new Rect (1, 2, 7, 1), rg.Frame);
 			Assert.Equal (0, rg.SelectedItem);
 		}
 
@@ -56,32 +70,78 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (0, rg.SelectedItem);
 		}
 
-		[Fact]
-		public void DisplayMode_Width_Height_HorizontalSpace ()
+		[Fact, AutoInitShutdown]
+		public void DisplayMode_Width_Height_Vertical_Horizontal_Space ()
 		{
-			var rg = new RadioGroup (new NStack.ustring [] { "Test", "New Test" });
+			var rg = new RadioGroup (new NStack.ustring [] { "Test", "New Test 你" });
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (rg);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+
 			Assert.Equal (DisplayModeLayout.Vertical, rg.DisplayMode);
 			Assert.Equal (2, rg.RadioLabels.Length);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.Y);
-			Assert.Equal (11, rg.Width);
+			Assert.Equal (14, rg.Width);
 			Assert.Equal (2, rg.Height);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│● Test                      │
+│◌ New Test 你               │
+│                            │
+└────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 
 			rg.DisplayMode = DisplayModeLayout.Horizontal;
+			Application.Refresh ();
+
 			Assert.Equal (DisplayModeLayout.Horizontal, rg.DisplayMode);
 			Assert.Equal (2, rg.HorizontalSpace);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.Y);
-			Assert.Equal (16, rg.Width);
+			Assert.Equal (21, rg.Width);
 			Assert.Equal (1, rg.Height);
 
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│● Test  ◌ New Test 你       │
+│                            │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
 			rg.HorizontalSpace = 4;
+			Application.Refresh ();
+
 			Assert.Equal (DisplayModeLayout.Horizontal, rg.DisplayMode);
 			Assert.Equal (4, rg.HorizontalSpace);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.Y);
-			Assert.Equal (20, rg.Width);
+			Assert.Equal (23, rg.Width);
 			Assert.Equal (1, rg.Height);
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│● Test    ◌ New Test 你     │
+│                            │
+│                            │
+└────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 
 		[Fact]

+ 24 - 56
UnitTests/ScenarioTests.cs

@@ -37,10 +37,14 @@ namespace Terminal.Gui {
 			return FakeConsole.MockKeyPresses.Count;
 		}
 
+
 		/// <summary>
+		/// <para>
 		/// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.
-		/// It puts a Ctrl-Q in the input queue so the Scenario immediately exits. 
-		/// Should find any egregious regressions.
+		/// </para>
+		/// <para>
+		/// Should find any Scenarios which crash on load or do not respond to <see cref="Application.RequestStop()"/>.
+		/// </para>
 		/// </summary>
 		[Fact]
 		public void Run_All_Scenarios ()
@@ -48,61 +52,25 @@ namespace Terminal.Gui {
 			List<Type> scenarioClasses = Scenario.GetDerivedClasses<Scenario> ();
 			Assert.NotEmpty (scenarioClasses);
 
-			lock (FakeConsole.MockKeyPresses) {
-				foreach (var scenarioClass in scenarioClasses) {
-
-					// Setup some fake keypresses 
-					// Passing empty string will cause just a ctrl-q to be fired
-					FakeConsole.MockKeyPresses.Clear ();
-					int stackSize = CreateInput ("");
-
-					Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
-
-					int iterations = 0;
-					Application.Iteration = () => {
-						iterations++;
-						// Stop if we run out of control...
-						if (iterations > 10) {
-							Application.RequestStop ();
-						}
-					};
-
-					int ms;
-					if (scenarioClass.Name == "CharacterMap") {
-						ms = 2000;
-					} else {
-						ms = 1000;
-					}
-					var abortCount = 0;
-					Func<MainLoop, bool> abortCallback = (MainLoop loop) => {
-						abortCount++;
-						Application.RequestStop ();
-						return false;
-					};
-					var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
-
-					var scenario = (Scenario)Activator.CreateInstance (scenarioClass);
-					scenario.Init (Application.Top, Colors.Base);
-					scenario.Setup ();
-					// There is no need to call Application.Begin because Init already creates the Application.Top
-					// If Application.RunState is used then the Application.RunLoop must also be used instead Application.Run.
-					//var rs = Application.Begin (Application.Top);
-					scenario.Run ();
-
-					//Application.End (rs);
-
-					// Shutdown must be called to safely clean up Application if Init has been called
-					Application.Shutdown ();
-
-					if (abortCount != 0) {
-						output.WriteLine ($"Scenario {scenarioClass} had abort count of {abortCount}");
-					}
+			foreach (var scenarioClass in scenarioClasses) {
 
-					Assert.Equal (0, abortCount);
-					// # of key up events should match # of iterations
-					Assert.Equal (1, iterations);
-					Assert.Equal (stackSize, iterations);
-				}
+				output.WriteLine ($"Running Scenario '{scenarioClass.Name}'");
+
+				Func<MainLoop, bool> closeCallback = (MainLoop loop) => {
+					Application.RequestStop ();
+					return false;
+				};
+
+				var scenario = (Scenario)Activator.CreateInstance (scenarioClass);
+				Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
+
+				// Close after a short period of time
+				var token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (200), closeCallback);
+
+				scenario.Init (Application.Top, Colors.Base);
+				scenario.Setup ();
+				scenario.Run ();
+				Application.Shutdown ();
 			}
 #if DEBUG_IDISPOSABLE
 			foreach (var inst in Responder.Instances) {

+ 139 - 1
UnitTests/ScrollBarViewTests.cs

@@ -3,9 +3,16 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Reflection;
 using Xunit;
+using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
 	public class ScrollBarViewTests {
+		readonly ITestOutputHelper output;
+
+		public ScrollBarViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
 
 		// This class enables test functions annotated with the [InitShutdown] attribute
 		// to have a function called before the test function is called and after.
@@ -13,7 +20,7 @@ namespace Terminal.Gui.Views {
 		// This is necessary because a) Application is a singleton and Init/Shutdown must be called
 		// as a pair, and b) all unit test functions should be atomic.
 		[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
-		public class InitShutdown : Xunit.Sdk.BeforeAfterTestAttribute {
+		public class InitShutdownAttribute : Xunit.Sdk.BeforeAfterTestAttribute {
 
 			public override void Before (MethodInfo methodUnderTest)
 			{
@@ -635,5 +642,136 @@ namespace Terminal.Gui.Views {
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.OtherScrollBarView.Visible);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Hosting_ShowBothScrollIndicator_Invisible ()
+		{
+			var textView = new TextView () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = "This is the help text for the Second Step.\n\nPress the button to see a message box.\n\nEnter name too."
+			};
+			var win = new Window ("Test") {
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			win.Add (textView);
+
+			var scrollBar = new ScrollBarView (textView, true);
+
+			scrollBar.ChangedPosition += () => {
+				textView.TopRow = scrollBar.Position;
+				if (textView.TopRow != scrollBar.Position) {
+					scrollBar.Position = textView.TopRow;
+				}
+				textView.SetNeedsDisplay ();
+			};
+
+			scrollBar.OtherScrollBarView.ChangedPosition += () => {
+				textView.LeftColumn = scrollBar.OtherScrollBarView.Position;
+				if (textView.LeftColumn != scrollBar.OtherScrollBarView.Position) {
+					scrollBar.OtherScrollBarView.Position = textView.LeftColumn;
+				}
+				textView.SetNeedsDisplay ();
+			};
+
+			scrollBar.VisibleChanged += () => {
+				if (scrollBar.Visible && textView.RightOffset == 0) {
+					textView.RightOffset = 1;
+				} else if (!scrollBar.Visible && textView.RightOffset == 1) {
+					textView.RightOffset = 0;
+				}
+			};
+
+			scrollBar.OtherScrollBarView.VisibleChanged += () => {
+				if (scrollBar.OtherScrollBarView.Visible && textView.BottomOffset == 0) {
+					textView.BottomOffset = 1;
+				} else if (!scrollBar.OtherScrollBarView.Visible && textView.BottomOffset == 1) {
+					textView.BottomOffset = 0;
+				}
+			};
+
+			textView.DrawContent += (e) => {
+				scrollBar.Size = textView.Lines;
+				scrollBar.Position = textView.TopRow;
+				if (scrollBar.OtherScrollBarView != null) {
+					scrollBar.OtherScrollBarView.Size = textView.Maxlength;
+					scrollBar.OtherScrollBarView.Position = textView.LeftColumn;
+				}
+				scrollBar.LayoutSubviews ();
+				scrollBar.Refresh ();
+			};
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (45, 20);
+
+			Assert.True (scrollBar.AutoHideScrollBars);
+			Assert.Equal (5, textView.Lines);
+			Assert.Equal (42, textView.Maxlength);
+			Assert.Equal (0, scrollBar.Position);
+			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
+			var expected = @"
+┌ Test ─────────────────────────────────────┐
+│This is the help text for the Second Step. │
+│                                           │
+│Press the button to see a message box.     │
+│                                           │
+│Enter name too.                            │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+│                                           │
+└───────────────────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 45, 20), pos);
+
+			textView.WordWrap = true;
+			((FakeDriver)Application.Driver).SetBufferSize (26, 20);
+			Application.Refresh ();
+
+			Assert.True (textView.WordWrap);
+			Assert.True (scrollBar.AutoHideScrollBars);
+			Assert.Equal (7, textView.Lines);
+			Assert.Equal (22, textView.Maxlength);
+			Assert.Equal (0, scrollBar.Position);
+			Assert.Equal (0, scrollBar.OtherScrollBarView.Position);
+			expected = @"
+┌ Test ──────────────────┐
+│This is the help text   │
+│for the Second Step.    │
+│                        │
+│Press the button to     │
+│see a message box.      │
+│                        │
+│Enter name too.         │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+│                        │
+└────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 26, 20), pos);
+		}
 	}
 }

+ 10 - 8
UnitTests/ScrollViewTests.cs

@@ -11,25 +11,27 @@ namespace Terminal.Gui.Views {
 		public void Constructors_Defaults ()
 		{
 			var sv = new ScrollView ();
+			Assert.Equal (LayoutStyle.Computed, sv.LayoutStyle);
 			Assert.True (sv.CanFocus);
 			Assert.Equal (new Rect (0, 0, 0, 0), sv.Frame);
 			Assert.Equal (Rect.Empty, sv.Frame);
-			Assert.Equal (0, sv.X);
-			Assert.Equal (0, sv.Y);
-			Assert.Equal (0, sv.Width);
-			Assert.Equal (0, sv.Height);
+			Assert.Null (sv.X);
+			Assert.Null (sv.Y);
+			Assert.Null (sv.Width);
+			Assert.Null (sv.Height);
 			Assert.Equal (Point.Empty, sv.ContentOffset);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.True (sv.AutoHideScrollBars);
 			Assert.True (sv.KeepContentAlwaysInViewport);
 
 			sv = new ScrollView (new Rect (1, 2, 20, 10));
+			Assert.Equal (LayoutStyle.Absolute, sv.LayoutStyle);
 			Assert.True (sv.CanFocus);
 			Assert.Equal (new Rect (1, 2, 20, 10), sv.Frame);
-			Assert.Equal (1, sv.X);
-			Assert.Equal (2, sv.Y);
-			Assert.Equal (20, sv.Width);
-			Assert.Equal (10, sv.Height);
+			Assert.Null (sv.X);
+			Assert.Null (sv.Y);
+			Assert.Null (sv.Width);
+			Assert.Null (sv.Height);
 			Assert.Equal (Point.Empty, sv.ContentOffset);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.True (sv.AutoHideScrollBars);

+ 1 - 1
UnitTests/StatusBarTests.cs

@@ -34,7 +34,7 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (Dim.Fill (), sb.Width);
 			Assert.Equal (1, sb.Height);
 
-			Assert.Equal (0, sb.Y);
+			Assert.Null (sb.Y);
 
 			var driver = new FakeDriver ();
 			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));

+ 8 - 12
UnitTests/TabViewTests.cs

@@ -9,7 +9,7 @@ using System.Globalization;
 using Xunit.Abstractions;
 
 namespace Terminal.Gui.Views {
-  
+
 	public class TabViewTests {
 		readonly ITestOutputHelper output;
 
@@ -25,7 +25,7 @@ namespace Terminal.Gui.Views {
 
 		private TabView GetTabView (out TabView.Tab tab1, out TabView.Tab tab2, bool initFakeDriver = true)
 		{
-			if(initFakeDriver)
+			if (initFakeDriver)
 				InitFakeDriver ();
 
 			var tv = new TabView ();
@@ -52,7 +52,6 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 		}
 
-
 		[Fact]
 		public void EnsureSelectedTabVisible_NullSelect ()
 		{
@@ -92,7 +91,6 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 		}
 
-
 		[Fact]
 		public void SelectedTabChanged_Called ()
 		{
@@ -119,6 +117,7 @@ namespace Terminal.Gui.Views {
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 		}
+
 		[Fact]
 		public void RemoveTab_ChangesSelection ()
 		{
@@ -224,8 +223,6 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 		}
 
-
-
 		[Fact]
 		public void SwitchTabBy_OutOfTabsRange ()
 		{
@@ -242,13 +239,12 @@ namespace Terminal.Gui.Views {
 
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
-
 		}
 
-		[Fact,AutoInitShutdown]
+		[Fact, AutoInitShutdown]
 		public void TestThinTabView_WithLongNames ()
 		{
-			var tv = GetTabView (out var tab1, out var tab2,false);
+			var tv = GetTabView (out var tab1, out var tab2, false);
 			tv.Width = 10;
 			tv.Height = 5;
 
@@ -306,10 +302,8 @@ namespace Terminal.Gui.Views {
 ◄       └┐
 │hi2     │
 └────────┘", output);
-
 		}
 
-
 		[Fact, AutoInitShutdown]
 		public void TestTabView_Width4 ()
 		{
@@ -327,6 +321,7 @@ namespace Terminal.Gui.Views {
 │hi│
 └──┘", output);
 		}
+
 		[Fact, AutoInitShutdown]
 		public void TestTabView_Width3 ()
 		{
@@ -339,9 +334,10 @@ namespace Terminal.Gui.Views {
 
 			GraphViewTests.AssertDriverContentsAre (@"
 ┌─┐
-│hi
+│h
 └─┘", output);
 		}
+
 		private void InitFakeDriver ()
 		{
 			var driver = new FakeDriver ();

+ 465 - 10
UnitTests/TableViewTests.cs

@@ -7,6 +7,7 @@ using Terminal.Gui;
 using Xunit;
 using System.Globalization;
 using Xunit.Abstractions;
+using System.Reflection;
 
 namespace Terminal.Gui.Views {
 
@@ -550,23 +551,311 @@ namespace Terminal.Gui.Views {
 		}
 
 		[Fact]
-		public void TableView_ColorsTest_ColorGetter ()
+		public void TableViewMultiSelect_CannotFallOffLeft()
 		{
 			var tv = SetUpMiniTable ();
+			tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
 
-			tv.Style.ExpandLastColumn = false;
+			tv.MultiSelect = true;
+			tv.SelectedColumn = 1;
+			tv.SelectedRow = 1;
+			tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single().Rect);
+
+			// this next shift left should be ignored because we are already at the bounds
+			tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
+
+			Assert.Equal (0, tv.SelectedColumn);
+			Assert.Equal (1, tv.SelectedRow);
+
+			Application.Shutdown ();
+		}
+		[Fact]
+		public void TableViewMultiSelect_CannotFallOffRight()
+		{
+			var tv = SetUpMiniTable ();
+			tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
+
+			tv.MultiSelect = true;
+			tv.SelectedColumn = 0;
+			tv.SelectedRow = 1;
+			tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
+
+			// this next shift right should be ignored because we are already at the right bounds
+			tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 1, 2, 1), tv.MultiSelectedRegions.Single ().Rect);
+
+			Assert.Equal (1, tv.SelectedColumn);
+			Assert.Equal (1, tv.SelectedRow);
+
+			Application.Shutdown ();
+		}
+		[Fact]
+		public void TableViewMultiSelect_CannotFallOffBottom ()
+		{
+			var tv = SetUpMiniTable ();
+			tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
+
+			tv.MultiSelect = true;
+			tv.SelectedColumn = 0;
+			tv.SelectedRow = 0;
+			tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
+			tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
+
+			// this next moves should be ignored because we already selected the whole table
+			tv.ProcessKey (new KeyEvent (Key.CursorRight | Key.ShiftMask, new KeyModifiers { Shift = true }));
+			tv.ProcessKey (new KeyEvent (Key.CursorDown | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
+			Assert.Equal (1, tv.SelectedColumn);
+			Assert.Equal (1, tv.SelectedRow);
+
+			Application.Shutdown ();
+		}
+
+		[Fact]
+		public void TableViewMultiSelect_CannotFallOffTop()
+		{
+			var tv = SetUpMiniTable ();
+			tv.Table.Rows.Add (1, 2); // add another row (brings us to 2 rows)
+
+			tv.MultiSelect = true;
+			tv.SelectedColumn = 1;
+			tv.SelectedRow = 1;
+			tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
+			tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
+
+			// this next moves should be ignored because we already selected the whole table
+			tv.ProcessKey (new KeyEvent (Key.CursorLeft | Key.ShiftMask, new KeyModifiers { Shift = true }));
+			tv.ProcessKey (new KeyEvent (Key.CursorUp | Key.ShiftMask, new KeyModifiers { Shift = true }));
+
+			Assert.Equal (new Rect (0, 0, 2, 2), tv.MultiSelectedRegions.Single ().Rect);
+			Assert.Equal (0, tv.SelectedColumn);
+			Assert.Equal (0, tv.SelectedRow);
+
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (false)]
+		[InlineData (true)]
+		public void TableView_ColorTests_FocusedOrNot (bool focused)
+		{
+			var tv = SetUpMiniTable ();
+
+			// width exactly matches the max col widths
+			tv.Bounds = new Rect (0, 0, 5, 4);
+
+			// private method for forcing the view to be focused/not focused
+			var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
+
+			// when the view is/isn't focused 
+			setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
+
+			tv.Redraw (tv.Bounds);
+
+			string expected = @"
+┌─┬─┐
+│A│B│
+├─┼─┤
+│1│2│
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+
+			string expectedColors = @"
+00000
+00000
+00000
+01000
+";
+			
+			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+				// 0
+				tv.ColorScheme.Normal,				
+				// 1
+				focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal});
+
+			Application.Shutdown();
+		}
+
+		[Theory]
+		[InlineData (false)]
+		[InlineData (true)]
+		public void TableView_ColorTests_InvertSelectedCellFirstCharacter (bool focused)
+		{
+			var tv = SetUpMiniTable ();
 			tv.Style.InvertSelectedCellFirstCharacter = true;
 
 			// width exactly matches the max col widths
 			tv.Bounds = new Rect (0, 0, 5, 4);
+
+			// private method for forcing the view to be focused/not focused
+			var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
+
+			// when the view is/isn't focused 
+			setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
+
+			tv.Redraw (tv.Bounds);
+
+			string expected = @"
+┌─┬─┐
+│A│B│
+├─┼─┤
+│1│2│
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+
+			string expectedColors = @"
+00000
+00000
+00000
+01000
+";
+			
+			var invertHotFocus = new Attribute(tv.ColorScheme.HotFocus.Background,tv.ColorScheme.HotFocus.Foreground);
+			var invertHotNormal = new Attribute(tv.ColorScheme.HotNormal.Background,tv.ColorScheme.HotNormal.Foreground);
+
+			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+				// 0
+				tv.ColorScheme.Normal,				
+				// 1
+				focused ?  invertHotFocus : invertHotNormal});
+			
+			Application.Shutdown();
+		}
+
+
+		[Theory]
+		[InlineData (false)]
+		[InlineData (true)]
+		public void TableView_ColorsTest_RowColorGetter (bool focused)
+		{
+			var tv = SetUpMiniTable ();
+
+			// width exactly matches the max col widths
+			tv.Bounds = new Rect (0, 0, 5, 4);
+
+			var rowHighlight = new ColorScheme () {
+				Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
+				HotNormal = Attribute.Make (Color.Green, Color.Blue),
+				HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
+				Focus = Attribute.Make (Color.Cyan, Color.Magenta),
+			};
+
+			// when B is 2 use the custom highlight colour for the row
+			tv.Style.RowColorGetter += (e)=>Convert.ToInt32(e.Table.Rows[e.RowIndex][1]) == 2 ? rowHighlight : null;
+
+			// private method for forcing the view to be focused/not focused
+			var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
+
+			// when the view is/isn't focused 
+			setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
+
+			tv.Redraw (tv.Bounds);
+
+			string expected = @"
+┌─┬─┐
+│A│B│
+├─┼─┤
+│1│2│
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+
+			string expectedColors = @"
+00000
+00000
+00000
+21222
+";
 			
+			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+				// 0
+				tv.ColorScheme.Normal,				
+				// 1
+				focused ? rowHighlight.HotFocus : rowHighlight.HotNormal,
+				// 2
+				rowHighlight.Normal});
+
+
+			// change the value in the table so that
+			// it no longer matches the RowColorGetter
+			// delegate conditional ( which checks for
+			// the value 2)
+			tv.Table.Rows[0][1] = 5;
+
+			tv.Redraw (tv.Bounds);
+			expected = @"
+┌─┬─┐
+│A│B│
+├─┼─┤
+│1│5│
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+
+			expectedColors = @"
+00000
+00000
+00000
+01000
+";
+
+			// now we only see 2 colors used (the selected cell color and Normal
+			// rowHighlight should no longer be used because the delegate returned null
+			// (now that the cell value is 5 - which does not match the conditional)
+			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+				// 0
+				tv.ColorScheme.Normal,
+				// 1
+				focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
+
+
+			// Shutdown must be called to safely clean up Application if Init has been called
+			Application.Shutdown ();
+		}
+
+		[Theory]
+		[InlineData (false)]
+		[InlineData (true)]
+		public void TableView_ColorsTest_ColorGetter (bool focused)
+		{
+			var tv = SetUpMiniTable ();
+
+			// width exactly matches the max col widths
+			tv.Bounds = new Rect (0, 0, 5, 4);
+
 			// Create a style for column B
 			var bStyle = tv.Style.GetOrCreateColumnStyle (tv.Table.Columns ["B"]);
 
 			// when B is 2 use the custom highlight colour
-			ColorScheme cellHighlight = new ColorScheme () { Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray) };
+			var cellHighlight = new ColorScheme () {
+				Normal = Attribute.Make (Color.BrightCyan, Color.DarkGray),
+				HotNormal = Attribute.Make (Color.Green, Color.Blue),
+				HotFocus = Attribute.Make (Color.BrightYellow, Color.White),
+				Focus = Attribute.Make (Color.Cyan, Color.Magenta),
+			};
+
 			bStyle.ColorGetter = (a) => Convert.ToInt32(a.CellValue) == 2 ? cellHighlight : null;
 
+			// private method for forcing the view to be focused/not focused
+			var setFocusMethod = typeof (View).GetMethod ("SetHasFocus", BindingFlags.Instance | BindingFlags.NonPublic);
+
+			// when the view is/isn't focused 
+			setFocusMethod.Invoke (tv, new object [] { focused, tv, true });
+
 			tv.Redraw (tv.Bounds);
 
 			string expected = @"
@@ -577,22 +866,56 @@ namespace Terminal.Gui.Views {
 ";
 			GraphViewTests.AssertDriverContentsAre (expected, output);
 
+
 			string expectedColors = @"
 00000
 00000
 00000
 01020
 ";
-			var invertedNormalColor = Application.Driver.MakeAttribute (tv.ColorScheme.Normal.Background, tv.ColorScheme.Normal.Foreground);
-
+			
 			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				tv.ColorScheme.Normal,				
 				// 1
-				invertedNormalColor,				
+				focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal,
 				// 2
 				cellHighlight.Normal});
 
+
+			// change the value in the table so that
+			// it no longer matches the ColorGetter
+			// delegate conditional ( which checks for
+			// the value 2)
+			tv.Table.Rows[0][1] = 5;
+
+			tv.Redraw (tv.Bounds);
+			expected = @"
+┌─┬─┐
+│A│B│
+├─┼─┤
+│1│5│
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+
+			expectedColors = @"
+00000
+00000
+00000
+01000
+";
+
+			// now we only see 2 colors used (the selected cell color and Normal
+			// cellHighlight should no longer be used because the delegate returned null
+			// (now that the cell value is 5 - which does not match the conditional)
+			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
+				// 0
+				tv.ColorScheme.Normal,				
+				// 1
+				focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal });
+
+
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 		}
@@ -615,10 +938,7 @@ namespace Terminal.Gui.Views {
 			tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
 
 			GraphViewTests.InitFakeDriver ();
-			tv.ColorScheme = new ColorScheme () {
-				Normal = Application.Driver.MakeAttribute (Color.White, Color.Black),
-				HotFocus = Application.Driver.MakeAttribute (Color.White, Color.Black)
-			};
+			tv.ColorScheme = Colors.Base;
 			return tv;
 		}
 
@@ -778,6 +1098,141 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 		}
 
+		[Fact]
+		public void LongColumnTest ()
+		{
+			GraphViewTests.InitFakeDriver ();
+
+			var tableView = new TableView ();
+			tableView.ColorScheme = Colors.TopLevel;
+
+			// 25 characters can be printed into table
+			tableView.Bounds = new Rect (0, 0, 25, 5);
+			tableView.Style.ShowHorizontalHeaderUnderline = true;
+			tableView.Style.ShowHorizontalHeaderOverline = false;
+			tableView.Style.AlwaysShowHeaders = true;
+			tableView.Style.SmoothHorizontalScrolling = true;
+
+			var dt = new DataTable ();
+			dt.Columns.Add ("A");
+			dt.Columns.Add ("B");
+			dt.Columns.Add ("Very Long Column");
+
+			dt.Rows.Add (1, 2, new string('a',500));
+			dt.Rows.Add (1, 2, "aaa");
+
+			tableView.Table = dt;
+
+			tableView.Redraw (tableView.Bounds);
+
+			// default behaviour of TableView is not to render
+			// columns unless there is sufficient space
+			string expected = 
+				@"
+│A│B                    │
+├─┼─────────────────────►
+│1│2                    │
+│1│2                    │
+";
+
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			// get a style for the long column
+			var style = tableView.Style.GetOrCreateColumnStyle(dt.Columns[2]);
+			
+			// one way the API user can fix this for long columns
+			// is to specify a max width for the column
+			style.MaxWidth = 10;
+
+			tableView.Redraw (tableView.Bounds);
+			expected = 
+				@"
+│A│B│Very Long          │
+├─┼─┼───────────────────┤
+│1│2│aaaaaaaaaa         │
+│1│2│aaa                │
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			// revert the style change
+			style.MaxWidth = TableView.DefaultMaxCellWidth;
+
+			// another way API user can fix problem is to implement
+			// RepresentationGetter and apply max length there
+
+			style.RepresentationGetter = (s)=>{
+				return s.ToString().Length < 15 ? s.ToString() : s.ToString().Substring(0,13)+"...";
+			};
+
+			tableView.Redraw (tableView.Bounds);
+			expected = 
+				@"
+│A│B│Very Long Column   │
+├─┼─┼───────────────────┤
+│1│2│aaaaaaaaaaaaa...   │
+│1│2│aaa                │
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			// revert style change
+			style.RepresentationGetter = null;
+
+			// Both of the above methods rely on having a fixed
+			// size limit for the column.  These are awkward if a
+			// table is resizeable e.g. Dim.Fill().  Ideally we want
+			// to render in any space available and truncate the content
+			// of the column dynamically so it fills the free space at
+			// the end of the table.
+
+			// We can now specify that the column can be any length
+			// (Up to MaxWidth) but the renderer can accept using
+			// less space down to this limit
+			style.MinAcceptableWidth = 5;
+
+			tableView.Redraw (tableView.Bounds);
+			expected = 
+				@"
+│A│B│Very Long Column   │
+├─┼─┼───────────────────┤
+│1│2│aaaaaaaaaaaaaaaaaaa│
+│1│2│aaa                │
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			// Now test making the width too small for the MinAcceptableWidth
+			// the Column won't fit so should not be rendered
+			Application.Shutdown ();
+			GraphViewTests.InitFakeDriver ();
+
+			tableView.Bounds = new Rect(0,0,9,5);
+			tableView.Redraw (tableView.Bounds);
+			expected =
+@"
+│A│B    │
+├─┼─────►
+│1│2    │
+│1│2    │
+
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			// setting width to 10 leaves just enough space for the column to
+			// meet MinAcceptableWidth of 5.  Column width includes terminator line
+			// symbol (e.g. ┤ or │)
+			tableView.Bounds = new Rect (0, 0, 10, 5);
+			tableView.Redraw (tableView.Bounds);
+			expected =
+@"
+│A│B│Very│
+├─┼─┼────┤
+│1│2│aaaa│
+│1│2│aaa │
+";
+			GraphViewTests.AssertDriverContentsAre (expected, output);
+
+			Application.Shutdown ();
+		}
+
 
 		[Fact]
 		public void ScrollIndicators ()

+ 28 - 10
UnitTests/TextFieldTests.cs

@@ -1181,35 +1181,53 @@ namespace Terminal.Gui.Views {
 
 		[Fact]
 		[AutoInitShutdown]
-		public void Test_RootKeyEvent_Cancel()
+		public void Test_RootKeyEvent_Cancel ()
 		{
 			Application.RootKeyEvent += SuppressKey;
 
-			var tf = new TextField();
+			var tf = new TextField ();
 
 			Application.Top.Add (tf);
 			Application.Begin (Application.Top);
-			
-			Application.Driver.SendKeys('a',ConsoleKey.A,false,false,false);
-			Assert.Equal("a", tf.Text.ToString ());
+
+			Application.Driver.SendKeys ('a', ConsoleKey.A, false, false, false);
+			Assert.Equal ("a", tf.Text.ToString ());
 
 			// SuppressKey suppresses the 'j' key
-			Application.Driver.SendKeys('j',ConsoleKey.A,false,false,false);
-			Assert.Equal("a", tf.Text.ToString ());
+			Application.Driver.SendKeys ('j', ConsoleKey.A, false, false, false);
+			Assert.Equal ("a", tf.Text.ToString ());
 
 			Application.RootKeyEvent -= SuppressKey;
 
 			// Now that the delegate has been removed we can type j again
-			Application.Driver.SendKeys('j',ConsoleKey.A,false,false,false);
-			Assert.Equal("aj", tf.Text.ToString ());
+			Application.Driver.SendKeys ('j', ConsoleKey.A, false, false, false);
+			Assert.Equal ("aj", tf.Text.ToString ());
 		}
 
 		private bool SuppressKey (KeyEvent arg)
 		{
-			if(arg.KeyValue == 'j')
+			if (arg.KeyValue == 'j')
 				return true;
 
 			return false;
 		}
+
+		[Fact, AutoInitShutdown]
+		public void ScrollOffset_Initialize ()
+		{
+			var tf = new TextField ("Testing Scrolls.") {
+				X = 1,
+				Y = 1,
+				Width = 20
+			};
+			Assert.Equal (0, tf.ScrollOffset);
+			Assert.Equal (16, tf.CursorPosition);
+
+			Application.Top.Add (tf);
+			Application.Begin (Application.Top);
+
+			Assert.Equal (0, tf.ScrollOffset);
+			Assert.Equal (16, tf.CursorPosition);
+		}
 	}
 }

文件差异内容过多而无法显示
+ 145 - 34
UnitTests/TextFormatterTests.cs


+ 64 - 4
UnitTests/TextViewTests.cs

@@ -5561,14 +5561,12 @@ line.
 			Assert.Equal (3, tv.Lines);
 			Assert.Equal (new Point (1, 1), tv.CursorPosition);
 
-			// Undo is disabled
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
-			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
+			Assert.Equal ($"This is the {Environment.NewLine}third line.{Environment.NewLine}", tv.Text);
 			Assert.Equal (3, tv.Lines);
-			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
 			Assert.True (tv.IsDirty);
 
-			// Redo is disabled
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
 			Assert.Equal ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
 			Assert.Equal (3, tv.Lines);
@@ -5707,5 +5705,67 @@ line.
 			});
 			Assert.Null (exception);
 		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ScrollDownTillCaretOffscreen_ThenType ()
+		{
+			var tv = new TextView {
+				Width = 10,
+				Height = 5
+			};
+
+			// add 100 lines of wide text to view
+			for (int i = 0; i < 100; i++)
+				tv.Text += new string ('x', 100) + Environment.NewLine;
+
+			Assert.Equal (0, tv.CursorPosition.Y);
+			tv.ScrollTo (50);
+			Assert.Equal (0, tv.CursorPosition.Y);
+
+			tv.ProcessKey (new KeyEvent (Key.p, new KeyModifiers ()));
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void MoveDown_By_Setting_CursorPosition ()
+		{
+			var tv = new TextView {
+				Width = 10,
+				Height = 5
+			};
+
+			// add 100 lines of wide text to view
+			for (int i = 0; i < 100; i++)
+				tv.Text += new string ('x', 100) + (i == 99 ? "" : Environment.NewLine);
+
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			tv.CursorPosition = new Point (5, 50);
+			Assert.Equal (new Point (5, 50), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (200, 200);
+			Assert.Equal (new Point (100, 99), tv.CursorPosition);
+		}
+
+		[Fact]
+		[AutoInitShutdown]
+		public void ScrollTo_CursorPosition ()
+		{
+			var tv = new TextView {
+				Width = 10,
+				Height = 5
+			};
+
+			// add 100 lines of wide text to view
+			for (int i = 0; i < 100; i++)
+				tv.Text += new string ('x', 100) + (i == 99 ? "" : Environment.NewLine);
+
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+			tv.ScrollTo (50);
+			Assert.Equal (new Point (0, 0), tv.CursorPosition);
+
+			tv.CursorPosition = new Point (tv.LeftColumn, tv.TopRow);
+			Assert.Equal (new Point (0, 50), tv.CursorPosition);
+		}
 	}
 }

+ 1 - 0
UnitTests/ToplevelTests.cs

@@ -37,6 +37,7 @@ namespace Terminal.Gui.Core {
 
 			Application.Iteration += () => {
 				if (iterations == 0) {
+					Assert.False (Application.Top.AutoSize);
 					Assert.Equal ("Top1", Application.Top.Text);
 					Assert.Equal (0, Application.Top.Frame.X);
 					Assert.Equal (0, Application.Top.Frame.Y);

+ 1519 - 68
UnitTests/ViewTests.cs

@@ -29,11 +29,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
-			Assert.Equal (Dim.Sized (0), r.Width);
-			Assert.Equal (Dim.Sized (0), r.Height);
-			// FIXED: Pos needs equality implemented
-			Assert.Equal (Pos.At (0), r.X);
-			Assert.Equal (Pos.At (0), r.Y);
+			Assert.Null (r.Width);
+			Assert.Null (r.Height);
+			Assert.Null (r.X);
+			Assert.Null (r.Y);
 			Assert.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
@@ -54,10 +53,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
-			Assert.NotNull (r.Width);       // All view Dim are initialized now,
-			Assert.NotNull (r.Height);      // avoiding Dim errors.
-			Assert.NotNull (r.X);           // All view Pos are initialized now,
-			Assert.NotNull (r.Y);           // avoiding Pos errors.
+			Assert.Null (r.Width);       // All view Dim are initialized now in the IsAdded setter,
+			Assert.Null (r.Height);      // avoiding Dim errors.
+			Assert.Null (r.X);           // All view Pos are initialized now in the IsAdded setter,
+			Assert.Null (r.Y);           // avoiding Pos errors.
 			Assert.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
@@ -78,10 +77,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (1, 2, 3, 4), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
-			Assert.NotNull (r.Width);
-			Assert.NotNull (r.Height);
-			Assert.NotNull (r.X);
-			Assert.NotNull (r.Y);
+			Assert.Null (r.Width);
+			Assert.Null (r.Height);
+			Assert.Null (r.X);
+			Assert.Null (r.Y);
 			Assert.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
@@ -102,10 +101,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (0, 0, 1, 13), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
-			Assert.NotNull (r.Width);       // All view Dim are initialized now,
-			Assert.NotNull (r.Height);      // avoiding Dim errors.
-			Assert.NotNull (r.X);           // All view Pos are initialized now,
-			Assert.NotNull (r.Y);           // avoiding Pos errors.
+			Assert.Null (r.Width);       // All view Dim are initialized now in the IsAdded setter,
+			Assert.Null (r.Height);      // avoiding Dim errors.
+			Assert.Null (r.X);           // All view Pos are initialized now in the IsAdded setter,
+			Assert.Null (r.Y);           // avoiding Pos errors.
 			Assert.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
@@ -1082,19 +1081,19 @@ namespace Terminal.Gui.Core {
 
 			// Default Constructor
 			view = new View ();
-			Assert.Equal (0, view.X);
-			Assert.Equal (0, view.Y);
-			Assert.Equal (0, view.Width);
-			Assert.Equal (0, view.Height);
+			Assert.Null (view.X);
+			Assert.Null (view.Y);
+			Assert.Null (view.Width);
+			Assert.Null (view.Height);
 			Assert.True (view.Frame.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 
 			// Constructor
 			view = new View (1, 2, "");
-			Assert.NotNull (view.X);
-			Assert.NotNull (view.Y);
-			Assert.NotNull (view.Width);
-			Assert.NotNull (view.Height);
+			Assert.Null (view.X);
+			Assert.Null (view.Y);
+			Assert.Null (view.Width);
+			Assert.Null (view.Height);
 			Assert.False (view.Frame.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 
@@ -1176,8 +1175,8 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 		}
 
-		[Fact]
-		public void SetWidth_CanSetWidth ()
+		[Fact, AutoInitShutdown]
+		public void SetWidth_CanSetWidth_ForceValidatePosDim ()
 		{
 			var top = new View () {
 				X = 0,
@@ -1186,7 +1185,8 @@ namespace Terminal.Gui.Core {
 			};
 
 			var v = new View () {
-				Width = Dim.Fill ()
+				Width = Dim.Fill (),
+				ForceValidatePosDim = true
 			};
 			top.Add (v);
 
@@ -1200,8 +1200,12 @@ namespace Terminal.Gui.Core {
 			v.Width = null;
 			Assert.True (v.SetWidth (70, out rWidth));
 			Assert.Equal (70, rWidth);
+			Assert.False (v.IsInitialized);
 
-			v.IsInitialized = true;
+			Application.Top.Add (top);
+			Application.Begin (Application.Top);
+
+			Assert.True (v.IsInitialized);
 			v.Width = Dim.Fill (1);
 			Assert.Throws<ArgumentException> (() => v.Width = 75);
 			v.LayoutStyle = LayoutStyle.Absolute;
@@ -1210,8 +1214,8 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (60, rWidth);
 		}
 
-		[Fact]
-		public void SetHeight_CanSetHeight ()
+		[Fact, AutoInitShutdown]
+		public void SetHeight_CanSetHeight_ForceValidatePosDim ()
 		{
 			var top = new View () {
 				X = 0,
@@ -1220,7 +1224,8 @@ namespace Terminal.Gui.Core {
 			};
 
 			var v = new View () {
-				Height = Dim.Fill ()
+				Height = Dim.Fill (),
+				ForceValidatePosDim = true
 			};
 			top.Add (v);
 
@@ -1234,8 +1239,13 @@ namespace Terminal.Gui.Core {
 			v.Height = null;
 			Assert.True (v.SetHeight (10, out rHeight));
 			Assert.Equal (10, rHeight);
+			Assert.False (v.IsInitialized);
+
+			Application.Top.Add (top);
+			Application.Begin (Application.Top);
+
+			Assert.True (v.IsInitialized);
 
-			v.IsInitialized = true;
 			v.Height = Dim.Fill (1);
 			Assert.Throws<ArgumentException> (() => v.Height = 15);
 			v.LayoutStyle = LayoutStyle.Absolute;
@@ -1258,11 +1268,17 @@ namespace Terminal.Gui.Core {
 			};
 			top.Add (v);
 
-			Assert.False (v.GetCurrentWidth (out int cWidth));
+			Assert.False (v.AutoSize);
+			Assert.True (v.GetCurrentWidth (out int cWidth));
 			Assert.Equal (80, cWidth);
 
 			v.Width = Dim.Fill (1);
-			Assert.False (v.GetCurrentWidth (out cWidth));
+			Assert.True (v.GetCurrentWidth (out cWidth));
+			Assert.Equal (79, cWidth);
+
+			v.AutoSize = true;
+
+			Assert.True (v.GetCurrentWidth (out cWidth));
 			Assert.Equal (79, cWidth);
 		}
 
@@ -1280,14 +1296,70 @@ namespace Terminal.Gui.Core {
 			};
 			top.Add (v);
 
-			Assert.False (v.GetCurrentHeight (out int cHeight));
+			Assert.False (v.AutoSize);
+			Assert.True (v.GetCurrentHeight (out int cHeight));
 			Assert.Equal (20, cHeight);
 
 			v.Height = Dim.Fill (1);
-			Assert.False (v.GetCurrentHeight (out cHeight));
+			Assert.True (v.GetCurrentHeight (out cHeight));
+			Assert.Equal (19, cHeight);
+
+			v.AutoSize = true;
+
+			Assert.True (v.GetCurrentHeight (out cHeight));
 			Assert.Equal (19, cHeight);
 		}
 
+		[Fact]
+		public void AutoSize_False_If_Text_Emmpty ()
+		{
+			var view1 = new View ();
+			var view2 = new View ("");
+			var view3 = new View () { Text = "" };
+
+			Assert.False (view1.AutoSize);
+			Assert.False (view2.AutoSize);
+			Assert.False (view3.AutoSize);
+		}
+
+		[Fact]
+		public void AutoSize_False_If_Text_Is_Not_Emmpty ()
+		{
+			var view1 = new View ();
+			view1.Text = "Hello World";
+			var view2 = new View ("Hello World");
+			var view3 = new View () { Text = "Hello World" };
+
+			Assert.False (view1.AutoSize);
+			Assert.False (view2.AutoSize);
+			Assert.False (view3.AutoSize);
+		}
+
+		[Fact]
+		public void AutoSize_True_Label_If_Text_Emmpty ()
+		{
+			var label1 = new Label ();
+			var label2 = new Label ("");
+			var label3 = new Label () { Text = "" };
+
+			Assert.True (label1.AutoSize);
+			Assert.True (label2.AutoSize);
+			Assert.True (label3.AutoSize);
+		}
+
+		[Fact]
+		public void AutoSize_True_Label_If_Text_Is_Not_Emmpty ()
+		{
+			var label1 = new Label ();
+			label1.Text = "Hello World";
+			var label2 = new Label ("Hello World");
+			var label3 = new Label () { Text = "Hello World" };
+
+			Assert.True (label1.AutoSize);
+			Assert.True (label2.AutoSize);
+			Assert.True (label3.AutoSize);
+		}
+
 		[Fact]
 		public void AutoSize_False_ResizeView_Is_Always_False ()
 		{
@@ -1296,7 +1368,7 @@ namespace Terminal.Gui.Core {
 			label.Text = "New text";
 
 			Assert.False (label.AutoSize);
-			Assert.Equal ("{X=0,Y=0,Width=0,Height=0}", label.Bounds.ToString ());
+			Assert.Equal ("{X=0,Y=0,Width=0,Height=1}", label.Bounds.ToString ());
 		}
 
 		[Fact]
@@ -1310,36 +1382,111 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("{X=0,Y=0,Width=8,Height=1}", label.Bounds.ToString ());
 		}
 
-		[Fact]
-		public void AutoSize_True_ResizeView_With_Dim_Fill ()
+		[Fact, AutoInitShutdown]
+		public void AutoSize_False_ResizeView_With_Dim_Fill_After_IsInitialized ()
 		{
 			var win = new Window (new Rect (0, 0, 30, 80), "");
-			var label = new Label () { Width = Dim.Fill (), Height = Dim.Fill () };
+			var label = new Label () { AutoSize = false, Width = Dim.Fill (), Height = Dim.Fill () };
 			win.Add (label);
+			Application.Top.Add (win);
+
+			// Text is empty so height=0
+			Assert.False (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=0,Height=0}", label.Bounds.ToString ());
 
 			label.Text = "New text\nNew line";
-			win.LayoutSubviews ();
+			Application.Top.LayoutSubviews ();
 
-			Assert.True (label.AutoSize);
+			Assert.False (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=78}", label.Bounds.ToString ());
+			Assert.False (label.IsInitialized);
+
+			Application.Begin (Application.Top);
+			Assert.True (label.IsInitialized);
+			Assert.False (label.AutoSize);
 			Assert.Equal ("{X=0,Y=0,Width=28,Height=78}", label.Bounds.ToString ());
 		}
 
-		[Fact]
-		public void AutoSize_True_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute ()
+		[Fact, AutoInitShutdown]
+		public void AutoSize_False_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute_After_IsAdded_And_IsInitialized ()
 		{
 			var win = new Window (new Rect (0, 0, 30, 80), "");
 			var label = new Label () { Width = Dim.Fill () };
 			win.Add (label);
+			Application.Top.Add (win);
 
-			label.Text = "New text\nNew line";
-			win.LayoutSubviews ();
+			Assert.True (label.IsAdded);
+
+			// Text is empty so height=0
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=0,Height=0}", label.Bounds.ToString ());
+
+			label.Text = "First line\nSecond line";
+			Application.Top.LayoutSubviews ();
 
 			Assert.True (label.AutoSize);
 			Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ());
+			Assert.False (label.IsInitialized);
+
+			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ());
+			Assert.True (label.IsInitialized);
+
+			label.AutoSize = false;
+			Application.Refresh ();
+
+			Assert.False (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=1}", label.Bounds.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_False_SetWidthHeight_With_Dim_Fill_And_Dim_Absolute_With_Initialization ()
+		{
+			var win = new Window (new Rect (0, 0, 30, 80), "");
+			var label = new Label () { Width = Dim.Fill () };
+			win.Add (label);
+			Application.Top.Add (win);
+
+			// Text is empty so height=0
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=0,Height=0}", label.Bounds.ToString ());
+
+			Application.Begin (Application.Top);
+
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=0}", label.Bounds.ToString ());
+
+			label.Text = "First line\nSecond line";
+			Application.Refresh ();
+
+			// Here the AutoSize ensuring the right size
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ());
+
+			label.AutoSize = false;
+			Application.Refresh ();
+
+			// Here the SetMinWidthHeight ensuring the minimum height
+			Assert.False (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=1}", label.Bounds.ToString ());
+
+			label.Text = "First changed line\nSecond changed line\nNew line";
+			Application.Refresh ();
+
+			Assert.False (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=1}", label.Bounds.ToString ());
+
+			label.AutoSize = true;
+			Application.Refresh ();
+
+			Assert.True (label.AutoSize);
+			Assert.Equal ("{X=0,Y=0,Width=28,Height=3}", label.Bounds.ToString ());
 		}
 
 		[Fact, AutoInitShutdown]
-		public void AutoSize_True_Setting_With_Height_Sets_AutoSize_False_Horizontal ()
+		public void AutoSize_True_Setting_With_Height_Horizontal ()
 		{
 			var label = new Label ("Hello") { Width = 10, Height = 2 };
 			var viewX = new View ("X") { X = Pos.Right (label) };
@@ -1348,21 +1495,36 @@ namespace Terminal.Gui.Core {
 			Application.Top.Add (label, viewX, viewY);
 			Application.Begin (Application.Top);
 
-			Assert.False (label.AutoSize);
+			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
 
 			var expected = @"
 Hello     X
-
-Y
+           
+Y          
 ";
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 11, 3), pos);
+
+			label.AutoSize = false;
+			Application.Refresh ();
+
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
+
+			expected = @"
+Hello     X
+           
+Y          
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 11, 3), pos);
 		}
 
 		[Fact, AutoInitShutdown]
-		public void AutoSize_True_Setting_With_Height_Sets_AutoSize_False_Vertical ()
+		public void AutoSize_True_Setting_With_Height_Vertical ()
 		{
 			var label = new Label ("Hello") { Width = 2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight };
 			var viewX = new View ("X") { X = Pos.Right (label) };
@@ -1371,24 +1533,47 @@ Y
 			Application.Top.Add (label, viewX, viewY);
 			Application.Begin (Application.Top);
 
-			Assert.False (label.AutoSize);
+			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 2, 10), label.Frame);
 
 			var expected = @"
 H X
-e
-l
-l
-o
-
+e  
+l  
+l  
+o  
+   
+   
+   
+   
+   
+Y  
+";
 
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 
+			label.AutoSize = false;
+			Application.Refresh ();
 
+			Assert.False (label.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 10), label.Frame);
 
-Y
+			expected = @"
+H X
+e  
+l  
+l  
+o  
+   
+   
+   
+   
+   
+Y  
 ";
 
-			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 		}
 
@@ -1463,11 +1648,16 @@ Y
 			view.SetRelativeLayout (top.Bounds);
 			Assert.Equal (79, view.Bounds.Width);
 			Assert.Equal (24, view.Bounds.Height);
+			view.X = 0;
+			view.Y = 0;
+			view.SetRelativeLayout (top.Bounds);
+			Assert.Equal (80, view.Bounds.Width);
+			Assert.Equal (25, view.Bounds.Height);
 			bool layoutStarted = false;
-			view.LayoutStarted += (_) => { layoutStarted = true; };
+			view.LayoutStarted += (_) => layoutStarted = true;
 			view.OnLayoutStarted (null);
 			Assert.True (layoutStarted);
-			view.LayoutComplete += (_) => { layoutStarted = false; };
+			view.LayoutComplete += (_) => layoutStarted = false;
 			view.OnLayoutComplete (null);
 			Assert.False (layoutStarted);
 			view.X = Pos.Center () - 41;
@@ -1668,13 +1858,12 @@ Y
 			Application.Top.Add (lbl);
 			Application.Begin (Application.Top);
 
+			Assert.True (lbl.AutoSize);
 			Assert.Equal ("123 ", GetContents ());
 
 			lbl.Text = "12";
 
-			if (!lbl.SuperView.NeedDisplay.IsEmpty) {
-				lbl.SuperView.Redraw (lbl.SuperView.NeedDisplay);
-			}
+			lbl.SuperView.Redraw (lbl.SuperView.NeedDisplay);
 
 			Assert.Equal ("12  ", GetContents ());
 
@@ -1974,8 +2163,8 @@ Y
 
 			expected = @"
 ┌──────
-│
-│
+│      
+│      
 └──────
 ";
 
@@ -1984,7 +2173,6 @@ Y
 
 			view.Frame = new Rect (0, 0, 8, 4);
 			((FakeDriver)Application.Driver).SetBufferSize (7, 3);
-
 		}
 
 		[Fact, AutoInitShutdown]
@@ -2352,5 +2540,1268 @@ Y
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (Rect.Empty, pos);
 		}
+
+		[Fact, AutoInitShutdown]
+		public void Width_Height_SetMinWidthHeight_Narrow_Wide_Runes ()
+		{
+			var text = $"First line{Environment.NewLine}Second line";
+			var horizontalView = new View () {
+				Width = 20,
+				Text = text
+			};
+			var verticalView = new View () {
+				Y = 3,
+				Height = 20,
+				Text = text,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = "Window"
+			};
+			win.Add (horizontalView, verticalView);
+			Application.Top.Add (win);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (32, 32);
+
+			Assert.False (horizontalView.AutoSize);
+			Assert.False (verticalView.AutoSize);
+			Assert.Equal (new Rect (0, 0, 20, 1), horizontalView.Frame);
+			Assert.Equal (new Rect (0, 3, 1, 20), verticalView.Frame);
+			var expected = @"
+┌──────────────────────────────┐
+│First line Second li          │
+│                              │
+│                              │
+│F                             │
+│i                             │
+│r                             │
+│s                             │
+│t                             │
+│                              │
+│l                             │
+│i                             │
+│n                             │
+│e                             │
+│                              │
+│S                             │
+│e                             │
+│c                             │
+│o                             │
+│n                             │
+│d                             │
+│                              │
+│l                             │
+│i                             │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+└──────────────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 32, 32), pos);
+
+			verticalView.Text = $"最初の行{Environment.NewLine}二行目";
+			Application.Top.Redraw (Application.Top.Bounds);
+			Assert.Equal (new Rect (0, 3, 2, 20), verticalView.Frame);
+			expected = @"
+┌──────────────────────────────┐
+│First line Second li          │
+│                              │
+│                              │
+│最                            │
+│初                            │
+│の                            │
+│行                            │
+│                              │
+│二                            │
+│行                            │
+│目                            │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+└──────────────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 32, 32), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void TextDirection_Toggle ()
+		{
+			var view = new View ();
+			var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
+			win.Add (view);
+			Application.Top.Add (win);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (22, 22);
+
+			Assert.False (view.AutoSize);
+			Assert.Equal (TextDirection.LeftRight_TopBottom, view.TextDirection);
+			Assert.Equal (Rect.Empty, view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view.Height.ToString ());
+			var expected = @"
+┌────────────────────┐
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.Text = "Hello World";
+			view.Width = 11;
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 11, 1), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│Hello World         │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.AutoSize = true;
+			view.Text = "Hello Worlds";
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 12, 1), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│Hello Worlds        │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.TextDirection = TextDirection.TopBottom_LeftRight;
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 11, 12), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│H                   │
+│e                   │
+│l                   │
+│l                   │
+│o                   │
+│                    │
+│W                   │
+│o                   │
+│r                   │
+│l                   │
+│d                   │
+│s                   │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.AutoSize = false;
+			view.Height = 1;
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 11, 1), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│HelloWorlds         │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.PreserveTrailingSpaces = true;
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 11, 1), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│Hello World         │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.PreserveTrailingSpaces = false;
+			var f = view.Frame;
+			view.Width = f.Height;
+			view.Height = f.Width;
+			view.TextDirection = TextDirection.TopBottom_LeftRight;
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 1, 11), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│H                   │
+│e                   │
+│l                   │
+│l                   │
+│o                   │
+│                    │
+│W                   │
+│o                   │
+│r                   │
+│l                   │
+│d                   │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			view.AutoSize = true;
+			Application.Refresh ();
+
+			Assert.Equal (new Rect (0, 0, 1, 12), view.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", view.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(11)", view.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│H                   │
+│e                   │
+│l                   │
+│l                   │
+│o                   │
+│                    │
+│W                   │
+│o                   │
+│r                   │
+│l                   │
+│d                   │
+│s                   │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Width_Height_AutoSize_True_Stay_True_If_TextFormatter_Size_Fit ()
+		{
+			var text = $"Fi_nish 終";
+			var horizontalView = new View () {
+				AutoSize = true,
+				HotKeySpecifier = '_',
+				Text = text
+			};
+			var verticalView = new View () {
+				Y = 3,
+				AutoSize = true,
+				HotKeySpecifier = '_',
+				Text = text,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Text = "Window"
+			};
+			win.Add (horizontalView, verticalView);
+			Application.Top.Add (win);
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (22, 22);
+
+			Assert.True (horizontalView.AutoSize);
+			Assert.True (verticalView.AutoSize);
+			Assert.Equal (new Size (10, 1), horizontalView.TextFormatter.Size);
+			Assert.Equal (new Size (2, 9), verticalView.TextFormatter.Size);
+			Assert.Equal (new Rect (0, 0, 9, 1), horizontalView.Frame);
+			Assert.Equal ("Pos.Absolute(0)", horizontalView.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", horizontalView.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(9)", horizontalView.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", horizontalView.Height.ToString ());
+			Assert.Equal (new Rect (0, 3, 2, 8), verticalView.Frame);
+			Assert.Equal ("Pos.Absolute(0)", verticalView.X.ToString ());
+			Assert.Equal ("Pos.Absolute(3)", verticalView.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(2)", verticalView.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(8)", verticalView.Height.ToString ());
+			var expected = @"
+┌────────────────────┐
+│Finish 終           │
+│                    │
+│                    │
+│F                   │
+│i                   │
+│n                   │
+│i                   │
+│s                   │
+│h                   │
+│                    │
+│終                  │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+			verticalView.Text = $"最初_の行二行目";
+			Application.Top.Redraw (Application.Top.Bounds);
+			Assert.True (horizontalView.AutoSize);
+			Assert.True (verticalView.AutoSize);
+			// height was initialized with 8 and is kept as minimum
+			Assert.Equal (new Rect (0, 3, 2, 8), verticalView.Frame);
+			Assert.Equal ("Pos.Absolute(0)", verticalView.X.ToString ());
+			Assert.Equal ("Pos.Absolute(3)", verticalView.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(2)", verticalView.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(8)", verticalView.Height.ToString ());
+			expected = @"
+┌────────────────────┐
+│Finish 終           │
+│                    │
+│                    │
+│最                  │
+│初                  │
+│の                  │
+│行                  │
+│二                  │
+│行                  │
+│目                  │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 22, 22), pos);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Stays_True_Center_HotKeySpecifier ()
+		{
+			var btn = new Button () {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				Text = "Say He_llo 你"
+			};
+
+			var win = new Window () {
+				Width = Dim.Fill (),
+				Height = Dim.Fill (),
+				Title = "Test Demo 你"
+			};
+			win.Add (btn);
+			Application.Top.Add (win);
+
+			Assert.True (btn.AutoSize);
+
+			Application.Begin (Application.Top);
+			((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+			var expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│      [ Say Hello 你 ]      │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+
+			Assert.True (btn.AutoSize);
+			btn.Text = "Say He_llo 你 changed";
+			Assert.True (btn.AutoSize);
+			Application.Refresh ();
+			expected = @"
+┌ Test Demo 你 ──────────────┐
+│                            │
+│  [ Say Hello 你 changed ]  │
+│                            │
+└────────────────────────────┘
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
+		}
+
+		[Fact]
+		public void GetTextFormatterBoundsSize_GetBoundsTextFormatterSize_HotKeySpecifier ()
+		{
+			var text = "Say Hello 你";
+			var horizontalView = new View () { Text = text, AutoSize = true, HotKeySpecifier = '_' };
+			var verticalView = new View () {
+				Text = text,
+				AutoSize = true,
+				HotKeySpecifier = '_',
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+
+			Assert.True (horizontalView.AutoSize);
+			Assert.Equal (new Rect (0, 0, 12, 1), horizontalView.Frame);
+			Assert.Equal (new Size (12, 1), horizontalView.GetTextFormatterBoundsSize ());
+			Assert.Equal (new Size (12, 1), horizontalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (horizontalView.TextFormatter.Size, horizontalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (horizontalView.Frame.Size, horizontalView.GetTextFormatterBoundsSize ());
+
+			Assert.True (verticalView.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame);
+			Assert.Equal (new Size (2, 11), verticalView.GetTextFormatterBoundsSize ());
+			Assert.Equal (new Size (2, 11), verticalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (verticalView.TextFormatter.Size, verticalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (verticalView.Frame.Size, verticalView.GetTextFormatterBoundsSize ());
+
+			text = "Say He_llo 你";
+			horizontalView.Text = text;
+			verticalView.Text = text;
+
+			Assert.True (horizontalView.AutoSize);
+			Assert.Equal (new Rect (0, 0, 12, 1), horizontalView.Frame);
+			Assert.Equal (new Size (12, 1), horizontalView.GetTextFormatterBoundsSize ());
+			Assert.Equal (new Size (13, 1), horizontalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (horizontalView.TextFormatter.Size, horizontalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (horizontalView.Frame.Size, horizontalView.GetTextFormatterBoundsSize ());
+
+			Assert.True (verticalView.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame);
+			Assert.Equal (new Size (2, 11), verticalView.GetTextFormatterBoundsSize ());
+			Assert.Equal (new Size (2, 12), verticalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (verticalView.TextFormatter.Size, verticalView.GetBoundsTextFormatterSize ());
+			Assert.Equal (verticalView.Frame.Size, verticalView.GetTextFormatterBoundsSize ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_False_Equal_Before_And_After_IsInitialized_With_Differents_Orders ()
+		{
+			var view1 = new View () { Text = "Say Hello view1 你", AutoSize = false, Width = 10, Height = 5 };
+			var view2 = new View () { Text = "Say Hello view2 你", Width = 10, Height = 5, AutoSize = false };
+			var view3 = new View () { AutoSize = false, Width = 10, Height = 5, Text = "Say Hello view3 你" };
+			var view4 = new View () {
+				Text = "Say Hello view4 你",
+				AutoSize = false,
+				Width = 10,
+				Height = 5,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			var view5 = new View () {
+				Text = "Say Hello view5 你",
+				Width = 10,
+				Height = 5,
+				AutoSize = false,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			var view6 = new View () {
+				AutoSize = false,
+				Width = 10,
+				Height = 5,
+				TextDirection = TextDirection.TopBottom_LeftRight,
+				Text = "Say Hello view6 你",
+			};
+			Application.Top.Add (view1, view2, view3, view4, view5, view6);
+
+			Assert.False (view1.IsInitialized);
+			Assert.False (view2.IsInitialized);
+			Assert.False (view3.IsInitialized);
+			Assert.False (view4.IsInitialized);
+			Assert.False (view5.IsInitialized);
+			Assert.False (view1.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view1.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view1.Height.ToString ());
+			Assert.False (view2.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view2.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view2.Height.ToString ());
+			Assert.False (view3.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view3.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view3.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view3.Height.ToString ());
+			Assert.False (view4.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view4.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view4.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view4.Height.ToString ());
+			Assert.False (view5.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view5.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view5.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view5.Height.ToString ());
+			Assert.False (view6.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view6.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view6.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view6.Height.ToString ());
+
+			Application.Begin (Application.Top);
+
+			Assert.True (view1.IsInitialized);
+			Assert.True (view2.IsInitialized);
+			Assert.True (view3.IsInitialized);
+			Assert.True (view4.IsInitialized);
+			Assert.True (view5.IsInitialized);
+			Assert.False (view1.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view1.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view1.Height.ToString ());
+			Assert.False (view2.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view2.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view2.Height.ToString ());
+			Assert.False (view3.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view3.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view3.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view3.Height.ToString ());
+			Assert.False (view4.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view4.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view4.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view4.Height.ToString ());
+			Assert.False (view5.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view5.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view5.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view5.Height.ToString ());
+			Assert.False (view6.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 5), view6.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view6.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view6.Height.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_True_Equal_Before_And_After_IsInitialized_With_Differents_Orders ()
+		{
+			var view1 = new View () { Text = "Say Hello view1 你", AutoSize = true, Width = 10, Height = 5 };
+			var view2 = new View () { Text = "Say Hello view2 你", Width = 10, Height = 5, AutoSize = true };
+			var view3 = new View () { AutoSize = true, Width = 10, Height = 5, Text = "Say Hello view3 你" };
+			var view4 = new View () {
+				Text = "Say Hello view4 你",
+				AutoSize = true,
+				Width = 10,
+				Height = 5,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			var view5 = new View () {
+				Text = "Say Hello view5 你",
+				Width = 10,
+				Height = 5,
+				AutoSize = true,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			var view6 = new View () {
+				AutoSize = true,
+				Width = 10,
+				Height = 5,
+				TextDirection = TextDirection.TopBottom_LeftRight,
+				Text = "Say Hello view6 你",
+			};
+			Application.Top.Add (view1, view2, view3, view4, view5, view6);
+
+			Assert.False (view1.IsInitialized);
+			Assert.False (view2.IsInitialized);
+			Assert.False (view3.IsInitialized);
+			Assert.False (view4.IsInitialized);
+			Assert.False (view5.IsInitialized);
+			Assert.True (view1.AutoSize);
+			Assert.Equal (new Rect (0, 0, 18, 5), view1.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view1.Height.ToString ());
+			Assert.True (view2.AutoSize);
+			Assert.Equal (new Rect (0, 0, 18, 5), view2.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view2.Height.ToString ());
+			Assert.True (view3.AutoSize);
+			Assert.Equal (new Rect (0, 0, 18, 5), view3.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view3.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view3.Height.ToString ());
+			Assert.True (view4.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 17), view4.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view4.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view4.Height.ToString ());
+			Assert.True (view5.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 17), view5.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view5.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view5.Height.ToString ());
+			Assert.True (view6.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 17), view6.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view6.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view6.Height.ToString ());
+
+			Application.Begin (Application.Top);
+
+			Assert.True (view1.IsInitialized);
+			Assert.True (view2.IsInitialized);
+			Assert.True (view3.IsInitialized);
+			Assert.True (view4.IsInitialized);
+			Assert.True (view5.IsInitialized);
+			Assert.True (view1.AutoSize);
+			Assert.Equal (new Rect (0, 0, 18, 5), view1.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view1.Height.ToString ());
+			Assert.True (view2.AutoSize);
+			Assert.Equal (new Rect (0, 0, 18, 5), view2.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view2.Height.ToString ());
+			Assert.True (view3.AutoSize);
+			Assert.Equal (new Rect (0, 0, 18, 5), view3.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view3.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view3.Height.ToString ());
+			Assert.True (view4.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 17), view4.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view4.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view4.Height.ToString ());
+			Assert.True (view5.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 17), view5.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view5.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view5.Height.ToString ());
+			Assert.True (view6.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 17), view6.Frame);
+			Assert.Equal ("Dim.Absolute(10)", view6.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(5)", view6.Height.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Setting_Frame_Dont_Respect_AutoSize_True_On_Layout_Absolute ()
+		{
+			var view1 = new View (new Rect (0, 0, 10, 0)) { Text = "Say Hello view1 你", AutoSize = true };
+			var view2 = new View (new Rect (0, 0, 0, 10)) {
+				Text = "Say Hello view2 你",
+				AutoSize = true,
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			Application.Top.Add (view1, view2);
+
+			var rs = Application.Begin (Application.Top);
+
+			Assert.True (view1.AutoSize);
+			Assert.Equal (LayoutStyle.Absolute, view1.LayoutStyle);
+			Assert.Equal (new Rect (0, 0, 18, 1), view1.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view1.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view1.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(18)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", view1.Height.ToString ());
+			Assert.True (view2.AutoSize);
+			Assert.Equal (LayoutStyle.Absolute, view2.LayoutStyle);
+			Assert.Equal (new Rect (0, 0, 2, 17), view2.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view2.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view2.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(2)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(17)", view2.Height.ToString ());
+
+			view1.Frame = new Rect (0, 0, 25, 4);
+			bool firstIteration = false;
+			Application.RunMainLoopIteration (ref rs, true, ref firstIteration);
+
+			Assert.True (view1.AutoSize);
+			Assert.Equal (LayoutStyle.Absolute, view1.LayoutStyle);
+			Assert.Equal (new Rect (0, 0, 25, 4), view1.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view1.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view1.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(18)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(1)", view1.Height.ToString ());
+
+			view2.Frame = new Rect (0, 0, 1, 25);
+			Application.RunMainLoopIteration (ref rs, true, ref firstIteration);
+
+			Assert.True (view2.AutoSize);
+			Assert.Equal (LayoutStyle.Absolute, view2.LayoutStyle);
+			Assert.Equal (new Rect (0, 0, 1, 25), view2.Frame);
+			Assert.Equal ("Pos.Absolute(0)", view2.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view2.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(2)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(17)", view2.Height.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Pos_Dim_Are_Null_If_Not_Initialized_On_Constructor_IsAdded_False ()
+		{
+			var top = Application.Top;
+			var view1 = new View ();
+			Assert.False (view1.IsAdded);
+			Assert.Null (view1.X);
+			Assert.Null (view1.Y);
+			Assert.Null (view1.Width);
+			Assert.Null (view1.Height);
+			top.Add (view1);
+			Assert.True (view1.IsAdded);
+			Assert.Equal ("Pos.Absolute(0)", view1.X.ToString ());
+			Assert.Equal ("Pos.Absolute(0)", view1.Y.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view1.Width.ToString ());
+			Assert.Equal ("Dim.Absolute(0)", view1.Height.ToString ());
+
+			var view2 = new View () {
+				X = Pos.Center (),
+				Y = Pos.Center (),
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
+			};
+			Assert.False (view2.IsAdded);
+			Assert.Equal ("Pos.Center", view2.X.ToString ());
+			Assert.Equal ("Pos.Center", view2.Y.ToString ());
+			Assert.Equal ("Dim.Fill(margin=0)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Fill(margin=0)", view2.Height.ToString ());
+			top.Add (view2);
+			Assert.True (view2.IsAdded);
+			Assert.Equal ("Pos.Center", view2.X.ToString ());
+			Assert.Equal ("Pos.Center", view2.Y.ToString ());
+			Assert.Equal ("Dim.Fill(margin=0)", view2.Width.ToString ());
+			Assert.Equal ("Dim.Fill(margin=0)", view2.Height.ToString ());
+		}
+
+		[Fact]
+		public void IsAdded_Added_Removed ()
+		{
+			var top = new Toplevel ();
+			var view = new View ();
+			Assert.False (view.IsAdded);
+			top.Add (view);
+			Assert.True (view.IsAdded);
+			top.Remove (view);
+			Assert.False (view.IsAdded);
+		}
+
+		[Fact]
+		public void AutoSize_Layout_Absolute_Without_Add_Horizontal_Narrow ()
+		{
+			var view = new View (new Rect (0, 0, 10, 1)) {
+				Text = "Test"
+			};
+
+			Assert.False (view.IsAdded);
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("Test", view.TextFormatter.Text);
+
+			view.Text = "First line\nSecond line";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 11, 2), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 11, 2), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Layout_Absolute_With_Add_Horizontal_Narrow ()
+		{
+			var view = new View (new Rect (0, 0, 10, 1)) {
+				Text = "Test"
+			};
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			Assert.True (view.IsAdded);
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("Test", view.TextFormatter.Text);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Test
+", output);
+
+			view.Text = "First line\nSecond line";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+First line
+", output);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 11, 2), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+First line 
+Second line
+", output);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+First line
+", output);
+		}
+
+		[Fact]
+		public void AutoSize_Layout_Absolute_Without_Add_Vertical_Narrow ()
+		{
+			var view = new View (new Rect (0, 0, 1, 10)) {
+				Text = "Test",
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+
+			Assert.False (view.IsAdded);
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
+			Assert.Equal ("Test", view.TextFormatter.Text);
+
+			view.Text = "First line\nSecond line";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 11), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 11), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Layout_Absolute_With_Add_Vertical_Narrow ()
+		{
+			var view = new View (new Rect (0, 0, 1, 10)) {
+				Text = "Test",
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			Assert.True (view.IsAdded);
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
+			Assert.Equal ("Test", view.TextFormatter.Text);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+T
+e
+s
+t
+", output);
+
+			view.Text = "First line\nSecond line";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+F
+i
+r
+s
+t
+ 
+l
+i
+n
+e
+", output);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 11), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+FS
+ie
+rc
+so
+tn
+ d
+l 
+il
+ni
+en
+ e
+", output);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 1, 10), view.Frame);
+			Assert.Equal ("First line\nSecond line", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+F
+i
+r
+s
+t
+ 
+l
+i
+n
+e
+", output);
+		}
+
+		[Fact]
+		public void AutoSize_Layout_Absolute_Without_Add_Horizontal_Wide ()
+		{
+			var view = new View (new Rect (0, 0, 10, 1)) {
+				Text = "Test 你"
+			};
+
+			Assert.False (view.IsAdded);
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("Test 你", view.TextFormatter.Text);
+
+			view.Text = "First line 你\nSecond line 你";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 14, 2), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 14, 2), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Layout_Absolute_With_Add_Horizontal_Wide ()
+		{
+			var view = new View (new Rect (0, 0, 10, 1)) {
+				Text = "Test 你"
+			};
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			Assert.True (view.IsAdded);
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("Test 你", view.TextFormatter.Text);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+Test 你
+", output);
+
+			view.Text = "First line 你\nSecond line 你";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+First line
+", output);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 14, 2), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+First line 你 
+Second line 你
+", output);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 10, 1), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+First line
+", output);
+		}
+
+		[Fact]
+		public void AutoSize_Layout_Absolute_Without_Add_Vertical_Wide ()
+		{
+			var view = new View (new Rect (0, 0, 1, 10)) {
+				Text = "Test 你",
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+
+			Assert.False (view.IsAdded);
+			Assert.False (view.AutoSize);
+			// SetMinWidthHeight ensuring the minimum width for the wide char
+			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
+			Assert.Equal ("Test 你", view.TextFormatter.Text);
+
+			view.Text = "First line 你\nSecond line 你";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 4, 13), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 4, 13), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void AutoSize_Layout_Absolute_With_Add_Vertical_Wide ()
+		{
+			var view = new View (new Rect (0, 0, 1, 10)) {
+				Text = "Test 你",
+				TextDirection = TextDirection.TopBottom_LeftRight
+			};
+			Application.Top.Add (view);
+			Application.Begin (Application.Top);
+
+			Assert.True (view.IsAdded);
+			Assert.False (view.AutoSize);
+			// SetMinWidthHeight ensuring the minimum width for the wide char
+			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
+			Assert.Equal ("Test 你", view.TextFormatter.Text);
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+T 
+e 
+s 
+t 
+  
+你
+", output);
+
+			view.Text = "First line 你\nSecond line 你";
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+F
+i
+r
+s
+t
+ 
+l
+i
+n
+e
+", output);
+
+			view.AutoSize = true;
+			Assert.True (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 4, 13), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+F S 
+i e 
+r c 
+s o 
+t n 
+  d 
+l   
+i l 
+n i 
+e n 
+  e 
+你  
+  你
+", output);
+
+			view.AutoSize = false;
+			Assert.False (view.AutoSize);
+			Assert.Equal (new Rect (0, 0, 2, 10), view.Frame);
+			Assert.Equal ("First line 你\nSecond line 你", view.TextFormatter.Text);
+			Application.Refresh ();
+			GraphViewTests.AssertDriverContentsWithFrameAre (@"
+F
+i
+r
+s
+t
+ 
+l
+i
+n
+e
+", output);
+		}
+
 	}
 }

+ 157 - 0
UnitTests/WindowTests.cs

@@ -0,0 +1,157 @@
+using System;
+using Xunit;
+using Xunit.Abstractions;
+using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
+
+// Alias Console to MockConsole so we don't accidentally use Console
+using Console = Terminal.Gui.FakeConsole;
+using NStack;
+
+namespace Terminal.Gui.Core {
+	public class WindowTests {
+		readonly ITestOutputHelper output;
+
+		public WindowTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		[Fact]
+		public void New_Initializes ()
+		{
+			// Parameterless
+			var r = new Window ();
+			Assert.NotNull (r);
+			Assert.Equal(ustring.Empty, r.Title);
+			Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
+			Assert.Equal ("Window()({X=0,Y=0,Width=0,Height=0})", r.ToString ());
+			Assert.True (r.CanFocus);
+			Assert.False (r.HasFocus);
+			Assert.Equal (new Rect (0, 0, 0, 0), r.Bounds);
+			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
+			Assert.Null (r.Focused);
+			Assert.NotNull (r.ColorScheme);
+			Assert.Equal (Dim.Fill (0), r.Width);
+			Assert.Equal (Dim.Fill (0), r.Height);
+			Assert.Null (r.X);
+			Assert.Null (r.Y);
+			Assert.False (r.IsCurrentTop);
+			Assert.Empty (r.Id);
+			Assert.NotEmpty (r.Subviews);
+			Assert.False (r.WantContinuousButtonPressed);
+			Assert.False (r.WantMousePositionReports);
+			Assert.Null (r.SuperView);
+			Assert.Null (r.MostFocused);
+			Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
+
+			// Empty Rect
+			r = new Window (Rect.Empty, "title");
+			Assert.NotNull (r);
+			Assert.Equal ("title", r.Title);
+			Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+			Assert.Equal ("Window()({X=0,Y=0,Width=0,Height=0})", r.ToString ());
+			Assert.True (r.CanFocus);
+			Assert.False (r.HasFocus);
+			Assert.Equal (new Rect (0, 0, 0, 0), r.Bounds);
+			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
+			Assert.Null (r.Focused);
+			Assert.NotNull (r.ColorScheme);
+			Assert.Null (r.Width);       // All view Dim are initialized now in the IsAdded setter,
+			Assert.Null (r.Height);      // avoiding Dim errors.
+			Assert.Null (r.X);           // All view Pos are initialized now in the IsAdded setter,
+			Assert.Null (r.Y);           // avoiding Pos errors.
+			Assert.False (r.IsCurrentTop);
+			Assert.Empty (r.Id);
+			Assert.NotEmpty (r.Subviews);
+			Assert.False (r.WantContinuousButtonPressed);
+			Assert.False (r.WantMousePositionReports);
+			Assert.Null (r.SuperView);
+			Assert.Null (r.MostFocused);
+			Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
+
+			// Rect with values
+			r = new Window (new Rect (1, 2, 3, 4), "title");
+			Assert.Equal ("title", r.Title);
+			Assert.NotNull (r);
+			Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+			Assert.Equal ("Window()({X=1,Y=2,Width=3,Height=4})", r.ToString ());
+			Assert.True (r.CanFocus);
+			Assert.False (r.HasFocus);
+			Assert.Equal (new Rect (0, 0, 3, 4), r.Bounds);
+			Assert.Equal (new Rect (1, 2, 3, 4), r.Frame);
+			Assert.Null (r.Focused);
+			Assert.NotNull (r.ColorScheme);
+			Assert.Null (r.Width);
+			Assert.Null (r.Height);
+			Assert.Null (r.X);
+			Assert.Null (r.Y);
+			Assert.False (r.IsCurrentTop);
+			Assert.Empty (r.Id);
+			Assert.NotEmpty (r.Subviews);
+			Assert.False (r.WantContinuousButtonPressed);
+			Assert.False (r.WantMousePositionReports);
+			Assert.Null (r.SuperView);
+			Assert.Null (r.MostFocused);
+			Assert.Equal (TextDirection.LeftRight_TopBottom, r.TextDirection);
+			r.Dispose ();
+		}
+
+		[Fact]
+		public void Set_Title_Fires_TitleChanging ()
+		{
+			var r = new Window ();
+			Assert.Equal (ustring.Empty, r.Title);
+
+			string expectedOld = null;
+			string expectedDuring = null;
+			string expectedAfter = null;
+			bool cancel = false;
+			r.TitleChanging += (args) => {
+				Assert.Equal (expectedOld, args.OldTitle);
+				Assert.Equal (expectedDuring, args.NewTitle);
+				args.Cancel = cancel;
+			};
+
+			expectedOld = string.Empty;
+			r.Title = expectedDuring = expectedAfter = "title";
+			Assert.Equal (expectedAfter, r.Title.ToString ());
+
+			expectedOld = r.Title.ToString();
+			r.Title = expectedDuring = expectedAfter = "a different title";
+			Assert.Equal (expectedAfter, r.Title.ToString ());
+
+			// Now setup cancelling the change and change it back to "title"
+			cancel = true;
+			expectedOld = r.Title.ToString();
+			r.Title = expectedDuring = "title";
+			Assert.Equal (expectedAfter, r.Title.ToString ());
+			r.Dispose ();
+
+		}
+
+		[Fact]
+		public void Set_Title_Fires_TitleChanged ()
+		{
+			var r = new Window ();
+			Assert.Equal (ustring.Empty, r.Title);
+
+			string expectedOld = null;
+			string expected = null;
+			r.TitleChanged += (args) => {
+				Assert.Equal (expectedOld, args.OldTitle);
+				Assert.Equal (r.Title, args.NewTitle);
+			};
+
+			expected = "title";
+			expectedOld = r.Title.ToString ();
+			r.Title = expected;
+			Assert.Equal (expected, r.Title.ToString ());
+
+			expected = "another title";
+			expectedOld = r.Title.ToString ();
+			r.Title = expected;
+			Assert.Equal (expected, r.Title.ToString ());
+			r.Dispose ();
+		}
+	}
+}

+ 610 - 0
UnitTests/WizardTests.cs

@@ -0,0 +1,610 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Threading.Tasks;
+using Terminal.Gui;
+using Xunit;
+using System.Globalization;
+using Xunit.Abstractions;
+using NStack;
+
+namespace Terminal.Gui.Views {
+
+	public class WizardTests {
+		readonly ITestOutputHelper output;
+
+		public WizardTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
+		private void RunButtonTestWizard (string title, int width, int height)
+		{
+			var wizard = new Wizard (title) { Width = width, Height = height };
+			Application.End (Application.Begin (wizard));
+		}
+
+		// =========== WizardStep Tests
+
+		[Fact, AutoInitShutdown]
+		public void WizardStep_ButtonText ()
+		{
+			// Verify default button text
+
+			// Verify set actually changes property
+
+			// Verify set actually changes buttons for the current step
+		}
+
+
+		[Fact]
+		public void WizardStep_Set_Title_Fires_TitleChanging ()
+		{
+			var r = new Window ();
+			Assert.Equal (ustring.Empty, r.Title);
+
+			string expectedAfter = string.Empty;
+			string expectedDuring = string.Empty;
+			bool cancel = false;
+			r.TitleChanging += (args) => {
+				Assert.Equal (expectedDuring, args.NewTitle);
+				args.Cancel = cancel;
+			};
+
+			r.Title = expectedDuring = expectedAfter = "title";
+			Assert.Equal (expectedAfter, r.Title.ToString ());
+
+			r.Title = expectedDuring = expectedAfter = "a different title";
+			Assert.Equal (expectedAfter, r.Title.ToString ());
+
+			// Now setup cancelling the change and change it back to "title"
+			cancel = true;
+			r.Title = expectedDuring = "title";
+			Assert.Equal (expectedAfter, r.Title.ToString ());
+			r.Dispose ();
+
+		}
+
+		[Fact]
+		public void WizardStep_Set_Title_Fires_TitleChanged ()
+		{
+			var r = new Window ();
+			Assert.Equal (ustring.Empty, r.Title);
+
+			string expected = string.Empty;
+			r.TitleChanged += (args) => {
+				Assert.Equal (r.Title, args.NewTitle);
+			};
+
+			expected = "title";
+			r.Title = expected;
+			Assert.Equal (expected, r.Title.ToString ());
+
+			expected = "another title";
+			r.Title = expected;
+			Assert.Equal (expected, r.Title.ToString ());
+			r.Dispose ();
+
+		}
+
+		// =========== Wizard Tests
+		[Fact, AutoInitShutdown]
+		public void DefaultConstructor_SizedProperly ()
+		{
+			var d = ((FakeDriver)Application.Driver);
+
+			var wizard = new Wizard ();
+			Assert.NotEqual (0, wizard.Width);
+			Assert.NotEqual (0, wizard.Height);
+		}
+
+		[Fact, AutoInitShutdown]
+		// Verify a zero-step wizard doesn't crash and shows a blank wizard
+		// and that the title is correct
+		public void ZeroStepWizard_Shows ()
+		{
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			var stepTitle = "";
+
+			int width = 30;
+			int height = 6;
+			d.SetBufferSize (width, height);
+
+			var btnBackText = "Back";
+			var btnBack = $"{d.LeftBracket} {btnBackText} {d.RightBracket}";
+			var btnNextText = "Finish";
+			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
+
+			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var row3 = row2;
+			var separatorRow = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+
+			var wizard = new Wizard (title) { Width = width, Height = height };
+			Application.End (Application.Begin (wizard));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		// This test verifies that a single step wizard shows the correct buttons
+		// and that the title is correct
+		public void OneStepWizard_Shows ()
+		{
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			var stepTitle = "ABCD";
+
+			int width = 30;
+			int height = 7;
+			d.SetBufferSize (width, height);
+
+			//	var btnBackText = "Back";
+			var btnBack = string.Empty; // $"{d.LeftBracket} {btnBackText} {d.RightBracket}";
+			var btnNextText = "Finish"; // "Next";
+			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
+
+			var topRow = $"{d.ULDCorner} {title} - {stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 7)}{d.URDCorner}";
+			var row2 = $"{d.VDLine}{new String (' ', width - 2)}{d.VDLine}";
+			var row3 = row2;
+			var row4 = row3;
+			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+			var buttonRow = $"{d.VDLine}{btnBack}{new String (' ', width - btnBack.Length - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+
+			var wizard = new Wizard (title) { Width = width, Height = height };
+			wizard.AddStep (new Wizard.WizardStep (stepTitle));
+			//wizard.LayoutSubviews ();
+			var firstIteration = false;
+			var runstate = Application.Begin (wizard);
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{row2}\n{row3}\n{row4}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+			Application.End (runstate);
+		}
+
+		[Fact, AutoInitShutdown]
+		// This test verifies that the 2nd step in a wizard with 2 steps 
+		// shows the correct buttons on both steps
+		// and that the title is correct
+		public void TwoStepWizard_Next_Shows_SecondStep ()
+		{
+			// verify step one
+
+			// Next
+
+			// verify step two
+
+			// Back
+
+			// verify step one again
+		}
+
+		[Fact, AutoInitShutdown]
+		// This test verifies that the 2nd step in a wizard with more than 2 steps 
+		// shows the correct buttons on all steps
+		// and that the title is correct
+		public void ThreeStepWizard_Next_Shows_Steps ()
+		{
+
+			// verify step one
+
+			// Next
+
+			// verify step two
+
+			// Back
+
+			// verify step one again
+		}
+
+		[Fact, AutoInitShutdown]
+		// this test is needed because Wizard overrides Dialog's title behavior ("Title - StepTitle")
+		public void Setting_Title_Works ()
+		{
+			var d = ((FakeDriver)Application.Driver);
+
+			var title = "1234";
+			var stepTitle = " - ABCD";
+
+			int width = 40;
+			int height = 4;
+			d.SetBufferSize (width, height);
+
+			var btnNextText = "Finish";
+			var btnNext = $"{d.LeftBracket}{d.LeftDefaultIndicator} {btnNextText} {d.RightDefaultIndicator}{d.RightBracket}";
+
+			var topRow = $"{d.ULDCorner} {title}{stepTitle} {new String (d.HDLine.ToString () [0], width - title.Length - stepTitle.Length - 4)}{d.URDCorner}";
+			var separatorRow = $"{d.VDLine}{new String (d.HLine.ToString () [0], width - 2)}{d.VDLine}";
+
+			// Once this is fixed, revert to commented out line: https://github.com/migueldeicaza/gui.cs/issues/1791
+			var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			//var buttonRow = $"{d.VDLine}{new String (' ', width - btnNext.Length - 2)}{btnNext}{d.VDLine}";
+			var bottomRow = $"{d.LLDCorner}{new String (d.HDLine.ToString () [0], width - 2)}{d.LRDCorner}";
+
+			var wizard = new Wizard (title) { Width = width, Height = height };
+			wizard.AddStep (new Wizard.WizardStep ("ABCD"));
+
+			Application.End (Application.Begin (wizard));
+			GraphViewTests.AssertDriverContentsWithFrameAre ($"{topRow}\n{separatorRow}\n{buttonRow}\n{bottomRow}", output);
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Navigate_GetPreviousStep_Correct ()
+		{
+			var wizard = new Wizard ();
+
+			// If no steps should be null
+			Assert.Null (wizard.GetPreviousStep ());
+
+			var step1 = new Wizard.WizardStep ("step1");
+			wizard.AddStep (step1);
+
+			// If no current step, should be last step
+			Assert.Equal (step1.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			wizard.CurrentStep = step1;
+			// If there is 1 step it's current step should be null
+			Assert.Null (wizard.GetPreviousStep ());
+
+			// If one disabled step should be null
+			step1.Enabled = false;
+			Assert.Null (wizard.GetPreviousStep ());
+
+			// If two steps and at 2 and step 1 is `Enabled = true`should be step1
+			var step2 = new Wizard.WizardStep ("step2");
+			wizard.AddStep (step2);
+			wizard.CurrentStep = step2;
+			step1.Enabled = true;
+			Assert.Equal (step1.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			// If two steps and at 2 and step 1 is `Enabled = false` should be null
+			step1.Enabled = false;
+			Assert.Null (wizard.GetPreviousStep ());
+
+			// If three steps with Step2.Enabled = true
+			//   At step 1 should be null
+			//   At step 2 should be step 1
+			//   At step 3 should be step 2
+			var step3 = new Wizard.WizardStep ("step3");
+			wizard.AddStep (step3);
+			step1.Enabled = true;
+			wizard.CurrentStep = step1;
+			step2.Enabled = true;
+			step3.Enabled = true;
+			Assert.Null (wizard.GetPreviousStep ());
+			wizard.CurrentStep = step2;
+			Assert.Equal (step1.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+			wizard.CurrentStep = step3;
+			Assert.Equal (step2.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			// If three steps with Step2.Enabled = false
+			//   At step 1 should be null
+			//   At step 3 should be step1
+			step1.Enabled = true;
+			step2.Enabled = false;
+			step3.Enabled = true;
+			wizard.CurrentStep = step1;
+			Assert.Null (wizard.GetPreviousStep ());
+			wizard.CurrentStep = step3;
+			Assert.Equal (step1.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			// If three steps with Step1.Enabled = false & Step2.Enabled = false
+			//   At step 3 should be null
+
+			// If no current step, GetPreviousStep provides equivalent to GetLastStep
+			wizard.CurrentStep = null;
+			step1.Enabled = true;
+			step2.Enabled = true;
+			step3.Enabled = true;
+			Assert.Equal (step3.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = true;
+			step3.Enabled = true;
+			Assert.Equal (step3.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = false;
+			step3.Enabled = true;
+			Assert.Equal (step3.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = true;
+			step3.Enabled = false;
+			Assert.Equal (step2.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+
+			step1.Enabled = true;
+			step2.Enabled = false;
+			step3.Enabled = false;
+			Assert.Equal (step1.Title.ToString (), wizard.GetPreviousStep ().Title.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Navigate_GetNextStep_Correct ()
+		{
+			var wizard = new Wizard ();
+
+			// If no steps should be null
+			Assert.Null (wizard.GetNextStep ());
+
+			var step1 = new Wizard.WizardStep ("step1");
+			wizard.AddStep (step1);
+
+			// If no current step, should be first step
+			Assert.Equal (step1.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+
+			wizard.CurrentStep = step1;
+			// If there is 1 step it's current step should be null
+			Assert.Null (wizard.GetNextStep ());
+
+			// If one disabled step should be null
+			step1.Enabled = false;
+			Assert.Null (wizard.GetNextStep ());
+
+			// If two steps and at 1 and step 2 is `Enabled = true`should be step 2
+			var step2 = new Wizard.WizardStep ("step2");
+			wizard.AddStep (step2);
+			Assert.Equal (step2.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+
+			// If two steps and at 1 and step 2 is `Enabled = false` should be null
+			step1.Enabled = true;
+			wizard.CurrentStep = step1;
+			step2.Enabled = false;
+			Assert.Null (wizard.GetNextStep ());
+
+			// If three steps with Step2.Enabled = true
+			//   At step 1 should be step 2
+			//   At step 2 should be step 3
+			//   At step 3 should be null
+			var step3 = new Wizard.WizardStep ("step3");
+			wizard.AddStep (step3);
+			step1.Enabled = true;
+			wizard.CurrentStep = step1;
+			step2.Enabled = true;
+			step3.Enabled = true;
+			Assert.Equal (step2.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+			wizard.CurrentStep = step2;
+			Assert.Equal (step3.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+			wizard.CurrentStep = step3;
+			Assert.Null (wizard.GetNextStep ());
+
+			// If three steps with Step2.Enabled = false
+			//   At step 1 should be step 3
+			//   At step 3 should be null
+			step1.Enabled = true;
+			wizard.CurrentStep = step1;
+			step2.Enabled = false;
+			step3.Enabled = true;
+			Assert.Equal (step3.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+			wizard.CurrentStep = step3;
+			Assert.Null (wizard.GetNextStep ());
+
+			// If three steps with Step2.Enabled = false & Step3.Enabled = false
+			//   At step 1 should be null
+			step1.Enabled = true;
+			wizard.CurrentStep = step1;
+			step2.Enabled = false;
+			step3.Enabled = false;
+			Assert.Null (wizard.GetNextStep ());
+
+			// If no current step, GetNextStep provides equivalent to GetFirstStep
+			wizard.CurrentStep = null;
+			step1.Enabled = true;
+			step2.Enabled = true;
+			step3.Enabled = true;
+			Assert.Equal (step1.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = true;
+			step3.Enabled = true;
+			Assert.Equal (step2.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = false;
+			step3.Enabled = true;
+			Assert.Equal (step3.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = true;
+			step3.Enabled = false;
+			Assert.Equal (step2.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+
+			step1.Enabled = true;
+			step2.Enabled = false;
+			step3.Enabled = false;
+			Assert.Equal (step1.Title.ToString (), wizard.GetNextStep ().Title.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Navigate_GoNext_Works ()
+		{
+			// If zero steps do nothing
+
+			// If one step do nothing (enabled or disabled)
+
+			// If two steps
+			//    If current is 1
+			//        If 2 is enabled 2 becomes current
+			//        If 2 is disabled 1 stays current
+			//    If current is 2 does nothing
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Navigate_GoBack_Works ()
+		{
+			// If zero steps do nothing
+
+			// If one step do nothing (enabled or disabled)
+
+			// If two steps
+			//    If current is 1 does nothing
+			//    If current is 2 does nothing
+			//        If 1 is enabled 2 becomes current
+			//        If 1 is disabled 1 stays current
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Navigate_GetFirstStep_Works ()
+		{
+			var wizard = new Wizard ();
+
+			Assert.Null (wizard.GetFirstStep ());
+
+			var step1 = new Wizard.WizardStep ("step1");
+			wizard.AddStep (step1);
+			Assert.Equal (step1.Title.ToString (), wizard.GetFirstStep ().Title.ToString ());
+
+			var step2 = new Wizard.WizardStep ("step2");
+			wizard.AddStep (step2);
+			Assert.Equal (step1.Title.ToString (), wizard.GetFirstStep ().Title.ToString ());
+
+			var step3 = new Wizard.WizardStep ("step3");
+			wizard.AddStep (step3);
+			Assert.Equal (step1.Title.ToString (), wizard.GetFirstStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			Assert.Equal (step2.Title.ToString (), wizard.GetFirstStep ().Title.ToString ());
+
+			step1.Enabled = true;
+			Assert.Equal (step1.Title.ToString (), wizard.GetFirstStep ().Title.ToString ());
+
+			step1.Enabled = false;
+			step2.Enabled = false;
+			Assert.Equal (step3.Title.ToString (), wizard.GetFirstStep ().Title.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Navigate_GetLastStep_Works ()
+		{
+			var wizard = new Wizard ();
+
+			Assert.Null (wizard.GetLastStep ());
+
+			var step1 = new Wizard.WizardStep ("step1");
+			wizard.AddStep (step1);
+			Assert.Equal (step1.Title.ToString (), wizard.GetLastStep ().Title.ToString ());
+
+			var step2 = new Wizard.WizardStep ("step2");
+			wizard.AddStep (step2);
+			Assert.Equal (step2.Title.ToString (), wizard.GetLastStep ().Title.ToString ());
+
+			var step3 = new Wizard.WizardStep ("step3");
+			wizard.AddStep (step3);
+			Assert.Equal (step3.Title.ToString (), wizard.GetLastStep ().Title.ToString ());
+
+			step3.Enabled = false;
+			Assert.Equal (step2.Title.ToString (), wizard.GetLastStep ().Title.ToString ());
+
+			step3.Enabled = true;
+			Assert.Equal (step3.Title.ToString (), wizard.GetLastStep ().Title.ToString ());
+
+			step3.Enabled = false;
+			step2.Enabled = false;
+			Assert.Equal (step1.Title.ToString (), wizard.GetLastStep ().Title.ToString ());
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Finish_Button_Closes ()
+		{
+			// https://github.com/migueldeicaza/gui.cs/issues/1833
+			var wizard = new Wizard ();
+			var step1 = new Wizard.WizardStep ("step1") { };
+			wizard.AddStep (step1);
+
+			var finishedFired = false;
+			wizard.Finished += (args) => {
+				finishedFired = true;
+			};
+
+			var closedFired = false;
+			wizard.Closed += (args) => {
+				closedFired = true;
+			};
+
+			var runstate = Application.Begin (wizard);
+			var firstIteration = true;
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+
+			wizard.NextFinishButton.OnClicked ();
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+			Application.End (runstate);
+			Assert.True (finishedFired);
+			Assert.True (closedFired);
+			step1.Dispose ();
+			wizard.Dispose ();
+
+			// Same test, but with two steps
+			wizard = new Wizard ();
+			firstIteration = false;
+			step1 = new Wizard.WizardStep ("step1") { };
+			wizard.AddStep (step1);
+			var step2 = new Wizard.WizardStep ("step2") { };
+			wizard.AddStep (step2);
+
+			finishedFired = false;
+			wizard.Finished += (args) => {
+				finishedFired = true;
+			};
+
+			closedFired = false;
+			wizard.Closed += (args) => {
+				closedFired = true;
+			};
+
+			runstate = Application.Begin (wizard);
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+
+			Assert.Equal (step1.Title.ToString(), wizard.CurrentStep.Title.ToString());
+			wizard.NextFinishButton.OnClicked ();
+			Assert.False (finishedFired);
+			Assert.False (closedFired);
+
+			Assert.Equal (step2.Title.ToString (), wizard.CurrentStep.Title.ToString ());
+			Assert.Equal (wizard.GetLastStep().Title.ToString(), wizard.CurrentStep.Title.ToString ());
+			wizard.NextFinishButton.OnClicked ();
+			Application.End (runstate);
+			Assert.True (finishedFired);
+			Assert.True (closedFired);
+
+			step1.Dispose ();
+			step2.Dispose ();
+			wizard.Dispose ();
+
+			// Same test, but with two steps but the 1st one disabled
+			wizard = new Wizard ();
+			firstIteration = false;
+			step1 = new Wizard.WizardStep ("step1") { };
+			wizard.AddStep (step1);
+			step2 = new Wizard.WizardStep ("step2") { };
+			wizard.AddStep (step2);
+			step1.Enabled = false;
+
+			finishedFired = false;
+			wizard.Finished += (args) => {
+				finishedFired = true;
+			};
+
+			closedFired = false;
+			wizard.Closed += (args) => {
+				closedFired = true;
+			};
+
+			runstate = Application.Begin (wizard);
+			Application.RunMainLoopIteration (ref runstate, true, ref firstIteration);
+
+			Assert.Equal (step2.Title.ToString (), wizard.CurrentStep.Title.ToString ());
+			Assert.Equal (wizard.GetLastStep ().Title.ToString (), wizard.CurrentStep.Title.ToString ());
+			wizard.NextFinishButton.OnClicked ();
+			Application.End (runstate);
+			Assert.True (finishedFired);
+			Assert.True (closedFired);
+
+		}
+	}
+}

+ 11 - 0
docfx/_exported_templates/README.md

@@ -0,0 +1,11 @@
+Regenerate anytime with 
+
+```powershell\
+docfx template export default
+```
+
+Technically, this can be deleted from the Terminal.Gui project, but it's left in place
+for convenience. 
+
+See https://dotnet.github.io/docfx/tutorial/howto_create_custom_template.html
+

+ 258 - 0
docfx/_exported_templates/default/ManagedReference.common.js

@@ -0,0 +1,258 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+var common = require('./common.js');
+var classCategory = 'class';
+var namespaceCategory = 'ns';
+
+exports.transform = function (model) {
+
+  if (!model) return null;
+
+  langs = model.langs;
+  handleItem(model, model._gitContribute, model._gitUrlPattern);
+  if (model.children) {
+    model.children.forEach(function (item) {
+      handleItem(item, model._gitContribute, model._gitUrlPattern);
+    });
+  }
+
+  if (model.type) {
+    switch (model.type.toLowerCase()) {
+      case 'namespace':
+        model.isNamespace = true;
+        if (model.children) groupChildren(model, namespaceCategory);
+        model[getTypePropertyName(model.type)] = true;
+        break;
+      case 'class':
+      case 'interface':
+      case 'struct':
+      case 'delegate':
+      case 'enum':
+        model.isClass = true;
+        if (model.children) groupChildren(model, classCategory);
+        model[getTypePropertyName(model.type)] = true;
+        break;
+      default:
+        break;
+    }
+  }
+
+  return model;
+}
+
+exports.getBookmarks = function (model, ignoreChildren) {
+  if (!model || !model.type || model.type.toLowerCase() === "namespace") return null;
+
+  var bookmarks = {};
+
+  if (typeof ignoreChildren == 'undefined' || ignoreChildren === false) {
+    if (model.children) {
+      model.children.forEach(function (item) {
+        bookmarks[item.uid] = common.getHtmlId(item.uid);
+        if (item.overload && item.overload.uid) {
+          bookmarks[item.overload.uid] = common.getHtmlId(item.overload.uid);
+        }
+      });
+    }
+  }
+
+  // Reference's first level bookmark should have no anchor
+  bookmarks[model.uid] = "";
+  return bookmarks;
+}
+
+exports.groupChildren = groupChildren;
+exports.getTypePropertyName = getTypePropertyName;
+exports.getCategory = getCategory;
+
+function groupChildren(model, category) {
+  if (!model || !model.type) {
+    return;
+  }
+  var typeChildrenItems = getDefinitions(category);
+  var grouped = {};
+
+  model.children.forEach(function (c) {
+    if (c.isEii) {
+      var type = "eii";
+    } else {
+      var type = c.type.toLowerCase();
+    }
+    if (!grouped.hasOwnProperty(type)) {
+      grouped[type] = [];
+    }
+    // special handle for field
+    if (type === "field" && c.syntax) {
+      c.syntax.fieldValue = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    // special handle for property
+    if ((type === "property" || type === "attachedproperty") && c.syntax) {
+      c.syntax.propertyValue = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    // special handle for event
+    if ((type === "event" || type === "attachedevent") && c.syntax) {
+      c.syntax.eventType = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    grouped[type].push(c);
+  })
+
+  var children = [];
+  for (var key in typeChildrenItems) {
+    if (typeChildrenItems.hasOwnProperty(key) && grouped.hasOwnProperty(key)) {
+      var typeChildrenItem = typeChildrenItems[key];
+      var items = grouped[key];
+      if (items && items.length > 0) {
+        var item = {};
+        for (var itemKey in typeChildrenItem) {
+          if (typeChildrenItem.hasOwnProperty(itemKey)) {
+            item[itemKey] = typeChildrenItem[itemKey];
+          }
+        }
+        item.children = items;
+        children.push(item);
+      }
+    }
+  }
+
+  model.children = children;
+}
+
+function getTypePropertyName(type) {
+  if (!type) {
+    return undefined;
+  }
+  var loweredType = type.toLowerCase();
+  var definition = getDefinition(loweredType);
+  if (definition) {
+    return definition.typePropertyName;
+  }
+
+  return undefined;
+}
+
+function getCategory(type) {
+  var classItems = getDefinitions(classCategory);
+  if (classItems.hasOwnProperty(type)) {
+    return classCategory;
+  }
+
+  var namespaceItems = getDefinitions(namespaceCategory);
+  if (namespaceItems.hasOwnProperty(type)) {
+    return namespaceCategory;
+  }
+  return undefined;
+}
+
+function getDefinition(type) {
+  var classItems = getDefinitions(classCategory);
+  if (classItems.hasOwnProperty(type)) {
+    return classItems[type];
+  }
+  var namespaceItems = getDefinitions(namespaceCategory);
+  if (namespaceItems.hasOwnProperty(type)) {
+    return namespaceItems[type];
+  }
+  return undefined;
+}
+
+function getDefinitions(category) {
+  var namespaceItems = {
+    "namespace":    { inNamespace: true,    typePropertyName: "inNamespace",    id: "namespaces" },
+    "class":        { inClass: true,        typePropertyName: "inClass",        id: "classes" },
+    "struct":       { inStruct: true,       typePropertyName: "inStruct",       id: "structs" },
+    "interface":    { inInterface: true,    typePropertyName: "inInterface",    id: "interfaces" },
+    "enum":         { inEnum: true,         typePropertyName: "inEnum",         id: "enums" },
+    "delegate":     { inDelegate: true,     typePropertyName: "inDelegate",     id: "delegates" }
+  };
+  var classItems = {
+    "constructor":      { inConstructor: true,      typePropertyName: "inConstructor",      id: "constructors" },
+    "field":            { inField: true,            typePropertyName: "inField",            id: "fields" },
+    "property":         { inProperty: true,         typePropertyName: "inProperty",         id: "properties" },
+    "attachedproperty": { inAttachedProperty: true, typePropertyName: "inAttachedProperty", id: "attachedProperties" },
+    "method":           { inMethod: true,           typePropertyName: "inMethod",           id: "methods" },
+    "event":            { inEvent: true,            typePropertyName: "inEvent",            id: "events" },
+    "attachedevent":    { inAttachedEvent: true,    typePropertyName: "inAttachedEvent",    id: "attachedEvents" },
+    "operator":         { inOperator: true,         typePropertyName: "inOperator",         id: "operators" },
+    "eii":              { inEii: true,              typePropertyName: "inEii",              id: "eii" }
+  };
+  if (category === 'class') {
+    return classItems;
+  }
+  if (category === 'ns') {
+    return namespaceItems;
+  }
+  console.err("category '" + category + "' is not valid.");
+  return undefined;
+}
+
+function handleItem(vm, gitContribute, gitUrlPattern) {
+  // get contribution information
+  vm.docurl = common.getImproveTheDocHref(vm, gitContribute, gitUrlPattern);
+  vm.sourceurl = common.getViewSourceHref(vm, null, gitUrlPattern);
+
+  // set to null incase mustache looks up
+  vm.summary = vm.summary || null;
+  vm.remarks = vm.remarks || null;
+  vm.conceptual = vm.conceptual || null;
+  vm.syntax = vm.syntax || null;
+  vm.implements = vm.implements || null;
+  vm.example = vm.example || null;
+  common.processSeeAlso(vm);
+
+  // id is used as default template's bookmark
+  vm.id = common.getHtmlId(vm.uid);
+  if (vm.overload && vm.overload.uid) {
+    vm.overload.id = common.getHtmlId(vm.overload.uid);
+  }
+
+  if (vm.supported_platforms) {
+    vm.supported_platforms = transformDictionaryToArray(vm.supported_platforms);
+  }
+
+  if (vm.requirements) {
+    var type = vm.type.toLowerCase();
+    if (type == "method") {
+      vm.requirements_method = transformDictionaryToArray(vm.requirements);
+    } else {
+      vm.requirements = transformDictionaryToArray(vm.requirements);
+    }
+  }
+
+  if (vm && langs) {
+    if (shouldHideTitleType(vm)) {
+      vm.hideTitleType = true;
+    } else {
+      vm.hideTitleType = false;
+    }
+
+    if (shouldHideSubtitle(vm)) {
+      vm.hideSubtitle = true;
+    } else {
+      vm.hideSubtitle = false;
+    }
+  }
+
+  function shouldHideTitleType(vm) {
+    var type = vm.type.toLowerCase();
+    return ((type === 'namespace' && langs.length == 1 && (langs[0] === 'objectivec' || langs[0] === 'java' || langs[0] === 'c'))
+      || ((type === 'class' || type === 'enum') && langs.length == 1 && langs[0] === 'c'));
+  }
+
+  function shouldHideSubtitle(vm) {
+    var type = vm.type.toLowerCase();
+    return (type === 'class' || type === 'namespace') && langs.length == 1 && langs[0] === 'c';
+  }
+
+  function transformDictionaryToArray(dic) {
+    var array = [];
+    for (var key in dic) {
+      if (dic.hasOwnProperty(key)) {
+        array.push({ "name": key, "value": dic[key] })
+      }
+    }
+
+    return array;
+  }
+}

+ 15 - 0
docfx/_exported_templates/default/ManagedReference.extension.js

@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/**
+ * This method will be called at the start of exports.transform in ManagedReference.html.primary.js
+ */
+exports.preTransform = function (model) {
+  return model;
+}
+
+/**
+ * This method will be called at the end of exports.transform in ManagedReference.html.primary.js
+ */
+exports.postTransform = function (model) {
+  return model;
+}

+ 40 - 0
docfx/_exported_templates/default/ManagedReference.html.primary.js

@@ -0,0 +1,40 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+var mrefCommon = require('./ManagedReference.common.js');
+var extension = require('./ManagedReference.extension.js');
+var overwrite = require('./ManagedReference.overwrite.js');
+
+exports.transform = function (model) {
+  if (overwrite && overwrite.transform) {
+    return overwrite.transform(model);
+  }
+
+  if (extension && extension.preTransform) {
+    model = extension.preTransform(model);
+  }
+
+  if (mrefCommon && mrefCommon.transform) {
+    model = mrefCommon.transform(model);
+  }
+  if (model.type.toLowerCase() === "enum") {
+    model.isClass = false;
+    model.isEnum = true;
+  }
+  model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
+
+  if (extension && extension.postTransform) {
+    model = extension.postTransform(model);
+  }
+
+  return model;
+}
+
+exports.getOptions = function (model) {
+  if (overwrite && overwrite.getOptions) {
+    return overwrite.getOptions(model);
+  }
+
+  return {
+    "bookmarks": mrefCommon.getBookmarks(model)
+  };
+}

+ 13 - 0
docfx/_exported_templates/default/ManagedReference.html.primary.tmpl

@@ -0,0 +1,13 @@
+{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
+{{!master(layout/_master.tmpl)}}
+
+{{#isNamespace}}
+  {{>partials/namespace}}
+{{/isNamespace}}
+{{#isClass}}
+  {{>partials/class}}
+{{/isClass}}
+{{#isEnum}}
+  {{>partials/enum}}
+{{/isEnum}}
+{{>partials/customMREFContent}}

+ 290 - 0
docfx/_exported_templates/default/RestApi.common.js

@@ -0,0 +1,290 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+var common = require('./common.js');
+
+exports.transform = function (model) {
+    var _fileNameWithoutExt = common.path.getFileNameWithoutExtension(model._path);
+    model._jsonPath = _fileNameWithoutExt + ".swagger.json";
+    model.title = model.title || model.name;
+    model.docurl = model.docurl || common.getImproveTheDocHref(model, model._gitContribute, model._gitUrlPattern);
+    model.sourceurl = model.sourceurl || common.getViewSourceHref(model, null, model._gitUrlPattern);
+    model.htmlId = common.getHtmlId(model.uid);
+    if (model.children) {
+        for (var i = 0; i < model.children.length; i++) {
+            var child = model.children[i];
+            child.docurl = child.docurl || common.getImproveTheDocHref(child, model._gitContribute, model._gitUrlPattern);
+            if (child.operation) {
+                child.operation = child.operation.toUpperCase();
+            }
+            child.path = appendQueryParamsToPath(child.path, child.parameters);
+            child.sourceurl = child.sourceurl || common.getViewSourceHref(child, null, model._gitUrlPattern);
+            child.conceptual = child.conceptual || ''; // set to empty incase mustache looks up
+            child.summary = child.summary || ''; // set to empty incase mustache looks up
+            child.description = child.description || ''; // set to empty incase mustache looks up
+            child.footer = child.footer || ''; // set to empty incase mustache looks up
+            child.remarks = child.remarks || ''; // set to empty incase mustache looks up
+            child.htmlId = common.getHtmlId(child.uid);
+
+            formatExample(child.responses);
+            resolveAllOf(child);
+            transformReference(child);
+        };
+        if (!model.tags || model.tags.length === 0) {
+            var childTags = [];
+            for (var i = 0; i < model.children.length; i++) {
+                var child = model.children[i];
+                if (child.tags && child.tags.length > 0) {
+                    for (var k = 0; k < child.tags.length; k++) {
+                        // for each tag in child, add unique tag string into childTags
+                        if (childTags.indexOf(child.tags[k]) === -1) {
+                            childTags.push(child.tags[k]);
+                        }
+                    }
+                }
+            }
+            // sort alphabetically
+            childTags.sort();
+            if (childTags.length > 0) {
+                model.tags = [];
+                for (var i = 0; i < childTags.length; i++) {
+                    // add tags into model
+                    model.tags.push({ "name": childTags[i] });
+                }
+            }
+        }
+        if (model.tags) {
+            for (var i = 0; i < model.tags.length; i++) {
+                var children = getChildrenByTag(model.children, model.tags[i].name);
+                if (children) {
+                    // set children into tag section
+                    model.tags[i].children = children;
+                }
+                model.tags[i].conceptual = model.tags[i].conceptual || ''; // set to empty incase mustache looks up
+                if (model.tags[i]["x-bookmark-id"]) {
+                    model.tags[i].htmlId = model.tags[i]["x-bookmark-id"];
+                } else if (model.tags[i].uid) {
+                    model.tags[i].htmlId = common.getHtmlId(model.tags[i].uid);
+                }
+            }
+            for (var i = 0; i < model.children.length; i++) {
+                var child = model.children[i];
+                if (child.includedInTags) {
+                    // set child to undefined, which is already moved to tag section
+                    model.children[i] = undefined;
+                    if (!model.isTagLayout) {
+                        // flags to indicate the model is tag layout
+                        model.isTagLayout = true;
+                    }
+                }
+            }
+            // remove undefined child
+            model.children = model.children.filter(function (o) { return o; });
+        }
+    }
+
+    return model;
+
+    function getChildrenByTag(children, tag) {
+        if (!children) return;
+        return children.filter(function (child) {
+            if (child.tags && child.tags.indexOf(tag) > -1) {
+                child.includedInTags = true;
+                return true;
+            }
+        })
+    }
+
+    function formatExample(responses) {
+        if (!responses) return;
+        for (var i = responses.length - 1; i >= 0; i--) {
+            var examples = responses[i].examples;
+            if (!examples) continue;
+            for (var j = examples.length - 1; j >= 0; j--) {
+                var content = examples[j].content;
+                if (!content) continue;
+                var mimeType = examples[j].mimeType;
+                if (mimeType === 'application/json') {
+                    try {
+                        var json = JSON.parse(content)
+                        responses[i].examples[j].content = JSON.stringify(json, null, '  ');
+                    } catch (e) {
+                        console.warn("example is not a valid JSON object.");
+                    }
+                }
+            }
+        }
+    }
+
+    function resolveAllOf(obj) {
+        if (Array.isArray(obj)) {
+            for (var i = 0; i < obj.length; i++) {
+                resolveAllOf(obj[i]);
+            }
+        }
+        else if (typeof obj === "object") {
+            for (var key in obj) {
+                if (obj.hasOwnProperty(key)) {
+                    if (key === "allOf" && Array.isArray(obj[key])) {
+                        // find 'allOf' array and process
+                        processAllOfArray(obj[key], obj);
+                        // delete 'allOf' value
+                        delete obj[key];
+                    } else {
+                        resolveAllOf(obj[key]);
+                    }
+                }
+            }
+        }
+    }
+
+    function processAllOfArray(allOfArray, originalObj) {
+        // for each object in 'allOf' array, merge the values to those in the same level with 'allOf'
+        for (var i = 0; i < allOfArray.length; i++) {
+            var item = allOfArray[i];
+            for (var key in item) {
+                if (originalObj.hasOwnProperty(key)) {
+                    mergeObjByKey(originalObj[key], item[key]);
+                } else {
+                    originalObj[key] = item[key];
+                }
+            }
+        }
+    }
+
+    function mergeObjByKey(targetObj, sourceObj) {
+        for (var key in sourceObj) {
+            // merge only when target object doesn't define the key
+            if (!targetObj.hasOwnProperty(key)) {
+                targetObj[key] = sourceObj[key];
+            }
+        }
+    }
+
+    function transformReference(obj) {
+        if (Array.isArray(obj)) {
+            for (var i = 0; i < obj.length; i++) {
+                transformReference(obj[i]);
+            }
+        }
+        else if (typeof obj === "object") {
+            for (var key in obj) {
+                if (obj.hasOwnProperty(key)) {
+                    if (key === "schema") {
+                        // transform schema.properties from obj to key value pair
+                        transformProperties(obj[key]);
+                    } else {
+                        transformReference(obj[key]);
+                    }
+                }
+            }
+        }
+    }
+
+    function transformProperties(obj) {
+        if (obj.properties) {
+            if (obj.required && Array.isArray(obj.required)) {
+                for (var i = 0; i < obj.required.length; i++) {
+                    var field = obj.required[i];
+                    if (obj.properties[field]) {
+                        // add required field as property
+                        obj.properties[field].required = true;
+                    }
+                }
+                delete obj.required;
+            }
+            var array = [];
+            for (var key in obj.properties) {
+                if (obj.properties.hasOwnProperty(key)) {
+                    var value = obj.properties[key];
+                    // set description to null incase mustache looks up
+                    value.description = value.description || null;
+
+                    transformPropertiesValue(value);
+                    array.push({ key: key, value: value });
+                }
+            }
+            obj.properties = array;
+        }
+    }
+
+    function transformPropertiesValue(obj) {
+        if (obj.type === "array" && obj.items) {
+            // expand array to transformProperties
+            obj.items.properties = obj.items.properties || null;
+            obj.items['x-internal-ref-name'] = obj.items['x-internal-ref-name'] || null;
+            obj.items['x-internal-loop-ref-name'] = obj.items['x-internal-loop-ref-name'] || null;
+            transformProperties(obj.items);
+        } else if (obj.properties && !obj.items) {
+            // fill obj.properties into obj.items.properties, to be rendered in the same way with array
+            obj.items = {};
+            obj.items.properties = obj.properties || null;
+            delete obj.properties;
+            if (obj.required) {
+                obj.items.required = obj.required;
+                delete obj.required;
+            }
+            obj.items['x-internal-ref-name'] = obj['x-internal-ref-name'] || null;
+            obj.items['x-internal-loop-ref-name'] = obj['x-internal-loop-ref-name'] || null;
+            transformProperties(obj.items);
+        }
+    }
+
+    function appendQueryParamsToPath(path, parameters) {
+        if (!path || !parameters) return path;
+
+        var requiredQueryParams = parameters.filter(function (p) { return p.in === 'query' && p.required; });
+        if (requiredQueryParams.length > 0) {
+            path = formatParams(path, requiredQueryParams, true);
+        }
+
+        var optionalQueryParams = parameters.filter(function (p) { return p.in === 'query' && !p.required; });
+        if (optionalQueryParams.length > 0) {
+            path += "[";
+            path = formatParams(path, optionalQueryParams, requiredQueryParams.length === 0);
+            path += "]";
+        }
+        return path;
+    }
+
+    function formatParams(path, parameters, isFirst) {
+        for (var i = 0; i < parameters.length; i++) {
+            if (i === 0 && isFirst) {
+                path += "?";
+            } else {
+                path += "&";
+            }
+            path += parameters[i].name;
+        }
+        return path;
+    }
+}
+
+exports.getBookmarks = function (model) {
+    if (!model) return null;
+
+    var bookmarks = {};
+
+    bookmarks[model.uid] = "";
+    if (model.tags) {
+        model.tags.forEach(function (tag) {
+            if (tag.uid) {
+                bookmarks[tag.uid] = tag["x-bookmark-id"] ? tag["x-bookmark-id"] : common.getHtmlId(tag.uid);
+            }
+            if (tag.children) {
+                tag.children.forEach(function (child) {
+                    if (child.uid) {
+                        bookmarks[child.uid] = common.getHtmlId(child.uid);
+                    }
+                })
+            }
+        })
+    }
+    if (model.children) {
+        model.children.forEach(function (child) {
+            if (child.uid) {
+                bookmarks[child.uid] = common.getHtmlId(child.uid);
+            }
+        });
+    }
+
+    return bookmarks;
+}

+ 15 - 0
docfx/_exported_templates/default/RestApi.extension.js

@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/**
+ * This method will be called at the start of exports.transform in RestApi.html.primary.js
+ */
+exports.preTransform = function (model) {
+  return model;
+}
+
+/**
+ * This method will be called at the end of exports.transform in RestApi.html.primary.js
+ */
+exports.postTransform = function (model) {
+  return model;
+}

+ 25 - 0
docfx/_exported_templates/default/RestApi.html.primary.js

@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+var restApiCommon = require('./RestApi.common.js');
+var extension = require('./RestApi.extension.js')
+
+exports.transform = function (model) {
+  if (extension && extension.preTransform) {
+    model = extension.preTransform(model);
+  }
+
+  if (restApiCommon && restApiCommon.transform) {
+    model = restApiCommon.transform(model);
+  }
+  model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
+
+  if (extension && extension.postTransform) {
+    model = extension.postTransform(model);
+  }
+
+  return model;
+}
+
+exports.getOptions = function (model) {
+  return { "bookmarks": restApiCommon.getBookmarks(model) };
+}

+ 3 - 0
docfx/_exported_templates/default/RestApi.html.primary.tmpl

@@ -0,0 +1,3 @@
+{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
+{{!master(layout/_master.tmpl)}}
+{{>partials/rest}}

+ 318 - 0
docfx/_exported_templates/default/UniversalReference.common.js

@@ -0,0 +1,318 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+var common = require('./common.js');;
+var classCategory = 'class';
+var namespaceCategory = 'ns';
+
+exports.transform = function (model) {
+  if (!model) return
+
+  handleItem(model, model._gitContribute, model._gitUrlPattern);
+  if (model.children) {
+    normalizeLanguageValuePairs(model.children).forEach(function (item) {
+      handleItem(item, model._gitContribute, model._gitUrlPattern);
+    });
+  };
+
+  if (model.type) {
+    switch (model.type.toLowerCase()) {
+      // packages and namespaces are both containers for other elements
+      case 'package':
+      case 'namespace':
+        model.isNamespace = true;
+        if (model.children) groupChildren(model, namespaceCategory);
+        model[getTypePropertyName(model.type)] = true;
+        break;
+      case 'class':
+      case 'interface':
+      case 'struct':
+      case 'delegate':
+        model.isClass = true;
+        if (model.children) groupChildren(model, classCategory);
+        model[getTypePropertyName(model.type)] = true;
+        break;
+      case 'enum':
+        model.isEnum = true;
+        if (model.children) groupChildren(model, classCategory);
+        model[getTypePropertyName(model.type)] = true;
+        break;
+      default:
+        break;
+    }
+  }
+
+  return model;
+}
+
+exports.getBookmarks = function (model, ignoreChildren)  {
+  if (!model || !model.type || model.type.toLowerCase() === "namespace") return null;
+
+  var bookmarks = {};
+
+  if (typeof ignoreChildren == 'undefined' || ignoreChildren === false) {
+    if (model.children) {
+      normalizeLanguageValuePairs(model.children).forEach(function (item) {
+        bookmarks[item.uid] = common.getHtmlId(item.uid);
+        if (item.overload && item.overload.uid) {
+          bookmarks[item.overload.uid] = common.getHtmlId(item.overload.uid);
+        }
+      });
+    }
+  }
+
+  // Reference's first level bookmark should have no anchor
+  bookmarks[model.uid] = "";
+  return bookmarks;
+}
+
+function handleItem(vm, gitContribute, gitUrlPattern) {
+  // get contribution information
+  vm.docurl = common.getImproveTheDocHref(vm, gitContribute, gitUrlPattern);
+  vm.sourceurl = common.getViewSourceHref(vm, null, gitUrlPattern);
+
+  // set to null incase mustache looks up
+  vm.summary = vm.summary || null;
+  vm.remarks = vm.remarks || null;
+  vm.conceptual = vm.conceptual || null;
+  vm.syntax = vm.syntax || null;
+  vm.implements = vm.implements || null;
+  vm.example = vm.example || null;
+  vm.inheritance = vm.inheritance || null;
+  if (vm.inheritance) {
+    normalizeLanguageValuePairs(vm.inheritance).forEach(handleInheritance);
+  }
+  
+  common.processSeeAlso(vm);
+
+  // id is used as default template's bookmark
+  vm.id = common.getHtmlId(vm.uid);
+  if (vm.overload && vm.overload.uid) {
+    vm.overload.id = common.getHtmlId(vm.overload.uid);
+  }
+
+  // concatenate multiple types with `|`
+  if (vm.syntax) {
+    var syntax = vm.syntax;
+    if (syntax.parameters) {
+      syntax.parameters = syntax.parameters.map(function (p) {
+        return joinType(p);
+      })
+      syntax.parameters = groupParameters(syntax.parameters);
+    }
+    if (syntax.return) {
+      syntax.return = joinType(syntax.return);
+    }
+  }
+}
+
+function handleInheritance(tree) {
+  tree.type = tree.type || null;
+  tree.inheritance = tree.inheritance || null;
+  if (tree.inheritance) {
+    tree.inheritance.forEach(handleInheritance);
+  }
+}
+
+function joinType(parameter) {
+  // change type in syntax from array to string
+  var joinTypeProperty = function (type, key) {
+    if (!type || !type[0] || !type[0][key]) return null;
+    var value = type.map(function (t) {
+      if (!t) return null;
+      if (!t[key]) return t.uid;
+      return t[key][0].value;
+    }).join(' | ');
+    return [{
+      lang: type[0][key][0].lang,
+      value: value
+    }];
+  };
+  if (parameter.type) {
+    parameter.type = {
+      name: joinTypeProperty(parameter.type, "name"),
+      nameWithType: joinTypeProperty(parameter.type, "nameWithType"),
+      fullName: joinTypeProperty(parameter.type, "fullName"),
+      specName: joinTypeProperty(parameter.type, "specName")
+    }
+  }
+  return parameter;
+}
+
+function groupParameters(parameters) {
+  // group parameter with properties
+  if (!parameters || parameters.length == 0) return parameters;
+  var groupedParameters = [];
+  var stack = [];
+  for (var i = 0; i < parameters.length; i++) {
+    var parameter = parameters[i];
+    parameter.properties = null;
+    var prefixLength = 0;
+    while (stack.length > 0) {
+      var top = stack.pop();
+      var prefix = top.id + '.';
+      if (parameter.id.indexOf(prefix) == 0) {
+        prefixLength = prefix.length;
+        if (!top.parameter.properties) {
+          top.parameter.properties = [];
+        }
+        top.parameter.properties.push(parameter);
+        stack.push(top);
+        break;
+      }
+      if (stack.length == 0) {
+        groupedParameters.push(top.parameter);
+      }
+    }
+    stack.push({ id: parameter.id, parameter: parameter });
+    parameter.id = parameter.id.substring(prefixLength);
+  }
+  while (stack.length > 0) {
+    top = stack.pop();
+  }
+  groupedParameters.push(top.parameter);
+  return groupedParameters;
+}
+
+function groupChildren(model, category, typeChildrenItems) {
+  if (!model || !model.type) {
+    return;
+  }
+  if (!typeChildrenItems) {
+    var typeChildrenItems = getDefinitions(category);
+  }
+  var grouped = {};
+
+  normalizeLanguageValuePairs(model.children).forEach(function (c) {
+    if (c.isEii) {
+      var type = "eii";
+    } else {
+      var type = c.type.toLowerCase();
+    }
+    if (!grouped.hasOwnProperty(type)) {
+      grouped[type] = [];
+    }
+    // special handle for field
+    if (type === "field" && c.syntax) {
+      c.syntax.fieldValue = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    // special handle for property
+    if (type === "property" && c.syntax) {
+      c.syntax.propertyValue = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    // special handle for event
+    if (type === "event" && c.syntax) {
+      c.syntax.eventType = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    if (type === "variable" && c.syntax) {
+      c.syntax.variableValue = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    if (type === "typealias" && c.syntax) {
+      c.syntax.typeAliasType = c.syntax.return;
+      c.syntax.return = undefined;
+    }
+    grouped[type].push(c);
+  })
+
+  var children = [];
+  for (var key in typeChildrenItems) {
+    if (typeChildrenItems.hasOwnProperty(key) && grouped.hasOwnProperty(key)) {
+      var typeChildrenItem = typeChildrenItems[key];
+      var items = grouped[key];
+      if (items && items.length > 0) {
+        var item = {};
+        for (var itemKey in typeChildrenItem) {
+          if (typeChildrenItem.hasOwnProperty(itemKey)){
+            item[itemKey] = typeChildrenItem[itemKey];
+          }
+        }
+        item.children = items;
+        children.push(item);
+      }
+    }
+  }
+
+  model.children = children;
+}
+
+function getTypePropertyName(type) {
+  if (!type) {
+    return undefined;
+  }
+  var loweredType = type.toLowerCase();
+  var definition = getDefinition(loweredType);
+  if (definition) {
+    return definition.typePropertyName;
+  }
+
+  return undefined;
+}
+
+function getCategory(type) {
+  var classItems = getDefinitions(classCategory);
+  if (classItems.hasOwnProperty(type)) {
+    return classCategory;
+  }
+
+  var namespaceItems = getDefinitions(namespaceCategory);
+  if (namespaceItems.hasOwnProperty(type)) {
+    return namespaceCategory;
+  }
+  return undefined;
+}
+
+function getDefinition(type) {
+  var classItems = getDefinitions(classCategory);
+  if (classItems.hasOwnProperty(type)) {
+    return classItems[type];
+  }
+  var namespaceItems = getDefinitions(namespaceCategory);
+  if (namespaceItems.hasOwnProperty(type)) {
+    return namespaceItems[type];
+  }
+  return undefined;
+}
+
+function getDefinitions(category) {
+  var namespaceItems = {
+    "package":      { inPackage: true,      typePropertyName: "inPackage",      id: "packages" },
+    "namespace":    { inNamespace: true,    typePropertyName: "inNamespace",    id: "namespaces" },
+    "class":        { inClass: true,        typePropertyName: "inClass",        id: "classes" },
+    "struct":       { inStruct: true,       typePropertyName: "inStruct",       id: "structs" },
+    "interface":    { inInterface: true,    typePropertyName: "inInterface",    id: "interfaces" },
+    "enum":         { inEnum: true,         typePropertyName: "inEnum",         id: "enums" },
+    "delegate":     { inDelegate: true,     typePropertyName: "inDelegate",     id: "delegates" },
+    "function":     { inFunction: true,     typePropertyName: "inFunction",     id: "functions",    isEmbedded: true },
+    "variable":     { inVariable: true,     typePropertyName: "inVariable",     id: "variables",    isEmbedded: true },
+    "typealias":    { inTypeAlias: true,    typePropertyName: "inTypeAlias",    id: "typealiases",  isEmbedded: true },
+  };
+  var classItems = {
+    "constructor":  { inConstructor: true,  typePropertyName: "inConstructor",  id: "constructors" },
+    "field":        { inField: true,        typePropertyName: "inField",        id: "fields" },
+    "property":     { inProperty: true,     typePropertyName: "inProperty",     id: "properties" },
+    "method":       { inMethod: true,       typePropertyName: "inMethod",       id: "methods" },
+    "event":        { inEvent: true,        typePropertyName: "inEvent",        id: "events" },
+    "operator":     { inOperator: true,     typePropertyName: "inOperator",     id: "operators" },
+    "eii":          { inEii: true,          typePropertyName: "inEii",          id: "eii" },
+    "member":       { inMember: true,       typePropertyName: "inMember",       id: "members"},
+    "function":     { inFunction: true,     typePropertyName: "inFunction",     id: "functions" }
+  };
+  if (category === 'class') {
+    return classItems;
+  }
+  if (category === 'ns') {
+    return namespaceItems;
+  }
+  console.err("category '" + category + "' is not valid.");
+  return undefined;
+}
+
+function normalizeLanguageValuePairs(list) {
+  if (list[0] && list[0].lang && list[0].value) {
+    return list[0].value;
+  }
+  return list;
+}

+ 15 - 0
docfx/_exported_templates/default/UniversalReference.extension.js

@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/**
+ * This method will be called at the start of exports.transform in UniversalReference.html.primary.js
+ */
+exports.preTransform = function (model) {
+  return model;
+}
+
+/**
+ * This method will be called at the end of exports.transform in UniversalReference.html.primary.js
+ */
+exports.postTransform = function (model) {
+  return model;
+}

+ 28 - 0
docfx/_exported_templates/default/UniversalReference.html.primary.js

@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+var urefCommon = require('./UniversalReference.common.js');
+var extension = require('./UniversalReference.extension.js');
+
+exports.transform = function (model) {
+  if (extension && extension.preTransform) {
+    model = extension.preTransform(model);
+  }
+
+  if (urefCommon && urefCommon.transform) {
+    model = urefCommon.transform(model);
+  }
+
+  model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
+
+  if (extension && extension.postTransform) {
+    model = extension.postTransform(model);
+  }
+
+  return model;
+}
+
+exports.getOptions = function (model) {
+  return {
+    "bookmarks": urefCommon.getBookmarks(model)
+  };
+}

+ 12 - 0
docfx/_exported_templates/default/UniversalReference.html.primary.tmpl

@@ -0,0 +1,12 @@
+{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
+{{!master(layout/_master.tmpl)}}
+
+{{#isNamespace}}
+  {{>partials/uref/namespace}}
+{{/isNamespace}}
+{{#isClass}}
+  {{>partials/uref/class}}
+{{/isClass}}
+{{#isEnum}}
+  {{>partials/enum}}
+{{/isEnum}}

+ 237 - 0
docfx/_exported_templates/default/common.js

@@ -0,0 +1,237 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+exports.path = {};
+exports.path.getFileNameWithoutExtension = getFileNameWithoutExtension;
+exports.path.getDirectoryName = getDirectoryName;
+
+exports.getHtmlId = getHtmlId;
+
+exports.getViewSourceHref = getViewSourceHref;
+exports.getImproveTheDocHref = getImproveTheDocHref;
+exports.processSeeAlso = processSeeAlso;
+
+exports.isAbsolutePath = isAbsolutePath;
+exports.isRelativePath = isRelativePath;
+
+function getFileNameWithoutExtension(path) {
+    if (!path || path[path.length - 1] === '/' || path[path.length - 1] === '\\') return '';
+    var fileName = path.split('\\').pop().split('/').pop();
+    return fileName.slice(0, fileName.lastIndexOf('.'));
+}
+
+function getDirectoryName(path) {
+    if (!path) return '';
+    var index = path.lastIndexOf('/');
+    return path.slice(0, index + 1);
+}
+
+function getHtmlId(input) {
+    if (!input) return '';
+    return input.replace(/\W/g, '_');
+}
+
+// Note: the parameter `gitContribute` won't be used in this function
+function getViewSourceHref(item, gitContribute, gitUrlPattern) {
+    if (!item || !item.source || !item.source.remote) return '';
+    return getRemoteUrl(item.source.remote, item.source.startLine - '0' + 1, null, gitUrlPattern);
+}
+
+function getImproveTheDocHref(item, gitContribute, gitUrlPattern) {
+    if (!item) return '';
+    if (!item.documentation || !item.documentation.remote) {
+        return getNewFileUrl(item, gitContribute, gitUrlPattern);
+    } else {
+        return getRemoteUrl(item.documentation.remote, item.documentation.startLine + 1, gitContribute, gitUrlPattern);
+    }
+}
+
+function processSeeAlso(item) {
+    if (item.seealso) {
+        for (var key in item.seealso) {
+            addIsCref(item.seealso[key]);
+        }
+    }
+    item.seealso = item.seealso || null;
+}
+
+function isAbsolutePath(path) {
+    return /^(\w+:)?\/\//g.test(path);
+}
+
+function isRelativePath(path) {
+    if (!path) return false;
+    return !exports.isAbsolutePath(path);
+}
+
+var gitUrlPatternItems = {
+    'github': {
+        // HTTPS form: https://github.com/{org}/{repo}.git
+        // SSH form: [email protected]:{org}/{repo}.git
+        // generate URL: https://github.com/{org}/{repo}/blob/{branch}/{path}
+        'testRegex': /^(https?:\/\/)?(\S+\@)?(\S+\.)?github\.com(\/|:).*/i,
+        'generateUrl': function (gitInfo) {
+            var url = normalizeGitUrlToHttps(gitInfo.repo);
+            url = getRepoWithoutGitExtension(url);
+            url += '/blob' + '/' + gitInfo.branch + '/' + gitInfo.path;
+            if (gitInfo.startLine && gitInfo.startLine > 0) {
+                url += '/#L' + gitInfo.startLine;
+            }
+            return url;
+        },
+        'generateNewFileUrl': function (gitInfo, uid) {
+            var url = normalizeGitUrlToHttps(gitInfo.repo);
+            url = getRepoWithoutGitExtension(url);
+            url += '/new';
+            url += '/' + gitInfo.branch;
+            url += '/' + getOverrideFolder(gitInfo.apiSpecFolder);
+            url += '/new?filename=' + getHtmlId(uid) + '.md';
+            url += '&value=' + encodeURIComponent(getOverrideTemplate(uid));
+            return url;
+        }
+    },
+    'vso': {
+        // HTTPS form: https://{account}@dev.azure.com/{account}/{project}/_git/{repo}
+        // HTTPS form: https://{user}.visualstudio.com/{org}/_git/{repo}
+        // SSH form: [email protected]:v3/{account}/{project}/{repo}
+        // SSH form: ssh://{user}@{user}.visualstudio.com:22/{org}/_git/{repo}
+        // generated URL under branch: https://{account}@dev.azure.com/{account}/{project}/_git/{repo}?version=GB{branch}
+        // generated URL under branch: https://{user}.visualstudio.com/{org}/_git/{repo}?path={path}&version=GB{branch}
+        // generated URL under detached HEAD: https://{user}.visualstudio.com/{org}/_git/{repo}?path={path}&version=GC{commit}
+        'testRegex': /^(https?:\/\/)?(ssh:\/\/\S+\@)?(\S+@)?(\S+\.)?(dev\.azure|visualstudio)\.com(\/|:).*/i,
+        'generateUrl': function (gitInfo) {
+            var url = normalizeGitUrlToHttps(gitInfo.repo);
+            var branchPrefix = /[0-9a-fA-F]{40}/.test(gitInfo.branch) ? 'GC' : 'GB';
+            url += '?path=' + gitInfo.path + '&version=' + branchPrefix + gitInfo.branch;
+            if (gitInfo.startLine && gitInfo.startLine > 0) {
+                url += '&line=' + gitInfo.startLine;
+            }
+            return url;
+        },
+        'generateNewFileUrl': function (gitInfo, uid) {
+            return '';
+        }
+    },
+    'bitbucket': {
+        // HTTPS form: https://{user}@bitbucket.org/{org}/{repo}.git
+        // SSH form: [email protected]:{org}/{repo}.git
+        // generate URL: https://bitbucket.org/{org}/{repo}/src/{branch}/{path}
+        'testRegex': /^(https?:\/\/)?(\S+\@)?(\S+\.)?bitbucket\.org(\/|:).*/i,
+        'generateUrl': function (gitInfo) {
+            var url = normalizeGitUrlToHttps(gitInfo.repo);
+            url = getRepoWithoutGitExtension(url);
+            url += '/src' + '/' + gitInfo.branch + '/' + gitInfo.path;
+            if (gitInfo.startLine && gitInfo.startLine > 0) {
+                url += '#lines-' + gitInfo.startLine;
+            }
+            return url;
+        },
+        'generateNewFileUrl': function (gitInfo, uid) {
+            return '';
+        }
+    }
+}
+
+function getRepoWithoutGitExtension(repo) {
+    if (repo.substr(-4) === '.git') {
+        repo = repo.substr(0, repo.length - 4);
+    }
+    return repo;
+}
+
+function normalizeGitUrlToHttps(repo) {
+    var pos = repo.indexOf('@');
+    if (pos == -1) return repo;
+    return 'https://' + repo.substr(pos + 1).replace(/:[0-9]+/g, '').replace(/:/g, '/');
+}
+
+function getNewFileUrl(item, gitContribute, gitUrlPattern) {
+    // do not support VSO for now
+    if (!item.source) {
+        return '';
+    }
+
+    var gitInfo = getGitInfo(gitContribute, item.source.remote);
+    if (!gitInfo.repo || !gitInfo.branch || !gitInfo.path) {
+        return '';
+    }
+
+    var patternName = getPatternName(gitInfo.repo, gitUrlPattern);
+    if (!patternName) return patternName;
+    return gitUrlPatternItems[patternName].generateNewFileUrl(gitInfo, item.uid);
+}
+
+function getRemoteUrl(remote, startLine, gitContribute, gitUrlPattern) {
+    var gitInfo = getGitInfo(gitContribute, remote);
+    if (!gitInfo.repo || !gitInfo.branch || !gitInfo.path) {
+        return '';
+    }
+
+    var patternName = getPatternName(gitInfo.repo, gitUrlPattern);
+    if (!patternName) return '';
+
+    gitInfo.startLine = startLine;
+    return gitUrlPatternItems[patternName].generateUrl(gitInfo);
+}
+
+function getGitInfo(gitContribute, gitRemote) {
+    // apiSpecFolder defines the folder contains overwrite files for MRef, the default value is apiSpec
+    var defaultApiSpecFolder = 'apiSpec';
+
+    var result = {};
+    if (gitContribute && gitContribute.apiSpecFolder) {
+        result.apiSpecFolder = gitContribute.apiSpecFolder;
+    } else {
+        result.apiSpecFolder = defaultApiSpecFolder;
+    }
+    mergeKey(gitContribute, gitRemote, result, 'repo');
+    mergeKey(gitContribute, gitRemote, result, 'branch');
+    mergeKey(gitContribute, gitRemote, result, 'path');
+
+    return result;
+
+    function mergeKey(source, sourceFallback, dest, key) {
+        if (source && source.hasOwnProperty(key)) {
+            dest[key] = source[key];
+        } else if (sourceFallback && sourceFallback.hasOwnProperty(key)) {
+            dest[key] = sourceFallback[key];
+        }
+    }
+}
+
+function getPatternName(repo, gitUrlPattern) {
+    if (gitUrlPattern && gitUrlPattern.toLowerCase() in gitUrlPatternItems) {
+        return gitUrlPattern.toLowerCase();
+    } else {
+        for (var p in gitUrlPatternItems) {
+            if (gitUrlPatternItems[p].testRegex.test(repo)) {
+                return p;
+            }
+        }
+    }
+    return '';
+}
+
+function getOverrideFolder(path) {
+    if (!path) return "";
+    path = path.replace(/\\/g, '/');
+    if (path.charAt(path.length - 1) == '/') path = path.substring(0, path.length - 1);
+    return path;
+}
+
+function getOverrideTemplate(uid) {
+    if (!uid) return "";
+    var content = "";
+    content += "---\n";
+    content += "uid: " + uid + "\n";
+    content += "summary: '*You can override summary for the API here using *MARKDOWN* syntax'\n";
+    content += "---\n";
+    content += "\n";
+    content += "*Please type below more information about this API:*\n";
+    content += "\n";
+    return content;
+}
+
+function addIsCref(seealso) {
+    if (!seealso.linkType || seealso.linkType.toLowerCase() == "cref") {
+        seealso.isCref = true;
+    }
+}

+ 15 - 0
docfx/_exported_templates/default/conceptual.extension.js

@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/**
+ * This method will be called at the start of exports.transform in conceptual.html.primary.js
+ */
+exports.preTransform = function (model) {
+  return model;
+}
+
+/**
+ * This method will be called at the end of exports.transform in conceptual.html.primary.js
+ */
+exports.postTransform = function (model) {
+  return model;
+}

+ 19 - 0
docfx/_exported_templates/default/conceptual.html.primary.js

@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+var common = require('./common.js');
+var extension = require('./conceptual.extension.js')
+
+exports.transform = function (model) {
+  if (extension && extension.preTransform) {
+    model = extension.preTransform(model);
+  }
+
+  model._disableToc = model._disableToc || !model._tocPath || (model._navPath === model._tocPath);
+  model.docurl = model.docurl || common.getImproveTheDocHref(model, model._gitContribute, model._gitUrlPattern);
+
+  if (extension && extension.postTransform) {
+    model = extension.postTransform(model);
+  }
+
+  return model;
+}

+ 4 - 0
docfx/_exported_templates/default/conceptual.html.primary.tmpl

@@ -0,0 +1,4 @@
+{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}}
+{{!master(layout/_master.tmpl)}}
+{{{rawTitle}}}
+{{{conceptual}}}

二进制
docfx/_exported_templates/default/favicon.ico


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