Browse Source

Merge branch 'develop' into update_workflows

Tig Kindel 3 years ago
parent
commit
804cf897c5
100 changed files with 10817 additions and 1347 deletions
  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. BIN
      docfx/_exported_templates/default/favicon.ico

+ 16 - 1
Example/demo.cs

@@ -624,6 +624,8 @@ static class Demo {
 
 
 		MenuItem miUseSubMenusSingleFrame = null;
 		MenuItem miUseSubMenusSingleFrame = null;
 		var useSubMenusSingleFrame = false;
 		var useSubMenusSingleFrame = false;
+		MenuItem miUseKeysUpDownAsKeysLeftRight = null;
+		var useKeysUpDownAsKeysLeftRight = false;
 
 
 		MenuItem miHeightAsBuffer = null;
 		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 ("_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 ("_Close", "", Close, null, null, Key.AltMask | Key.Q),
 				new MenuItem ("_Disabled", "", () => { }, () => false),
 				new MenuItem ("_Disabled", "", () => { }, () => false),
+				new MenuBarItem ("_SubMenu Disabled", new MenuItem [] {
+					new MenuItem ("_Disabled", "", () => { }, () => false)
+				}),
 				null,
 				null,
 				new MenuItem ("_Quit", "", () => { if (Quit ()) { running = null; top.Running = false; } }, null, null, Key.CtrlMask | Key.Q)
 				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 MenuBarItem ("_Find and Replace",
 					new MenuItem [] { menuItems [0], menuItems [1] }),
 					new MenuItem [] { menuItems [0], menuItems [1] }),
 				menuItems[3],
 				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", "",
 				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
 					CheckType = MenuItemCheckStyle.Checked, Checked = useSubMenusSingleFrame
 				},
 				},
 				miHeightAsBuffer = new MenuItem ("_Height As Buffer", "", () => {
 				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.
 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
 ## Controls and Views
 
 
 *Terminal.Gui* provides a rich set of views and controls for building terminal user interfaces:
 *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
 ### 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. 
 * **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.
 * **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.
 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.
 * **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#.
 * **[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. 
 * **[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
 * **[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
 ## 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
 ### 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).) 
 (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();
 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:
 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>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
     <PackageReference Include="ReactiveUI.Fody" Version="18.0.10" />
     <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" />
     <PackageReference Include="ReactiveMarbles.ObservableEvents.SourceGenerator" Version="1.1.4" PrivateAssets="all" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

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

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

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

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

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

@@ -117,7 +117,7 @@ namespace Unix.Terminal {
 			}
 			}
 
 
 			if (this.handle == IntPtr.Zero) {
 			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);
 					return Mono.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
 				}
 				}
 				if (IsNetCore) {
 				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);
 				return Linux.dlopen (libraryPath, RTLD_GLOBAL + RTLD_LAZY);
 			}
 			}

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

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

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

@@ -58,24 +58,39 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		bool IMainLoopDriver.EventsPending (bool wait)
 		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;
 			long now = DateTime.UtcNow.Ticks;
 
 
-			int waitTimeout;
 			if (mainLoop.timeouts.Count > 0) {
 			if (mainLoop.timeouts.Count > 0) {
 				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
 				waitTimeout = (int)((mainLoop.timeouts.Keys [0] - now) / TimeSpan.TicksPerMillisecond);
 				if (waitTimeout < 0)
 				if (waitTimeout < 0)
 					return true;
 					return true;
-			} else
+			} else {
 				waitTimeout = -1;
 				waitTimeout = -1;
+			}
 
 
 			if (!wait)
 			if (!wait)
 				waitTimeout = 0;
 				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 ()
 		void IMainLoopDriver.MainIteration ()

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

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

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

@@ -105,6 +105,11 @@ namespace Terminal.Gui {
 		/// <value>The current.</value>
 		/// <value>The current.</value>
 		public static Toplevel Current { get; private set; }
 		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>
 		/// <summary>
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// The current <see cref="ConsoleDriver.HeightAsBuffer"/> used in the terminal.
 		/// </summary>
 		/// </summary>
@@ -210,6 +215,24 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public static bool IsMouseDisabled { get; set; }
 		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>
 		/// <summary>
 		///   This event is raised on each iteration of the <see cref="MainLoop"/> 
 		///   This event is raised on each iteration of the <see cref="MainLoop"/> 
 		/// </summary>
 		/// </summary>
@@ -297,12 +320,12 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			// Used only for start debugging on Unix.
 			// 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).
 			// Reset all class variables (Application is a singleton).
 			ResetState ();
 			ResetState ();
@@ -352,7 +375,10 @@ namespace Terminal.Gui {
 			{
 			{
 				Toplevel = view;
 				Toplevel = view;
 			}
 			}
-			internal Toplevel Toplevel;
+			/// <summary>
+			/// The <see cref="Toplevel"/> belong to this <see cref="RunState"/>.
+			/// </summary>
+			public Toplevel Toplevel { get; internal set; }
 
 
 			/// <summary>
 			/// <summary>
 			/// Releases alTop = l resource used by the <see cref="Application.RunState"/> object.
 			/// Releases alTop = l resource used by the <see cref="Application.RunState"/> object.
@@ -385,7 +411,7 @@ namespace Terminal.Gui {
 
 
 		static void ProcessKeyEvent (KeyEvent ke)
 		static void ProcessKeyEvent (KeyEvent ke)
 		{
 		{
-			if(RootKeyEvent?.Invoke(ke) ?? false) {
+			if (RootKeyEvent?.Invoke (ke) ?? false) {
 				return;
 				return;
 			}
 			}
 
 
@@ -580,9 +606,8 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </para>
 		/// <para>Return true to suppress the KeyPress event</para>
 		/// <para>Return true to suppress the KeyPress event</para>
 		/// </summary>
 		/// </summary>
-		public static Func<KeyEvent,bool> RootKeyEvent;
+		public static Func<KeyEvent, bool> RootKeyEvent;
 
 
-		internal static View wantContinuousButtonPressedView;
 		static View lastMouseOwnerView;
 		static View lastMouseOwnerView;
 
 
 		static void ProcessMouseEvent (MouseEvent me)
 		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);
 			var view = FindDeepestView (Current, me.X, me.Y, out int rx, out int ry);
 
 
 			if (view != null && view.WantContinuousButtonPressed)
 			if (view != null && view.WantContinuousButtonPressed)
-				wantContinuousButtonPressedView = view;
+				WantContinuousButtonPressedView = view;
 			else
 			else
-				wantContinuousButtonPressedView = null;
+				WantContinuousButtonPressedView = null;
 			if (view != null) {
 			if (view != null) {
 				me.View = view;
 				me.View = view;
 			}
 			}
@@ -655,9 +680,9 @@ namespace Terminal.Gui {
 					return;
 					return;
 
 
 				if (view.WantContinuousButtonPressed)
 				if (view.WantContinuousButtonPressed)
-					wantContinuousButtonPressedView = view;
+					WantContinuousButtonPressedView = view;
 				else
 				else
-					wantContinuousButtonPressedView = null;
+					WantContinuousButtonPressedView = null;
 
 
 				// Should we bubbled up the event, if it is not handled?
 				// Should we bubbled up the event, if it is not handled?
 				view.OnMouseEvent (nme);
 				view.OnMouseEvent (nme);
@@ -868,6 +893,8 @@ namespace Terminal.Gui {
 			RootMouseEvent = null;
 			RootMouseEvent = null;
 			RootKeyEvent = null;
 			RootKeyEvent = null;
 			Resized = null;
 			Resized = null;
+			NotifyNewRunState = null;
+			NotifyStopRunState = null;
 			_initialized = false;
 			_initialized = false;
 			mouseGrabView = null;
 			mouseGrabView = null;
 
 
@@ -952,51 +979,65 @@ namespace Terminal.Gui {
 
 
 			bool firstIteration = true;
 			bool firstIteration = true;
 			for (state.Toplevel.Running = true; state.Toplevel.Running;) {
 			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;
 					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) {
 			if (_initialized && Driver != null) {
 				var top = new T ();
 				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);
 				Run (top, errorHandler);
 			} else {
 			} else {
@@ -1107,7 +1152,12 @@ namespace Terminal.Gui {
 				resume = false;
 				resume = false;
 				var runToken = Begin (view);
 				var runToken = Begin (view);
 				RunLoop (runToken);
 				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
 #if !DEBUG
 				}
 				}
 				catch (Exception error)
 				catch (Exception error)
@@ -1158,7 +1208,9 @@ namespace Terminal.Gui {
 					return;
 					return;
 				}
 				}
 				Current.Running = false;
 				Current.Running = false;
+				OnNotifyStopRunState (Current);
 				top.Running = false;
 				top.Running = false;
+				OnNotifyStopRunState (top);
 			} else if ((MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
 			} else if ((MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
 				&& Current?.Running == true && !top.Running)
 				&& Current?.Running == true && !top.Running)
 				|| (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
 				|| (MdiTop != null && top != MdiTop && top != Current && Current?.Modal == false
@@ -1169,11 +1221,13 @@ namespace Terminal.Gui {
 				&& Current?.Modal == true && top.Modal) {
 				&& Current?.Modal == true && top.Modal) {
 				// The Current and the top are both modal so needed to set the Current.Running to false too.
 				// The Current and the top are both modal so needed to set the Current.Running to false too.
 				Current.Running = false;
 				Current.Running = false;
+				OnNotifyStopRunState (Current);
 			} else if (MdiTop != null && Current == top && MdiTop?.Running == true && Current?.Running == true && top.Running
 			} else if (MdiTop != null && Current == top && MdiTop?.Running == true && Current?.Running == true && top.Running
 				&& Current?.Modal == true && top.Modal) {
 				&& Current?.Modal == true && top.Modal) {
 				// The MdiTop was requested to stop inside a modal toplevel which is the Current and top,
 				// 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.
 				// both are the same, so needed to set the Current.Running to false too.
 				Current.Running = false;
 				Current.Running = false;
+				OnNotifyStopRunState (Current);
 			} else {
 			} else {
 				Toplevel currentTop;
 				Toplevel currentTop;
 				if (top == Current || (Current?.Modal == true && !top.Modal)) {
 				if (top == Current || (Current?.Modal == true && !top.Modal)) {
@@ -1190,9 +1244,16 @@ namespace Terminal.Gui {
 					return;
 					return;
 				}
 				}
 				currentTop.Running = false;
 				currentTop.Running = false;
+				OnNotifyStopRunState (currentTop);
 			}
 			}
 		}
 		}
 
 
+		static void OnNotifyStopRunState (Toplevel top)
+		{
+			if (ExitRunLoopAfterFirstIteration)
+				NotifyStopRunState?.Invoke (top);
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Event arguments for the <see cref="Application.Resized"/> event.
 		/// Event arguments for the <see cref="Application.Resized"/> event.
 		/// </summary>
 		/// </summary>

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

@@ -173,26 +173,30 @@ namespace Terminal.Gui {
 				} else {
 				} else {
 					Border = border;
 					Border = border;
 				}
 				}
+				AdjustContentView (frame);
 			}
 			}
 
 
 			void AdjustContentView (Rect frame)
 			void AdjustContentView (Rect frame)
 			{
 			{
 				var borderLength = Border.DrawMarginFrame ? 1 : 0;
 				var borderLength = Border.DrawMarginFrame ? 1 : 0;
 				var sumPadding = Border.GetSumThickness ();
 				var sumPadding = Border.GetSumThickness ();
+				var wp = new Point ();
 				var wb = new Size ();
 				var wb = new Size ();
 				if (frame == Rect.Empty) {
 				if (frame == Rect.Empty) {
+					wp.X = borderLength + sumPadding.Left;
+					wp.Y = borderLength + sumPadding.Top;
 					wb.Width = borderLength + sumPadding.Right;
 					wb.Width = borderLength + sumPadding.Right;
 					wb.Height = borderLength + sumPadding.Bottom;
 					wb.Height = borderLength + sumPadding.Bottom;
 					if (Border.Child == null) {
 					if (Border.Child == null) {
 						Border.Child = new ChildContentView (this) {
 						Border.Child = new ChildContentView (this) {
-							X = borderLength + sumPadding.Left,
-							Y = borderLength + sumPadding.Top,
+							X = wp.X,
+							Y = wp.Y,
 							Width = Dim.Fill (wb.Width),
 							Width = Dim.Fill (wb.Width),
 							Height = Dim.Fill (wb.Height)
 							Height = Dim.Fill (wb.Height)
 						};
 						};
 					} else {
 					} 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.Width = Dim.Fill (wb.Width);
 						Border.Child.Height = Dim.Fill (wb.Height);
 						Border.Child.Height = Dim.Fill (wb.Height);
 					}
 					}
@@ -206,7 +210,8 @@ namespace Terminal.Gui {
 						Border.Child.Frame = cFrame;
 						Border.Child.Frame = cFrame;
 					}
 					}
 				}
 				}
-				base.Add (Border.Child);
+				if (Subviews?.Count == 0)
+					base.Add (Border.Child);
 				Border.ChildContainer = this;
 				Border.ChildContainer = this;
 			}
 			}
 
 
@@ -248,7 +253,7 @@ namespace Terminal.Gui {
 			{
 			{
 				if (!NeedDisplay.IsEmpty) {
 				if (!NeedDisplay.IsEmpty) {
 					Driver.SetAttribute (GetNormalColor ());
 					Driver.SetAttribute (GetNormalColor ());
-					Border.DrawContent ();
+					Clear ();
 				}
 				}
 				var savedClip = Border.Child.ClipToBounds ();
 				var savedClip = Border.Child.ClipToBounds ();
 				Border.Child.Redraw (Border.Child.Bounds);
 				Border.Child.Redraw (Border.Child.Bounds);
@@ -257,10 +262,13 @@ namespace Terminal.Gui {
 				ClearLayoutNeeded ();
 				ClearLayoutNeeded ();
 				ClearNeedsDisplay ();
 				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.
 				// Checks if there are any SuperView view which intersect with this window.
 				if (SuperView != null) {
 				if (SuperView != null) {
@@ -540,24 +548,26 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 			driver.SetAttribute (savedAttribute);
 
 
 			// Draw margin frame
 			// 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);
 			driver.SetAttribute (savedAttribute);
 
 
 			// Draw the MarginFrame
 			// 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) {
 			if (Effect3D) {
@@ -810,14 +822,16 @@ namespace Terminal.Gui {
 			driver.SetAttribute (savedAttribute);
 			driver.SetAttribute (savedAttribute);
 
 
 			// Draw the MarginFrame
 			// 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) {
 			if (Effect3D) {

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

@@ -665,8 +665,10 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public abstract bool HeightAsBuffer { get; set; }
 		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>
 		/// <summary>
 		/// Initializes the driver
 		/// Initializes the driver
@@ -769,6 +771,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public abstract void End ();
 		public abstract void End ();
 
 
+		/// <summary>
+		/// Resizes the clip area when the screen is resized.
+		/// </summary>
+		public abstract void ResizeScreen ();
+
 		/// <summary>
 		/// <summary>
 		/// Reset and recreate the contents and the driver buffer.
 		/// Reset and recreate the contents and the driver buffer.
 		/// </summary>
 		/// </summary>

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

@@ -47,7 +47,9 @@ namespace Terminal.Gui {
 			Dispose ();
 			Dispose ();
 		}
 		}
 
 
-		/// <inheritdoc/>
+		/// <summary>
+		/// Disposes the all the context menu objects instances.
+		/// </summary>
 		public void Dispose ()
 		public void Dispose ()
 		{
 		{
 			if (IsShow) {
 			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.
 	///   does not seem to be a way of supporting this on Windows.
 	/// </remarks>
 	/// </remarks>
 	public class MainLoop {
 	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;
 			public TimeSpan Span;
+			/// <summary>
+			/// The function that will be invoked.
+			/// </summary>
 			public Func<MainLoop, bool> Callback;
 			public Func<MainLoop, bool> Callback;
 		}
 		}
 
 
@@ -54,12 +63,30 @@ namespace Terminal.Gui {
 		object timeoutsLockToken = new object ();
 		object timeoutsLockToken = new object ();
 		internal List<Func<bool>> idleHandlers = new List<Func<bool>> ();
 		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>
 		/// <summary>
 		/// The current IMainLoopDriver in use.
 		/// The current IMainLoopDriver in use.
 		/// </summary>
 		/// </summary>
 		/// <value>The driver.</value>
 		/// <value>The driver.</value>
 		public IMainLoopDriver Driver { get; }
 		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>
 		/// <summary>
 		///  Creates a new Mainloop. 
 		///  Creates a new Mainloop. 
 		/// </summary>
 		/// </summary>
@@ -120,7 +147,8 @@ namespace Terminal.Gui {
 		{
 		{
 			lock (timeoutsLockToken) {
 			lock (timeoutsLockToken) {
 				var k = (DateTime.UtcNow + time).Ticks;
 				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);
 						AddTimeout (timeout.Span, timeout);
 				} else {
 				} else {
 					lock (timeoutsLockToken) {
 					lock (timeoutsLockToken) {
-						timeouts.Add (NudgeToUniqueKey(k), timeout);
+						timeouts.Add (NudgeToUniqueKey (k), timeout);
 					}
 					}
 				}
 				}
 			}
 			}
-			
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -203,7 +230,7 @@ namespace Terminal.Gui {
 		/// <returns></returns>
 		/// <returns></returns>
 		private long NudgeToUniqueKey (long k)
 		private long NudgeToUniqueKey (long k)
 		{
 		{
-			lock(timeoutsLockToken) {
+			lock (timeoutsLockToken) {
 				while (timeouts.ContainsKey (k)) {
 				while (timeouts.ContainsKey (k)) {
 					k++;
 					k++;
 				}
 				}

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

@@ -36,6 +36,40 @@ namespace Terminal.Gui {
 			return 0;
 			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 {
 		internal class PosFactor : Pos {
 			float factor;
 			float factor;
 
 
@@ -53,6 +87,10 @@ namespace Terminal.Gui {
 			{
 			{
 				return $"Pos.Factor({factor})";
 				return $"Pos.Factor({factor})";
 			}
 			}
+
+			public override int GetHashCode () => factor.GetHashCode ();
+
+			public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -182,7 +220,6 @@ namespace Terminal.Gui {
 			public override int GetHashCode () => n.GetHashCode ();
 			public override int GetHashCode () => n.GetHashCode ();
 
 
 			public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
 			public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
-
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -310,6 +347,10 @@ namespace Terminal.Gui {
 				}
 				}
 				return $"Pos.View(side={tside}, target={Target.ToString ()})";
 				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>
 		/// <summary>
@@ -353,6 +394,16 @@ namespace Terminal.Gui {
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
 		/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 		/// <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));
 		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>
 	/// <summary>
@@ -375,6 +426,40 @@ namespace Terminal.Gui {
 			return 0;
 			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 {
 		internal class DimFactor : Dim {
 			float factor;
 			float factor;
 			bool remaining;
 			bool remaining;
@@ -403,7 +488,6 @@ namespace Terminal.Gui {
 			public override int GetHashCode () => factor.GetHashCode ();
 			public override int GetHashCode () => factor.GetHashCode ();
 
 
 			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
 			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
-
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -449,7 +533,6 @@ namespace Terminal.Gui {
 			public override int GetHashCode () => n.GetHashCode ();
 			public override int GetHashCode () => n.GetHashCode ();
 
 
 			public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
 			public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
-
 		}
 		}
 
 
 		internal class DimFill : Dim {
 		internal class DimFill : Dim {
@@ -590,6 +673,16 @@ namespace Terminal.Gui {
 				this.side = side;
 				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 ()
 			public override string ToString ()
 			{
 			{
 				string tside;
 				string tside;
@@ -601,20 +694,9 @@ namespace Terminal.Gui {
 				return $"DimView(side={tside}, target={Target.ToString ()})";
 				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 int GetHashCode () => Target.GetHashCode ();
 
 
 			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
 			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
-
 		}
 		}
 		/// <summary>
 		/// <summary>
 		/// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
 		/// 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>
 		/// <summary>Serves as the default hash function. </summary>
 		/// <returns>A hash code for the current object.</returns>
 		/// <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>
 		/// <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>
 		/// <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 {
 			set {
 				text = value;
 				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)
 					// 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?
 					// 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);
 					Size = new Size (TextFormatter.MaxWidth (Text, int.MaxValue), 1);
@@ -154,9 +154,16 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public bool AutoSize { get; set; }
 		public bool AutoSize { get; set; }
 
 
-		// TODO: Add Vertical Text Alignment
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <value>The text alignment.</value>
 		/// <value>The text alignment.</value>
 		public TextAlignment Alignment {
 		public TextAlignment Alignment {
@@ -309,7 +316,7 @@ namespace Terminal.Gui {
 		public List<ustring> Lines {
 		public List<ustring> Lines {
 			get {
 			get {
 				// With this check, we protect against subclasses with overrides of Text
 				// 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 = new List<ustring> ();
 					lines.Add (ustring.Empty);
 					lines.Add (ustring.Empty);
 					NeedsFormat = false;
 					NeedsFormat = false;
@@ -323,19 +330,20 @@ namespace Terminal.Gui {
 						shown_text = RemoveHotKeySpecifier (Text, hotKeyPos, HotKeySpecifier);
 						shown_text = RemoveHotKeySpecifier (Text, hotKeyPos, HotKeySpecifier);
 						shown_text = ReplaceHotKeyWithTag (shown_text, hotKeyPos);
 						shown_text = ReplaceHotKeyWithTag (shown_text, hotKeyPos);
 					}
 					}
-					if (Size.IsEmpty) {
-						throw new InvalidOperationException ("Size must be set before accessing Lines");
-					}
 
 
 					if (IsVerticalDirection (textDirection)) {
 					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 {
 					} else {
 						lines = Format (shown_text, Size.Width, textAlignment == TextAlignment.Justified, Size.Height > 1,
 						lines = Format (shown_text, Size.Width, textAlignment == TextAlignment.Justified, Size.Height > 1,
-							false, 0, textDirection);
+							PreserveTrailingSpaces, 0, textDirection);
 						if (!AutoSize && lines.Count > Size.Height) {
 						if (!AutoSize && lines.Count > Size.Height) {
 							lines.RemoveRange (Size.Height, lines.Count - Size.Height);
 							lines.RemoveRange (Size.Height, lines.Count - Size.Height);
 						}
 						}
@@ -465,7 +473,7 @@ namespace Terminal.Gui {
 			var runes = StripCRLF (text).ToRuneList ();
 			var runes = StripCRLF (text).ToRuneList ();
 			if (!preserveTrailingSpaces) {
 			if (!preserveTrailingSpaces) {
 				if (IsHorizontalDirection (textDirection)) {
 				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)
 						while (runes [end] != ' ' && end > start)
 							end--;
 							end--;
 						if (end == start)
 						if (end == start)
@@ -491,16 +499,25 @@ namespace Terminal.Gui {
 				}
 				}
 			} else {
 			} else {
 				while ((end = start) < runes.Count) {
 				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)));
 					lines.Add (ustring.Make (runes.GetRange (start, end - start)));
 					start = end;
 					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 to = from;
 				var length = cLength;
 				var length = cLength;
+				incomplete = false;
 
 
 				while (length < cWidth && to < runes.Count) {
 				while (length < cWidth && to < runes.Count) {
 					var rune = runes [to];
 					var rune = runes [to];
@@ -509,13 +526,19 @@ namespace Terminal.Gui {
 					} else {
 					} else {
 						length++;
 						length++;
 					}
 					}
+					if (length > cWidth) {
+						if (to >= runes.Count || (length > 1 && cWidth <= 1)) {
+							incomplete = true;
+						}
+						return to;
+					}
 					if (rune == ' ') {
 					if (rune == ' ') {
 						if (length == cWidth) {
 						if (length == cWidth) {
 							return to + 1;
 							return to + 1;
 						} else if (length > cWidth) {
 						} else if (length > cWidth) {
 							return to;
 							return to;
 						} else {
 						} else {
-							return GetNextWhiteSpace (to + 1, cWidth, length);
+							return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
 						}
 						}
 					} else if (rune == '\t') {
 					} else if (rune == '\t') {
 						length += tabWidth + 1;
 						length += tabWidth + 1;
@@ -524,7 +547,7 @@ namespace Terminal.Gui {
 						} else if (length > cWidth && tabWidth > cWidth) {
 						} else if (length > cWidth && tabWidth > cWidth) {
 							return to;
 							return to;
 						} else {
 						} else {
-							return GetNextWhiteSpace (to + 1, cWidth, length);
+							return GetNextWhiteSpace (to + 1, cWidth, out incomplete, length);
 						}
 						}
 					}
 					}
 					to++;
 					to++;
@@ -576,11 +599,15 @@ namespace Terminal.Gui {
 			var runes = text.ToRuneList ();
 			var runes = text.ToRuneList ();
 			int slen = runes.Count;
 			int slen = runes.Count;
 			if (slen > width) {
 			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 {
 			} else {
 				if (justify) {
 				if (justify) {
 					return Justify (text, width, ' ', textDirection);
 					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 ustring.Make (runes.GetRange (0, GetMaxLengthForWidth (text, width)));
 				}
 				}
 				return text;
 				return text;
@@ -687,9 +714,6 @@ namespace Terminal.Gui {
 			if (width < 0) {
 			if (width < 0) {
 				throw new ArgumentOutOfRangeException ("width cannot be negative");
 				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> ();
 			List<ustring> lineResult = new List<ustring> ();
 
 
 			if (ustring.IsNullOrEmpty (text) || width == 0) {
 			if (ustring.IsNullOrEmpty (text) || width == 0) {
@@ -846,6 +870,28 @@ namespace Terminal.Gui {
 			return runeIdx;
 			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>
 		/// <summary>
 		///  Calculates the rectangle required to hold text, assuming no word wrapping.
 		///  Calculates the rectangle required to hold text, assuming no word wrapping.
 		/// </summary>
 		/// </summary>
@@ -889,7 +935,7 @@ namespace Terminal.Gui {
 				w = mw;
 				w = mw;
 				h = ml;
 				h = ml;
 			} else {
 			} else {
-				int vw = 0;
+				int vw = 1, cw = 1;
 				int vh = 0;
 				int vh = 0;
 
 
 				int rows = 0;
 				int rows = 0;
@@ -900,14 +946,13 @@ namespace Terminal.Gui {
 							vh = rows;
 							vh = rows;
 						}
 						}
 						rows = 0;
 						rows = 0;
+						cw = 1;
 					} else if (rune != '\r') {
 					} else if (rune != '\r') {
 						rows++;
 						rows++;
 						var rw = Rune.ColumnWidth (rune);
 						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;
 				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;
 					continue;
 
 
 				var runes = lines [line].ToRunes ();
 				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.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Linq;
 using System.Linq;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <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>
 	/// </summary>
 	/// <remarks>
 	/// <remarks>
 	///   <para>
 	///   <para>
 	///     Toplevels can be modally executing views, started by calling <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>. 
 	///     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 
 	///     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>
 	///   <para>
 	///   <para>
 	///     A Toplevel is created when an application initializes Terminal.Gui by calling <see cref="Application.Init(ConsoleDriver, IMainLoopDriver)"/>.
 	///     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; }
 		public bool Running { get; set; }
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action Loaded;
 		public event Action Loaded;
 
 
 		/// <summary>
 		/// <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.
 		/// 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>
 		/// </summary>
 		public event Action Ready;
 		public event Action Ready;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action Unloaded;
 		public event Action Unloaded;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Toplevel> Activate;
 		public event Action<Toplevel> Activate;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Toplevel> Deactivate;
 		public event Action<Toplevel> Deactivate;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Toplevel> ChildClosed;
 		public event Action<Toplevel> ChildClosed;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action AllChildClosed;
 		public event Action AllChildClosed;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<ToplevelClosingEventArgs> Closing;
 		public event Action<ToplevelClosingEventArgs> Closing;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Toplevel> Closed;
 		public event Action<Toplevel> Closed;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Toplevel> ChildLoaded;
 		public event Action<Toplevel> ChildLoaded;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Toplevel> ChildUnloaded;
 		public event Action<Toplevel> ChildUnloaded;
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public event Action<Size> Resized;
 		public event Action<Size> Resized;
 
 
@@ -162,18 +163,25 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
-		internal virtual void OnLoaded ()
+		virtual public void OnLoaded ()
 		{
 		{
+			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+				tl.OnLoaded ();
+			}
 			Loaded?.Invoke ();
 			Loaded?.Invoke ();
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		internal virtual void OnReady ()
 		internal virtual void OnReady ()
 		{
 		{
+			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+				tl.OnReady ();
+			}
 			Ready?.Invoke ();
 			Ready?.Invoke ();
 		}
 		}
 
 
@@ -182,11 +190,14 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		internal virtual void OnUnloaded ()
 		internal virtual void OnUnloaded ()
 		{
 		{
+			foreach (Toplevel tl in Subviews.Where (v => v is Toplevel)) {
+				tl.OnUnloaded ();
+			}
 			Unloaded?.Invoke ();
 			Unloaded?.Invoke ();
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <param name="frame">A superview-relative rectangle specifying the location and size for the new Toplevel</param>
 		/// <param name="frame">A superview-relative rectangle specifying the location and size for the new Toplevel</param>
 		public Toplevel (Rect frame) : base (frame)
 		public Toplevel (Rect frame) : base (frame)
@@ -195,7 +206,8 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public Toplevel () : base ()
 		public Toplevel () : base ()
 		{
 		{
@@ -291,7 +303,7 @@ namespace Terminal.Gui {
 		/// <summary>
 		/// <summary>
 		/// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
 		/// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
 		/// </summary>
 		/// </summary>
-		/// <returns>The create.</returns>
+		/// <returns>The created Toplevel.</returns>
 		public static Toplevel Create ()
 		public static Toplevel Create ()
 		{
 		{
 			return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
 			return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows));
@@ -306,19 +318,38 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public bool Modal { get; set; }
 		public bool Modal { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the menu for this Toplevel
+		/// Gets or sets the menu for this Toplevel.
 		/// </summary>
 		/// </summary>
 		public virtual MenuBar MenuBar { get; set; }
 		public virtual MenuBar MenuBar { get; set; }
 
 
 		/// <summary>
 		/// <summary>
-		/// Gets or sets the status bar for this Toplevel
+		/// Gets or sets the status bar for this Toplevel.
 		/// </summary>
 		/// </summary>
 		public virtual StatusBar StatusBar { get; set; }
 		public virtual StatusBar StatusBar { get; set; }
 
 
@@ -647,7 +678,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <param name="top">The toplevel.</param>
 		/// <param name="top">The toplevel.</param>
 		public virtual void PositionToplevel (Toplevel top)
 		public virtual void PositionToplevel (Toplevel top)
@@ -734,20 +765,12 @@ namespace Terminal.Gui {
 			return false;
 			return false;
 		}
 		}
 
 
-		//
-		// FIXED:It does not look like the event is raised on clicked-drag
-		// need to figure that out.
-		//
 		internal static Point? dragPosition;
 		internal static Point? dragPosition;
 		Point start;
 		Point start;
 
 
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override bool MouseEvent (MouseEvent mouseEvent)
 		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) {
 			if (!CanFocus) {
 				return true;
 				return true;
 			}
 			}
@@ -800,7 +823,6 @@ namespace Terminal.Gui {
 					}
 					}
 					//System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}");
 					//System.Diagnostics.Debug.WriteLine ($"nx:{nx},ny:{ny}");
 
 
-					// FIXED: optimize, only SetNeedsDisplay on the before/after regions.
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
 					return true;
 					return true;
 				}
 				}
@@ -817,8 +839,8 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public virtual void WillPresent ()
 		public virtual void WillPresent ()
 		{
 		{
@@ -842,7 +864,8 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		public virtual void RequestStop ()
 		public virtual void RequestStop ()
 		{
 		{
@@ -880,7 +903,8 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <param name="top">The toplevel to request stop.</param>
 		/// <param name="top">The toplevel to request stop.</param>
 		public virtual void RequestStop (Toplevel top)
 		public virtual void RequestStop (Toplevel top)
@@ -908,7 +932,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </summary>
 		/// <param name="type">The type.</param>
 		/// <param name="type">The type.</param>
 		/// <param name="exclude">The strings to exclude.</param>
 		/// <param name="exclude">The strings to exclude.</param>
@@ -933,10 +957,10 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// </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)
 		public virtual bool ShowChild (Toplevel top = null)
 		{
 		{
 			if (Application.MdiTop != null) {
 			if (Application.MdiTop != null) {
@@ -947,7 +971,8 @@ namespace Terminal.Gui {
 	}
 	}
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
 	public class ToplevelEqualityComparer : IEqualityComparer<Toplevel> {
 		/// <summary>Determines whether the specified objects are equal.</summary>
 		/// <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>
 		/// <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>
 		/// <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>
 		/// <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)
 		public int GetHashCode (Toplevel obj)
 		{
 		{
 			if (obj == null)
 			if (obj == null)
@@ -985,7 +1011,8 @@ namespace Terminal.Gui {
 	}
 	}
 
 
 	/// <summary>
 	/// <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>
 	/// </summary>
 	public sealed class ToplevelComparer : IComparer<Toplevel> {
 	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>
 		/// <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>
 		/// </summary>
 		public event Action<Key> HotKeyChanged;
 		public event Action<Key> HotKeyChanged;
 
 
+		Key hotKey = Key.Null;
+
 		/// <summary>
 		/// <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.
 		/// 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>
 		/// </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>
 		/// <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'. 
 		/// 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>
 		/// </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>
 		/// <summary>
 		/// This is the global setting that can be used as a global shortcut to invoke an action if provided.
 		/// 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 (frame);
 					SuperView.SetNeedsDisplay (value);
 					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 ();
 				SetNeedsLayout ();
 				SetNeedsDisplay (frame);
 				SetNeedsDisplay (frame);
 			}
 			}
@@ -513,17 +528,13 @@ namespace Terminal.Gui {
 		public Pos X {
 		public Pos X {
 			get => x;
 			get => x;
 			set {
 			set {
-				if (!ValidatePosDim (x, value)) {
+				if (ForceValidatePosDim && !ValidatePosDim (x, value)) {
 					throw new ArgumentException ();
 					throw new ArgumentException ();
 				}
 				}
 
 
 				x = value;
 				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 {
 		public Pos Y {
 			get => y;
 			get => y;
 			set {
 			set {
-				if (!ValidatePosDim (y, value)) {
+				if (ForceValidatePosDim && !ValidatePosDim (y, value)) {
 					throw new ArgumentException ();
 					throw new ArgumentException ();
 				}
 				}
 
 
 				y = value;
 				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;
 		Dim width, height;
 
 
 		/// <summary>
 		/// <summary>
@@ -563,20 +569,20 @@ namespace Terminal.Gui {
 		public Dim Width {
 		public Dim Width {
 			get => width;
 			get => width;
 			set {
 			set {
-				if (!ValidatePosDim (width, value)) {
-					throw new ArgumentException ();
+				if (ForceValidatePosDim && !ValidatePosDim (width, value)) {
+					throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width));
 				}
 				}
 
 
 				width = value;
 				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 {
 		public Dim Height {
 			get => height;
 			get => height;
 			set {
 			set {
-				if (!ValidatePosDim (height, value)) {
-					throw new ArgumentException ();
+				if (ForceValidatePosDim && !ValidatePosDim (height, value)) {
+					throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height));
 				}
 				}
 
 
 				height = value;
 				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)
 		bool ValidatePosDim (object oldvalue, object newValue)
 		{
 		{
 			if (!IsInitialized || layoutStyle == LayoutStyle.Absolute || oldvalue == null || oldvalue.GetType () == newValue.GetType () || this is Toplevel) {
 			if (!IsInitialized || layoutStyle == LayoutStyle.Absolute || oldvalue == null || oldvalue.GetType () == newValue.GetType () || this is Toplevel) {
@@ -618,6 +630,50 @@ namespace Terminal.Gui {
 			return false;
 			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>
 		/// <summary>
 		/// Gets or sets the <see cref="Terminal.Gui.TextFormatter"/> which can be handled differently by any derived class.
 		/// Gets or sets the <see cref="Terminal.Gui.TextFormatter"/> which can be handled differently by any derived class.
 		/// </summary>
 		/// </summary>
@@ -724,7 +780,7 @@ namespace Terminal.Gui {
 		}
 		}
 
 
 		void Initialize (ustring text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed,
 		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 = new TextFormatter ();
 			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
 			TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
@@ -745,14 +801,45 @@ namespace Terminal.Gui {
 			} else {
 			} else {
 				r = rect;
 				r = rect;
 			}
 			}
-			x = Pos.At (r.X);
-			y = Pos.At (r.Y);
-			Width = r.Width;
-			Height = r.Height;
-
 			Frame = r;
 			Frame = r;
 
 
 			Text = text;
 			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)
 		private void TextFormatter_HotKeyChanged (Key obj)
@@ -1279,6 +1366,12 @@ namespace Terminal.Gui {
 		/// <param name="view">The subview being added.</param>
 		/// <param name="view">The subview being added.</param>
 		public virtual void OnAdded (View view)
 		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);
 			view.Added?.Invoke (this);
 		}
 		}
 
 
@@ -1288,6 +1381,7 @@ namespace Terminal.Gui {
 		/// <param name="view">The subview being removed.</param>
 		/// <param name="view">The subview being removed.</param>
 		public virtual void OnRemoved (View view)
 		public virtual void OnRemoved (View view)
 		{
 		{
+			view.IsAdded = false;
 			view.Removed?.Invoke (this);
 			view.Removed?.Invoke (this);
 		}
 		}
 
 
@@ -1429,8 +1523,8 @@ namespace Terminal.Gui {
 				containerBounds.Width = Math.Min (containerBounds.Width, Driver.Clip.Width);
 				containerBounds.Width = Math.Min (containerBounds.Width, Driver.Clip.Width);
 				containerBounds.Height = Math.Min (containerBounds.Height, Driver.Clip.Height);
 				containerBounds.Height = Math.Min (containerBounds.Height, Driver.Clip.Height);
 				TextFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (),
 				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
 			// Invoke DrawContentEvent
@@ -2021,47 +2115,64 @@ namespace Terminal.Gui {
 		internal void SetRelativeLayout (Rect hostFrame)
 		internal void SetRelativeLayout (Rect hostFrame)
 		{
 		{
 			int w, h, _x, _y;
 			int w, h, _x, _y;
+			var s = Size.Empty;
+
+			if (AutoSize) {
+				s = GetAutoSize ();
+			}
 
 
 			if (x is Pos.PosCenter) {
 			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 = width.Anchor (hostFrame.Width);
+					w = AutoSize && s.Width > w ? s.Width : w;
+				}
 				_x = x.Anchor (hostFrame.Width - w);
 				_x = x.Anchor (hostFrame.Width - w);
 			} else {
 			} else {
 				if (x == null)
 				if (x == null)
 					_x = 0;
 					_x = 0;
 				else
 				else
 					_x = x.Anchor (hostFrame.Width);
 					_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);
 					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 = Math.Max (width.Anchor (hostFrame.Width - _x), 0);
+					w = AutoSize && s.Width > w ? s.Width : w;
+				}
 			}
 			}
 
 
 			if (y is Pos.PosCenter) {
 			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 = height.Anchor (hostFrame.Height);
+					h = AutoSize && s.Height > h ? s.Height : h;
+				}
 				_y = y.Anchor (hostFrame.Height - h);
 				_y = y.Anchor (hostFrame.Height - h);
 			} else {
 			} else {
 				if (y == null)
 				if (y == null)
 					_y = 0;
 					_y = 0;
 				else
 				else
 					_y = y.Anchor (hostFrame.Height);
 					_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);
 					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 = Math.Max (height.Anchor (hostFrame.Height - _y), 0);
+					h = AutoSize && s.Height > h ? s.Height : h;
+				}
 			}
 			}
 			var r = new Rect (_x, _y, w, h);
 			var r = new Rect (_x, _y, w, h);
 			if (Frame != r) {
 			if (Frame != r) {
 				Frame = new Rect (_x, _y, w, h);
 				Frame = new Rect (_x, _y, w, h);
+				if (!SetMinWidthHeight ())
+					TextFormatter.Size = GetBoundsTextFormatterSize ();
 			}
 			}
 		}
 		}
 
 
@@ -2177,7 +2288,7 @@ namespace Terminal.Gui {
 			Rect oldBounds = Bounds;
 			Rect oldBounds = Bounds;
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
 
 
-			TextFormatter.Size = Bounds.Size;
+			TextFormatter.Size = GetBoundsTextFormatterSize ();
 
 
 
 
 			// Sort out the dependencies of the X, Y, Width, Height properties
 			// 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
 			if (SuperView != null && SuperView == Application.Top && LayoutNeeded
-				&& ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
+			    && ordered.Count == 0 && LayoutStyle == LayoutStyle.Computed) {
 				SetRelativeLayout (SuperView.Frame);
 				SetRelativeLayout (SuperView.Frame);
 			}
 			}
 
 
@@ -2259,6 +2370,8 @@ namespace Terminal.Gui {
 			OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
 			OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
 		}
 		}
 
 
+		ustring text;
+
 		/// <summary>
 		/// <summary>
 		///   The text displayed by the <see cref="View"/>.
 		///   The text displayed by the <see cref="View"/>.
 		/// </summary>
 		/// </summary>
@@ -2278,27 +2391,22 @@ namespace Terminal.Gui {
 		/// </para>
 		/// </para>
 		/// </remarks>
 		/// </remarks>
 		public virtual ustring Text {
 		public virtual ustring Text {
-			get => TextFormatter.Text;
+			get => text;
 			set {
 			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>
 		/// <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>
 		/// </summary>
 		public virtual bool AutoSize {
 		public virtual bool AutoSize {
 			get => autoSize;
 			get => autoSize;
@@ -2308,8 +2416,24 @@ namespace Terminal.Gui {
 				if (autoSize != v) {
 				if (autoSize != v) {
 					autoSize = v;
 					autoSize = v;
 					TextFormatter.NeedsFormat = true;
 					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;
 			get => TextFormatter.Alignment;
 			set {
 			set {
 				TextFormatter.Alignment = value;
 				TextFormatter.Alignment = value;
-				SetNeedsDisplay ();
+				UpdateTextFormatterText ();
+				ProcessResizeView ();
 			}
 			}
 		}
 		}
 
 
@@ -2346,14 +2471,23 @@ namespace Terminal.Gui {
 			get => TextFormatter.Direction;
 			get => TextFormatter.Direction;
 			set {
 			set {
 				if (TextFormatter.Direction != value) {
 				if (TextFormatter.Direction != value) {
+					var isValidOldAutSize = autoSize && IsValidAutoSize (out Size autSize);
+					var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
+					    != TextFormatter.IsHorizontalDirection (value);
+
 					TextFormatter.Direction = 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 ();
 					SetNeedsDisplay ();
 				}
 				}
 			}
 			}
@@ -2365,6 +2499,11 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public virtual bool IsInitialized { get; set; }
 		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;
 		bool oldEnabled;
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
@@ -2431,32 +2570,38 @@ namespace Terminal.Gui {
 			return $"{GetType ().Name}({Id})({Frame})";
 			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)
 		bool ResizeView (bool autoSize)
 		{
 		{
 			if (!autoSize) {
 			if (!autoSize) {
 				return false;
 				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;
 			return aSize;
 		}
 		}
 
 
-		bool SetWidthHeight (Rect nBounds)
+		bool SetWidthHeight (Size nBounds)
 		{
 		{
 			bool aSize = false;
 			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) {
 			if (canSizeW) {
 				aSize = true;
 				aSize = true;
 				width = rW;
 				width = rW;
@@ -2467,12 +2612,90 @@ namespace Terminal.Gui {
 			}
 			}
 			if (aSize) {
 			if (aSize) {
 				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
 				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
-				TextFormatter.Size = Bounds.Size;
+				TextFormatter.Size = GetBoundsTextFormatterSize ();
 			}
 			}
 
 
 			return aSize;
 			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>
 		/// <summary>
 		/// Specifies the event arguments for <see cref="MouseEvent"/>
 		/// Specifies the event arguments for <see cref="MouseEvent"/>
 		/// </summary>
 		/// </summary>
@@ -2655,7 +2878,7 @@ namespace Terminal.Gui {
 			if (Width is Dim.DimCombine || Width is Dim.DimView || Width is Dim.DimFill) {
 			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.
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's width anchored.
 				w = Width.Anchor (w);
 				w = Width.Anchor (w);
-				canSetWidth = false;
+				canSetWidth = !ForceValidatePosDim;
 			} else if (Width is Dim.DimFactor factor) {
 			} else if (Width is Dim.DimFactor factor) {
 				// Tries to get the SuperView width otherwise the view width.
 				// Tries to get the SuperView width otherwise the view width.
 				var sw = SuperView != null ? SuperView.Frame.Width : w;
 				var sw = SuperView != null ? SuperView.Frame.Width : w;
@@ -2663,7 +2886,7 @@ namespace Terminal.Gui {
 					sw -= Frame.X;
 					sw -= Frame.X;
 				}
 				}
 				w = Width.Anchor (sw);
 				w = Width.Anchor (sw);
-				canSetWidth = false;
+				canSetWidth = !ForceValidatePosDim;
 			} else {
 			} else {
 				canSetWidth = true;
 				canSetWidth = true;
 			}
 			}
@@ -2679,7 +2902,7 @@ namespace Terminal.Gui {
 			if (Height is Dim.DimCombine || Height is Dim.DimView || Height is Dim.DimFill) {
 			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.
 				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
 				h = Height.Anchor (h);
 				h = Height.Anchor (h);
-				canSetHeight = false;
+				canSetHeight = !ForceValidatePosDim;
 			} else if (Height is Dim.DimFactor factor) {
 			} else if (Height is Dim.DimFactor factor) {
 				// Tries to get the SuperView height otherwise the view height.
 				// Tries to get the SuperView height otherwise the view height.
 				var sh = SuperView != null ? SuperView.Frame.Height : h;
 				var sh = SuperView != null ? SuperView.Frame.Height : h;
@@ -2687,7 +2910,7 @@ namespace Terminal.Gui {
 					sh -= Frame.Y;
 					sh -= Frame.Y;
 				}
 				}
 				h = Height.Anchor (sh);
 				h = Height.Anchor (sh);
-				canSetHeight = false;
+				canSetHeight = !ForceValidatePosDim;
 			} else {
 			} else {
 				canSetHeight = true;
 				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>
 		/// <returns><c>true</c> if the width can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool GetCurrentWidth (out int currentWidth)
 		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 _);
 			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>
 		/// <returns><c>true</c> if the height can be directly assigned, <c>false</c> otherwise.</returns>
 		public bool GetCurrentHeight (out int currentHeight)
 		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 _);
 			return CanSetHeight (0, out _);
 		}
 		}

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

@@ -9,6 +9,7 @@
 //  - FrameView Does not support IEnumerable
 //  - FrameView Does not support IEnumerable
 // Any udpates done here should probably be done in FrameView as well; TODO: Merge these classes
 // Any udpates done here should probably be done in FrameView as well; TODO: Merge these classes
 
 
+using System;
 using System.Collections;
 using System.Collections;
 using NStack;
 using NStack;
 
 
@@ -22,7 +23,7 @@ namespace Terminal.Gui {
 	/// </remarks>
 	/// </remarks>
 	public class Window : Toplevel {
 	public class Window : Toplevel {
 		View contentView;
 		View contentView;
-		ustring title;
+		ustring title = ustring.Empty;
 
 
 		/// <summary>
 		/// <summary>
 		/// The title to be displayed for this window.
 		/// The title to be displayed for this window.
@@ -31,7 +32,11 @@ namespace Terminal.Gui {
 		public ustring Title {
 		public ustring Title {
 			get => title;
 			get => title;
 			set {
 			set {
-				title = value;
+				if (!OnTitleChanging (title, value)) {
+					var old = title;
+					title = value;
+					OnTitleChanged (old, title);
+				}
 				SetNeedsDisplay ();
 				SetNeedsDisplay ();
 			}
 			}
 		}
 		}
@@ -70,7 +75,6 @@ namespace Terminal.Gui {
 			AdjustContentView (frame);
 			AdjustContentView (frame);
 		}
 		}
 
 
-
 		/// <summary>
 		/// <summary>
 		/// ContentView is an internal implementation detail of Window. It is used to host Views added with <see cref="Add(View)"/>. 
 		/// 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 
 		/// 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;
 			CanFocus = true;
 			ColorScheme = Colors.Base;
 			ColorScheme = Colors.Base;
+			if (title == null) title = ustring.Empty;
 			Title = title;
 			Title = title;
 			if (border == null) {
 			if (border == null) {
 				Border = new Border () {
 				Border = new Border () {
@@ -180,26 +185,30 @@ namespace Terminal.Gui {
 			} else {
 			} else {
 				Border = border;
 				Border = border;
 			}
 			}
+			AdjustContentView (frame);
 		}
 		}
 
 
 		void AdjustContentView (Rect frame)
 		void AdjustContentView (Rect frame)
 		{
 		{
 			var borderLength = Border.DrawMarginFrame ? 1 : 0;
 			var borderLength = Border.DrawMarginFrame ? 1 : 0;
 			var sumPadding = Border.GetSumThickness ();
 			var sumPadding = Border.GetSumThickness ();
+			var wp = new Point ();
 			var wb = new Size ();
 			var wb = new Size ();
 			if (frame == Rect.Empty) {
 			if (frame == Rect.Empty) {
+				wp.X = borderLength + sumPadding.Left;
+				wp.Y = borderLength + sumPadding.Top;
 				wb.Width = borderLength + sumPadding.Right;
 				wb.Width = borderLength + sumPadding.Right;
 				wb.Height = borderLength + sumPadding.Bottom;
 				wb.Height = borderLength + sumPadding.Bottom;
 				if (contentView == null) {
 				if (contentView == null) {
 					contentView = new ContentView (this) {
 					contentView = new ContentView (this) {
-						X = borderLength + sumPadding.Left,
-						Y = borderLength + sumPadding.Top,
+						X = wp.X,
+						Y = wp.Y,
 						Width = Dim.Fill (wb.Width),
 						Width = Dim.Fill (wb.Width),
 						Height = Dim.Fill (wb.Height)
 						Height = Dim.Fill (wb.Height)
 					};
 					};
 				} else {
 				} 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.Width = Dim.Fill (wb.Width);
 					contentView.Height = Dim.Fill (wb.Height);
 					contentView.Height = Dim.Fill (wb.Height);
 				}
 				}
@@ -213,7 +222,8 @@ namespace Terminal.Gui {
 					contentView.Frame = cFrame;
 					contentView.Frame = cFrame;
 				}
 				}
 			}
 			}
-			base.Add (contentView);
+			if (Subviews?.Count == 0)
+				base.Add (contentView);
 			Border.Child = contentView;
 			Border.Child = contentView;
 		}
 		}
 
 
@@ -283,15 +293,15 @@ namespace Terminal.Gui {
 
 
 			ClearLayoutNeeded ();
 			ClearLayoutNeeded ();
 			ClearNeedsDisplay ();
 			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.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
-			}
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
 
 
 			// Checks if there are any SuperView view which intersect with this window.
 			// 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"/>.
 		///   The text displayed by the <see cref="Label"/>.
 		/// </summary>
 		/// </summary>
 		public override ustring Text {
 		public override ustring Text {
-			get => contentView.Text;
+			get => contentView?.Text;
 			set {
 			set {
 				base.Text = value;
 				base.Text = value;
 				if (contentView != null) {
 				if (contentView != null) {
@@ -333,5 +343,70 @@ namespace Terminal.Gui {
 				base.TextAlignment = contentView.TextAlignment = value;
 				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);
                 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"?>
 <?xml version="1.0" encoding="utf-8"?>
 <root>
 <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: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:element name="root" msdata:IsDataSet="true">
       <xsd:complexType>
       <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
         <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:element name="data">
             <xsd:complexType>
             <xsd:complexType>
               <xsd:sequence>
               <xsd:sequence>
                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                 <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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
               </xsd:sequence>
               </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="type" type="xsd:string" msdata:Ordinal="3" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
             </xsd:complexType>
           </xsd:element>
           </xsd:element>
           <xsd:element name="resheader">
           <xsd:element name="resheader">
@@ -89,13 +109,13 @@
     <value>text/microsoft-resx</value>
     <value>text/microsoft-resx</value>
   </resheader>
   </resheader>
   <resheader name="version">
   <resheader name="version">
-    <value>1.3</value>
+    <value>2.0</value>
   </resheader>
   </resheader>
   <resheader name="reader">
   <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>
   <resheader name="writer">
   <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>
   </resheader>
   <data name="ctxCopy" xml:space="preserve">
   <data name="ctxCopy" xml:space="preserve">
     <value>_Copier</value>
     <value>_Copier</value>
@@ -139,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
   <data name="fdSelectMixed" xml:space="preserve">
     <value>Sélection _mixte</value>
     <value>Sélection _mixte</value>
   </data>
   </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>
 </root>

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

@@ -159,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
   <data name="fdSelectMixed" xml:space="preserve">
     <value>混在選択</value>
     <value>混在選択</value>
   </data>
   </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>
 </root>

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

@@ -1,76 +1,96 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <root>
 <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: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:element name="root" msdata:IsDataSet="true">
       <xsd:complexType>
       <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
         <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:element name="data">
             <xsd:complexType>
             <xsd:complexType>
               <xsd:sequence>
               <xsd:sequence>
                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                 <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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
               </xsd:sequence>
               </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="type" type="xsd:string" msdata:Ordinal="3" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
             </xsd:complexType>
           </xsd:element>
           </xsd:element>
           <xsd:element name="resheader">
           <xsd:element name="resheader">
@@ -89,13 +109,13 @@
     <value>text/microsoft-resx</value>
     <value>text/microsoft-resx</value>
   </resheader>
   </resheader>
   <resheader name="version">
   <resheader name="version">
-    <value>1.3</value>
+    <value>2.0</value>
   </resheader>
   </resheader>
   <resheader name="reader">
   <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>
   <resheader name="writer">
   <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>
   </resheader>
   <data name="ctxCopy" xml:space="preserve">
   <data name="ctxCopy" xml:space="preserve">
     <value>_Copiar</value>
     <value>_Copiar</value>
@@ -139,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
   <data name="fdSelectMixed" xml:space="preserve">
     <value>Selecione misto</value>
     <value>Selecione misto</value>
   </data>
   </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>
 </root>

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

@@ -1,76 +1,96 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <root>
 <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: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:element name="root" msdata:IsDataSet="true">
       <xsd:complexType>
       <xsd:complexType>
         <xsd:choice maxOccurs="unbounded">
         <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:element name="data">
             <xsd:complexType>
             <xsd:complexType>
               <xsd:sequence>
               <xsd:sequence>
                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                 <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:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
               </xsd:sequence>
               </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="type" type="xsd:string" msdata:Ordinal="3" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
             </xsd:complexType>
             </xsd:complexType>
           </xsd:element>
           </xsd:element>
           <xsd:element name="resheader">
           <xsd:element name="resheader">
@@ -89,13 +109,13 @@
     <value>text/microsoft-resx</value>
     <value>text/microsoft-resx</value>
   </resheader>
   </resheader>
   <resheader name="version">
   <resheader name="version">
-    <value>1.3</value>
+    <value>2.0</value>
   </resheader>
   </resheader>
   <resheader name="reader">
   <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>
   <resheader name="writer">
   <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>
   </resheader>
   <data name="ctxSelectAll" xml:space="preserve">
   <data name="ctxSelectAll" xml:space="preserve">
     <value>_Select All</value>
     <value>_Select All</value>
@@ -139,4 +159,13 @@
   <data name="fdSelectMixed" xml:space="preserve">
   <data name="fdSelectMixed" xml:space="preserve">
     <value>Select Mixed</value>
     <value>Select Mixed</value>
   </data>
   </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>
 </root>

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

@@ -31,8 +31,11 @@ namespace Terminal.Gui {
 	/// </para>
 	/// </para>
 	/// </remarks>
 	/// </remarks>
 	public class Button : View {
 	public class Button : View {
-		ustring text;
 		bool is_default;
 		bool is_default;
+		Rune _leftBracket;
+		Rune _rightBracket;
+		Rune _leftDefault;
+		Rune _rightDefault;
 
 
 		/// <summary>
 		/// <summary>
 		///   Initializes a new instance of <see cref="Button"/> using <see cref="LayoutStyle.Computed"/> layout.
 		///   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);
 			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)
 		void Initialize (ustring text, bool is_default)
 		{
 		{
 			TextAlignment = TextAlignment.Centered;
 			TextAlignment = TextAlignment.Centered;
+			VerticalTextAlignment = VerticalTextAlignment.Middle;
 
 
 			HotKeySpecifier = new Rune ('_');
 			HotKeySpecifier = new Rune ('_');
 
 
@@ -111,9 +108,11 @@ namespace Terminal.Gui {
 			_rightDefault = new Rune (Driver != null ? Driver.RightDefaultIndicator : '>');
 			_rightDefault = new Rune (Driver != null ? Driver.RightDefaultIndicator : '>');
 
 
 			CanFocus = true;
 			CanFocus = true;
+			AutoSize = true;
 			this.is_default = is_default;
 			this.is_default = is_default;
-			this.text = text ?? string.Empty;
-			Update ();
+			Text = text ?? string.Empty;
+			UpdateTextFormatterText ();
+			ProcessResizeView ();
 
 
 			// Things this view knows how to do
 			// Things this view knows how to do
 			AddCommand (Command.Accept, () => AcceptKey ());
 			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>
 		/// <summary>
 		/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
 		/// Gets or sets whether the <see cref="Button"/> is the default action to activate in a dialog.
 		/// </summary>
 		/// </summary>
@@ -149,76 +133,38 @@ namespace Terminal.Gui {
 			get => is_default;
 			get => is_default;
 			set {
 			set {
 				is_default = value;
 				is_default = value;
-				Update ();
+				UpdateTextFormatterText ();
+				ProcessResizeView ();
 			}
 			}
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <inheritdoc/>
 		public override Key HotKey {
 		public override Key HotKey {
-			get => hotKey;
+			get => base.HotKey;
 			set {
 			set {
-				if (hotKey != value) {
+				if (base.HotKey != value) {
 					var v = value == Key.Unknown ? Key.Null : 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) {
 						if (v == Key.Null) {
-							ClearKeybinding (Key.Space | hotKey);
+							ClearKeybinding (Key.Space | base.HotKey);
 						} else {
 						} else {
-							ReplaceKeyBinding (Key.Space | hotKey, Key.Space | v);
+							ReplaceKeyBinding (Key.Space | base.HotKey, Key.Space | v);
 						}
 						}
 					} else if (v != Key.Null) {
 					} else if (v != Key.Null) {
 						AddKeyBinding (Key.Space | v, Command.Accept);
 						AddKeyBinding (Key.Space | v, Command.Accept);
 					}
 					}
-					hotKey = v;
+					base.HotKey = TextFormatter.HotKey = v;
 				}
 				}
 			}
 			}
 		}
 		}
 
 
 		/// <inheritdoc/>
 		/// <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)
 			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
 			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/>
 		///<inheritdoc/>
@@ -321,9 +267,9 @@ namespace Terminal.Gui {
 		///<inheritdoc/>
 		///<inheritdoc/>
 		public override void PositionCursor ()
 		public override void PositionCursor ()
 		{
 		{
-			if (HotKey == Key.Unknown && text != "") {
+			if (HotKey == Key.Unknown && Text != "") {
 				for (int i = 0; i < TextFormatter.Text.RuneCount; i++) {
 				for (int i = 0; i < TextFormatter.Text.RuneCount; i++) {
-					if (TextFormatter.Text [i] == text [0]) {
+					if (TextFormatter.Text [i] == Text [0]) {
 						Move (i, 0);
 						Move (i, 0);
 						return;
 						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
 	/// The <see cref="CheckBox"/> <see cref="View"/> shows an on/off toggle that the user can set
 	/// </summary>
 	/// </summary>
 	public class CheckBox : View {
 	public class CheckBox : View {
-		ustring text;
-		int hot_pos = -1;
-		Rune hot_key;
+		Rune charChecked;
+		Rune charUnChecked;
+		bool @checked;
 
 
 		/// <summary>
 		/// <summary>
 		///   Toggled event, raised when the <see cref="CheckBox"/>  is toggled.
 		///   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
 		///   The size of <see cref="CheckBox"/> is computed based on the
 		///   text length. 
 		///   text length. 
 		/// </remarks>
 		/// </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);
 			Initialize (s, is_checked);
 		}
 		}
 
 
 		void Initialize (ustring s, bool 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;
 			Checked = is_checked;
-			Text = s;
+			HotKeySpecifier = new Rune ('_');
 			CanFocus = true;
 			CanFocus = true;
-			Height = 1;
-			Width = s.RuneCount + 4;
+			AutoSize = true;
+			Text = s;
+			UpdateTextFormatterText ();
+			ProcessResizeView ();
 
 
 			// Things this view knows how to do
 			// Things this view knows how to do
 			AddCommand (Command.ToggleChecked, () => ToggleChecked ());
 			AddCommand (Command.ToggleChecked, () => ToggleChecked ());
@@ -89,52 +93,38 @@ namespace Terminal.Gui {
 			AddKeyBinding (Key.Space, Command.ToggleChecked);
 			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 {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <summary>
-	/// ComboBox control
+	/// Provides a drop-down list of items the user can select from.
 	/// </summary>
 	/// </summary>
 	public class ComboBox : View {
 	public class ComboBox : View {
 
 
@@ -109,7 +109,7 @@ namespace Terminal.Gui {
 
 
 		private void Initialize ()
 		private void Initialize ()
 		{
 		{
-			if (Bounds.Height < minimumHeight && Height is Dim.DimAbsolute) {
+			if (Bounds.Height < minimumHeight && (Height == null || Height is Dim.DimAbsolute)) {
 				Height = minimumHeight;
 				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)
 		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) {
 			if (border == null) {
 				Border = new Border () {
 				Border = new Border () {
 					BorderStyle = BorderStyle.Single
 					BorderStyle = BorderStyle.Single
@@ -123,20 +124,23 @@ namespace Terminal.Gui {
 		{
 		{
 			var borderLength = Border.DrawMarginFrame ? 1 : 0;
 			var borderLength = Border.DrawMarginFrame ? 1 : 0;
 			var sumPadding = Border.GetSumThickness ();
 			var sumPadding = Border.GetSumThickness ();
+			var wp = new Point ();
 			var wb = new Size ();
 			var wb = new Size ();
 			if (frame == Rect.Empty) {
 			if (frame == Rect.Empty) {
+				wp.X = borderLength + sumPadding.Left;
+				wp.Y = borderLength + sumPadding.Top;
 				wb.Width = borderLength + sumPadding.Right;
 				wb.Width = borderLength + sumPadding.Right;
 				wb.Height = borderLength + sumPadding.Bottom;
 				wb.Height = borderLength + sumPadding.Bottom;
 				if (contentView == null) {
 				if (contentView == null) {
 					contentView = new ContentView () {
 					contentView = new ContentView () {
-						X = borderLength + sumPadding.Left,
-						Y = borderLength + sumPadding.Top,
+						X = wp.X,
+						Y = wp.Y,
 						Width = Dim.Fill (wb.Width),
 						Width = Dim.Fill (wb.Width),
 						Height = Dim.Fill (wb.Height)
 						Height = Dim.Fill (wb.Height)
 					};
 					};
 				} else {
 				} 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.Width = Dim.Fill (wb.Width);
 					contentView.Height = Dim.Fill (wb.Height);
 					contentView.Height = Dim.Fill (wb.Height);
 				}
 				}
@@ -224,15 +228,14 @@ namespace Terminal.Gui {
 			Driver.Clip = savedClip;
 			Driver.Clip = savedClip;
 
 
 			ClearNeedsDisplay ();
 			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.DrawWindowTitle (scrRect, Title, padding.Left, padding.Top, padding.Right, padding.Bottom);
-			}
 			Driver.SetAttribute (GetNormalColor ());
 			Driver.SetAttribute (GetNormalColor ());
 		}
 		}
 
 
@@ -240,7 +243,7 @@ namespace Terminal.Gui {
 		///   The text displayed by the <see cref="Label"/>.
 		///   The text displayed by the <see cref="Label"/>.
 		/// </summary>
 		/// </summary>
 		public override ustring Text {
 		public override ustring Text {
-			get => contentView.Text;
+			get => contentView?.Text;
 			set {
 			set {
 				base.Text = value;
 				base.Text = value;
 				if (contentView != null) {
 				if (contentView != null) {

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

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

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

@@ -68,10 +68,10 @@ namespace Terminal.Gui {
 				}
 				}
 				child = value;
 				child = value;
 				savedChild = new SavedPosDim () {
 				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) {
 				if (child == null) {
 					Visible = false;
 					Visible = false;

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

@@ -5,7 +5,7 @@ using System.Linq;
 
 
 namespace Terminal.Gui {
 namespace Terminal.Gui {
 	/// <summary>
 	/// <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>
 	/// </summary>
 	public class RadioGroup : View {
 	public class RadioGroup : View {
 		int selected = -1;
 		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>
 		/// <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 ()
 		public RadioGroup (ustring [] radioLabels, int selected = 0) : base ()
 		{
 		{
-			Initialize (radioLabels, selected);
+			Initialize (Rect.Empty, radioLabels, selected);
 		}
 		}
 
 
 		/// <summary>
 		/// <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>
 		/// <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)
 		public RadioGroup (Rect rect, ustring [] radioLabels, int selected = 0) : base (rect)
 		{
 		{
-			Initialize (radioLabels, selected);
+			Initialize (rect, radioLabels, selected);
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -52,7 +52,7 @@ namespace Terminal.Gui {
 			this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
 			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) {
 			if (radioLabels == null) {
 				this.radioLabels = new List<ustring> ();
 				this.radioLabels = new List<ustring> ();
@@ -61,7 +61,11 @@ namespace Terminal.Gui {
 			}
 			}
 
 
 			this.selected = selected;
 			this.selected = selected;
-			SetWidthHeight (this.radioLabels);
+			if (rect == Rect.Empty) {
+				SetWidthHeight (this.radioLabels);
+			} else {
+				Frame = rect;
+			}
 			CanFocus = true;
 			CanFocus = true;
 
 
 			// Things this view knows how to do
 			// Things this view knows how to do
@@ -102,6 +106,7 @@ namespace Terminal.Gui {
 				if (horizontalSpace != value && displayMode == DisplayModeLayout.Horizontal) {
 				if (horizontalSpace != value && displayMode == DisplayModeLayout.Horizontal) {
 					horizontalSpace = value;
 					horizontalSpace = value;
 					SetWidthHeight (radioLabels);
 					SetWidthHeight (radioLabels);
+					UpdateTextFormatterText ();
 					SetNeedsDisplay ();
 					SetNeedsDisplay ();
 				}
 				}
 			}
 			}
@@ -112,7 +117,7 @@ namespace Terminal.Gui {
 			switch (displayMode) {
 			switch (displayMode) {
 			case DisplayModeLayout.Vertical:
 			case DisplayModeLayout.Vertical:
 				var r = MakeRect (0, 0, radioLabels);
 				var r = MakeRect (0, 0, radioLabels);
-				if (LayoutStyle == LayoutStyle.Computed) {
+				if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
 					Width = r.Width;
 					Width = r.Width;
 					Height = radioLabels.Count;
 					Height = radioLabels.Count;
 				} else {
 				} else {
@@ -126,9 +131,11 @@ namespace Terminal.Gui {
 					length += item.length;
 					length += item.length;
 				}
 				}
 				var hr = new Rect (0, 0, length, 1);
 				var hr = new Rect (0, 0, length, 1);
-				if (LayoutStyle == LayoutStyle.Computed) {
+				if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
 					Width = hr.Width;
 					Width = hr.Width;
 					Height = 1;
 					Height = 1;
+				} else {
+					Frame = new Rect (Frame.Location, new Size (hr.Width, radioLabels.Count));
 				}
 				}
 				break;
 				break;
 			}
 			}
@@ -136,18 +143,17 @@ namespace Terminal.Gui {
 
 
 		static Rect MakeRect (int x, int y, List<ustring> radioLabels)
 		static Rect MakeRect (int x, int y, List<ustring> radioLabels)
 		{
 		{
-			int width = 0;
-
 			if (radioLabels == null) {
 			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)
 			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);
 			return new Rect (x, y, width, radioLabels.Count);
 		}
 		}
 
 
-
 		List<ustring> radioLabels = new List<ustring> ();
 		List<ustring> radioLabels = new List<ustring> ();
 
 
 		/// <summary>
 		/// <summary>
@@ -176,7 +182,7 @@ namespace Terminal.Gui {
 				int length = 0;
 				int length = 0;
 				for (int i = 0; i < radioLabels.Count; i++) {
 				for (int i = 0; i < radioLabels.Count; i++) {
 					start += length;
 					start += length;
-					length = radioLabels [i].RuneCount + horizontalSpace;
+					length = radioLabels [i].ConsoleWidth + 2 + (i < radioLabels.Count - 1 ? horizontalSpace : 0);
 					horizontal.Add ((start, length));
 					horizontal.Add ((start, length));
 				}
 				}
 			}
 			}
@@ -188,7 +194,7 @@ namespace Terminal.Gui {
 		//	for (int i = 0; i < radioLabels.Count; i++) {
 		//	for (int i = 0; i < radioLabels.Count; i++) {
 		//		Move(0, i);
 		//		Move(0, i);
 		//		Driver.SetAttribute(ColorScheme.Normal);
 		//		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) {
 		//	if (newRadioLabels.Count != radioLabels.Count) {
 		//		SetWidthHeight(newRadioLabels);
 		//		SetWidthHeight(newRadioLabels);
@@ -210,7 +216,7 @@ namespace Terminal.Gui {
 					break;
 					break;
 				}
 				}
 				Driver.SetAttribute (GetNormalColor ());
 				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);
 				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;
 			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) {
 				if (scrollBarView.showScrollIndicator) {
 					scrollBarView.ShowScrollIndicator = false;
 					scrollBarView.ShowScrollIndicator = false;
 				}
 				}

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

@@ -193,7 +193,9 @@ namespace Terminal.Gui {
 			if (Tabs.Any ()) {
 			if (Tabs.Any ()) {
 				tabsBar.Redraw (tabsBar.Bounds);
 				tabsBar.Redraw (tabsBar.Bounds);
 				contentView.SetNeedsDisplay ();
 				contentView.SetNeedsDisplay ();
+				var savedClip = contentView.ClipToBounds ();
 				contentView.Redraw (contentView.Bounds);
 				contentView.Redraw (contentView.Bounds);
+				Driver.Clip = savedClip;
 			}
 			}
 		}
 		}
 
 
@@ -338,11 +340,11 @@ namespace Terminal.Gui {
 				var tabTextWidth = tab.Text.Sum (c => Rune.ColumnWidth (c));
 				var tabTextWidth = tab.Text.Sum (c => Rune.ColumnWidth (c));
 
 
 				string text = tab.Text.ToString ();
 				string text = tab.Text.ToString ();
-				
+
 				// The maximum number of characters to use for the tab name as specified
 				// 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
 				// 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!
 				// 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 tab view is width <= 3 don't render any tabs
 				if (maxWidth == 0)
 				if (maxWidth == 0)

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

@@ -68,6 +68,12 @@ namespace Terminal.Gui {
 		/// </summary>
 		/// </summary>
 		public const int DefaultMaxCellWidth = 100;
 		public const int DefaultMaxCellWidth = 100;
 
 
+
+		/// <summary>
+		/// The default minimum cell width for <see cref="ColumnStyle.MinAcceptableWidth"/>
+		/// </summary>
+		public const int DefaultMinAcceptableWidth = 100;
+
 		/// <summary>
 		/// <summary>
 		/// The data table to render in the view.  Setting this property automatically updates and redraws the control.
 		/// The data table to render in the view.  Setting this property automatically updates and redraws the control.
 		/// </summary>
 		/// </summary>
@@ -480,6 +486,8 @@ namespace Terminal.Gui {
 		}
 		}
 		private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender)
 		private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRender)
 		{
 		{
+			var focused = HasFocus;
+
 			var rowScheme = (Style.RowColorGetter?.Invoke (
 			var rowScheme = (Style.RowColorGetter?.Invoke (
 				new RowColorGetterArgs(Table,rowToRender))) ?? ColorScheme;
 				new RowColorGetterArgs(Table,rowToRender))) ?? ColorScheme;
 
 
@@ -489,8 +497,18 @@ namespace Terminal.Gui {
 
 
 			//start by clearing the entire line
 			//start by clearing the entire line
 			Move (0, row);
 			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));
 			Driver.AddStr (new string (' ', Bounds.Width));
 
 
 			// Render cells for each visible header for the current row
 			// Render cells for each visible header for the current row
@@ -530,7 +548,12 @@ namespace Terminal.Gui {
 					scheme = rowScheme;
 					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);
 				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
 				// Reset color scheme to normal for drawing separators if we drew text with custom scheme
 				if (scheme != rowScheme) {
 				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
 				// 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>
 		/// <returns></returns>
 		private TableSelection CreateTableSelection (int pt1X, int pt1Y, int pt2X, int pt2Y)
 		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
 			// 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));
 			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;
 				int colWidth;
 
 
 				// is there enough space for this column (and it's data)?
 				// 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
 				// there is space
 				yield return new ColumnToRender (col, startingIdxForCurrentHeader,
 				yield return new ColumnToRender (col, startingIdxForCurrentHeader,
@@ -1351,10 +1409,24 @@ namespace Terminal.Gui {
 			public int MaxWidth { get; set; } = TableView.DefaultMaxCellWidth;
 			public int MaxWidth { get; set; } = TableView.DefaultMaxCellWidth;
 
 
 			/// <summary>
 			/// <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>
 			/// </summary>
 			public int MinWidth { get; set; }
 			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>
 			/// <summary>
 			/// Returns the alignment for the cell based on <paramref name="cellValue"/> and <see cref="AlignmentGetter"/>/<see cref="Alignment"/>
 			/// Returns the alignment for the cell based on <paramref name="cellValue"/> and <see cref="AlignmentGetter"/>/<see cref="Alignment"/>
 			/// </summary>
 			/// </summary>

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

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

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

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

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

@@ -109,24 +109,115 @@ namespace Terminal.Gui {
 			LayoutSubviews ();
 			LayoutSubviews ();
 		}
 		}
 
 
+		// Get the width of all buttons, not including any spacing
 		internal int GetButtonsWidth ()
 		internal int GetButtonsWidth ()
 		{
 		{
 			if (buttons.Count == 0) {
 			if (buttons.Count == 0) {
 				return 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 ()
 		void LayoutStartedHandler ()
 		{
 		{
+			if (buttons.Count == 0 || !IsInitialized) return;
+
+			int shiftLeft = 0;
+
 			int buttonsWidth = GetButtonsWidth ();
 			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) {
 			switch (kb.Key) {
 			case Key.Esc:
 			case Key.Esc:
-				Running = false;
+				Application.RequestStop (this);
 				return true;
 				return true;
 			}
 			}
 			return base.ProcessKey (kb);
 			return base.ProcessKey (kb);
 		}
 		}
+
 	}
 	}
 }
 }

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

@@ -292,12 +292,12 @@ namespace Terminal.Gui {
 			d.Width = msgboxWidth;
 			d.Width = msgboxWidth;
 
 
 			// Setup actions
 			// Setup actions
-			int clicked = -1;
+			Clicked = -1;
 			for (int n = 0; n < buttonList.Count; n++) {
 			for (int n = 0; n < buttonList.Count; n++) {
 				int buttonId = n;
 				int buttonId = n;
 				var b = buttonList [n];
 				var b = buttonList [n];
 				b.Clicked += () => {
 				b.Clicked += () => {
-					clicked = buttonId;
+					Clicked = buttonId;
 					Application.RequestStop ();
 					Application.RequestStop ();
 				};
 				};
 				if (b.IsDefault) {
 				if (b.IsDefault) {
@@ -307,7 +307,13 @@ namespace Terminal.Gui {
 
 
 			// Run the modal; do not shutdown the mainloop driver when done
 			// Run the modal; do not shutdown the mainloop driver when done
 			Application.Run (d);
 			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": {
     "UICatalog : -usc": {
       "commandName": "Project",
       "commandName": "Project",
       "commandLineArgs": "-usc"
       "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 ()
 		public override void Setup ()
 		{
 		{
 			var text = "Hello World";
 			var text = "Hello World";
+			var wideText = "Hello World 你";
 			var color = Colors.Dialog;
 			var color = Colors.Dialog;
 
 
 			var labelH = new Label (text, TextDirection.LeftRight_TopBottom) {
 			var labelH = new Label (text, TextDirection.LeftRight_TopBottom) {
 				X = 1,
 				X = 1,
 				Y = 1,
 				Y = 1,
+				Width = 11,
+				Height = 1,
 				ColorScheme = color
 				ColorScheme = color
 			};
 			};
 			Win.Add (labelH);
 			Win.Add (labelH);
@@ -19,6 +22,8 @@ namespace UICatalog.Scenarios {
 			var labelV = new Label (text, TextDirection.TopBottom_LeftRight) {
 			var labelV = new Label (text, TextDirection.TopBottom_LeftRight) {
 				X = 70,
 				X = 70,
 				Y = 1,
 				Y = 1,
+				Width = 1,
+				Height = 11,
 				ColorScheme = color
 				ColorScheme = color
 			};
 			};
 			Win.Add (labelV);
 			Win.Add (labelV);
@@ -59,6 +64,33 @@ namespace UICatalog.Scenarios {
 			ckbAutoSize.Toggled += (_) => labelH.AutoSize = labelV.AutoSize = ckbAutoSize.Checked;
 			ckbAutoSize.Toggled += (_) => labelH.AutoSize = labelV.AutoSize = ckbAutoSize.Checked;
 			Win.Add (ckbAutoSize);
 			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 += (_) =>
 			Win.KeyUp += (_) =>
 				labelH.Text = labelV.Text = text = editText.Text.ToString ();
 				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)
 			public void WriteLog (string msg)
 			{
 			{
 				log.Add (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.IO;
 using System.Text;
 using System.Text;
 using NStack;
 using NStack;
+using System.Text.RegularExpressions;
 
 
 namespace UICatalog.Scenarios {
 namespace UICatalog.Scenarios {
 
 
@@ -26,7 +27,7 @@ namespace UICatalog.Scenarios {
 		private MenuItem miLeft;
 		private MenuItem miLeft;
 		private MenuItem miRight;
 		private MenuItem miRight;
 		private MenuItem miCentered;
 		private MenuItem miCentered;
-		private Label selectedCellLabel;
+		private TextField selectedCellLabel;
 
 
 		public override void Setup ()
 		public override void Setup ()
 		{
 		{
@@ -78,14 +79,14 @@ namespace UICatalog.Scenarios {
 
 
 			Win.Add (tableView);
 			Win.Add (tableView);
 
 
-			selectedCellLabel = new Label(){
+			selectedCellLabel = new TextField(){
 				X = 0,
 				X = 0,
 				Y = Pos.Bottom(tableView),
 				Y = Pos.Bottom(tableView),
 				Text = "0,0",
 				Text = "0,0",
 				Width = Dim.Fill(),
 				Width = Dim.Fill(),
-				TextAlignment = TextAlignment.Right
-				
+				TextAlignment = TextAlignment.Right				
 			};
 			};
+			selectedCellLabel.TextChanged += SelectedCellLabel_TextChanged;
 
 
 			Win.Add(selectedCellLabel);
 			Win.Add(selectedCellLabel);
 
 
@@ -96,10 +97,26 @@ namespace UICatalog.Scenarios {
 			SetupScrollBar();
 			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)
 		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)
 			if(tableView.Table == null || tableView.SelectedColumn == -1)
 				return;
 				return;

+ 85 - 21
UICatalog/Scenarios/Dialogs.cs

@@ -9,13 +9,13 @@ namespace UICatalog.Scenarios {
 	[ScenarioMetadata (Name: "Dialogs", Description: "Demonstrates how to the Dialog class")]
 	[ScenarioMetadata (Name: "Dialogs", Description: "Demonstrates how to the Dialog class")]
 	[ScenarioCategory ("Dialogs")]
 	[ScenarioCategory ("Dialogs")]
 	public class Dialogs : Scenario {
 	public class Dialogs : Scenario {
+		static int CODE_POINT = '你'; // We know this is a wide char
 		public override void Setup ()
 		public override void Setup ()
 		{
 		{
 			var frame = new FrameView ("Dialog Options") {
 			var frame = new FrameView ("Dialog Options") {
 				X = Pos.Center (),
 				X = Pos.Center (),
-				Y = 1,
-				Width = Dim.Percent (75),
-				Height = 10
+				Y = 0,
+				Width = Dim.Percent (75)
 			};
 			};
 			Win.Add (frame);
 			Win.Add (frame);
 
 
@@ -92,10 +92,30 @@ namespace UICatalog.Scenarios {
 			};
 			};
 			frame.Add (numButtonsEdit);
 			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 ()
 			void Top_Loaded ()
 			{
 			{
 				frame.Height = Dim.Height (widthEdit) + Dim.Height (heightEdit) + Dim.Height (titleEdit)
 				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;
 			}
 			}
 			Top.Loaded += Top_Loaded;
 			Top.Loaded += Top_Loaded;
@@ -114,8 +134,11 @@ namespace UICatalog.Scenarios {
 				Height = 1,
 				Height = 1,
 				ColorScheme = Colors.Error,
 				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") {
 			var showDialogButton = new Button ("Show Dialog") {
 				X = Pos.Center (),
 				X = Pos.Center (),
 				Y = Pos.Bottom (frame) + 2,
 				Y = Pos.Bottom (frame) + 2,
@@ -123,51 +146,92 @@ namespace UICatalog.Scenarios {
 			};
 			};
 			showDialogButton.Clicked += () => {
 			showDialogButton.Clicked += () => {
 				try {
 				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 buttons = new List<Button> ();
 					var clicked = -1;
 					var clicked = -1;
 					for (int i = 0; i < numButtons; i++) {
 					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 += () => {
 						button.Clicked += () => {
 							clicked = buttonId;
 							clicked = buttonId;
 							Application.RequestStop ();
 							Application.RequestStop ();
 						};
 						};
 						buttons.Add (button);
 						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 
 					// This tests dynamically adding buttons; ensuring the dialog resizes if needed and 
 					// the buttons are laid out correctly
 					// 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") {
 					var add = new Button ("Add a button") {
 						X = Pos.Center (),
 						X = Pos.Center (),
 						Y = Pos.Center ()
 						Y = Pos.Center ()
 					};
 					};
 					add.Clicked += () => {
 					add.Clicked += () => {
 						var buttonId = buttons.Count;
 						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 += () => {
 						button.Clicked += () => {
 							clicked = buttonId;
 							clicked = buttonId;
 							Application.RequestStop ();
 							Application.RequestStop ();
+
 						};
 						};
 						buttons.Add (button);
 						buttons.Add (button);
 						dialog.AddButton (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);
 					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);
 					Application.Run (dialog);
-					buttonPressedLabel.Text = $"{clicked}";
 
 
 				} catch (FormatException) {
 				} catch (FormatException) {
 					buttonPressedLabel.Text = "Invalid Options";
 					buttonPressedLabel.Text = "Invalid Options";

+ 36 - 24
UICatalog/Scenarios/Editor.cs

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

+ 4 - 0
UICatalog/Scenarios/LabelsAsButtons.cs

@@ -90,6 +90,8 @@ namespace UICatalog.Scenarios {
 				Y = Pos.Bottom (Label) + 1,
 				Y = Pos.Bottom (Label) + 1,
 				HotKeySpecifier = (System.Rune)'_',
 				HotKeySpecifier = (System.Rune)'_',
 				CanFocus = true,
 				CanFocus = true,
+				TextAlignment = TextAlignment.Centered,
+				VerticalTextAlignment = VerticalTextAlignment.Middle
 			});
 			});
 			Label.Clicked += () => MessageBox.Query ("Message", "Question?", "Yes", "No");
 			Label.Clicked += () => MessageBox.Query ("Message", "Question?", "Yes", "No");
 
 
@@ -159,6 +161,7 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Error,
 				ColorScheme = Colors.Error,
 				HotKeySpecifier = (System.Rune)'_',
 				HotKeySpecifier = (System.Rune)'_',
 				CanFocus = true,
 				CanFocus = true,
+				AutoSize = false
 			};
 			};
 			sizeBtn.Clicked += () => {
 			sizeBtn.Clicked += () => {
 				sizeBtn.Width = sizeBtn.Frame.Width + 5;
 				sizeBtn.Width = sizeBtn.Frame.Width + 5;
@@ -190,6 +193,7 @@ namespace UICatalog.Scenarios {
 				ColorScheme = Colors.Error,
 				ColorScheme = Colors.Error,
 				HotKeySpecifier = (System.Rune)'_',
 				HotKeySpecifier = (System.Rune)'_',
 				CanFocus = true,
 				CanFocus = true,
+				AutoSize = false
 			};
 			};
 			sizeBtnA.Clicked += () => {
 			sizeBtnA.Clicked += () => {
 				sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height);
 				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,
 				Y = 0,
 				Width = Dim.Fill (),  // FIXED: I don't think this should be needed; DimFill() should respect container's frame. X does.
 				Width = Dim.Fill (),  // FIXED: I don't think this should be needed; DimFill() should respect container's frame. X does.
 				Height = 2,
 				Height = 2,
-				ColorScheme = Colors.Error
+				ColorScheme = Colors.Error,
+				AutoSize = false
 			};
 			};
 			scrollView.Add (horizontalRuler);
 			scrollView.Add (horizontalRuler);
 			const string vrule = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n";
 			const string vrule = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n";
@@ -139,7 +140,8 @@ namespace UICatalog.Scenarios {
 				Y = 0,
 				Y = 0,
 				Width = 1,
 				Width = 1,
 				Height = Dim.Fill (),
 				Height = Dim.Fill (),
-				ColorScheme = Colors.Error
+				ColorScheme = Colors.Error,
+				AutoSize = false
 			};
 			};
 			scrollView.Add (verticalRuler);
 			scrollView.Add (verticalRuler);
 
 

+ 311 - 3
UICatalog/Scenarios/TableEditor.cs

@@ -4,6 +4,7 @@ using System.Data;
 using Terminal.Gui;
 using Terminal.Gui;
 using System.Linq;
 using System.Linq;
 using System.Globalization;
 using System.Globalization;
+using static Terminal.Gui.TableView;
 
 
 namespace UICatalog.Scenarios {
 namespace UICatalog.Scenarios {
 
 
@@ -24,6 +25,7 @@ namespace UICatalog.Scenarios {
 		private MenuItem miCellLines;
 		private MenuItem miCellLines;
 		private MenuItem miFullRowSelect;
 		private MenuItem miFullRowSelect;
 		private MenuItem miExpandLastColumn;
 		private MenuItem miExpandLastColumn;
+		private MenuItem miSmoothScrolling;
 		private MenuItem miAlternatingColors;
 		private MenuItem miAlternatingColors;
 		private MenuItem miCursor;
 		private MenuItem miCursor;
 
 
@@ -49,6 +51,7 @@ namespace UICatalog.Scenarios {
 				new MenuBarItem ("_File", new MenuItem [] {
 				new MenuBarItem ("_File", new MenuItem [] {
 					new MenuItem ("_OpenBigExample", "", () => OpenExample(true)),
 					new MenuItem ("_OpenBigExample", "", () => OpenExample(true)),
 					new MenuItem ("_OpenSmallExample", "", () => OpenExample(false)),
 					new MenuItem ("_OpenSmallExample", "", () => OpenExample(false)),
+					new MenuItem ("OpenCharacter_Map","",()=>OpenUnicodeMap()),
 					new MenuItem ("_CloseExample", "", () => CloseExample()),
 					new MenuItem ("_CloseExample", "", () => CloseExample()),
 					new MenuItem ("_Quit", "", () => Quit()),
 					new MenuItem ("_Quit", "", () => Quit()),
 				}),
 				}),
@@ -61,14 +64,23 @@ namespace UICatalog.Scenarios {
 					miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked },
 					miFullRowSelect =new MenuItem ("_FullRowSelect", "", () => ToggleFullRowSelect()){Checked = tableView.FullRowSelect, CheckType = MenuItemCheckStyle.Checked },
 					miCellLines =new MenuItem ("_CellLines", "", () => ToggleCellLines()){Checked = tableView.Style.ShowVerticalCellLines, 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 },
 					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 ("_AllLines", "", () => ToggleAllCellLines()),
 					new MenuItem ("_NoLines", "", () => ToggleNoCellLines()),
 					new MenuItem ("_NoLines", "", () => ToggleNoCellLines()),
 					miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors()){CheckType = MenuItemCheckStyle.Checked},
 					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},
 					miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter()){Checked = tableView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked},
 					new MenuItem ("_ClearColumnStyles", "", () => ClearColumnStyles()),
 					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 [] {
 			var statusBar = new StatusBar (new StatusItem [] {
 				new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)),
 				new StatusItem(Key.F2, "~F2~ OpenExample", () => OpenExample(true)),
@@ -91,7 +103,7 @@ namespace UICatalog.Scenarios {
 
 
 			Win.Add(selectedCellLabel);
 			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.CellActivated += EditCurrentCell;
 			tableView.KeyPress += TableViewKeyPress;
 			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 ()
 		private void SetupScrollBar ()
 		{
 		{
 			var _scrollBar = new ScrollBarView (tableView, true);
 			var _scrollBar = new ScrollBarView (tableView, true);
@@ -227,6 +318,14 @@ namespace UICatalog.Scenarios {
 
 
 			tableView.Update();
 			tableView.Update();
 
 
+		}
+		private void ToggleSmoothScrolling()
+		{
+			miSmoothScrolling.Checked = !miSmoothScrolling.Checked;
+			tableView.Style.SmoothHorizontalScrolling = miSmoothScrolling.Checked;
+
+			tableView.Update ();
+
 		}
 		}
 		private void ToggleCellLines()
 		private void ToggleCellLines()
 		{
 		{
@@ -301,6 +400,207 @@ namespace UICatalog.Scenarios {
 			SetDemoTableStyles();
 			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 ()
 		private void SetDemoTableStyles ()
 		{
 		{
 			var alignMid = new TableView.ColumnStyle () {
 			var alignMid = new TableView.ColumnStyle () {
@@ -353,6 +653,9 @@ namespace UICatalog.Scenarios {
 		{
 		{
 			if(e.Table == null)
 			if(e.Table == null)
 				return;
 				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();
 			var oldValue = e.Table.Rows[e.Row][e.Col].ToString();
 			bool okPressed = false;
 			bool okPressed = false;
@@ -361,7 +664,7 @@ namespace UICatalog.Scenarios {
 			ok.Clicked += () => { okPressed = true; Application.RequestStop (); };
 			ok.Clicked += () => { okPressed = true; Application.RequestStop (); };
 			var cancel = new Button ("Cancel");
 			var cancel = new Button ("Cancel");
 			cancel.Clicked += () => { Application.RequestStop (); };
 			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() {
 			var lbl = new Label() {
 				X = 0,
 				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>
 		/// <summary>
 		/// Generates a new demo <see cref="DataTable"/> with the given number of <paramref name="cols"/> (min 5) and <paramref name="rows"/>
 		/// Generates a new demo <see cref="DataTable"/> with the given number of <paramref name="cols"/> (min 5) and <paramref name="rows"/>
 		/// </summary>
 		/// </summary>

+ 2 - 2
UICatalog/Scenarios/TextAlignments.cs

@@ -22,8 +22,8 @@ namespace UICatalog.Scenarios {
 			var multiLineHeight = 5;
 			var multiLineHeight = 5;
 
 
 			foreach (var alignment in alignments) {
 			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
 			// 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
 			// 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 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 };
 			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 text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line.";
 			string unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου.";
 			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 ();
 			var block = new StringBuilder ();
 			block.AppendLine ("  ▄████  █    ██  ██▓      ▄████▄    ██████ ");
 			block.AppendLine ("  ▄████  █    ██  ██▓      ▄████▄    ██████ ");
@@ -56,8 +56,8 @@ namespace UICatalog.Scenarios {
 			var multiLineHeight = 5;
 			var multiLineHeight = 5;
 
 
 			foreach (var alignment in alignments) {
 			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 };
 			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);
 			var rs = Application.Begin (Application.Top);
 			Assert.Equal (Application.Top, rs.Toplevel);
 			Assert.Equal (Application.Top, rs.Toplevel);
 			Assert.Null (Application.mouseGrabView);
 			Assert.Null (Application.mouseGrabView);
-			Assert.Null (Application.wantContinuousButtonPressedView);
+			Assert.Null (Application.WantContinuousButtonPressedView);
 			Assert.False (Application.DebugDrawBounds);
 			Assert.False (Application.DebugDrawBounds);
 			Assert.False (Application.ShowChild (Application.Top));
 			Assert.False (Application.ShowChild (Application.Top));
 			Application.End (Application.Top);
 			Application.End (Application.Top);
@@ -1293,45 +1293,47 @@ namespace Terminal.Gui.Core {
 			int numberOfTimeoutsPerThread = 100;
 			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
 // 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.
 // as a pair, and b) all unit test functions should be atomic.
 [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
 [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;
 	static bool _init = false;
 	public override void Before (MethodInfo methodUnderTest)
 	public override void Before (MethodInfo methodUnderTest)

+ 335 - 17
UnitTests/ButtonTests.cs

@@ -17,47 +17,57 @@ namespace Terminal.Gui.Views {
 			var btn = new Button ();
 			var btn = new Button ();
 			Assert.Equal (string.Empty, btn.Text);
 			Assert.Equal (string.Empty, btn.Text);
 			Application.Top.Add (btn);
 			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.False (btn.IsDefault);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.True (btn.CanFocus);
 			Assert.True (btn.CanFocus);
 			Assert.Equal (new Rect (0, 0, 4, 1), btn.Frame);
 			Assert.Equal (new Rect (0, 0, 4, 1), btn.Frame);
 			Assert.Equal (Key.Null, btn.HotKey);
 			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);
 			Assert.Equal ("Test", btn.Text);
 			Application.Top.Add (btn);
 			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.True (btn.IsDefault);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.True (btn.CanFocus);
 			Assert.True (btn.CanFocus);
 			Assert.Equal (new Rect (0, 0, 10, 1), btn.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 1), btn.Frame);
 			Assert.Equal (Key.T, btn.HotKey);
 			Assert.Equal (Key.T, btn.HotKey);
+			expected = @"
+[◦ Test ◦]
+";
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 
 
+			Application.End (rs);
 			btn = new Button (3, 4, "Test", true);
 			btn = new Button (3, 4, "Test", true);
 			Assert.Equal ("Test", btn.Text);
 			Assert.Equal ("Test", btn.Text);
 			Application.Top.Add (btn);
 			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.True (btn.IsDefault);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal (TextAlignment.Centered, btn.TextAlignment);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.Equal ('_', btn.HotKeySpecifier);
 			Assert.True (btn.CanFocus);
 			Assert.True (btn.CanFocus);
 			Assert.Equal (new Rect (3, 4, 10, 1), btn.Frame);
 			Assert.Equal (new Rect (3, 4, 10, 1), btn.Frame);
 			Assert.Equal (Key.T, btn.HotKey);
 			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]
 		[Fact]
@@ -166,7 +176,7 @@ namespace Terminal.Gui.Views {
 		[Fact]
 		[Fact]
 		public void TestAssignTextToButton ()
 		public void TestAssignTextToButton ()
 		{
 		{
-			View b = new Button () {Text="heya"};
+			View b = new Button () { Text = "heya" };
 			Assert.Equal ("heya", b.Text);
 			Assert.Equal ("heya", b.Text);
 			Assert.True (b.TextFormatter.Text.Contains ("heya"));
 			Assert.True (b.TextFormatter.Text.Contains ("heya"));
 			b.Text = "heyb";
 			b.Text = "heyb";
@@ -228,7 +238,7 @@ namespace Terminal.Gui.Views {
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 		}
-		
+
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
 		public void Update_Parameterless_Only_On_Or_After_Initialize ()
 		public void Update_Parameterless_Only_On_Or_After_Initialize ()
 		{
 		{
@@ -266,5 +276,313 @@ namespace Terminal.Gui.Views {
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 			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.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class CheckboxTests {
 	public class CheckboxTests {
+		readonly ITestOutputHelper output;
+
+		public CheckboxTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		[Fact]
 		public void Constructors_Defaults ()
 		public void Constructors_Defaults ()
 		{
 		{
 			var ckb = new CheckBox ();
 			var ckb = new CheckBox ();
+			Assert.True (ckb.AutoSize);
 			Assert.False (ckb.Checked);
 			Assert.False (ckb.Checked);
 			Assert.Equal (string.Empty, ckb.Text);
 			Assert.Equal (string.Empty, ckb.Text);
+			Assert.Equal ("╴ ", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
 			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);
 			ckb = new CheckBox ("Test", true);
+			Assert.True (ckb.AutoSize);
 			Assert.True (ckb.Checked);
 			Assert.True (ckb.Checked);
 			Assert.Equal ("Test", ckb.Text);
 			Assert.Equal ("Test", ckb.Text);
+			Assert.Equal ("√ Test", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
 			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");
 			ckb = new CheckBox (1, 2, "Test");
+			Assert.True (ckb.AutoSize);
 			Assert.False (ckb.Checked);
 			Assert.False (ckb.Checked);
 			Assert.Equal ("Test", ckb.Text);
 			Assert.Equal ("Test", ckb.Text);
+			Assert.Equal ("╴ Test", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
 			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);
 			ckb = new CheckBox (3, 4, "Test", true);
+			Assert.True (ckb.AutoSize);
 			Assert.True (ckb.Checked);
 			Assert.True (ckb.Checked);
 			Assert.Equal ("Test", ckb.Text);
 			Assert.Equal ("Test", ckb.Text);
+			Assert.Equal ("√ Test", ckb.TextFormatter.Text);
 			Assert.True (ckb.CanFocus);
 			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]
 		[Fact]
@@ -40,17 +56,19 @@ namespace Terminal.Gui.Views {
 		public void KeyBindings_Command ()
 		public void KeyBindings_Command ()
 		{
 		{
 			var isChecked = false;
 			var isChecked = false;
-			CheckBox ckb = new CheckBox ("Test");
+			CheckBox ckb = new CheckBox ();
 			ckb.Toggled += (e) => isChecked = true;
 			ckb.Toggled += (e) => isChecked = true;
 			Application.Top.Add (ckb);
 			Application.Top.Add (ckb);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 
 
 			Assert.Equal (Key.Null, ckb.HotKey);
 			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 (ckb.ProcessHotKey (new KeyEvent (Key.T, new KeyModifiers ())));
 			Assert.False (isChecked);
 			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);
 			Assert.True (isChecked);
 			isChecked = false;
 			isChecked = false;
 			Assert.True (ckb.ProcessKey (new KeyEvent ((Key)' ', new KeyModifiers ())));
 			Assert.True (ckb.ProcessKey (new KeyEvent ((Key)' ', new KeyModifiers ())));
@@ -58,6 +76,441 @@ namespace Terminal.Gui.Views {
 			isChecked = false;
 			isChecked = false;
 			Assert.True (ckb.ProcessKey (new KeyEvent (Key.Space, new KeyModifiers ())));
 			Assert.True (ckb.ProcessKey (new KeyEvent (Key.Space, new KeyModifiers ())));
 			Assert.True (isChecked);
 			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;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
-using Terminal.Gui;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class ComboBoxTests {
 	public class ComboBoxTests {
+		ITestOutputHelper output;
+
+		public ComboBoxTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		[Fact]
 		public void Constructors_Defaults ()
 		public void Constructors_Defaults ()
 		{
 		{
 			var cb = new ComboBox ();
 			var cb = new ComboBox ();
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.Null (cb.Source);
 			Assert.Null (cb.Source);
+			Assert.False (cb.AutoSize);
+			Assert.Equal (new Rect (0, 0, 0, 2), cb.Frame);
 
 
 			cb = new ComboBox ("Test");
 			cb = new ComboBox ("Test");
 			Assert.Equal ("Test", cb.Text);
 			Assert.Equal ("Test", cb.Text);
 			Assert.Null (cb.Source);
 			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" });
 			cb = new ComboBox (new Rect (1, 2, 10, 20), new List<string> () { "One", "Two", "Three" });
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.Equal (string.Empty, cb.Text);
 			Assert.NotNull (cb.Source);
 			Assert.NotNull (cb.Source);
+			Assert.False (cb.AutoSize);
+			Assert.Equal (new Rect (1, 2, 10, 20), cb.Frame);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -44,12 +57,12 @@ namespace Terminal.Gui.Views {
 		public void KeyBindings_Command ()
 		public void KeyBindings_Command ()
 		{
 		{
 			List<string> source = new List<string> () { "One", "Two", "Three" };
 			List<string> source = new List<string> () { "One", "Two", "Three" };
-			ComboBox cb = new ComboBox ();
+			ComboBox cb = new ComboBox () { Width = 10 };
 			cb.SetSource (source);
 			cb.SetSource (source);
 			Application.Top.Add (cb);
 			Application.Top.Add (cb);
 			Application.Top.FocusFirst ();
 			Application.Top.FocusFirst ();
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
-			Assert.Equal(string.Empty,cb.Text);
+			Assert.Equal (string.Empty, cb.Text);
 			var opened = false;
 			var opened = false;
 			cb.OpenSelectedItem += (_) => opened = true;
 			cb.OpenSelectedItem += (_) => opened = true;
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			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.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); // with no source also expand empty
 			Assert.True (cb.IsShow);
 			Assert.True (cb.IsShow);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
-			cb.SetSource(source);
+			cb.SetSource (source);
 			cb.Text = "";
 			cb.Text = "";
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); // collapse
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.F4, new KeyModifiers ()))); // collapse
 			Assert.False (cb.IsShow);
 			Assert.False (cb.IsShow);
@@ -106,8 +119,33 @@ namespace Terminal.Gui.Views {
 			Assert.True (cb.IsShow);
 			Assert.True (cb.IsShow);
 			Assert.Equal (0, cb.SelectedItem);
 			Assert.Equal (0, cb.SelectedItem);
 			Assert.Equal ("One", cb.Text);
 			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.ProcessKey (new KeyEvent (Key.PageDown, new KeyModifiers ())));
 			Assert.True (cb.IsShow);
 			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 (1, cb.SelectedItem);
 			Assert.Equal ("Two", cb.Text);
 			Assert.Equal ("Two", cb.Text);
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.PageUp, new KeyModifiers ())));
 			Assert.True (cb.ProcessKey (new KeyEvent (Key.PageUp, new KeyModifiers ())));
@@ -167,10 +205,10 @@ namespace Terminal.Gui.Views {
 			var cb = new ComboBox ();
 			var cb = new ComboBox ();
 			Application.Top.Add (cb);
 			Application.Top.Add (cb);
 			Application.Top.FocusFirst ();
 			Application.Top.FocusFirst ();
-			Assert.Null(cb.Source);
+			Assert.Null (cb.Source);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
 			var source = new List<string> ();
 			var source = new List<string> ();
-			cb.SetSource(source);
+			cb.SetSource (source);
 			Assert.NotNull (cb.Source);
 			Assert.NotNull (cb.Source);
 			Assert.Equal (0, cb.Source.Count);
 			Assert.Equal (0, cb.Source.Count);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
@@ -197,7 +235,7 @@ namespace Terminal.Gui.Views {
 			Assert.False (cb.IsShow);
 			Assert.False (cb.IsShow);
 			Assert.Equal (1, cb.SelectedItem); // retains last accept selected item
 			Assert.Equal (1, cb.SelectedItem); // retains last accept selected item
 			Assert.Equal ("", cb.Text); // clear text
 			Assert.Equal ("", cb.Text); // clear text
-			cb.SetSource(new List<string> ());
+			cb.SetSource (new List<string> ());
 			Assert.Equal (0, cb.Source.Count);
 			Assert.Equal (0, cb.Source.Count);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal (-1, cb.SelectedItem);
 			Assert.Equal ("", cb.Text);
 			Assert.Equal ("", cb.Text);

+ 2 - 2
UnitTests/ConsoleDriverTests.cs

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

+ 15 - 15
UnitTests/ContextMenuTests.cs

@@ -309,7 +309,7 @@ namespace Terminal.Gui.Core {
                                                                       │ One  │
                                                                       │ One  │
                                                                       │ Two  │
                                                                       │ Two  │
                                                                       └──────┘
                                                                       └──────┘
-                                                                      View
+                                                                      View    
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -340,7 +340,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (10, 5), cm.Position);
 			Assert.Equal (new Point (10, 5), cm.Position);
 
 
 			var expected = @"
 			var expected = @"
-          View
+          View    
           ┌──────┐
           ┌──────┐
           │ One  │
           │ One  │
           │ Two  │
           │ Two  │
@@ -361,9 +361,9 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (5, 12), cm.Position);
 			Assert.Equal (new Point (5, 12), cm.Position);
 
 
 			expected = @"
 			expected = @"
-     View
-
-
+     View    
+             
+             
      ┌──────┐
      ┌──────┐
      │ One  │
      │ One  │
      │ Two  │
      │ Two  │
@@ -592,10 +592,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Assert.Equal (new Point (9, 3), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  File   Edit
-
-
-  Label: TextField
+  File   Edit                         
+                                      
+                                      
+  Label: TextField                    
          ┌───────────────────────────┐
          ┌───────────────────────────┐
          │ Select All         Ctrl+T │
          │ Select All         Ctrl+T │
          │ Delete All   Ctrl+Shift+D │
          │ Delete All   Ctrl+Shift+D │
@@ -605,10 +605,10 @@ namespace Terminal.Gui.Core {
          │ Undo               Ctrl+Z │
          │ Undo               Ctrl+Z │
          │ Redo               Ctrl+Y │
          │ Redo               Ctrl+Y │
          └───────────────────────────┘
          └───────────────────────────┘
-
-
-
- F1 Help │ ^Q Quit
+                                      
+                                      
+                                      
+ F1 Help │ ^Q Quit                    
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -657,7 +657,7 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Point (10, 5), tf.ContextMenu.Position);
 			Assert.Equal (new Point (10, 5), tf.ContextMenu.Position);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
-  File   Edit
+  File   Edit                               
 ┌ Window ──────────────────────────────────┐
 ┌ Window ──────────────────────────────────┐
 │                                          │
 │                                          │
 │                                          │
 │                                          │
@@ -673,7 +673,7 @@ namespace Terminal.Gui.Core {
 │         │ Redo               Ctrl+Y │    │
 │         │ Redo               Ctrl+Y │    │
 │         └───────────────────────────┘    │
 │         └───────────────────────────┘    │
 └──────────────────────────────────────────┘
 └──────────────────────────────────────────┘
- F1 Help │ ^Q Quit
+ F1 Help │ ^Q Quit                          
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			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.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Globalization;
 using System.Globalization;
@@ -6,15 +6,20 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using Terminal.Gui;
 using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
 namespace Terminal.Gui.Core {
 namespace Terminal.Gui.Core {
 	public class DimTests {
 	public class DimTests {
-		public DimTests ()
+		readonly ITestOutputHelper output;
+
+		public DimTests (ITestOutputHelper output)
 		{
 		{
+			this.output = output;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			Console.OutputEncoding = System.Text.Encoding.Default;
 			// Change current culture
 			// Change current culture
 			CultureInfo culture = CultureInfo.CreateSpecificCulture ("en-US");
 			CultureInfo culture = CultureInfo.CreateSpecificCulture ("en-US");
@@ -245,7 +250,7 @@ namespace Terminal.Gui.Core {
 		}
 		}
 
 
 		[Fact]
 		[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)));
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 
@@ -257,7 +262,8 @@ namespace Terminal.Gui.Core {
 			};
 			};
 			var v = new View ("v") {
 			var v = new View ("v") {
 				Width = Dim.Width (w) - 2,
 				Width = Dim.Width (w) - 2,
-				Height = Dim.Percent (10)
+				Height = Dim.Percent (10),
+				ForceValidatePosDim = true
 			};
 			};
 
 
 			w.Add (v);
 			w.Add (v);
@@ -268,6 +274,13 @@ namespace Terminal.Gui.Core {
 				Assert.Equal (2, w.Height = 2);
 				Assert.Equal (2, w.Height = 2);
 				Assert.Throws<ArgumentException> (() => v.Width = 2);
 				Assert.Throws<ArgumentException> (() => v.Width = 2);
 				Assert.Throws<ArgumentException> (() => v.Height = 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 ();
 			Application.Iteration += () => Application.RequestStop ();
@@ -357,39 +370,51 @@ namespace Terminal.Gui.Core {
 			};
 			};
 
 
 			var v1 = new Button ("v1") {
 			var v1 = new Button ("v1") {
+				AutoSize = false,
 				X = Pos.X (f1) + 2,
 				X = Pos.X (f1) + 2,
 				Y = Pos.Bottom (f1) + 2,
 				Y = Pos.Bottom (f1) + 2,
 				Width = Dim.Width (f1) - 2,
 				Width = Dim.Width (f1) - 2,
-				Height = Dim.Fill () - 2
+				Height = Dim.Fill () - 2,
+				ForceValidatePosDim = true
 			};
 			};
 
 
 			var v2 = new Button ("v2") {
 			var v2 = new Button ("v2") {
+				AutoSize = false,
 				X = Pos.X (f2) + 2,
 				X = Pos.X (f2) + 2,
 				Y = Pos.Bottom (f2) + 2,
 				Y = Pos.Bottom (f2) + 2,
 				Width = Dim.Width (f2) - 2,
 				Width = Dim.Width (f2) - 2,
-				Height = Dim.Fill () - 2
+				Height = Dim.Fill () - 2,
+				ForceValidatePosDim = true
 			};
 			};
 
 
 			var v3 = new Button ("v3") {
 			var v3 = new Button ("v3") {
+				AutoSize = false,
 				Width = Dim.Percent (10),
 				Width = Dim.Percent (10),
-				Height = Dim.Percent (10)
+				Height = Dim.Percent (10),
+				ForceValidatePosDim = true
 			};
 			};
 
 
 			var v4 = new Button ("v4") {
 			var v4 = new Button ("v4") {
+				AutoSize = false,
 				Width = Dim.Sized (50),
 				Width = Dim.Sized (50),
-				Height = Dim.Sized (50)
+				Height = Dim.Sized (50),
+				ForceValidatePosDim = true
 			};
 			};
 
 
 			var v5 = new Button ("v5") {
 			var v5 = new Button ("v5") {
+				AutoSize = false,
 				Width = Dim.Width (v1) - Dim.Width (v3),
 				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") {
 			var v6 = new Button ("v6") {
+				AutoSize = false,
 				X = Pos.X (f2),
 				X = Pos.X (f2),
 				Y = Pos.Bottom (f2) + 2,
 				Y = Pos.Bottom (f2) + 2,
 				Width = Dim.Percent (20, true),
 				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);
 			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 (47, v1.Frame.Width); // 49-2=47
 				Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89
 				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(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 ("Dim.Combine(Dim.Fill(margin=0)-Dim.Absolute(2))", v2.Height.ToString ());
 				Assert.Equal (47, v2.Frame.Width); // 49-2=47
 				Assert.Equal (47, v2.Frame.Width); // 49-2=47
@@ -466,26 +490,28 @@ namespace Terminal.Gui.Core {
 
 
 				v1.Text = "Button1";
 				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.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 (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";
 				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.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 (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";
 				v3.Text = "Button3";
 				Assert.Equal ("Dim.Factor(factor=0.1, remaining=False)", v3.Width.ToString ());
 				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 (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.Text = "Button4";
 				v4.AutoSize = false;
 				v4.AutoSize = false;
 				Assert.Equal ("Dim.Absolute(50)", v4.Width.ToString ());
 				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;
 				v4.AutoSize = true;
 				Assert.Equal ("Dim.Absolute(11)", v4.Width.ToString ());
 				Assert.Equal ("Dim.Absolute(11)", v4.Width.ToString ());
 				Assert.Equal ("Dim.Absolute(1)", v4.Height.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
 				Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute
 
 
 				v5.Text = "Button5";
 				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 (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";
 				v6.Text = "Button6";
 				Assert.Equal ("Dim.Factor(factor=0.2, remaining=True)", v6.Width.ToString ());
 				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 (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 ();
 			Application.Iteration += () => Application.RequestStop ();
@@ -641,6 +667,391 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 			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]
 		[Fact]
 		public void Dim_Subtract_Operator ()
 		public void Dim_Subtract_Operator ()
 		{
 		{
@@ -701,6 +1112,88 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 			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]
 		[Fact]
 		public void Internal_Tests ()
 		public void Internal_Tests ()
 		{
 		{
@@ -724,5 +1217,34 @@ namespace Terminal.Gui.Core {
 			var dimViewWidth = new Dim.DimView (view, 1);
 			var dimViewWidth = new Dim.DimView (view, 1);
 			Assert.Equal (20, dimViewWidth.Anchor (0));
 			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 {
 	public class GraphViewTests {
 
 
+		static string LastInitFakeDriver;
 
 
 		public static FakeDriver InitFakeDriver ()
 		public static FakeDriver InitFakeDriver ()
 		{
 		{
 			var driver = new FakeDriver ();
 			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 (() => { });
 			driver.Init (() => { });
+
+			LastInitFakeDriver = Environment.StackTrace;
 			return driver;
 			return driver;
 		}
 		}
 
 
@@ -169,7 +184,8 @@ namespace Terminal.Gui.Views {
 			for (int r = 0; r < lines.Count; r++) {
 			for (int r = 0; r < lines.Count; r++) {
 				List<char> row = lines [r];
 				List<char> row = lines [r];
 				for (int c = row.Count - 1; c >= 0; c--) {
 				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;
 						break;
 					}
 					}
 					row.RemoveAt (c);
 					row.RemoveAt (c);
@@ -240,7 +256,7 @@ namespace Terminal.Gui.Views {
 
 
 					var match = expectedColors.Where (e => e.Value == val).ToList ();
 					var match = expectedColors.Where (e => e.Value == val).ToList ();
 					if (match.Count == 0) {
 					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) {
 					} else if (match.Count > 1) {
 						throw new ArgumentException ($"Bad value for expectedColors, {match.Count} Attributes had the same Value");
 						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];
 					var userExpected = line [c];
 
 
 					if (colorUsed != userExpected) {
 					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
 		#region Screen to Graph Tests
 
 
 		[Fact]
 		[Fact]

+ 337 - 39
UnitTests/MenuTests.cs

@@ -177,6 +177,99 @@ Edit
 			void Copy () => miAction = "Copy";
 			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]
 		[Fact]
 		[AutoInitShutdown]
 		[AutoInitShutdown]
 		public void MouseEvent_Test ()
 		public void MouseEvent_Test ()
@@ -293,6 +386,7 @@ Edit
 				miCurrent = null;
 				miCurrent = null;
 				mCurrent = null;
 				mCurrent = null;
 			};
 			};
+			menu.UseKeysUpDownAsKeysLeftRight = true;
 			Application.Top.Add (menu);
 			Application.Top.Add (menu);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 
 
@@ -304,7 +398,7 @@ Edit
 			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
 			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.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
@@ -363,7 +457,7 @@ Edit
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
-			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Assert.Equal ("_About", GetCurrentMenuTitle ());
 
 
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorRight, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
@@ -393,7 +487,7 @@ Edit
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.CursorLeft, new KeyModifiers ())));
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
 			Assert.Equal ("_About", GetCurrentMenuBarItemTitle ());
-			Assert.Equal ("None", GetCurrentMenuTitle ());
+			Assert.Equal ("_About", GetCurrentMenuTitle ());
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.True (mCurrent.ProcessKey (new KeyEvent (Key.Enter, new KeyModifiers ())));
 			Assert.False (menu.IsMenuOpen);
 			Assert.False (menu.IsMenuOpen);
 			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
 			Assert.Equal ("Closed", GetCurrentMenuBarItemTitle ());
@@ -566,8 +660,8 @@ Edit
 
 
 			expected = @"
 			expected = @"
 ┌──────
 ┌──────
-│ One
-│ Two
+│ One  
+│ Two  
 └──────
 └──────
 ";
 ";
 
 
@@ -582,8 +676,8 @@ Edit
 
 
 			expected = @"
 			expected = @"
 ┌──────
 ┌──────
-│ One
-│ Two
+│ One  
+│ Two  
 ";
 ";
 
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -603,7 +697,7 @@ Edit
 					new MenuItem ("Three", "", null),
 					new MenuItem ("Three", "", null),
 				})
 				})
 			});
 			});
-
+			menu.UseKeysUpDownAsKeysLeftRight = true;
 			Application.Top.Add (menu);
 			Application.Top.Add (menu);
 
 
 			Assert.Equal (Point.Empty, new Point (menu.Frame.X, menu.Frame.Y));
 			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)));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -634,9 +728,9 @@ Edit
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.CursorDown, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
-┌────────┐
-│ One    │
+  Numbers                
+┌────────┐               
+│ One    │               
 │ Two   ►│┌─────────────┐
 │ Two   ►│┌─────────────┐
 │ Three  ││ Sub-Menu 1  │
 │ Three  ││ Sub-Menu 1  │
 └────────┘│ Sub-Menu 2  │
 └────────┘│ Sub-Menu 2  │
@@ -649,7 +743,7 @@ Edit
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null)));
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.CursorLeft, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -705,7 +799,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -724,9 +818,9 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
-┌────────┐
-│ One    │
+  Numbers                
+┌────────┐               
+│ One    │               
 │ Two   ►│┌─────────────┐
 │ Two   ►│┌─────────────┐
 │ Three  ││ Sub-Menu 1  │
 │ Three  ││ Sub-Menu 1  │
 └────────┘│ Sub-Menu 2  │
 └────────┘│ Sub-Menu 2  │
@@ -744,7 +838,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -802,7 +896,7 @@ Edit
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Assert.True (menu.ProcessHotKey (new KeyEvent (Key.F9, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -817,7 +911,7 @@ Edit
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Assert.True (Application.Top.Subviews [1].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers      
 ┌─────────────┐
 ┌─────────────┐
 │◄    Two     │
 │◄    Two     │
 ├─────────────┤
 ├─────────────┤
@@ -832,7 +926,7 @@ Edit
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Assert.True (Application.Top.Subviews [2].ProcessKey (new KeyEvent (Key.Enter, null)));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -890,7 +984,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -909,7 +1003,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers      
 ┌─────────────┐
 ┌─────────────┐
 │◄    Two     │
 │◄    Two     │
 ├─────────────┤
 ├─────────────┤
@@ -929,7 +1023,7 @@ Edit
 			}));
 			}));
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  Numbers
+  Numbers 
 ┌────────┐
 ┌────────┐
 │ One    │
 │ One    │
 │ Two   ►│
 │ Two   ►│
@@ -1031,9 +1125,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
   File   Edit
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1047,7 +1141,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  File   Edit
+  File   Edit   
        ┌───────┐
        ┌───────┐
        │ Copy  │
        │ Copy  │
        └───────┘
        └───────┘
@@ -1081,9 +1175,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
   File   Edit
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1093,7 +1187,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  File   Edit
+  File   Edit   
        ┌───────┐
        ┌───────┐
        │ Copy  │
        │ Copy  │
        └───────┘
        └───────┘
@@ -1127,9 +1221,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
   File   Edit
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 ";
 
 
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1139,7 +1233,7 @@ Edit
 			Assert.True (menu.IsMenuOpen);
 			Assert.True (menu.IsMenuOpen);
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			expected = @"
 			expected = @"
-  File   Edit
+  File   Edit   
        ┌───────┐
        ┌───────┐
        │ Copy  │
        │ Copy  │
        └───────┘
        └───────┘
@@ -1168,9 +1262,9 @@ Edit
 			Application.Top.Redraw (Application.Top.Bounds);
 			Application.Top.Redraw (Application.Top.Bounds);
 			var expected = @"
 			var expected = @"
   File   Edit
   File   Edit
-┌──────┐
-│ New  │
-└──────┘
+┌──────┐     
+│ New  │     
+└──────┘     
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
@@ -1186,5 +1280,209 @@ Edit
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (2, 0, 13, 1), pos);
 			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 top = Application.Top;
 			var win = new Window ();
 			var win = new Window ();
-			var label = new Label ("Hello World") {
-				Width = 24,
-				Height = 13,
+			var label = new Label () {
 				ColorScheme = Colors.TopLevel,
 				ColorScheme = Colors.TopLevel,
 				Text = "This is a test\nwith a \nPanelView",
 				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) {
 			var pv = new PanelView (label) {
 				Width = 24,
 				Width = 24,
@@ -370,6 +371,7 @@ namespace Terminal.Gui.Views {
 
 
 			Application.Begin (top);
 			Application.Begin (top);
 
 
+			Assert.False (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 24, 13), label.Frame);
 			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, 34, 23), pv.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
@@ -412,7 +414,7 @@ namespace Terminal.Gui.Views {
 		{
 		{
 			var top = Application.Top;
 			var top = Application.Top;
 			var win = new Window ();
 			var win = new Window ();
-			var label = new Label ("Hello World") {
+			var label = new Label () {
 				ColorScheme = Colors.TopLevel,
 				ColorScheme = Colors.TopLevel,
 				Text = "This is a test\nwith a \nPanelView",
 				Text = "This is a test\nwith a \nPanelView",
 				TextAlignment = TextAlignment.Centered
 				TextAlignment = TextAlignment.Centered
@@ -435,6 +437,8 @@ namespace Terminal.Gui.Views {
 
 
 			Application.Begin (top);
 			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, 14, 3), label.Frame);
 			Assert.Equal (new Rect (0, 0, 24, 13), pv.Frame);
 			Assert.Equal (new Rect (0, 0, 24, 13), pv.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.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.Collections.Generic;
 using System.ComponentModel;
 using System.ComponentModel;
 using System.Data;
 using System.Data;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using Terminal.Gui;
 using Terminal.Gui;
+using Terminal.Gui.Views;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 // Alias Console to MockConsole so we don't accidentally use Console
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 using Console = Terminal.Gui.FakeConsole;
 
 
 namespace Terminal.Gui.Core {
 namespace Terminal.Gui.Core {
 	public class PosTests {
 	public class PosTests {
+		readonly ITestOutputHelper output;
+
+		public PosTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		[Fact]
 		public void New_Works ()
 		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), top.Frame);
 			Assert.Equal (new Rect (0, 0, 80, 25), win.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 (
 			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);
 			Assert.Equal (new Rect (68, 22, 10, 1), tv.Frame);
 		}
 		}
 
 
@@ -123,7 +132,7 @@ namespace Terminal.Gui.Core {
 				ColorScheme = Colors.Menu,
 				ColorScheme = Colors.Menu,
 				Width = Dim.Fill (),
 				Width = Dim.Fill (),
 				X = Pos.Center (),
 				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);
 			win.Add (label);
@@ -131,15 +140,78 @@ namespace Terminal.Gui.Core {
 			var top = Application.Top;
 			var top = Application.Top;
 			top.Add (win);
 			top.Add (win);
 			Application.Begin (top);
 			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.Left, win.Frame.Top,
 				win.Frame.Right, win.Frame.Bottom));
 				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]
 		[Fact]
@@ -153,17 +225,81 @@ namespace Terminal.Gui.Core {
 				ColorScheme = Colors.Menu,
 				ColorScheme = Colors.Menu,
 				Width = Dim.Fill (),
 				Width = Dim.Fill (),
 				X = Pos.Center (),
 				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);
 			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;
 			var top = Application.Top;
 			top.Add (win, menu, status);
 			top.Add (win, menu, status);
 			Application.Begin (top);
 			Application.Begin (top);
 
 
+			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
 			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, 0, 80, 1), menu.Frame);
 			Assert.Equal (new Rect (0, 24, 80, 1), status.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.Left, win.Frame.Top,
 				win.Frame.Right, win.Frame.Bottom));
 				win.Frame.Right, win.Frame.Bottom));
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
 			Assert.Equal (new Rect (0, 20, 78, 1), label.Frame);
+			var expected = @"
+  Menu                                                                          
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                        This should be the last line.                         │
+└──────────────────────────────────────────────────────────────────────────────┘
+ F1 Help                                                                        
+";
+
+			GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 		}
 		}
 
 
 		[Fact]
 		[Fact]
@@ -488,11 +653,37 @@ namespace Terminal.Gui.Core {
 		[Fact]
 		[Fact]
 		public void Percent_Equal ()
 		public void Percent_Equal ()
 		{
 		{
-			var n1 = 0;
-			var n2 = 0;
+			float n1 = 0;
+			float n2 = 0;
 			var pos1 = Pos.Percent (n1);
 			var pos1 = Pos.Percent (n1);
 			var pos2 = Pos.Percent (n2);
 			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);
 			Assert.NotEqual (pos1, pos2);
 		}
 		}
 
 
@@ -507,7 +698,7 @@ namespace Terminal.Gui.Core {
 		}
 		}
 
 
 		[Fact]
 		[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)));
 			Application.Init (new FakeDriver (), new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 
 
@@ -519,7 +710,8 @@ namespace Terminal.Gui.Core {
 			};
 			};
 			var v = new View ("v") {
 			var v = new View ("v") {
 				X = Pos.Center (),
 				X = Pos.Center (),
-				Y = Pos.Percent (10)
+				Y = Pos.Percent (10),
+				ForceValidatePosDim = true
 			};
 			};
 
 
 			w.Add (v);
 			w.Add (v);
@@ -811,5 +1003,34 @@ namespace Terminal.Gui.Core {
 			var posViewBottom = new Pos.PosView (view, 3);
 			var posViewBottom = new Pos.PosView (view, 3);
 			Assert.Equal (11, posViewBottom.Anchor (0));
 			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.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class RadioGroupTests {
 	public class RadioGroupTests {
+		readonly ITestOutputHelper output;
+
+		public RadioGroupTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
+
 		[Fact]
 		[Fact]
 		public void Constructors_Defaults ()
 		public void Constructors_Defaults ()
 		{
 		{
 			var rg = new RadioGroup ();
 			var rg = new RadioGroup ();
 			Assert.True (rg.CanFocus);
 			Assert.True (rg.CanFocus);
 			Assert.Empty (rg.RadioLabels);
 			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);
 			Assert.Equal (0, rg.SelectedItem);
 
 
 			rg = new RadioGroup (new NStack.ustring [] { "Test" });
 			rg = new RadioGroup (new NStack.ustring [] { "Test" });
 			Assert.True (rg.CanFocus);
 			Assert.True (rg.CanFocus);
 			Assert.Single (rg.RadioLabels);
 			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);
 			Assert.Equal (0, rg.SelectedItem);
 
 
 			rg = new RadioGroup (new Rect (1, 2, 20, 5), new NStack.ustring [] { "Test" });
 			rg = new RadioGroup (new Rect (1, 2, 20, 5), new NStack.ustring [] { "Test" });
 			Assert.True (rg.CanFocus);
 			Assert.True (rg.CanFocus);
 			Assert.Single (rg.RadioLabels);
 			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);
 			Assert.Equal (0, rg.SelectedItem);
 
 
 			rg = new RadioGroup (1, 2, new NStack.ustring [] { "Test" });
 			rg = new RadioGroup (1, 2, new NStack.ustring [] { "Test" });
 			Assert.True (rg.CanFocus);
 			Assert.True (rg.CanFocus);
 			Assert.Single (rg.RadioLabels);
 			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);
 			Assert.Equal (0, rg.SelectedItem);
 		}
 		}
 
 
@@ -56,32 +70,78 @@ namespace Terminal.Gui.Views {
 			Assert.Equal (0, rg.SelectedItem);
 			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 (DisplayModeLayout.Vertical, rg.DisplayMode);
 			Assert.Equal (2, rg.RadioLabels.Length);
 			Assert.Equal (2, rg.RadioLabels.Length);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.Y);
 			Assert.Equal (0, rg.Y);
-			Assert.Equal (11, rg.Width);
+			Assert.Equal (14, rg.Width);
 			Assert.Equal (2, rg.Height);
 			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;
 			rg.DisplayMode = DisplayModeLayout.Horizontal;
+			Application.Refresh ();
+
 			Assert.Equal (DisplayModeLayout.Horizontal, rg.DisplayMode);
 			Assert.Equal (DisplayModeLayout.Horizontal, rg.DisplayMode);
 			Assert.Equal (2, rg.HorizontalSpace);
 			Assert.Equal (2, rg.HorizontalSpace);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.Y);
 			Assert.Equal (0, rg.Y);
-			Assert.Equal (16, rg.Width);
+			Assert.Equal (21, rg.Width);
 			Assert.Equal (1, rg.Height);
 			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;
 			rg.HorizontalSpace = 4;
+			Application.Refresh ();
+
 			Assert.Equal (DisplayModeLayout.Horizontal, rg.DisplayMode);
 			Assert.Equal (DisplayModeLayout.Horizontal, rg.DisplayMode);
 			Assert.Equal (4, rg.HorizontalSpace);
 			Assert.Equal (4, rg.HorizontalSpace);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.X);
 			Assert.Equal (0, rg.Y);
 			Assert.Equal (0, rg.Y);
-			Assert.Equal (20, rg.Width);
+			Assert.Equal (23, rg.Width);
 			Assert.Equal (1, rg.Height);
 			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]
 		[Fact]

+ 24 - 56
UnitTests/ScenarioTests.cs

@@ -37,10 +37,14 @@ namespace Terminal.Gui {
 			return FakeConsole.MockKeyPresses.Count;
 			return FakeConsole.MockKeyPresses.Count;
 		}
 		}
 
 
+
 		/// <summary>
 		/// <summary>
+		/// <para>
 		/// This runs through all Scenarios defined in UI Catalog, calling Init, Setup, and Run.
 		/// 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>
 		/// </summary>
 		[Fact]
 		[Fact]
 		public void Run_All_Scenarios ()
 		public void Run_All_Scenarios ()
@@ -48,61 +52,25 @@ namespace Terminal.Gui {
 			List<Type> scenarioClasses = Scenario.GetDerivedClasses<Scenario> ();
 			List<Type> scenarioClasses = Scenario.GetDerivedClasses<Scenario> ();
 			Assert.NotEmpty (scenarioClasses);
 			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
 #if DEBUG_IDISPOSABLE
 			foreach (var inst in Responder.Instances) {
 			foreach (var inst in Responder.Instances) {

+ 139 - 1
UnitTests/ScrollBarViewTests.cs

@@ -3,9 +3,16 @@ using System.Collections.Generic;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Reflection;
 using System.Reflection;
 using Xunit;
 using Xunit;
+using Xunit.Abstractions;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 	public class ScrollBarViewTests {
 	public class ScrollBarViewTests {
+		readonly ITestOutputHelper output;
+
+		public ScrollBarViewTests (ITestOutputHelper output)
+		{
+			this.output = output;
+		}
 
 
 		// This class enables test functions annotated with the [InitShutdown] attribute
 		// This class enables test functions annotated with the [InitShutdown] attribute
 		// to have a function called before the test function is called and after.
 		// 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
 		// 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.
 		// as a pair, and b) all unit test functions should be atomic.
 		[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
 		[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)
 			public override void Before (MethodInfo methodUnderTest)
 			{
 			{
@@ -635,5 +642,136 @@ namespace Terminal.Gui.Views {
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.Visible);
 			Assert.True (sbv.OtherScrollBarView.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 ()
 		public void Constructors_Defaults ()
 		{
 		{
 			var sv = new ScrollView ();
 			var sv = new ScrollView ();
+			Assert.Equal (LayoutStyle.Computed, sv.LayoutStyle);
 			Assert.True (sv.CanFocus);
 			Assert.True (sv.CanFocus);
 			Assert.Equal (new Rect (0, 0, 0, 0), sv.Frame);
 			Assert.Equal (new Rect (0, 0, 0, 0), sv.Frame);
 			Assert.Equal (Rect.Empty, 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 (Point.Empty, sv.ContentOffset);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.True (sv.AutoHideScrollBars);
 			Assert.True (sv.AutoHideScrollBars);
 			Assert.True (sv.KeepContentAlwaysInViewport);
 			Assert.True (sv.KeepContentAlwaysInViewport);
 
 
 			sv = new ScrollView (new Rect (1, 2, 20, 10));
 			sv = new ScrollView (new Rect (1, 2, 20, 10));
+			Assert.Equal (LayoutStyle.Absolute, sv.LayoutStyle);
 			Assert.True (sv.CanFocus);
 			Assert.True (sv.CanFocus);
 			Assert.Equal (new Rect (1, 2, 20, 10), sv.Frame);
 			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 (Point.Empty, sv.ContentOffset);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.True (sv.AutoHideScrollBars);
 			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 (Dim.Fill (), sb.Width);
 			Assert.Equal (1, sb.Height);
 			Assert.Equal (1, sb.Height);
 
 
-			Assert.Equal (0, sb.Y);
+			Assert.Null (sb.Y);
 
 
 			var driver = new FakeDriver ();
 			var driver = new FakeDriver ();
 			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));
 			Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true)));

+ 8 - 12
UnitTests/TabViewTests.cs

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

+ 465 - 10
UnitTests/TableViewTests.cs

@@ -7,6 +7,7 @@ using Terminal.Gui;
 using Xunit;
 using Xunit;
 using System.Globalization;
 using System.Globalization;
 using Xunit.Abstractions;
 using Xunit.Abstractions;
+using System.Reflection;
 
 
 namespace Terminal.Gui.Views {
 namespace Terminal.Gui.Views {
 
 
@@ -550,23 +551,311 @@ namespace Terminal.Gui.Views {
 		}
 		}
 
 
 		[Fact]
 		[Fact]
-		public void TableView_ColorsTest_ColorGetter ()
+		public void TableViewMultiSelect_CannotFallOffLeft()
 		{
 		{
 			var tv = SetUpMiniTable ();
 			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;
 			tv.Style.InvertSelectedCellFirstCharacter = true;
 
 
 			// width exactly matches the max col widths
 			// width exactly matches the max col widths
 			tv.Bounds = new Rect (0, 0, 5, 4);
 			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
 			// Create a style for column B
 			var bStyle = tv.Style.GetOrCreateColumnStyle (tv.Table.Columns ["B"]);
 			var bStyle = tv.Style.GetOrCreateColumnStyle (tv.Table.Columns ["B"]);
 
 
 			// when B is 2 use the custom highlight colour
 			// 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;
 			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);
 			tv.Redraw (tv.Bounds);
 
 
 			string expected = @"
 			string expected = @"
@@ -577,22 +866,56 @@ namespace Terminal.Gui.Views {
 ";
 ";
 			GraphViewTests.AssertDriverContentsAre (expected, output);
 			GraphViewTests.AssertDriverContentsAre (expected, output);
 
 
+
 			string expectedColors = @"
 			string expectedColors = @"
 00000
 00000
 00000
 00000
 00000
 00000
 01020
 01020
 ";
 ";
-			var invertedNormalColor = Application.Driver.MakeAttribute (tv.ColorScheme.Normal.Background, tv.ColorScheme.Normal.Foreground);
-
+			
 			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
 			GraphViewTests.AssertDriverColorsAre (expectedColors, new Attribute [] {
 				// 0
 				// 0
 				tv.ColorScheme.Normal,				
 				tv.ColorScheme.Normal,				
 				// 1
 				// 1
-				invertedNormalColor,				
+				focused ? tv.ColorScheme.HotFocus : tv.ColorScheme.HotNormal,
 				// 2
 				// 2
 				cellHighlight.Normal});
 				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
 			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
@@ -615,10 +938,7 @@ namespace Terminal.Gui.Views {
 			tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
 			tv.Style.GetOrCreateColumnStyle (colB).MaxWidth = 1;
 
 
 			GraphViewTests.InitFakeDriver ();
 			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;
 			return tv;
 		}
 		}
 
 
@@ -778,6 +1098,141 @@ namespace Terminal.Gui.Views {
 			Application.Shutdown ();
 			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]
 		[Fact]
 		public void ScrollIndicators ()
 		public void ScrollIndicators ()

+ 28 - 10
UnitTests/TextFieldTests.cs

@@ -1181,35 +1181,53 @@ namespace Terminal.Gui.Views {
 
 
 		[Fact]
 		[Fact]
 		[AutoInitShutdown]
 		[AutoInitShutdown]
-		public void Test_RootKeyEvent_Cancel()
+		public void Test_RootKeyEvent_Cancel ()
 		{
 		{
 			Application.RootKeyEvent += SuppressKey;
 			Application.RootKeyEvent += SuppressKey;
 
 
-			var tf = new TextField();
+			var tf = new TextField ();
 
 
 			Application.Top.Add (tf);
 			Application.Top.Add (tf);
 			Application.Begin (Application.Top);
 			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
 			// 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;
 			Application.RootKeyEvent -= SuppressKey;
 
 
 			// Now that the delegate has been removed we can type j again
 			// 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)
 		private bool SuppressKey (KeyEvent arg)
 		{
 		{
-			if(arg.KeyValue == 'j')
+			if (arg.KeyValue == 'j')
 				return true;
 				return true;
 
 
 			return false;
 			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);
+		}
 	}
 	}
 }
 }

File diff suppressed because it is too large
+ 145 - 34
UnitTests/TextFormatterTests.cs


+ 64 - 4
UnitTests/TextViewTests.cs

@@ -5561,14 +5561,12 @@ line.
 			Assert.Equal (3, tv.Lines);
 			Assert.Equal (3, tv.Lines);
 			Assert.Equal (new Point (1, 1), tv.CursorPosition);
 			Assert.Equal (new Point (1, 1), tv.CursorPosition);
 
 
-			// Undo is disabled
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.Z | Key.CtrlMask, new KeyModifiers ())));
 			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 (3, tv.Lines);
-			Assert.Equal (new Point (1, 1), tv.CursorPosition);
+			Assert.Equal (new Point (0, 1), tv.CursorPosition);
 			Assert.True (tv.IsDirty);
 			Assert.True (tv.IsDirty);
 
 
-			// Redo is disabled
 			Assert.True (tv.ProcessKey (new KeyEvent (Key.R | Key.CtrlMask, new KeyModifiers ())));
 			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 ($"This is the {Environment.NewLine}athird line.{Environment.NewLine}", tv.Text);
 			Assert.Equal (3, tv.Lines);
 			Assert.Equal (3, tv.Lines);
@@ -5707,5 +5705,67 @@ line.
 			});
 			});
 			Assert.Null (exception);
 			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 += () => {
 			Application.Iteration += () => {
 				if (iterations == 0) {
 				if (iterations == 0) {
+					Assert.False (Application.Top.AutoSize);
 					Assert.Equal ("Top1", Application.Top.Text);
 					Assert.Equal ("Top1", Application.Top.Text);
 					Assert.Equal (0, Application.Top.Frame.X);
 					Assert.Equal (0, Application.Top.Frame.X);
 					Assert.Equal (0, Application.Top.Frame.Y);
 					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.Equal (new Rect (0, 0, 0, 0), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
 			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.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
 			Assert.Empty (r.Subviews);
@@ -54,10 +53,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
 			Assert.Equal (new Rect (0, 0, 0, 0), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
 			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.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
 			Assert.Empty (r.Subviews);
@@ -78,10 +77,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (1, 2, 3, 4), r.Frame);
 			Assert.Equal (new Rect (1, 2, 3, 4), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
 			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.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
 			Assert.Empty (r.Subviews);
@@ -102,10 +101,10 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (new Rect (0, 0, 1, 13), r.Frame);
 			Assert.Equal (new Rect (0, 0, 1, 13), r.Frame);
 			Assert.Null (r.Focused);
 			Assert.Null (r.Focused);
 			Assert.Null (r.ColorScheme);
 			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.False (r.IsCurrentTop);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Id);
 			Assert.Empty (r.Subviews);
 			Assert.Empty (r.Subviews);
@@ -1082,19 +1081,19 @@ namespace Terminal.Gui.Core {
 
 
 			// Default Constructor
 			// Default Constructor
 			view = new View ();
 			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.Frame.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 
 
 			// Constructor
 			// Constructor
 			view = new View (1, 2, "");
 			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.False (view.Frame.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 			Assert.True (view.Bounds.IsEmpty);
 
 
@@ -1176,8 +1175,8 @@ namespace Terminal.Gui.Core {
 			Application.Shutdown ();
 			Application.Shutdown ();
 		}
 		}
 
 
-		[Fact]
-		public void SetWidth_CanSetWidth ()
+		[Fact, AutoInitShutdown]
+		public void SetWidth_CanSetWidth_ForceValidatePosDim ()
 		{
 		{
 			var top = new View () {
 			var top = new View () {
 				X = 0,
 				X = 0,
@@ -1186,7 +1185,8 @@ namespace Terminal.Gui.Core {
 			};
 			};
 
 
 			var v = new View () {
 			var v = new View () {
-				Width = Dim.Fill ()
+				Width = Dim.Fill (),
+				ForceValidatePosDim = true
 			};
 			};
 			top.Add (v);
 			top.Add (v);
 
 
@@ -1200,8 +1200,12 @@ namespace Terminal.Gui.Core {
 			v.Width = null;
 			v.Width = null;
 			Assert.True (v.SetWidth (70, out rWidth));
 			Assert.True (v.SetWidth (70, out rWidth));
 			Assert.Equal (70, 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);
 			v.Width = Dim.Fill (1);
 			Assert.Throws<ArgumentException> (() => v.Width = 75);
 			Assert.Throws<ArgumentException> (() => v.Width = 75);
 			v.LayoutStyle = LayoutStyle.Absolute;
 			v.LayoutStyle = LayoutStyle.Absolute;
@@ -1210,8 +1214,8 @@ namespace Terminal.Gui.Core {
 			Assert.Equal (60, rWidth);
 			Assert.Equal (60, rWidth);
 		}
 		}
 
 
-		[Fact]
-		public void SetHeight_CanSetHeight ()
+		[Fact, AutoInitShutdown]
+		public void SetHeight_CanSetHeight_ForceValidatePosDim ()
 		{
 		{
 			var top = new View () {
 			var top = new View () {
 				X = 0,
 				X = 0,
@@ -1220,7 +1224,8 @@ namespace Terminal.Gui.Core {
 			};
 			};
 
 
 			var v = new View () {
 			var v = new View () {
-				Height = Dim.Fill ()
+				Height = Dim.Fill (),
+				ForceValidatePosDim = true
 			};
 			};
 			top.Add (v);
 			top.Add (v);
 
 
@@ -1234,8 +1239,13 @@ namespace Terminal.Gui.Core {
 			v.Height = null;
 			v.Height = null;
 			Assert.True (v.SetHeight (10, out rHeight));
 			Assert.True (v.SetHeight (10, out rHeight));
 			Assert.Equal (10, 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);
 			v.Height = Dim.Fill (1);
 			Assert.Throws<ArgumentException> (() => v.Height = 15);
 			Assert.Throws<ArgumentException> (() => v.Height = 15);
 			v.LayoutStyle = LayoutStyle.Absolute;
 			v.LayoutStyle = LayoutStyle.Absolute;
@@ -1258,11 +1268,17 @@ namespace Terminal.Gui.Core {
 			};
 			};
 			top.Add (v);
 			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);
 			Assert.Equal (80, cWidth);
 
 
 			v.Width = Dim.Fill (1);
 			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);
 			Assert.Equal (79, cWidth);
 		}
 		}
 
 
@@ -1280,14 +1296,70 @@ namespace Terminal.Gui.Core {
 			};
 			};
 			top.Add (v);
 			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);
 			Assert.Equal (20, cHeight);
 
 
 			v.Height = Dim.Fill (1);
 			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);
 			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]
 		[Fact]
 		public void AutoSize_False_ResizeView_Is_Always_False ()
 		public void AutoSize_False_ResizeView_Is_Always_False ()
 		{
 		{
@@ -1296,7 +1368,7 @@ namespace Terminal.Gui.Core {
 			label.Text = "New text";
 			label.Text = "New text";
 
 
 			Assert.False (label.AutoSize);
 			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]
 		[Fact]
@@ -1310,36 +1382,111 @@ namespace Terminal.Gui.Core {
 			Assert.Equal ("{X=0,Y=0,Width=8,Height=1}", label.Bounds.ToString ());
 			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 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);
 			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";
 			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 ());
 			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 win = new Window (new Rect (0, 0, 30, 80), "");
 			var label = new Label () { Width = Dim.Fill () };
 			var label = new Label () { Width = Dim.Fill () };
 			win.Add (label);
 			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.True (label.AutoSize);
 			Assert.Equal ("{X=0,Y=0,Width=28,Height=2}", label.Bounds.ToString ());
 			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]
 		[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 label = new Label ("Hello") { Width = 10, Height = 2 };
 			var viewX = new View ("X") { X = Pos.Right (label) };
 			var viewX = new View ("X") { X = Pos.Right (label) };
@@ -1348,21 +1495,36 @@ namespace Terminal.Gui.Core {
 			Application.Top.Add (label, viewX, viewY);
 			Application.Top.Add (label, viewX, viewY);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 
 
-			Assert.False (label.AutoSize);
+			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
 			Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
 
 
 			var expected = @"
 			var expected = @"
 Hello     X
 Hello     X
-
-Y
+           
+Y          
 ";
 ";
 
 
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			var pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (new Rect (0, 0, 11, 3), pos);
 			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]
 		[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 label = new Label ("Hello") { Width = 2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight };
 			var viewX = new View ("X") { X = Pos.Right (label) };
 			var viewX = new View ("X") { X = Pos.Right (label) };
@@ -1371,24 +1533,47 @@ Y
 			Application.Top.Add (label, viewX, viewY);
 			Application.Top.Add (label, viewX, viewY);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 
 
-			Assert.False (label.AutoSize);
+			Assert.True (label.AutoSize);
 			Assert.Equal (new Rect (0, 0, 2, 10), label.Frame);
 			Assert.Equal (new Rect (0, 0, 2, 10), label.Frame);
 
 
 			var expected = @"
 			var expected = @"
 H X
 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);
 			Assert.Equal (new Rect (0, 0, 3, 11), pos);
 		}
 		}
 
 
@@ -1463,11 +1648,16 @@ Y
 			view.SetRelativeLayout (top.Bounds);
 			view.SetRelativeLayout (top.Bounds);
 			Assert.Equal (79, view.Bounds.Width);
 			Assert.Equal (79, view.Bounds.Width);
 			Assert.Equal (24, view.Bounds.Height);
 			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;
 			bool layoutStarted = false;
-			view.LayoutStarted += (_) => { layoutStarted = true; };
+			view.LayoutStarted += (_) => layoutStarted = true;
 			view.OnLayoutStarted (null);
 			view.OnLayoutStarted (null);
 			Assert.True (layoutStarted);
 			Assert.True (layoutStarted);
-			view.LayoutComplete += (_) => { layoutStarted = false; };
+			view.LayoutComplete += (_) => layoutStarted = false;
 			view.OnLayoutComplete (null);
 			view.OnLayoutComplete (null);
 			Assert.False (layoutStarted);
 			Assert.False (layoutStarted);
 			view.X = Pos.Center () - 41;
 			view.X = Pos.Center () - 41;
@@ -1668,13 +1858,12 @@ Y
 			Application.Top.Add (lbl);
 			Application.Top.Add (lbl);
 			Application.Begin (Application.Top);
 			Application.Begin (Application.Top);
 
 
+			Assert.True (lbl.AutoSize);
 			Assert.Equal ("123 ", GetContents ());
 			Assert.Equal ("123 ", GetContents ());
 
 
 			lbl.Text = "12";
 			lbl.Text = "12";
 
 
-			if (!lbl.SuperView.NeedDisplay.IsEmpty) {
-				lbl.SuperView.Redraw (lbl.SuperView.NeedDisplay);
-			}
+			lbl.SuperView.Redraw (lbl.SuperView.NeedDisplay);
 
 
 			Assert.Equal ("12  ", GetContents ());
 			Assert.Equal ("12  ", GetContents ());
 
 
@@ -1974,8 +2163,8 @@ Y
 
 
 			expected = @"
 			expected = @"
 ┌──────
 ┌──────
-│
-│
+│      
+│      
 └──────
 └──────
 ";
 ";
 
 
@@ -1984,7 +2173,6 @@ Y
 
 
 			view.Frame = new Rect (0, 0, 8, 4);
 			view.Frame = new Rect (0, 0, 8, 4);
 			((FakeDriver)Application.Driver).SetBufferSize (7, 3);
 			((FakeDriver)Application.Driver).SetBufferSize (7, 3);
-
 		}
 		}
 
 
 		[Fact, AutoInitShutdown]
 		[Fact, AutoInitShutdown]
@@ -2352,5 +2540,1268 @@ Y
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			pos = GraphViewTests.AssertDriverContentsWithFrameAre (expected, output);
 			Assert.Equal (Rect.Empty, pos);
 			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}}}

BIN
docfx/_exported_templates/default/favicon.ico


Some files were not shown because too many files changed in this diff