Răsfoiți Sursa

merged - broken

Tig Kindel 1 an în urmă
părinte
comite
9a3d1b6189
70 a modificat fișierele cu 15019 adăugiri și 13758 ștergeri
  1. 5 5
      Terminal.Gui/Application.cs
  2. 5 0
      Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
  3. 1 0
      Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs
  4. 0 11
      Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs
  5. 73 34
      Terminal.Gui/Text/TextFormatter.cs
  6. 1 1
      Terminal.Gui/View/Frame.cs
  7. 215 211
      Terminal.Gui/View/Layout/PosDim.cs
  8. 257 320
      Terminal.Gui/View/Layout/ViewLayout.cs
  9. 27 26
      Terminal.Gui/View/SuperViewChangedEventArgs.cs
  10. 392 329
      Terminal.Gui/View/View.cs
  11. 134 110
      Terminal.Gui/View/ViewDrawing.cs
  12. 608 612
      Terminal.Gui/View/ViewSubViews.cs
  13. 168 85
      Terminal.Gui/View/ViewText.cs
  14. 0 2
      Terminal.Gui/Views/Button.cs
  15. 0 2
      Terminal.Gui/Views/CheckBox.cs
  16. 227 233
      Terminal.Gui/Views/ColorPicker.cs
  17. 40 32
      Terminal.Gui/Views/ComboBox.cs
  18. 121 66
      Terminal.Gui/Views/DateField.cs
  19. 1151 1187
      Terminal.Gui/Views/FileDialog.cs
  20. 21 14
      Terminal.Gui/Views/HexView.cs
  21. 98 123
      Terminal.Gui/Views/Label.cs
  22. 1 1
      Terminal.Gui/Views/Menu/ContextMenu.cs
  23. 2 2
      Terminal.Gui/Views/Menu/MenuBar.cs
  24. 4 5
      Terminal.Gui/Views/RadioGroup.cs
  25. 708 700
      Terminal.Gui/Views/ScrollBarView.cs
  26. 0 3
      Terminal.Gui/Views/Slider.cs
  27. 11 14
      Terminal.Gui/Views/TextField.cs
  28. 1050 859
      Terminal.Gui/Views/TextView.cs
  29. 32 17
      Terminal.Gui/Views/Toplevel.cs
  30. 4 0
      UICatalog/Properties/launchSettings.json
  31. 1 1
      UICatalog/Scenarios/ASCIICustomButton.cs
  32. 2 3
      UICatalog/Scenarios/AllViewsTester.cs
  33. 1 1
      UICatalog/Scenarios/ColorPicker.cs
  34. 314 287
      UICatalog/Scenarios/ListColumns.cs
  35. 0 4
      UICatalog/Scenarios/Sliders.cs
  36. 1 1
      UICatalog/Scenarios/TabViewExample.cs
  37. 1 1
      UICatalog/Scenarios/Text.cs
  38. 6 12
      UICatalog/UICatalog.cs
  39. 741 757
      UnitTests/Dialogs/DialogTests.cs
  40. 52 44
      UnitTests/Input/KeyTests.cs
  41. 9 0
      UnitTests/Input/ResponderTests.cs
  42. 1 1
      UnitTests/Text/AutocompleteTests.cs
  43. 1701 1620
      UnitTests/Text/TextFormatterTests.cs
  44. 3 3
      UnitTests/UICatalog/ScenarioTests.cs
  45. 18 9
      UnitTests/View/DrawTests.cs
  46. 217 170
      UnitTests/View/Layout/AbsoluteLayoutTests.cs
  47. 0 838
      UnitTests/View/Layout/AutoSizeTests.cs
  48. 42 203
      UnitTests/View/Layout/DimTests.cs
  49. 66 1243
      UnitTests/View/Layout/LayoutTests.cs
  50. 45 340
      UnitTests/View/Layout/PosTests.cs
  51. 70 124
      UnitTests/View/Layout/SetRelativeLayoutTests.cs
  52. 2643 0
      UnitTests/View/Text/AutoSizeTextTests.cs
  53. 652 0
      UnitTests/View/Text/TextTests.cs
  54. 630 619
      UnitTests/View/ViewTests.cs
  55. 17 19
      UnitTests/Views/AppendAutocompleteTests.cs
  56. 44 45
      UnitTests/Views/CheckBoxTests.cs
  57. 1 1
      UnitTests/Views/ContextMenuTests.cs
  58. 82 0
      UnitTests/Views/DateFieldTests.cs
  59. 17 0
      UnitTests/Views/HexViewTests.cs
  60. 440 435
      UnitTests/Views/LabelTests.cs
  61. 5 10
      UnitTests/Views/OverlappedTests.cs
  62. 0 16
      UnitTests/Views/RadioGroupTests.cs
  63. 661 717
      UnitTests/Views/ScrollBarViewTests.cs
  64. 224 228
      UnitTests/Views/ScrollViewTests.cs
  65. 433 444
      UnitTests/Views/TabViewTests.cs
  66. 1 0
      UnitTests/Views/TextViewTests.cs
  67. 434 436
      UnitTests/Views/TileViewTests.cs
  68. 61 88
      UnitTests/Views/ToplevelTests.cs
  69. 26 25
      UnitTests/Views/WindowTests.cs
  70. 1 9
      pull_request_template.md

+ 5 - 5
Terminal.Gui/Application.cs

@@ -196,7 +196,7 @@ public static partial class Application {
 		Current = Top;
 
 		// Ensure Top's layout is up to date.
-		Current.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); 
+		Current.SetRelativeLayout (Driver.Bounds); 
 		
 		_cachedSupportedCultures = GetSupportedCultures ();
 		_mainThreadId = Thread.CurrentThread.ManagedThreadId;
@@ -401,9 +401,9 @@ public static partial class Application {
 			MoveCurrent (Current);
 		}
 
-		if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
-			Toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows));
-		}
+		//if (Toplevel.LayoutStyle == LayoutStyle.Computed) {
+			Toplevel.SetRelativeLayout (Driver.Bounds);
+		//}
 		Toplevel.LayoutSubviews ();
 		Toplevel.PositionToplevels ();
 		Toplevel.FocusFirst ();
@@ -714,7 +714,7 @@ public static partial class Application {
 					&& (Driver.Cols != state.Toplevel.Frame.Width || Driver.Rows != state.Toplevel.Frame.Height)
 					&& (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded)) {
 
-			state.Toplevel.Clear (new Rect (Point.Empty, new Size (Driver.Cols, Driver.Rows)));
+			state.Toplevel.Clear (Driver.Bounds);
 		}
 
 		if (state.Toplevel.NeedsDisplay ||

+ 5 - 0
Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

@@ -552,6 +552,11 @@ public abstract class ConsoleDriver {
 	/// </summary>
 	public static DiagnosticFlags Diagnostics { get; set; }
 
+	/// <summary>
+	/// Gets the dimensions of the terminal.
+	/// </summary>
+	public Rect Bounds => new Rect (0, 0, Cols, Rows);
+
 	/// <summary>
 	/// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver.
 	/// </summary>

+ 1 - 0
Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs

@@ -83,6 +83,7 @@ namespace Terminal.Gui {
 		public override void GenerateSuggestions (AutocompleteContext context)
 		{
 			if (_suspendSuggestions) {
+				_suspendSuggestions = false;
 				return;
 			}
 			base.GenerateSuggestions (context);

+ 0 - 11
Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs

@@ -23,17 +23,6 @@ namespace Terminal.Gui {
 				WantMousePositionReports = true;
 			}
 
-			public override Rect Frame {
-				get => base.Frame;
-				set {
-					base.Frame = value;
-					X = value.X;
-					Y = value.Y;
-					Width = value.Width;
-					Height = value.Height;
-				}
-			}
-
 			public override void OnDrawContent (Rect contentArea)
 			{
 				if (autocomplete.LastPopupPos == null) {

+ 73 - 34
Terminal.Gui/Text/TextFormatter.cs

@@ -50,23 +50,50 @@ namespace Terminal.Gui {
 		Justified
 	}
 
-	/// TextDirection  [H] = Horizontal  [V] = Vertical
-	/// =============
-	/// LeftRight_TopBottom [H] Normal
-	/// TopBottom_LeftRight [V] Normal
-	/// 
-	/// RightLeft_TopBottom [H] Invert Text
-	/// TopBottom_RightLeft [V] Invert Lines
-	/// 
-	/// LeftRight_BottomTop [H] Invert Lines
-	/// BottomTop_LeftRight [V] Invert Text
-	/// 
-	/// RightLeft_BottomTop [H] Invert Text + Invert Lines
-	/// BottomTop_RightLeft [V] Invert Text + Invert Lines
-	///
 	/// <summary>
 	/// Text direction enumeration, controls how text is displayed.
 	/// </summary>
+	/// <remarks>
+	/// <para>TextDirection  [H] = Horizontal  [V] = Vertical</para>
+	/// <table>
+	///   <tr>
+	///     <th>TextDirection</th>
+	///     <th>Description</th>
+	///   </tr>
+	///   <tr>
+	///     <td>LeftRight_TopBottom [H]</td>
+	///     <td>Normal</td>
+	///   </tr>
+	///   <tr>
+	///     <td>TopBottom_LeftRight [V]</td>
+	///     <td>Normal</td>
+	///   </tr>
+	///   <tr>
+	///     <td>RightLeft_TopBottom [H]</td>
+	///     <td>Invert Text</td>
+	///   </tr>
+	///   <tr>
+	///     <td>TopBottom_RightLeft [V]</td>
+	///     <td>Invert Lines</td>
+	///   </tr>
+	///   <tr>
+	///     <td>LeftRight_BottomTop [H]</td>
+	///     <td>Invert Lines</td>
+	///   </tr>
+	///   <tr>
+	///     <td>BottomTop_LeftRight [V]</td>
+	///     <td>Invert Text</td>
+	///   </tr>
+	///   <tr>
+	///     <td>RightLeft_BottomTop [H]</td>
+	///     <td>Invert Text + Invert Lines</td>
+	///   </tr>
+	///   <tr>
+	///     <td>BottomTop_RightLeft [V]</td>
+	///     <td>Invert Text + Invert Lines</td>
+	///   </tr>
+	/// </table>
+	/// </remarks>
 	public enum TextDirection {
 		/// <summary>
 		/// Normal horizontal direction.
@@ -1075,7 +1102,7 @@ namespace Terminal.Gui {
 				_text = EnableNeedsFormat (value);
 
 				if ((AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) || (textWasNull && Size.IsEmpty)) {
-					Size = CalcRect (0, 0, _text, _textDirection, TabWidth).Size;
+					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
 				}
 
 				//if (_text != null && _text.GetRuneCount () > 0 && (Size.Width == 0 || Size.Height == 0 || Size.Width != _text.GetColumns ())) {
@@ -1087,20 +1114,22 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Used by <see cref="Text"/> to resize the view's <see cref="View.Bounds"/> with the <see cref="Size"/>.
-		/// Setting <see cref="AutoSize"/> to true only work if the <see cref="View.Width"/> and <see cref="View.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 whether the <see cref="Size"/> should be automatically changed to fit the <see cref="Text"/>.
 		/// </summary>
 		/// <remarks>
-		///   Auto size is ignored if the <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
+		/// <para>
+		/// Used by <see cref="View.AutoSize"/> to resize the view's <see cref="View.Bounds"/> to fit <see cref="Size"/>.
+		/// </para>
+		/// <para>
+		/// AutoSize is ignored if <see cref="TextAlignment.Justified"/> and <see cref="VerticalTextAlignment.Justified"/> are used.
+		/// </para>
 		/// </remarks>
 		public bool AutoSize {
 			get => _autoSize;
 			set {
 				_autoSize = EnableNeedsFormat (value);
 				if (_autoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					Size = CalcRect (0, 0, Text, _textDirection, TabWidth).Size;
+					Size = CalcRect (0, 0, _text, Direction, TabWidth).Size;
 				}
 			}
 		}
@@ -1140,7 +1169,12 @@ namespace Terminal.Gui {
 		/// <value>The text vertical alignment.</value>
 		public TextDirection Direction {
 			get => _textDirection;
-			set => _textDirection = EnableNeedsFormat (value);
+			set {
+				_textDirection = EnableNeedsFormat (value);
+				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
+					Size = CalcRect (0, 0, Text, Direction, TabWidth).Size;
+				}
+			}
 		}
 
 		/// <summary>
@@ -1204,7 +1238,7 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Allows word wrap the to fit the available container width.
+		/// Gets or sets whether word wrap will be used to fit <see cref="Text"/> to <see cref="Size"/>.
 		/// </summary>
 		public bool WordWrap {
 			get => _wordWrap;
@@ -1212,16 +1246,16 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets the size of the area the text will be constrained to when formatted.
+		/// Gets or sets the size <see cref="Text"/> will be constrained to when formatted.
 		/// </summary>
 		/// <remarks>
-		/// Does not return the size the formatted text; just the value that was set.
+		/// Does not return the size of the formatted text but the size that will be used to constrain the text when formatted.
 		/// </remarks>
 		public Size Size {
 			get => _size;
 			set {
 				if (AutoSize && Alignment != TextAlignment.Justified && VerticalAlignment != VerticalTextAlignment.Justified) {
-					_size = EnableNeedsFormat (CalcRect (0, 0, Text, _textDirection, TabWidth).Size);
+					_size = EnableNeedsFormat (CalcRect (0, 0, Text, Direction, TabWidth).Size);
 				} else {
 					_size = EnableNeedsFormat (value);
 				}
@@ -1300,7 +1334,7 @@ namespace Terminal.Gui {
 						shown_text = ReplaceHotKeyWithTag (shown_text, _hotKeyPos);
 					}
 
-					if (IsVerticalDirection (_textDirection)) {
+					if (IsVerticalDirection (Direction)) {
 						var colsWidth = GetSumMaxCharWidth (shown_text, 0, 1, TabWidth);
 						_lines = Format (shown_text, Size.Height, VerticalAlignment == VerticalTextAlignment.Justified, Size.Width > colsWidth && WordWrap,
 							PreserveTrailingSpaces, TabWidth, Direction, MultiLine);
@@ -1325,11 +1359,16 @@ namespace Terminal.Gui {
 		}
 
 		/// <summary>
-		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text when <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/> is called.
-		/// If it is <c>false</c> when Draw is called, the Draw call will be faster.
+		/// Gets or sets whether the <see cref="TextFormatter"/> needs to format the text. 
 		/// </summary>
 		/// <remarks>
 		/// <para>
+		/// If <c>false</c> when Draw is called, the Draw call will be faster.
+		/// </para>
+		/// <para>
+		/// Used by <see cref="Draw(Rect, Attribute, Attribute, Rect, bool, ConsoleDriver)"/>
+		/// </para>
+		/// <para>
 		/// This is set to true when the properties of <see cref="TextFormatter"/> are set.
 		/// </para>
 		/// </remarks>
@@ -1400,7 +1439,7 @@ namespace Terminal.Gui {
 			// Use "Lines" to ensure a Format (don't use "lines"))
 
 			var linesFormated = Lines;
-			switch (_textDirection) {
+			switch (Direction) {
 			case TextDirection.TopBottom_RightLeft:
 			case TextDirection.LeftRight_BottomTop:
 			case TextDirection.RightLeft_BottomTop:
@@ -1409,7 +1448,7 @@ namespace Terminal.Gui {
 				break;
 			}
 
-			var isVertical = IsVerticalDirection (_textDirection);
+			var isVertical = IsVerticalDirection (Direction);
 			var maxBounds = bounds;
 			if (driver != null) {
 				maxBounds = containerBounds == default
@@ -1441,7 +1480,7 @@ namespace Terminal.Gui {
 
 				var runes = _lines [line].ToRunes ();
 
-				switch (_textDirection) {
+				switch (Direction) {
 				case TextDirection.RightLeft_BottomTop:
 				case TextDirection.RightLeft_TopBottom:
 				case TextDirection.BottomTop_LeftRight:
@@ -1454,7 +1493,7 @@ namespace Terminal.Gui {
 
 				int x, y;
 				// Horizontal Alignment
-				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (_textDirection))) {
+				if (_textAlignment == TextAlignment.Right || (_textAlignment == TextAlignment.Justified && !IsLeftToRight (Direction))) {
 					if (isVertical) {
 						var runesWidth = GetSumMaxCharWidth (Lines, line, TabWidth);
 						x = bounds.Right - runesWidth;
@@ -1487,7 +1526,7 @@ namespace Terminal.Gui {
 				}
 
 				// Vertical Alignment
-				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (_textDirection))) {
+				if (_textVerticalAlignment == VerticalTextAlignment.Bottom || (_textVerticalAlignment == VerticalTextAlignment.Justified && !IsTopToBottom (Direction))) {
 					if (isVertical) {
 						y = bounds.Bottom - runes.Length;
 					} else {

+ 1 - 1
Terminal.Gui/View/Frame.cs

@@ -58,7 +58,7 @@ namespace Terminal.Gui {
 		/// <inheritdoc/>
 		public override Rect FrameToScreen ()
 		{
-			// Frames are *Children* of a View, not SubViews. Thus View.FramToScreen will not work.
+			// Frames are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work.
 			// To get the screen-relative coordinates of a Frame, we need to know who
 			// the Parent is
 			var ret = Parent?.Frame ?? Frame;

+ 215 - 211
Terminal.Gui/View/Layout/PosDim.cs

@@ -3,26 +3,26 @@
 namespace Terminal.Gui;
 
 /// <summary>
-///         Describes the position of a <see cref="View" /> which can be an absolute value, a percentage, centered, or
-///         relative to the ending dimension. Integer values are implicitly convertible to
-///         an absolute <see cref="Pos" />. These objects are created using the static methods Percent,
-///         AnchorEnd, and Center. The <see cref="Pos" /> objects can be combined with the addition and
-///         subtraction operators.
+/// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
+/// relative to the ending dimension. Integer values are implicitly convertible to
+/// an absolute <see cref="Pos"/>. These objects are created using the static methods Percent,
+/// AnchorEnd, and Center. The <see cref="Pos"/> objects can be combined with the addition and
+/// subtraction operators.
 /// </summary>
 /// <remarks>
 ///         <para>
-///                 Use the <see cref="Pos" /> objects on the X or Y properties of a view to control the position.
+///         Use the <see cref="Pos"/> objects on the X or Y properties of a view to control the position.
 ///         </para>
 ///         <para>
-///                 These can be used to set the absolute position, when merely assigning an
-///                 integer value (via the implicit integer to <see cref="Pos" /> conversion), and they can be combined
-///                 to produce more useful layouts, like: Pos.Center - 3, which would shift the position
-///                 of the <see cref="View" /> 3 characters to the left after centering for example.
+///         These can be used to set the absolute position, when merely assigning an
+///         integer value (via the implicit integer to <see cref="Pos"/> conversion), and they can be combined
+///         to produce more useful layouts, like: Pos.Center - 3, which would shift the position
+///         of the <see cref="View"/> 3 characters to the left after centering for example.
 ///         </para>
 ///         <para>
-///                 Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View),
-///                 Top(View). The X(View) and Y(View) are
-///                 aliases to Left(View) and Top(View) respectively.
+///         Reference coordinates of another view by using the methods Left(View), Right(View), Bottom(View), Top(View).
+///         The X(View) and Y(View) are
+///         aliases to Left(View) and Top(View) respectively.
 ///         </para>
 ///         <para>
 ///                 <list type="table">
@@ -32,103 +32,101 @@ namespace Terminal.Gui;
 ///                         </listheader>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Function(Func{int})" />
+///                                         <see cref="Pos.Function(Func{int})"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that computes the position by executing the
-///                                         provided function. The function will be called every time the position is
-///                                         needed.
+///                                 Creates a <see cref="Pos"/> object that computes the position by executing the provided
+///                                 function. The function will be called every time the position is needed.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Percent(float)" />
+///                                         <see cref="Pos.Percent(float)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that is a percentage of the width or height
-///                                         of the SuperView.
+///                                 Creates a <see cref="Pos"/> object that is a percentage of the width or height of the
+///                                 SuperView.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Anchor(int)" />
+///                                         <see cref="Pos.Anchor(int)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that is anchored to the end (right side or
-///                                         bottom) of the dimension,
-///                                         useful to flush the layout from the right or bottom.
+///                                 Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom)
+///                                 of the dimension,
+///                                 useful to flush the layout from the right or bottom.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Center" />
+///                                         <see cref="Pos.Center"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that can be used to center the
-///                                         <see cref="View" />.
+///                                 Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.At(int)" />
+///                                         <see cref="Pos.At(int)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that is an absolute position based on the
-///                                         specified integer value.
+///                                 Creates a <see cref="Pos"/> object that is an absolute position based on the specified
+///                                 integer value.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Left" />
+///                                         <see cref="Pos.Left"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that tracks the Left (X) position of the
-///                                         specified <see cref="View" />.
+///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                                 <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.X(View)" />
+///                                         <see cref="Pos.X(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that tracks the Left (X) position of the
-///                                         specified <see cref="View" />.
+///                                 Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified
+///                                 <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Top(View)" />
+///                                         <see cref="Pos.Top(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that tracks the Top (Y) position of the
-///                                         specified <see cref="View" />.
+///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                                 <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Y(View)" />
+///                                         <see cref="Pos.Y(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that tracks the Top (Y) position of the
-///                                         specified <see cref="View" />.
+///                                 Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified
+///                                 <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Right(View)" />
+///                                         <see cref="Pos.Right(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that tracks the Right (X+Width) coordinate
-///                                         of the specified <see cref="View" />.
+///                                 Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the
+///                                 specified <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Pos.Bottom(View)" />
+///                                         <see cref="Pos.Bottom(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Pos" /> object that tracks the Bottom (Y+Height)
-///                                         coordinate of the specified <see cref="View" />
+///                                 Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the
+///                                 specified <see cref="View"/>
 ///                                 </description>
 ///                         </item>
 /// 
@@ -139,22 +137,22 @@ public class Pos {
 	internal virtual int Anchor (int width) => 0;
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that computes the position by executing the provided function. The function
-	///         will be called every time the position is needed.
+	/// Creates a <see cref="Pos"/> object that computes the position by executing the provided function. The function will be
+	/// called every time the position is needed.
 	/// </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) => new PosFunc (function);
 
 	/// <summary>
-	///         Creates a percentage <see cref="Pos" /> object
+	/// Creates a percentage <see cref="Pos"/> object
 	/// </summary>
-	/// <returns>The percent <see cref="Pos" /> object.</returns>
+	/// <returns>The percent <see cref="Pos"/> object.</returns>
 	/// <param name="n">A value between 0 and 100 representing the percentage.</param>
 	/// <example>
-	///         This creates a <see cref="TextField" />that is centered horizontally, is 50% of the way down,
-	///         is 30% the height, and is 80% the width of the <see cref="View" /> it added to.
-	///  <code>
+	/// This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
+	/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
+	/// <code>
 	///  var textView = new TextView () {
 	/// 	X = Pos.Center (),
 	/// 	Y = Pos.Percent (50),
@@ -173,14 +171,14 @@ public class Pos {
 	}
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that is anchored to the end (right side or bottom) of the dimension,
-	///         useful to flush the layout from the right or bottom.
+	/// Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of the dimension,
+	/// useful to flush the layout from the right or bottom.
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> object anchored to the end (the bottom or the right side).</returns>
+	/// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side).</returns>
 	/// <param name="offset">The view will be shifted left or up by the amount specified.</param>
 	/// <example>
-	///         This sample shows how align a <see cref="Button" /> to the bottom-right of a <see cref="View" />.
-	///         <code>
+	/// This sample shows how align a <see cref="Button"/> to the bottom-right of a <see cref="View"/>.
+	/// <code>
 	/// // See Issue #502 
 	/// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
 	/// anchorButton.Y = Pos.AnchorEnd (1);
@@ -196,13 +194,13 @@ public class Pos {
 	}
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that can be used to center the <see cref="View" />.
+	/// Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.
 	/// </summary>
 	/// <returns>The center Pos.</returns>
 	/// <example>
-	///         This creates a <see cref="TextField" />that is centered horizontally, is 50% of the way down,
-	///         is 30% the height, and is 80% the width of the <see cref="View" /> it added to.
-	///         <code>
+	/// This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
+	/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
+	/// <code>
 	///  var textView = new TextView () {
 	/// 	X = Pos.Center (),
 	/// 	Y = Pos.Percent (50),
@@ -214,25 +212,25 @@ public class Pos {
 	public static Pos Center () => new PosCenter ();
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that is an absolute position based on the specified integer value.
+	/// Creates a <see cref="Pos"/> object that is an absolute position based on the specified integer value.
 	/// </summary>
-	/// <returns>The Absolute <see cref="Pos" />.</returns>
-	/// <param name="n">The value to convert to the <see cref="Pos" />.</param>
+	/// <returns>The Absolute <see cref="Pos"/>.</returns>
+	/// <param name="n">The value to convert to the <see cref="Pos"/>.</param>
 	public static Pos At (int n) => new PosAbsolute (n);
 
 	/// <summary>
-	///         Creates an Absolute <see cref="Pos" /> from the specified integer value.
+	/// Creates an Absolute <see cref="Pos"/> from the specified integer value.
 	/// </summary>
-	/// <returns>The Absolute <see cref="Pos" />.</returns>
-	/// <param name="n">The value to convert to the <see cref="Pos" /> .</param>
+	/// <returns>The Absolute <see cref="Pos"/>.</returns>
+	/// <param name="n">The value to convert to the <see cref="Pos"/> .</param>
 	public static implicit operator Pos (int n) => new PosAbsolute (n);
 
 	/// <summary>
-	///         Adds a <see cref="Terminal.Gui.Pos" /> to a <see cref="Terminal.Gui.Pos" />, yielding a new <see cref="Pos" />.
+	/// Adds a <see cref="Terminal.Gui.Pos"/> to a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.
 	/// </summary>
-	/// <param name="left">The first <see cref="Terminal.Gui.Pos" /> to add.</param>
-	/// <param name="right">The second <see cref="Terminal.Gui.Pos" /> to add.</param>
-	/// <returns>The <see cref="Pos" /> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
+	/// <param name="left">The first <see cref="Terminal.Gui.Pos"/> to add.</param>
+	/// <param name="right">The second <see cref="Terminal.Gui.Pos"/> to add.</param>
+	/// <returns>The <see cref="Pos"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
 	public static Pos operator + (Pos left, Pos right)
 	{
 		if (left is PosAbsolute && right is PosAbsolute) {
@@ -244,12 +242,11 @@ public class Pos {
 	}
 
 	/// <summary>
-	///         Subtracts a <see cref="Terminal.Gui.Pos" /> from a <see cref="Terminal.Gui.Pos" />, yielding a new
-	///         <see cref="Pos" />.
+	/// Subtracts a <see cref="Terminal.Gui.Pos"/> from a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.
 	/// </summary>
-	/// <param name="left">The <see cref="Terminal.Gui.Pos" /> to subtract from (the minuend).</param>
-	/// <param name="right">The <see cref="Terminal.Gui.Pos" /> to subtract (the subtrahend).</param>
-	/// <returns>The <see cref="Pos" /> that is the <c>left</c> minus <c>right</c>.</returns>
+	/// <param name="left">The <see cref="Terminal.Gui.Pos"/> to subtract from (the minuend).</param>
+	/// <param name="right">The <see cref="Terminal.Gui.Pos"/> to subtract (the subtrahend).</param>
+	/// <returns>The <see cref="Pos"/> that is the <c>left</c> minus <c>right</c>.</returns>
 	public static Pos operator - (Pos left, Pos right)
 	{
 		if (left is PosAbsolute && right is PosAbsolute) {
@@ -269,47 +266,45 @@ public class Pos {
 	}
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that tracks the Left (X) position of the specified <see cref="View" />.
+	/// Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View" />  that will be tracked.</param>
+	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 	public static Pos Left (View view) => new PosView (view, 0);
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that tracks the Left (X) position of the specified <see cref="View" />.
+	/// Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View" />  that will be tracked.</param>
+	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 	public static Pos X (View view) => new PosView (view, 0);
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that tracks the Top (Y) position of the specified <see cref="View" />.
+	/// Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View" />  that will be tracked.</param>
+	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 	public static Pos Top (View view) => new PosView (view, 1);
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that tracks the Top (Y) position of the specified <see cref="View" />.
+	/// Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View" />  that will be tracked.</param>
+	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 	public static Pos Y (View view) => new PosView (view, 1);
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that tracks the Right (X+Width) coordinate of the specified
-	///         <see cref="View" />.
+	/// Creates a <see cref="Pos"/> object that tracks the Right (X+Width) coordinate of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View" />  that will be tracked.</param>
+	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 	public static Pos Right (View view) => new PosView (view, 2);
 
 	/// <summary>
-	///         Creates a <see cref="Pos" /> object that tracks the Bottom (Y+Height) coordinate of the specified
-	///         <see cref="View" />
+	/// Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/>
 	/// </summary>
-	/// <returns>The <see cref="Pos" /> that depends on the other view.</returns>
-	/// <param name="view">The <see cref="View" />  that will be tracked.</param>
+	/// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
+	/// <param name="view">The <see cref="View"/>  that will be tracked.</param>
 	public static Pos Bottom (View view) => new PosView (view, 3);
 
 	/// <summary>Serves as the default hash function. </summary>
@@ -319,8 +314,7 @@ public class Pos {
 	/// <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" />.
+	/// <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;
 
@@ -422,25 +416,39 @@ public class Pos {
 
 		internal override int Anchor (int width)
 		{
-			return side switch {
-				0 => Target.Frame.X,
-				1 => Target.Frame.Y,
-				2 => Target.Frame.Right,
-				3 => Target.Frame.Bottom,
-				_ => 0
-			};
+			switch (side) {
+			case 0: return Target.Frame.X;
+			case 1: return Target.Frame.Y;
+			case 2: return Target.Frame.Right;
+			case 3: return Target.Frame.Bottom;
+			default:
+				return 0;
+			}
 		}
 
 		public override string ToString ()
 		{
-			string tside = side switch {
-				0 => "x",
-				1 => "y",
-				2 => "right",
-				3 => "bottom",
-				_ => "unknown"
-			};
-			// Note: We do not checkt `Target` for null here to intentionally throw if so
+			string tside;
+			switch (side) {
+			case 0:
+				tside = "x";
+				break;
+			case 1:
+				tside = "y";
+				break;
+			case 2:
+				tside = "right";
+				break;
+			case 3:
+				tside = "bottom";
+				break;
+			default:
+				tside = "unknown";
+				break;
+			}
+			if (Target == null) {
+				throw new NullReferenceException (nameof (Target));
+			}
 			return $"View(side={tside},target={Target})";
 		}
 
@@ -452,16 +460,16 @@ public class Pos {
 
 /// <summary>
 ///         <para>
-///                 A Dim object describes the dimensions of a <see cref="View" />. Dim is the type of the
-///                 <see cref="View.Width" /> and
-///                 <see cref="View.Height" /> properties of <see cref="View" />. Dim objects enable Computed Layout (see
-///                 <see cref="LayoutStyle.Computed" />)
-///                 to automatically manage the dimensions of a view.
+///         A Dim object describes the dimensions of a <see cref="View"/>. Dim is the type of the <see cref="View.Width"/>
+///         and
+///         <see cref="View.Height"/> properties of <see cref="View"/>. Dim objects enable Computed Layout (see
+///         <see cref="LayoutStyle.Computed"/>)
+///         to automatically manage the dimensions of a view.
 ///         </para>
 ///         <para>
-///                 Integer values are implicitly convertible to an absolute <see cref="Dim" />. These objects are created
-///                 using the static methods described below.
-///                 The <see cref="Dim" /> objects can be combined with the addition and subtraction operators.
+///         Integer values are implicitly convertible to an absolute <see cref="Dim"/>. These objects are created using the
+///         static methods described below.
+///         The <see cref="Dim"/> objects can be combined with the addition and subtraction operators.
 ///         </para>
 /// </summary>
 /// <remarks>
@@ -473,57 +481,56 @@ public class Pos {
 ///                         </listheader>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Dim.Function(Func{int})" />
+///                                         <see cref="Dim.Auto"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Dim" /> object that computes the dimension by executing
-///                                         the provided function. The function will be called every time the dimension is
-///                                         needed.
+///                                  Creates a <see cref="Dim" /> object that automatically sizes the view to fit
+///                                  all of the view's SubViews.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Dim.Percent(float, bool)" />
+///                                         <see cref="Dim.Function(Func{int})"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Dim" /> object that is a percentage of the width or height
-///                                         of the SuperView.
+///                                 Creates a <see cref="Dim"/> object that computes the dimension by executing the
+///                                 provided function. The function will be called every time the dimension is needed.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Dim.Fill(int)" />
+///                                         <see cref="Dim.Percent(float, bool)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Dim" /> object that fills the dimension, leaving the
-///                                         specified number of columns for a margin.
+///                                 Creates a <see cref="Dim"/> object that is a percentage of the width or height of the
+///                                 SuperView.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Dim.Auto" />
+///                                         <see cref="Dim.Fill(int)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Dim" /> object that automatically sizes the view to fit
-///                                         all of the view's SubViews.
+///                                 Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified
+///                                 number of columns for a margin.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Dim.Width(View)" />
+///                                         <see cref="Dim.Width(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Dim" /> object that tracks the Width of the specified
-///                                         <see cref="View" />.
+///                                 Creates a <see cref="Dim"/> object that tracks the Width of the specified
+///                                 <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                         <item>
 ///                                 <term>
-///                                         <see cref="Dim.Height(View)" />
+///                                         <see cref="Dim.Height(View)"/>
 ///                                 </term>
 ///                                 <description>
-///                                         Creates a <see cref="Dim" /> object that tracks the Height of the specified
-///                                         <see cref="View" />.
+///                                 Creates a <see cref="Dim"/> object that tracks the Height of the specified
+///                                 <see cref="View"/>.
 ///                                 </description>
 ///                         </item>
 ///                 </list>
@@ -532,6 +539,8 @@ public class Pos {
 ///         </para>
 /// </remarks>
 public class Dim {
+	internal virtual int Anchor (int width) => 0;
+
 	/// <summary>
 	///         Specifies how <see cref="DimAuto" /> will compute the dimension.
 	/// </summary>
@@ -547,54 +556,6 @@ public class Dim {
 		Subviews
 	}
 
-	internal virtual int Anchor (int width) => 0;
-
-	/// <summary>
-	///         Creates a function <see cref="Dim" /> object that computes the dimension by executing the provided function.
-	///         The function will be called every time the dimension is needed.
-	/// </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) => new DimFunc (function);
-
-	/// <summary>
-	///         Creates a percentage <see cref="Dim" /> object that is a percentage of the width or height of the SuperView.
-	/// </summary>
-	/// <returns>The percent <see cref="Dim" /> object.</returns>
-	/// <param name="n">A value between 0 and 100 representing the percentage.</param>
-	/// <param name="r">
-	///         If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions.
-	///         If <c>false</c> is computed based on the whole original space.
-	/// </param>
-	/// <example>
-	///         This initializes a <see cref="TextField" />that is centered horizontally, is 50% of the way down,
-	///         is 30% the height, and is 80% the width of the <see cref="View" /> it added to.
-	///         <code>
-	///  var textView = new TextView () {
-	/// 	X = Pos.Center (),
-	/// 	Y = Pos.Percent (50),
-	/// 	Width = Dim.Percent (80),
-	///  	Height = Dim.Percent (30),
-	///  };
-	///  </code>
-	/// </example>
-	public static Dim Percent (float n, bool r = false)
-	{
-		if (n is < 0 or > 100) {
-			throw new ArgumentException ("Percent value must be between 0 and 100");
-		}
-
-		return new DimFactor (n / 100, r);
-	}
-
-	/// <summary>
-	///         Creates a <see cref="Dim" /> object that fills the dimension, leaving the specified number of columns for a
-	///         margin.
-	/// </summary>
-	/// <returns>The Fill dimension.</returns>
-	/// <param name="margin">Margin to use.</param>
-	public static Dim Fill (int margin = 0) => new DimFill (margin);
-
 	/// <summary>
 	///         Creates a <see cref="Dim" /> object that automatically sizes the view to fit all of the view's SubViews.
 	/// </summary>
@@ -628,27 +589,72 @@ public class Dim {
 		}
 		return new DimAuto (style, min, max);
 	}
+	
+	/// <summary>
+	/// Creates a function <see cref="Dim"/> object that computes the dimension by executing the provided function.
+	/// The function will be called every time the dimension is needed.
+	/// </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) => new DimFunc (function);
+
+	/// <summary>
+	/// Creates a percentage <see cref="Dim"/> object that is a percentage of the width or height of the SuperView.
+	/// </summary>
+	/// <returns>The percent <see cref="Dim"/> object.</returns>
+	/// <param name="n">A value between 0 and 100 representing the percentage.</param>
+	/// <param name="r">
+	/// If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions.
+	/// If <c>false</c> is computed based on the whole original space.
+	/// </param>
+	/// <example>
+	/// This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down,
+	/// is 30% the height, and is 80% the width of the <see cref="View"/> it added to.
+	/// <code>
+	///  var textView = new TextView () {
+	/// 	X = Pos.Center (),
+	/// 	Y = Pos.Percent (50),
+	/// 	Width = Dim.Percent (80),
+	///  	Height = Dim.Percent (30),
+	///  };
+	///  </code>
+	/// </example>
+	public static Dim Percent (float n, bool r = false)
+	{
+		if (n is < 0 or > 100) {
+			throw new ArgumentException ("Percent value must be between 0 and 100");
+		}
+
+		return new DimFactor (n / 100, r);
+	}
+
+	/// <summary>
+	/// Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified number of columns for a margin.
+	/// </summary>
+	/// <returns>The Fill dimension.</returns>
+	/// <param name="margin">Margin to use.</param>
+	public static Dim Fill (int margin = 0) => new DimFill (margin);
 
 	/// <summary>
-	///         Creates an Absolute <see cref="Dim" /> from the specified integer value.
+	/// Creates an Absolute <see cref="Dim"/> from the specified integer value.
 	/// </summary>
-	/// <returns>The Absolute <see cref="Dim" />.</returns>
+	/// <returns>The Absolute <see cref="Dim"/>.</returns>
 	/// <param name="n">The value to convert to the pos.</param>
 	public static implicit operator Dim (int n) => new DimAbsolute (n);
 
 	/// <summary>
-	///         Creates an Absolute <see cref="Dim" /> from the specified integer value.
+	/// Creates an Absolute <see cref="Dim"/> from the specified integer value.
 	/// </summary>
-	/// <returns>The Absolute <see cref="Dim" />.</returns>
-	/// <param name="n">The value to convert to the <see cref="Dim" />.</param>
+	/// <returns>The Absolute <see cref="Dim"/>.</returns>
+	/// <param name="n">The value to convert to the <see cref="Dim"/>.</param>
 	public static Dim Sized (int n) => new DimAbsolute (n);
 
 	/// <summary>
-	///         Adds a <see cref="Terminal.Gui.Dim" /> to a <see cref="Terminal.Gui.Dim" />, yielding a new <see cref="Dim" />.
+	/// Adds a <see cref="Terminal.Gui.Dim"/> to a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.
 	/// </summary>
-	/// <param name="left">The first <see cref="Terminal.Gui.Dim" /> to add.</param>
-	/// <param name="right">The second <see cref="Terminal.Gui.Dim" /> to add.</param>
-	/// <returns>The <see cref="Dim" /> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
+	/// <param name="left">The first <see cref="Terminal.Gui.Dim"/> to add.</param>
+	/// <param name="right">The second <see cref="Terminal.Gui.Dim"/> to add.</param>
+	/// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
 	public static Dim operator + (Dim left, Dim right)
 	{
 		if (left is DimAbsolute && right is DimAbsolute) {
@@ -660,12 +666,11 @@ public class Dim {
 	}
 
 	/// <summary>
-	///         Subtracts a <see cref="Terminal.Gui.Dim" /> from a <see cref="Terminal.Gui.Dim" />, yielding a new
-	///         <see cref="Dim" />.
+	/// Subtracts a <see cref="Terminal.Gui.Dim"/> from a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.
 	/// </summary>
-	/// <param name="left">The <see cref="Terminal.Gui.Dim" /> to subtract from (the minuend).</param>
-	/// <param name="right">The <see cref="Terminal.Gui.Dim" /> to subtract (the subtrahend).</param>
-	/// <returns>The <see cref="Dim" /> that is the <c>left</c> minus <c>right</c>.</returns>
+	/// <param name="left">The <see cref="Terminal.Gui.Dim"/> to subtract from (the minuend).</param>
+	/// <param name="right">The <see cref="Terminal.Gui.Dim"/> to subtract (the subtrahend).</param>
+	/// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
 	public static Dim operator - (Dim left, Dim right)
 	{
 		if (left is DimAbsolute && right is DimAbsolute) {
@@ -680,16 +685,16 @@ public class Dim {
 	static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout ();
 
 	/// <summary>
-	///         Creates a <see cref="Dim" /> object that tracks the Width of the specified <see cref="View" />.
+	/// Creates a <see cref="Dim"/> object that tracks the Width of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The width <see cref="Dim" /> of the other <see cref="View" />.</returns>
+	/// <returns>The width <see cref="Dim"/> of the other <see cref="View"/>.</returns>
 	/// <param name="view">The view that will be tracked.</param>
 	public static Dim Width (View view) => new DimView (view, 1);
 
 	/// <summary>
-	///         Creates a <see cref="Dim" /> object that tracks the Height of the specified <see cref="View" />.
+	/// Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.
 	/// </summary>
-	/// <returns>The height <see cref="Dim" /> of the other <see cref="View" />.</returns>
+	/// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>
 	/// <param name="view">The view that will be tracked.</param>
 	public static Dim Height (View view) => new DimView (view, 0);
 
@@ -700,8 +705,7 @@ public class Dim {
 	/// <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" />.
+	/// <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 Dim abs && abs == this;
 

+ 257 - 320
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -1,4 +1,5 @@
 using System;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics;
@@ -7,71 +8,87 @@ using System.Linq;
 namespace Terminal.Gui;
 
 /// <summary>
-/// Determines the LayoutStyle for a <see cref="View"/>, if Absolute, during <see cref="View.LayoutSubviews"/>, the
-/// value from the <see cref="View.Frame"/> will be used, if the value is Computed, then <see cref="View.Frame"/>
-/// will be updated from the X, Y <see cref="Pos"/> objects and the Width and Height <see cref="Dim"/> objects.
+///         <para>
+///         Indicates the LayoutStyle for the <see cref="View"/>.
+///         </para>
+///         <para>
+///         If Absolute, the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and
+///         <see cref="View.Height"/>
+///         objects are all absolute values and are not relative. The position and size of the view is described by
+///         <see cref="View.Frame"/>.
+///         </para>
+///         <para>
+///         If Computed, one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
+///         <see cref="View.Height"/>
+///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
+///         </para>
 /// </summary>
 public enum LayoutStyle {
 	/// <summary>
-	/// The position and size of the view are based <see cref="View.Frame"/>.
+	/// Indicates the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/>
+	/// objects are all absolute values and are not relative. The position and size of the view is described by
+	/// <see cref="View.Frame"/>.
 	/// </summary>
 	Absolute,
 
 	/// <summary>
-	/// The position and size of the view will be computed based on
-	/// <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and <see cref="View.Height"/>.
-	/// <see cref="View.Frame"/> will
-	/// provide the absolute computed values.
+	/// Indicates one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
+	/// <see cref="View.Height"/>
+	/// objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.  The position and size of the
+	/// view
+	/// will be computed based on these objects at layout time. <see cref="View.Frame"/> will provide the absolute computed
+	/// values.
 	/// </summary>
 	Computed
 }
 
 public partial class View {
 	bool _autoSize;
-
-	// The frame for the object. Relative to the SuperView's Bounds.
 	Rect _frame;
-
-	LayoutStyle _layoutStyle;
-
-	Dim _width, _height;
-
-	Pos _x, _y;
+	Dim _height = Dim.Sized (0);
+	Dim _width = Dim.Sized (0);
+	Pos _x = Pos.At (0);
+	Pos _y = Pos.At (0);
 
 	/// <summary>
-	/// Gets or sets location and size of the view. The frame is relative to the <see cref="SuperView"/>'s
-	/// <see cref="Bounds"/>.
+	/// Gets or sets the absolute location and dimension of the view.
 	/// </summary>
 	/// <value>
-	/// The rectangle describing the location and size of the view, in coordinates relative to the
-	/// <see cref="SuperView"/>.
+	/// The rectangle describing absolute location and dimension of the view,
+	/// in coordinates relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
 	/// </value>
 	/// <remarks>
 	///         <para>
-	///         Change the Frame when using the <see cref="LayoutStyle.Absolute"/> layout style to move or resize views.
+	///         Frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
 	///         </para>
 	///         <para>
-	///         Altering the Frame will change <see cref="LayoutStyle"/> to <see cref="LayoutStyle.Absolute"/>.
-	///         Additionally, <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> will be set
-	///         to the values of the Frame (using <see cref="Pos.PosAbsolute"/> and <see cref="Dim.DimAbsolute"/>).
+	///         Setting Frame will set <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/>
+	///         to the values of the corresponding properties of the <paramref name="value"/> parameter.
 	///         </para>
 	///         <para>
-	///         Altering the Frame will eventually (when the view is next drawn) cause the
-	///         <see cref="LayoutSubview(View, Rect)"/>
-	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
+	///         This causes <see cref="LayoutStyle"/> to be <see cref="LayoutStyle.Absolute"/>.
+	///         </para>
+	///         <para>
+	///         Altering the Frame will eventually (when the view hierarchy is next laid out via  see cref="LayoutSubviews"/>)
+	///         cause <see cref="LayoutSubview(View, Rect)"/> and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	///         </para>
 	/// </remarks>
-	public virtual Rect Frame {
+	public Rect Frame {
 		get => _frame;
 		set {
 			_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
-			//X = _frame.X;
-			//Y = _frame.Y;
-			//Width = _frame.Width;
-			//Height = _frame.Height;
-			if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
+
+			// If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so
+			// set all Pos/Dim to Absolute values.
+			_x = _frame.X;
+			_y = _frame.Y;
+			_width = _frame.Width;
+			_height = _frame.Height;
+
+			// TODO: Figure out if the below can be optimized.
+			if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) {
 				LayoutFrames ();
-				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+				SetTextFormatterSize ();
 				SetNeedsLayout ();
 				SetNeedsDisplay ();
 			}
@@ -84,8 +101,8 @@ public partial class View {
 	/// </summary>
 	/// <remarks>
 	///         <para>
-	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
-	///         View's content
+	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
+	///         content
 	///         and are not clipped by the View's Clip Area.
 	///         </para>
 	///         <para>
@@ -109,8 +126,8 @@ public partial class View {
 	///         <see cref="BorderStyle"/> provides a simple helper for turning a simple border frame on or off.
 	///         </para>
 	///         <para>
-	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
-	///         View's content
+	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
+	///         content
 	///         and are not clipped by the View's Clip Area.
 	///         </para>
 	///         <para>
@@ -164,8 +181,8 @@ public partial class View {
 	/// </summary>
 	/// <remarks>
 	///         <para>
-	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the
-	///         View's content
+	///         The frames (<see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>) are not part of the View's
+	///         content
 	///         and are not clipped by the View's Clip Area.
 	///         </para>
 	///         <para>
@@ -178,54 +195,28 @@ public partial class View {
 	public Frame Padding { get; private set; }
 
 	/// <summary>
-	/// Controls how the View's <see cref="Frame"/> is computed during <see cref="LayoutSubviews"/>. If the style is set to
-	/// <see cref="LayoutStyle.Absolute"/>, LayoutSubviews does not change the <see cref="Frame"/>.
-	/// If the style is <see cref="LayoutStyle.Computed"/> the <see cref="Frame"/> is updated using
-	/// the <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties.
-	/// </summary>
-	/// <remarks>
 	///         <para>
-	///         Setting this property to <see cref="LayoutStyle.Absolute"/> will cause <see cref="Frame"/> to determine the
-	///         size and position of the view. <see cref="X"/> and <see cref="Y"/> will be set to
-	///         <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
+	///         Gets the LayoutStyle for the <see cref="View"/>.
 	///         </para>
 	///         <para>
-	///         Setting this property to <see cref="LayoutStyle.Computed"/> will cause the view to use the
-	///         <see cref="LayoutSubviews"/> method to
-	///         size and position of the view. If either of the <see cref="X"/> and <see cref="Y"/> properties are `null`
-	///         they will be set to <see cref="Pos.PosAbsolute"/> using
-	///         the current value of <see cref="Frame"/>.
-	///         If either of the <see cref="Width"/> and <see cref="Height"/> properties are `null` they will be set to
-	///         <see cref="Dim.DimAbsolute"/> using <see cref="Frame"/>.
+	///         If Absolute, the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, and
+	///         <see cref="View.Height"/>
+	///         objects are all absolute values and are not relative. The position and size of the view is described by
+	///         <see cref="View.Frame"/>.
 	///         </para>
-	/// </remarks>
+	///         <para>
+	///         If Computed, one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
+	///         <see cref="View.Height"/>
+	///         objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
+	///         </para>
+	/// </summary>
 	/// <value>The layout style.</value>
 	public LayoutStyle LayoutStyle {
-		get => _layoutStyle;
-		//if ((X == null || X is Pos.PosAbsolute) && (Y == null || Y is Pos.PosAbsolute) &&
-		//(Width == null || Width is Dim.DimAbsolute) && (Height == null || Height is Dim.DimAbsolute)) {
-		//	return LayoutStyle.Absolute;
-		//} else {
-		//	return LayoutStyle.Computed;
-		//}
-		set {
-			_layoutStyle = value;
-			//switch (_layoutStyle) {
-			//case LayoutStyle.Absolute:
-			//	X = Frame.X;
-			//	Y = Frame.Y;
-			//	Width = Frame.Width;
-			//	Height = Frame.Height;
-			//	break;
-
-			//case LayoutStyle.Computed:
-			//	X ??= Frame.X;
-			//	Y ??= Frame.Y;
-			//	Width ??= Frame.Width;
-			//	Height ??= Frame.Height;
-			//	break;
-			//}
-			SetNeedsLayout ();
+		get {
+			if (_x is Pos.PosAbsolute && _y is Pos.PosAbsolute && _width is Dim.DimAbsolute && _height is Dim.DimAbsolute) {
+				return LayoutStyle.Absolute;
+			}
+			return LayoutStyle.Computed;
 		}
 	}
 
@@ -237,16 +228,15 @@ public partial class View {
 	/// <remarks>
 	///         <para>
 	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value of Bounds is indeterminate until
-	///         the
-	///         view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+	///         the view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
 	///         called.
 	///         </para>
 	///         <para>
-	///         Updates to the Bounds updates <see cref="Frame"/>, and has the same side effects as updating the
+	///         Updates to the Bounds updates <see cref="Frame"/>, and has the same effect as updating the
 	///         <see cref="Frame"/>.
 	///         </para>
 	///         <para>
-	///         Altering the Bounds will eventually (when the view is next drawn) cause the
+	///         Altering the Bounds will eventually (when the view is next laid out) cause the
 	///         <see cref="LayoutSubview(View, Rect)"/>
 	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	///         </para>
@@ -263,12 +253,17 @@ public partial class View {
 				Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
 			}
 #endif // DEBUG
-			//var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size);
 			var frameRelativeBounds = FrameGetInsideBounds ();
 			return new Rect (default, frameRelativeBounds.Size);
 		}
 		set {
-			// BUGBUG: Margin etc.. can be null (if typeof(Frame))
+			// TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
+			// TODO: correct behavior, but is silent. Perhaps an exception?
+#if DEBUG
+			if (value.Location != Point.Empty) {
+				Debug.WriteLine ($"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}");
+			}
+#endif // DEBUG
 			Frame = new Rect (Frame.Location,
 				new Size (
 					value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
@@ -284,40 +279,28 @@ public partial class View {
 	/// <value>The <see cref="Pos"/> object representing the X position.</value>
 	/// <remarks>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the
-	///         view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+	///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the
+	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
 	///         called.
 	///         </para>
 	///         <para>
 	///         Changing this property will eventually (when the view is next drawn) cause the
-	///         <see cref="LayoutSubview(View, Rect)"/> and
+	///          <see cref="LayoutSubview(View, Rect)"/> and
 	///         <see cref="OnDrawContent(Rect)"/> methods to be called.
 	///         </para>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the
-	///         <see cref="Frame"/> to be updated. If
+	///         Changing this property will cause <see cref="Frame"/> to be updated. If
 	///         the new value is not of type <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to
 	///         <see cref="LayoutStyle.Computed"/>.
 	///         </para>
 	///         <para>
-	///         <see langword="null"/> is the same as <c>Pos.Absolute(0)</c>.
+	///         The default value is <c>Pos.At (0)</c>.
 	///         </para>
 	/// </remarks>
 	public Pos X {
 		get => VerifyIsInitialized (_x, nameof (X));
 		set {
-			// BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
-
-			if (value == null) {
-				_x = Pos;
-			}
-			
-			if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) {
-				CheckAbsolute (nameof (X), _x, value);
-			}
-
-			_x = value;
-
+			_x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null");
 			OnResizeNeeded ();
 		}
 	}
@@ -328,8 +311,8 @@ public partial class View {
 	/// <value>The <see cref="Pos"/> object representing the Y position.</value>
 	/// <remarks>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the
-	///         view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+	///         If set to a relative value (e.g. <see cref="Pos.Center"/>) the value is indeterminate until the
+	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
 	///         called.
 	///         </para>
 	///         <para>
@@ -338,38 +321,30 @@ public partial class View {
 	///         <see cref="OnDrawContent(Rect)"/> methods to be called.
 	///         </para>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the
-	///         <see cref="Frame"/> to be updated. If
+	///         Changing this property will cause <see cref="Frame"/> to be updated. If
 	///         the new value is not of type <see cref="Pos.PosAbsolute"/> the <see cref="LayoutStyle"/> will change to
 	///         <see cref="LayoutStyle.Computed"/>.
 	///         </para>
 	///         <para>
-	///         <see langword="null"/> is the same as <c>Pos.Absolute(0)</c>.
+	///         The default value is <c>Pos.At (0)</c>.
 	///         </para>
 	/// </remarks>
 	public Pos Y {
 		get => VerifyIsInitialized (_y, nameof (Y));
 		set {
-			// BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
-
-			if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) {
-				CheckAbsolute (nameof (Y), _y, value);
-			}
-
-			_y = value;
-
+			_y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null");
 			OnResizeNeeded ();
 		}
 	}
 
 	/// <summary>
-	/// Gets or sets the width of the view.
+	/// Gets or sets the width dimension of the view.
 	/// </summary>
 	/// <value>The <see cref="Dim"/> object representing the width of the view (the number of columns).</value>
 	/// <remarks>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the
-	///         view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+	///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the
+	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
 	///         called.
 	///         </para>
 	///         <para>
@@ -378,26 +353,21 @@ public partial class View {
 	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	///         </para>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the
-	///         <see cref="Frame"/> to be updated. If
+	///         Changing this property will cause <see cref="Frame"/> to be updated. If
 	///         the new value is not of type <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to
 	///         <see cref="LayoutStyle.Computed"/>.
 	///         </para>
+	///         <para>
+	///         The default value is <c>Dim.Sized (0)</c>.
+	///         </para>
 	/// </remarks>
 	public Dim Width {
 		get => VerifyIsInitialized (_width, nameof (Width));
 		set {
-			// BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
-			if (ValidatePosDim) {
-				CheckDimAuto ();
-				if (LayoutStyle == LayoutStyle.Computed) {
-					CheckAbsolute (nameof (Width), _width, value);
-				}
-			}
-
-			_width = value;
+			_width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null");
 
 			if (ValidatePosDim) {
+				CheckDimAuto ();
 				var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
 
 				if (IsAdded && AutoSize && !isValidNewAutSize) {
@@ -409,13 +379,13 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets the height of the view.
+	/// Gets or sets the height dimension of the view.
 	/// </summary>
 	/// <value>The <see cref="Dim"/> object representing the height of the view (the number of rows).</value>
 	/// <remarks>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Computed"/> the value is indeterminate until the
-	///         view has been initialized (<see creft="IsInitialized"/> is true) and <see cref="LayoutSubviews"/> has been
+	///         If set to a relative value (e.g. <see cref="Dim.Fill(int)"/>) the value is indeterminate until the
+	///         view has been initialized (<see cref="IsInitialized"/> is true) and <see cref="SetRelativeLayout(Rect)"/> has been
 	///         called.
 	///         </para>
 	///         <para>
@@ -424,26 +394,21 @@ public partial class View {
 	///         and <see cref="OnDrawContent(Rect)"/> methods to be called.
 	///         </para>
 	///         <para>
-	///         If <see cref="LayoutStyle"/> is <see cref="LayoutStyle.Absolute"/> changing this property will cause the
-	///         <see cref="Frame"/> to be updated. If
+	///         Changing this property will cause <see cref="Frame"/> to be updated. If
 	///         the new value is not of type <see cref="Dim.DimAbsolute"/> the <see cref="LayoutStyle"/> will change to
 	///         <see cref="LayoutStyle.Computed"/>.
 	///         </para>
+	///         <para>
+	///         The default value is <c>Dim.Sized (0)</c>.
+	///         </para>
 	/// </remarks>
 	public Dim Height {
 		get => VerifyIsInitialized (_height, nameof (Height));
 		set {
-			// BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
-			if (ValidatePosDim) {
-				CheckDimAuto ();
-				if (LayoutStyle == LayoutStyle.Computed) {
-					CheckAbsolute (nameof (Height), _height, value);
-				}
-			}
-
-			_height = value;
+			_height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null");
 
 			if (ValidatePosDim) {
+				CheckDimAuto ();
 				var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
 
 				if (IsAdded && AutoSize && !isValidNewAutSize) {
@@ -458,19 +423,73 @@ public partial class View {
 	/// Gets or sets whether validation of <see cref="Pos"/> and <see cref="Dim"/> occurs.
 	/// </summary>
 	/// <remarks>
-	/// Setting this to <see langword="true"/> will enable validation of <see cref="X"/>, <see cref="Y"/>,
-	/// <see cref="Width"/>, and <see cref="Height"/>
-	/// during set operations and in <see cref="LayoutSubviews"/>.If invalid settings are discovered exceptions will be thrown
+	/// Setting this to <see langword="true"/> will enable validation of <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>,
+	/// and <see cref="Height"/>
+	/// during set operations and in <see cref="LayoutSubviews"/>. If invalid settings are discovered exceptions will be thrown
 	/// indicating the error.
 	/// This will impose a performance penalty and thus should only be used for debugging.
 	/// </remarks>
 	public bool ValidatePosDim { get; set; }
 
+	/// <summary>
+	/// Throws an <see cref="InvalidOperationException"/> if any of the SubViews are using Dim objects that depend on this
+	/// Views dimensions.
+	/// </summary>
+	/// <exception cref="InvalidOperationException"></exception>
+	void CheckDimAuto ()
+	{
+		if (!ValidatePosDim || !IsInitialized || Width is not Dim.DimAuto && Height is not Dim.DimAuto) {
+			return;
+		}
+
+		void ThrowInvalid (View view, object checkPosDim, string name)
+		{
+			// TODO: Figure out how to make CheckDimAuto deal with PosCombine
+			object bad = null;
+			switch (checkPosDim) {
+			case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine:
+				bad = pos;
+				break;
+			case Pos pos and Pos.PosCombine:
+				// Recursively check for not Absolute or not View
+				ThrowInvalid (view, (pos as Pos.PosCombine)._left,  name);
+				ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
+				break;
+
+			case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine:
+				bad = dim;
+				break;
+			case Dim dim and Dim.DimCombine:
+				// Recursively check for not Absolute or not View
+				ThrowInvalid (view, (dim as Dim.DimCombine)._left,  name);
+				ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
+				break;
+			}
+
+			if (bad != null) {
+				throw new InvalidOperationException (
+					@$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto.");
+			}
+		}
+
+		// Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
+		foreach (var view in Subviews) {
+			if (Width is Dim.DimAuto { _min: null }) {
+				ThrowInvalid (view, view.Width, nameof (view.Width));
+				ThrowInvalid (view, view.X,     nameof (view.X));
+			}
+			if (Height is Dim.DimAuto { _min: null }) {
+				ThrowInvalid (view, view.Height, nameof (view.Height));
+				ThrowInvalid (view, view.Y,      nameof (view.Y));
+			}
+		}
+	}
+	
 	internal bool LayoutNeeded { get; private set; } = true;
 
 	/// <summary>
 	/// Gets or sets a flag that determines whether the View will be automatically resized to fit the <see cref="Text"/>
-	/// within <see cref="Bounds"/>
+	/// within <see cref="Bounds"/>.
 	/// <para>
 	/// The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If <see langword="true"/>
 	/// then
@@ -478,8 +497,12 @@ public partial class View {
 	/// if <see cref="Text"/> won't fit the view will be resized as needed.
 	/// </para>
 	/// <para>
-	/// In addition, if <see cref="ValidatePosDim"/> is <see langword="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.
+	/// If <see cref="AutoSize"/> is set to <see langword="true"/> then <see cref="Width"/> and <see cref="Height"/>
+	/// will be changed to <see cref="Dim.DimAbsolute"/> if they are not already.
+	/// </para>
+	/// <para>
+	/// If <see cref="AutoSize"/> is set to <see langword="false"/> then <see cref="Width"/> and <see cref="Height"/>
+	/// will left unchanged.
 	/// </para>
 	/// </summary>
 	public virtual bool AutoSize {
@@ -598,125 +621,49 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Throws an <see cref="InvalidOperationException"/> if any of the SubViews are using Dim objects that depend on this
-	/// Views dimensions.
-	/// </summary>
-	/// <exception cref="InvalidOperationException"></exception>
-	void CheckDimAuto ()
-	{
-		if (!ValidatePosDim || !IsInitialized || Width is not Dim.DimAuto && Height is not Dim.DimAuto) {
-			return;
-		}
-
-		void ThrowInvalid (View view, object checkPosDim, string name)
-		{
-			// TODO: Figure out how to make CheckDimAuto deal with PosCombine
-			object bad = null;
-			switch (checkPosDim) {
-			case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine:
-				bad = pos;
-				break;
-			case Pos pos and Pos.PosCombine:
-				// Recursively check for not Absolute or not View
-				ThrowInvalid (view, (pos as Pos.PosCombine)._left,  name);
-				ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
-				break;
-
-			case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine:
-				bad = dim;
-				break;
-			case Dim dim and Dim.DimCombine:
-				// Recursively check for not Absolute or not View
-				ThrowInvalid (view, (dim as Dim.DimCombine)._left,  name);
-				ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
-				break;
-			}
-
-			if (bad != null) {
-				throw new InvalidOperationException (
-					@$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto.");
-			}
-		}
-
-		// Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
-		foreach (var view in Subviews) {
-			if (Width is Dim.DimAuto { _min: null }) {
-				ThrowInvalid (view, view.Width, nameof (view.Width));
-				ThrowInvalid (view, view.X,     nameof (view.X));
-			}
-			if (Height is Dim.DimAuto { _min: null }) {
-				ThrowInvalid (view, view.Height, nameof (view.Height));
-				ThrowInvalid (view, view.Y,      nameof (view.Y));
-			}
-		}
-	}
-
-	/// <summary>
-	/// Throws an <see cref="ArgumentException"/> if <paramref name="newValue"/> is <see cref="Pos.PosAbsolute"/> or
-	/// <see cref="Dim.DimAbsolute"/>.
-	/// Used when <see cref="ValidatePosDim"/> is turned on to verify correct <see cref="LayoutStyle.Computed"/> behavior.
-	/// </summary>
-	/// <remarks>
-	/// Does not verify if this view is Toplevel (WHY??!?).
-	/// </remarks>
-	/// <param name="prop">The property name.</param>
-	/// <param name="oldValue"></param>
-	/// <param name="newValue"></param>
-	void CheckAbsolute (string prop, object oldValue, object newValue)
-	{
-		if (!IsInitialized || !ValidatePosDim || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) {
-			return;
-		}
-
-		if (oldValue.GetType () != newValue.GetType () && newValue is (Pos.PosAbsolute or Dim.DimAbsolute)) {
-			throw new ArgumentException ($@"{prop} must not be Absolute if LayoutStyle is Computed", prop);
-		}
-	}
-
-	/// <summary>
-	/// Called whenever the view needs to be resized. Sets <see cref="Frame"/> and
-	/// triggers a <see cref="LayoutSubviews()"/> call.
+	/// Called whenever the view needs to be resized. This is called whenever <see cref="Frame"/>,
+	/// <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or <see cref="View.Height"/> changes.
 	/// </summary>
 	/// <remarks>
-	/// Can be overridden if the view resize behavior is different than the default.
+	///         <para>
+	///         Determines the relative bounds of the <see cref="View"/> and its <see cref="Frame"/>s, and then calls
+	///         <see cref="SetRelativeLayout(Rect)"/> to update the view. 
+	///         </para>
 	/// </remarks>
-	protected virtual void OnResizeNeeded ()
+	internal void OnResizeNeeded ()
 	{
-		var actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X;
-		var actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y;
-
-		if (AutoSize) {
-			//if (TextAlignment == TextAlignment.Justified) {
-			//	throw new InvalidOperationException ("TextAlignment.Justified cannot be used with 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 (actX, actY), new Size (w, h)); // Set frame, not Frame!
-		} else {
-			var w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width;
-			var h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height;
-			// BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm...
-			// This is needed for DimAbsolute values by setting the frame before LayoutSubViews.
-			_frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame!
-		}
-		//// BUGBUG: I think these calls are redundant or should be moved into just the AutoSize case
-		if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
+		// TODO: Identify a real-world use-case where this API should be virtual. 
+		// TODO: Until then leave it `internal` and non-virtual
+		// First try SuperView.Bounds, then Application.Top, then Driver.Bounds.
+		// Finally, if none of those are valid, use int.MaxValue (for Unit tests).
+		var relativeBounds = SuperView is { IsInitialized: true }        ? SuperView.Bounds :
+			Application.Top != null && Application.Top.IsInitialized ? Application.Top.Bounds :
+										   Application.Driver?.Bounds ??
+										   new Rect (0, 0, int.MaxValue, int.MaxValue);
+		SetRelativeLayout (relativeBounds);
+
+		// TODO: Determine what, if any of the below is actually needed here.
+		if (IsInitialized) {
 			SetFrameToFitText ();
 			LayoutFrames ();
-			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+			SetTextFormatterSize ();
 			SetNeedsLayout ();
 			SetNeedsDisplay ();
 		}
 
-		if (IsInitialized && SuperView != null && LayoutStyle == LayoutStyle.Computed && (SuperView?.Height is Dim.DimAuto || SuperView?.Width is Dim.DimAuto)) {
-			// DimAuto is in play, force a layout.
-			// BUGBUG: This can cause LayoutSubviews to be called recursively resulting in a deadlock. 
-			//         SetNeedsLayout should be sufficient, but it's not.
-			SuperView.LayoutSubviews ();
-		}
+		//if (IsInitialized && SuperView != null && LayoutStyle == LayoutStyle.Computed && (SuperView?.Height is Dim.DimAuto || SuperView?.Width is Dim.DimAuto)) {
+		//	// DimAuto is in play, force a layout.
+		//	// BUGBUG: This can cause LayoutSubviews to be called recursively resulting in a deadlock. 
+		//	//         SetNeedsLayout should be sufficient, but it's not.
+		//	SuperView.LayoutSubviews ();
+		//}
 	}
 
+	/// <summary>
+	/// Sets the internal <see cref="LayoutNeeded"/> flag for this View and all of it's
+	/// subviews and it's SuperView. The main loop will call SetRelativeLayout and LayoutSubviews
+	/// for any view with <see cref="LayoutNeeded"/> set.
+	/// </summary>
 	internal void SetNeedsLayout ()
 	{
 		if (LayoutNeeded) {
@@ -826,7 +773,6 @@ public partial class View {
 		return ret;
 	}
 
-	// TODO: Come up with a better name for this method. "SetRelativeLayout" lacks clarity and confuses. AdjustSizeAndPosition?
 	/// <summary>
 	/// Applies the view's position (<see cref="X"/>, <see cref="Y"/>) and dimension (<see cref="Width"/>, and
 	/// <see cref="Height"/>) to
@@ -839,6 +785,11 @@ public partial class View {
 	/// </param>
 	internal void SetRelativeLayout (Rect superviewBounds)
 	{
+		Debug.Assert (_x != null);
+		Debug.Assert (_y != null);
+		Debug.Assert (_width != null);
+		Debug.Assert (_height != null);
+
 		int newX, newW, newY, newH;
 		var autosize = Size.Empty;
 
@@ -848,6 +799,10 @@ public partial class View {
 			autosize = GetAutoSize ();
 		}
 
+		// TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs
+		// TODO: to make architecture more clean. Do this after DimAuto is implemented and the 
+		// TODO: View.AutoSize stuff is removed.
+
 		// Returns the new dimension (width or height) and location (x or y) for the View given
 		//   the superview's Bounds
 		//   the current Pos (View.X or View.Y)
@@ -857,19 +812,16 @@ public partial class View {
 		{
 			// Gets the new dimension (width or height, dependent on `width`) of the given Dim given:
 			//   location: the current location (x or y)
-			//   dimension: the current dimension (width or height)
+			//   dimension: the new dimension (width or height) (if relevant for Dim type)
 			//   autosize: the size to use if autosize = true
-			// This mehod is recursive if d is Dim.DimCombine
+			// This method is recursive if d is Dim.DimCombine
 			int GetNewDimension (Dim d, int location, int dimension, int autosize)
 			{
 				int newDimension;
 				switch (d) {
-				case null:
-					// dim == null is the same as dim == Dim.FIll (0)
-					newDimension = AutoSize ? autosize : dimension;
-					break;
 
 				case Dim.DimCombine combine:
+					// TODO: Move combine logic into DimCombine?
 					var leftNewDim = GetNewDimension (combine._left,   location, dimension, autosize);
 					var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
 					if (combine._add) {
@@ -899,6 +851,12 @@ public partial class View {
 					}
 					break;
 
+				case Dim.DimAbsolute:
+					// DimAbsoulte.Anchor (int width) ignores width and returns n
+					newDimension = Math.Max (d.Anchor (0), 0);
+					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+					break;
+
 				case Dim.DimFill:
 				default:
 					newDimension = Math.Max (d.Anchor (dimension - location), 0);
@@ -923,6 +881,7 @@ public partial class View {
 				break;
 
 			case Pos.PosCombine combine:
+				// TODO: Move combine logic into PosCombine?
 				int left, right;
 				(left, newDimension) = GetNewLocationAndDimension (width,  superviewBounds, combine._left,  dim, autosizeDimension);
 				(right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension);
@@ -936,7 +895,6 @@ public partial class View {
 
 			case Pos.PosAnchorEnd:
 			case Pos.PosAbsolute:
-			case null:
 			case Pos.PosFactor:
 			case Pos.PosFunc:
 			case Pos.PosView:
@@ -950,7 +908,6 @@ public partial class View {
 			return (newLocation, newDimension);
 		}
 
-
 		// horizontal/width
 		(newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
 
@@ -959,10 +916,33 @@ public partial class View {
 
 		var r = new Rect (newX, newY, newW, newH);
 		if (Frame != r) {
-			Frame = r;
+			// Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
+			// the view LayoutStyle.Absolute.
+			_frame = r;
+			if (_x is Pos.PosAbsolute) {
+				_x = Frame.X;
+			}
+			if (_y is Pos.PosAbsolute) {
+				_y = Frame.Y;
+			}
+			if (_width is Dim.DimAbsolute) {
+				_width = Frame.Width;
+			}
+			if (_height is Dim.DimAbsolute) {
+				_height = Frame.Height;
+			}
+
+			if (IsInitialized) {
+				// TODO: Figure out what really is needed here. All unit tests (except AutoSize) pass as-is
+				//LayoutFrames ();
+				SetTextFormatterSize ();
+				SetNeedsLayout ();
+				//SetNeedsDisplay ();
+			}
+
 			// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
 			if (!SetFrameToFitText ()) {
-				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+				SetTextFormatterSize ();
 			}
 		}
 	}
@@ -992,8 +972,8 @@ public partial class View {
 	public event EventHandler<LayoutEventArgs> LayoutComplete;
 
 	/// <summary>
-	/// Raises the <see cref="LayoutComplete"/> event. Called from  <see cref="LayoutSubviews"/> before all sub-views have
-	/// been laid out.
+	/// Raises the <see cref="LayoutComplete"/> event. Called from  <see cref="LayoutSubviews"/> before all sub-views have been
+	/// laid out.
 	/// </summary>
 	internal virtual void OnLayoutComplete (LayoutEventArgs args) => LayoutComplete?.Invoke (this, args);
 
@@ -1188,7 +1168,7 @@ public partial class View {
 		var oldBounds = Bounds;
 		OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds });
 
-		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+		SetTextFormatterSize ();
 
 		// Sort out the dependencies of the X, Y, Width, Height properties
 		var nodes = new HashSet<View> ();
@@ -1226,9 +1206,9 @@ public partial class View {
 
 	void LayoutSubview (View v, Rect contentArea)
 	{
-		if (v.LayoutStyle == LayoutStyle.Computed) {
-			v.SetRelativeLayout (contentArea);
-		}
+		//if (v.LayoutStyle == LayoutStyle.Computed) {
+		v.SetRelativeLayout (contentArea);
+		//}
 
 		v.LayoutSubviews ();
 		v.LayoutNeeded = false;
@@ -1279,49 +1259,6 @@ public partial class View {
 		return boundsChanged;
 	}
 
-	/// <summary>
-	/// Gets the Frame dimensions required to fit <see cref="Text"/> within <see cref="Bounds"/> using the text
-	/// <see cref="Direction"/> specified by the
-	/// <see cref="TextFormatter"/> property and accounting for any <see cref="HotKeySpecifier"/> characters.
-	/// </summary>
-	/// <returns>The <see cref="Size"/> of the view required to fit the text.</returns>
-	public Size GetAutoSize ()
-	{
-		var x = 0;
-		var y = 0;
-		if (IsInitialized) {
-			x = Bounds.X;
-			y = Bounds.Y;
-		}
-		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction);
-		var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal;
-		var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical;
-		return new Size (newWidth, newHeight);
-	}
-
-	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 !(ValidatePosDim && (!(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 !(ValidatePosDim && !(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 !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
-	}
 
 	/// <summary>
 	/// Determines if the View's <see cref="Width"/> can be set to a new value.

+ 27 - 26
Terminal.Gui/View/SuperViewChangedEventArgs.cs

@@ -1,33 +1,34 @@
 using System;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Args for events where the <see cref="View.SuperView"/> of a <see cref="View"/> is changed
+/// (e.g. <see cref="View.Removed"/> / <see cref="View.Added"/> events).
+/// </summary>
+public class SuperViewChangedEventArgs : EventArgs {
 	/// <summary>
-	/// Args for events where the <see cref="View.SuperView"/> of a <see cref="View"/> is changed
-	/// (e.g. <see cref="View.Removed"/> / <see cref="View.Added"/> events).
+	/// Creates a new instance of the <see cref="SuperViewChangedEventArgs"/> class.
 	/// </summary>
-	public class SuperViewChangedEventArgs : EventArgs
+	/// <param name="parent"></param>
+	/// <param name="child"></param>
+	public SuperViewChangedEventArgs (View parent, View child)
 	{
-		/// <summary>
-		/// Creates a new instance of the <see cref="SuperViewChangedEventArgs"/> class.
-		/// </summary>
-		/// <param name="parent"></param>
-		/// <param name="child"></param>
-		public SuperViewChangedEventArgs (View parent, View child)
-		{
-			Parent = parent;
-			Child = child;
-		}
+		Parent = parent;
+		Child = child;
+	}
 
-		/// <summary>
-		/// The parent.  For <see cref="View.Removed"/> this is the old
-		/// parent (new parent now being null).  For <see cref="View.Added"/>
-		/// it is the new parent to whom view now belongs.
-		/// </summary>
-		public View Parent { get; }
+	// TODO: Parent is the wrong name. It should be SuperView.
+	/// <summary>
+	/// The parent.  For <see cref="View.Removed"/> this is the old
+	/// parent (new parent now being null).  For <see cref="View.Added"/>
+	/// it is the new parent to whom view now belongs.
+	/// </summary>
+	public View Parent { get; }
 
-		/// <summary>
-		/// The view that is having it's <see cref="View.SuperView"/> changed
-		/// </summary>
-		public View Child { get; }
-	}
-}
+	// TODO: Child is the wrong name. It should be View.
+	/// <summary>
+	/// The view that is having it's <see cref="View.SuperView"/> changed
+	/// </summary>
+	public View Child { get; }
+}

+ 392 - 329
Terminal.Gui/View/View.cs

@@ -1,315 +1,137 @@
 using System;
 using System.ComponentModel;
+using System.Diagnostics;
 
 namespace Terminal.Gui;
 
 #region API Docs
 /// <summary>
-/// View is the base class for all views on the screen and represents a visible element that can render itself and 
+/// View is 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, called SubViews. View provides basic functionality for layout, positioning,
 /// and drawing. In addition, View provides keyboard and mouse event handling.
 /// </summary>
 /// <remarks>
 /// <list type="table">
-///	<listheader>
-///	<term>Term</term><description>Definition</description>
-///	</listheader>
-///	<item>
-///	<term>SubView</term><description>A View that is contained in another view and will be rendered as part of the containing view's ContentArea. 
-/// SubViews are added to another view via the <see cref="View.Add(View)"/>` method. A View may only be a SubView of a single View. </description>
-///	</item>
-///	<item>
-///		<term>SuperView</term><description>The View that is a container for SubViews.</description>
-///	</item>
+///         <listheader>
+///                 <term>Term</term><description>Definition</description>
+///         </listheader>
+///         <item>
+///                 <term>SubView</term>
+///                 <description>
+///                 A View that is contained in another view and will be rendered as part of the containing view's
+///                 ContentArea.
+///                 SubViews are added to another view via the <see cref="View.Add(View)"/>` method. A View may only be a
+///                 SubView of a single View.
+///                 </description>
+///         </item>
+///         <item>
+///                 <term>SuperView</term><description>The View that is a container for SubViews.</description>
+///         </item>
 /// </list>
 /// <para>
 /// Focus is a concept that is used to describe which View is currently receiving user input. Only Views that are
 /// <see cref="Enabled"/>, <see cref="Visible"/>, and <see cref="CanFocus"/> will receive focus.
 /// </para>
 /// <para>
-///    Views that are focusable should implement the <see cref="PositionCursor"/> to make sure that
-///    the cursor is placed in a location that makes sense. Unix terminals do not have
-///    a way of hiding the cursor, so it can be distracting to have the cursor left at
-///    the last focused view. So views should make sure that they place the cursor
-///    in a visually sensible place.
+/// Views that are focusable should implement the <see cref="PositionCursor"/> to make sure that
+/// the cursor is placed in a location that makes sense. Unix terminals do not have
+/// a way of hiding the cursor, so it can be distracting to have the cursor left at
+/// the last focused view. So views should make sure that they place the cursor
+/// in a visually sensible place.
 /// </para>
 /// <para>
-///    The View defines the base functionality for user interface elements in Terminal.Gui. Views
-///    can contain one or more subviews, can respond to user input and render themselves on the screen.
+/// The View defines the base functionality for user interface elements in Terminal.Gui. Views
+/// can contain one or more subviews, can respond to user input and render themselves on the screen.
 /// </para>
 /// <para>
-///    Views supports two layout styles: <see cref="LayoutStyle.Absolute"/> or <see cref="LayoutStyle.Computed"/>. 
-///    The choice as to which layout style is used by the View 
-///    is determined when the View is initialized. To create a View using Absolute layout, call a constructor that takes a
-///    Rect parameter to specify the absolute position and size (the View.<see cref="View.Frame "/>). To create a View 
-///    using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height 
-///    properties on the view. Both approaches use coordinates that are relative to the container they are being added to. 
+/// View supports two layout styles: <see cref="LayoutStyle.Absolute"/> or <see cref="LayoutStyle.Computed"/>.
+/// The style is determined by the values of <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and
+/// <see cref="Height"/>.
+/// If any of these is set to non-absolute <see cref="Pos"/> or <see cref="Dim"/> object,
+/// then the layout style is <see cref="LayoutStyle.Computed"/>. Otherwise it is <see cref="LayoutStyle.Absolute"/>.
 /// </para>
 /// <para>
-///    To switch between Absolute and Computed layout, use the <see cref="LayoutStyle"/> property. 
+/// To create a View using Absolute layout, call a constructor that takes a
+/// Rect parameter to specify the absolute position and size or simply set <see cref="View.Frame "/>). To create a View
+/// using Computed layout use a constructor that does not take a Rect parameter and set the X, Y, Width and Height
+/// properties on the view to non-absolute values. Both approaches use coordinates that are relative to the
+/// <see cref="Bounds"/> of the <see cref="SuperView"/> the View is added to.
 /// </para>
 /// <para>
-///    Computed layout is more flexible and supports dynamic console apps where controls adjust layout
-///    as the terminal resizes or other Views change size or position. The X, Y, Width and Height 
-///    properties are Dim and Pos objects that dynamically update the position of a view.
-///    The X and Y properties are of type <see cref="Pos"/>
-///    and you can use either absolute positions, percentages or anchor
-///    points. The Width and Height properties are of type
-///    <see cref="Dim"/> and can use absolute position,
-///    percentages and anchors. These are useful as they will take
-///    care of repositioning views when view's frames are resized or
-///    if the terminal size changes.
+/// Computed layout is more flexible and supports dynamic console apps where controls adjust layout
+/// as the terminal resizes or other Views change size or position. The
+/// <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and
+/// <see cref="Height"/> properties are <see cref="Dim"/> and <see cref="Pos"/> objects that dynamically update the
+/// position of a view.
+/// The X and Y properties are of type <see cref="Pos"/>
+/// and you can use either absolute positions, percentages, or anchor
+/// points. The Width and Height properties are of type
+/// <see cref="Dim"/> and can use absolute position,
+/// percentages, and anchors. These are useful as they will take
+/// care of repositioning views when view's frames are resized or
+/// if the terminal size changes.
 /// </para>
 /// <para>
-///    Absolute layout requires specifying coordinates and sizes of Views explicitly, and the
-///    View will typically stay in a fixed position and size. To change the position and size use the
-///    <see cref="Frame"/> property.
+/// Absolute layout requires specifying coordinates and sizes of Views explicitly, and the
+/// View will typically stay in a fixed position and size. To change the position and size use the
+/// <see cref="Frame"/> property.
 /// </para>
 /// <para>
-///    Subviews (child views) can be added to a View by calling the <see cref="Add(View)"/> method. 
-///    The container of a View can be accessed with the <see cref="SuperView"/> property.
+/// Subviews (child views) can be added to a View by calling the <see cref="Add(View)"/> method.
+/// The container of a View can be accessed with the <see cref="SuperView"/> property.
 /// </para>
 /// <para>
-///    To flag a region of the View's <see cref="Bounds"/> to be redrawn call <see cref="SetNeedsDisplay(Rect)"/>. 
-///    To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
+/// To flag a region of the View's <see cref="Bounds"/> to be redrawn call <see cref="SetNeedsDisplay(Rect)"/>.
+/// To flag the entire view for redraw call <see cref="SetNeedsDisplay()"/>.
 /// </para>
 /// <para>
-///    The <see cref="LayoutSubviews"/> method is invoked when the size or layout of a view has
-///    changed. The default processing system will keep the size and dimensions
-///    for views that use the <see cref="LayoutStyle.Absolute"/>, and will recompute the
-///    frames for the vies that use <see cref="LayoutStyle.Computed"/>.
+/// The <see cref="LayoutSubviews"/> method is invoked when the size or layout of a view has
+/// changed. The default processing system will keep the size and dimensions
+/// for views that use the <see cref="LayoutStyle.Absolute"/>, and will recompute the
+/// frames for the vies that use <see cref="LayoutStyle.Computed"/>.
 /// </para>
 /// <para>
-///    Views have a <see cref="ColorScheme"/> property that defines the default colors that subviews
-///    should use for rendering. This ensures that the views fit in the context where
-///    they are being used, and allows for themes to be plugged in. For example, the
-///    default colors for windows and Toplevels uses a blue background, while it uses
-///    a white background for dialog boxes and a red background for errors.
+/// Views have a <see cref="ColorScheme"/> property that defines the default colors that subviews
+/// should use for rendering. This ensures that the views fit in the context where
+/// they are being used, and allows for themes to be plugged in. For example, the
+/// default colors for windows and Toplevels uses a blue background, while it uses
+/// a white background for dialog boxes and a red background for errors.
 /// </para>
 /// <para>
-///    Subclasses should not rely on <see cref="ColorScheme"/> being
-///    set at construction time. If a <see cref="ColorScheme"/> is not set on a view, the view will inherit the
-///    value from its <see cref="SuperView"/> and the value might only be valid once a view has been
-///    added to a SuperView. 
+/// Subclasses should not rely on <see cref="ColorScheme"/> being
+/// set at construction time. If a <see cref="ColorScheme"/> is not set on a view, the view will inherit the
+/// value from its <see cref="SuperView"/> and the value might only be valid once a view has been
+/// added to a SuperView.
 /// </para>
 /// <para>
-///    By using  <see cref="ColorScheme"/> applications will work both
-///    in color as well as black and white displays.
+/// By using  <see cref="ColorScheme"/> applications will work both
+/// in color as well as black and white displays.
 /// </para>
 /// <para>
-///     Views can also opt-in to more sophisticated initialization
-///     by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
-///     <see cref="ISupportInitialize.EndInit"/> which will be called
-///     when the view is added to a <see cref="SuperView"/>. 
+/// Views can also opt-in to more sophisticated initialization
+/// by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
+/// <see cref="ISupportInitialize.EndInit"/> which will be called
+/// when the view is added to a <see cref="SuperView"/>.
 /// </para>
 /// <para>
-///     If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
-///     can be implemented, in which case the <see cref="ISupportInitialize"/>
-///     methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
-///     is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
-///     to override base class layout code optimally by doing so only on first run,
-///     instead of on every run.
-///   </para>
+/// If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
+/// can be implemented, in which case the <see cref="ISupportInitialize"/>
+/// methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
+/// is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
+/// to override base class layout code optimally by doing so only on first run,
+/// instead of on every run.
+/// </para>
 /// <para>
-///	See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see>
-/// </para>	/// </remarks>
+/// See <see href="../docs/keyboard.md">for an overview of View keyboard handling.</see>
+/// </para>
+/// ///
+/// </remarks>
 #endregion API Docs
 
 public partial class View : Responder, ISupportInitializeNotification {
-	#region Constructors and Initialization
-	/// <summary>
-	/// Initializes a new instance of a <see cref="Terminal.Gui.LayoutStyle.Absolute"/> <see cref="View"/> class with the absolute
-	/// dimensions specified in the <paramref name="frame"/> parameter. 
-	/// </summary>
-	/// <param name="frame">The region covered by this view.</param>
-	/// <remarks>
-	/// This constructor initialize a View with a <see cref="LayoutStyle"/> of <see cref="Terminal.Gui.LayoutStyle.Absolute"/>.
-	/// Use <see cref="View"/> to initialize a View with  <see cref="LayoutStyle"/> of <see cref="Terminal.Gui.LayoutStyle.Computed"/> 
-	/// </remarks>
-	public View (Rect frame) : this (frame, null) { }
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Computed"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically control the size and location of the view.
-	///   The <see cref="View"/> will be created using <see cref="Terminal.Gui.LayoutStyle.Computed"/>
-	///   coordinates. The initial size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   If <see cref="Height"/> is greater than one, word wrapping is provided.
-	/// </para>
-	/// <para>
-	///   This constructor initialize a View with a <see cref="LayoutStyle"/> of <see cref="Terminal.Gui.LayoutStyle.Computed"/>. 
-	///   Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically control the size and location of the view.
-	/// </para>
-	/// </remarks>
-	public View () : this (string.Empty, TextDirection.LeftRight_TopBottom) { }
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The <see cref="View"/> will be created at the given
-	///   coordinates with the given string. The size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   No line wrapping is provided.
-	/// </para>
-	/// </remarks>
-	/// <param name="x">column to locate the View.</param>
-	/// <param name="y">row to locate the View.</param>
-	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-	public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { }
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The <see cref="View"/> will be created at the given
-	///   coordinates with the given string. The initial size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   If <c>rect.Height</c> is greater than one, word wrapping is provided.
-	/// </para>
-	/// </remarks>
-	/// <param name="rect">Location.</param>
-	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-	public View (Rect rect, string text) => SetInitialProperties (text, rect, LayoutStyle.Absolute, TextDirection.LeftRight_TopBottom);
-
-	/// <summary>
-	///   Initializes a new instance of <see cref="View"/> using <see cref="Terminal.Gui.LayoutStyle.Computed"/> layout.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The <see cref="View"/> will be created using <see cref="Terminal.Gui.LayoutStyle.Computed"/>
-	///   coordinates with the given string. The initial size (<see cref="View.Frame"/>) will be 
-	///   adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines. 
-	/// </para>
-	/// <para>
-	///   If <see cref="Height"/> is greater than one, word wrapping is provided.
-	/// </para>
-	/// </remarks>
-	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
-	/// <param name="direction">The text direction.</param>
-	public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) => SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction);
-
-	// TODO: v2 - Remove constructors with parameters
-	/// <summary>
-	/// Private helper to set the initial properties of the View that were provided via constructors.
-	/// </summary>
-	/// <param name="text"></param>
-	/// <param name="rect"></param>
-	/// <param name="layoutStyle"></param>
-	/// <param name="direction"></param>
-	void SetInitialProperties (string text, Rect rect, LayoutStyle layoutStyle = LayoutStyle.Computed,
-				TextDirection direction = TextDirection.LeftRight_TopBottom)
-	{
-		TextFormatter = new TextFormatter ();
-		TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
-		TextDirection = direction;
-
-		CanFocus = false;
-		TabIndex = -1;
-		TabStop = false;
-		LayoutStyle = layoutStyle;
-
-		Text = text == null ? string.Empty : text;
-		LayoutStyle = layoutStyle;
-		Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
-		OnResizeNeeded ();
-
-		AddCommands ();
-
-		CreateFrames ();
-	}
-
-	/// <summary>
-	/// Get or sets if  the <see cref="View"/> has been initialized (via <see cref="ISupportInitialize.BeginInit"/> 
-	/// and <see cref="ISupportInitialize.EndInit"/>).
-	/// </summary>
-	/// <para>
-	///     If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification.IsInitialized"/>
-	///     can be implemented, in which case the <see cref="ISupportInitialize"/>
-	///     methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
-	///     is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
-	///     to override base class layout code optimally by doing so only on first run,
-	///     instead of on every run.
-	///   </para>
-	public virtual bool IsInitialized { get; set; }
-
-	/// <summary>
-	///  Signals the View that initialization is starting. See <see cref="ISupportInitialize"/>.
-	/// </summary>
-	/// <remarks>
-	/// <para>
-	///     Views can opt-in to more sophisticated initialization
-	///     by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
-	///     <see cref="ISupportInitialize.EndInit"/> which will be called
-	///     when the view is added to a <see cref="SuperView"/>. 
-	/// </para>
-	/// <para>
-	///     If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
-	///     can be implemented too, in which case the <see cref="ISupportInitialize"/>
-	///     methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
-	///     is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
-	///     to override base class layout code optimally by doing so only on first run,
-	///     instead of on every run.
-	///   </para>
-	/// </remarks>
-	public virtual void BeginInit ()
-	{
-		if (!IsInitialized) {
-			_oldCanFocus = CanFocus;
-			_oldTabIndex = _tabIndex;
-
-
-			// TODO: Figure out why ScrollView and other tests fail if this call is put here 
-			// instead of the constructor.
-			//InitializeFrames ();
-
-		} else {
-			//throw new InvalidOperationException ("The view is already initialized.");
-
-		}
-
-		if (_subviews?.Count > 0) {
-			foreach (var view in _subviews) {
-				if (!view.IsInitialized) {
-					view.BeginInit ();
-				}
-			}
-		}
-	}
-
-	/// <summary>
-	///  Signals the View that initialization is ending. See <see cref="ISupportInitialize"/>.
-	/// </summary>
-	public void EndInit ()
-	{
-		IsInitialized = true;
-		// These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called.
-		UpdateTextDirection (TextDirection);
-		UpdateTextFormatterText ();
-		SetHotKey ();
+	bool _oldEnabled;
 
-		OnResizeNeeded ();
-		if (_subviews != null) {
-			foreach (var view in _subviews) {
-				if (!view.IsInitialized) {
-					view.EndInit ();
-				}
-			}
-		}
-		Initialized?.Invoke (this, EventArgs.Empty);
-	}
-	#endregion Constructors and Initialization
+	string _title = string.Empty;
 
 	/// <summary>
 	/// Points to the current driver in use by the view, it is a convenience property
@@ -330,10 +152,9 @@ public partial class View : Responder, ISupportInitializeNotification {
 	/// <remarks>The id should be unique across all Views that share a SuperView.</remarks>
 	public string Id { get; set; } = "";
 
-	string _title = string.Empty;
-
 	/// <summary>
-	/// The title to be displayed for this <see cref="View"/>. The title will be displayed if <see cref="Border"/>.<see cref="Thickness.Top"/>
+	/// The title to be displayed for this <see cref="View"/>. The title will be displayed if <see cref="Border"/>.
+	/// <see cref="Thickness.Top"/>
 	/// is greater than 0.
 	/// </summary>
 	/// <value>The title.</value>
@@ -341,12 +162,12 @@ public partial class View : Responder, ISupportInitializeNotification {
 		get => _title;
 		set {
 			if (!OnTitleChanging (_title, value)) {
-				string old = _title;
+				var old = _title;
 				_title = value;
 				SetNeedsDisplay ();
 #if DEBUG
 				if (_title != null && string.IsNullOrEmpty (Id)) {
-					Id = _title.ToString ();
+					Id = _title;
 				}
 #endif // DEBUG
 				OnTitleChanged (old, _title);
@@ -354,51 +175,6 @@ public partial class View : Responder, ISupportInitializeNotification {
 		}
 	}
 
-	/// <summary>
-	/// Called before the <see cref="View.Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be cancelled.
-	/// </summary>
-	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
-	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
-	/// <returns>`true` if an event handler canceled the Title change.</returns>
-	public virtual bool OnTitleChanging (string oldTitle, string newTitle)
-	{
-		var args = new TitleEventArgs (oldTitle, newTitle);
-		TitleChanging?.Invoke (this, args);
-		return args.Cancel;
-	}
-
-	/// <summary>
-	/// Event fired when the <see cref="View.Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to 
-	/// `true` to cancel the Title change.
-	/// </summary>
-	public event EventHandler<TitleEventArgs> TitleChanging;
-
-	/// <summary>
-	/// Called when the <see cref="View.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
-	/// </summary>
-	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
-	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
-	public virtual void OnTitleChanged (string oldTitle, string newTitle)
-	{
-		var args = new TitleEventArgs (oldTitle, newTitle);
-		TitleChanged?.Invoke (this, args);
-	}
-
-	/// <summary>
-	/// Event fired after the <see cref="View.Title"/> has been changed. 
-	/// </summary>
-	public event EventHandler<TitleEventArgs> TitleChanged;
-
-	/// <summary>
-	/// Event fired when the <see cref="Enabled"/> value is being changed.
-	/// </summary>
-	public event EventHandler EnabledChanged;
-
-	/// <inheritdoc/>
-	public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty);
-
-	bool _oldEnabled;
-
 	/// <inheritdoc/>
 	public override bool Enabled {
 		get => base.Enabled;
@@ -432,20 +208,13 @@ public partial class View : Responder, ISupportInitializeNotification {
 		}
 	}
 
-	/// <summary>
-	/// Event fired when the <see cref="Visible"/> value is being changed.
-	/// </summary>
-	public event EventHandler VisibleChanged;
-
-	/// <inheritdoc/>
-	public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty);
-
 	/// <summary>
 	/// Gets or sets whether a view is cleared if the <see cref="Visible"/> property is <see langword="false"/>.
 	/// </summary>
 	public bool ClearOnVisibleFalse { get; set; } = true;
 
-	/// <inheritdoc/>>
+	/// <inheritdoc/>
+	/// >
 	public override bool Visible {
 		get => base.Visible;
 		set {
@@ -465,6 +234,58 @@ public partial class View : Responder, ISupportInitializeNotification {
 		}
 	}
 
+	/// <summary>
+	/// Called before the <see cref="View.Title"/> changes. Invokes the <see cref="TitleChanging"/> event, which can be
+	/// cancelled.
+	/// </summary>
+	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
+	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
+	/// <returns>`true` if an event handler canceled the Title change.</returns>
+	public virtual bool OnTitleChanging (string oldTitle, string newTitle)
+	{
+		var args = new TitleEventArgs (oldTitle, newTitle);
+		TitleChanging?.Invoke (this, args);
+		return args.Cancel;
+	}
+
+	/// <summary>
+	/// Event fired when the <see cref="View.Title"/> is changing. Set <see cref="TitleEventArgs.Cancel"/> to
+	/// `true` to cancel the Title change.
+	/// </summary>
+	public event EventHandler<TitleEventArgs> TitleChanging;
+
+	/// <summary>
+	/// Called when the <see cref="View.Title"/> has been changed. Invokes the <see cref="TitleChanged"/> event.
+	/// </summary>
+	/// <param name="oldTitle">The <see cref="View.Title"/> that is/has been replaced.</param>
+	/// <param name="newTitle">The new <see cref="View.Title"/> to be replaced.</param>
+	public virtual void OnTitleChanged (string oldTitle, string newTitle)
+	{
+		var args = new TitleEventArgs (oldTitle, newTitle);
+		TitleChanged?.Invoke (this, args);
+	}
+
+	/// <summary>
+	/// Event fired after the <see cref="View.Title"/> has been changed.
+	/// </summary>
+	public event EventHandler<TitleEventArgs> TitleChanged;
+
+	/// <summary>
+	/// Event fired when the <see cref="Enabled"/> value is being changed.
+	/// </summary>
+	public event EventHandler EnabledChanged;
+
+	/// <inheritdoc/>
+	public override void OnEnabledChanged () => EnabledChanged?.Invoke (this, EventArgs.Empty);
+
+	/// <summary>
+	/// Event fired when the <see cref="Visible"/> value is being changed.
+	/// </summary>
+	public event EventHandler VisibleChanged;
+
+	/// <inheritdoc/>
+	public override void OnVisibleChanged () => VisibleChanged?.Invoke (this, EventArgs.Empty);
+
 	bool CanBeVisible (View view)
 	{
 		if (!view.Visible) {
@@ -497,18 +318,260 @@ public partial class View : Responder, ISupportInitializeNotification {
 		Padding?.Dispose ();
 		Padding = null;
 
-		_height = null;
-		_width = null;
-		_x = null;
-		_y = null;
-
-		for (int i = InternalSubviews.Count - 1; i >= 0; i--) {
+		for (var i = InternalSubviews.Count - 1; i >= 0; i--) {
 			var subview = InternalSubviews [i];
 			Remove (subview);
 			subview.Dispose ();
 		}
 
 		base.Dispose (disposing);
-		System.Diagnostics.Debug.Assert (InternalSubviews.Count == 0);
+		Debug.Assert (InternalSubviews.Count == 0);
 	}
+
+	#region Constructors and Initialization
+	/// <summary>
+	/// Initializes a new instance of a <see cref="View"/> class with the absolute
+	/// dimensions specified in the <paramref name="frame"/> parameter.
+	/// </summary>
+	/// <param name="frame">The region covered by this view.</param>
+	/// <remarks>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         The <see cref="View"/> will be created using <see cref="LayoutStyle.Absolute"/>
+	///         coordinates. The initial size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	public View (Rect frame) : this (frame, null) { }
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="View"/>.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         The <see cref="View"/> will be created using <see cref="LayoutStyle.Absolute"/>
+	///         coordinates. The initial size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	public View () : this (string.Empty) { }
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="View"/> in at the position specified with the
+	/// dimensions specified in the <paramref name="x"/> and <paramref name="y"/> parameters.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         The <see cref="View"/> will be created at the given
+	///         coordinates with the given string. The size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	/// <param name="x">column to locate the View.</param>
+	/// <param name="y">row to locate the View.</param>
+	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+	public View (int x, int y, string text) : this (TextFormatter.CalcRect (x, y, text), text) { }
+
+	/// <summary>
+	/// Initializes a new instance of a <see cref="View"/> class with the absolute
+	/// dimensions specified in the <paramref name="frame"/> parameter.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         The <see cref="View"/> will be created at the given
+	///         coordinates with the given string. The size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	/// <param name="rect">Location.</param>
+	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+	public View (Rect rect, string text) => SetInitialProperties (text, rect, LayoutStyle.Absolute);
+
+
+	/// <summary>
+	/// Initializes a new instance of a <see cref="View"/> class with using the given text and text styling information.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         The <see cref="View"/> will be created at the given
+	///         coordinates with the given string. The size (<see cref="View.Frame"/>) will be
+	///         adjusted to fit the contents of <see cref="Text"/>, including newlines ('\n') for multiple lines.
+	///         </para>
+	///         <para>
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view.
+	///         </para>
+	///         <para>
+	///         If <see cref="Height"/> is greater than one, word wrapping is provided.
+	///         </para>
+	///         <para>
+	///         This constructor initialize a View with a <see cref="LayoutStyle"/> of
+	///         <see cref="LayoutStyle.Absolute"/>.
+	///         Use <see cref="X"/>, <see cref="Y"/>, <see cref="Width"/>, and <see cref="Height"/> properties to dynamically
+	///         control the size and location of the view, changing it to  <see cref="LayoutStyle.Computed"/>.
+	///         </para>
+	/// </remarks>
+	/// <param name="text">text to initialize the <see cref="Text"/> property with.</param>
+	/// <param name="direction">The text direction.</param>
+	public View (string text, TextDirection direction = TextDirection.LeftRight_TopBottom) => SetInitialProperties (text, Rect.Empty, LayoutStyle.Computed, direction);
+
+	// TODO: v2 - Remove constructors with parameters
+
+
+	/// <summary>
+	/// Private helper to set the initial properties of the View that were provided via constructors.
+	/// </summary>
+	/// <param name="text"></param>
+	/// <param name="rect"></param>
+	/// <param name="layoutStyle"></param>
+	/// <param name="direction"></param>
+	void SetInitialProperties (string text,
+				   Rect rect,
+				   LayoutStyle layoutStyle = LayoutStyle.Computed,
+				   TextDirection direction = TextDirection.LeftRight_TopBottom)
+	{
+		TextFormatter = new TextFormatter ();
+		TextFormatter.HotKeyChanged += TextFormatter_HotKeyChanged;
+		TextDirection = direction;
+
+		CanFocus = false;
+		TabIndex = -1;
+		TabStop = false;
+
+		Text = text == null ? string.Empty : text;
+		Frame = rect.IsEmpty ? TextFormatter.CalcRect (0, 0, text, direction) : rect;
+
+		AddCommands ();
+
+		CreateFrames ();
+	}
+
+	/// <summary>
+	/// Get or sets if  the <see cref="View"/> has been initialized (via <see cref="ISupportInitialize.BeginInit"/>
+	/// and <see cref="ISupportInitialize.EndInit"/>).
+	/// </summary>
+	/// <para>
+	/// If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification.IsInitialized"/>
+	/// can be implemented, in which case the <see cref="ISupportInitialize"/>
+	/// methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
+	/// is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
+	/// to override base class layout code optimally by doing so only on first run,
+	/// instead of on every run.
+	/// </para>
+	public virtual bool IsInitialized { get; set; }
+
+	/// <summary>
+	/// Signals the View that initialization is starting. See <see cref="ISupportInitialize"/>.
+	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         Views can opt-in to more sophisticated initialization
+	///         by implementing overrides to <see cref="ISupportInitialize.BeginInit"/> and
+	///         <see cref="ISupportInitialize.EndInit"/> which will be called
+	///         when the view is added to a <see cref="SuperView"/>.
+	///         </para>
+	///         <para>
+	///         If first-run-only initialization is preferred, overrides to <see cref="ISupportInitializeNotification"/>
+	///         can be implemented too, in which case the <see cref="ISupportInitialize"/>
+	///         methods will only be called if <see cref="ISupportInitializeNotification.IsInitialized"/>
+	///         is <see langword="false"/>. This allows proper <see cref="View"/> inheritance hierarchies
+	///         to override base class layout code optimally by doing so only on first run,
+	///         instead of on every run.
+	///         </para>
+	/// </remarks>
+	public virtual void BeginInit ()
+	{
+		if (!IsInitialized) {
+			_oldCanFocus = CanFocus;
+			_oldTabIndex = _tabIndex;
+
+
+			// TODO: Figure out why ScrollView and other tests fail if this call is put here 
+			// instead of the constructor.
+			//InitializeFrames ();
+
+		}
+
+		//throw new InvalidOperationException ("The view is already initialized.");
+		if (_subviews?.Count > 0) {
+			foreach (var view in _subviews) {
+				if (!view.IsInitialized) {
+					view.BeginInit ();
+				}
+			}
+		}
+	}
+
+	/// <summary>
+	/// Signals the View that initialization is ending. See <see cref="ISupportInitialize"/>.
+	/// </summary>
+	public void EndInit ()
+	{
+		IsInitialized = true;
+		// These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called.
+		UpdateTextDirection (TextDirection);
+		UpdateTextFormatterText ();
+		SetHotKey ();
+
+		OnResizeNeeded ();
+		if (_subviews != null) {
+			foreach (var view in _subviews) {
+				if (!view.IsInitialized) {
+					view.EndInit ();
+				}
+			}
+		}
+		Initialized?.Invoke (this, EventArgs.Empty);
+	}
+	#endregion Constructors and Initialization
 }

+ 134 - 110
Terminal.Gui/View/ViewDrawing.cs

@@ -1,5 +1,4 @@
 using System;
-using System.Collections.Generic;
 using System.Linq;
 using System.Text;
 
@@ -8,9 +7,12 @@ namespace Terminal.Gui;
 public partial class View {
 	ColorScheme _colorScheme;
 
+	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
+	internal Rect _needsDisplayRect = Rect.Empty;
+
 	/// <summary>
 	/// The color scheme for this view, if it is not defined, it returns the <see cref="SuperView"/>'s
-	/// color scheme. 
+	/// color scheme.
 	/// </summary>
 	public virtual ColorScheme ColorScheme {
 		get {
@@ -27,12 +29,47 @@ public partial class View {
 		}
 	}
 
+	/// <summary>
+	/// Gets or sets whether the view needs to be redrawn.
+	/// </summary>
+	public bool NeedsDisplay {
+		get => _needsDisplayRect != Rect.Empty;
+		set {
+			if (value) {
+				SetNeedsDisplay ();
+			} else {
+				ClearNeedsDisplay ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// Gets whether any Subviews need to be redrawn.
+	/// </summary>
+	public bool SubViewNeedsDisplay { get; private set; }
+
+	/// <summary>
+	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
+	/// </summary>
+	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
+	public LineCanvas LineCanvas { get; } = new ();
+
+	/// <summary>
+	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
+	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
+	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
+	/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
+	/// </summary>
+	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
+
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.Normal"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetNormalColor ()
 	{
 		var cs = ColorScheme;
@@ -45,17 +82,21 @@ public partial class View {
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.Focus"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetFocusColor () => Enabled ? ColorScheme.Focus : ColorScheme.Disabled;
 
 	/// <summary>
 	/// Determines the current <see cref="ColorScheme"/> based on the <see cref="Enabled"/> value.
 	/// </summary>
-	/// <returns><see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
+	/// <returns>
+	/// <see cref="Terminal.Gui.ColorScheme.HotNormal"/> if <see cref="Enabled"/> is <see langword="true"/>
 	/// or <see cref="Terminal.Gui.ColorScheme.Disabled"/> if <see cref="Enabled"/> is <see langword="false"/>.
-	/// If it's overridden can return other values.</returns>
+	/// If it's overridden can return other values.
+	/// </returns>
 	public virtual Attribute GetHotNormalColor () => Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled;
 
 	/// <summary>
@@ -81,25 +122,8 @@ public partial class View {
 	/// </summary>
 	protected void ClearNeedsDisplay ()
 	{
-		_needsDisplayRect = Rect.Empty;
-		_subViewNeedsDisplay = false;
-	}
-
-	// The view-relative region that needs to be redrawn. Marked internal for unit tests.
-	internal Rect _needsDisplayRect = Rect.Empty;
-
-	/// <summary>
-	/// Gets or sets whether the view needs to be redrawn.
-	/// </summary>
-	public bool NeedsDisplay {
-		get => _needsDisplayRect != Rect.Empty;
-		set {
-			if (value) {
-				SetNeedsDisplay ();
-			} else {
-				ClearNeedsDisplay ();
-			}
-		}
+		_needsDisplayRect   = Rect.Empty;
+		SubViewNeedsDisplay = false;
 	}
 
 	/// <summary>
@@ -133,18 +157,18 @@ public partial class View {
 		if (_needsDisplayRect.IsEmpty) {
 			_needsDisplayRect = region;
 		} else {
-			int x = Math.Min (_needsDisplayRect.X, region.X);
-			int y = Math.Min (_needsDisplayRect.Y, region.Y);
-			int w = Math.Max (_needsDisplayRect.Width, region.Width);
-			int h = Math.Max (_needsDisplayRect.Height, region.Height);
+			var x = Math.Min (_needsDisplayRect.X, region.X);
+			var y = Math.Min (_needsDisplayRect.Y, region.Y);
+			var w = Math.Max (_needsDisplayRect.Width,  region.Width);
+			var h = Math.Max (_needsDisplayRect.Height, region.Height);
 			_needsDisplayRect = new Rect (x, y, w, h);
 		}
 		_superView?.SetSubViewNeedsDisplay ();
 
 		if (_needsDisplayRect.X < Bounds.X ||
-		_needsDisplayRect.Y < Bounds.Y ||
-		_needsDisplayRect.Width > Bounds.Width ||
-		_needsDisplayRect.Height > Bounds.Height) {
+		    _needsDisplayRect.Y < Bounds.Y ||
+		    _needsDisplayRect.Width > Bounds.Width ||
+		    _needsDisplayRect.Height > Bounds.Height) {
 			Margin?.SetNeedsDisplay (Margin.Bounds);
 			Border?.SetNeedsDisplay (Border.Bounds);
 			Padding?.SetNeedsDisplay (Padding.Bounds);
@@ -164,31 +188,24 @@ public partial class View {
 		}
 	}
 
-	/// <summary>
-	/// Gets whether any Subviews need to be redrawn.
-	/// </summary>
-	public bool SubViewNeedsDisplay => _subViewNeedsDisplay;
-
-	bool _subViewNeedsDisplay;
-
 	/// <summary>
 	/// Indicates that any Subviews (in the <see cref="Subviews"/> list) need to be repainted.
 	/// </summary>
 	public void SetSubViewNeedsDisplay ()
 	{
-		_subViewNeedsDisplay = true;
-		if (_superView != null && !_superView._subViewNeedsDisplay) {
+		SubViewNeedsDisplay = true;
+		if (_superView != null && !_superView.SubViewNeedsDisplay) {
 			_superView.SetSubViewNeedsDisplay ();
 		}
 	}
 
 	/// <summary>
-	///   Clears the <see cref="Bounds"/> with the normal background color.
+	/// Clears the <see cref="Bounds"/> with the normal background color.
 	/// </summary>
 	/// <remarks>
-	///   <para>
-	///     This clears the Bounds used by this view.
-	///   </para>
+	///         <para>
+	///         This clears the Bounds used by this view.
+	///         </para>
 	/// </remarks>
 	public void Clear ()
 	{
@@ -202,7 +219,7 @@ public partial class View {
 	// "View APIs only deal with View-relative coords". This is only used by ComboBox which can
 	// be refactored to use the View-relative version.
 	/// <summary>
-	///   Clears the specified screen-relative rectangle with the normal background. 
+	/// Clears the specified screen-relative rectangle with the normal background.
 	/// </summary>
 	/// <remarks>
 	/// </remarks>
@@ -220,10 +237,10 @@ public partial class View {
 	// Clips a rectangle in screen coordinates to the dimensions currently available on the screen
 	internal Rect ScreenClip (Rect regionScreen)
 	{
-		int x = regionScreen.X < 0 ? 0 : regionScreen.X;
-		int y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
-		int w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
-		int h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
+		var x = regionScreen.X < 0 ? 0 : regionScreen.X;
+		var y = regionScreen.Y < 0 ? 0 : regionScreen.Y;
+		var w = regionScreen.X + regionScreen.Width >= Driver.Cols ? Driver.Cols - regionScreen.X : regionScreen.Width;
+		var h = regionScreen.Y + regionScreen.Height >= Driver.Rows ? Driver.Rows - regionScreen.Y : regionScreen.Height;
 
 		return new Rect (x, y, w, h);
 	}
@@ -231,11 +248,15 @@ public partial class View {
 	/// <summary>
 	/// Expands the <see cref="ConsoleDriver"/>'s clip region to include <see cref="Bounds"/>.
 	/// </summary>
-	/// <returns>The current screen-relative clip region, which can be then re-applied by setting <see cref="ConsoleDriver.Clip"/>.</returns>
+	/// <returns>
+	/// The current screen-relative clip region, which can be then re-applied by setting
+	/// <see cref="ConsoleDriver.Clip"/>.
+	/// </returns>
 	/// <remarks>
-	/// <para>
-	/// If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to <see cref="Rect.Empty"/>.
-	/// </para>
+	///         <para>
+	///         If <see cref="ConsoleDriver.Clip"/> and <see cref="Bounds"/> do not intersect, the clip region will be set to
+	///         <see cref="Rect.Empty"/>.
+	///         </para>
 	/// </remarks>
 	public Rect ClipToBounds ()
 	{
@@ -251,14 +272,17 @@ public partial class View {
 	/// <param name="hotColor">Hot color.</param>
 	/// <param name="normalColor">Normal color.</param>
 	/// <remarks>
-	/// <para>The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default.</para>
-	/// <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
+	///         <para>
+	///         The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by
+	///         default.
+	///         </para>
+	///         <para>The hotkey specifier can be changed via <see cref="HotKeySpecifier"/></para>
 	/// </remarks>
 	public void DrawHotString (string text, Attribute hotColor, Attribute normalColor)
 	{
 		var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier;
 		Application.Driver.SetAttribute (normalColor);
-		foreach (char rune in text) {
+		foreach (var rune in text) {
 			if (rune == hotkeySpec.Value) {
 				Application.Driver.SetAttribute (hotColor);
 				continue;
@@ -272,7 +296,10 @@ public partial class View {
 	/// Utility function to draw strings that contains a hotkey using a <see cref="ColorScheme"/> and the "focused" state.
 	/// </summary>
 	/// <param name="text">String to display, the underscore before a letter flags the next letter as the hotkey.</param>
-	/// <param name="focused">If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise the regular ones.</param>
+	/// <param name="focused">
+	/// If set to <see langword="true"/> this uses the focused colors from the color scheme, otherwise
+	/// the regular ones.
+	/// </param>
 	/// <param name="scheme">The color scheme to use.</param>
 	public void DrawHotString (string text, bool focused, ColorScheme scheme)
 	{
@@ -295,28 +322,15 @@ public partial class View {
 			return;
 		}
 
-		BoundsToScreen (col, row, out int rCol, out int rRow, false);
+		BoundsToScreen (col, row, out var rCol, out var rRow, false);
 		Driver?.Move (rCol, rRow);
 	}
 
-	/// <summary>
-	/// The canvas that any line drawing that is to be shared by subviews of this view should add lines to.
-	/// </summary>
-	/// <remarks><see cref="Border"/> adds border lines to this LineCanvas.</remarks>
-	public LineCanvas LineCanvas { get; } = new ();
-
-	/// <summary>
-	/// Gets or sets whether this View will use it's SuperView's <see cref="LineCanvas"/> for
-	/// rendering any border lines. If <see langword="true"/> the rendering of any borders drawn
-	/// by this Frame will be done by it's parent's SuperView. If <see langword="false"/> (the default)
-	/// this View's <see cref="OnDrawFrames()"/> method will be called to render the borders.
-	/// </summary>
-	public virtual bool SuperViewRendersLineCanvas { get; set; } = false;
-
 	// TODO: Make this cancelable
 	/// <summary>
-	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// Prepares <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+	/// <see cref="LineCanvas"/> of
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this
 	/// method will cause the <see cref="LineCanvas"/> be prepared to be rendered.
 	/// </summary>
 	/// <returns></returns>
@@ -336,21 +350,22 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Draws the view. Causes the following virtual methods to be called (along with their related events): 
+	/// Draws the view. Causes the following virtual methods to be called (along with their related events):
 	/// <see cref="OnDrawContent"/>, <see cref="OnDrawContentComplete"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	///    Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT <see cref="Frame"/> (superview-relative).
-	/// </para>
-	/// <para>
-	///    Views should set the color that they want to use on entry, as otherwise this will inherit
-	///    the last color that was set globally on the driver.
-	/// </para>
-	/// <para>
-	///    Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
-	///    larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
-	/// </para>
+	///         <para>
+	///         Always use <see cref="Bounds"/> (view-relative) when calling <see cref="OnDrawContent(Rect)"/>, NOT
+	///         <see cref="Frame"/> (superview-relative).
+	///         </para>
+	///         <para>
+	///         Views should set the color that they want to use on entry, as otherwise this will inherit
+	///         the last color that was set globally on the driver.
+	///         </para>
+	///         <para>
+	///         Overrides of <see cref="OnDrawContent(Rect)"/> must ensure they do not set <c>Driver.Clip</c> to a clip region
+	///         larger than the <ref name="Bounds"/> property, as this will cause the driver to clip the entire region.
+	///         </para>
 	/// </remarks>
 	public void Draw ()
 	{
@@ -387,8 +402,9 @@ public partial class View {
 
 	// TODO: Make this cancelable
 	/// <summary>
-	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the <see cref="LineCanvas"/> of 
-	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this 
+	/// Renders <see cref="View.LineCanvas"/>. If <see cref="SuperViewRendersLineCanvas"/> is true, only the
+	/// <see cref="LineCanvas"/> of
+	/// this view's subviews will be rendered. If <see cref="SuperViewRendersLineCanvas"/> is false (the default), this
 	/// method will cause the <see cref="LineCanvas"/> to be rendered.
 	/// </summary>
 	/// <returns></returns>
@@ -411,7 +427,7 @@ public partial class View {
 		}
 
 		if (Subviews.Any (s => s.SuperViewRendersLineCanvas)) {
-			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas == true)) {
+			foreach (var subview in Subviews.Where (s => s.SuperViewRendersLineCanvas)) {
 				// Combine the LineCanvas'
 				LineCanvas.Merge (subview.LineCanvas);
 				subview.LineCanvas.Clear ();
@@ -434,21 +450,25 @@ public partial class View {
 	/// Event invoked when the content area of the View is to be drawn.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
-	/// </para>
-	/// <para>
-	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-	/// </para>
+	///         <para>
+	///         Will be invoked before any subviews added with <see cref="Add(View)"/> have been drawn.
+	///         </para>
+	///         <para>
+	///         Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>
+	///         .
+	///         </para>
 	/// </remarks>
 	public event EventHandler<DrawEventArgs> DrawContent;
 
 	/// <summary>
-	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. 
+	/// Enables overrides to draw infinitely scrolled content and/or a background behind added controls.
 	/// </summary>
-	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <param name="contentArea">
+	/// The view-relative rectangle describing the currently visible viewport into the
+	/// <see cref="View"/>
+	/// </param>
 	/// <remarks>
-	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn. 
+	/// This method will be called before any subviews added with <see cref="Add(View)"/> have been drawn.
 	/// </remarks>
 	public virtual void OnDrawContent (Rect contentArea)
 	{
@@ -475,8 +495,8 @@ public partial class View {
 			var subviewsNeedingDraw = _subviews.Where (
 				view => view.Visible &&
 					(view.NeedsDisplay ||
-					view.SubViewNeedsDisplay ||
-					view.LayoutNeeded)
+					 view.SubViewNeedsDisplay ||
+					 view.LayoutNeeded)
 			);
 
 			foreach (var view in subviewsNeedingDraw) {
@@ -499,19 +519,23 @@ public partial class View {
 	/// Event invoked when the content area of the View is completed drawing.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	/// Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
-	/// </para>
-	/// <para>
-	/// Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>.
-	/// </para>
+	///         <para>
+	///         Will be invoked after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
+	///         </para>
+	///         <para>
+	///         Rect provides the view-relative rectangle describing the currently visible viewport into the <see cref="View"/>
+	///         .
+	///         </para>
 	/// </remarks>
 	public event EventHandler<DrawEventArgs> DrawContentComplete;
 
 	/// <summary>
 	/// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed controls.
 	/// </summary>
-	/// <param name="contentArea">The view-relative rectangle describing the currently visible viewport into the <see cref="View"/></param>
+	/// <param name="contentArea">
+	/// The view-relative rectangle describing the currently visible viewport into the
+	/// <see cref="View"/>
+	/// </param>
 	/// <remarks>
 	/// This method will be called after any subviews removed with <see cref="Remove(View)"/> have been completed drawing.
 	/// </remarks>

+ 608 - 612
Terminal.Gui/View/ViewSubViews.cs

@@ -1,729 +1,725 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Linq;
-using System.Text;
-
-namespace Terminal.Gui {
-	public partial class View {
-		static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
-
-		View _superView = null;
-
-		/// <summary>
-		/// Returns the container for this view, or null if this view has not been added to a container.
-		/// </summary>
-		/// <value>The super view.</value>
-		public virtual View SuperView {
-			get {
-				return _superView;
-			}
-			set {
-				throw new NotImplementedException ();
-			}
-		}
 
-		List<View> _subviews; // This is null, and allocated on demand.
-		/// <summary>
-		/// This returns a list of the subviews contained by this view.
-		/// </summary>
-		/// <value>The subviews.</value>
-		public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
-
-		// Internally, we use InternalSubviews rather than subviews, as we do not expect us
-		// to make the same mistakes our users make when they poke at the Subviews.
-		internal IList<View> InternalSubviews => _subviews ?? _empty;
-
-		/// <summary>
-		/// Returns a value indicating if this View is currently on Top (Active)
-		/// </summary>
-		public bool IsCurrentTop => Application.Current == this;
-
-		/// <summary>
-		/// Event fired when this view is added to another.
-		/// </summary>
-		public event EventHandler<SuperViewChangedEventArgs> Added;
-
-		internal bool _addingView;
-
-		/// <summary>
-		///   Adds a subview (child) to this view.
-		/// </summary>
-		/// <remarks>
-		/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. 
-		/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/> 
-		/// </remarks>
-		public virtual void Add (View view)
-		{
-			if (view == null) {
-				return;
-			}
-			if (_subviews == null) {
-				_subviews = new List<View> ();
-			}
-			if (_tabIndexes == null) {
-				_tabIndexes = new List<View> ();
-			}
-			_subviews.Add (view);
-			_tabIndexes.Add (view);
-			view._superView = this;
-			if (view.CanFocus) {
-				_addingView = true;
-				if (SuperView?.CanFocus == false) {
-					SuperView._addingView = true;
-					SuperView.CanFocus = true;
-					SuperView._addingView = false;
-				}
-				CanFocus = true;
-				view._tabIndex = _tabIndexes.IndexOf (view);
-				_addingView = false;
-			}
-			if (view.Enabled && !Enabled) {
-				view._oldEnabled = true;
-				view.Enabled = false;
-			}
+namespace Terminal.Gui; 
 
-			OnAdded (new SuperViewChangedEventArgs (this, view));
-			if (IsInitialized && !view.IsInitialized) {
-				view.BeginInit ();
-				view.EndInit ();
-			}
+public partial class View {
+	static readonly IList<View> _empty = new List<View> (0).AsReadOnly ();
 
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
+	internal bool _addingView;
+
+	List<View> _subviews; // This is null, and allocated on demand.
+
+	View _superView;
+
+	/// <summary>
+	/// Returns the container for this view, or null if this view has not been added to a container.
+	/// </summary>
+	/// <value>The super view.</value>
+	public virtual View SuperView {
+		get => _superView;
+		set => throw new NotImplementedException ();
+	}
+
+	/// <summary>
+	/// This returns a list of the subviews contained by this view.
+	/// </summary>
+	/// <value>The subviews.</value>
+	public IList<View> Subviews => _subviews?.AsReadOnly () ?? _empty;
+
+	// Internally, we use InternalSubviews rather than subviews, as we do not expect us
+	// to make the same mistakes our users make when they poke at the Subviews.
+	internal IList<View> InternalSubviews => _subviews ?? _empty;
+
+	/// <summary>
+	/// Returns a value indicating if this View is currently on Top (Active)
+	/// </summary>
+	public bool IsCurrentTop => Application.Current == this;
+
+	/// <summary>
+	/// Indicates whether the view was added to <see cref="SuperView"/>.
+	/// </summary>
+	public bool IsAdded { get; private set; }
+
+	/// <summary>
+	/// Event fired when this view is added to another.
+	/// </summary>
+	public event EventHandler<SuperViewChangedEventArgs> Added;
+
+	/// <summary>
+	/// Adds a subview (child) to this view.
+	/// </summary>
+	/// <remarks>
+	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+	/// </remarks>
+	public virtual void Add (View view)
+	{
+		if (view == null) {
+			return;
+		}
+		if (_subviews == null) {
+			_subviews = new List<View> ();
+		}
+		if (_tabIndexes == null) {
+			_tabIndexes = new List<View> ();
+		}
+		_subviews.Add (view);
+		_tabIndexes.Add (view);
+		view._superView = this;
+		if (view.CanFocus) {
+			_addingView = true;
+			if (SuperView?.CanFocus == false) {
+				SuperView._addingView = true;
+				SuperView.CanFocus    = true;
+				SuperView._addingView = false;
+			}
+			CanFocus       = true;
+			view._tabIndex = _tabIndexes.IndexOf (view);
+			_addingView    = false;
+		}
+		if (view.Enabled && !Enabled) {
+			view._oldEnabled = true;
+			view.Enabled     = false;
 		}
 
-		/// <summary>
-		/// Adds the specified views (children) to the view.
-		/// </summary>
-		/// <param name="views">Array of one or more views (can be optional parameter).</param>
-		/// <remarks>
-		/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property. 
-		/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/> 
-		/// </remarks>
-		public void Add (params View [] views)
-		{
-			if (views == null) {
-				return;
-			}
-			foreach (var view in views) {
-				Add (view);
-			}
+		OnAdded (new SuperViewChangedEventArgs (this, view));
+		if (IsInitialized && !view.IsInitialized) {
+			view.BeginInit ();
+			view.EndInit ();
 		}
 
-		/// <summary>
-		/// Method invoked when a subview is being added to this view.
-		/// </summary>
-		/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
-		public virtual void OnAdded (SuperViewChangedEventArgs e)
-		{
-			var view = e.Child;
-			view.IsAdded = true;
-			view.OnResizeNeeded ();
-			view._x ??= view._frame.X;
-			view._y ??= view._frame.Y;
-			view._width ??= view._frame.Width;
-			view._height ??= view._frame.Height;
-
-			view.Added?.Invoke (this, e);
-		}
-
-		/// <summary>
-		/// Indicates whether the view was added to <see cref="SuperView"/>.
-		/// </summary>
-		public bool IsAdded { get; private set; }
-
-		/// <summary>
-		/// Event fired when this view is removed from another.
-		/// </summary>
-		public event EventHandler<SuperViewChangedEventArgs> Removed;
-
-		/// <summary>
-		///   Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-		/// </summary>
-		public virtual void RemoveAll ()
-		{
-			if (_subviews == null) {
-				return;
-			}
+		SetNeedsLayout ();
+		SetNeedsDisplay ();
+	}
 
-			while (_subviews.Count > 0) {
-				Remove (_subviews [0]);
-			}
+	/// <summary>
+	/// Adds the specified views (children) to the view.
+	/// </summary>
+	/// <param name="views">Array of one or more views (can be optional parameter).</param>
+	/// <remarks>
+	/// The Views that have been added to this view can be retrieved via the <see cref="Subviews"/> property.
+	/// See also <seealso cref="Remove(View)"/> <seealso cref="RemoveAll"/>
+	/// </remarks>
+	public void Add (params View [] views)
+	{
+		if (views == null) {
+			return;
+		}
+		foreach (var view in views) {
+			Add (view);
 		}
+	}
 
-		/// <summary>
-		///   Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
-		/// </summary>
-		/// <remarks>
-		/// </remarks>
-		public virtual void Remove (View view)
-		{
-			if (view == null || _subviews == null) return;
-
-			var touched = view.Frame;
-			_subviews.Remove (view);
-			_tabIndexes.Remove (view);
-			view._superView = null;
-			view._tabIndex = -1;
-			SetNeedsLayout ();
-			SetNeedsDisplay ();
+	/// <summary>
+	/// Method invoked when a subview is being added to this view.
+	/// </summary>
+	/// <param name="e">Event where <see cref="ViewEventArgs.View"/> is the subview being added.</param>
+	public virtual void OnAdded (SuperViewChangedEventArgs e)
+	{
+		var view = e.Child;
+		view.IsAdded = true;
+		view.OnResizeNeeded ();
+		view.Added?.Invoke (this, e);
+	}
 
-			foreach (var v in _subviews) {
-				if (v.Frame.IntersectsWith (touched))
-					view.SetNeedsDisplay ();
-			}
-			OnRemoved (new SuperViewChangedEventArgs (this, view));
-			if (_focused == view) {
-				_focused = null;
-			}
+	/// <summary>
+	/// Event fired when this view is removed from another.
+	/// </summary>
+	public event EventHandler<SuperViewChangedEventArgs> Removed;
+
+	/// <summary>
+	/// Removes all subviews (children) added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+	/// </summary>
+	public virtual void RemoveAll ()
+	{
+		if (_subviews == null) {
+			return;
 		}
 
-		/// <summary>
-		/// Method invoked when a subview is being removed from this view.
-		/// </summary>
-		/// <param name="e">Event args describing the subview being removed.</param>
-		public virtual void OnRemoved (SuperViewChangedEventArgs e)
-		{
-			var view = e.Child;
-			view.IsAdded = false;
-			view.Removed?.Invoke (this, e);
+		while (_subviews.Count > 0) {
+			Remove (_subviews [0]);
 		}
+	}
 
+	/// <summary>
+	/// Removes a subview added via <see cref="Add(View)"/> or <see cref="Add(View[])"/> from this View.
+	/// </summary>
+	/// <remarks>
+	/// </remarks>
+	public virtual void Remove (View view)
+	{
+		if (view == null || _subviews == null) {
+			return;
+		}
 
-		void PerformActionForSubview (View subview, Action<View> action)
-		{
-			if (_subviews.Contains (subview)) {
-				action (subview);
-			}
+		var touched = view.Frame;
+		_subviews.Remove (view);
+		_tabIndexes.Remove (view);
+		view._superView = null;
+		view._tabIndex  = -1;
+		SetNeedsLayout ();
+		SetNeedsDisplay ();
 
-			SetNeedsDisplay ();
-			subview.SetNeedsDisplay ();
-		}
-
-		/// <summary>
-		/// Brings the specified subview to the front so it is drawn on top of any other views.
-		/// </summary>
-		/// <param name="subview">The subview to send to the front</param>
-		/// <remarks>
-		///   <seealso cref="SendSubviewToBack"/>.
-		/// </remarks>
-		public void BringSubviewToFront (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				_subviews.Remove (x);
-				_subviews.Add (x);
-			});
-		}
-
-		/// <summary>
-		/// Sends the specified subview to the front so it is the first view drawn
-		/// </summary>
-		/// <param name="subview">The subview to send to the front</param>
-		/// <remarks>
-		///   <seealso cref="BringSubviewToFront(View)"/>.
-		/// </remarks>
-		public void SendSubviewToBack (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				_subviews.Remove (x);
-				_subviews.Insert (0, subview);
-			});
-		}
-
-		/// <summary>
-		/// Moves the subview backwards in the hierarchy, only one step
-		/// </summary>
-		/// <param name="subview">The subview to send backwards</param>
-		/// <remarks>
-		/// If you want to send the view all the way to the back use SendSubviewToBack.
-		/// </remarks>
-		public void SendSubviewBackwards (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				var idx = _subviews.IndexOf (x);
-				if (idx > 0) {
-					_subviews.Remove (x);
-					_subviews.Insert (idx - 1, x);
-				}
-			});
-		}
-
-		/// <summary>
-		/// Moves the subview backwards in the hierarchy, only one step
-		/// </summary>
-		/// <param name="subview">The subview to send backwards</param>
-		/// <remarks>
-		/// If you want to send the view all the way to the back use SendSubviewToBack.
-		/// </remarks>
-		public void BringSubviewForward (View subview)
-		{
-			PerformActionForSubview (subview, x => {
-				var idx = _subviews.IndexOf (x);
-				if (idx + 1 < _subviews.Count) {
-					_subviews.Remove (x);
-					_subviews.Insert (idx + 1, x);
-				}
-			});
-		}
-
-		/// <summary>
-		/// Get the top superview of a given <see cref="View"/>.
-		/// </summary>
-		/// <returns>The superview view.</returns>
-		public View GetTopSuperView (View view = null, View superview = null)
-		{
-			View top = superview ?? Application.Top;
-			for (var v = view?.SuperView ?? (this?.SuperView); v != null; v = v.SuperView) {
-				top = v;
-				if (top == superview) {
-					break;
-				}
+		foreach (var v in _subviews) {
+			if (v.Frame.IntersectsWith (touched)) {
+				view.SetNeedsDisplay ();
 			}
-
-			return top;
 		}
+		OnRemoved (new SuperViewChangedEventArgs (this, view));
+		if (Focused == view) {
+			Focused = null;
+		}
+	}
 
+	/// <summary>
+	/// Method invoked when a subview is being removed from this view.
+	/// </summary>
+	/// <param name="e">Event args describing the subview being removed.</param>
+	public virtual void OnRemoved (SuperViewChangedEventArgs e)
+	{
+		var view = e.Child;
+		view.IsAdded = false;
+		view.Removed?.Invoke (this, e);
+	}
 
 
-		#region Focus
-		View _focused = null;
+	void PerformActionForSubview (View subview, Action<View> action)
+	{
+		if (_subviews.Contains (subview)) {
+			action (subview);
+		}
 
-		internal enum Direction {
-			Forward,
-			Backward
+		SetNeedsDisplay ();
+		subview.SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Brings the specified subview to the front so it is drawn on top of any other views.
+	/// </summary>
+	/// <param name="subview">The subview to send to the front</param>
+	/// <remarks>
+	/// <seealso cref="SendSubviewToBack"/>.
+	/// </remarks>
+	public void BringSubviewToFront (View subview) => PerformActionForSubview (subview, x => {
+		_subviews.Remove (x);
+		_subviews.Add (x);
+	});
+
+	/// <summary>
+	/// Sends the specified subview to the front so it is the first view drawn
+	/// </summary>
+	/// <param name="subview">The subview to send to the front</param>
+	/// <remarks>
+	/// <seealso cref="BringSubviewToFront(View)"/>.
+	/// </remarks>
+	public void SendSubviewToBack (View subview) => PerformActionForSubview (subview, x => {
+		_subviews.Remove (x);
+		_subviews.Insert (0, subview);
+	});
+
+	/// <summary>
+	/// Moves the subview backwards in the hierarchy, only one step
+	/// </summary>
+	/// <param name="subview">The subview to send backwards</param>
+	/// <remarks>
+	/// If you want to send the view all the way to the back use SendSubviewToBack.
+	/// </remarks>
+	public void SendSubviewBackwards (View subview) => PerformActionForSubview (subview, x => {
+		var idx = _subviews.IndexOf (x);
+		if (idx > 0) {
+			_subviews.Remove (x);
+			_subviews.Insert (idx - 1, x);
+		}
+	});
+
+	/// <summary>
+	/// Moves the subview backwards in the hierarchy, only one step
+	/// </summary>
+	/// <param name="subview">The subview to send backwards</param>
+	/// <remarks>
+	/// If you want to send the view all the way to the back use SendSubviewToBack.
+	/// </remarks>
+	public void BringSubviewForward (View subview) => PerformActionForSubview (subview, x => {
+		var idx = _subviews.IndexOf (x);
+		if (idx + 1 < _subviews.Count) {
+			_subviews.Remove (x);
+			_subviews.Insert (idx + 1, x);
+		}
+	});
+
+	/// <summary>
+	/// Get the top superview of a given <see cref="View"/>.
+	/// </summary>
+	/// <returns>The superview view.</returns>
+	public View GetTopSuperView (View view = null, View superview = null)
+	{
+		var top = superview ?? Application.Top;
+		for (var v = view?.SuperView ?? this?.SuperView; v != null; v = v.SuperView) {
+			top = v;
+			if (top == superview) {
+				break;
+			}
 		}
 
-		/// <summary>
-		/// Event fired when the view gets focus.
-		/// </summary>
-		public event EventHandler<FocusEventArgs> Enter;
+		return top;
+	}
+
 
-		/// <summary>
-		/// Event fired when the view looses focus.
-		/// </summary>
-		public event EventHandler<FocusEventArgs> Leave;
 
-		Direction _focusDirection;
-		internal Direction FocusDirection {
-			get => SuperView?.FocusDirection ?? _focusDirection;
-			set {
-				if (SuperView != null)
-					SuperView.FocusDirection = value;
-				else
-					_focusDirection = value;
+	#region Focus
+	internal enum Direction {
+		Forward,
+		Backward
+	}
+
+	/// <summary>
+	/// Event fired when the view gets focus.
+	/// </summary>
+	public event EventHandler<FocusEventArgs> Enter;
+
+	/// <summary>
+	/// Event fired when the view looses focus.
+	/// </summary>
+	public event EventHandler<FocusEventArgs> Leave;
+
+	Direction _focusDirection;
+
+	internal Direction FocusDirection {
+		get => SuperView?.FocusDirection ?? _focusDirection;
+		set {
+			if (SuperView != null) {
+				SuperView.FocusDirection = value;
+			} else {
+				_focusDirection = value;
 			}
 		}
+	}
 
 
-		// BUGBUG: v2 - Seems weird that this is in View and not Responder.
-		bool _hasFocus;
+	// BUGBUG: v2 - Seems weird that this is in View and not Responder.
+	bool _hasFocus;
 
-		/// <inheritdoc/>
-		public override bool HasFocus => _hasFocus;
+	/// <inheritdoc/>
+	public override bool HasFocus => _hasFocus;
 
-		void SetHasFocus (bool value, View view, bool force = false)
-		{
-			if (_hasFocus != value || force) {
-				_hasFocus = value;
-				if (value) {
-					OnEnter (view);
-				} else {
-					OnLeave (view);
-				}
-				SetNeedsDisplay ();
+	void SetHasFocus (bool value, View view, bool force = false)
+	{
+		if (_hasFocus != value || force) {
+			_hasFocus = value;
+			if (value) {
+				OnEnter (view);
+			} else {
+				OnLeave (view);
 			}
+			SetNeedsDisplay ();
+		}
 
-			// Remove focus down the chain of subviews if focus is removed
-			if (!value && _focused != null) {
-				var f = _focused;
-				f.OnLeave (view);
-				f.SetHasFocus (false, view);
-				_focused = null;
-			}
+		// Remove focus down the chain of subviews if focus is removed
+		if (!value && Focused != null) {
+			var f = Focused;
+			f.OnLeave (view);
+			f.SetHasFocus (false, view);
+			Focused = null;
 		}
+	}
 
-		/// <summary>
-		/// Event fired when the <see cref="CanFocus"/> value is being changed.
-		/// </summary>
-		public event EventHandler CanFocusChanged;
+	/// <summary>
+	/// Event fired when the <see cref="CanFocus"/> value is being changed.
+	/// </summary>
+	public event EventHandler CanFocusChanged;
 
-		/// <inheritdoc/>
-		public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
+	/// <inheritdoc/>
+	public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (this, EventArgs.Empty);
+
+	bool _oldCanFocus;
+
+	/// <inheritdoc/>
+	public override bool CanFocus {
+		get => base.CanFocus;
+		set {
+			if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
+				throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+			}
+			if (base.CanFocus != value) {
+				base.CanFocus = value;
 
-		bool _oldCanFocus;
-		/// <inheritdoc/>
-		public override bool CanFocus {
-			get => base.CanFocus;
-			set {
-				if (!_addingView && IsInitialized && SuperView?.CanFocus == false && value) {
-					throw new InvalidOperationException ("Cannot set CanFocus to true if the SuperView CanFocus is false!");
+				switch (value) {
+				case false when _tabIndex > -1:
+					TabIndex = -1;
+					break;
+				case true when SuperView?.CanFocus == false && _addingView:
+					SuperView.CanFocus = true;
+					break;
 				}
-				if (base.CanFocus != value) {
-					base.CanFocus = value;
-
-					switch (value) {
-					case false when _tabIndex > -1:
-						TabIndex = -1;
-						break;
-					case true when SuperView?.CanFocus == false && _addingView:
-						SuperView.CanFocus = true;
-						break;
-					}
 
-					if (value && _tabIndex == -1) {
-						TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
-					}
-					TabStop = value;
+				if (value && _tabIndex == -1) {
+					TabIndex = SuperView != null ? SuperView._tabIndexes.IndexOf (this) : -1;
+				}
+				TabStop = value;
 
-					if (!value && SuperView?.Focused == this) {
-						SuperView._focused = null;
-					}
-					if (!value && HasFocus) {
-						SetHasFocus (false, this);
-						SuperView?.EnsureFocus ();
-						if (SuperView != null && SuperView.Focused == null) {
-							SuperView.FocusNext ();
-							if (SuperView.Focused == null && Application.Current != null) {
-								Application.Current.FocusNext ();
-							}
-							Application.BringOverlappedTopToFront ();
+				if (!value && SuperView?.Focused == this) {
+					SuperView.Focused = null;
+				}
+				if (!value && HasFocus) {
+					SetHasFocus (false, this);
+					SuperView?.EnsureFocus ();
+					if (SuperView != null && SuperView.Focused == null) {
+						SuperView.FocusNext ();
+						if (SuperView.Focused == null && Application.Current != null) {
+							Application.Current.FocusNext ();
 						}
+						Application.BringOverlappedTopToFront ();
 					}
-					if (_subviews != null && IsInitialized) {
-						foreach (var view in _subviews) {
-							if (view.CanFocus != value) {
-								if (!value) {
-									view._oldCanFocus = view.CanFocus;
-									view._oldTabIndex = view._tabIndex;
-									view.CanFocus = false;
-									view._tabIndex = -1;
-								} else {
-									if (_addingView) {
-										view._addingView = true;
-									}
-									view.CanFocus = view._oldCanFocus;
-									view._tabIndex = view._oldTabIndex;
-									view._addingView = false;
+				}
+				if (_subviews != null && IsInitialized) {
+					foreach (var view in _subviews) {
+						if (view.CanFocus != value) {
+							if (!value) {
+								view._oldCanFocus = view.CanFocus;
+								view._oldTabIndex = view._tabIndex;
+								view.CanFocus     = false;
+								view._tabIndex    = -1;
+							} else {
+								if (_addingView) {
+									view._addingView = true;
 								}
+								view.CanFocus    = view._oldCanFocus;
+								view._tabIndex   = view._oldTabIndex;
+								view._addingView = false;
 							}
 						}
 					}
-					OnCanFocusChanged ();
-					SetNeedsDisplay ();
 				}
+				OnCanFocusChanged ();
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
 
-		/// <inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			var args = new FocusEventArgs (view);
-			Enter?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
-			}
-			if (base.OnEnter (view)) {
-				return true;
-			}
+	/// <inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		var args = new FocusEventArgs (view);
+		Enter?.Invoke (this, args);
+		if (args.Handled) {
+			return true;
+		}
+		if (base.OnEnter (view)) {
+			return true;
+		}
 
-			return false;
+		return false;
+	}
+
+	/// <inheritdoc/>
+	public override bool OnLeave (View view)
+	{
+		var args = new FocusEventArgs (view);
+		Leave?.Invoke (this, args);
+		if (args.Handled) {
+			return true;
+		}
+		if (base.OnLeave (view)) {
+			return true;
 		}
 
-		/// <inheritdoc/>
-		public override bool OnLeave (View view)
-		{
-			var args = new FocusEventArgs (view);
-			Leave?.Invoke (this, args);
-			if (args.Handled) {
-				return true;
+		Driver?.SetCursorVisibility (CursorVisibility.Invisible);
+		return false;
+	}
+
+	/// <summary>
+	/// Returns the currently focused view inside this view, or null if nothing is focused.
+	/// </summary>
+	/// <value>The focused.</value>
+	public View Focused { get; private set; }
+
+	/// <summary>
+	/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
+	/// </summary>
+	/// <value>The most focused View.</value>
+	public View MostFocused {
+		get {
+			if (Focused == null) {
+				return null;
 			}
-			if (base.OnLeave (view)) {
-				return true;
+			var most = Focused.MostFocused;
+			if (most != null) {
+				return most;
 			}
-
-			Driver?.SetCursorVisibility (CursorVisibility.Invisible);
-			return false;
+			return Focused;
 		}
+	}
 
-		/// <summary>
-		/// Returns the currently focused view inside this view, or null if nothing is focused.
-		/// </summary>
-		/// <value>The focused.</value>
-		public View Focused => _focused;
-
-		/// <summary>
-		/// Returns the most focused view in the chain of subviews (the leaf view that has the focus).
-		/// </summary>
-		/// <value>The most focused View.</value>
-		public View MostFocused {
-			get {
-				if (Focused == null)
-					return null;
-				var most = Focused.MostFocused;
-				if (most != null)
-					return most;
-				return Focused;
-			}
+	/// <summary>
+	/// Causes the specified subview to have focus.
+	/// </summary>
+	/// <param name="view">View.</param>
+	void SetFocus (View view)
+	{
+		if (view == null) {
+			return;
+		}
+		//Console.WriteLine ($"Request to focus {view}");
+		if (!view.CanFocus || !view.Visible || !view.Enabled) {
+			return;
 		}
+		if (Focused?._hasFocus == true && Focused == view) {
+			return;
+		}
+		if (Focused?._hasFocus == true && Focused?.SuperView == view || view == this) {
 
-		/// <summary>
-		/// Causes the specified subview to have focus.
-		/// </summary>
-		/// <param name="view">View.</param>
-		void SetFocus (View view)
-		{
-			if (view == null) {
-				return;
-			}
-			//Console.WriteLine ($"Request to focus {view}");
-			if (!view.CanFocus || !view.Visible || !view.Enabled) {
-				return;
+			if (!view._hasFocus) {
+				view._hasFocus = true;
 			}
-			if (_focused?._hasFocus == true && _focused == view) {
-				return;
+			return;
+		}
+		// Make sure that this view is a subview
+		View c;
+		for (c = view._superView; c != null; c = c._superView) {
+			if (c == this) {
+				break;
 			}
-			if ((_focused?._hasFocus == true && _focused?.SuperView == view) || view == this) {
+		}
+		if (c == null) {
+			throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+		}
 
-				if (!view._hasFocus) {
-					view._hasFocus = true;
-				}
-				return;
-			}
-			// Make sure that this view is a subview
-			View c;
-			for (c = view._superView; c != null; c = c._superView)
-				if (c == this)
-					break;
-			if (c == null)
-				throw new ArgumentException ("the specified view is not part of the hierarchy of this view");
+		if (Focused != null) {
+			Focused.SetHasFocus (false, view);
+		}
 
-			if (_focused != null)
-				_focused.SetHasFocus (false, view);
+		var f = Focused;
+		Focused = view;
+		Focused.SetHasFocus (true, f);
+		Focused.EnsureFocus ();
 
-			var f = _focused;
-			_focused = view;
-			_focused.SetHasFocus (true, f);
-			_focused.EnsureFocus ();
+		// Send focus upwards
+		if (SuperView != null) {
+			SuperView.SetFocus (this);
+		} else {
+			SetFocus (this);
+		}
+	}
 
-			// Send focus upwards
-			if (SuperView != null) {
-				SuperView.SetFocus (this);
-			} else {
-				SetFocus (this);
+	/// <summary>
+	/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
+	/// </summary>
+	public void SetFocus ()
+	{
+		if (!CanBeVisible (this) || !Enabled) {
+			if (HasFocus) {
+				SetHasFocus (false, this);
 			}
+			return;
 		}
 
-		/// <summary>
-		/// Causes the specified view and the entire parent hierarchy to have the focused order updated.
-		/// </summary>
-		public void SetFocus ()
-		{
-			if (!CanBeVisible (this) || !Enabled) {
-				if (HasFocus) {
-					SetHasFocus (false, this);
-				}
-				return;
-			}
+		if (SuperView != null) {
+			SuperView.SetFocus (this);
+		} else {
+			SetFocus (this);
+		}
+	}
 
-			if (SuperView != null) {
-				SuperView.SetFocus (this);
+	/// <summary>
+	/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does
+	/// nothing.
+	/// </summary>
+	public void EnsureFocus ()
+	{
+		if (Focused == null && _subviews?.Count > 0) {
+			if (FocusDirection == Direction.Forward) {
+				FocusFirst ();
 			} else {
-				SetFocus (this);
+				FocusLast ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, does nothing.
-		/// </summary>
-		public void EnsureFocus ()
-		{
-			if (_focused == null && _subviews?.Count > 0) {
-				if (FocusDirection == Direction.Forward) {
-					FocusFirst ();
-				} else {
-					FocusLast ();
-				}
-			}
+	/// <summary>
+	/// Focuses the first focusable subview if one exists.
+	/// </summary>
+	public void FocusFirst ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
 
-		/// <summary>
-		/// Focuses the first focusable subview if one exists.
-		/// </summary>
-		public void FocusFirst ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
+		if (_tabIndexes == null) {
+			SuperView?.SetFocus (this);
+			return;
+		}
 
-			if (_tabIndexes == null) {
-				SuperView?.SetFocus (this);
+		foreach (var view in _tabIndexes) {
+			if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
+				SetFocus (view);
 				return;
 			}
+		}
+	}
 
-			foreach (var view in _tabIndexes) {
-				if (view.CanFocus && view._tabStop && view.Visible && view.Enabled) {
-					SetFocus (view);
-					return;
-				}
-			}
+	/// <summary>
+	/// Focuses the last focusable subview if one exists.
+	/// </summary>
+	public void FocusLast ()
+	{
+		if (!CanBeVisible (this)) {
+			return;
 		}
 
-		/// <summary>
-		/// Focuses the last focusable subview if one exists.
-		/// </summary>
-		public void FocusLast ()
-		{
-			if (!CanBeVisible (this)) {
-				return;
-			}
+		if (_tabIndexes == null) {
+			SuperView?.SetFocus (this);
+			return;
+		}
 
-			if (_tabIndexes == null) {
-				SuperView?.SetFocus (this);
+		for (var i = _tabIndexes.Count; i > 0;) {
+			i--;
+
+			var v = _tabIndexes [i];
+			if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
+				SetFocus (v);
 				return;
 			}
+		}
+	}
 
-			for (var i = _tabIndexes.Count; i > 0;) {
-				i--;
+	/// <summary>
+	/// Focuses the previous view.
+	/// </summary>
+	/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
+	public bool FocusPrev ()
+	{
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-				var v = _tabIndexes [i];
-				if (v.CanFocus && v._tabStop && v.Visible && v.Enabled) {
-					SetFocus (v);
-					return;
-				}
-			}
+		FocusDirection = Direction.Backward;
+		if (_tabIndexes == null || _tabIndexes.Count == 0) {
+			return false;
 		}
 
-		/// <summary>
-		/// Focuses the previous view.
-		/// </summary>
-		/// <returns><see langword="true"/> if previous was focused, <see langword="false"/> otherwise.</returns>
-		public bool FocusPrev ()
-		{
-			if (!CanBeVisible (this)) {
-				return false;
-			}
+		if (Focused == null) {
+			FocusLast ();
+			return Focused != null;
+		}
 
-			FocusDirection = Direction.Backward;
-			if (_tabIndexes == null || _tabIndexes.Count == 0)
-				return false;
+		var focusedIdx = -1;
+		for (var i = _tabIndexes.Count; i > 0;) {
+			i--;
+			var w = _tabIndexes [i];
 
-			if (_focused == null) {
-				FocusLast ();
-				return _focused != null;
+			if (w.HasFocus) {
+				if (w.FocusPrev ()) {
+					return true;
+				}
+				focusedIdx = i;
+				continue;
 			}
+			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
+				Focused.SetHasFocus (false, w);
 
-			var focusedIdx = -1;
-			for (var i = _tabIndexes.Count; i > 0;) {
-				i--;
-				var w = _tabIndexes [i];
-
-				if (w.HasFocus) {
-					if (w.FocusPrev ())
-						return true;
-					focusedIdx = i;
-					continue;
+				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
+					w.FocusLast ();
 				}
-				if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-					_focused.SetHasFocus (false, w);
-
-					if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-						w.FocusLast ();
 
-					SetFocus (w);
-					return true;
-				}
-			}
-			if (_focused != null) {
-				_focused.SetHasFocus (false, this);
-				_focused = null;
+				SetFocus (w);
+				return true;
 			}
-			return false;
 		}
+		if (Focused != null) {
+			Focused.SetHasFocus (false, this);
+			Focused = null;
+		}
+		return false;
+	}
 
-		/// <summary>
-		/// Focuses the next view.
-		/// </summary>
-		/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
-		public bool FocusNext ()
-		{
-			if (!CanBeVisible (this)) {
-				return false;
-			}
-
-			FocusDirection = Direction.Forward;
-			if (_tabIndexes == null || _tabIndexes.Count == 0)
-				return false;
+	/// <summary>
+	/// Focuses the next view.
+	/// </summary>
+	/// <returns><see langword="true"/> if next was focused, <see langword="false"/> otherwise.</returns>
+	public bool FocusNext ()
+	{
+		if (!CanBeVisible (this)) {
+			return false;
+		}
 
-			if (_focused == null) {
-				FocusFirst ();
-				return _focused != null;
-			}
-			var focusedIdx = -1;
-			for (var i = 0; i < _tabIndexes.Count; i++) {
-				var w = _tabIndexes [i];
-
-				if (w.HasFocus) {
-					if (w.FocusNext ())
-						return true;
-					focusedIdx = i;
-					continue;
-				}
-				if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
-					_focused.SetHasFocus (false, w);
+		FocusDirection = Direction.Forward;
+		if (_tabIndexes == null || _tabIndexes.Count == 0) {
+			return false;
+		}
 
-					if (w.CanFocus && w._tabStop && w.Visible && w.Enabled)
-						w.FocusFirst ();
+		if (Focused == null) {
+			FocusFirst ();
+			return Focused != null;
+		}
+		var focusedIdx = -1;
+		for (var i = 0; i < _tabIndexes.Count; i++) {
+			var w = _tabIndexes [i];
 
-					SetFocus (w);
+			if (w.HasFocus) {
+				if (w.FocusNext ()) {
 					return true;
 				}
+				focusedIdx = i;
+				continue;
 			}
-			if (_focused != null) {
-				_focused.SetHasFocus (false, this);
-				_focused = null;
-			}
-			return false;
-		}
+			if (w.CanFocus && focusedIdx != -1 && w._tabStop && w.Visible && w.Enabled) {
+				Focused.SetHasFocus (false, w);
 
-		View GetMostFocused (View view)
-		{
-			if (view == null) {
-				return null;
+				if (w.CanFocus && w._tabStop && w.Visible && w.Enabled) {
+					w.FocusFirst ();
+				}
+
+				SetFocus (w);
+				return true;
 			}
+		}
+		if (Focused != null) {
+			Focused.SetHasFocus (false, this);
+			Focused = null;
+		}
+		return false;
+	}
 
-			return view._focused != null ? GetMostFocused (view._focused) : view;
+	View GetMostFocused (View view)
+	{
+		if (view == null) {
+			return null;
 		}
 
-		/// <summary>
-		///   Positions the cursor in the right position based on the currently focused view in the chain.
-		/// </summary>
-		///    Views that are focusable should override <see cref="PositionCursor"/> to ensure
-		///    the cursor is placed in a location that makes sense. Unix terminals do not have
-		///    a way of hiding the cursor, so it can be distracting to have the cursor left at
-		///    the last focused view. Views should make sure that they place the cursor
-		///    in a visually sensible place.
-		public virtual void PositionCursor ()
-		{
-			if (!CanBeVisible (this) || !Enabled) {
-				return;
-			}
+		return view.Focused != null ? GetMostFocused (view.Focused) : view;
+	}
 
-			// BUGBUG: v2 - This needs to support children of Frames too
+	/// <summary>
+	/// Positions the cursor in the right position based on the currently focused view in the chain.
+	/// </summary>
+	/// Views that are focusable should override
+	/// <see cref="PositionCursor"/>
+	/// to ensure
+	/// the cursor is placed in a location that makes sense. Unix terminals do not have
+	/// a way of hiding the cursor, so it can be distracting to have the cursor left at
+	/// the last focused view. Views should make sure that they place the cursor
+	/// in a visually sensible place.
+	public virtual void PositionCursor ()
+	{
+		if (!CanBeVisible (this) || !Enabled) {
+			return;
+		}
 
-			if (_focused == null && SuperView != null) {
-				SuperView.EnsureFocus ();
-			} else if (_focused?.Visible == true && _focused?.Enabled == true && _focused?.Frame.Width > 0 && _focused.Frame.Height > 0) {
-				_focused.PositionCursor ();
-			} else if (_focused?.Visible == true && _focused?.Enabled == false) {
-				_focused = null;
-			} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
-				Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
-			} else {
-				Move (_frame.X, _frame.Y);
-			}
+		// BUGBUG: v2 - This needs to support children of Frames too
+
+		if (Focused == null && SuperView != null) {
+			SuperView.EnsureFocus ();
+		} else if (Focused?.Visible == true && Focused?.Enabled == true && Focused?.Frame.Width > 0 && Focused.Frame.Height > 0) {
+			Focused.PositionCursor ();
+		} else if (Focused?.Visible == true && Focused?.Enabled == false) {
+			Focused = null;
+		} else if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) {
+			Move (TextFormatter.HotKeyPos == -1 ? 0 : TextFormatter.CursorPosition, 0);
+		} else {
+			Move (_frame.X, _frame.Y);
 		}
-		#endregion Focus
 	}
-}
+	#endregion Focus
+}

+ 168 - 85
Terminal.Gui/View/ViewText.cs

@@ -7,24 +7,28 @@ public partial class View {
 	string _text;
 
 	/// <summary>
-	///   The text displayed by the <see cref="View"/>.
+	/// The text displayed by the <see cref="View"/>.
 	/// </summary>
 	/// <remarks>
-	/// <para>
-	///  The text will be drawn before any subviews are drawn.
-	/// </para>
-	/// <para>
-	///  The text will be drawn starting at the view origin (0, 0) and will be formatted according
-	///  to <see cref="TextAlignment"/> and <see cref="TextDirection"/>. 
-	/// </para>
-	/// <para>
-	///  The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
-	///  is 1, the text will be clipped.	
-	///  </para>
-	/// <para>
-	///  Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set <see cref="HotKeySpecifier"/> to
-	///  <c>(Rune)0xffff</c>.
-	/// </para>
+	///         <para>
+	///         The text will be drawn before any subviews are drawn.
+	///         </para>
+	///         <para>
+	///         The text will be drawn starting at the view origin (0, 0) and will be formatted according
+	///         to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
+	///         </para>
+	///         <para>
+	///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Bounds"/>'s height
+	///         is 1, the text will be clipped.
+	///         </para>
+	///         <para>
+	///         Set the <see cref="HotKeySpecifier"/> to enable hotkey support. To disable hotkey support set
+	///         <see cref="HotKeySpecifier"/> to
+	///         <c>(Rune)0xffff</c>.
+	///         </para>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
 	/// </remarks>
 	public virtual string Text {
 		get => _text;
@@ -32,7 +36,6 @@ public partial class View {
 			_text = value;
 			SetHotKey ();
 			UpdateTextFormatterText ();
-			//TextFormatter.Format ();
 			OnResizeNeeded ();
 
 #if DEBUG
@@ -48,21 +51,10 @@ public partial class View {
 	/// </summary>
 	public TextFormatter TextFormatter { get; set; }
 
-	/// <summary>
-	/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
-	///  different format than the default.
-	/// </summary>
-	protected virtual void UpdateTextFormatterText ()
-	{
-		if (TextFormatter != null) {
-			TextFormatter.Text = _text;
-		}
-	}
-
 	/// <summary>
 	/// Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
-	/// or not when <see cref="TextFormatter.WordWrap"/> is enabled. 
-	/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when 
+	/// or not when <see cref="TextFormatter.WordWrap"/> is enabled.
+	/// If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
 	/// <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
 	/// </summary>
 	public virtual bool PreserveTrailingSpaces {
@@ -76,8 +68,14 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will redisplay the <see cref="View"/>.
+	/// Gets or sets how the View's <see cref="Text"/> is aligned horizontally when drawn. Changing this property will
+	/// redisplay the <see cref="View"/>.
 	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
+	/// </remarks>
 	/// <value>The text alignment.</value>
 	public virtual TextAlignment TextAlignment {
 		get => TextFormatter.Alignment;
@@ -89,8 +87,14 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay the <see cref="View"/>.
+	/// Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will redisplay
+	/// the <see cref="View"/>.
 	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
+	/// </remarks>
 	/// <value>The text alignment.</value>
 	public virtual VerticalTextAlignment VerticalTextAlignment {
 		get => TextFormatter.VerticalAlignment;
@@ -101,8 +105,14 @@ public partial class View {
 	}
 
 	/// <summary>
-	/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the <see cref="View"/>.
+	/// Gets or sets the direction of the View's <see cref="Text"/>. Changing this property will redisplay the
+	/// <see cref="View"/>.
 	/// </summary>
+	/// <remarks>
+	///         <para>
+	///         If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Bounds"/> will be adjusted to fit the text.
+	///         </para>
+	/// </remarks>
 	/// <value>The text alignment.</value>
 	public virtual TextDirection TextDirection {
 		get => TextFormatter.Direction;
@@ -112,18 +122,27 @@ public partial class View {
 		}
 	}
 
+	/// <summary>
+	/// Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
+	/// different format than the default.
+	/// </summary>
+	protected virtual void UpdateTextFormatterText ()
+	{
+		if (TextFormatter != null) {
+			TextFormatter.Text = _text;
+		}
+	}
+
 	void UpdateTextDirection (TextDirection newDirection)
 	{
-		bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
-					!= TextFormatter.IsHorizontalDirection (newDirection);
+		var directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection);
 		TextFormatter.Direction = newDirection;
 
-		bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
+		var isValidOldAutoSize = AutoSize && IsValidAutoSize (out var _);
 
 		UpdateTextFormatterText ();
 
-		if (!ValidatePosDim && directionChanged && AutoSize
-		|| ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) {
+		if (!ValidatePosDim && directionChanged && AutoSize || ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize) {
 			OnResizeNeeded ();
 		} else if (directionChanged && IsAdded) {
 			ResizeBoundsToFit (Bounds.Size);
@@ -132,16 +151,19 @@ public partial class View {
 		} else {
 			SetFrameToFitText ();
 		}
-		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+		SetTextFormatterSize ();
 		SetNeedsDisplay ();
 	}
 
 
 	/// <summary>
-	/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>. 
+	/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>.
 	/// </summary>
-	/// <returns><see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> == <see langword="true"/> or
-	/// <see cref="Text"/> will not fit.</returns>
+	/// <returns>
+	/// <see langword="true"/> if the size was changed; <see langword="false"/> if <see cref="AutoSize"/> ==
+	/// <see langword="true"/> or
+	/// <see cref="Text"/> will not fit.
+	/// </returns>
 	/// <remarks>
 	/// Always returns <see langword="false"/> if <see cref="AutoSize"/> is <see langword="true"/> or
 	/// if <see cref="Height"/> (Horizontal) or <see cref="Width"/> (Vertical) are not not set or zero.
@@ -168,89 +190,150 @@ public partial class View {
 			}
 			sizeRequired = Bounds.Size;
 
-			if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
-				switch (TextFormatter.IsVerticalDirection (TextDirection)) {
-				case true:
-					int colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
-					// TODO: v2 - This uses frame.Width; it should only use Bounds
-					if (_frame.Width < colWidth &&
-					(Width == null ||
-					Bounds.Width >= 0 &&
-					Width is Dim.DimAbsolute &&
-					Width.Anchor (0) >= 0 &&
-					Width.Anchor (0) < colWidth)) {
-						sizeRequired = new Size (colWidth, Bounds.Height);
-						return true;
-					}
-					break;
-				default:
-					if (_frame.Height < 1 &&
-					(Height == null ||
-					Height is Dim.DimAbsolute &&
-					Height.Anchor (0) == 0)) {
-						sizeRequired = new Size (Bounds.Width, 1);
-						return true;
-					}
-					break;
+			if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) {
+				return false;
+			}
+			
+			switch (TextFormatter.IsVerticalDirection (TextDirection)) {
+			case true:
+				var colWidth = TextFormatter.GetSumMaxCharWidth (new List<string> { TextFormatter.Text }, 0, 1);
+				// TODO: v2 - This uses frame.Width; it should only use Bounds
+				if (_frame.Width < colWidth &&
+				    (Width == null ||
+				     Bounds.Width >= 0 &&
+				     Width is Dim.DimAbsolute &&
+				     Width.Anchor (0) >= 0 &&
+				     Width.Anchor (0) < colWidth)) {
+					sizeRequired = new Size (colWidth, Bounds.Height);
+					return true;
+				}
+				break;
+			default:
+				if (_frame.Height < 1 &&
+				    (Height == null ||
+				     Height is Dim.DimAbsolute &&
+				     Height.Anchor (0) == 0)) {
+					sizeRequired = new Size (Bounds.Width, 1);
+					return true;
 				}
+				break;
 			}
 			return false;
 		}
 
 		if (GetMinimumSizeOfText (out var size)) {
+			// TODO: This is a hack.
+			//_width  = size.Width;
+			//_height = size.Height;
 			_frame = new Rect (_frame.Location, size);
+			//throw new InvalidOperationException ("This is a hack.");
 			return true;
 		}
 		return false;
 	}
 
 	/// <summary>
-	/// Gets the width or height of the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> characters 
+	/// Gets the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> characters
 	/// in the <see cref="Text"/> property.
 	/// </summary>
 	/// <remarks>
-	/// Only the first hotkey specifier found in <see cref="Text"/> is supported.
+	/// Only the first HotKey specifier found in <see cref="Text"/> is supported.
 	/// </remarks>
-	/// <param name="isWidth">If <see langword="true"/> (the default) the width required for the hotkey specifier is returned. Otherwise the height is returned.</param>
-	/// <returns>The number of characters required for the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>. If the text direction specified
-	/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.</returns>
+	/// <param name="isWidth">
+	/// If <see langword="true"/> (the default) the width required for the HotKey specifier is returned. Otherwise the height
+	/// is returned.
+	/// </param>
+	/// <returns>
+	/// The number of characters required for the <see cref="TextFormatter.HotKeySpecifier"/>. If the text
+	/// direction specified
+	/// by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.
+	/// </returns>
 	public int GetHotKeySpecifierLength (bool isWidth = true)
 	{
 		if (isWidth) {
 			return TextFormatter.IsHorizontalDirection (TextDirection) &&
-				TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
-				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
-		} else {
-			return TextFormatter.IsVerticalDirection (TextDirection) &&
-				TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+			       TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
 				? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
 		}
+		return TextFormatter.IsVerticalDirection (TextDirection) &&
+		       TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+			? Math.Max (HotKeySpecifier.GetColumns (), 0) : 0;
 	}
 
 	/// <summary>
-	/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/>.
+	/// Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.
 	/// </summary>
 	/// <returns></returns>
-	public Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
+	internal Size GetSizeNeededForTextWithoutHotKey () => new (TextFormatter.Size.Width - GetHotKeySpecifierLength (),
 		TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
 
 	/// <summary>
-	/// Gets the dimensions required for <see cref="Text"/> accounting for a <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> .
+	/// Sets <see cref="TextFormatter"/>.Size to the current <see cref="Bounds"/> size, adjusted for
+	/// <see cref="TextFormatter.HotKeySpecifier"/>.
 	/// </summary>
+	/// <remarks>
+	/// Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the
+	/// size required to fit the text has changed.
+	/// changes.
+	/// </remarks>
 	/// <returns></returns>
-	public Size GetTextFormatterSizeNeededForTextAndHotKey ()
+	internal void SetTextFormatterSize ()
 	{
 		if (!IsInitialized) {
-			return Size.Empty;
+			TextFormatter.Size = Size.Empty;
+			return;
 		}
 
 		if (string.IsNullOrEmpty (TextFormatter.Text)) {
-			return Bounds.Size;
+			TextFormatter.Size = Bounds.Size;
+			return;
 		}
 
-		// BUGBUG: This IGNORES what Text is set to, using on only the current View size. This doesn't seem to make sense.
-		// BUGBUG: This uses Frame; in v2 it should be Bounds
-		return new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
+		TextFormatter.Size = new Size (Bounds.Size.Width + GetHotKeySpecifierLength (),
 			Bounds.Size.Height + GetHotKeySpecifierLength (false));
 	}
+
+	/// <summary>
+	/// Gets the Frame dimensions required to fit <see cref="Text"/> within <see cref="Bounds"/> using the text
+	/// <see cref="Direction"/> specified by the
+	/// <see cref="TextFormatter"/> property and accounting for any <see cref="HotKeySpecifier"/> characters.
+	/// </summary>
+	/// <returns>The <see cref="Size"/> the <see cref="Frame"/> needs to be set to fit the text.</returns>
+	public Size GetAutoSize ()
+	{
+		var x = 0;
+		var y = 0;
+		if (IsInitialized) {
+			x = Bounds.X;
+			y = Bounds.Y;
+		}
+		var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); 
+		int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
+		int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
+		return new Size (newWidth, newHeight);
+	}
+
+	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 !(ValidatePosDim && (!(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 !(ValidatePosDim && !(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 !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
+	}
 }

+ 0 - 2
Terminal.Gui/Views/Button.cs

@@ -118,8 +118,6 @@ public class Button : View {
 		_isDefault = is_default;
 		Text = text ?? string.Empty;
 
-		OnResizeNeeded ();
-
 		// Override default behavior of View
 		// Command.Default sets focus
 		AddCommand (Command.Accept, () => { OnClicked (); return true; });

+ 0 - 2
Terminal.Gui/Views/CheckBox.cs

@@ -86,8 +86,6 @@ public class CheckBox : View {
 		AutoSize = true;
 		Text = s;
 
-		OnResizeNeeded ();
-
 		// Things this view knows how to do
 		AddCommand (Command.ToggleChecked, () => ToggleChecked ());
 		AddCommand (Command.Accept, () => {

+ 227 - 233
Terminal.Gui/Views/ColorPicker.cs

@@ -1,281 +1,275 @@
 using System;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
 
+/// <summary>
+/// Event arguments for the <see cref="Color"/> events.
+/// </summary>
+public class ColorEventArgs : EventArgs {
 	/// <summary>
-	/// Event arguments for the <see cref="Color"/> events.
+	/// Initializes a new instance of <see cref="ColorEventArgs"/>
 	/// </summary>
-	public class ColorEventArgs : EventArgs {
+	public ColorEventArgs () { }
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="ColorEventArgs"/>
-		/// </summary>
-		public ColorEventArgs ()
-		{
-		}
+	/// <summary>
+	/// The new Thickness.
+	/// </summary>
+	public Color Color { get; set; }
+
+	/// <summary>
+	/// The previous Thickness.
+	/// </summary>
+	public Color PreviousColor { get; set; }
+}
 
-		/// <summary>
-		/// The new Thickness.
-		/// </summary>
-		public Color Color { get; set; }
+/// <summary>
+/// The <see cref="ColorPicker"/> <see cref="View"/> Color picker.
+/// </summary>
+public class ColorPicker : View {
+	int _boxHeight = 2;
+	int _boxWidth = 4;
 
-		/// <summary>
-		/// The previous Thickness.
-		/// </summary>
-		public Color PreviousColor { get; set; }
-	}
 
 	/// <summary>
-	/// The <see cref="ColorPicker"/> <see cref="View"/> Color picker.
+	/// Columns of color boxes
 	/// </summary>
-	public class ColorPicker : View {
-		private int _selectColorIndex = (int)Color.Black;
-
-
-		/// <summary>
-		/// Columns of color boxes
-		/// </summary>
-		private int _cols = 8;
-
-		/// <summary>
-		/// Rows of color boxes
-		/// </summary>
-		private int _rows = 2;
-
-		/// <summary>
-		/// Width of a color box
-		/// </summary>
-		public int BoxWidth {
-			get => _boxWidth;
-			set {
-				if (_boxWidth != value) {
-					_boxWidth = value;
-					if (IsInitialized) {
-						Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
-					}
-				}
-			}
-		}
-		private int _boxWidth = 4;
-
-		/// <summary>
-		/// Height of a color box
-		/// </summary>
-		public int BoxHeight {
-			get => _boxHeight;
-			set {
-				if (_boxHeight != value) {
-					_boxHeight = value;
-					if (IsInitialized) {
-						Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
-					}
-				}
-			}
-		}
-		int _boxHeight = 2;
-
-		/// <summary>
-		/// Cursor for the selected color.
-		/// </summary>
-		public Point Cursor {
-			get {
-				return new Point (_selectColorIndex % _cols, _selectColorIndex / _cols);
-			}
+	readonly int _cols = 8;
 
-			set {
-				var colorIndex = value.Y * _cols + value.X;
-				SelectedColor = (ColorName)colorIndex;
-			}
-		}
+	/// <summary>
+	/// Rows of color boxes
+	/// </summary>
+	readonly int _rows = 2;
 
-		/// <summary>
-		/// Fired when a color is picked.
-		/// </summary>
-		public event EventHandler<ColorEventArgs> ColorChanged;
-
-		/// <summary>
-		/// Selected color.
-		/// </summary>
-		public ColorName SelectedColor {
-			get {
-				return (ColorName)_selectColorIndex;
-			}
+	int _selectColorIndex = (int)Color.Black;
+
+	/// <summary>
+	/// Initializes a new instance of <see cref="ColorPicker"/>.
+	/// </summary>
+	public ColorPicker () => SetInitialProperties ();
 
-			set {
-				ColorName prev = (ColorName)_selectColorIndex;
-				_selectColorIndex = (int)value;
-				ColorChanged?.Invoke (this, new ColorEventArgs () {
-					PreviousColor = new Color (prev),
-					Color = new Color (value),
-				});
-				SetNeedsDisplay ();
+	/// <summary>
+	/// Width of a color box
+	/// </summary>
+	public int BoxWidth {
+		get => _boxWidth;
+		set {
+			if (_boxWidth != value) {
+				_boxWidth = value;
+				SetNeedsLayout ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Initializes a new instance of <see cref="ColorPicker"/>.
-		/// </summary>
-		public ColorPicker ()
-		{
-			SetInitialProperties ();
+	/// <summary>
+	/// Height of a color box
+	/// </summary>
+	public int BoxHeight {
+		get => _boxHeight;
+		set {
+			if (_boxHeight != value) {
+				_boxHeight = value;
+				SetNeedsLayout ();
+			}
 		}
+	}
 
-		private void SetInitialProperties ()
-		{
-			CanFocus = true;
-			AddCommands ();
-			AddKeyBindings ();
-			LayoutStarted += (o, a) => {
-				Bounds = new Rect (Bounds.Location, new Size (_cols * BoxWidth, _rows * BoxHeight));
-			};
+	/// <summary>
+	/// Cursor for the selected color.
+	/// </summary>
+	public Point Cursor {
+		get => new (_selectColorIndex % _cols, _selectColorIndex / _cols);
+		set {
+			var colorIndex = value.Y * _cols + value.X;
+			SelectedColor = (ColorName)colorIndex;
 		}
+	}
 
-		/// <summary>
-		/// Add the commands.
-		/// </summary>
-		private void AddCommands ()
-		{
-			AddCommand (Command.Left, () => MoveLeft ());
-			AddCommand (Command.Right, () => MoveRight ());
-			AddCommand (Command.LineUp, () => MoveUp ());
-			AddCommand (Command.LineDown, () => MoveDown ());
+	/// <summary>
+	/// Selected color.
+	/// </summary>
+	public ColorName SelectedColor {
+		get => (ColorName)_selectColorIndex;
+		set {
+			var prev = (ColorName)_selectColorIndex;
+			_selectColorIndex = (int)value;
+			ColorChanged?.Invoke (this, new ColorEventArgs {
+									       PreviousColor = new Color (prev),
+									       Color         = new Color (value)
+								       });
+			SetNeedsDisplay ();
 		}
+	}
 
-		/// <summary>
-		/// Add the KeyBindinds.
-		/// </summary>
-		private void AddKeyBindings ()
-		{
-			KeyBindings.Add (KeyCode.CursorLeft, Command.Left);
-			KeyBindings.Add (KeyCode.CursorRight, Command.Right);
-			KeyBindings.Add (KeyCode.CursorUp, Command.LineUp);
-			KeyBindings.Add (KeyCode.CursorDown, Command.LineDown);
-		}
+	/// <summary>
+	/// Fired when a color is picked.
+	/// </summary>
+	public event EventHandler<ColorEventArgs> ColorChanged;
+
+	void SetInitialProperties ()
+	{
+		CanFocus = true;
+		AddCommands ();
+		AddKeyBindings ();
+		LayoutStarted += (o, a) => {
+			var thickness = GetFramesThickness ();
+			Width  = _cols * BoxWidth + thickness.Vertical;
+			Height = _rows * BoxHeight + thickness.Horizontal;
+		};
+	}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
-
-			Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
-			var colorIndex = 0;
-
-			for (var y = 0; y < (Bounds.Height / BoxHeight); y++) {
-				for (var x = 0; x < (Bounds.Width / BoxWidth); x++) {
-					var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
-					Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
-					var selected = x == Cursor.X && y == Cursor.Y;
-					DrawColorBox (x, y, selected);
-					colorIndex++;
-				}
-			}
-		}
+	/// <summary>
+	/// Add the commands.
+	/// </summary>
+	void AddCommands ()
+	{
+		AddCommand (Command.Left,     () => MoveLeft ());
+		AddCommand (Command.Right,    () => MoveRight ());
+		AddCommand (Command.LineUp,   () => MoveUp ());
+		AddCommand (Command.LineDown, () => MoveDown ());
+	}
 
-		/// <summary>
-		/// Draw a box for one color.
-		/// </summary>
-		/// <param name="x">X location.</param>
-		/// <param name="y">Y location</param>
-		/// <param name="selected"></param>
-		private void DrawColorBox (int x, int y, bool selected)
-		{
-			var index = 0;
-
-			for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++) {
-				for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) {
-					Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
-					Driver.AddRune ((Rune)' ');
-					index++;
-				}
-			}
+	/// <summary>
+	/// Add the KeyBindinds.
+	/// </summary>
+	void AddKeyBindings ()
+	{
+		KeyBindings.Add (KeyCode.CursorLeft,  Command.Left);
+		KeyBindings.Add (KeyCode.CursorRight, Command.Right);
+		KeyBindings.Add (KeyCode.CursorUp,    Command.LineUp);
+		KeyBindings.Add (KeyCode.CursorDown,  Command.LineDown);
+	}
 
-			if (selected) {
-				DrawFocusRect (new Rect (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
+
+		Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ());
+		var colorIndex = 0;
+
+		for (var y = 0; y < Bounds.Height / BoxHeight; y++) {
+			for (var x = 0; x < Bounds.Width / BoxWidth; x++) {
+				var foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols;
+				Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
+				var selected = x == Cursor.X && y == Cursor.Y;
+				DrawColorBox (x, y, selected);
+				colorIndex++;
 			}
 		}
+	}
 
-		private void DrawFocusRect (Rect rect)
-		{
-			var lc = new LineCanvas ();
-			if (rect.Width == 1) {
-				lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
-			} else if (rect.Height == 1) {
-				lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-			} else {
-				lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-				lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted);
-
-				lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
-				lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted);
-			}
-			foreach (var p in lc.GetMap ()) {
-				AddRune (p.Key.X, p.Key.Y, p.Value);
+	/// <summary>
+	/// Draw a box for one color.
+	/// </summary>
+	/// <param name="x">X location.</param>
+	/// <param name="y">Y location</param>
+	/// <param name="selected"></param>
+	void DrawColorBox (int x, int y, bool selected)
+	{
+		var index = 0;
+
+		for (var zoomedY = 0; zoomedY < BoxHeight; zoomedY++) {
+			for (var zoomedX = 0; zoomedX < BoxWidth; zoomedX++) {
+				Move (x * BoxWidth + zoomedX, y * BoxHeight + zoomedY);
+				Driver.AddRune ((Rune)' ');
+				index++;
 			}
 		}
 
-		/// <summary>
-		/// Moves the selected item index to the previous column.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveLeft ()
-		{
-			if (Cursor.X > 0) SelectedColor--;
-			return true;
+		if (selected) {
+			DrawFocusRect (new Rect (x * BoxWidth, y * BoxHeight, BoxWidth, BoxHeight));
 		}
+	}
 
-		/// <summary>
-		/// Moves the selected item index to the next column.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveRight ()
-		{
-			if (Cursor.X < _cols - 1) SelectedColor++;
-			return true;
+	void DrawFocusRect (Rect rect)
+	{
+		var lc = new LineCanvas ();
+		if (rect.Width == 1) {
+			lc.AddLine (rect.Location, rect.Height, Orientation.Vertical, LineStyle.Dotted);
+		} else if (rect.Height == 1) {
+			lc.AddLine (rect.Location, rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+		} else {
+			lc.AddLine (rect.Location,                                                  rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+			lc.AddLine (new Point (rect.Location.X, rect.Location.Y + rect.Height - 1), rect.Width, Orientation.Horizontal, LineStyle.Dotted);
+
+			lc.AddLine (rect.Location,                                                 rect.Height, Orientation.Vertical, LineStyle.Dotted);
+			lc.AddLine (new Point (rect.Location.X + rect.Width - 1, rect.Location.Y), rect.Height, Orientation.Vertical, LineStyle.Dotted);
+		}
+		foreach (var p in lc.GetMap ()) {
+			AddRune (p.Key.X, p.Key.Y, p.Value);
 		}
+	}
 
-		/// <summary>
-		/// Moves the selected item index to the previous row.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveUp ()
-		{
-			if (Cursor.Y > 0) SelectedColor -= _cols;
-			return true;
+	/// <summary>
+	/// Moves the selected item index to the previous column.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveLeft ()
+	{
+		if (Cursor.X > 0) {
+			SelectedColor--;
+		}
+		return true;
+	}
+
+	/// <summary>
+	/// Moves the selected item index to the next column.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveRight ()
+	{
+		if (Cursor.X < _cols - 1) {
+			SelectedColor++;
 		}
+		return true;
+	}
 
-		/// <summary>
-		/// Moves the selected item index to the next row.
-		/// </summary>
-		/// <returns></returns>
-		public virtual bool MoveDown ()
-		{
-			if (Cursor.Y < _rows - 1) SelectedColor += _cols;
-			return true;
+	/// <summary>
+	/// Moves the selected item index to the previous row.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveUp ()
+	{
+		if (Cursor.Y > 0) {
+			SelectedColor -= _cols;
 		}
+		return true;
+	}
 
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent me)
-		{
-			if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) {
-				return false;
-			}
+	/// <summary>
+	/// Moves the selected item index to the next row.
+	/// </summary>
+	/// <returns></returns>
+	public virtual bool MoveDown ()
+	{
+		if (Cursor.Y < _rows - 1) {
+			SelectedColor += _cols;
+		}
+		return true;
+	}
 
-			SetFocus ();
-			if (me.X > Bounds.Width || me.Y > Bounds.Height) {
-				return true;
-			}
-			Cursor = new Point ((me.X ) / _boxWidth, (me.Y) / _boxHeight);
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent me)
+	{
+		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) || !CanFocus) {
+			return false;
+		}
 
+		SetFocus ();
+		if (me.X > Bounds.Width || me.Y > Bounds.Height) {
 			return true;
 		}
+		Cursor = new Point (me.X / _boxWidth, me.Y / _boxHeight);
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return true;
+	}
 
-			return base.OnEnter (view);
-		}
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+
+		return base.OnEnter (view);
 	}
-}
+}

+ 40 - 32
Terminal.Gui/Views/ComboBox.cs

@@ -10,7 +10,7 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Text;
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
 /// Provides a drop-down list of items the user can select from.
@@ -32,6 +32,7 @@ public class ComboBox : View {
 		{
 			_container = container ?? throw new ArgumentNullException (nameof (container), "ComboBox container cannot be null.");
 			HideDropdownListOnClick = hideDropdownListOnClick;
+			AddCommand (Command.LineUp, () => _container.MoveUpList ());
 		}
 
 		public bool HideDropdownListOnClick {
@@ -240,7 +241,7 @@ public class ComboBox : View {
 	public ComboBox (string text) : base ()
 	{
 		_search = new TextField ("");
-		_listview = new ComboListView (this, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false };
+		_listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = false };
 
 		SetInitialProperties ();
 		Text = text;
@@ -254,7 +255,7 @@ public class ComboBox : View {
 	public ComboBox (Rect rect, IList source) : base (rect)
 	{
 		_search = new TextField ("") { Width = rect.Width };
-		_listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+		_listview = new ComboListView (this, rect, source, HideDropdownListOnClick) { ColorScheme = Colors.Base };
 
 		SetInitialProperties ();
 		SetSource (source);
@@ -267,7 +268,7 @@ public class ComboBox : View {
 	public ComboBox (IList source) : this (string.Empty)
 	{
 		_search = new TextField ("");
-		_listview = new ComboListView (this, source, HideDropdownListOnClick) { LayoutStyle = LayoutStyle.Computed, ColorScheme = Colors.Base };
+		_listview = new ComboListView (this, source, HideDropdownListOnClick) { ColorScheme = Colors.Base };
 
 		SetInitialProperties ();
 		SetSource (source);
@@ -589,18 +590,23 @@ public class ComboBox : View {
 
 	bool? MoveUp ()
 	{
-		if (_search.HasFocus) {
-			// stop odd behavior on KeyUp when search has focus
-			return true;
+		if (HasItems ()) {
+			_listview.MoveUp ();
 		}
+		return true;
+	}
 
+	bool? MoveUpList ()
+	{
 		if (_listview.HasFocus && _listview.SelectedItem == 0 && _searchset?.Count > 0) // jump back to search
 		{
 			_search.CursorPosition = _search.Text.GetRuneCount ();
 			_search.SetFocus ();
-			return true;
+		} else {
+			MoveUp ();
 		}
-		return null;
+
+		return true;
 	}
 
 	bool? MoveDown ()
@@ -612,6 +618,8 @@ public class ComboBox : View {
 				_listview.SetFocus ();
 				if (_listview.SelectedItem > -1) {
 					SetValue (_searchset [_listview.SelectedItem]);
+				} else {
+					_listview.SelectedItem = 0;
 				}
 			} else {
 				_listview.TabStop = false;
@@ -731,18 +739,18 @@ public class ComboBox : View {
 		_isShow = false;
 	}
 
-		private int GetSelectedItemFromSource (string searchText)
-		{
-			if (_source is null) {
-				return -1;
-			}
-			for (int i = 0; i < _searchset.Count; i++) {
-				if (_searchset [i].ToString () == searchText) {
-					return i;
-				}
-			}
+	private int GetSelectedItemFromSource (string searchText)
+	{
+		if (_source is null) {
 			return -1;
 		}
+		for (int i = 0; i < _searchset.Count; i++) {
+			if (_searchset [i].ToString () == searchText) {
+				return i;
+			}
+		}
+		return -1;
+	}
 
 	/// <summary>
 	/// Reset to full original list
@@ -784,20 +792,20 @@ public class ComboBox : View {
 		}
 	}
 
-		private void Search_Changed (object sender, TextChangedEventArgs e)
-		{
-			if (_source is null) { // Object initialization		
-				return;
-			}
+	private void Search_Changed (object sender, TextChangedEventArgs e)
+	{
+		if (_source is null) { // Object initialization		
+			return;
+		}
 
-			if (string.IsNullOrEmpty (_search.Text) && string.IsNullOrEmpty (e.OldValue)) {
-				ResetSearchSet ();
-			} else if (_search.Text != e.OldValue) {
-				if (_search.Text.Length < e.OldValue.Length) {
-					_selectedItem = -1;
-				}
-				_isShow = true;
-				ResetSearchSet (noCopy: true);
+		if (string.IsNullOrEmpty (_search.Text) && string.IsNullOrEmpty (e.OldValue)) {
+			ResetSearchSet ();
+		} else if (_search.Text != e.OldValue) {
+			if (_search.Text.Length < e.OldValue.Length) {
+				_selectedItem = -1;
+			}
+			_isShow = true;
+			ResetSearchSet (noCopy: true);
 
 			foreach (object item in _source.ToList ()) {
 				// Iterate to preserver object type and force deep copy

+ 121 - 66
Terminal.Gui/Views/DateField.cs

@@ -10,7 +10,7 @@ using System.Globalization;
 using System.Linq;
 using System.Text;
 
-namespace Terminal.Gui; 
+namespace Terminal.Gui;
 
 /// <summary>
 ///   Simple Date editing <see cref="View"/>
@@ -19,17 +19,17 @@ namespace Terminal.Gui;
 ///   The <see cref="DateField"/> <see cref="View"/> provides date editing functionality with mouse support.
 /// </remarks>
 public class DateField : TextField {
-	DateTime date;
-	bool isShort;
-	int longFieldLen = 10;
-	int shortFieldLen = 8;
-	string sepChar;
-	string longFormat;
-	string shortFormat;
+	DateTime _date;
+	bool _isShort;
+	int _longFieldLen = 10;
+	int _shortFieldLen = 8;
+	string _sepChar;
+	string _longFormat;
+	string _shortFormat;
 
-	int fieldLen => isShort ? shortFieldLen : longFieldLen;
+	int _fieldLen => _isShort ? _shortFieldLen : _longFieldLen;
 
-	string format => isShort ? shortFormat : longFormat;
+	string _format => _isShort ? _shortFormat : _longFormat;
 
 	/// <summary>
 	///   DateChanged event, raised when the <see cref="Date"/> property has changed.
@@ -49,7 +49,7 @@ public class DateField : TextField {
 	/// <param name="y">The y coordinate.</param>
 	/// <param name="date">Initial date contents.</param>
 	/// <param name="isShort">If true, shows only two digits for the year.</param>
-	public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "") => Initialize (date, isShort);
+	public DateField (int x, int y, DateTime date, bool isShort = false) : base (x, y, isShort ? 10 : 12, "") => SetInitialProperties (date, isShort);
 
 	/// <summary>
 	///  Initializes a new instance of <see cref="DateField"/> using <see cref="LayoutStyle.Computed"/> layout.
@@ -62,17 +62,17 @@ public class DateField : TextField {
 	/// <param name="date"></param>
 	public DateField (DateTime date) : base ("")
 	{
-		Width = fieldLen + 2;
-		Initialize (date);
+		Width = _fieldLen + 2;
+		SetInitialProperties (date);
 	}
 
-	void Initialize (DateTime date, bool isShort = false)
+	void SetInitialProperties (DateTime date, bool isShort = false)
 	{
 		var cultureInfo = CultureInfo.CurrentCulture;
-		sepChar = cultureInfo.DateTimeFormat.DateSeparator;
-		longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
-		shortFormat = GetShortFormat (longFormat);
-		this.isShort = isShort;
+		_sepChar = cultureInfo.DateTimeFormat.DateSeparator;
+		_longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern);
+		_shortFormat = GetShortFormat (_longFormat);
+		this._isShort = isShort;
 		Date = date;
 		CursorPosition = 1;
 		TextChanged += DateField_Changed;
@@ -95,8 +95,8 @@ public class DateField : TextField {
 		KeyBindings.Add (KeyCode.Delete, Command.DeleteCharRight);
 		KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
 
-		KeyBindings.Add (Key.Delete, Command.DeleteCharLeft);
 		KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
+		KeyBindings.Add (Key.D.WithAlt, Command.DeleteCharLeft);
 
 		KeyBindings.Add (Key.Home, Command.LeftHome);
 		KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
@@ -130,7 +130,14 @@ public class DateField : TextField {
 	void DateField_Changed (object sender, TextChangedEventArgs e)
 	{
 		try {
-			if (!DateTime.TryParseExact (GetDate (Text), GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) {
+			var date = GetInvarianteDate (Text, _isShort);
+			if ($" {date}" != Text) {
+				Text = $" {date}";
+			}
+			if (_isShort) {
+				date = GetInvarianteDate (Text, false);
+			}
+			if (!DateTime.TryParseExact (date, GetInvarianteFormat (), CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) {
 				Text = e.OldValue;
 			}
 		} catch (Exception) {
@@ -138,11 +145,11 @@ public class DateField : TextField {
 		}
 	}
 
-	string GetInvarianteFormat () => $"MM{sepChar}dd{sepChar}yyyy";
+	string GetInvarianteFormat () => $"MM{_sepChar}dd{_sepChar}yyyy";
 
 	string GetLongFormat (string lf)
 	{
-		string [] frm = lf.Split (sepChar);
+		string [] frm = lf.Split (_sepChar);
 		for (int i = 0; i < frm.Length; i++) {
 			if (frm [i].Contains ("M") && frm [i].GetRuneCount () < 2) {
 				lf = lf.Replace ("M", "MM");
@@ -165,16 +172,16 @@ public class DateField : TextField {
 	/// <remarks>
 	/// </remarks>
 	public DateTime Date {
-		get => date;
+		get => _date;
 		set {
 			if (ReadOnly) {
 				return;
 			}
 
-			var oldData = date;
-			date = value;
-			Text = value.ToString (format);
-			var args = new DateTimeEventArgs<DateTime> (oldData, value, format);
+			var oldData = _date;
+			_date = value;
+			Text = value.ToString (_format);
+			var args = new DateTimeEventArgs<DateTime> (oldData, value, _format);
 			if (oldData != value) {
 				OnDateChanged (args);
 			}
@@ -185,10 +192,10 @@ public class DateField : TextField {
 	/// Get or set the date format for the widget.
 	/// </summary>
 	public bool IsShortFormat {
-		get => isShort;
+		get => _isShort;
 		set {
-			isShort = value;
-			if (isShort) {
+			_isShort = value;
+			if (_isShort) {
 				Width = 10;
 			} else {
 				Width = 12;
@@ -206,15 +213,23 @@ public class DateField : TextField {
 	/// <inheritdoc/>
 	public override int CursorPosition {
 		get => base.CursorPosition;
-		set => base.CursorPosition = Math.Max (Math.Min (value, fieldLen), 1);
+		set => base.CursorPosition = Math.Max (Math.Min (value, _fieldLen), 1);
 	}
 
 	bool SetText (Rune key)
 	{
+		if (CursorPosition > _fieldLen) {
+			CursorPosition = _fieldLen;
+			return false;
+		} else if (CursorPosition < 1) {
+			CursorPosition = 1;
+			return false;
+		}
+
 		var text = Text.EnumerateRunes ().ToList ();
 		var newText = text.GetRange (0, CursorPosition);
 		newText.Add (key);
-		if (CursorPosition < fieldLen) {
+		if (CursorPosition < _fieldLen) {
 			newText = newText.Concat (text.GetRange (CursorPosition + 1, text.Count - (CursorPosition + 1))).ToList ();
 		}
 		return SetText (StringExtensions.ToString (newText));
@@ -226,20 +241,24 @@ public class DateField : TextField {
 			return false;
 		}
 
-		string [] vals = text.Split (sepChar);
-		string [] frm = format.Split (sepChar);
-		bool isValidDate = true;
-		int idx = GetFormatIndex (frm, "y");
-		int year = Int32.Parse (vals [idx]);
+		text = NormalizeFormat (text);
+		string [] vals = text.Split (_sepChar);
+		string [] frm = _format.Split (_sepChar);
+		int year;
 		int month;
 		int day;
+		int idx = GetFormatIndex (frm, "y");
+		if (Int32.Parse (vals [idx]) < 1) {
+			year = 1;
+			vals [idx] = "1";
+		} else {
+			year = Int32.Parse (vals [idx]);
+		}
 		idx = GetFormatIndex (frm, "M");
 		if (Int32.Parse (vals [idx]) < 1) {
-			isValidDate = false;
 			month = 1;
 			vals [idx] = "1";
 		} else if (Int32.Parse (vals [idx]) > 12) {
-			isValidDate = false;
 			month = 12;
 			vals [idx] = "12";
 		} else {
@@ -247,11 +266,9 @@ public class DateField : TextField {
 		}
 		idx = GetFormatIndex (frm, "d");
 		if (Int32.Parse (vals [idx]) < 1) {
-			isValidDate = false;
 			day = 1;
 			vals [idx] = "1";
 		} else if (Int32.Parse (vals [idx]) > 31) {
-			isValidDate = false;
 			day = DateTime.DaysInMonth (year, month);
 			vals [idx] = day.ToString ();
 		} else {
@@ -259,14 +276,36 @@ public class DateField : TextField {
 		}
 		string d = GetDate (month, day, year, frm);
 
-		if (!DateTime.TryParseExact (d, format, CultureInfo.CurrentCulture, DateTimeStyles.None, out var result) ||
-		!isValidDate) {
+		if (!DateTime.TryParseExact (d, _format, CultureInfo.CurrentCulture, DateTimeStyles.None, out var result)) {
 			return false;
 		}
 		Date = result;
 		return true;
 	}
 
+	string NormalizeFormat (string text, string fmt = null, string sepChar = null)
+	{
+		if (string.IsNullOrEmpty (fmt)) {
+			fmt = _format;
+		}
+		if (string.IsNullOrEmpty (sepChar)) {
+			sepChar = _sepChar;
+		}
+		if (fmt.Length != text.Length) {
+			return text;
+		}
+
+		var fmtText = text.ToCharArray ();
+		for (int i = 0; i < text.Length; i++) {
+			var c = fmt [i];
+			if (c.ToString () == sepChar && text [i].ToString () != sepChar) {
+				fmtText [i] = c;
+			}
+		}
+
+		return new string (fmtText);
+	}
+
 	string GetDate (int month, int day, int year, string [] fm)
 	{
 		string date = " ";
@@ -276,26 +315,25 @@ public class DateField : TextField {
 			} else if (fm [i].Contains ("d")) {
 				date += $"{day,2:00}";
 			} else {
-				if (!isShort && year.ToString ().Length == 2) {
-					string y = DateTime.Now.Year.ToString ();
-					date += y.Substring (0, 2) + year.ToString ();
-				} else if (isShort && year.ToString ().Length == 4) {
+				if (_isShort && year.ToString ().Length == 4) {
 					date += $"{year.ToString ().Substring (2, 2)}";
-				} else {
+				} else if (_isShort) {
 					date += $"{year,2:00}";
+				} else {
+					date += $"{year,4:0000}";
 				}
 			}
 			if (i < 2) {
-				date += $"{sepChar}";
+				date += $"{_sepChar}";
 			}
 		}
 		return date;
 	}
 
-	string GetDate (string text)
+	string GetInvarianteDate (string text, bool isShort)
 	{
-		string [] vals = text.Split (sepChar);
-		string [] frm = format.Split (sepChar);
+		string [] vals = text.Split (_sepChar);
+		string [] frm = (isShort ? $"MM{_sepChar}dd{_sepChar}yy" : GetInvarianteFormat ()).Split (_sepChar);
 		string [] date = { null, null, null };
 
 		for (int i = 0; i < frm.Length; i++) {
@@ -304,16 +342,25 @@ public class DateField : TextField {
 			} else if (frm [i].Contains ("d")) {
 				date [1] = vals [i].Trim ();
 			} else {
-				string year = vals [i].Trim ();
-				if (year.GetRuneCount () == 2) {
-					string y = DateTime.Now.Year.ToString ();
-					date [2] = y.Substring (0, 2) + year.ToString ();
+				string yearString;
+				if (isShort && vals [i].Length > 2) {
+					yearString = vals [i].Substring (0, 2);
+				} else if (!isShort && vals [i].Length > 4) {
+					yearString = vals [i].Substring (0, 4);
+				} else {
+					yearString = vals [i].Trim ();
+				}
+				var year = int.Parse (yearString);
+				if (isShort && year.ToString ().Length == 4) {
+					date [2] = year.ToString ().Substring (2, 2);
+				} else if (isShort) {
+					date [2] = year.ToString ();
 				} else {
-					date [2] = vals [i].Trim ();
+					date [2] = $"{year,4:0000}";
 				}
 			}
 		}
-		return date [0] + sepChar + date [1] + sepChar + date [2];
+		return $"{date [0]}{_sepChar}{date [1]}{_sepChar}{date [2]}";
 	}
 
 	int GetFormatIndex (string [] fm, string t)
@@ -330,45 +377,50 @@ public class DateField : TextField {
 
 	void IncCursorPosition ()
 	{
-		if (CursorPosition == fieldLen) {
+		if (CursorPosition >= _fieldLen) {
+			CursorPosition = _fieldLen;
 			return;
 		}
-		if (Text [++CursorPosition] == sepChar.ToCharArray () [0]) {
+		if (Text [++CursorPosition] == _sepChar.ToCharArray () [0]) {
 			CursorPosition++;
 		}
 	}
 
 	void DecCursorPosition ()
 	{
-		if (CursorPosition == 1) {
+		if (CursorPosition <= 1) {
+			CursorPosition = 1;
 			return;
 		}
-		if (Text [--CursorPosition] == sepChar.ToCharArray () [0]) {
+		if (Text [--CursorPosition] == _sepChar.ToCharArray () [0]) {
 			CursorPosition--;
 		}
 	}
 
 	void AdjCursorPosition ()
 	{
-		if (Text [CursorPosition] == sepChar.ToCharArray () [0]) {
+		if (Text [CursorPosition] == _sepChar.ToCharArray () [0]) {
 			CursorPosition++;
 		}
 	}
 
 	bool MoveRight ()
 	{
+		ClearAllSelection ();
 		IncCursorPosition ();
 		return true;
 	}
 
 	new bool MoveEnd ()
 	{
-		CursorPosition = fieldLen;
+		ClearAllSelection ();
+		CursorPosition = _fieldLen;
 		return true;
 	}
 
 	bool MoveLeft ()
 	{
+		ClearAllSelection ();
 		DecCursorPosition ();
 		return true;
 	}
@@ -376,6 +428,7 @@ public class DateField : TextField {
 	bool MoveHome ()
 	{
 		// Home, C-A
+		ClearAllSelection ();
 		CursorPosition = 1;
 		return true;
 	}
@@ -387,6 +440,7 @@ public class DateField : TextField {
 			return;
 		}
 
+		ClearAllSelection ();
 		SetText ((Rune)'0');
 		DecCursorPosition ();
 		return;
@@ -399,6 +453,7 @@ public class DateField : TextField {
 			return;
 		}
 
+		ClearAllSelection ();
 		SetText ((Rune)'0');
 		return;
 	}
@@ -414,8 +469,8 @@ public class DateField : TextField {
 		}
 
 		int point = ev.X;
-		if (point > fieldLen) {
-			point = fieldLen;
+		if (point > _fieldLen) {
+			point = _fieldLen;
 		}
 		if (point < 1) {
 			point = 1;

+ 1151 - 1187
Terminal.Gui/Views/FileDialog.cs

@@ -1,1536 +1,1500 @@
 using System;
 using System.Collections.Generic;
-using System.Data;
 using System.IO;
 using System.IO.Abstractions;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Threading;
 using System.Threading.Tasks;
-using System.Text;
 using Terminal.Gui.Resources;
-using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+/// Modal dialog for selecting files/directories. Has auto-complete and expandable
+/// navigation pane (Recent, Root drives etc).
+/// </summary>
+public class FileDialog : Dialog {
 	/// <summary>
-	/// Modal dialog for selecting files/directories. Has auto-complete and expandable
-	/// navigation pane (Recent, Root drives etc).
+	/// Gets the Path separators for the operating system
 	/// </summary>
-	public partial class FileDialog : Dialog {
+	internal static char [] Separators = {
+		System.IO.Path.AltDirectorySeparatorChar,
+		System.IO.Path.DirectorySeparatorChar
+	};
 
-		/// <summary>
-		/// Gets settings for controlling how visual elements behave.  Style changes should
-		/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
-		/// first time.
-		/// </summary>
-		public FileDialogStyle Style { get; }
+	/// <summary>
+	/// Characters to prevent entry into <see cref="tbPath"/>. Note that this is not using
+	/// <see cref="System.IO.Path.GetInvalidFileNameChars"/> because we do want to allow directory
+	/// separators, arrow keys etc.
+	/// </summary>
+	static readonly char [] badChars = {
+		'"', '<', '>', '|', '*', '?'
+	};
 
-		/// <summary>
-		/// The maximum number of results that will be collected
-		/// when searching before stopping.
-		/// </summary>
-		/// <remarks>
-		/// This prevents performance issues e.g. when searching
-		/// root of file system for a common letter (e.g. 'e').
-		/// </remarks>
-		[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
-		public static int MaxSearchResults { get; set; } = 10000;
+	Dictionary<IDirectoryInfo, string> _treeRoots = new ();
+	MenuBarItem allowedTypeMenu;
+	MenuBar allowedTypeMenuBar;
+	MenuItem [] allowedTypeMenuItems;
+	readonly Button btnBack;
+	readonly Button btnCancel;
+	readonly Button btnForward;
+	readonly Button btnOk;
+	readonly Button btnToggleSplitterCollapse;
+	readonly Button btnUp;
 
-		/// <summary>
-		/// True if the file/folder must exist already to be selected.
-		/// This prevents user from entering the name of something that
-		/// doesn't exist. Defaults to false.
-		/// </summary>
-		public bool MustExist { get; set; }
+	int currentSortColumn;
 
-		/// <summary>
-		/// Gets the Path separators for the operating system
-		/// </summary>
-		internal static char [] Separators = new []
-		{
-			System.IO.Path.AltDirectorySeparatorChar,
-			System.IO.Path.DirectorySeparatorChar,
-		};
+	bool currentSortIsAsc = true;
 
-		/// <summary>
-		/// Characters to prevent entry into <see cref="tbPath"/>. Note that this is not using
-		/// <see cref="System.IO.Path.GetInvalidFileNameChars"/> because we do want to allow directory
-		/// separators, arrow keys etc.
-		/// </summary>
-		private static char [] badChars = new []
-		{
-			'"','<','>','|','*','?',
-		};
+	bool disposed;
+	string feedback;
+	readonly IFileSystem fileSystem;
 
-		/// <summary>
-		/// The UI selected <see cref="IAllowedType"/> from combo box. May be null.
-		/// </summary>
-		public IAllowedType CurrentFilter { get; private set; }
+	readonly FileDialogHistory history;
+	bool loaded;
 
-		private bool pushingState = false;
-		private bool loaded = false;
+	/// <summary>
+	/// Locking object for ensuring only a single <see cref="SearchState"/> executes at once.
+	/// </summary>
+	internal object onlyOneSearchLock = new ();
 
-		/// <summary>
-		/// Gets the currently open directory and known children presented in the dialog.
-		/// </summary>
-		internal FileDialogState State { get; private set; }
+	bool pushingState;
+	readonly SpinnerView spinnerView;
+	readonly TileView splitContainer;
 
-		/// <summary>
-		/// Locking object for ensuring only a single <see cref="SearchState"/> executes at once.
-		/// </summary>
-		internal object onlyOneSearchLock = new object ();
-
-		private bool disposed = false;
-		private IFileSystem fileSystem;
-		private TextField tbPath;
-
-		private FileDialogHistory history;
-
-		private TableView tableView;
-		private TreeView<IFileSystemInfo> treeView;
-		private TileView splitContainer;
-		private Button btnOk;
-		private Button btnCancel;
-		private Button btnToggleSplitterCollapse;
-		private Button btnForward;
-		private Button btnBack;
-		private Button btnUp;
-		private string feedback;
-		private TextField tbFind;
-		private SpinnerView spinnerView;
-		private MenuBar allowedTypeMenuBar;
-		private MenuBarItem allowedTypeMenu;
-		private MenuItem [] allowedTypeMenuItems;
-
-		private int currentSortColumn;
-
-		private bool currentSortIsAsc = true;
-		private Dictionary<IDirectoryInfo, string> _treeRoots = new Dictionary<IDirectoryInfo, string> ();
+	readonly TableView tableView;
+	readonly TextField tbFind;
+	readonly TextField tbPath;
+	readonly TreeView<IFileSystemInfo> treeView;
 
-		/// <summary>
-		/// Event fired when user attempts to confirm a selection (or multi selection).
-		/// Allows you to cancel the selection or undertake alternative behavior e.g.
-		/// open a dialog "File already exists, Overwrite? yes/no".
-		/// </summary>
-		public event EventHandler<FilesSelectedEventArgs> FilesSelected;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="FileDialog"/> class.
+	/// </summary>
+	public FileDialog () : this (new FileSystem ()) { }
 
-		/// <summary>
-		/// Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts
-		/// to delete a selected file(s).  Set to null to prevent deleting.
-		/// </summary>
-		/// <remarks>Ensure you use a try/catch block with appropriate
-		/// error handling (e.g. showing a <see cref="MessageBox"/></remarks>
-		public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations ();
+	/// <summary>
+	/// Initializes a new instance of the <see cref="FileDialog"/> class with
+	/// a custom <see cref="IFileSystem"/>.
+	/// </summary>
+	/// <remarks>This overload is mainly useful for testing.</remarks>
+	public FileDialog (IFileSystem fileSystem)
+	{
+		this.fileSystem = fileSystem;
+		Style = new FileDialogStyle (fileSystem);
+
+		btnOk = new Button (Style.OkButtonText) {
+			Y = Pos.AnchorEnd (1),
+			X = Pos.Function (CalculateOkButtonPosX),
+			IsDefault = true
+		};
+		btnOk.Clicked += (s, e) => Accept (true);
+		btnOk.KeyDown += (s, k) => {
+			NavigateIf (k, KeyCode.CursorLeft, btnCancel);
+			NavigateIf (k, KeyCode.CursorUp,   tableView);
+		};
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileDialog"/> class.
-		/// </summary>
-		public FileDialog () : this (new FileSystem ())
-		{
+		btnCancel = new Button (Strings.btnCancel) {
+			Y = Pos.AnchorEnd (1),
+			X = Pos.Right (btnOk) + 1
+		};
+		btnCancel.KeyDown += (s, k) => {
+			NavigateIf (k, KeyCode.CursorLeft,  btnToggleSplitterCollapse);
+			NavigateIf (k, KeyCode.CursorUp,    tableView);
+			NavigateIf (k, KeyCode.CursorRight, btnOk);
+		};
+		btnCancel.Clicked += (s, e) => {
+			Application.RequestStop ();
+		};
 
-		}
+		btnUp = new Button { X = 0, Y = 1, NoPadding = true };
+		btnUp.Text = GetUpButtonText ();
+		btnUp.Clicked += (s, e) => history.Up ();
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="FileDialog"/> class with
-		/// a custom <see cref="IFileSystem"/>.
-		/// </summary>
-		/// <remarks>This overload is mainly useful for testing.</remarks>
-		public FileDialog (IFileSystem fileSystem)
-		{
-			this.fileSystem = fileSystem;
-			Style = new FileDialogStyle (fileSystem);
+		btnBack = new Button { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true };
+		btnBack.Text = GetBackButtonText ();
+		btnBack.Clicked += (s, e) => history.Back ();
 
-			this.btnOk = new Button (Style.OkButtonText) {
-				Y = Pos.AnchorEnd (1),
-				X = Pos.Function (CalculateOkButtonPosX),
-				IsDefault = true
-			};
-			this.btnOk.Clicked += (s, e) => this.Accept (true);
-			this.btnOk.KeyDown += (s, k) => {
-				this.NavigateIf (k, KeyCode.CursorLeft, this.btnCancel);
-				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
-			};
+		btnForward = new Button { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true };
+		btnForward.Text = GetForwardButtonText ();
+		btnForward.Clicked += (s, e) => history.Forward ();
 
-			this.btnCancel = new Button (Strings.btnCancel) {
-				Y = Pos.AnchorEnd (1),
-				X = Pos.Right (btnOk) + 1
-			};
-			this.btnCancel.KeyDown += (s, k) => {
-				this.NavigateIf (k, KeyCode.CursorLeft, this.btnToggleSplitterCollapse);
-				this.NavigateIf (k, KeyCode.CursorUp, this.tableView);
-				this.NavigateIf (k, KeyCode.CursorRight, this.btnOk);
-			};
-			this.btnCancel.Clicked += (s, e) => {
-				Application.RequestStop ();
-			};
+		tbPath = new TextField {
+			Width = Dim.Fill (),
+			CaptionColor = new Color (Color.Black)
+		};
+		tbPath.KeyDown += (s, k) => {
 
-			this.btnUp = new Button () { X = 0, Y = 1, NoPadding = true };
-			btnUp.Text = GetUpButtonText ();
-			this.btnUp.Clicked += (s, e) => this.history.Up ();
+			ClearFeedback ();
 
-			this.btnBack = new Button () { X = Pos.Right (btnUp) + 1, Y = 1, NoPadding = true };
-			btnBack.Text = GetBackButtonText ();
-			this.btnBack.Clicked += (s, e) => this.history.Back ();
+			AcceptIf (k, KeyCode.Enter);
 
-			this.btnForward = new Button () { X = Pos.Right (btnBack) + 1, Y = 1, NoPadding = true };
-			btnForward.Text = GetForwardButtonText ();
-			this.btnForward.Clicked += (s, e) => this.history.Forward ();
+			SuppressIfBadChar (k);
+		};
 
-			this.tbPath = new TextField {
-				Width = Dim.Fill (0),
-				CaptionColor = new Color (Color.Black)
-			};
-			this.tbPath.KeyDown += (s, k) => {
+		tbPath.Autocomplete = new AppendAutocomplete (tbPath);
+		tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
 
-				ClearFeedback ();
+		splitContainer = new TileView {
+			X = 0,
+			Y = 2,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1)
+		};
 
-				this.AcceptIf (k, KeyCode.Enter);
+		Initialized += (s, e) => {
+			splitContainer.SetSplitterPos (0, 30);
+			splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
+		};
+		//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
 
-				this.SuppressIfBadChar (k);
-			};
+		tableView = new TableView {
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+			FullRowSelect = true,
+			CollectionNavigator = new FileDialogCollectionNavigator (this)
+		};
+		tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
+		tableView.MouseClick += OnTableViewMouseClick;
+		tableView.Style.InvertSelectedCellFirstCharacter = true;
+		Style.TableStyle = tableView.Style;
 
-			tbPath.Autocomplete = new AppendAutocomplete (tbPath);
-			tbPath.Autocomplete.SuggestionGenerator = new FilepathSuggestionGenerator ();
+		var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
+		nameStyle.MinWidth = 10;
+		nameStyle.ColorGetter = ColorGetter;
 
-			this.splitContainer = new TileView () {
-				X = 0,
-				Y = 2,
-				Width = Dim.Fill (0),
-				Height = Dim.Fill (1),
-			};
-			
-			Initialized += (s, e) => {
-				this.splitContainer.SetSplitterPos (0, 30);
-				this.splitContainer.Tiles.ElementAt (0).ContentView.Visible = false;
-			};
-			//			this.splitContainer.Border.BorderStyle = BorderStyle.None;
+		var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
+		sizeStyle.MinWidth = 10;
+		sizeStyle.ColorGetter = ColorGetter;
 
-			this.tableView = new TableView {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-				FullRowSelect = true,
-				CollectionNavigator = new FileDialogCollectionNavigator (this)
-			};
-			this.tableView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
-			this.tableView.MouseClick += OnTableViewMouseClick;
-			tableView.Style.InvertSelectedCellFirstCharacter = true;
-			Style.TableStyle = tableView.Style;
-
-			var nameStyle = Style.TableStyle.GetOrCreateColumnStyle (0);
-			nameStyle.MinWidth = 10;
-			nameStyle.ColorGetter = this.ColorGetter;
-
-			var sizeStyle = Style.TableStyle.GetOrCreateColumnStyle (1);
-			sizeStyle.MinWidth = 10;
-			sizeStyle.ColorGetter = this.ColorGetter;
-
-			var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
-			dateModifiedStyle.MinWidth = 30;
-			dateModifiedStyle.ColorGetter = this.ColorGetter;
-
-			var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
-			typeStyle.MinWidth = 6;
-			typeStyle.ColorGetter = this.ColorGetter;
-
-			this.tableView.KeyDown += (s, k) => {
-				if (this.tableView.SelectedRow <= 0) {
-					this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
-				}
-				if (this.tableView.SelectedRow == this.tableView.Table.Rows - 1) {
-					this.NavigateIf (k, KeyCode.CursorDown, this.btnToggleSplitterCollapse);
-				}
+		var dateModifiedStyle = Style.TableStyle.GetOrCreateColumnStyle (2);
+		dateModifiedStyle.MinWidth = 30;
+		dateModifiedStyle.ColorGetter = ColorGetter;
 
-				if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
-					this.NavigateIf (k, KeyCode.CursorLeft, this.treeView);
-				}
+		var typeStyle = Style.TableStyle.GetOrCreateColumnStyle (3);
+		typeStyle.MinWidth = 6;
+		typeStyle.ColorGetter = ColorGetter;
 
-				if (k.Handled) {
-					return;
-				}
-			};
+		tableView.KeyDown += (s, k) => {
+			if (tableView.SelectedRow <= 0) {
+				NavigateIf (k, KeyCode.CursorUp, tbPath);
+			}
+			if (tableView.SelectedRow == tableView.Table.Rows - 1) {
+				NavigateIf (k, KeyCode.CursorDown, btnToggleSplitterCollapse);
+			}
 
-			this.treeView = new TreeView<IFileSystemInfo> () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill (),
-			};
+			if (splitContainer.Tiles.First ().ContentView.Visible && tableView.SelectedColumn == 0) {
+				NavigateIf (k, KeyCode.CursorLeft, treeView);
+			}
 
-			var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
-			this.treeView.TreeBuilder = fileDialogTreeBuilder;
-			this.treeView.AspectGetter = this.AspectGetter;
-			this.Style.TreeStyle = treeView.Style;
+			if (k.Handled) { }
+		};
 
-			this.treeView.SelectionChanged += this.TreeView_SelectionChanged;
+		treeView = new TreeView<IFileSystemInfo> {
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
 
-			this.splitContainer.Tiles.ElementAt (0).ContentView.Add (this.treeView);
-			this.splitContainer.Tiles.ElementAt (1).ContentView.Add (this.tableView);
+		var fileDialogTreeBuilder = new FileSystemTreeBuilder ();
+		treeView.TreeBuilder = fileDialogTreeBuilder;
+		treeView.AspectGetter = AspectGetter;
+		Style.TreeStyle = treeView.Style;
 
-			this.btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) {
-				Y = Pos.AnchorEnd (1),
-			};
-			this.btnToggleSplitterCollapse.Clicked += (s, e) => {
-				var tile = this.splitContainer.Tiles.ElementAt (0);
+		treeView.SelectionChanged += TreeView_SelectionChanged;
 
-				var newState = !tile.ContentView.Visible;
-				tile.ContentView.Visible = newState;
-				this.btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
-				this.LayoutSubviews ();
-			};
+		splitContainer.Tiles.ElementAt (0).ContentView.Add (treeView);
+		splitContainer.Tiles.ElementAt (1).ContentView.Add (tableView);
 
-			tbFind = new TextField {
-				X = Pos.Right (this.btnToggleSplitterCollapse) + 1,
-				CaptionColor = new Color (Color.Black),
-				Width = 30,
-				Y = Pos.AnchorEnd (1),
-				HotKey = KeyCode.F | KeyCode.AltMask
-			};
-			spinnerView = new SpinnerView () {
-				X = Pos.Right (tbFind) + 1,
-				Y = Pos.AnchorEnd (1),
-				Visible = false,
-			};
+		btnToggleSplitterCollapse = new Button (GetToggleSplitterText (false)) {
+			Y = Pos.AnchorEnd (1)
+		};
+		btnToggleSplitterCollapse.Clicked += (s, e) => {
+			var tile = splitContainer.Tiles.ElementAt (0);
 
-			tbFind.TextChanged += (s, o) => RestartSearch ();
-			tbFind.KeyDown += (s, o) => {
-				if (o.KeyCode == KeyCode.Enter) {
-					RestartSearch ();
-					o.Handled = true;
-				}
+			var newState = !tile.ContentView.Visible;
+			tile.ContentView.Visible = newState;
+			btnToggleSplitterCollapse.Text = GetToggleSplitterText (newState);
+			LayoutSubviews ();
+		};
 
-				if (o.KeyCode == KeyCode.Esc) {
-					if (CancelSearch ()) {
-						o.Handled = true;
-					}
-				}
-				if (tbFind.CursorIsAtEnd ()) {
-					NavigateIf (o, KeyCode.CursorRight, btnCancel);
-				}
-				if (tbFind.CursorIsAtStart ()) {
-					NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+		tbFind = new TextField {
+			X = Pos.Right (btnToggleSplitterCollapse) + 1,
+			CaptionColor = new Color (Color.Black),
+			Width = 30,
+			Y = Pos.AnchorEnd (1),
+			HotKey = KeyCode.F | KeyCode.AltMask
+		};
+		spinnerView = new SpinnerView {
+			X = Pos.Right (tbFind) + 1,
+			Y = Pos.AnchorEnd (1),
+			Visible = false
+		};
+
+		tbFind.TextChanged += (s, o) => RestartSearch ();
+		tbFind.KeyDown += (s, o) => {
+			if (o.KeyCode == KeyCode.Enter) {
+				RestartSearch ();
+				o.Handled = true;
+			}
+
+			if (o.KeyCode == KeyCode.Esc) {
+				if (CancelSearch ()) {
+					o.Handled = true;
 				}
-			};
+			}
+			if (tbFind.CursorIsAtEnd ()) {
+				NavigateIf (o, KeyCode.CursorRight, btnCancel);
+			}
+			if (tbFind.CursorIsAtStart ()) {
+				NavigateIf (o, KeyCode.CursorLeft, btnToggleSplitterCollapse);
+			}
+		};
 
-			this.tableView.Style.ShowHorizontalHeaderOverline = true;
-			this.tableView.Style.ShowVerticalCellLines = true;
-			this.tableView.Style.ShowVerticalHeaderLines = true;
-			this.tableView.Style.AlwaysShowHeaders = true;
-			this.tableView.Style.ShowHorizontalHeaderUnderline = true;
-			this.tableView.Style.ShowHorizontalScrollIndicators = true;
+		tableView.Style.ShowHorizontalHeaderOverline = true;
+		tableView.Style.ShowVerticalCellLines = true;
+		tableView.Style.ShowVerticalHeaderLines = true;
+		tableView.Style.AlwaysShowHeaders = true;
+		tableView.Style.ShowHorizontalHeaderUnderline = true;
+		tableView.Style.ShowHorizontalScrollIndicators = true;
 
-			this.history = new FileDialogHistory (this);
+		history = new FileDialogHistory (this);
 
-			this.tbPath.TextChanged += (s, e) => this.PathChanged ();
+		tbPath.TextChanged += (s, e) => PathChanged ();
 
-			this.tableView.CellActivated += this.CellActivate;
-			this.tableView.KeyUp += (s, k) => k.Handled = this.TableView_KeyUp (k);
-			this.tableView.SelectedCellChanged += this.TableView_SelectedCellChanged;
+		tableView.CellActivated += CellActivate;
+		tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
+		tableView.SelectedCellChanged += TableView_SelectedCellChanged;
 
-			this.tableView.KeyBindings.Add (KeyCode.Home, Command.TopHome);
-			this.tableView.KeyBindings.Add (KeyCode.End, Command.BottomEnd);
-			this.tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
-			this.tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask, Command.BottomEndExtend);
+		tableView.KeyBindings.Add (KeyCode.Home,                     Command.TopHome);
+		tableView.KeyBindings.Add (KeyCode.End,                      Command.BottomEnd);
+		tableView.KeyBindings.Add (KeyCode.Home | KeyCode.ShiftMask, Command.TopHomeExtend);
+		tableView.KeyBindings.Add (KeyCode.End | KeyCode.ShiftMask,  Command.BottomEndExtend);
 
-			this.treeView.KeyDown += (s, k) => {
+		treeView.KeyDown += (s, k) => {
 
-				var selected = treeView.SelectedObject;
-				if (selected != null) {
-					if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
-						this.NavigateIf (k, KeyCode.CursorRight, this.tableView);
-					} else
-					if (treeView.GetObjectRow (selected) == 0) {
-						this.NavigateIf (k, KeyCode.CursorUp, this.tbPath);
-					}
+			var selected = treeView.SelectedObject;
+			if (selected != null) {
+				if (!treeView.CanExpand (selected) || treeView.IsExpanded (selected)) {
+					NavigateIf (k, KeyCode.CursorRight, tableView);
+				} else if (treeView.GetObjectRow (selected) == 0) {
+					NavigateIf (k, KeyCode.CursorUp, tbPath);
 				}
+			}
 
-				if (k.Handled) {
-					return;
-				}
+			if (k.Handled) {
+				return;
+			}
 
-				k.Handled = this.TreeView_KeyDown (k);
+			k.Handled = TreeView_KeyDown (k);
 
-			};
+		};
 
-			this.AllowsMultipleSelection = false;
+		AllowsMultipleSelection = false;
+
+		UpdateNavigationVisibility ();
+
+		// Determines tab order
+		Add (btnToggleSplitterCollapse);
+		Add (tbFind);
+		Add (spinnerView);
+		Add (btnOk);
+		Add (btnCancel);
+		Add (btnUp);
+		Add (btnBack);
+		Add (btnForward);
+		Add (tbPath);
+		Add (splitContainer);
+	}
 
-			this.UpdateNavigationVisibility ();
+	/// <summary>
+	/// Gets settings for controlling how visual elements behave.  Style changes should
+	/// be made before the <see cref="Dialog"/> is loaded and shown to the user for the
+	/// first time.
+	/// </summary>
+	public FileDialogStyle Style { get; }
 
-			// Determines tab order
-			this.Add (this.btnToggleSplitterCollapse);
-			this.Add (this.tbFind);
-			this.Add (this.spinnerView);
-			this.Add (this.btnOk);
-			this.Add (this.btnCancel);
-			this.Add (this.btnUp);
-			this.Add (this.btnBack);
-			this.Add (this.btnForward);
-			this.Add (this.tbPath);
-			this.Add (this.splitContainer);
-		}
+	/// <summary>
+	/// The maximum number of results that will be collected
+	/// when searching before stopping.
+	/// </summary>
+	/// <remarks>
+	/// This prevents performance issues e.g. when searching
+	/// root of file system for a common letter (e.g. 'e').
+	/// </remarks>
+	[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+	public static int MaxSearchResults { get; set; } = 10000;
 
-		private int CalculateOkButtonPosX ()
-		{
-			return this.Bounds.Width
-				- btnOk.Bounds.Width
-				- btnCancel.Bounds.Width
-				- 1
-				// TODO: Fiddle factor, seems the Bounds are wrong for someone
-				- 2;
-		}
+	/// <summary>
+	/// True if the file/folder must exist already to be selected.
+	/// This prevents user from entering the name of something that
+	/// doesn't exist. Defaults to false.
+	/// </summary>
+	public bool MustExist { get; set; }
 
-		private string AspectGetter (object o)
-		{
-			var fsi = (IFileSystemInfo)o;
+	/// <summary>
+	/// The UI selected <see cref="IAllowedType"/> from combo box. May be null.
+	/// </summary>
+	public IAllowedType CurrentFilter { get; private set; }
 
-			if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) {
+	/// <summary>
+	/// Gets the currently open directory and known children presented in the dialog.
+	/// </summary>
+	internal FileDialogState State { get; private set; }
 
-				// Directory has a special name e.g. 'Pictures'
-				return _treeRoots [dir];
-			}
+	/// <summary>
+	/// Gets or sets behavior of the <see cref="FileDialog"/> when the user attempts
+	/// to delete a selected file(s).  Set to null to prevent deleting.
+	/// </summary>
+	/// <remarks>
+	/// Ensure you use a try/catch block with appropriate
+	/// error handling (e.g. showing a <see cref="MessageBox"/>
+	/// </remarks>
+	public IFileOperations FileOperationsHandler { get; set; } = new DefaultFileOperations ();
+
+	/// <summary>
+	/// Gets or Sets which <see cref="System.IO.FileSystemInfo"/> type can be selected.
+	/// Defaults to <see cref="OpenMode.Mixed"/> (i.e. <see cref="DirectoryInfo"/> or
+	/// <see cref="FileInfo"/>).
+	/// </summary>
+	public OpenMode OpenMode { get; set; } = OpenMode.Mixed;
 
-			return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
+	/// <summary>
+	/// Gets or Sets the selected path in the dialog. This is the result that should
+	/// be used if <see cref="AllowsMultipleSelection"/> is off and <see cref="Canceled"/>
+	/// is true.
+	/// </summary>
+	public string Path {
+		get => tbPath.Text;
+		set {
+			tbPath.Text = value;
+			tbPath.MoveEnd ();
 		}
+	}
 
-		private void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
-		{
-			var clickedCell = this.tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
+	/// <summary>
+	/// Defines how the dialog matches files/folders when using the search
+	/// box. Provide a custom implementation if you want to tailor how matching
+	/// is performed.
+	/// </summary>
+	public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher ();
 
-			if (clickedCol != null) {
-				if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
+	/// <summary>
+	/// Gets or Sets a value indicating whether to allow selecting
+	/// multiple existing files/directories. Defaults to false.
+	/// </summary>
+	public bool AllowsMultipleSelection {
+		get => tableView.MultiSelect;
+		set => tableView.MultiSelect = value;
+	}
 
-					// left click in a header
-					this.SortColumn (clickedCol.Value);
-				} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	/// <summary>
+	/// Gets or Sets a collection of file types that the user can/must select. Only applies
+	/// when <see cref="OpenMode"/> is <see cref="OpenMode.File"/> or <see cref="OpenMode.Mixed"/>.
+	/// </summary>
+	/// <remarks>
+	/// <see cref="AllowedTypeAny"/> adds the option to select any type (*.*). If this
+	/// collection is empty then any type is supported and no Types drop-down is shown.
+	/// </remarks>
+	public List<IAllowedType> AllowedTypes { get; set; } = new ();
 
-					// right click in a header
-					this.ShowHeaderContextMenu (clickedCol.Value, e);
-				}
-			} else {
-				if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+	/// <summary>
+	/// Gets a value indicating whether the <see cref="FileDialog"/> was closed
+	/// without confirming a selection.
+	/// </summary>
+	public bool Canceled { get; private set; } = true;
 
-					// right click in rest of table
-					this.ShowCellContextMenu (clickedCell, e);
-				}
-			}
-		}
+	/// <summary>
+	/// Gets all files/directories selected or an empty collection
+	/// <see cref="AllowsMultipleSelection"/> is <see langword="false"/> or <see cref="Canceled"/>.
+	/// </summary>
+	/// <remarks>If selecting only a single file/directory then you should use <see cref="Path"/> instead.</remarks>
+	public IReadOnlyList<string> MultiSelected { get; private set; }
+		= Enumerable.Empty<string> ().ToList ().AsReadOnly ();
 
-		private string GetForwardButtonText ()
-		{
-			return "-" + CM.Glyphs.RightArrow;
-		}
+	/// <summary>
+	/// Event fired when user attempts to confirm a selection (or multi selection).
+	/// Allows you to cancel the selection or undertake alternative behavior e.g.
+	/// open a dialog "File already exists, Overwrite? yes/no".
+	/// </summary>
+	public event EventHandler<FilesSelectedEventArgs> FilesSelected;
 
-		private string GetBackButtonText ()
-		{
-			return CM.Glyphs.LeftArrow + "-";
+	int CalculateOkButtonPosX ()
+	{
+		if (!IsInitialized || !btnOk.IsInitialized || !btnCancel.IsInitialized) {
+			return 0;
 		}
+		return Bounds.Width -
+		       btnOk.Bounds.Width -
+		       btnCancel.Bounds.Width -
+		       1
+		       // TODO: Fiddle factor, seems the Bounds are wrong for someone
+		       -
+		       2;
+	}
 
-		private string GetUpButtonText ()
-		{
-			return Style.UseUnicodeCharacters ? "◭" : "▲";
-		}
+	string AspectGetter (object o)
+	{
+		var fsi = (IFileSystemInfo)o;
 
-		private string GetToggleSplitterText (bool isExpanded)
-		{
-			return isExpanded ?
-				new string ((char)CM.Glyphs.LeftArrow.Value, 2) :
-				new string ((char)CM.Glyphs.RightArrow.Value, 2);
+		if (o is IDirectoryInfo dir && _treeRoots.ContainsKey (dir)) {
+
+			// Directory has a special name e.g. 'Pictures'
+			return _treeRoots [dir];
 		}
 
-		private void Delete ()
-		{
-			var toDelete = GetFocusedFiles ();
+		return (Style.IconProvider.GetIconWithOptionalSpace (fsi) + fsi.Name).Trim ();
+	}
 
-			if (toDelete != null && FileOperationsHandler.Delete (toDelete)) {
-				RefreshState ();
-			}
-		}
+	void OnTableViewMouseClick (object sender, MouseEventEventArgs e)
+	{
+		var clickedCell = tableView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
 
-		private void Rename ()
-		{
-			var toRename = GetFocusedFiles ();
+		if (clickedCol != null) {
+			if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) {
 
-			if (toRename?.Length == 1) {
-				var newNamed = FileOperationsHandler.Rename (this.fileSystem, toRename.Single ());
+				// left click in a header
+				SortColumn (clickedCol.Value);
+			} else if (e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
 
-				if (newNamed != null) {
-					RefreshState ();
-					RestoreSelection (newNamed);
-				}
+				// right click in a header
+				ShowHeaderContextMenu (clickedCol.Value, e);
 			}
-		}
-		private void New ()
-		{
-			if (State != null) {
-				var created = FileOperationsHandler.New (this.fileSystem, State.Directory);
-				if (created != null) {
-					RefreshState ();
-					RestoreSelection (created);
-				}
+		} else {
+			if (clickedCell != null && e.MouseEvent.Flags.HasFlag (MouseFlags.Button3Clicked)) {
+
+				// right click in rest of table
+				ShowCellContextMenu (clickedCell, e);
 			}
 		}
-		private IFileSystemInfo [] GetFocusedFiles ()
-		{
+	}
 
-			if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) {
-				return null;
-			}
+	string GetForwardButtonText () => "-" + Glyphs.RightArrow;
 
-			tableView.EnsureValidSelection ();
+	string GetBackButtonText () => Glyphs.LeftArrow + "-";
 
-			if (tableView.SelectedRow < 0) {
-				return null;
-			}
+	string GetUpButtonText () => Style.UseUnicodeCharacters ? "◭" : "▲";
 
-			return tableView.GetAllSelectedCells ()
-				.Select (c => c.Y)
-				.Distinct ()
-				.Select (RowToStats)
-				.Where (s => !s.IsParent)
-				.Select (d => d.FileSystemInfo)
-				.ToArray ();
-		}
+	string GetToggleSplitterText (bool isExpanded) => isExpanded ?
+		new string ((char)Glyphs.LeftArrow.Value,  2) :
+		new string ((char)Glyphs.RightArrow.Value, 2);
 
+	void Delete ()
+	{
+		var toDelete = GetFocusedFiles ();
 
-//		/// <inheritdoc/>
-//		public override bool OnHotKey (KeyEventArgs keyEvent)
-//		{
-//#if BROKE_IN_2927
-//			// BUGBUG: Ctrl-F is forward in a TextField. 
-//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
-//				return true;
-//			}
-//#endif
+		if (toDelete != null && FileOperationsHandler.Delete (toDelete)) {
+			RefreshState ();
+		}
+	}
 
-//			ClearFeedback ();
+	void Rename ()
+	{
+		var toRename = GetFocusedFiles ();
 
-//			if (allowedTypeMenuBar != null &&
-//				keyEvent.ConsoleDriverKey == Key.Tab &&
-//				allowedTypeMenuBar.IsMenuOpen) {
-//				allowedTypeMenuBar.CloseMenu (false, false, false);
-//			}
+		if (toRename?.Length == 1) {
+			var newNamed = FileOperationsHandler.Rename (fileSystem, toRename.Single ());
 
-//			return base.OnHotKey (keyEvent);
-//		}
-		private void RestartSearch ()
-		{
-			if (disposed || State?.Directory == null) {
-				return;
+			if (newNamed != null) {
+				RefreshState ();
+				RestoreSelection (newNamed);
 			}
+		}
+	}
 
-			if (State is SearchState oldSearch) {
-				oldSearch.Cancel ();
+	void New ()
+	{
+		if (State != null) {
+			var created = FileOperationsHandler.New (fileSystem, State.Directory);
+			if (created != null) {
+				RefreshState ();
+				RestoreSelection (created);
 			}
+		}
+	}
 
-			// user is clearing search terms
-			if (tbFind.Text == null || tbFind.Text.Length == 0) {
-
-				// Wait for search cancellation (if any) to finish
-				// then push the current dir state
-				lock (onlyOneSearchLock) {
-					PushState (new FileDialogState (State.Directory, this), false);
-				}
-				return;
-			}
+	IFileSystemInfo [] GetFocusedFiles ()
+	{
 
-			PushState (new SearchState (State?.Directory, this, tbFind.Text), true);
+		if (!tableView.HasFocus || !tableView.CanFocus || FileOperationsHandler == null) {
+			return null;
 		}
 
-		/// <inheritdoc/>
-		protected override void Dispose (bool disposing)
-		{
-			disposed = true;
-			base.Dispose (disposing);
+		tableView.EnsureValidSelection ();
 
-			CancelSearch ();
+		if (tableView.SelectedRow < 0) {
+			return null;
 		}
 
-		private bool CancelSearch ()
-		{
-			if (State is SearchState search) {
-				return search.Cancel ();
-			}
+		return tableView.GetAllSelectedCells ()
+				.Select (c => c.Y)
+				.Distinct ()
+				.Select (RowToStats)
+				.Where (s => !s.IsParent)
+				.Select (d => d.FileSystemInfo)
+				.ToArray ();
+	}
 
-			return false;
+
+	//		/// <inheritdoc/>
+	//		public override bool OnHotKey (KeyEventArgs keyEvent)
+	//		{
+	//#if BROKE_IN_2927
+	//			// BUGBUG: Ctrl-F is forward in a TextField. 
+	//			if (this.NavigateIf (keyEvent, Key.Alt | Key.F, this.tbFind)) {
+	//				return true;
+	//			}
+	//#endif
+
+	//			ClearFeedback ();
+
+	//			if (allowedTypeMenuBar != null &&
+	//				keyEvent.ConsoleDriverKey == Key.Tab &&
+	//				allowedTypeMenuBar.IsMenuOpen) {
+	//				allowedTypeMenuBar.CloseMenu (false, false, false);
+	//			}
+
+	//			return base.OnHotKey (keyEvent);
+	//		}
+	void RestartSearch ()
+	{
+		if (disposed || State?.Directory == null) {
+			return;
 		}
 
-		private void ClearFeedback ()
-		{
-			feedback = null;
+		if (State is SearchState oldSearch) {
+			oldSearch.Cancel ();
 		}
 
-		/// <summary>
-		/// Gets or Sets which <see cref="System.IO.FileSystemInfo"/> type can be selected.
-		/// Defaults to <see cref="OpenMode.Mixed"/> (i.e. <see cref="DirectoryInfo"/> or
-		/// <see cref="FileInfo"/>).
-		/// </summary>
-		public OpenMode OpenMode { get; set; } = OpenMode.Mixed;
+		// user is clearing search terms
+		if (tbFind.Text == null || tbFind.Text.Length == 0) {
 
-		/// <summary>
-		/// Gets or Sets the selected path in the dialog. This is the result that should
-		/// be used if <see cref="AllowsMultipleSelection"/> is off and <see cref="Canceled"/>
-		/// is true.
-		/// </summary>
-		public string Path {
-			get => this.tbPath.Text;
-			set {
-				this.tbPath.Text = value;
-				this.tbPath.MoveEnd ();
+			// Wait for search cancellation (if any) to finish
+			// then push the current dir state
+			lock (onlyOneSearchLock) {
+				PushState (new FileDialogState (State.Directory, this), false);
 			}
+			return;
 		}
 
-		/// <summary>
-		/// Defines how the dialog matches files/folders when using the search
-		/// box. Provide a custom implementation if you want to tailor how matching
-		/// is performed.
-		/// </summary>
-		public ISearchMatcher SearchMatcher { get; set; } = new DefaultSearchMatcher ();
+		PushState (new SearchState (State?.Directory, this, tbFind.Text), true);
+	}
 
-		/// <summary>
-		/// Gets or Sets a value indicating whether to allow selecting 
-		/// multiple existing files/directories. Defaults to false.
-		/// </summary>
-		public bool AllowsMultipleSelection {
-			get => this.tableView.MultiSelect;
-			set => this.tableView.MultiSelect = value;
+	/// <inheritdoc/>
+	protected override void Dispose (bool disposing)
+	{
+		disposed = true;
+		base.Dispose (disposing);
+
+		CancelSearch ();
+	}
+
+	bool CancelSearch ()
+	{
+		if (State is SearchState search) {
+			return search.Cancel ();
 		}
 
-		/// <summary>
-		/// Gets or Sets a collection of file types that the user can/must select. Only applies
-		/// when <see cref="OpenMode"/> is <see cref="OpenMode.File"/> or <see cref="OpenMode.Mixed"/>.
-		/// </summary>
-		/// <remarks><see cref="AllowedTypeAny"/> adds the option to select any type (*.*). If this
-		/// collection is empty then any type is supported and no Types drop-down is shown.</remarks> 
-		public List<IAllowedType> AllowedTypes { get; set; } = new List<IAllowedType> ();
+		return false;
+	}
 
-		/// <summary>
-		/// Gets a value indicating whether the <see cref="FileDialog"/> was closed
-		/// without confirming a selection.
-		/// </summary>
-		public bool Canceled { get; private set; } = true;
+	void ClearFeedback () => feedback = null;
 
-		/// <summary>
-		/// Gets all files/directories selected or an empty collection
-		/// <see cref="AllowsMultipleSelection"/> is <see langword="false"/> or <see cref="Canceled"/>.
-		/// </summary>
-		/// <remarks>If selecting only a single file/directory then you should use <see cref="Path"/> instead.</remarks>
-		public IReadOnlyList<string> MultiSelected { get; private set; }
-			= Enumerable.Empty<string> ().ToList ().AsReadOnly ();
+	/// <inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		base.OnDrawContent (contentArea);
 
-		/// <inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			base.OnDrawContent (contentArea);
+		if (!string.IsNullOrWhiteSpace (feedback)) {
+			var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
+			var feedbackPadLeft = (Bounds.Width - feedbackWidth) / 2 - 1;
 
-			if (!string.IsNullOrWhiteSpace (feedback)) {
-				var feedbackWidth = feedback.EnumerateRunes ().Sum (c => c.GetColumns ());
-				var feedbackPadLeft = ((Bounds.Width - feedbackWidth) / 2) - 1;
+			feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
+			feedbackPadLeft = Math.Max (0, feedbackPadLeft);
 
-				feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft);
-				feedbackPadLeft = Math.Max (0, feedbackPadLeft);
+			var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
+			feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
+			feedbackPadRight = Math.Max (0, feedbackPadRight);
 
-				var feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2);
-				feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight);
-				feedbackPadRight = Math.Max (0, feedbackPadRight);
+			Move (0, Bounds.Height / 2);
 
-				Move (0, Bounds.Height / 2);
+			Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background));
+			Driver.AddStr (new string (' ', feedbackPadLeft));
+			Driver.AddStr (feedback);
+			Driver.AddStr (new string (' ', feedbackPadRight));
+		}
+	}
 
-				Driver.SetAttribute (new Attribute (Color.Red, this.ColorScheme.Normal.Background));
-				Driver.AddStr (new string (' ', feedbackPadLeft));
-				Driver.AddStr (feedback);
-				Driver.AddStr (new string (' ', feedbackPadRight));
-			}
+	/// <inheritdoc/>
+	public override void OnLoaded ()
+	{
+		base.OnLoaded ();
+		if (loaded) {
+			return;
 		}
+		loaded = true;
 
-		/// <inheritdoc/>
-		public override void OnLoaded ()
-		{
-			base.OnLoaded ();
-			if (loaded) {
-				return;
-			}
-			loaded = true;
+		// May have been updated after instance was constructed
+		btnOk.Text = Style.OkButtonText;
+		btnCancel.Text = Style.CancelButtonText;
+		btnUp.Text = GetUpButtonText ();
+		btnBack.Text = GetBackButtonText ();
+		btnForward.Text = GetForwardButtonText ();
+		btnToggleSplitterCollapse.Text = GetToggleSplitterText (false);
 
-			// May have been updated after instance was constructed
-			this.btnOk.Text = Style.OkButtonText;
-			this.btnCancel.Text = Style.CancelButtonText;
-			this.btnUp.Text = this.GetUpButtonText ();
-			this.btnBack.Text = this.GetBackButtonText ();
-			this.btnForward.Text = this.GetForwardButtonText ();
-			this.btnToggleSplitterCollapse.Text = this.GetToggleSplitterText (false);
+		if (Style.FlipOkCancelButtonLayoutOrder) {
+			btnCancel.X = Pos.Function (CalculateOkButtonPosX);
+			btnOk.X = Pos.Right (btnCancel) + 1;
 
-			if (Style.FlipOkCancelButtonLayoutOrder) {
-				btnCancel.X = Pos.Function (this.CalculateOkButtonPosX);
-				btnOk.X = Pos.Right (btnCancel) + 1;
 
+			// Flip tab order too for consistency
+			var p1 = btnOk.TabIndex;
+			var p2 = btnCancel.TabIndex;
 
-				// Flip tab order too for consistency
-				var p1 = this.btnOk.TabIndex;
-				var p2 = this.btnCancel.TabIndex;
+			btnOk.TabIndex = p2;
+			btnCancel.TabIndex = p1;
+		}
 
-				this.btnOk.TabIndex = p2;
-				this.btnCancel.TabIndex = p1;
-			}
+		tbPath.Caption = Style.PathCaption;
+		tbFind.Caption = Style.SearchCaption;
 
-			tbPath.Caption = Style.PathCaption;
-			tbFind.Caption = Style.SearchCaption;
+		tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
 
-			tbPath.Autocomplete.ColorScheme.Normal = new Attribute (Color.Black, tbPath.ColorScheme.Normal.Background);
+		_treeRoots = Style.TreeRootGetter ();
+		Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
 
-			_treeRoots = Style.TreeRootGetter ();
-			Style.IconProvider.IsOpenGetter = treeView.IsExpanded;
+		treeView.AddObjects (_treeRoots.Keys);
 
-			treeView.AddObjects (_treeRoots.Keys);
+		// if filtering on file type is configured then create the ComboBox and establish
+		// initial filtering by extension(s)
+		if (AllowedTypes.Any ()) {
 
-			// if filtering on file type is configured then create the ComboBox and establish
-			// initial filtering by extension(s)
-			if (this.AllowedTypes.Any ()) {
+			CurrentFilter = AllowedTypes [0];
 
-				this.CurrentFilter = this.AllowedTypes [0];
+			// Fiddle factor
+			var width = AllowedTypes.Max (a => a.ToString ().Length) + 6;
 
-				// Fiddle factor
-				var width = this.AllowedTypes.Max (a => a.ToString ().Length) + 6;
+			allowedTypeMenu = new MenuBarItem ("<placeholder>",
+				allowedTypeMenuItems = AllowedTypes.Select (
+									   (a, i) => new MenuItem (a.ToString (), null, () => {
+										   AllowedTypeMenuClicked (i);
+									   }))
+								   .ToArray ());
 
-				allowedTypeMenu = new MenuBarItem ("<placeholder>",
-					allowedTypeMenuItems = AllowedTypes.Select (
-						(a, i) => new MenuItem (a.ToString (), null, () => {
-							AllowedTypeMenuClicked (i);
-						}))
-					.ToArray ());
+			allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) {
+				Width = width,
+				Y = 1,
+				X = Pos.AnchorEnd (width),
 
-				allowedTypeMenuBar = new MenuBar (new [] { allowedTypeMenu }) {
-					Width = width,
-					Y = 1,
-					X = Pos.AnchorEnd (width),
+				// TODO: Does not work, if this worked then we could tab to it instead
+				// of having to hit F9
+				CanFocus = true,
+				TabStop = true
+			};
+			AllowedTypeMenuClicked (0);
 
-					// TODO: Does not work, if this worked then we could tab to it instead
-					// of having to hit F9
-					CanFocus = true,
-					TabStop = true
-				};
-				AllowedTypeMenuClicked (0);
+			allowedTypeMenuBar.Enter += (s, e) => {
+				allowedTypeMenuBar.OpenMenu (0);
+			};
 
-				allowedTypeMenuBar.Enter += (s, e) => {
-					allowedTypeMenuBar.OpenMenu (0);
-				};
+			allowedTypeMenuBar.DrawContentComplete += (s, e) => {
 
-				allowedTypeMenuBar.DrawContentComplete += (s, e) => {
+				allowedTypeMenuBar.Move (e.Rect.Width - 1, 0);
+				Driver.AddRune (Glyphs.DownArrow);
 
-					allowedTypeMenuBar.Move (e.Rect.Width - 1, 0);
-					Driver.AddRune (CM.Glyphs.DownArrow);
+			};
 
-				};
+			Add (allowedTypeMenuBar);
+		}
 
-				this.Add (allowedTypeMenuBar);
-			}
+		// if no path has been provided
+		if (tbPath.Text.Length <= 0) {
+			Path = Environment.CurrentDirectory;
+		}
 
-			// if no path has been provided
-			if (this.tbPath.Text.Length <= 0) {
-				this.Path = Environment.CurrentDirectory;
-			}
+		// to streamline user experience and allow direct typing of paths
+		// with zero navigation we start with focus in the text box and any
+		// default/current path fully selected and ready to be overwritten
+		tbPath.FocusFirst ();
+		tbPath.SelectAll ();
 
-			// to streamline user experience and allow direct typing of paths
-			// with zero navigation we start with focus in the text box and any
-			// default/current path fully selected and ready to be overwritten
-			this.tbPath.FocusFirst ();
-			this.tbPath.SelectAll ();
+		if (string.IsNullOrEmpty (Title)) {
+			Title = GetDefaultTitle ();
+		}
+		LayoutSubviews ();
+	}
 
-			if (string.IsNullOrEmpty (Title)) {
-				this.Title = GetDefaultTitle ();
-			}
-			this.LayoutSubviews ();
+	/// <summary>
+	/// Gets a default dialog title, when <see cref="View.Title"/> is not set or empty,
+	/// result of the function will be shown.
+	/// </summary>
+	protected virtual string GetDefaultTitle ()
+	{
+		List<string> titleParts = new () {
+			Strings.fdOpen
+		};
+		if (MustExist) {
+			titleParts.Add (Strings.fdExisting);
 		}
-		/// <summary>
-		/// Gets a default dialog title, when <see cref="View.Title"/> is not set or empty, 
-		/// result of the function will be shown.
-		/// </summary>
-		protected virtual string GetDefaultTitle ()
-		{
-			List<string> titleParts = new () {
-				Strings.fdOpen
-			};
-			if (MustExist) {
-				titleParts.Add (Strings.fdExisting);
-			}
-			switch (OpenMode) {
-			case OpenMode.File:
-				titleParts.Add (Strings.fdFile);
-				break;
-			case OpenMode.Directory:
-				titleParts.Add (Strings.fdDirectory);
-				break;
-			}
-			return string.Join (' ', titleParts);
+		switch (OpenMode) {
+		case OpenMode.File:
+			titleParts.Add (Strings.fdFile);
+			break;
+		case OpenMode.Directory:
+			titleParts.Add (Strings.fdDirectory);
+			break;
 		}
+		return string.Join (' ', titleParts);
+	}
 
-		private void AllowedTypeMenuClicked (int idx)
-		{
+	void AllowedTypeMenuClicked (int idx)
+	{
 
-			var allow = AllowedTypes [idx];
-			for (int i = 0; i < AllowedTypes.Count; i++) {
-				allowedTypeMenuItems [i].Checked = i == idx;
-			}
-			allowedTypeMenu.Title = allow.ToString ();
+		var allow = AllowedTypes [idx];
+		for (var i = 0; i < AllowedTypes.Count; i++) {
+			allowedTypeMenuItems [i].Checked = i == idx;
+		}
+		allowedTypeMenu.Title = allow.ToString ();
 
-			this.CurrentFilter = allow;
+		CurrentFilter = allow;
 
-			this.tbPath.ClearAllSelection ();
-			this.tbPath.Autocomplete.ClearSuggestions ();
+		tbPath.ClearAllSelection ();
+		tbPath.Autocomplete.ClearSuggestions ();
 
-			if (this.State != null) {
-				this.State.RefreshChildren ();
-				this.WriteStateToTableView ();
-			}
+		if (State != null) {
+			State.RefreshChildren ();
+			WriteStateToTableView ();
 		}
+	}
 
-		private void SuppressIfBadChar (Key k)
-		{
-			// don't let user type bad letters
-			var ch = (char)k;
+	void SuppressIfBadChar (Key k)
+	{
+		// don't let user type bad letters
+		var ch = (char)k;
 
-			if (badChars.Contains (ch)) {
-				k.Handled = true;
-			}
+		if (badChars.Contains (ch)) {
+			k.Handled = true;
 		}
+	}
 
-		private bool TreeView_KeyDown (Key keyEvent)
-		{
-			if (this.treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
-				this.tbPath.FocusFirst ();
+	bool TreeView_KeyDown (Key keyEvent)
+	{
+		if (treeView.HasFocus && Separators.Contains ((char)keyEvent)) {
+			tbPath.FocusFirst ();
 
-				// let that keystroke go through on the tbPath instead
-				return true;
-			}
-
-			return false;
+			// let that keystroke go through on the tbPath instead
+			return true;
 		}
 
-		private void AcceptIf (Key keyEvent, KeyCode isKey)
-		{
-			if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
-				keyEvent.Handled = true;
+		return false;
+	}
 
-				// User hit Enter in text box so probably wants the
-				// contents of the text box as their selection not
-				// whatever lingering selection is in TableView
-				this.Accept (false);
-			}
+	void AcceptIf (Key keyEvent, KeyCode isKey)
+	{
+		if (!keyEvent.Handled && keyEvent.KeyCode == isKey) {
+			keyEvent.Handled = true;
+
+			// User hit Enter in text box so probably wants the
+			// contents of the text box as their selection not
+			// whatever lingering selection is in TableView
+			Accept (false);
 		}
+	}
 
-		private void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
-		{
-			if (!this.AllowsMultipleSelection) {
-				return;
-			}
+	void Accept (IEnumerable<FileSystemInfoStats> toMultiAccept)
+	{
+		if (!AllowsMultipleSelection) {
+			return;
+		}
 
-			// Don't include ".." (IsParent) in multiselections
-			this.MultiSelected = toMultiAccept
+		// Don't include ".." (IsParent) in multiselections
+		MultiSelected = toMultiAccept
 				.Where (s => !s.IsParent)
 				.Select (s => s.FileSystemInfo.FullName)
 				.ToList ().AsReadOnly ();
 
-			this.Path = this.MultiSelected.Count == 1 ? this.MultiSelected [0] : string.Empty;
+		Path = MultiSelected.Count == 1 ? MultiSelected [0] : string.Empty;
+
+		FinishAccept ();
+	}
 
-			FinishAccept ();
+	void Accept (IFileInfo f)
+	{
+		if (!IsCompatibleWithOpenMode (f.FullName, out var reason)) {
+			feedback = reason;
+			SetNeedsDisplay ();
+			return;
 		}
 
-		private void Accept (IFileInfo f)
-		{
-			if (!this.IsCompatibleWithOpenMode (f.FullName, out var reason)) {
+		Path = f.FullName;
+
+		if (AllowsMultipleSelection) {
+			MultiSelected = new List<string> { f.FullName }.AsReadOnly ();
+		}
+
+		FinishAccept ();
+	}
+
+	void Accept (bool allowMulti)
+	{
+		if (allowMulti && TryAcceptMulti ()) {
+			return;
+		}
+
+		if (!IsCompatibleWithOpenMode (tbPath.Text, out var reason)) {
+			if (reason != null) {
 				feedback = reason;
 				SetNeedsDisplay ();
-				return;
 			}
+			return;
+		}
 
-			this.Path = f.FullName;
+		FinishAccept ();
+	}
 
-			if (AllowsMultipleSelection) {
-				this.MultiSelected = new List<string> { f.FullName }.AsReadOnly ();
-			}
+	void FinishAccept ()
+	{
+		var e = new FilesSelectedEventArgs (this);
 
-			FinishAccept ();
-		}
+		FilesSelected?.Invoke (this, e);
 
-		private void Accept (bool allowMulti)
-		{
-			if (allowMulti && TryAcceptMulti ()) {
-				return;
-			}
+		if (e.Cancel) {
+			return;
+		}
 
-			if (!this.IsCompatibleWithOpenMode (this.tbPath.Text, out string reason)) {
-				if (reason != null) {
-					feedback = reason;
-					SetNeedsDisplay ();
-				}
-				return;
-			}
+		// if user uses Path selection mode (e.g. Enter in text box)
+		// then also copy to MultiSelected
+		if (AllowsMultipleSelection && !MultiSelected.Any ()) {
 
-			FinishAccept ();
+			MultiSelected = string.IsNullOrWhiteSpace (Path) ?
+				Enumerable.Empty<string> ().ToList ().AsReadOnly () :
+				new List<string> { Path }.AsReadOnly ();
 		}
 
-		private void FinishAccept ()
-		{
-			var e = new FilesSelectedEventArgs (this);
+		Canceled = false;
+		Application.RequestStop ();
+	}
 
-			this.FilesSelected?.Invoke (this, e);
+	bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
+	{
+		if (keyEvent.KeyCode == isKey) {
 
-			if (e.Cancel) {
-				return;
+			to.FocusFirst ();
+			if (to == tbPath) {
+				tbPath.MoveEnd ();
 			}
+			return true;
+		}
 
-			// if user uses Path selection mode (e.g. Enter in text box)
-			// then also copy to MultiSelected
-			if (AllowsMultipleSelection && (!MultiSelected.Any ())) {
-
-				MultiSelected = string.IsNullOrWhiteSpace (Path) ?
-						Enumerable.Empty<string> ().ToList ().AsReadOnly () :
-						new List<string> () { Path }.AsReadOnly ();
-			}
+		return false;
+	}
 
-			this.Canceled = false;
-			Application.RequestStop ();
+	void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
+	{
+		if (e.NewValue == null) {
+			return;
 		}
 
-		private bool NavigateIf (Key keyEvent, KeyCode isKey, View to)
-		{
-			if (keyEvent.KeyCode == isKey) {
+		Path = e.NewValue.FullName;
+	}
 
-				to.FocusFirst ();
-				if (to == tbPath) {
-					tbPath.MoveEnd ();
-				}
-				return true;
-			}
+	void UpdateNavigationVisibility ()
+	{
+		btnBack.Visible = history.CanBack ();
+		btnForward.Visible = history.CanForward ();
+		btnUp.Visible = history.CanUp ();
+	}
 
-			return false;
+	void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
+	{
+		if (!tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
+			return;
 		}
 
-		private void TreeView_SelectionChanged (object sender, SelectionChangedEventArgs<IFileSystemInfo> e)
-		{
-			if (e.NewValue == null) {
-				return;
-			}
-
-			this.Path = e.NewValue.FullName;
+		if (tableView.MultiSelect && tableView.MultiSelectedRegions.Any ()) {
+			return;
 		}
 
-		private void UpdateNavigationVisibility ()
-		{
-			this.btnBack.Visible = this.history.CanBack ();
-			this.btnForward.Visible = this.history.CanForward ();
-			this.btnUp.Visible = this.history.CanUp ();
-		}
+		var stats = RowToStats (obj.NewRow);
 
-		private void TableView_SelectedCellChanged (object sender, SelectedCellChangedEventArgs obj)
-		{
-			if (!this.tableView.HasFocus || obj.NewRow == -1 || obj.Table.Rows == 0) {
-				return;
-			}
+		if (stats == null) {
+			return;
+		}
+		IFileSystemInfo dest;
 
-			if (this.tableView.MultiSelect && this.tableView.MultiSelectedRegions.Any ()) {
-				return;
-			}
+		if (stats.IsParent) {
+			dest = State.Directory;
+		} else {
+			dest = stats.FileSystemInfo;
+		}
 
-			var stats = this.RowToStats (obj.NewRow);
+		try {
+			pushingState = true;
 
-			if (stats == null) {
-				return;
-			}
-			IFileSystemInfo dest;
+			Path = dest.FullName;
+			State.Selected = stats;
+			tbPath.Autocomplete.ClearSuggestions ();
 
-			if (stats.IsParent) {
-				dest = State.Directory;
-			} else {
-				dest = stats.FileSystemInfo;
-			}
+		} finally {
 
-			try {
-				this.pushingState = true;
+			pushingState = false;
+		}
+	}
 
-				this.Path = dest.FullName;
-				this.State.Selected = stats;
-				this.tbPath.Autocomplete.ClearSuggestions ();
+	bool TableView_KeyUp (Key keyEvent)
+	{
+		if (keyEvent.KeyCode == KeyCode.Backspace) {
+			return history.Back ();
+		}
+		if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
+			return history.Forward ();
+		}
 
-			} finally {
+		if (keyEvent.KeyCode == KeyCode.Delete) {
 
-				this.pushingState = false;
-			}
+			Delete ();
+			return true;
 		}
 
-		private bool TableView_KeyUp (Key keyEvent)
-		{
-			if (keyEvent.KeyCode == KeyCode.Backspace) {
-				return this.history.Back ();
-			}
-			if (keyEvent.KeyCode == (KeyCode.ShiftMask | KeyCode.Backspace)) {
-				return this.history.Forward ();
-			}
+		if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
 
-			if (keyEvent.KeyCode == KeyCode.Delete) {
+			Rename ();
+			return true;
+		}
 
-				Delete ();
-				return true;
-			}
+		if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
+			New ();
+			return true;
+		}
 
-			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.R)) {
+		return false;
+	}
 
-				Rename ();
-				return true;
-			}
+	void CellActivate (object sender, CellActivatedEventArgs obj)
+	{
+		if (TryAcceptMulti ()) {
+			return;
+		}
 
-			if (keyEvent.KeyCode == (KeyCode.CtrlMask | KeyCode.N)) {
-				New ();
-				return true;
-			}
+		var stats = RowToStats (obj.Row);
 
-			return false;
+		if (stats.FileSystemInfo is IDirectoryInfo d) {
+			PushState (d, true);
+			return;
 		}
 
-		private void CellActivate (object sender, CellActivatedEventArgs obj)
-		{
-			if (TryAcceptMulti ()) {
-				return;
-			}
-
-			var stats = this.RowToStats (obj.Row);
+		if (stats.FileSystemInfo is IFileInfo f) {
+			Accept (f);
+		}
+	}
 
-			if (stats.FileSystemInfo is IDirectoryInfo d) {
-				this.PushState (d, true);
-				return;
-			}
+	bool TryAcceptMulti ()
+	{
+		var multi = MultiRowToStats ();
+		string reason = null;
 
-			if (stats.FileSystemInfo is IFileInfo f) {
-				this.Accept (f);
-			}
+		if (!multi.Any ()) {
+			return false;
 		}
 
-		private bool TryAcceptMulti ()
-		{
-			var multi = this.MultiRowToStats ();
-			string reason = null;
-
-			if (!multi.Any ()) {
-				return false;
-			}
+		if (multi.All (m => IsCompatibleWithOpenMode (
+			m.FileSystemInfo.FullName, out reason))) {
+			Accept (multi);
+			return true;
+		}
+		if (reason != null) {
+			feedback = reason;
+			SetNeedsDisplay ();
+		}
 
-			if (multi.All (m => this.IsCompatibleWithOpenMode (
-				m.FileSystemInfo.FullName, out reason))) {
-				this.Accept (multi);
-				return true;
-			} else {
-				if (reason != null) {
-					feedback = reason;
-					SetNeedsDisplay ();
-				}
+		return false;
+	}
 
-				return false;
-			}
+	/// <summary>
+	/// Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees
+	/// that <paramref name="file"/> <see cref="IAllowedType.IsAllowed(string)"/>.
+	/// </summary>
+	/// <param name="file"></param>
+	/// <returns></returns>
+	public bool IsCompatibleWithAllowedExtensions (IFileInfo file)
+	{
+		// no restrictions
+		if (!AllowedTypes.Any ()) {
+			return true;
 		}
+		return MatchesAllowedTypes (file);
+	}
 
-		/// <summary>
-		/// Returns true if there are no <see cref="AllowedTypes"/> or one of them agrees
-		/// that <paramref name="file"/> <see cref="IAllowedType.IsAllowed(string)"/>.
-		/// </summary>
-		/// <param name="file"></param>
-		/// <returns></returns>
-		public bool IsCompatibleWithAllowedExtensions (IFileInfo file)
-		{
-			// no restrictions
-			if (!this.AllowedTypes.Any ()) {
-				return true;
-			}
-			return this.MatchesAllowedTypes (file);
+	bool IsCompatibleWithAllowedExtensions (string path)
+	{
+		// no restrictions
+		if (!AllowedTypes.Any ()) {
+			return true;
 		}
 
-		private bool IsCompatibleWithAllowedExtensions (string path)
-		{
-			// no restrictions
-			if (!this.AllowedTypes.Any ()) {
-				return true;
-			}
+		return AllowedTypes.Any (t => t.IsAllowed (path));
+	}
 
-			return this.AllowedTypes.Any (t => t.IsAllowed (path));
+	/// <summary>
+	/// Returns true if any <see cref="AllowedTypes"/> matches <paramref name="file"/>.
+	/// </summary>
+	/// <param name="file"></param>
+	/// <returns></returns>
+	bool MatchesAllowedTypes (IFileInfo file) => AllowedTypes.Any (t => t.IsAllowed (file.FullName));
+
+	bool IsCompatibleWithOpenMode (string s, out string reason)
+	{
+		reason = null;
+		if (string.IsNullOrWhiteSpace (s)) {
+			return false;
 		}
 
-		/// <summary>
-		/// Returns true if any <see cref="AllowedTypes"/> matches <paramref name="file"/>.
-		/// </summary>
-		/// <param name="file"></param>
-		/// <returns></returns>
-		private bool MatchesAllowedTypes (IFileInfo file)
-		{
-			return this.AllowedTypes.Any (t => t.IsAllowed (file.FullName));
+		if (!IsCompatibleWithAllowedExtensions (s)) {
+			reason = Style.WrongFileTypeFeedback;
+			return false;
 		}
-		private bool IsCompatibleWithOpenMode (string s, out string reason)
-		{
-			reason = null;
-			if (string.IsNullOrWhiteSpace (s)) {
+
+		switch (OpenMode) {
+		case OpenMode.Directory:
+			if (MustExist && !Directory.Exists (s)) {
+				reason = Style.DirectoryMustExistFeedback;
 				return false;
 			}
 
-			if (!this.IsCompatibleWithAllowedExtensions (s)) {
-				reason = Style.WrongFileTypeFeedback;
+			if (File.Exists (s)) {
+				reason = Style.FileAlreadyExistsFeedback;
 				return false;
 			}
+			return true;
+		case OpenMode.File:
 
-			switch (this.OpenMode) {
-			case OpenMode.Directory:
-				if (MustExist && !Directory.Exists (s)) {
-					reason = Style.DirectoryMustExistFeedback;
-					return false;
-				}
-
-				if (File.Exists (s)) {
-					reason = Style.FileAlreadyExistsFeedback;
-					return false;
-				}
-				return true;
-			case OpenMode.File:
-
-				if (MustExist && !File.Exists (s)) {
-					reason = Style.FileMustExistFeedback;
-					return false;
-				}
-				if (Directory.Exists (s)) {
-					reason = Style.DirectoryAlreadyExistsFeedback;
-					return false;
-				}
-				return true;
-			case OpenMode.Mixed:
-				if (MustExist && !File.Exists (s) && !Directory.Exists (s)) {
-					reason = Style.FileOrDirectoryMustExistFeedback;
-					return false;
-				}
-				return true;
-			default: throw new ArgumentOutOfRangeException (nameof (this.OpenMode));
+			if (MustExist && !File.Exists (s)) {
+				reason = Style.FileMustExistFeedback;
+				return false;
 			}
-		}
-
-		/// <summary>
-		/// Changes the dialog such that <paramref name="d"/> is being explored.
-		/// </summary>
-		/// <param name="d"></param>
-		/// <param name="addCurrentStateToHistory"></param>
-		/// <param name="setPathText"></param>
-		/// <param name="clearForward"></param>
-		/// <param name="pathText">Optional alternate string to set path to.</param>
-		internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
-		{
-			// no change of state
-			if (d == this.State?.Directory) {
-				return;
+			if (Directory.Exists (s)) {
+				reason = Style.DirectoryAlreadyExistsFeedback;
+				return false;
 			}
-			if (d.FullName == this.State?.Directory.FullName) {
-				return;
+			return true;
+		case OpenMode.Mixed:
+			if (MustExist && !File.Exists (s) && !Directory.Exists (s)) {
+				reason = Style.FileOrDirectoryMustExistFeedback;
+				return false;
 			}
-
-			PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText);
+			return true;
+		default: throw new ArgumentOutOfRangeException (nameof (OpenMode));
 		}
+	}
 
-		private void RefreshState ()
-		{
-			State.RefreshChildren ();
-			PushState (State, false, false, false);
+	/// <summary>
+	/// Changes the dialog such that <paramref name="d"/> is being explored.
+	/// </summary>
+	/// <param name="d"></param>
+	/// <param name="addCurrentStateToHistory"></param>
+	/// <param name="setPathText"></param>
+	/// <param name="clearForward"></param>
+	/// <param name="pathText">Optional alternate string to set path to.</param>
+	internal void PushState (IDirectoryInfo d, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
+	{
+		// no change of state
+		if (d == State?.Directory) {
+			return;
+		}
+		if (d.FullName == State?.Directory.FullName) {
+			return;
 		}
 
-		private void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
-		{
-			if (State is SearchState search) {
-				search.Cancel ();
-			}
+		PushState (new FileDialogState (d, this), addCurrentStateToHistory, setPathText, clearForward, pathText);
+	}
 
-			try {
-				this.pushingState = true;
+	void RefreshState ()
+	{
+		State.RefreshChildren ();
+		PushState (State, false, false, false);
+	}
 
-				// push the old state to history
-				if (addCurrentStateToHistory) {
-					this.history.Push (this.State, clearForward);
-				}
+	void PushState (FileDialogState newState, bool addCurrentStateToHistory, bool setPathText = true, bool clearForward = true, string pathText = null)
+	{
+		if (State is SearchState search) {
+			search.Cancel ();
+		}
 
-				this.tbPath.Autocomplete.ClearSuggestions ();
+		try {
+			pushingState = true;
 
-				if (pathText != null) {
-					this.Path = pathText;
-				} else
-				if (setPathText) {
-					this.Path = newState.Directory.FullName;
-				}
+			// push the old state to history
+			if (addCurrentStateToHistory) {
+				history.Push (State, clearForward);
+			}
 
-				this.State = newState;
-				this.tbPath.Autocomplete.GenerateSuggestions (
-					new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, this.State));
+			tbPath.Autocomplete.ClearSuggestions ();
 
-				this.WriteStateToTableView ();
+			if (pathText != null) {
+				Path = pathText;
+			} else if (setPathText) {
+				Path = newState.Directory.FullName;
+			}
 
-				if (clearForward) {
-					this.history.ClearForward ();
-				}
+			State = newState;
+			tbPath.Autocomplete.GenerateSuggestions (
+				new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
 
-				this.tableView.RowOffset = 0;
-				this.tableView.SelectedRow = 0;
+			WriteStateToTableView ();
 
-				this.SetNeedsDisplay ();
-				this.UpdateNavigationVisibility ();
+			if (clearForward) {
+				history.ClearForward ();
+			}
 
-			} finally {
+			tableView.RowOffset = 0;
+			tableView.SelectedRow = 0;
 
-				this.pushingState = false;
-			}
-			ClearFeedback ();
-		}
+			SetNeedsDisplay ();
+			UpdateNavigationVisibility ();
 
-		private void WriteStateToTableView ()
-		{
-			if (this.State == null) {
-				return;
-			}
-			this.tableView.Table = new FileDialogTableSource (this, this.State, this.Style, currentSortColumn, currentSortIsAsc);
+		} finally {
 
-			this.ApplySort ();
-			this.tableView.Update ();
+			pushingState = false;
 		}
+		ClearFeedback ();
+	}
 
-		private ColorScheme ColorGetter (CellColorGetterArgs args)
-		{
-			var stats = this.RowToStats (args.RowIndex);
-
-			if (!Style.UseColors) {
-				return tableView.ColorScheme;
-			}
+	void WriteStateToTableView ()
+	{
+		if (State == null) {
+			return;
+		}
+		tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
 
+		ApplySort ();
+		tableView.Update ();
+	}
 
-			var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White);
-			var black = new Color (Color.Black);
+	ColorScheme ColorGetter (CellColorGetterArgs args)
+	{
+		var stats = RowToStats (args.RowIndex);
 
-			// TODO: Add some kind of cache for this
-			return new ColorScheme {
-				Normal = new Attribute (color, black),
-				HotNormal = new Attribute (color, black),
-				Focus = new Attribute (black, color),
-				HotFocus = new Attribute (black, color),
-			};
+		if (!Style.UseColors) {
+			return tableView.ColorScheme;
 		}
 
-		/// <summary>
-		/// If <see cref="TableView.MultiSelect"/> is this returns a union of all
-		/// <see cref="FileSystemInfoStats"/> in the selection.
-		/// </summary>
-		/// <returns></returns>
-		private IEnumerable<FileSystemInfoStats> MultiRowToStats ()
-		{
-			var toReturn = new HashSet<FileSystemInfoStats> ();
-
-			if (this.AllowsMultipleSelection && this.tableView.MultiSelectedRegions.Any ()) {
 
-				foreach (var p in this.tableView.GetAllSelectedCells ()) {
+		var color = Style.ColorProvider.GetColor (stats.FileSystemInfo) ?? new Color (Color.White);
+		var black = new Color (Color.Black);
 
-					var add = this.State?.Children [p.Y];
-					if (add != null) {
-						toReturn.Add (add);
-					}
-				}
-			}
+		// TODO: Add some kind of cache for this
+		return new ColorScheme {
+			Normal = new Attribute (color,    black),
+			HotNormal = new Attribute (color, black),
+			Focus = new Attribute (black,     color),
+			HotFocus = new Attribute (black,  color)
+		};
+	}
 
-			return toReturn;
-		}
-		private FileSystemInfoStats RowToStats (int rowIndex)
-		{
-			return this.State?.Children [rowIndex];
-		}
+	/// <summary>
+	/// If <see cref="TableView.MultiSelect"/> is this returns a union of all
+	/// <see cref="FileSystemInfoStats"/> in the selection.
+	/// </summary>
+	/// <returns></returns>
+	IEnumerable<FileSystemInfoStats> MultiRowToStats ()
+	{
+		var toReturn = new HashSet<FileSystemInfoStats> ();
 
-		private void PathChanged ()
-		{
-			// avoid re-entry
-			if (this.pushingState) {
-				return;
-			}
+		if (AllowsMultipleSelection && tableView.MultiSelectedRegions.Any ()) {
 
-			var path = this.tbPath.Text;
+			foreach (var p in tableView.GetAllSelectedCells ()) {
 
-			if (string.IsNullOrWhiteSpace (path)) {
-				return;
+				var add = State?.Children [p.Y];
+				if (add != null) {
+					toReturn.Add (add);
+				}
 			}
+		}
 
-			var dir = this.StringToDirectoryInfo (path);
+		return toReturn;
+	}
 
-			if (dir.Exists) {
-				this.PushState (dir, true, false);
-			} else
-			if (dir.Parent?.Exists ?? false) {
-				this.PushState (dir.Parent, true, false);
-			}
+	FileSystemInfoStats RowToStats (int rowIndex) => State?.Children [rowIndex];
 
-			tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
+	void PathChanged ()
+	{
+		// avoid re-entry
+		if (pushingState) {
+			return;
 		}
 
-		private IDirectoryInfo StringToDirectoryInfo (string path)
-		{
-			// if you pass new DirectoryInfo("C:") you get a weird object
-			// where the FullName is in fact the current working directory.
-			// really not what most users would expect
-			if (Regex.IsMatch (path, "^\\w:$")) {
-				return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar);
-			}
+		var path = tbPath.Text;
 
-			return fileSystem.DirectoryInfo.New (path);
+		if (string.IsNullOrWhiteSpace (path)) {
+			return;
 		}
 
-		/// <summary>
-		/// Select <paramref name="toRestore"/> in the table view (if present)
-		/// </summary>
-		/// <param name="toRestore"></param>
-		internal void RestoreSelection (IFileSystemInfo toRestore)
-		{
-			tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
-			tableView.EnsureSelectedCellIsVisible ();
+		var dir = StringToDirectoryInfo (path);
+
+		if (dir.Exists) {
+			PushState (dir, true, false);
+		} else if (dir.Parent?.Exists ?? false) {
+			PushState (dir.Parent, true, false);
 		}
 
-		internal void ApplySort ()
-		{
-			var stats = State?.Children ?? new FileSystemInfoStats [0];
+		tbPath.Autocomplete.GenerateSuggestions (new AutocompleteFilepathContext (tbPath.Text, tbPath.CursorPosition, State));
+	}
 
-			// This portion is never reordered (aways .. at top then folders)
-			var forcedOrder = stats
-			.OrderByDescending (f => f.IsParent)
-					.ThenBy (f => f.IsDir ? -1 : 100);
+	IDirectoryInfo StringToDirectoryInfo (string path)
+	{
+		// if you pass new DirectoryInfo("C:") you get a weird object
+		// where the FullName is in fact the current working directory.
+		// really not what most users would expect
+		if (Regex.IsMatch (path, "^\\w:$")) {
+			return fileSystem.DirectoryInfo.New (path + System.IO.Path.DirectorySeparatorChar);
+		}
 
-			// This portion is flexible based on the column clicked (e.g. alphabetical)
-			var ordered =
-				this.currentSortIsAsc ?
-					forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f)) :
-					forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
+		return fileSystem.DirectoryInfo.New (path);
+	}
 
-			State.Children = ordered.ToArray ();
+	/// <summary>
+	/// Select <paramref name="toRestore"/> in the table view (if present)
+	/// </summary>
+	/// <param name="toRestore"></param>
+	internal void RestoreSelection (IFileSystemInfo toRestore)
+	{
+		tableView.SelectedRow = State.Children.IndexOf (r => r.FileSystemInfo == toRestore);
+		tableView.EnsureSelectedCellIsVisible ();
+	}
 
-			this.tableView.Update ();
-		}
+	internal void ApplySort ()
+	{
+		var stats = State?.Children ?? new FileSystemInfoStats [0];
 
-		private void SortColumn (int clickedCol)
-		{
-			this.GetProposedNewSortOrder (clickedCol, out var isAsc);
-			this.SortColumn (clickedCol, isAsc);
-			this.tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
-		}
+		// This portion is never reordered (aways .. at top then folders)
+		var forcedOrder = stats
+				  .OrderByDescending (f => f.IsParent)
+				  .ThenBy (f => f.IsDir ? -1 : 100);
 
-		internal void SortColumn (int col, bool isAsc)
-		{
-			// set a sort order
-			this.currentSortColumn = col;
-			this.currentSortIsAsc = isAsc;
+		// This portion is flexible based on the column clicked (e.g. alphabetical)
+		var ordered =
+			currentSortIsAsc ?
+				forcedOrder.ThenBy (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn,           f)) :
+				forcedOrder.ThenByDescending (f => FileDialogTableSource.GetRawColumnValue (currentSortColumn, f));
 
-			this.ApplySort ();
-		}
+		State.Children = ordered.ToArray ();
 
-		private string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
-		{
-			// work out new sort order
-			if (this.currentSortColumn == clickedCol && this.currentSortIsAsc) {
-				isAsc = false;
-				return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]);
-			} else {
-				isAsc = true;
-				return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]);
-			}
-		}
+		tableView.Update ();
+	}
 
-		private void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
-		{
-			var sort = this.GetProposedNewSortOrder (clickedCol, out var isAsc);
+	void SortColumn (int clickedCol)
+	{
+		GetProposedNewSortOrder (clickedCol, out var isAsc);
+		SortColumn (clickedCol, isAsc);
+		tableView.Table = new FileDialogTableSource (this, State, Style, currentSortColumn, currentSortIsAsc);
+	}
 
-			var contextMenu = new ContextMenu (
-				e.MouseEvent.X + 1,
-				e.MouseEvent.Y + 1,
-				new MenuBarItem (new MenuItem []
-				{
-					new MenuItem(string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames[clickedCol])), string.Empty, () => this.HideColumn (clickedCol)),
-					new MenuItem(StripArrows (sort), string.Empty, () => this.SortColumn (clickedCol, isAsc)),
-				})
-			);
+	internal void SortColumn (int col, bool isAsc)
+	{
+		// set a sort order
+		currentSortColumn = col;
+		currentSortIsAsc = isAsc;
 
-			contextMenu.Show ();
-		}
+		ApplySort ();
+	}
 
-		private static string StripArrows (string columnName)
-		{
-			return columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
+	string GetProposedNewSortOrder (int clickedCol, out bool isAsc)
+	{
+		// work out new sort order
+		if (currentSortColumn == clickedCol && currentSortIsAsc) {
+			isAsc = false;
+			return string.Format (Strings.fdCtxSortDesc, tableView.Table.ColumnNames [clickedCol]);
 		}
+		isAsc = true;
+		return string.Format (Strings.fdCtxSortAsc, tableView.Table.ColumnNames [clickedCol]);
+	}
 
-		private void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
-		{
-			if (clickedCell == null) {
-				return;
-			}
+	void ShowHeaderContextMenu (int clickedCol, MouseEventEventArgs e)
+	{
+		var sort = GetProposedNewSortOrder (clickedCol, out var isAsc);
 
-			var contextMenu = new ContextMenu (
-				e.MouseEvent.X + 1,
-				e.MouseEvent.Y + 1,
-				new MenuBarItem (new MenuItem []
-				{
-					new MenuItem(Strings.fdCtxNew, string.Empty, New),
-					new MenuItem(Strings.fdCtxRename, string.Empty, Rename),
-					new MenuItem(Strings.fdCtxDelete,string.Empty, Delete),
-				})
-			);
+		var contextMenu = new ContextMenu (
+			e.MouseEvent.X + 1,
+			e.MouseEvent.Y + 1,
+			new MenuBarItem (new MenuItem [] {
+				new (string.Format (Strings.fdCtxHide, StripArrows (tableView.Table.ColumnNames [clickedCol])), string.Empty, () => HideColumn (clickedCol)),
+				new (StripArrows (sort), string.Empty, () => SortColumn (clickedCol, isAsc))
+			})
+		);
 
-			tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
+		contextMenu.Show ();
+	}
 
-			contextMenu.Show ();
-		}
+	static string StripArrows (string columnName) => columnName.Replace (" (▼)", string.Empty).Replace (" (▲)", string.Empty);
 
-		private void HideColumn (int clickedCol)
-		{
-			var style = this.tableView.Style.GetOrCreateColumnStyle (clickedCol);
-			style.Visible = false;
-			this.tableView.Update ();
+	void ShowCellContextMenu (Point? clickedCell, MouseEventEventArgs e)
+	{
+		if (clickedCell == null) {
+			return;
 		}
 
-		/// <summary>
-		/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
-		/// downwards.
-		/// </summary>
-		internal class SearchState : FileDialogState {
+		var contextMenu = new ContextMenu (
+			e.MouseEvent.X + 1,
+			e.MouseEvent.Y + 1,
+			new MenuBarItem (new MenuItem [] {
+				new (Strings.fdCtxNew, string.Empty, New),
+				new (Strings.fdCtxRename, string.Empty, Rename),
+				new (Strings.fdCtxDelete, string.Empty, Delete)
+			})
+		);
 
-			bool cancel = false;
-			bool finished = false;
+		tableView.SetSelection (clickedCell.Value.X, clickedCell.Value.Y, false);
 
-			// TODO: Add thread safe child adding
-			List<FileSystemInfoStats> found = new List<FileSystemInfoStats> ();
-			object oLockFound = new object ();
-			CancellationTokenSource token = new CancellationTokenSource ();
+		contextMenu.Show ();
+	}
 
-			public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
-			{
-				parent.SearchMatcher.Initialize (searchTerms);
-				Children = new FileSystemInfoStats [0];
-				BeginSearch ();
-			}
+	void HideColumn (int clickedCol)
+	{
+		var style = tableView.Style.GetOrCreateColumnStyle (clickedCol);
+		style.Visible = false;
+		tableView.Update ();
+	}
 
-			private void BeginSearch ()
-			{
-				Task.Run (() => {
-					RecursiveFind (Directory);
-					finished = true;
-				});
+	/// <summary>
+	/// State representing a recursive search from <see cref="FileDialogState.Directory"/>
+	/// downwards.
+	/// </summary>
+	internal class SearchState : FileDialogState {
+		bool cancel;
+		bool finished;
 
-				Task.Run (() => {
-					UpdateChildren ();
-				});
-			}
+		// TODO: Add thread safe child adding
+		readonly List<FileSystemInfoStats> found = new ();
+		readonly object oLockFound = new ();
+		readonly CancellationTokenSource token = new ();
 
-			private void UpdateChildren ()
-			{
-				lock (Parent.onlyOneSearchLock) {
-					while (!cancel && !finished) {
+		public SearchState (IDirectoryInfo dir, FileDialog parent, string searchTerms) : base (dir, parent)
+		{
+			parent.SearchMatcher.Initialize (searchTerms);
+			Children = new FileSystemInfoStats [0];
+			BeginSearch ();
+		}
 
-						try {
-							Task.Delay (250).Wait (token.Token);
-						} catch (OperationCanceledException) {
-							cancel = true;
-						}
+		void BeginSearch ()
+		{
+			Task.Run (() => {
+				RecursiveFind (Directory);
+				finished = true;
+			});
+
+			Task.Run (() => {
+				UpdateChildren ();
+			});
+		}
 
-						if (cancel || finished) {
-							break;
-						}
+		void UpdateChildren ()
+		{
+			lock (Parent.onlyOneSearchLock) {
+				while (!cancel && !finished) {
 
-						UpdateChildrenToFound ();
+					try {
+						Task.Delay (250).Wait (token.Token);
+					} catch (OperationCanceledException) {
+						cancel = true;
 					}
 
-					if (finished && !cancel) {
-						UpdateChildrenToFound ();
+					if (cancel || finished) {
+						break;
 					}
 
-					Application.Invoke (() => {
-						Parent.spinnerView.Visible = false;
-					});
+					UpdateChildrenToFound ();
 				}
-			}
 
-			private void UpdateChildrenToFound ()
-			{
-				lock (oLockFound) {
-					Children = found.ToArray ();
+				if (finished && !cancel) {
+					UpdateChildrenToFound ();
 				}
 
 				Application.Invoke (() => {
-					Parent.tbPath.Autocomplete.GenerateSuggestions (
-						new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this)
-						);
-					Parent.WriteStateToTableView ();
-
-					Parent.spinnerView.Visible = true;
-					Parent.spinnerView.SetNeedsDisplay ();
+					Parent.spinnerView.Visible = false;
 				});
 			}
+		}
+
+		void UpdateChildrenToFound ()
+		{
+			lock (oLockFound) {
+				Children = found.ToArray ();
+			}
 
-			private void RecursiveFind (IDirectoryInfo directory)
-			{
-				foreach (var f in GetChildren (directory)) {
+			Application.Invoke (() => {
+				Parent.tbPath.Autocomplete.GenerateSuggestions (
+					new AutocompleteFilepathContext (Parent.tbPath.Text, Parent.tbPath.CursorPosition, this)
+				);
+				Parent.WriteStateToTableView ();
 
-					if (cancel) {
-						return;
-					}
+				Parent.spinnerView.Visible = true;
+				Parent.spinnerView.SetNeedsDisplay ();
+			});
+		}
 
-					if (f.IsParent) {
-						continue;
-					}
+		void RecursiveFind (IDirectoryInfo directory)
+		{
+			foreach (var f in GetChildren (directory)) {
 
-					lock (oLockFound) {
-						if (found.Count >= FileDialog.MaxSearchResults) {
-							finished = true;
-							return;
-						}
-					}
+				if (cancel) {
+					return;
+				}
+
+				if (f.IsParent) {
+					continue;
+				}
 
-					if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) {
-						lock (oLockFound) {
-							found.Add (f);
-						}
+				lock (oLockFound) {
+					if (found.Count >= MaxSearchResults) {
+						finished = true;
+						return;
 					}
+				}
 
-					if (f.FileSystemInfo is IDirectoryInfo sub) {
-						RecursiveFind (sub);
+				if (Parent.SearchMatcher.IsMatch (f.FileSystemInfo)) {
+					lock (oLockFound) {
+						found.Add (f);
 					}
 				}
-			}
 
-			internal override void RefreshChildren ()
-			{
+				if (f.FileSystemInfo is IDirectoryInfo sub) {
+					RecursiveFind (sub);
+				}
 			}
+		}
 
-			/// <summary>
-			/// Cancels the current search (if any).  Returns true if a search
-			/// was running and cancellation was successfully set.
-			/// </summary>
-			/// <returns></returns>
-			internal bool Cancel ()
-			{
-				var alreadyCancelled = token.IsCancellationRequested || cancel;
+		internal override void RefreshChildren () { }
+
+		/// <summary>
+		/// Cancels the current search (if any).  Returns true if a search
+		/// was running and cancellation was successfully set.
+		/// </summary>
+		/// <returns></returns>
+		internal bool Cancel ()
+		{
+			var alreadyCancelled = token.IsCancellationRequested || cancel;
 
-				cancel = true;
-				token.Cancel ();
+			cancel = true;
+			token.Cancel ();
 
-				return !alreadyCancelled;
-			}
+			return !alreadyCancelled;
 		}
-		internal class FileDialogCollectionNavigator : CollectionNavigatorBase {
-			private FileDialog fileDialog;
+	}
 
-			public FileDialogCollectionNavigator (FileDialog fileDialog)
-			{
-				this.fileDialog = fileDialog;
-			}
+	internal class FileDialogCollectionNavigator : CollectionNavigatorBase {
+		readonly FileDialog fileDialog;
 
-			protected override object ElementAt (int idx)
-			{
-				var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]);
-				if (val == null) {
-					return string.Empty;
-				}
+		public FileDialogCollectionNavigator (FileDialog fileDialog) => this.fileDialog = fileDialog;
 
-				return val.ToString ().Trim ('.');
+		protected override object ElementAt (int idx)
+		{
+			var val = FileDialogTableSource.GetRawColumnValue (fileDialog.tableView.SelectedColumn, fileDialog.State?.Children [idx]);
+			if (val == null) {
+				return string.Empty;
 			}
 
-			protected override int GetCollectionLength ()
-			{
-				return fileDialog.State?.Children.Length ?? 0;
-			}
+			return val.ToString ().Trim ('.');
 		}
+
+		protected override int GetCollectionLength () => fileDialog.State?.Children.Length ?? 0;
 	}
 }

+ 21 - 14
Terminal.Gui/Views/HexView.cs

@@ -95,6 +95,17 @@ public partial class HexView : View {
 		KeyBindings.Add (KeyCode.CursorRight | KeyCode.CtrlMask, Command.EndOfLine);
 		KeyBindings.Add (KeyCode.CursorUp | KeyCode.CtrlMask, Command.StartOfPage);
 		KeyBindings.Add (KeyCode.CursorDown | KeyCode.CtrlMask, Command.EndOfPage);
+
+		LayoutComplete += HexView_LayoutComplete;
+	}
+
+	private void HexView_LayoutComplete (object sender, LayoutEventArgs e)
+	{
+		// Small buffers will just show the position, with the bsize field value (4 bytes)
+		bytesPerLine = bsize;
+		if (Bounds.Width - displayWidth > 17) {
+			bytesPerLine = bsize * ((Bounds.Width - displayWidth) / 18);
+		}
 	}
 
 	/// <summary>
@@ -174,20 +185,6 @@ public partial class HexView : View {
 		}
 	}
 
-	/// <inheritdoc/>
-	public override Rect Frame {
-		get => base.Frame;
-		set {
-			base.Frame = value;
-
-			// Small buffers will just show the position, with the bsize field value (4 bytes)
-			bytesPerLine = bsize;
-			if (value.Width - displayWidth > 17) {
-				bytesPerLine = bsize * ((value.Width - displayWidth) / 18);
-			}
-		}
-	}
-
 	//
 	// This is used to support editing of the buffer on a peer List<>, 
 	// the offset corresponds to an offset relative to DisplayStart, and
@@ -214,6 +211,7 @@ public partial class HexView : View {
 		Driver.SetAttribute (current);
 		Move (0, 0);
 
+		// BUGBUG: Bounds!!!!
 		var frame = Frame;
 
 		int nblocks = bytesPerLine / bsize;
@@ -309,6 +307,7 @@ public partial class HexView : View {
 		int delta = (int)(pos - DisplayStart);
 		int line = delta / bytesPerLine;
 
+		// BUGBUG: Bounds!
 		SetNeedsDisplay (new Rect (0, line, Frame.Width, 1));
 	}
 
@@ -331,6 +330,7 @@ public partial class HexView : View {
 	bool MoveEnd ()
 	{
 		position = source.Length;
+		// BUGBUG: Bounds!
 		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
 			SetDisplayStart (position);
 			SetNeedsDisplay ();
@@ -396,6 +396,7 @@ public partial class HexView : View {
 		if (position < source.Length) {
 			position++;
 		}
+		// BUGBUG: Bounds!
 		if (position >= DisplayStart + bytesPerLine * Frame.Height) {
 			SetDisplayStart (DisplayStart + bytesPerLine);
 			SetNeedsDisplay ();
@@ -424,6 +425,7 @@ public partial class HexView : View {
 
 	bool MoveDown (int bytes)
 	{
+		// BUGBUG: Bounds!
 		RedisplayLine (position);
 		if (position + bytes < source.Length) {
 			position += bytes;
@@ -513,6 +515,8 @@ public partial class HexView : View {
 	/// <inheritdoc/>
 	public override bool MouseEvent (MouseEvent me)
 	{
+		// BUGBUG: Test this with a border! Assumes Frame == Bounds!
+
 		if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
 								&& !me.Flags.HasFlag (MouseFlags.WheeledDown) && !me.Flags.HasFlag (MouseFlags.WheeledUp)) {
 			return false;
@@ -595,6 +599,9 @@ public partial class HexView : View {
 	/// </summary>
 	public Point CursorPosition {
 		get {
+			if (!IsInitialized) {
+				return new Point (0, 0);
+			}
 			int delta = (int)position;
 			int line = delta / bytesPerLine + 1;
 			int item = delta % bytesPerLine + 1;

+ 98 - 123
Terminal.Gui/Views/Label.cs

@@ -1,141 +1,116 @@
-//
-// Label.cs: Label control
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
+using System;
+
+namespace Terminal.Gui; 
+
+/// <summary>
+/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by newline
+/// characters.
+/// Multi-line Labels support word wrap.
+/// </summary>
+/// <remarks>
+/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards
+/// compatibility.
+/// </remarks>
+public class Label : View {
+	/// <inheritdoc/>
+	public Label () => SetInitialProperties ();
+
+	/// <inheritdoc/>
+	public Label (Rect frame, bool autosize = false) : base (frame) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (string text, bool autosize = true) : base (text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (Rect rect, string text, bool autosize = false) : base (rect, text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (int x, int y, string text, bool autosize = true) : base (x, y, text) => SetInitialProperties (autosize);
+
+	/// <inheritdoc/>
+	public Label (string text, TextDirection direction, bool autosize = true)
+		: base (text, direction) => SetInitialProperties (autosize);
+
+	void SetInitialProperties (bool autosize = true)
+	{
+		Height   = 1;
+		AutoSize = autosize;
+		// Things this view knows how to do
+		AddCommand (Command.Default, () => {
+			// BUGBUG: This is a hack, but it does work.
+			var can = CanFocus;
+			CanFocus = true;
+			SetFocus ();
+			SuperView.FocusNext ();
+			CanFocus = can;
+			return true;
+		});
+		AddCommand (Command.Accept, () => AcceptKey ());
+
+		// Default key bindings for this view
+		KeyBindings.Add (KeyCode.Space, Command.Accept);
+	}
 
-using System;
-using System.Text;
+	bool AcceptKey ()
+	{
+		if (!HasFocus) {
+			SetFocus ();
+		}
+		OnClicked ();
+		return true;
+	}
 
-namespace Terminal.Gui {
 	/// <summary>
-	/// The Label <see cref="View"/> displays a string at a given position and supports multiple lines separated by newline characters.
-	/// Multi-line Labels support word wrap.
+	/// The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
+	/// or if the user presses the action key while this view is focused. (TODO: IsDefault)
 	/// </summary>
 	/// <remarks>
-	/// The <see cref="Label"/> view is functionality identical to <see cref="View"/> and is included for API backwards compatibility.
+	/// Client code can hook up to this event, it is
+	/// raised when the button is activated either with
+	/// the mouse or the keyboard.
 	/// </remarks>
-	public class Label : View {
-		/// <inheritdoc/>
-		public Label ()
-		{
-			SetInitialProperties ();
-		}
-
-		/// <inheritdoc/>
-		public Label (Rect frame, bool autosize = false) : base (frame)
-		{
-			SetInitialProperties (autosize);
-		}
+	public event EventHandler Clicked;
 
-		/// <inheritdoc/>
-		public Label (string text, bool autosize = true) : base (text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (Rect rect, string text, bool autosize = false) : base (rect, text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (int x, int y, string text, bool autosize = true) : base (x, y, text)
-		{
-			SetInitialProperties (autosize);
-		}
-
-		/// <inheritdoc/>
-		public Label (string text, TextDirection direction, bool autosize = true)
-			: base (text, direction)
-		{
-			SetInitialProperties (autosize);
+	/// <summary>
+	/// Method invoked when a mouse event is generated
+	/// </summary>
+	/// <param name="mouseEvent"></param>
+	/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
+	public override bool OnMouseEvent (MouseEvent mouseEvent)
+	{
+		var args = new MouseEventEventArgs (mouseEvent);
+		if (OnMouseClick (args)) {
+			return true;
 		}
-
-		void SetInitialProperties (bool autosize = true)
-		{
-			Height = 1;
-			AutoSize = autosize;
-			// Things this view knows how to do
-			AddCommand (Command.Default, () => {
-				// BUGBUG: This is a hack, but it does work.
-				var can = CanFocus;
-				CanFocus = true;
-				SetFocus ();
-				SuperView.FocusNext ();
-				CanFocus = can;
-				return true;
-			});
-			AddCommand (Command.Accept, () => AcceptKey ());
-
-			// Default key bindings for this view
-			KeyBindings.Add (KeyCode.Space, Command.Accept);
+		if (MouseEvent (mouseEvent)) {
+			return true;
 		}
 
-		bool AcceptKey ()
-		{
-			if (!HasFocus) {
+		if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
+			if (!HasFocus && SuperView != null) {
+				if (!SuperView.HasFocus) {
+					SuperView.SetFocus ();
+				}
 				SetFocus ();
+				SetNeedsDisplay ();
 			}
+
 			OnClicked ();
 			return true;
 		}
-		
-		/// <summary>
-		///   The event fired when the user clicks the primary mouse button within the Bounds of this <see cref="View"/>
-		///   or if the user presses the action key while this view is focused. (TODO: IsDefault)
-		/// </summary>
-		/// <remarks>
-		///   Client code can hook up to this event, it is
-		///   raised when the button is activated either with
-		///   the mouse or the keyboard.
-		/// </remarks>
-		public event EventHandler Clicked;
-
-		/// <summary>
-		/// Method invoked when a mouse event is generated
-		/// </summary>
-		/// <param name="mouseEvent"></param>
-		/// <returns><c>true</c>, if the event was handled, <c>false</c> otherwise.</returns>
-		public override bool OnMouseEvent (MouseEvent mouseEvent)
-		{
-			MouseEventEventArgs args = new MouseEventEventArgs (mouseEvent);
-			if (OnMouseClick (args))
-				return true;
-			if (MouseEvent (mouseEvent))
-				return true;
-
-			if (mouseEvent.Flags == MouseFlags.Button1Clicked) {
-				if (!HasFocus && SuperView != null) {
-					if (!SuperView.HasFocus) {
-						SuperView.SetFocus ();
-					}
-					SetFocus ();
-					SetNeedsDisplay ();
-				}
-
-				OnClicked ();
-				return true;
-			}
-			return false;
-		}
-
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+		return false;
+	}
 
-			return base.OnEnter (view);
-		}
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-		/// <summary>
-		/// Virtual method to invoke the <see cref="Clicked"/> event.
-		/// </summary>
-		public virtual void OnClicked ()
-		{
-			Clicked?.Invoke (this, EventArgs.Empty);
-		}
+		return base.OnEnter (view);
 	}
-}
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="Clicked"/> event.
+	/// </summary>
+	public virtual void OnClicked () => Clicked?.Invoke (this, EventArgs.Empty);
+}

+ 1 - 1
Terminal.Gui/Views/Menu/ContextMenu.cs

@@ -99,7 +99,7 @@ public sealed class ContextMenu : IDisposable {
 		}
 		_container = Application.Current;
 		_container.Closing += Container_Closing;
-		var frame = new Rect (0, 0, View.Driver.Cols, View.Driver.Rows);
+		var frame = Application.Driver.Bounds;
 		var position = Position;
 		if (Host != null) {
 			Host.BoundsToScreen (frame.X, frame.Y, out int x, out int y);

+ 2 - 2
Terminal.Gui/Views/Menu/MenuBar.cs

@@ -1445,7 +1445,7 @@ public class MenuBar : View {
 		if (Driver == null) {
 			return Point.Empty;
 		}
-		var superViewFrame = SuperView == null ? new Rect (0, 0, Driver.Cols, Driver.Rows) : SuperView.Frame;
+		var superViewFrame = SuperView == null ? Driver.Bounds : SuperView.Frame;
 		var sv = SuperView == null ? Application.Current : SuperView;
 		var boundsOffset = sv.GetBoundsOffset ();
 		return new Point (superViewFrame.X - sv.Frame.X - boundsOffset.X,
@@ -1458,7 +1458,7 @@ public class MenuBar : View {
 	/// <returns>The location offset.</returns>
 	internal Point GetScreenOffsetFromCurrent ()
 	{
-		var screen = new Rect (0, 0, Driver.Cols, Driver.Rows);
+		var screen = Driver.Bounds;
 		var currentFrame = Application.Current.Frame;
 		var boundsOffset = Application.Top.GetBoundsOffset ();
 		return new Point (screen.X - currentFrame.X - boundsOffset.X

+ 4 - 5
Terminal.Gui/Views/RadioGroup.cs

@@ -26,7 +26,7 @@ public class RadioGroup : View {
 	/// <param name="selected">The index of the item to be selected, the value is clamped to the number of items.</param>
 	public RadioGroup (string [] radioLabels, int selected = 0) : base ()
 	{
-		SetInitialProperties (Rect.Empty, radioLabels, selected);
+		SetInitialProperties (radioLabels, selected);
 	}
 
 	/// <summary>
@@ -37,7 +37,7 @@ public class RadioGroup : View {
 	/// <param name="selected">The index of item to be selected, the value is clamped to the number of items.</param>
 	public RadioGroup (Rect rect, string [] radioLabels, int selected = 0) : base (rect)
 	{
-		SetInitialProperties (rect, radioLabels, selected);
+		SetInitialProperties (radioLabels, selected);
 	}
 
 	/// <summary>
@@ -52,7 +52,7 @@ public class RadioGroup : View {
 		this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected)
 	{ }
 
-	void SetInitialProperties (Rect rect, string [] radioLabels, int selected)
+	void SetInitialProperties (string [] radioLabels, int selected)
 	{
 		HotKeySpecifier = new Rune ('_');
 
@@ -61,7 +61,6 @@ public class RadioGroup : View {
 		}
 
 		_selected = selected;
-		Frame = rect;
 		CanFocus = true;
 
 		// Things this view knows how to do
@@ -130,7 +129,7 @@ public class RadioGroup : View {
 				length += item.length;
 			}
 			var hr = new Rect (0, 0, length, 1);
-			if (IsAdded && LayoutStyle == LayoutStyle.Computed) {
+			if (IsAdded) {
 				Width = hr.Width;
 				Height = 1;
 			} else {

+ 708 - 700
Terminal.Gui/Views/ScrollBarView.cs

@@ -8,816 +8,824 @@
 using System;
 using System.Text;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui; 
+
+/// <summary>
+/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
+/// </summary>
+/// <remarks>
+///         <para>
+///         The scrollbar is drawn to be a representation of the Size, assuming that the
+///         scroll position is set at Position.
+///         </para>
+///         <para>
+///         If the region to display the scrollbar is larger than three characters,
+///         arrow indicators are drawn.
+///         </para>
+/// </remarks>
+public class ScrollBarView : View {
+	bool _autoHideScrollBars = true;
+	View _contentBottomRightCorner;
+	bool _hosted;
+	bool _keepContentAlwaysInViewport = true;
+
+	int _lastLocation = -1;
+	ScrollBarView _otherScrollBarView;
+	int _posBarOffset;
+	int _posBottomTee;
+	int _posLeftTee;
+	int _posRightTee;
+
+	int _posTopTee;
+	bool _showScrollIndicator;
+	int _size, _position;
+	bool _vertical;
+
 	/// <summary>
-	/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/>
+	/// layout.
 	/// </summary>
-	/// <remarks>
-	/// <para>
-	///   The scrollbar is drawn to be a representation of the Size, assuming that the 
-	///   scroll position is set at Position.
-	/// </para>
-	/// <para>
-	///   If the region to display the scrollbar is larger than three characters, 
-	///   arrow indicators are drawn.
-	/// </para>
-	/// </remarks>
-	public class ScrollBarView : View {
-		bool _vertical;
-		int _size, _position;
-		bool _showScrollIndicator;
-		bool _keepContentAlwaysInViewport = true;
-		bool _autoHideScrollBars = true;
-		bool _hosted;
-		ScrollBarView _otherScrollBarView;
-		View _contentBottomRightCorner;
-
-		bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator;
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Frame for the scrollbar.</param>
-		public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/> layout.
-		/// </summary>
-		/// <param name="rect">Frame for the scrollbar.</param>
-		/// <param name="size">The size that this scrollbar represents. Sets the <see cref="Size"/> property.</param>
-		/// <param name="position">The position within this scrollbar. Sets the <see cref="Position"/> property.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the <see cref="IsVertical"/> property.</param>
-		public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect)
-		{
-			SetInitialProperties (size, position, isVertical);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		public ScrollBarView () : this (0, 0, false) { }
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="size">The size that this scrollbar represents.</param>
-		/// <param name="position">The position within this scrollbar.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
-		public ScrollBarView (int size, int position, bool isVertical) : base ()
-		{
-			SetInitialProperties (size, position, isVertical);
-		}
-
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/> layout.
-		/// </summary>
-		/// <param name="host">The view that will host this scrollbar.</param>
-		/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
-		/// <param name="showBothScrollIndicator">If set to <c>true (default)</c> will have the other scrollbar, otherwise will have only one.</param>
-		public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical)
-		{
-			if (host == null) {
-				throw new ArgumentNullException ("The host parameter can't be null.");
-			} else if (host.SuperView == null) {
-				throw new ArgumentNullException ("The host SuperView parameter can't be null.");
-			}
-			_hosted = true;
-			ColorScheme = host.ColorScheme;
-			X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
-			Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
-			Host = host;
-			CanFocus = false;
-			Enabled = host.Enabled;
-			Visible = host.Visible;
-			//Host.CanFocusChanged += Host_CanFocusChanged;
-			Host.EnabledChanged += Host_EnabledChanged;
-			Host.VisibleChanged += Host_VisibleChanged;
-			Host.SuperView.Add (this);
-			AutoHideScrollBars = true;
-			if (showBothScrollIndicator) {
-				OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
-					Id = "OtherScrollBarView",
-					ColorScheme = host.ColorScheme,
-					Host = host,
-					CanFocus = false,
-					Enabled = host.Enabled,
-					Visible = host.Visible,
-					OtherScrollBarView = this
-				};
-				OtherScrollBarView._hosted = true;
-				OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
-				OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
-				OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
-				OtherScrollBarView.ShowScrollIndicator = true;
-			}
-			ShowScrollIndicator = true;
-			CreateBottomRightCorner ();
-			ClearOnVisibleFalse = false;
-		}
-
-		private void CreateBottomRightCorner ()
-		{
-			if (Host != null && (_contentBottomRightCorner == null && OtherScrollBarView == null
-				|| (_contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null))) {
-
-				_contentBottomRightCorner = new View () {
-					Id = "contentBottomRightCorner",
-					Visible = Host.Visible,
-					ClearOnVisibleFalse = false,
-					ColorScheme = ColorScheme
-				};
-				if (_hosted) {
-					Host.SuperView.Add (_contentBottomRightCorner);
-				} else {
-					Host.Add (_contentBottomRightCorner);
-				}
-				_contentBottomRightCorner.X = Pos.Right (Host) - 1;
-				_contentBottomRightCorner.Y = Pos.Bottom (Host) - 1;
-				_contentBottomRightCorner.Width = 1;
-				_contentBottomRightCorner.Height = 1;
-				_contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
-				_contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent;
-			}
-		}
-
-		private void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e)
-		{
-			Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
-		}
+	/// <param name="rect">Frame for the scrollbar.</param>
+	public ScrollBarView (Rect rect) : this (rect, 0, 0, false) { }
 
-		private void Host_VisibleChanged (object sender, EventArgs e)
-		{
-			if (!Host.Visible) {
-				Visible = Host.Visible;
-				if (_otherScrollBarView != null) {
-					_otherScrollBarView.Visible = Visible;
-				}
-				_contentBottomRightCorner.Visible = Visible;
-			} else {
-				ShowHideScrollBars ();
-			}
-		}
-
-		private void Host_EnabledChanged (object sender, EventArgs e)
-		{
-			Enabled = Host.Enabled;
-			if (_otherScrollBarView != null) {
-				_otherScrollBarView.Enabled = Enabled;
-			}
-			_contentBottomRightCorner.Enabled = Enabled;
-		}
-
-		//private void Host_CanFocusChanged ()
-		//{
-		//	CanFocus = Host.CanFocus;
-		//	if (otherScrollBarView != null) {
-		//		otherScrollBarView.CanFocus = CanFocus;
-		//	}
-		//}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Absolute"/>
+	/// layout.
+	/// </summary>
+	/// <param name="rect">Frame for the scrollbar.</param>
+	/// <param name="size">The size that this scrollbar represents. Sets the <see cref="Size"/> property.</param>
+	/// <param name="position">The position within this scrollbar. Sets the <see cref="Position"/> property.</param>
+	/// <param name="isVertical">
+	/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal. Sets the
+	/// <see cref="IsVertical"/> property.
+	/// </param>
+	public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) => SetInitialProperties (size, position, isVertical);
 
-		void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me)
-		{
-			if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp
-			    || me.MouseEvent.Flags == MouseFlags.WheeledRight || me.MouseEvent.Flags == MouseFlags.WheeledLeft) {
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/>
+	/// layout.
+	/// </summary>
+	public ScrollBarView () : this (0, 0, false) { }
 
-				MouseEvent (me.MouseEvent);
-			} else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) {
-				Host.SetFocus ();
-			}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/>
+	/// layout.
+	/// </summary>
+	/// <param name="size">The size that this scrollbar represents.</param>
+	/// <param name="position">The position within this scrollbar.</param>
+	/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
+	public ScrollBarView (int size, int position, bool isVertical) => SetInitialProperties (size, position, isVertical);
 
-			me.Handled = true;
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Gui.ScrollBarView"/> class using <see cref="LayoutStyle.Computed"/>
+	/// layout.
+	/// </summary>
+	/// <param name="host">The view that will host this scrollbar.</param>
+	/// <param name="isVertical">If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.</param>
+	/// <param name="showBothScrollIndicator">
+	/// If set to <c>true (default)</c> will have the other scrollbar, otherwise will
+	/// have only one.
+	/// </param>
+	public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true) : this (0, 0, isVertical)
+	{
+		if (host == null) {
+			throw new ArgumentNullException ("The host parameter can't be null.");
 		}
+		if (host.SuperView == null) {
+			throw new ArgumentNullException ("The host SuperView parameter can't be null.");
+		}
+		_hosted = true;
+		ColorScheme = host.ColorScheme;
+		X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
+		Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
+		Host = host;
+		CanFocus = false;
+		Enabled = host.Enabled;
+		Visible = host.Visible;
+		//Host.CanFocusChanged += Host_CanFocusChanged;
+		Host.EnabledChanged += Host_EnabledChanged;
+		Host.VisibleChanged += Host_VisibleChanged;
+		Host.SuperView.Add (this);
+		AutoHideScrollBars = true;
+		if (showBothScrollIndicator) {
+			OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) {
+				Id = "OtherScrollBarView",
+				ColorScheme = host.ColorScheme,
+				Host = host,
+				CanFocus = false,
+				Enabled = host.Enabled,
+				Visible = host.Visible,
+				OtherScrollBarView = this
+			};
+			OtherScrollBarView._hosted = true;
+			OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
+			OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
+			OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
+			OtherScrollBarView.ShowScrollIndicator = true;
+		}
+		ShowScrollIndicator = true;
+		CreateBottomRightCorner ();
+		ClearOnVisibleFalse = false;
+	}
 
-		void SetInitialProperties (int size, int position, bool isVertical)
-		{
-			Id = "ScrollBarView";
-			_vertical = isVertical;
-			this._position = position;
-			this._size = size;
-			WantContinuousButtonPressed = true;
-			ClearOnVisibleFalse = false;
-
-			Added += (s, e) => CreateBottomRightCorner ();
+	bool _showBothScrollIndicator => OtherScrollBarView?._showScrollIndicator == true && _showScrollIndicator;
 
-			Initialized += (s, e) => {
+	/// <summary>
+	/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
+	/// </summary>
+	public bool IsVertical {
+		get => _vertical;
+		set {
+			_vertical = value;
+			if (IsInitialized) {
 				SetWidthHeight ();
-				SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame);
-				if (Id == "OtherScrollBarView" || OtherScrollBarView == null) {
-					// Only do this once if both scrollbars are enabled
-					ShowHideScrollBars ();
-				}
-				SetPosition (position);
-			};
+			}
 		}
+	}
 
-		/// <summary>
-		/// If set to <c>true</c> this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
-		/// </summary>
-		public bool IsVertical {
-			get => _vertical;
-			set {
-				_vertical = value;
-				if (IsInitialized) {
-					SetWidthHeight ();
-				}
+	/// <summary>
+	/// The size of content the scrollbar represents.
+	/// </summary>
+	/// <value>The size.</value>
+	/// <remarks>
+	/// The <see cref="Size"/> is typically the size of the virtual content. E.g. when a Scrollbar is
+	/// part of a <see cref="View"/> the Size is set to the appropriate dimension of <see cref="Host"/>.
+	/// </remarks>
+	public int Size {
+		get => _size;
+		set {
+			_size = value;
+			if (IsInitialized) {
+				SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+				ShowHideScrollBars (false);
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// The size of content the scrollbar represents.
-		/// </summary>
-		/// <value>The size.</value>
-		/// <remarks>The <see cref="Size"/> is typically the size of the virtual content. E.g. when a Scrollbar is
-		/// part of a <see cref="View"/> the Size is set to the appropriate dimension of <see cref="Host"/>.</remarks>
-		public int Size {
-			get => _size;
-			set {
-				_size = value;
-				if (IsInitialized) {
-					SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
-					ShowHideScrollBars (false);
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// The position, relative to <see cref="Size"/>, to set the scrollbar at.
+	/// </summary>
+	/// <value>The position.</value>
+	public int Position {
+		get => _position;
+		set {
+			_position = value;
+			if (IsInitialized) {
+				// We're not initialized so we can't do anything fancy. Just cache value.
+				SetPosition (value);
 			}
 		}
+	}
 
-		/// <summary>
-		/// This event is raised when the position on the scrollbar has changed.
-		/// </summary>
-		public event EventHandler ChangedPosition;
+	// BUGBUG: v2 - for consistency this should be named "Parent" not "Host"
+	/// <summary>
+	/// Get or sets the view that host this <see cref="ScrollBarView"/>
+	/// </summary>
+	public View Host { get; internal set; }
 
-		/// <summary>
-		/// The position, relative to <see cref="Size"/>, to set the scrollbar at.
-		/// </summary>
-		/// <value>The position.</value>
-		public int Position {
-			get => _position;
-			set {
-				_position = value;
-				if (IsInitialized) {
-					// We're not initialized so we can't do anything fancy. Just cache value.
-					SetPosition (value);
-				}
+	/// <summary>
+	/// Represent a vertical or horizontal ScrollBarView other than this.
+	/// </summary>
+	public ScrollBarView OtherScrollBarView {
+		get => _otherScrollBarView;
+		set {
+			if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) {
+				throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView.");
 			}
+			_otherScrollBarView = value;
 		}
+	}
 
-		// Helper to assist Initialized event handler
-		private void SetPosition (int newPosition)
-		{
-			if (CanScroll (newPosition - _position, out int max, _vertical)) {
-				if (max == newPosition - _position) {
-					_position = newPosition;
+	// BUGBUG: v2 - Why can't we get rid of this and just use Visible?
+	/// <summary>
+	/// Gets or sets the visibility for the vertical or horizontal scroll indicator.
+	/// </summary>
+	/// <value><c>true</c> if show vertical or horizontal scroll indicator; otherwise, <c>false</c>.</value>
+	public bool ShowScrollIndicator {
+		get => _showScrollIndicator;
+		set {
+			//if (value == showScrollIndicator) {
+			//	return;
+			//}
+
+			_showScrollIndicator = value;
+			if (IsInitialized) {
+				SetNeedsLayout ();
+				if (value) {
+					Visible = true;
 				} else {
-					_position = Math.Max (_position + max, 0);
-				}
-			} else if (max < 0) {
-				_position = Math.Max (_position + max, 0);
-			} else {
-				_position = Math.Max (newPosition, 0);
-			}
-			OnChangedPosition ();
-			SetNeedsDisplay ();
-		}
-
-		// BUGBUG: v2 - for consistency this should be named "Parent" not "Host"
-		/// <summary>
-		/// Get or sets the view that host this <see cref="ScrollBarView"/>
-		/// </summary>
-		public View Host { get; internal set; }
-
-		/// <summary>
-		/// Represent a vertical or horizontal ScrollBarView other than this.
-		/// </summary>
-		public ScrollBarView OtherScrollBarView {
-			get => _otherScrollBarView;
-			set {
-				if (value != null && (value.IsVertical && _vertical || !value.IsVertical && !_vertical)) {
-					throw new ArgumentException ($"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView.");
-				}
-				_otherScrollBarView = value;
-			}
-		}
-
-		// BUGBUG: v2 - Why can't we get rid of this and just use Visible?
-		/// <summary>
-		/// Gets or sets the visibility for the vertical or horizontal scroll indicator.
-		/// </summary>
-		/// <value><c>true</c> if show vertical or horizontal scroll indicator; otherwise, <c>false</c>.</value>
-		public bool ShowScrollIndicator {
-			get => _showScrollIndicator;
-			set {
-				//if (value == showScrollIndicator) {
-				//	return;
-				//}
-
-				_showScrollIndicator = value;
-				if (IsInitialized) {
-					SetNeedsLayout ();
-					if (value) {
-						Visible = true;
-					} else {
-						Visible = false;
-						Position = 0;
-					}
-					SetWidthHeight ();
+					Visible = false;
+					Position = 0;
 				}
+				SetWidthHeight ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollBarView"/>
-		/// </summary>
-		public bool KeepContentAlwaysInViewport {
-			get { return _keepContentAlwaysInViewport; }
-			set {
-				if (_keepContentAlwaysInViewport != value) {
-					_keepContentAlwaysInViewport = value;
-					int pos = 0;
-					if (value && !_vertical && _position + Host.Bounds.Width > _size) {
-						pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0);
-					}
-					if (value && _vertical && _position + Host.Bounds.Height > _size) {
-						pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0);
-					}
-					if (pos != 0) {
-						Position = pos;
-					}
-					if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) {
-						OtherScrollBarView.KeepContentAlwaysInViewport = value;
-					}
-					if (pos == 0) {
-						Refresh ();
-					}
+	/// <summary>
+	/// Get or sets if the view-port is kept always visible in the area of this <see cref="ScrollBarView"/>
+	/// </summary>
+	public bool KeepContentAlwaysInViewport {
+		get => _keepContentAlwaysInViewport;
+		set {
+			if (_keepContentAlwaysInViewport != value) {
+				_keepContentAlwaysInViewport = value;
+				var pos = 0;
+				if (value && !_vertical && _position + Host.Bounds.Width > _size) {
+					pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0);
+				}
+				if (value && _vertical && _position + Host.Bounds.Height > _size) {
+					pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0);
+				}
+				if (pos != 0) {
+					Position = pos;
+				}
+				if (OtherScrollBarView != null && OtherScrollBarView._keepContentAlwaysInViewport != value) {
+					OtherScrollBarView.KeepContentAlwaysInViewport = value;
+				}
+				if (pos == 0) {
+					Refresh ();
 				}
 			}
 		}
+	}
 
-		/// <summary>
-		/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
-		/// </summary>
-		public bool AutoHideScrollBars {
-			get => _autoHideScrollBars;
-			set {
-				if (_autoHideScrollBars != value) {
-					_autoHideScrollBars = value;
-					SetNeedsDisplay ();
-				}
+	/// <summary>
+	/// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
+	/// </summary>
+	public bool AutoHideScrollBars {
+		get => _autoHideScrollBars;
+		set {
+			if (_autoHideScrollBars != value) {
+				_autoHideScrollBars = value;
+				SetNeedsDisplay ();
 			}
 		}
+	}
 
-		/// <summary>
-		/// Virtual method to invoke the <see cref="ChangedPosition"/> action event.
-		/// </summary>
-		public virtual void OnChangedPosition ()
-		{
-			ChangedPosition?.Invoke (this, EventArgs.Empty);
+	void CreateBottomRightCorner ()
+	{
+		if (Host != null &&
+		    (_contentBottomRightCorner == null && OtherScrollBarView == null ||
+		     _contentBottomRightCorner == null && OtherScrollBarView != null && OtherScrollBarView._contentBottomRightCorner == null)) {
+
+			_contentBottomRightCorner = new View {
+				Id = "contentBottomRightCorner",
+				Visible = Host.Visible,
+				ClearOnVisibleFalse = false,
+				ColorScheme = ColorScheme
+			};
+			if (_hosted) {
+				Host.SuperView.Add (_contentBottomRightCorner);
+			} else {
+				Host.Add (_contentBottomRightCorner);
+			}
+			_contentBottomRightCorner.X = Pos.Right (Host) - 1;
+			_contentBottomRightCorner.Y = Pos.Bottom (Host) - 1;
+			_contentBottomRightCorner.Width = 1;
+			_contentBottomRightCorner.Height = 1;
+			_contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
+			_contentBottomRightCorner.DrawContent += _contentBottomRightCorner_DrawContent;
 		}
+	}
 
-		/// <summary>
-		/// Only used for a hosted view that will update and redraw the scrollbars.
-		/// </summary>
-		public virtual void Refresh ()
-		{
+	void _contentBottomRightCorner_DrawContent (object sender, DrawEventArgs e) => Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
+
+	void Host_VisibleChanged (object sender, EventArgs e)
+	{
+		if (!Host.Visible) {
+			Visible = Host.Visible;
+			if (_otherScrollBarView != null) {
+				_otherScrollBarView.Visible = Visible;
+			}
+			_contentBottomRightCorner.Visible = Visible;
+		} else {
 			ShowHideScrollBars ();
 		}
+	}
 
-		void ShowHideScrollBars (bool redraw = true)
-		{
-			if (!_hosted || (_hosted && !_autoHideScrollBars)) {
-				if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
-					_contentBottomRightCorner.Visible = false;
-				} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
-					_otherScrollBarView._contentBottomRightCorner.Visible = false;
-				}
-				return;
-			}
+	void Host_EnabledChanged (object sender, EventArgs e)
+	{
+		Enabled = Host.Enabled;
+		if (_otherScrollBarView != null) {
+			_otherScrollBarView.Enabled = Enabled;
+		}
+		_contentBottomRightCorner.Enabled = Enabled;
+	}
 
-			var pending = CheckBothScrollBars (this);
-			if (_otherScrollBarView != null) {
-				CheckBothScrollBars (_otherScrollBarView, pending);
-			}
+	//private void Host_CanFocusChanged ()
+	//{
+	//	CanFocus = Host.CanFocus;
+	//	if (otherScrollBarView != null) {
+	//		otherScrollBarView.CanFocus = CanFocus;
+	//	}
+	//}
+
+	void ContentBottomRightCorner_MouseClick (object sender, MouseEventEventArgs me)
+	{
+		if (me.MouseEvent.Flags == MouseFlags.WheeledDown ||
+		    me.MouseEvent.Flags == MouseFlags.WheeledUp ||
+		    me.MouseEvent.Flags == MouseFlags.WheeledRight ||
+		    me.MouseEvent.Flags == MouseFlags.WheeledLeft) {
+
+			MouseEvent (me.MouseEvent);
+		} else if (me.MouseEvent.Flags == MouseFlags.Button1Clicked) {
+			Host.SetFocus ();
+		}
 
+		me.Handled = true;
+	}
+
+	void SetInitialProperties (int size, int position, bool isVertical)
+	{
+		Id = "ScrollBarView";
+		_vertical = isVertical;
+		_position = position;
+		_size = size;
+		WantContinuousButtonPressed = true;
+		ClearOnVisibleFalse = false;
+
+		Added += (s, e) => CreateBottomRightCorner ();
+
+		Initialized += (s, e) => {
 			SetWidthHeight ();
-			SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
-			if (_otherScrollBarView != null) {
-				OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+			SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame);
+			// BUGBUG: We're not supposed to use Id internally!
+			if (Id == "OtherScrollBarView" || OtherScrollBarView == null) {
+				// Only do this once if both scrollbars are enabled
+				ShowHideScrollBars ();
 			}
+			SetPosition (position);
+		};
+	}
 
-			if (_showBothScrollIndicator) {
-				if (_contentBottomRightCorner != null) {
-					_contentBottomRightCorner.Visible = true;
-				} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
-					_otherScrollBarView._contentBottomRightCorner.Visible = true;
-				}
-			} else if (!_showScrollIndicator) {
-				if (_contentBottomRightCorner != null) {
-					_contentBottomRightCorner.Visible = false;
-				} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
-					_otherScrollBarView._contentBottomRightCorner.Visible = false;
-				}
-				if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
-					Application.UngrabMouse ();
-				}
-			} else if (_contentBottomRightCorner != null) {
+	/// <summary>
+	/// This event is raised when the position on the scrollbar has changed.
+	/// </summary>
+	public event EventHandler ChangedPosition;
+
+	// Helper to assist Initialized event handler
+	void SetPosition (int newPosition)
+	{
+		if (CanScroll (newPosition - _position, out var max, _vertical)) {
+			if (max == newPosition - _position) {
+				_position = newPosition;
+			} else {
+				_position = Math.Max (_position + max, 0);
+			}
+		} else if (max < 0) {
+			_position = Math.Max (_position + max, 0);
+		} else {
+			_position = Math.Max (newPosition, 0);
+		}
+		OnChangedPosition ();
+		SetNeedsDisplay ();
+	}
+
+	/// <summary>
+	/// Virtual method to invoke the <see cref="ChangedPosition"/> action event.
+	/// </summary>
+	public virtual void OnChangedPosition () => ChangedPosition?.Invoke (this, EventArgs.Empty);
+
+	/// <summary>
+	/// Only used for a hosted view that will update and redraw the scrollbars.
+	/// </summary>
+	public virtual void Refresh () => ShowHideScrollBars ();
+
+	void ShowHideScrollBars (bool redraw = true)
+	{
+		if (!_hosted || _hosted && !_autoHideScrollBars) {
+			if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
 				_contentBottomRightCorner.Visible = false;
-			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
 				_otherScrollBarView._contentBottomRightCorner.Visible = false;
 			}
-			if (Host?.Visible == true && _showScrollIndicator && !Visible) {
-				Visible = true;
-			}
-			if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) {
-				_otherScrollBarView.Visible = true;
-			}
+			return;
+		}
 
-			if (!redraw) {
-				return;
-			}
+		var pending = CheckBothScrollBars (this);
+		if (_otherScrollBarView != null) {
+			CheckBothScrollBars (_otherScrollBarView, pending);
+		}
+
+		SetWidthHeight ();
+		SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+		if (_otherScrollBarView != null) {
+			OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame);
+		}
 
-			if (_showScrollIndicator) {
-				Draw ();
+		if (_showBothScrollIndicator) {
+			if (_contentBottomRightCorner != null) {
+				_contentBottomRightCorner.Visible = true;
+			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+				_otherScrollBarView._contentBottomRightCorner.Visible = true;
 			}
-			if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) {
-				_otherScrollBarView.Draw ();
+		} else if (!_showScrollIndicator) {
+			if (_contentBottomRightCorner != null) {
+				_contentBottomRightCorner.Visible = false;
+			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+				_otherScrollBarView._contentBottomRightCorner.Visible = false;
 			}
-			if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
-				_contentBottomRightCorner.Draw ();
-			} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
-				_otherScrollBarView._contentBottomRightCorner.Draw ();
+			if (Application.MouseGrabView != null && Application.MouseGrabView == this) {
+				Application.UngrabMouse ();
 			}
+		} else if (_contentBottomRightCorner != null) {
+			_contentBottomRightCorner.Visible = false;
+		} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null) {
+			_otherScrollBarView._contentBottomRightCorner.Visible = false;
+		}
+		if (Host?.Visible == true && _showScrollIndicator && !Visible) {
+			Visible = true;
+		}
+		if (Host?.Visible == true && _otherScrollBarView?._showScrollIndicator == true && !_otherScrollBarView.Visible) {
+			_otherScrollBarView.Visible = true;
 		}
 
-		bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
-		{
-			int barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
+		if (!redraw) {
+			return;
+		}
 
-			if (barsize == 0 || barsize >= scrollBarView._size) {
-				if (scrollBarView._showScrollIndicator) {
-					scrollBarView.ShowScrollIndicator = false;
-				}
-				if (scrollBarView.Visible) {
-					scrollBarView.Visible = false;
-				}
-			} else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) {
-				if (scrollBarView._showScrollIndicator) {
-					scrollBarView.ShowScrollIndicator = false;
-				}
-				if (scrollBarView.Visible) {
-					scrollBarView.Visible = false;
-				}
-				if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) {
-					scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
-				}
-				if (scrollBarView.OtherScrollBarView.Visible) {
-					scrollBarView.OtherScrollBarView.Visible = false;
-				}
-			} else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) {
-				pending = true;
-			} else {
-				if (scrollBarView.OtherScrollBarView != null && pending) {
-					if (!scrollBarView._showBothScrollIndicator) {
-						scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
-					}
-					if (!scrollBarView.OtherScrollBarView.Visible) {
-						scrollBarView.OtherScrollBarView.Visible = true;
-					}
-				}
-				if (!scrollBarView._showScrollIndicator) {
-					scrollBarView.ShowScrollIndicator = true;
+		if (_showScrollIndicator) {
+			Draw ();
+		}
+		if (_otherScrollBarView != null && _otherScrollBarView._showScrollIndicator) {
+			_otherScrollBarView.Draw ();
+		}
+		if (_contentBottomRightCorner != null && _contentBottomRightCorner.Visible) {
+			_contentBottomRightCorner.Draw ();
+		} else if (_otherScrollBarView != null && _otherScrollBarView._contentBottomRightCorner != null && _otherScrollBarView._contentBottomRightCorner.Visible) {
+			_otherScrollBarView._contentBottomRightCorner.Draw ();
+		}
+	}
+
+	bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
+	{
+		var barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width;
+
+		if (barsize == 0 || barsize >= scrollBarView._size) {
+			if (scrollBarView._showScrollIndicator) {
+				scrollBarView.ShowScrollIndicator = false;
+			}
+			if (scrollBarView.Visible) {
+				scrollBarView.Visible = false;
+			}
+		} else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView != null && pending) {
+			if (scrollBarView._showScrollIndicator) {
+				scrollBarView.ShowScrollIndicator = false;
+			}
+			if (scrollBarView.Visible) {
+				scrollBarView.Visible = false;
+			}
+			if (scrollBarView.OtherScrollBarView != null && scrollBarView._showBothScrollIndicator) {
+				scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
+			}
+			if (scrollBarView.OtherScrollBarView.Visible) {
+				scrollBarView.OtherScrollBarView.Visible = false;
+			}
+		} else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView != null && !pending) {
+			pending = true;
+		} else {
+			if (scrollBarView.OtherScrollBarView != null && pending) {
+				if (!scrollBarView._showBothScrollIndicator) {
+					scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
 				}
-				if (!scrollBarView.Visible) {
-					scrollBarView.Visible = true;
+				if (!scrollBarView.OtherScrollBarView.Visible) {
+					scrollBarView.OtherScrollBarView.Visible = true;
 				}
 			}
-
-			return pending;
+			if (!scrollBarView._showScrollIndicator) {
+				scrollBarView.ShowScrollIndicator = true;
+			}
+			if (!scrollBarView.Visible) {
+				scrollBarView.Visible = true;
+			}
 		}
 
-		// BUGBUG: v2 - rationalize this with View.SetMinWidthHeight
-		void SetWidthHeight ()
-		{
-			// BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not
-			// supported that a view can reference it's superview's Dims. This code also assumes the host does 
-			//  not have a margin/borderframe/padding.
-			if (!IsInitialized) {
-				return;
-			}
+		return pending;
+	}
 
-			if (_showBothScrollIndicator) {
-				Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
-				Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
+	// BUGBUG: v2 - rationalize this with View.SetMinWidthHeight
+	void SetWidthHeight ()
+	{
+		// BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not
+		// supported that a view can reference it's superview's Dims. This code also assumes the host does 
+		//  not have a margin/borderframe/padding.
+		if (!IsInitialized) {
+			return;
+		}
+
+		if (_showBothScrollIndicator) {
+			Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
+			Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
+
+			_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
+			_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
+		} else if (_showScrollIndicator) {
+			Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill ();
+			Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1;
+		} else if (_otherScrollBarView?._showScrollIndicator == true) {
+			_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0;
+			_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1;
+		}
+	}
 
-				_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
-				_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
-			} else if (_showScrollIndicator) {
-				Width = _vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill ();
-				Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1;
-			} else if (_otherScrollBarView?._showScrollIndicator == true) {
-				_otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 : Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0;
-				_otherScrollBarView.Height = _otherScrollBarView._vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0 : 1;
+	///<inheritdoc/>
+	public override void OnDrawContent (Rect contentArea)
+	{
+		if (ColorScheme == null || (!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
+			if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
+				ShowHideScrollBars (false);
 			}
+			return;
 		}
 
-		int _posTopTee;
-		int _posLeftTee;
-		int _posBottomTee;
-		int _posRightTee;
+		if (Size == 0 || _vertical && Bounds.Height == 0 || !_vertical && Bounds.Width == 0) {
+			return;
+		}
 
-		///<inheritdoc/>
-		public override void OnDrawContent (Rect contentArea)
-		{
-			if (ColorScheme == null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) {
-				if ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible) {
-					ShowHideScrollBars (false);
-				}
-				return;
-			}
+		Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
 
-			if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0)) {
+		if (_vertical) {
+			if (Bounds.Right < Bounds.Width - 1) {
 				return;
 			}
 
-			Driver.SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
+			var col = Bounds.Width - 1;
+			var bh = Bounds.Height;
+			Rune special;
 
-			if (_vertical) {
-				if (Bounds.Right < Bounds.Width - 1) {
-					return;
-				}
-
-				var col = Bounds.Width - 1;
-				var bh = Bounds.Height;
-				Rune special;
+			if (bh < 4) {
+				var by1 = _position * bh / Size;
+				var by2 = (_position + bh) * bh / Size;
 
-				if (bh < 4) {
-					var by1 = _position * bh / Size;
-					var by2 = (_position + bh) * bh / Size;
-
-					Move (col, 0);
-					if (Bounds.Height == 1) {
-						Driver.AddRune (CM.Glyphs.Diamond);
-					} else {
-						Driver.AddRune (CM.Glyphs.UpArrow);
-					}
-					if (Bounds.Height == 3) {
-						Move (col, 1);
-						Driver.AddRune (CM.Glyphs.Diamond);
-					}
-					if (Bounds.Height > 1) {
-						Move (col, Bounds.Height - 1);
-						Driver.AddRune (CM.Glyphs.DownArrow);
-					}
+				Move (col, 0);
+				if (Bounds.Height == 1) {
+					Driver.AddRune (Glyphs.Diamond);
 				} else {
-					bh -= 2;
-					var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh);
-					var by2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bh) * bh / Size) + 1, bh - 1) : (_position + bh) * bh / (Size + bh);
-					if (KeepContentAlwaysInViewport && by1 == by2) {
-						by1 = Math.Max (by1 - 1, 0);
-					}
-
-					Move (col, 0);
-					Driver.AddRune (CM.Glyphs.UpArrow);
-
-					bool hasTopTee = false;
-					bool hasDiamond = false;
-					bool hasBottomTee = false;
-					for (int y = 0; y < bh; y++) {
-						Move (col, y + 1);
-						if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) {
-							special = CM.Glyphs.Stipple;
+					Driver.AddRune (Glyphs.UpArrow);
+				}
+				if (Bounds.Height == 3) {
+					Move (col, 1);
+					Driver.AddRune (Glyphs.Diamond);
+				}
+				if (Bounds.Height > 1) {
+					Move (col, Bounds.Height - 1);
+					Driver.AddRune (Glyphs.DownArrow);
+				}
+			} else {
+				bh -= 2;
+				var by1 = KeepContentAlwaysInViewport ? _position * bh / Size : _position * bh / (Size + bh);
+				var by2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bh) * bh / Size + 1, bh - 1) : (_position + bh) * bh / (Size + bh);
+				if (KeepContentAlwaysInViewport && by1 == by2) {
+					by1 = Math.Max (by1 - 1, 0);
+				}
+
+				Move (col, 0);
+				Driver.AddRune (Glyphs.UpArrow);
+
+				var hasTopTee = false;
+				var hasDiamond = false;
+				var hasBottomTee = false;
+				for (var y = 0; y < bh; y++) {
+					Move (col, y + 1);
+					if ((y < by1 || y > by2) && (_position > 0 && !hasTopTee || hasTopTee && hasBottomTee)) {
+						special = Glyphs.Stipple;
+					} else {
+						if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
+							hasDiamond = true;
+							special = Glyphs.Diamond;
 						} else {
-							if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond) {
-								hasDiamond = true;
-								special = CM.Glyphs.Diamond;
+							if (y == by1 && !hasTopTee) {
+								hasTopTee = true;
+								_posTopTee = y;
+								special = Glyphs.TopTee;
+							} else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
+								hasBottomTee = true;
+								_posBottomTee = y;
+								special = Glyphs.BottomTee;
 							} else {
-								if (y == by1 && !hasTopTee) {
-									hasTopTee = true;
-									_posTopTee = y;
-									special = CM.Glyphs.TopTee;
-								} else if ((_position == 0 && y == bh - 1 || y >= by2 || by2 == 0) && !hasBottomTee) {
-									hasBottomTee = true;
-									_posBottomTee = y;
-									special = CM.Glyphs.BottomTee;
-								} else {
-									special = CM.Glyphs.VLine;
-								}
+								special = Glyphs.VLine;
 							}
 						}
-						Driver.AddRune (special);
 					}
-					if (!hasTopTee) {
-						Move (col, Bounds.Height - 2);
-						Driver.AddRune (CM.Glyphs.TopTee);
-					}
-					Move (col, Bounds.Height - 1);
-					Driver.AddRune (CM.Glyphs.DownArrow);
+					Driver.AddRune (special);
 				}
-			} else {
-				if (Bounds.Bottom < Bounds.Height - 1) {
-					return;
+				if (!hasTopTee) {
+					Move (col, Bounds.Height - 2);
+					Driver.AddRune (Glyphs.TopTee);
 				}
+				Move (col, Bounds.Height - 1);
+				Driver.AddRune (Glyphs.DownArrow);
+			}
+		} else {
+			if (Bounds.Bottom < Bounds.Height - 1) {
+				return;
+			}
 
-				var row = Bounds.Height - 1;
-				var bw = Bounds.Width;
-				Rune special;
-
-				if (bw < 4) {
-					var bx1 = _position * bw / Size;
-					var bx2 = (_position + bw) * bw / Size;
-
-					Move (0, row);
-					Driver.AddRune (CM.Glyphs.LeftArrow);
-					Driver.AddRune (CM.Glyphs.RightArrow);
-				} else {
-					bw -= 2;
-					var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw);
-					var bx2 = KeepContentAlwaysInViewport ? Math.Min (((_position + bw) * bw / Size) + 1, bw - 1) : (_position + bw) * bw / (Size + bw);
-					if (KeepContentAlwaysInViewport && bx1 == bx2) {
-						bx1 = Math.Max (bx1 - 1, 0);
-					}
+			var row = Bounds.Height - 1;
+			var bw = Bounds.Width;
+			Rune special;
 
-					Move (0, row);
-					Driver.AddRune (CM.Glyphs.LeftArrow);
+			if (bw < 4) {
+				var bx1 = _position * bw / Size;
+				var bx2 = (_position + bw) * bw / Size;
 
-					bool hasLeftTee = false;
-					bool hasDiamond = false;
-					bool hasRightTee = false;
-					for (int x = 0; x < bw; x++) {
-						if ((x < bx1 || x >= bx2 + 1) && ((_position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee))) {
-							special = CM.Glyphs.Stipple;
+				Move (0, row);
+				Driver.AddRune (Glyphs.LeftArrow);
+				Driver.AddRune (Glyphs.RightArrow);
+			} else {
+				bw -= 2;
+				var bx1 = KeepContentAlwaysInViewport ? _position * bw / Size : _position * bw / (Size + bw);
+				var bx2 = KeepContentAlwaysInViewport ? Math.Min ((_position + bw) * bw / Size + 1, bw - 1) : (_position + bw) * bw / (Size + bw);
+				if (KeepContentAlwaysInViewport && bx1 == bx2) {
+					bx1 = Math.Max (bx1 - 1, 0);
+				}
+
+				Move (0, row);
+				Driver.AddRune (Glyphs.LeftArrow);
+
+				var hasLeftTee = false;
+				var hasDiamond = false;
+				var hasRightTee = false;
+				for (var x = 0; x < bw; x++) {
+					if ((x < bx1 || x >= bx2 + 1) && (_position > 0 && !hasLeftTee || hasLeftTee && hasRightTee)) {
+						special = Glyphs.Stipple;
+					} else {
+						if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
+							hasDiamond = true;
+							special = Glyphs.Diamond;
 						} else {
-							if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond) {
-								hasDiamond = true;
-								special = CM.Glyphs.Diamond;
+							if (x == bx1 && !hasLeftTee) {
+								hasLeftTee = true;
+								_posLeftTee = x;
+								special = Glyphs.LeftTee;
+							} else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
+								hasRightTee = true;
+								_posRightTee = x;
+								special = Glyphs.RightTee;
 							} else {
-								if (x == bx1 && !hasLeftTee) {
-									hasLeftTee = true;
-									_posLeftTee = x;
-									special = CM.Glyphs.LeftTee;
-								} else if ((_position == 0 && x == bw - 1 || x >= bx2 || bx2 == 0) && !hasRightTee) {
-									hasRightTee = true;
-									_posRightTee = x;
-									special = CM.Glyphs.RightTee;
-								} else {
-									special = CM.Glyphs.HLine;
-								}
+								special = Glyphs.HLine;
 							}
 						}
-						Driver.AddRune (special);
 					}
-					if (!hasLeftTee) {
-						Move (Bounds.Width - 2, row);
-						Driver.AddRune (CM.Glyphs.LeftTee);
-					}
-
-					Driver.AddRune (CM.Glyphs.RightArrow);
+					Driver.AddRune (special);
+				}
+				if (!hasLeftTee) {
+					Move (Bounds.Width - 2, row);
+					Driver.AddRune (Glyphs.LeftTee);
 				}
-			}
-		}
-
-		int _lastLocation = -1;
-		int _posBarOffset;
-
-		///<inheritdoc/>
-		public override bool MouseEvent (MouseEvent mouseEvent)
-		{
-			if (mouseEvent.Flags != MouseFlags.Button1Pressed && mouseEvent.Flags != MouseFlags.Button1DoubleClicked &&
-				!mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) &&
-				mouseEvent.Flags != MouseFlags.Button1Released && mouseEvent.Flags != MouseFlags.WheeledDown &&
-				mouseEvent.Flags != MouseFlags.WheeledUp && mouseEvent.Flags != MouseFlags.WheeledRight &&
-				mouseEvent.Flags != MouseFlags.WheeledLeft && mouseEvent.Flags != MouseFlags.Button1TripleClicked) {
 
-				return false;
+				Driver.AddRune (Glyphs.RightArrow);
 			}
+		}
+	}
 
-			if (!Host.CanFocus) {
-				return true;
-			}
-			if (Host?.HasFocus == false) {
-				Host.SetFocus ();
-			}
+	///<inheritdoc/>
+	public override bool MouseEvent (MouseEvent mouseEvent)
+	{
+		if (mouseEvent.Flags != MouseFlags.Button1Pressed &&
+		    mouseEvent.Flags != MouseFlags.Button1DoubleClicked &&
+		    !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) &&
+		    mouseEvent.Flags != MouseFlags.Button1Released &&
+		    mouseEvent.Flags != MouseFlags.WheeledDown &&
+		    mouseEvent.Flags != MouseFlags.WheeledUp &&
+		    mouseEvent.Flags != MouseFlags.WheeledRight &&
+		    mouseEvent.Flags != MouseFlags.WheeledLeft &&
+		    mouseEvent.Flags != MouseFlags.Button1TripleClicked) {
 
-			int location = _vertical ? mouseEvent.Y : mouseEvent.X;
-			int barsize = _vertical ? Bounds.Height : Bounds.Width;
-			int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
-			int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
-			barsize -= 2;
-			var pos = Position;
+			return false;
+		}
 
-			if (mouseEvent.Flags != MouseFlags.Button1Released
-				&& (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
-				Application.GrabMouse (this);
-			} else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
-				_lastLocation = -1;
-				Application.UngrabMouse ();
-				return true;
-			}
-			if (_showScrollIndicator && (mouseEvent.Flags == MouseFlags.WheeledDown || mouseEvent.Flags == MouseFlags.WheeledUp ||
-				mouseEvent.Flags == MouseFlags.WheeledRight || mouseEvent.Flags == MouseFlags.WheeledLeft)) {
+		if (!Host.CanFocus) {
+			return true;
+		}
+		if (Host?.HasFocus == false) {
+			Host.SetFocus ();
+		}
 
-				return Host.MouseEvent (mouseEvent);
-			}
+		var location = _vertical ? mouseEvent.Y : mouseEvent.X;
+		var barsize = _vertical ? Bounds.Height : Bounds.Width;
+		var posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
+		var posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
+		barsize -= 2;
+		var pos = Position;
+
+		if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView == null || Application.MouseGrabView != this)) {
+			Application.GrabMouse (this);
+		} else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView != null && Application.MouseGrabView == this) {
+			_lastLocation = -1;
+			Application.UngrabMouse ();
+			return true;
+		}
+		if (_showScrollIndicator &&
+		    (mouseEvent.Flags == MouseFlags.WheeledDown ||
+		     mouseEvent.Flags == MouseFlags.WheeledUp ||
+		     mouseEvent.Flags == MouseFlags.WheeledRight ||
+		     mouseEvent.Flags == MouseFlags.WheeledLeft)) {
 
-			if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) {
-				if (pos > 0) {
-					Position = pos - 1;
-				}
-			} else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) {
-				if (CanScroll (1, out _, _vertical)) {
-					Position = pos + 1;
-				}
-			} else if (location > 0 && location < barsize + 1) {
-				//var b1 = pos * (Size > 0 ? barsize / Size : 0);
-				//var b2 = Size > 0
-				//	? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size)
-				//	: 0;
-				//if (KeepContentAlwaysInViewport && b1 == b2) {
-				//	b1 = Math.Max (b1 - 1, 0);
-				//}
-
-				if (_lastLocation > -1 || (location >= posTopLeftTee && location <= posBottomRightTee
-				&& mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))) {
-					if (_lastLocation == -1) {
-						_lastLocation = location;
-						_posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0;
-						return true;
-					}
+			return Host.MouseEvent (mouseEvent);
+		}
 
-					if (location > _lastLocation) {
-						if (location - _posBarOffset < barsize) {
-							var np = ((location - _posBarOffset) * Size / barsize) + (Size / barsize);
-							if (CanScroll (np - pos, out int nv, _vertical)) {
-								Position = pos + nv;
-							}
-						} else if (CanScroll (Size - pos, out int nv, _vertical)) {
-							Position = Math.Min (pos + nv, Size);
-						}
-					} else if (location < _lastLocation) {
-						if (location - _posBarOffset > 0) {
-							var np = ((location - _posBarOffset) * Size / barsize) - (Size / barsize);
-							if (CanScroll (np - pos, out int nv, _vertical)) {
-								Position = pos + nv;
-							}
-						} else {
-							Position = 0;
+		if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0) {
+			if (pos > 0) {
+				Position = pos - 1;
+			}
+		} else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1) {
+			if (CanScroll (1, out _, _vertical)) {
+				Position = pos + 1;
+			}
+		} else if (location > 0 && location < barsize + 1) {
+			//var b1 = pos * (Size > 0 ? barsize / Size : 0);
+			//var b2 = Size > 0
+			//	? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size)
+			//	: 0;
+			//if (KeepContentAlwaysInViewport && b1 == b2) {
+			//	b1 = Math.Max (b1 - 1, 0);
+			//}
+
+			if (_lastLocation > -1 || location >= posTopLeftTee && location <= posBottomRightTee && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)) {
+				if (_lastLocation == -1) {
+					_lastLocation = location;
+					_posBarOffset = _keepContentAlwaysInViewport ? Math.Max (location - posTopLeftTee, 1) : 0;
+					return true;
+				}
+
+				if (location > _lastLocation) {
+					if (location - _posBarOffset < barsize) {
+						var np = (location - _posBarOffset) * Size / barsize + Size / barsize;
+						if (CanScroll (np - pos, out var nv, _vertical)) {
+							Position = pos + nv;
 						}
-					} else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, _vertical)) {
-						Position = Math.Min (pos + nv, Size);
-					} else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) {
+					} else if (CanScroll (Size - pos, out var nv, _vertical)) {
 						Position = Math.Min (pos + nv, Size);
-					} else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) {
-						Position = 0;
-					}
-				} else if (location > posBottomRightTee) {
-					if (CanScroll (barsize, out int nv, _vertical)) {
-						Position = pos + nv;
 					}
-				} else if (location < posTopLeftTee) {
-					if (CanScroll (-barsize, out int nv, _vertical)) {
-						Position = pos + nv;
+				} else if (location < _lastLocation) {
+					if (location - _posBarOffset > 0) {
+						var np = (location - _posBarOffset) * Size / barsize - Size / barsize;
+						if (CanScroll (np - pos, out var nv, _vertical)) {
+							Position = pos + nv;
+						}
+					} else {
+						Position = 0;
 					}
-				} else if (location == 1 && posTopLeftTee <= 3) {
+				} else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out var nv, _vertical)) {
+					Position = Math.Min (pos + nv, Size);
+				} else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical)) {
+					Position = Math.Min (pos + nv, Size);
+				} else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3) {
 					Position = 0;
-				} else if (location == barsize) {
-					if (CanScroll (Size - pos, out int nv, _vertical)) {
-						Position = Math.Min (pos + nv, Size);
-					}
+				}
+			} else if (location > posBottomRightTee) {
+				if (CanScroll (barsize, out var nv, _vertical)) {
+					Position = pos + nv;
+				}
+			} else if (location < posTopLeftTee) {
+				if (CanScroll (-barsize, out var nv, _vertical)) {
+					Position = pos + nv;
+				}
+			} else if (location == 1 && posTopLeftTee <= 3) {
+				Position = 0;
+			} else if (location == barsize) {
+				if (CanScroll (Size - pos, out var nv, _vertical)) {
+					Position = Math.Min (pos + nv, Size);
 				}
 			}
-
-			return true;
 		}
 
-		internal bool CanScroll (int n, out int max, bool isVertical = false)
-		{
-			if (Host?.Bounds.IsEmpty != false) {
-				max = 0;
-				return false;
-			}
-			int s = GetBarsize (isVertical);
-			var newSize = Math.Max (Math.Min (_size - s, _position + n), 0);
-			max = _size > s + newSize ? (newSize == 0 ? -_position : n) : _size - (s + _position) - 1;
-			if (_size >= s + newSize && max != 0) {
-				return true;
-			}
+		return true;
+	}
+
+	internal bool CanScroll (int n, out int max, bool isVertical = false)
+	{
+		if (Host?.Bounds.IsEmpty != false) {
+			max = 0;
 			return false;
 		}
+		var s = GetBarsize (isVertical);
+		var newSize = Math.Max (Math.Min (_size - s, _position + n), 0);
+		max = _size > s + newSize ? newSize == 0 ? -_position : n : _size - (s + _position) - 1;
+		if (_size >= s + newSize && max != 0) {
+			return true;
+		}
+		return false;
+	}
 
-		int GetBarsize (bool isVertical)
-		{
-			if (Host?.Bounds.IsEmpty != false) {
-				return 0;
-			}
-			return isVertical ?
-				(KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0) :
-				(KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0);
+	int GetBarsize (bool isVertical)
+	{
+		if (Host?.Bounds.IsEmpty != false) {
+			return 0;
 		}
+		return isVertical ?
+			KeepContentAlwaysInViewport ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) : 0 :
+			KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0;
+	}
 
-		///<inheritdoc/>
-		public override bool OnEnter (View view)
-		{
-			Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
+	///<inheritdoc/>
+	public override bool OnEnter (View view)
+	{
+		Application.Driver.SetCursorVisibility (CursorVisibility.Invisible);
 
-			return base.OnEnter (view);
-		}
+		return base.OnEnter (view);
 	}
-}
+}

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

@@ -804,8 +804,6 @@ public class Slider<T> : View {
 		if (!IsInitialized || AutoSize == false) {
 			return;
 		}
-		// Hack???  Otherwise we can't go back to Dim.Absolute.
-		LayoutStyle = LayoutStyle.Absolute;
 		Width = 0;
 		Height = 0;
 		if (_config._sliderOrientation == Orientation.Horizontal) {
@@ -817,7 +815,6 @@ public class Slider<T> : View {
 				new Size (int.Min (SuperView.Bounds.Width - GetFramesThickness ().Horizontal, CalcThickness ()),
 					int.Min (SuperView.Bounds.Height - GetFramesThickness ().Vertical,    CalcBestLength ())));
 		}
-		LayoutStyle = LayoutStyle.Computed;
 	}
 
 	/// <summary>

+ 11 - 14
Terminal.Gui/Views/TextField.cs

@@ -110,6 +110,8 @@ namespace Terminal.Gui {
 
 			Initialized += TextField_Initialized;
 
+			LayoutComplete += TextField_LayoutComplete;
+
 			// Things this view knows how to do
 			AddCommand (Command.DeleteCharRight, () => { DeleteCharRight (); return true; });
 			AddCommand (Command.DeleteCharLeft, () => { DeleteCharLeft (false); return true; });
@@ -219,6 +221,15 @@ namespace Terminal.Gui {
 			KeyBindings.Add (ContextMenu.Key.KeyCode, KeyBindingScope.HotKey, Command.ShowContextMenu);
 		}
 
+		private void TextField_LayoutComplete (object sender, LayoutEventArgs e)
+		{
+			// Don't let height > 1
+			if (Frame.Height > 1) {
+				Height = 1;
+			} 
+		}
+
+
 		private MenuBarItem BuildContextMenuBarItem ()
 		{
 			return new MenuBarItem (new MenuItem [] {
@@ -280,20 +291,6 @@ namespace Terminal.Gui {
 		/// </summary>
 		public IAutocomplete Autocomplete { get; set; } = new TextFieldAutocomplete ();
 
-		///<inheritdoc/>
-		public override Rect Frame {
-			get => base.Frame;
-			set {
-				if (value.Height > 1) {
-					base.Frame = new Rect (value.X, value.Y, value.Width, 1);
-					Height = 1;
-				} else {
-					base.Frame = value;
-				}
-				Adjust ();
-			}
-		}
-
 		/// <summary>
 		///   Sets or gets the text held by the view.
 		/// </summary>

Fișier diff suprimat deoarece este prea mare
+ 1050 - 859
Terminal.Gui/Views/TextView.cs


+ 32 - 17
Terminal.Gui/Views/Toplevel.cs

@@ -12,7 +12,7 @@ namespace Terminal.Gui;
 /// <remarks>
 ///         <para>
 ///         Toplevels can run as modal (popup) views, started by calling
-///         <see cref="Application.Run(Toplevel, System.Func{System.Exception,bool}(System.Exception))"/>.
+///         <see cref="Application.Run(Toplevel, System.Func{System.Exception,bool})"/>.
 ///         They return control to the caller when <see cref="Application.RequestStop(Toplevel)"/> has
 ///         been called (which sets the <see cref="Toplevel.Running"/> property to <c>false</c>).
 ///         </para>
@@ -22,14 +22,13 @@ namespace Terminal.Gui;
 ///         The application Toplevel can be accessed via <see cref="Application.Top"/>. Additional
 ///         Toplevels can be created
 ///         and run (e.g. <see cref="Dialog"/>s. To run a Toplevel, create the <see cref="Toplevel"/> and
-///         call <see cref="Application.Run(Toplevel, System.Func{System.Exception,bool}(System.Exception))"/>.
+///         call <see cref="Application.Run(Toplevel, Func{Exception, bool})"/>.
 ///         </para>
 /// </remarks>
 public partial class Toplevel : View {
 	internal static Point? _dragPosition;
 	Point _startGrabPoint;
 
-	// BUGBUG: Remove; Toplevel should be ComputedLayout
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Toplevel"/> class with the specified
 	/// <see cref="LayoutStyle.Absolute"/> layout.
@@ -42,7 +41,8 @@ public partial class Toplevel : View {
 
 	/// <summary>
 	/// Initializes a new instance of the <see cref="Toplevel"/> class with
-	/// <see cref="LayoutStyle.Computed"/> layout, defaulting to full screen. The <see cref="View.Width"/> and <see cref="View.Height"/> properties
+	/// <see cref="LayoutStyle.Computed"/> layout, defaulting to full screen. The <see cref="View.Width"/> and
+	/// <see cref="View.Height"/> properties
 	/// will be set to the dimensions of the terminal using <see cref="Dim.Fill"/>.
 	/// </summary>
 	public Toplevel ()
@@ -306,17 +306,25 @@ public partial class Toplevel : View {
 		KeyBindings.Add ((KeyCode)Application.QuitKey, Command.QuitToplevel);
 
 		KeyBindings.Add (KeyCode.CursorRight, Command.NextView);
-		KeyBindings.Add (KeyCode.CursorDown, Command.NextView);
-		KeyBindings.Add (KeyCode.CursorLeft, Command.PreviousView);
-		KeyBindings.Add (KeyCode.CursorUp, Command.PreviousView);
-
-		KeyBindings.Add (KeyCode.Tab, Command.NextView);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask, Command.PreviousView);
-		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask, Command.NextViewOrTop);
+		KeyBindings.Add (KeyCode.CursorDown,  Command.NextView);
+		KeyBindings.Add (KeyCode.CursorLeft,  Command.PreviousView);
+		KeyBindings.Add (KeyCode.CursorUp,    Command.PreviousView);
+		KeyBindings.Add (KeyCode.CursorDown,  Command.NextView);
+		KeyBindings.Add (KeyCode.CursorLeft,  Command.PreviousView);
+		KeyBindings.Add (KeyCode.CursorUp,    Command.PreviousView);
+
+		KeyBindings.Add (KeyCode.Tab,                                        Command.NextView);
+		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask,                    Command.PreviousView);
+		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask,                     Command.NextViewOrTop);
+		KeyBindings.Add (KeyCode.Tab,                                        Command.NextView);
+		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask,                    Command.PreviousView);
+		KeyBindings.Add (KeyCode.Tab | KeyCode.CtrlMask,                     Command.NextViewOrTop);
 		KeyBindings.Add (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask, Command.PreviousViewOrTop);
 
-		KeyBindings.Add (KeyCode.F5, Command.Refresh);
-		KeyBindings.Add ((KeyCode)Application.AlternateForwardKey, Command.NextViewOrTop);     // Needed on Unix
+		KeyBindings.Add (KeyCode.F5,                                Command.Refresh);
+		KeyBindings.Add ((KeyCode)Application.AlternateForwardKey,  Command.NextViewOrTop);     // Needed on Unix
+		KeyBindings.Add (KeyCode.F5,                                Command.Refresh);
+		KeyBindings.Add ((KeyCode)Application.AlternateForwardKey,  Command.NextViewOrTop);     // Needed on Unix
 		KeyBindings.Add ((KeyCode)Application.AlternateBackwardKey, Command.PreviousViewOrTop); // Needed on Unix
 
 #if UNIX_KEY_BINDINGS
@@ -389,6 +397,12 @@ public partial class Toplevel : View {
 		QuitKeyChanged?.Invoke (this, e);
 	}
 
+	/// <summary>
+	/// Convenience factory method that creates a new Toplevel with the current terminal dimensions.
+	/// </summary>
+	/// <returns>The created Toplevel.</returns>
+	public static Toplevel Create () => new (new Rect (0, 0, Driver.Cols, Driver.Rows));
+
 	void MovePreviousViewOrTop ()
 	{
 		if (Application.OverlappedTop == null) {
@@ -676,15 +690,16 @@ public partial class Toplevel : View {
 	public virtual void PositionToplevel (Toplevel top)
 	{
 		var superView = GetLocationThatFits (top, top.Frame.X, top.Frame.Y,
-			out var nx, out var ny, out _, out var sb);
+			out var nx,                       out var ny,  out _, out var sb);
 		var layoutSubviews = false;
 		var maxWidth = 0;
 		if (superView.Margin != null && superView == top.SuperView) {
 			maxWidth -= superView.GetFramesThickness ().Left + superView.GetFramesThickness ().Right;
 		}
-		if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped) &&
-		    (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) &&
-		    top.LayoutStyle == LayoutStyle.Computed) {
+		if ((superView != top || top?.SuperView != null || top != Application.Top && top.Modal || top?.SuperView == null && top.IsOverlapped)
+		    // BUGBUG: Prevously PositionToplevel required LayotuStyle.Computed
+		    &&
+		    (top.Frame.X + top.Frame.Width > maxWidth || ny > top.Frame.Y) /*&& top.LayoutStyle == LayoutStyle.Computed*/) {
 
 			if ((top.X == null || top.X is Pos.PosAbsolute) && top.Frame.X != nx) {
 				top.X = nx;

+ 4 - 0
UICatalog/Properties/launchSettings.json

@@ -65,6 +65,10 @@
     "MenuBarScenario": {
       "commandName": "Project",
       "commandLineArgs": "MenuBar"
+    },
+    "ListView & ComboBox": {
+      "commandName": "Project",
+      "commandLineArgs": "\"ListView & ComboBox\""
     }
   }
 }

+ 1 - 1
UICatalog/Scenarios/ASCIICustomButton.cs

@@ -77,7 +77,7 @@ namespace UICatalog.Scenarios {
 				};
 
 				AutoSize = false;
-				LayoutStyle = LayoutStyle.Absolute;
+				//LayoutStyle = LayoutStyle.Absolute;
 
 				var fillText = new System.Text.StringBuilder ();
 				for (int i = 0; i < Bounds.Height; i++) {

+ 2 - 3
UICatalog/Scenarios/AllViewsTester.cs

@@ -111,7 +111,6 @@ public class AllViewsTester : Scenario {
 		_computedCheckBox = new CheckBox ("Computed Layout", true) { X = 0, Y = 0 };
 		_computedCheckBox.Toggled += (s, e) => {
 			if (_curView != null) {
-				_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed;
 				_hostPane.LayoutSubviews ();
 			}
 		};
@@ -246,7 +245,7 @@ public class AllViewsTester : Scenario {
 		var layout = view.LayoutStyle;
 
 		try {
-			view.LayoutStyle = LayoutStyle.Absolute;
+			//view.LayoutStyle = LayoutStyle.Absolute;
 
 			view.X = _xRadioGroup.SelectedItem switch {
 				0 => Pos.Percent (_xVal),
@@ -280,7 +279,7 @@ public class AllViewsTester : Scenario {
 		} catch (Exception e) {
 			MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
 		} finally {
-			view.LayoutStyle = layout;
+			//view.LayoutStyle = layout;
 		}
 		UpdateTitle (view);
 	}

+ 1 - 1
UICatalog/Scenarios/ColorPicker.cs

@@ -58,7 +58,7 @@ namespace UICatalog.Scenarios {
 			Win.Add (_foregroundColorLabel);
 
 			// Background ColorPicker.
-			backgroundColorPicker = new ColorPicker () { 
+			backgroundColorPicker = new ColorPicker () {
 				Title = "Background Color",
 				Y = 0,
 				X = 0,

+ 314 - 287
UICatalog/Scenarios/ListColumns.cs

@@ -3,331 +3,358 @@ using System.Collections;
 using System.Collections.Generic;
 using System.Data;
 using Terminal.Gui;
-using static Terminal.Gui.TableView;
-
-namespace UICatalog.Scenarios {
-
-	[ScenarioMetadata (Name: "ListColumns", Description: "Implements a columned list via a data table.")]
-	[ScenarioCategory ("TableView")]
-	[ScenarioCategory ("Controls")]
-	[ScenarioCategory ("Dialogs")]
-	[ScenarioCategory ("Text and Formatting")]
-	[ScenarioCategory ("Top Level Windows")]
-	public class ListColumns : Scenario {
-		TableView listColView;
-		DataTable currentTable;
-		private MenuItem _miCellLines;
-		private MenuItem _miExpandLastColumn;
-		private MenuItem _miAlwaysUseNormalColorForVerticalCellLines;
-		private MenuItem _miSmoothScrolling;
-		private MenuItem _miAlternatingColors;
-		private MenuItem _miCursor;
-		private MenuItem _miTopline;
-		private MenuItem _miBottomline;
-		private MenuItem _miOrientVertical;
-		private MenuItem _miScrollParallel;
-
-		ColorScheme alternatingColorScheme;
-
-		public override void Setup ()
-		{
-			Win.Title = this.GetName ();
-			Win.Y = 1; // menu
-			Win.Height = Dim.Fill (1); // status bar
-
-			this.listColView = new TableView () {
-				X = 0,
-				Y = 0,
-				Width = Dim.Fill (),
-				Height = Dim.Fill (1),
-				Style = new TableStyle {
-					ShowHeaders = false,
-					ShowHorizontalHeaderOverline = false,
-					ShowHorizontalHeaderUnderline = false,
-					ShowHorizontalBottomline = false,
-					ExpandLastColumn = false,
-				}
-			};
-			var listColStyle = new ListColumnStyle ();
-
-			var menu = new MenuBar (new MenuBarItem [] {
-				new MenuBarItem ("_File", new MenuItem [] {
-					new MenuItem ("Open_BigListExample", "", () => OpenSimpleList (true)),
-					new MenuItem ("Open_SmListExample", "", () => OpenSimpleList (false)),
-					new MenuItem ("_CloseExample", "", () => CloseExample ()),
-					new MenuItem ("_Quit", "", () => Quit()),
-				}),
-				new MenuBarItem ("_View", new MenuItem [] {
-					_miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) { Checked = listColView.Style.ShowHorizontalHeaderOverline, CheckType = MenuItemCheckStyle.Checked },
-					_miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) { Checked = listColView.Style.ShowHorizontalBottomline, CheckType = MenuItemCheckStyle.Checked },
-					_miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) { Checked = listColView.Style.ShowVerticalCellLines, CheckType = MenuItemCheckStyle.Checked },
-					_miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) { Checked = listColView.Style.ExpandLastColumn, CheckType = MenuItemCheckStyle.Checked },
-					_miAlwaysUseNormalColorForVerticalCellLines = new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "", () => ToggleAlwaysUseNormalColorForVerticalCellLines ()) { Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines, CheckType = MenuItemCheckStyle.Checked },
-					_miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) { Checked = listColView.Style.SmoothHorizontalScrolling, CheckType = MenuItemCheckStyle.Checked },
-					_miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ()) { CheckType = MenuItemCheckStyle.Checked},
-					_miCursor = new MenuItem ("Invert Selected Cell First Character", "", () => ToggleInvertSelectedCellFirstCharacter ()) { Checked = listColView.Style.InvertSelectedCellFirstCharacter,CheckType = MenuItemCheckStyle.Checked},
-				}),
-				new MenuBarItem ("_List", new MenuItem [] {
-					//new MenuItem ("_Hide Headers", "", HideHeaders),
-					_miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) { Checked = listColStyle.Orientation == Orientation.Vertical, CheckType = MenuItemCheckStyle.Checked },
-					_miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ()) { Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked },
-					new MenuItem ("Set _Max Cell Width", "", SetListMaxWidth),
-					new MenuItem ("Set Mi_n Cell Width", "", SetListMinWidth),
-				}),
-			});
-
-			Application.Top.Add (menu);
-
-			var statusBar = new StatusBar (new StatusItem [] {
-				new StatusItem(KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)),
-				new StatusItem(KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()),
-				new StatusItem(KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)),
-				new StatusItem(Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit()),
-			});
-			Application.Top.Add (statusBar);
-
-			Win.Add (listColView);
-
-			var selectedCellLabel = new Label () {
-				X = 0,
-				Y = Pos.Bottom (listColView),
-				Text = "0,0",
-				Width = Dim.Fill (),
-				TextAlignment = TextAlignment.Right
-
-			};
-
-			Win.Add (selectedCellLabel);
-
-			listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; };
-			listColView.KeyDown += TableViewKeyPress;
-
-			SetupScrollBar ();
-
-			alternatingColorScheme = new ColorScheme () {
-
-				Disabled = Win.ColorScheme.Disabled,
-				HotFocus = Win.ColorScheme.HotFocus,
-				Focus = Win.ColorScheme.Focus,
-				Normal = new Attribute (Color.White, Color.BrightBlue)
-			};
-
-			// if user clicks the mouse in TableView
-			listColView.MouseClick += (s, e) => {
-
-				listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out int? clickedCol);
-			};
-
-			listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
-		}
-
-		private void SetupScrollBar ()
-		{
-			var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true);
 
-			scrollBar.ChangedPosition += (s, e) => {
-				listColView.RowOffset = scrollBar.Position;
-				if (listColView.RowOffset != scrollBar.Position) {
-					scrollBar.Position = listColView.RowOffset;
-				}
-				listColView.SetNeedsDisplay ();
-			};
-			/*
-			scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
-				listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position;
-				if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) {
-					scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("ListColumns", "Implements a columned list via a data table.")]
+[ScenarioCategory ("TableView")]
+[ScenarioCategory ("Controls")]
+[ScenarioCategory ("Dialogs")]
+[ScenarioCategory ("Text and Formatting")]
+[ScenarioCategory ("Top Level Windows")]
+public class ListColumns : Scenario {
+	MenuItem _miAlternatingColors;
+	MenuItem _miAlwaysUseNormalColorForVerticalCellLines;
+	MenuItem _miBottomline;
+	MenuItem _miCellLines;
+	MenuItem _miCursor;
+	MenuItem _miExpandLastColumn;
+	MenuItem _miOrientVertical;
+	MenuItem _miScrollParallel;
+	MenuItem _miSmoothScrolling;
+	MenuItem _miTopline;
+
+	ColorScheme alternatingColorScheme;
+	DataTable currentTable;
+	TableView listColView;
+
+	public override void Setup ()
+	{
+		Win.Title = GetName ();
+		Win.Y = 1;                 // menu
+		Win.Height = Dim.Fill (1); // status bar
+
+		listColView = new TableView {
+			X = 0,
+			Y = 0,
+			Width = Dim.Fill (),
+			Height = Dim.Fill (1),
+			Style = new TableStyle {
+				ShowHeaders = false,
+				ShowHorizontalHeaderOverline = false,
+				ShowHorizontalHeaderUnderline = false,
+				ShowHorizontalBottomline = false,
+				ExpandLastColumn = false
+			}
+		};
+		var listColStyle = new ListColumnStyle ();
+
+		var menu = new MenuBar (new MenuBarItem [] {
+			new ("_File", new MenuItem [] {
+				new ("Open_BigListExample", "", () => OpenSimpleList (true)),
+				new ("Open_SmListExample", "", () => OpenSimpleList (false)),
+				new ("_CloseExample", "", () => CloseExample ()),
+				new ("_Quit", "", () => Quit ())
+			}),
+			new ("_View", new [] {
+				_miTopline = new MenuItem ("_TopLine", "", () => ToggleTopline ()) {
+					Checked = listColView.Style.ShowHorizontalHeaderOverline,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miBottomline = new MenuItem ("_BottomLine", "", () => ToggleBottomline ()) {
+					Checked = listColView.Style.ShowHorizontalBottomline,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miCellLines = new MenuItem ("_CellLines", "", () => ToggleCellLines ()) {
+					Checked = listColView.Style.ShowVerticalCellLines,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miExpandLastColumn = new MenuItem ("_ExpandLastColumn", "", () => ToggleExpandLastColumn ()) {
+					Checked = listColView.Style.ExpandLastColumn,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miAlwaysUseNormalColorForVerticalCellLines =
+					new MenuItem ("_AlwaysUseNormalColorForVerticalCellLines", "",
+						() => ToggleAlwaysUseNormalColorForVerticalCellLines ()) {
+						Checked = listColView.Style.AlwaysUseNormalColorForVerticalCellLines,
+						CheckType = MenuItemCheckStyle.Checked
+					},
+				_miSmoothScrolling = new MenuItem ("_SmoothHorizontalScrolling", "", () => ToggleSmoothScrolling ()) {
+					Checked = listColView.Style.SmoothHorizontalScrolling,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miAlternatingColors = new MenuItem ("Alternating Colors", "", () => ToggleAlternatingColors ())
+					{ CheckType = MenuItemCheckStyle.Checked },
+				_miCursor = new MenuItem ("Invert Selected Cell First Character", "",
+					() => ToggleInvertSelectedCellFirstCharacter ()) {
+					Checked = listColView.Style.InvertSelectedCellFirstCharacter,
+					CheckType = MenuItemCheckStyle.Checked
 				}
-				listColView.SetNeedsDisplay ();
-			};
-			*/
+			}),
+			new ("_List", new [] {
+				//new MenuItem ("_Hide Headers", "", HideHeaders),
+				_miOrientVertical = new MenuItem ("_OrientVertical", "", () => ToggleVerticalOrientation ()) {
+					Checked = listColStyle.Orientation == Orientation.Vertical,
+					CheckType = MenuItemCheckStyle.Checked
+				},
+				_miScrollParallel = new MenuItem ("_ScrollParallel", "", () => ToggleScrollParallel ())
+					{ Checked = listColStyle.ScrollParallel, CheckType = MenuItemCheckStyle.Checked },
+				new ("Set _Max Cell Width", "", SetListMaxWidth),
+				new ("Set Mi_n Cell Width", "", SetListMinWidth)
+			})
+		});
+
+		Application.Top.Add (menu);
+
+		var statusBar = new StatusBar (new StatusItem [] {
+			new (KeyCode.F2, "~F2~ OpenBigListEx", () => OpenSimpleList (true)),
+			new (KeyCode.F3, "~F3~ CloseExample", () => CloseExample ()),
+			new (KeyCode.F4, "~F4~ OpenSmListEx", () => OpenSimpleList (false)),
+			new (Application.QuitKey, $"{Application.QuitKey} to Quit", () => Quit ())
+		});
+		Application.Top.Add (statusBar);
+
+		Win.Add (listColView);
+
+		var selectedCellLabel = new Label {
+			X = 0,
+			Y = Pos.Bottom (listColView),
+			Text = "0,0",
+			Width = Dim.Fill (),
+			TextAlignment = TextAlignment.Right
+
+		};
+
+		Win.Add (selectedCellLabel);
+
+		listColView.SelectedCellChanged += (s, e) => { selectedCellLabel.Text = $"{listColView.SelectedRow},{listColView.SelectedColumn}"; };
+		listColView.KeyDown += TableViewKeyPress;
+
+		SetupScrollBar ();
+
+		alternatingColorScheme = new ColorScheme {
+
+			Disabled = Win.ColorScheme.Disabled,
+			HotFocus = Win.ColorScheme.HotFocus,
+			Focus = Win.ColorScheme.Focus,
+			Normal = new Attribute (Color.White, Color.BrightBlue)
+		};
+
+		// if user clicks the mouse in TableView
+		listColView.MouseClick += (s, e) => {
+
+			listColView.ScreenToCell (e.MouseEvent.X, e.MouseEvent.Y, out var clickedCol);
+		};
+
+		listColView.KeyBindings.Add (KeyCode.Space, Command.ToggleChecked);
+	}
+
+	void SetupScrollBar ()
+	{
+		var scrollBar = new ScrollBarView (listColView, true); // (listColView, true, true);
 
-			listColView.DrawContent += (s, e) => {
-				scrollBar.Size = listColView.Table?.Rows ?? 0;
+		scrollBar.ChangedPosition += (s, e) => {
+			listColView.RowOffset = scrollBar.Position;
+			if (listColView.RowOffset != scrollBar.Position) {
 				scrollBar.Position = listColView.RowOffset;
-				//scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0;
-				//scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
-				scrollBar.Refresh ();
-			};
+			}
+			listColView.SetNeedsDisplay ();
+		};
+		/*
+		scrollBar.OtherScrollBarView.ChangedPosition += (s,e) => {
+			listColView.ColumnOffset = scrollBar.OtherScrollBarView.Position;
+			if (listColView.ColumnOffset != scrollBar.OtherScrollBarView.Position) {
+				scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
+			}
+			listColView.SetNeedsDisplay ();
+		};
+		*/
 
-		}
+		listColView.DrawContent += (s, e) => {
+			scrollBar.Size = listColView.Table?.Rows ?? 0;
+			scrollBar.Position = listColView.RowOffset;
+			//scrollBar.OtherScrollBarView.Size = listColView.Table?.Columns - 1 ?? 0;
+			//scrollBar.OtherScrollBarView.Position = listColView.ColumnOffset;
+			scrollBar.Refresh ();
+		};
 
-		private void TableViewKeyPress (object sender, Key e)
-		{
-			if (e.KeyCode == KeyCode.Delete) {
+	}
 
-				// set all selected cells to null
-				foreach (var pt in listColView.GetAllSelectedCells ()) {
-					currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
-				}
+	void TableViewKeyPress (object sender, Key e)
+	{
+		if (e.KeyCode == KeyCode.Delete) {
 
-				listColView.Update ();
-				e.Handled = true;
+			// set all selected cells to null
+			foreach (var pt in listColView.GetAllSelectedCells ()) {
+				currentTable.Rows [pt.Y] [pt.X] = DBNull.Value;
 			}
 
-		}
-
-		private void ToggleTopline ()
-		{
-			_miTopline.Checked = !_miTopline.Checked;
-			listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked;
-			listColView.Update ();
-		}
-		private void ToggleBottomline ()
-		{
-			_miBottomline.Checked = !_miBottomline.Checked;
-			listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked;
 			listColView.Update ();
+			e.Handled = true;
 		}
-		private void ToggleExpandLastColumn ()
-		{
-			_miExpandLastColumn.Checked = !_miExpandLastColumn.Checked;
-			listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked;
 
-			listColView.Update ();
+	}
 
-		}
+	void ToggleTopline ()
+	{
+		_miTopline.Checked = !_miTopline.Checked;
+		listColView.Style.ShowHorizontalHeaderOverline = (bool)_miTopline.Checked;
+		listColView.Update ();
+	}
 
-		private void ToggleAlwaysUseNormalColorForVerticalCellLines ()
-		{
-			_miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked;
-			listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked;
+	void ToggleBottomline ()
+	{
+		_miBottomline.Checked = !_miBottomline.Checked;
+		listColView.Style.ShowHorizontalBottomline = (bool)_miBottomline.Checked;
+		listColView.Update ();
+	}
 
-			listColView.Update ();
-		}
-		private void ToggleSmoothScrolling ()
-		{
-			_miSmoothScrolling.Checked = !_miSmoothScrolling.Checked;
-			listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked;
+	void ToggleExpandLastColumn ()
+	{
+		_miExpandLastColumn.Checked = !_miExpandLastColumn.Checked;
+		listColView.Style.ExpandLastColumn = (bool)_miExpandLastColumn.Checked;
 
-			listColView.Update ();
+		listColView.Update ();
 
-		}
-		private void ToggleCellLines ()
-		{
-			_miCellLines.Checked = !_miCellLines.Checked;
-			listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked;
-			listColView.Update ();
-		}
-		private void ToggleAlternatingColors ()
-		{
-			//toggle menu item
-			_miAlternatingColors.Checked = !_miAlternatingColors.Checked;
-
-			if (_miAlternatingColors.Checked == true) {
-				listColView.Style.RowColorGetter = (a) => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; };
-			} else {
-				listColView.Style.RowColorGetter = null;
-			}
-			listColView.SetNeedsDisplay ();
-		}
+	}
 
-		private void ToggleInvertSelectedCellFirstCharacter ()
-		{
-			//toggle menu item
-			_miCursor.Checked = !_miCursor.Checked;
-			listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked;
-			listColView.SetNeedsDisplay ();
-		}
+	void ToggleAlwaysUseNormalColorForVerticalCellLines ()
+	{
+		_miAlwaysUseNormalColorForVerticalCellLines.Checked = !_miAlwaysUseNormalColorForVerticalCellLines.Checked;
+		listColView.Style.AlwaysUseNormalColorForVerticalCellLines = (bool)_miAlwaysUseNormalColorForVerticalCellLines.Checked;
 
-		private void ToggleVerticalOrientation ()
-		{
-			_miOrientVertical.Checked = !_miOrientVertical.Checked;
-			if ((ListTableSource)listColView.Table != null) {
-				((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal;
-				listColView.SetNeedsDisplay ();
-			}
-		}
+		listColView.Update ();
+	}
 
-		private void ToggleScrollParallel ()
-		{
-			_miScrollParallel.Checked = !_miScrollParallel.Checked;
-			if ((ListTableSource)listColView.Table != null) {
-				((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked;
-				listColView.SetNeedsDisplay ();
-			}
+	void ToggleSmoothScrolling ()
+	{
+		_miSmoothScrolling.Checked = !_miSmoothScrolling.Checked;
+		listColView.Style.SmoothHorizontalScrolling = (bool)_miSmoothScrolling.Checked;
+
+		listColView.Update ();
+
+	}
+
+	void ToggleCellLines ()
+	{
+		_miCellLines.Checked = !_miCellLines.Checked;
+		listColView.Style.ShowVerticalCellLines = (bool)_miCellLines.Checked;
+		listColView.Update ();
+	}
+
+	void ToggleAlternatingColors ()
+	{
+		//toggle menu item
+		_miAlternatingColors.Checked = !_miAlternatingColors.Checked;
+
+		if (_miAlternatingColors.Checked == true) {
+			listColView.Style.RowColorGetter = a => { return a.RowIndex % 2 == 0 ? alternatingColorScheme : null; };
+		} else {
+			listColView.Style.RowColorGetter = null;
 		}
+		listColView.SetNeedsDisplay ();
+	}
+
+	void ToggleInvertSelectedCellFirstCharacter ()
+	{
+		//toggle menu item
+		_miCursor.Checked = !_miCursor.Checked;
+		listColView.Style.InvertSelectedCellFirstCharacter = (bool)_miCursor.Checked;
+		listColView.SetNeedsDisplay ();
+	}
 
-		private void SetListMinWidth ()
-		{
-			RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, (s) => s.MinCellWidth);
+	void ToggleVerticalOrientation ()
+	{
+		_miOrientVertical.Checked = !_miOrientVertical.Checked;
+		if ((ListTableSource)listColView.Table != null) {
+			((ListTableSource)listColView.Table).Style.Orientation = (bool)_miOrientVertical.Checked ? Orientation.Vertical : Orientation.Horizontal;
 			listColView.SetNeedsDisplay ();
 		}
+	}
 
-		private void SetListMaxWidth ()
-		{
-			RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, (s) => s.MaxCellWidth);
+	void ToggleScrollParallel ()
+	{
+		_miScrollParallel.Checked = !_miScrollParallel.Checked;
+		if ((ListTableSource)listColView.Table != null) {
+			((ListTableSource)listColView.Table).Style.ScrollParallel = (bool)_miScrollParallel.Checked;
 			listColView.SetNeedsDisplay ();
 		}
+	}
 
-		private void RunListWidthDialog (string prompt, Action<TableView, int> setter, Func<TableView, int> getter)
-		{
-			var accepted = false;
-			var ok = new Button ("Ok", is_default: true);
-			ok.Clicked += (s, e) => { accepted = true; Application.RequestStop (); };
-			var cancel = new Button ("Cancel");
-			cancel.Clicked += (s, e) => { Application.RequestStop (); };
-			var d = new Dialog (ok, cancel) { Title = prompt };
-
-			var tf = new TextField () {
-				Text = getter (listColView).ToString (),
-				X = 0,
-				Y = 1,
-				Width = Dim.Fill ()
-			};
-
-			d.Add (tf);
-			tf.SetFocus ();
-
-			Application.Run (d);
-
-			if (accepted) {
-
-				try {
-					setter (listColView, int.Parse (tf.Text));
-				} catch (Exception ex) {
-					MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok");
-				}
-			}
-		}
+	void SetListMinWidth ()
+	{
+		RunListWidthDialog ("MinCellWidth", (s, v) => s.MinCellWidth = v, s => s.MinCellWidth);
+		listColView.SetNeedsDisplay ();
+	}
 
-		private void CloseExample ()
-		{
-			listColView.Table = null;
-		}
+	void SetListMaxWidth ()
+	{
+		RunListWidthDialog ("MaxCellWidth", (s, v) => s.MaxCellWidth = v, s => s.MaxCellWidth);
+		listColView.SetNeedsDisplay ();
+	}
 
-		private void Quit ()
-		{
+	void RunListWidthDialog (string prompt, Action<TableView, int> setter, Func<TableView, int> getter)
+	{
+		var accepted = false;
+		var ok = new Button ("Ok", true);
+		ok.Clicked += (s, e) => {
+			accepted = true;
 			Application.RequestStop ();
-		}
+		};
+		var cancel = new Button ("Cancel");
+		cancel.Clicked += (s, e) => { Application.RequestStop (); };
+		var d = new Dialog (ok, cancel) { Title = prompt };
 
-		private void OpenSimpleList (bool big)
-		{
-			SetTable (BuildSimpleList (big ? 1023 : 31));
-		}
+		var tf = new TextField {
+			Text = getter (listColView).ToString (),
+			X = 0,
+			Y = 1,
+			Width = Dim.Fill ()
+		};
+
+		d.Add (tf);
+		tf.SetFocus ();
+
+		Application.Run (d);
+
+		if (accepted) {
 
-		private void SetTable (IList list)
-		{
-			listColView.Table = new ListTableSource (list, listColView);
-			if ((ListTableSource)listColView.Table != null) {
-				currentTable = ((ListTableSource)listColView.Table).DataTable;
+			try {
+				setter (listColView, int.Parse (tf.Text));
+			} catch (Exception ex) {
+				MessageBox.ErrorQuery (60, 20, "Failed to set", ex.Message, "Ok");
 			}
 		}
+	}
 
-		/// <summary>
-		/// Builds a simple list in which values are the index.  This helps testing that scrolling etc is working correctly and not skipping out values when paging
-		/// </summary>
-		/// <param name="items"></param>
-		/// <returns></returns>
-		public static IList BuildSimpleList (int items)
-		{
-			var list = new List<object> ();
-
-			for (int i = 0; i < items; i++) {
-				list.Add ("Item " + i);
-			}
+	void CloseExample () => listColView.Table = null;
+
+	void Quit () => Application.RequestStop ();
+
+	void OpenSimpleList (bool big) => SetTable (BuildSimpleList (big ? 1023 : 31));
 
-			return list;
+	void SetTable (IList list)
+	{
+		listColView.Table = new ListTableSource (list, listColView);
+		if ((ListTableSource)listColView.Table != null) {
+			currentTable = ((ListTableSource)listColView.Table).DataTable;
 		}
 	}
+
+	/// <summary>
+	/// Builds a simple list in which values are the index.  This helps testing that scrolling etc is working correctly and not
+	/// skipping out values when paging
+	/// </summary>
+	/// <param name="items"></param>
+	/// <returns></returns>
+	public static IList BuildSimpleList (int items)
+	{
+		var list = new List<object> ();
+
+		for (var i = 0; i < items; i++) {
+			list.Add ("Item " + i);
+		}
+
+		return list;
+	}
 }

+ 0 - 4
UICatalog/Scenarios/Sliders.cs

@@ -104,9 +104,7 @@ public class Sliders : Scenario {
 					s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.HLine };
 
 					if (prev == null) {
-						s.LayoutStyle = LayoutStyle.Absolute;
 						s.Y = 0;
-						s.LayoutStyle = LayoutStyle.Computed;
 					} else {
 						s.Y = Pos.Bottom (prev) + 1;
 					}
@@ -119,9 +117,7 @@ public class Sliders : Scenario {
 					s.Style.SpaceChar = new Cell { Rune = CM.Glyphs.VLine };
 
 					if (prev == null) {
-						s.LayoutStyle = LayoutStyle.Absolute;
 						s.X = 0;
-						s.LayoutStyle = LayoutStyle.Computed;
 					} else {
 						s.X = Pos.Right (prev) + 2;
 					}

+ 1 - 1
UICatalog/Scenarios/TabViewExample.cs

@@ -59,7 +59,7 @@ namespace UICatalog.Scenarios {
 			};
 
 			tabView.AddTab (new Tab ("Tab1", new Label ("hodor!")), false);
-			tabView.AddTab (new Tab ("Tab2", new Label ("durdur")), false);
+			tabView.AddTab (new Tab ("Tab2", new TextField ("durdur")), false);
 			tabView.AddTab (new Tab ("Interactive Tab", GetInteractiveTab ()), false);
 			tabView.AddTab (new Tab ("Big Text", GetBigTextFileTab ()), false);
 			tabView.AddTab (new Tab (

+ 1 - 1
UICatalog/Scenarios/Text.cs

@@ -233,7 +233,7 @@ namespace UICatalog.Scenarios {
 			};
 			var appendAutocompleteTextField = new TextField () {
 				X = Pos.Right (labelAppendAutocomplete),
-				Y = labelAppendAutocomplete.Y,
+				Y = Pos.Bottom (labelAppendAutocomplete),
 				Width = Dim.Fill ()
 			};
 			appendAutocompleteTextField.Autocomplete = new AppendAutocomplete (appendAutocompleteTextField);

+ 6 - 12
UICatalog/UICatalog.cs

@@ -63,6 +63,8 @@ class UICatalogApp {
 		/* etc. */
 	}
 
+	static Options _options;
+
 	static int Main (string [] args)
 	{
 		Console.OutputEncoding = Encoding.Default;
@@ -101,21 +103,13 @@ class UICatalogApp {
 				Scenario = context.ParseResult.GetValueForArgument (scenarioArgument),
 				/* etc. */
 			};
-			context.ExitCode = CommandWrapper (options);
+			// See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery
+			_options = options; ;
 		});
 
-		return rootCommand.Invoke (args);
-	}
+		rootCommand.Invoke (args);
 
-	// See https://github.com/dotnet/command-line-api/issues/796 for the rationale behind this hackery
-	static int CommandWrapper (Options options)
-	{
-		try {
-			UICatalogMain (options);
-		} catch (Exception e) {
-			Console.WriteLine (e.ToString());
-			return 1;
-		}
+		UICatalogMain (_options);
 		return 0;
 	}
 

Fișier diff suprimat deoarece este prea mare
+ 741 - 757
UnitTests/Dialogs/DialogTests.cs


+ 52 - 44
UnitTests/Input/KeyTests.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using System.Text;
 using Xunit;
 using Xunit.Abstractions;
@@ -37,7 +38,9 @@ public class KeyTests {
 	[InlineData ('!', (KeyCode)'!')]
 	[InlineData ('\r', KeyCode.Enter)]
 	[InlineData ('\t', KeyCode.Tab)]
+#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to
 	[InlineData ('\r', (KeyCode)13)]
+#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to
 	[InlineData ('\n', (KeyCode)10)]
 	[InlineData ('ó', (KeyCode)'ó')]
 	[InlineData ('Ó', (KeyCode)'Ó')]
@@ -54,50 +57,54 @@ public class KeyTests {
 		Assert.Equal (expectedKeyCode, key.KeyCode);
 	}
 
+	public static IEnumerable<object []> ConstructorStrings ()
+	{
+		yield return new object [] { "a", new Key (KeyCode.A) };
+		yield return new object [] { "Ctrl+A", new Key (KeyCode.A | KeyCode.CtrlMask) };
+		yield return new object [] { "Alt+A", new Key (KeyCode.A | KeyCode.AltMask) };
+		yield return new object [] { "Shift+A", new Key (KeyCode.A | KeyCode.ShiftMask) };
+		yield return new object [] { "A", new Key (KeyCode.A | KeyCode.ShiftMask) };
+		yield return new object [] { "â", new Key ((KeyCode)'â')};
+		yield return new object [] { "Shift+â", new Key ((KeyCode)'â' | KeyCode.ShiftMask) };
+		yield return new object [] { "Shift+Â", new Key ((KeyCode)'Â' | KeyCode.ShiftMask) };
+		yield return new object [] { "Ctrl+Shift+CursorUp", new Key (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.CursorUp) };
+		yield return new object [] { "Ctrl+Alt+Shift+CursorUp", new Key (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp) };
+		yield return new object [] { "ctrl+alt+shift+cursorup", new Key (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp) };
+		yield return new object [] { "CTRL+ALT+SHIFT+CURSORUP", new Key (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp) };
+		yield return new object [] { "Ctrl+Alt+Shift+Delete", new Key (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Delete) };
+		yield return new object [] { "Ctrl+Alt+Shift+Enter", new Key (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Enter) };
+		yield return new object [] { "Tab", new Key (KeyCode.Tab) };
+		yield return new object [] { "Shift+Tab", new Key (KeyCode.Tab | KeyCode.ShiftMask) };
+		yield return new object [] { "Ctrl+Tab", new Key (KeyCode.Tab | KeyCode.CtrlMask) };
+		yield return new object [] { "Alt+Tab", new Key (KeyCode.Tab | KeyCode.AltMask) };
+		yield return new object [] { "Ctrl+Shift+Tab", new Key (KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask) };
+		yield return new object [] { "Ctrl+Alt+Tab", new Key (KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask) };
+		yield return new object [] { "", new Key (KeyCode.Null) };
+		yield return new object [] { " ", new Key (KeyCode.Space) };
+		yield return new object [] { "Space", new Key (KeyCode.Space) };
+		yield return new object [] { "Shift+Space", new Key (KeyCode.Space | KeyCode.ShiftMask) };
+		yield return new object [] { "Ctrl+Space", new Key (KeyCode.Space | KeyCode.CtrlMask) };
+		yield return new object [] { "Alt+Space", new Key (KeyCode.Space | KeyCode.AltMask) };
+		yield return new object [] { "Shift+ ", new Key (KeyCode.Space | KeyCode.ShiftMask) };
+		yield return new object [] { "Ctrl+ ", new Key (KeyCode.Space | KeyCode.CtrlMask) };
+		yield return new object [] { "Alt+ ", new Key (KeyCode.Space | KeyCode.AltMask) };
+		yield return new object [] { "F1", new Key (KeyCode.F1) };
+		yield return new object [] { "0", new Key (KeyCode.D0) };
+		yield return new object [] { "9", new Key (KeyCode.D9) };
+		yield return new object [] { "D0", new Key (KeyCode.D0) };
+		yield return new object [] { "65", new Key (KeyCode.A | KeyCode.ShiftMask) };
+		yield return new object [] { "97", new Key (KeyCode.A)};
+		yield return new object [] { "Shift", new Key (KeyCode.ShiftMask) };
+		yield return new object [] { "Ctrl", new Key (KeyCode.CtrlMask) };
+		yield return new object [] { "Ctrl-A", new Key (KeyCode.A | KeyCode.CtrlMask) };
+		yield return new object [] { "Alt-A", new Key (KeyCode.A | KeyCode.AltMask) };
+		yield return new object [] { "A-Ctrl", new Key (KeyCode.A | KeyCode.CtrlMask) };
+		yield return new object [] { "Alt-A-Ctrl", new Key (KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask) };
+	}
 
 	// TryParse
 	[Theory]
-	[InlineData ("a", KeyCode.A)]
-	[InlineData ("Ctrl+A", KeyCode.A | KeyCode.CtrlMask)]
-	[InlineData ("Alt+A", KeyCode.A | KeyCode.AltMask)]
-	[InlineData ("Shift+A", KeyCode.A | KeyCode.ShiftMask)]
-	[InlineData ("A", KeyCode.A | KeyCode.ShiftMask)]
-	[InlineData ("â", (KeyCode)'â')]
-	[InlineData ("Shift+â", (KeyCode)'â' | KeyCode.ShiftMask)]
-	[InlineData ("Shift+Â", (KeyCode)'Â' | KeyCode.ShiftMask)]
-	[InlineData ("Ctrl+Shift+CursorUp", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.CursorUp)]
-	[InlineData ("Ctrl+Alt+Shift+CursorUp", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp)]
-	[InlineData ("ctrl+alt+shift+cursorup", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp)]
-	[InlineData ("CTRL+ALT+SHIFT+CURSORUP", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.CursorUp)]
-	[InlineData ("Ctrl+Alt+Shift+Delete", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Delete)]
-	[InlineData ("Ctrl+Alt+Shift+Enter", KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask | KeyCode.Enter)]
-	[InlineData ("Tab", KeyCode.Tab)]
-	[InlineData ("Shift+Tab", KeyCode.Tab | KeyCode.ShiftMask)]
-	[InlineData ("Ctrl+Tab", KeyCode.Tab | KeyCode.CtrlMask)]
-	[InlineData ("Alt+Tab", KeyCode.Tab | KeyCode.AltMask)]
-	[InlineData ("Ctrl+Shift+Tab", KeyCode.Tab | KeyCode.ShiftMask | KeyCode.CtrlMask)]
-	[InlineData ("Ctrl+Alt+Tab", KeyCode.Tab | KeyCode.AltMask | KeyCode.CtrlMask)]
-	[InlineData ("", KeyCode.Null)]
-	[InlineData (" ", KeyCode.Space)]
-	[InlineData ("Space", KeyCode.Space)]
-	[InlineData ("Shift+Space", KeyCode.Space | KeyCode.ShiftMask)]
-	[InlineData ("Ctrl+Space", KeyCode.Space | KeyCode.CtrlMask)]
-	[InlineData ("Alt+Space", KeyCode.Space | KeyCode.AltMask)]
-	[InlineData ("Shift+ ", KeyCode.Space | KeyCode.ShiftMask)]
-	[InlineData ("Ctrl+ ", KeyCode.Space | KeyCode.CtrlMask)]
-	[InlineData ("Alt+ ", KeyCode.Space | KeyCode.AltMask)]
-	[InlineData ("F1", KeyCode.F1)]
-	[InlineData ("0", KeyCode.D0)]
-	[InlineData ("9", KeyCode.D9)]
-	[InlineData ("D0", KeyCode.D0)]
-	[InlineData ("65", KeyCode.A | KeyCode.ShiftMask)]
-	[InlineData ("97", KeyCode.A)]
-	[InlineData ("Shift", KeyCode.ShiftMask)]
-	[InlineData ("Ctrl", KeyCode.CtrlMask)]
-	[InlineData ("Ctrl-A", KeyCode.A | KeyCode.CtrlMask)]
-	[InlineData ("Alt-A", KeyCode.A | KeyCode.AltMask)]
-	[InlineData ("A-Ctrl", KeyCode.A | KeyCode.CtrlMask)]
-	[InlineData ("Alt-A-Ctrl", KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)]
+	[MemberData (nameof (ConstructorStrings))]
 	public void Constructor_String_Valid (string keyString, Key expected)
 	{
 		Key key = new Key (keyString);
@@ -121,7 +128,9 @@ public class KeyTests {
 	[InlineData ('!', (KeyCode)'!')]
 	[InlineData ('\r', KeyCode.Enter)]
 	[InlineData ('\t', KeyCode.Tab)]
+#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to
 	[InlineData ('\r', (KeyCode)13)]
+#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to
 	[InlineData ('\n', (KeyCode)10)]
 	[InlineData ('ó', (KeyCode)'ó')]
 	[InlineData ('Ó', (KeyCode)'Ó')]
@@ -262,7 +271,9 @@ public class KeyTests {
 	[InlineData (KeyCode.F1, '\0')]
 	[InlineData (KeyCode.ShiftMask | KeyCode.F1, '\0')]
 	[InlineData (KeyCode.CtrlMask | KeyCode.F1, '\0')]
+#pragma warning disable xUnit1025 // InlineData should be unique within the Theory it belongs to
 	[InlineData (KeyCode.Enter, '\r')]
+#pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to
 	[InlineData (KeyCode.Tab, '\t')]
 	[InlineData (KeyCode.Esc, 0x1b)]
 	[InlineData (KeyCode.Space, ' ')]
@@ -421,9 +432,6 @@ public class KeyTests {
 	[InlineData (KeyCode.AltMask | KeyCode.CtrlMask, "Ctrl+Alt")]
 	[InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask, "Ctrl+Alt+Shift")]
 #pragma warning restore xUnit1025 // InlineData should be unique within the Theory it belongs to
-	[InlineData (KeyCode.AltMask, "Alt")]
-	[InlineData (KeyCode.CtrlMask, "Ctrl")]
-	[InlineData (KeyCode.ShiftMask, "Shift")]
 	[InlineData (KeyCode.CharMask, "CharMask")]
 	[InlineData (KeyCode.SpecialMask, "Ctrl+Alt+Shift")]
 	[InlineData ((KeyCode)'+', "+")]

+ 9 - 0
UnitTests/Input/ResponderTests.cs

@@ -139,6 +139,9 @@ public class ResponderTests {
 	[Fact]
 	public void Responder_Not_Notifying_Dispose ()
 	{
+		// Only clear before because need to test after assert
+		Responder.Instances.Clear ();
+
 		var container1 = new View () { Id = "Container1" };
 
 		var view = new View () { Id = "View" };
@@ -175,6 +178,9 @@ public class ResponderTests {
 	[Fact]
 	public void Disposing_Event_Notify_All_Subscribers_On_The_Second_Container ()
 	{
+		// Only clear before because need to test after assert
+		Responder.Instances.Clear ();
+
 		var container1 = new View () { Id = "Container1" };
 
 		var view = new View () { Id = "View" };
@@ -212,6 +218,9 @@ public class ResponderTests {
 	[Fact]
 	public void Disposing_Event_Notify_All_Subscribers_On_The_First_Container ()
 	{
+		// Only clear before because need to test after assert
+		Responder.Instances.Clear ();
+
 		var container1 = new View () { Id = "Container1" };
 		var count = 0;
 

+ 1 - 1
UnitTests/Text/AutocompleteTests.cs

@@ -153,7 +153,7 @@ namespace Terminal.Gui.TextTests {
 			Assert.Equal (3, g.AllSuggestions.Count);
 			Assert.True (tv.NewKeyDownEvent (new (tv.Autocomplete.SelectionKey)));
 			tv.PositionCursor ();
-			Assert.Equal ($"fortunately Fortunately super feature.", tv.Text);
+			Assert.Equal ($"Fortunately Fortunately super feature.", tv.Text);
 			Assert.Equal (new Point (11, 0), tv.CursorPosition);
 			Assert.Empty (tv.Autocomplete.Suggestions);
 			Assert.Equal (3, g.AllSuggestions.Count);

+ 1701 - 1620
UnitTests/Text/TextFormatterTests.cs

@@ -1,100 +1,96 @@
-using System.Text;
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Text;
 using Xunit;
 using Xunit.Abstractions;
-
 // Alias Console to MockConsole so we don't accidentally use Console
 using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.TextTests {
-	public class TextFormatterTests {
-		readonly ITestOutputHelper output;
+namespace Terminal.Gui.TextTests; 
 
-		public TextFormatterTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
+public class TextFormatterTests {
+	readonly ITestOutputHelper output;
 
-		[Fact]
-		public void Basic_Usage ()
-		{
-			var testText = "test";
-			var expectedSize = new Size ();
-			var testBounds = new Rect (0, 0, 100, 1);
-			var tf = new TextFormatter ();
-
-			tf.Text = testText;
-			expectedSize = new Size (testText.Length, 1);
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Left, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-
-			tf.Alignment = TextAlignment.Right;
-			expectedSize = new Size (testText.Length, 1);
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Right, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-
-			tf.Alignment = TextAlignment.Right;
-			expectedSize = new Size (testText.Length * 2, 1);
-			tf.Size = expectedSize;
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Right, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-
-			tf.Alignment = TextAlignment.Centered;
-			expectedSize = new Size (testText.Length * 2, 1);
-			tf.Size = expectedSize;
-			Assert.Equal (testText, tf.Text);
-			Assert.Equal (TextAlignment.Centered, tf.Alignment);
-			Assert.Equal (expectedSize, tf.Size);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.Equal (expectedSize, tf.Size);
-			Assert.NotEmpty (tf.Lines);
-		}
+	public TextFormatterTests (ITestOutputHelper output) => this.output = output;
 
-		[Theory]
-		[InlineData (TextDirection.LeftRight_TopBottom, false)]
-		[InlineData (TextDirection.LeftRight_TopBottom, true)]
-		[InlineData (TextDirection.TopBottom_LeftRight, false)]
-		[InlineData (TextDirection.TopBottom_LeftRight, true)]
-		public void TestSize_TextChange (TextDirection textDirection, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = textDirection, Text = "你", AutoSize = autoSize };
-			Assert.Equal (2, tf.Size.Width);
-			Assert.Equal (1, tf.Size.Height);
-			tf.Text = "你你";
-			if (autoSize) {
-				if (textDirection == TextDirection.LeftRight_TopBottom) {
-					Assert.Equal (4, tf.Size.Width);
-					Assert.Equal (1, tf.Size.Height);
-				} else {
-					Assert.Equal (2, tf.Size.Width);
-					Assert.Equal (2, tf.Size.Height);
-				}
-			} else {
-				Assert.Equal (2, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
+	public static IEnumerable<object []> CMGlyphs =>
+		new List<object []> {
+			new object [] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 }
+		};
+
+	public static IEnumerable<object []> FormatEnvironmentNewLine =>
+		new List<object []> {
+			new object [] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new [] { "Line1", "Line2", "Line3" } }
+		};
+
+	public static IEnumerable<object []> SplitEnvironmentNewLine =>
+		new List<object []> {
+			new object [] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new [] { "First Line 界", "Second Line 界", "Third Line 界" } },
+			new object [] {
+				$"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new [] { "First Line 界", "Second Line 界", "Third Line 界", "" }
 			}
-		}
+		};
 
-		[Theory]
-		[InlineData (TextDirection.LeftRight_TopBottom)]
-		[InlineData (TextDirection.TopBottom_LeftRight)]
-		public void TestSize_AutoSizeChange (TextDirection textDirection)
-		{
-			var tf = new TextFormatter () { Direction = textDirection, Text = "你你" };
+	[Fact]
+	public void Basic_Usage ()
+	{
+		var testText = "test";
+		var expectedSize = new Size ();
+		var testBounds = new Rect (0, 0, 100, 1);
+		var tf = new TextFormatter ();
+
+		tf.Text = testText;
+		expectedSize = new Size (testText.Length, 1);
+		Assert.Equal (testText,           tf.Text);
+		Assert.Equal (TextAlignment.Left, tf.Alignment);
+		Assert.Equal (expectedSize,       tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+
+		tf.Alignment = TextAlignment.Right;
+		expectedSize = new Size (testText.Length, 1);
+		Assert.Equal (testText,            tf.Text);
+		Assert.Equal (TextAlignment.Right, tf.Alignment);
+		Assert.Equal (expectedSize,        tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+
+		tf.Alignment = TextAlignment.Right;
+		expectedSize = new Size (testText.Length * 2, 1);
+		tf.Size = expectedSize;
+		Assert.Equal (testText,            tf.Text);
+		Assert.Equal (TextAlignment.Right, tf.Alignment);
+		Assert.Equal (expectedSize,        tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+
+		tf.Alignment = TextAlignment.Centered;
+		expectedSize = new Size (testText.Length * 2, 1);
+		tf.Size = expectedSize;
+		Assert.Equal (testText,               tf.Text);
+		Assert.Equal (TextAlignment.Centered, tf.Alignment);
+		Assert.Equal (expectedSize,           tf.Size);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.Equal (expectedSize, tf.Size);
+		Assert.NotEmpty (tf.Lines);
+	}
+
+	[Theory]
+	[InlineData (TextDirection.LeftRight_TopBottom, false)]
+	[InlineData (TextDirection.LeftRight_TopBottom, true)]
+	[InlineData (TextDirection.TopBottom_LeftRight, false)]
+	[InlineData (TextDirection.TopBottom_LeftRight, true)]
+	public void TestSize_TextChange (TextDirection textDirection, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = textDirection, Text = "你", AutoSize = autoSize };
+		Assert.Equal (2, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+		tf.Text = "你你";
+		if (autoSize) {
 			if (textDirection == TextDirection.LeftRight_TopBottom) {
 				Assert.Equal (4, tf.Size.Width);
 				Assert.Equal (1, tf.Size.Height);
@@ -102,29 +98,58 @@ namespace Terminal.Gui.TextTests {
 				Assert.Equal (2, tf.Size.Width);
 				Assert.Equal (2, tf.Size.Height);
 			}
-			Assert.False (tf.AutoSize);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		}
+	}
 
-			tf.Size = new Size (1, 1);
-			Assert.Equal (1, tf.Size.Width);
+	[Theory]
+	[InlineData (TextDirection.LeftRight_TopBottom)]
+	[InlineData (TextDirection.TopBottom_LeftRight)]
+	public void TestSize_AutoSizeChange (TextDirection textDirection)
+	{
+		var tf = new TextFormatter { Direction = textDirection, Text = "你你" };
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
+			Assert.Equal (4, tf.Size.Width);
 			Assert.Equal (1, tf.Size.Height);
-			tf.AutoSize = true;
-			if (textDirection == TextDirection.LeftRight_TopBottom) {
-				Assert.Equal (4, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			} else {
-				Assert.Equal (2, tf.Size.Width);
-				Assert.Equal (2, tf.Size.Height);
-			}
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+		}
+		Assert.False (tf.AutoSize);
+
+		tf.Size = new Size (1, 1);
+		Assert.Equal (1, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+		tf.AutoSize = true;
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (TextDirection.LeftRight_TopBottom, false)]
-		[InlineData (TextDirection.LeftRight_TopBottom, true)]
-		[InlineData (TextDirection.TopBottom_LeftRight, false)]
-		[InlineData (TextDirection.TopBottom_LeftRight, true)]
-		public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = textDirection, Text = "你你", AutoSize = autoSize };
+	[Theory]
+	[InlineData (TextDirection.LeftRight_TopBottom, false)]
+	[InlineData (TextDirection.LeftRight_TopBottom, true)]
+	[InlineData (TextDirection.TopBottom_LeftRight, false)]
+	[InlineData (TextDirection.TopBottom_LeftRight, true)]
+	public void TestSize_SizeChange_AutoSize_True_Or_False (TextDirection textDirection, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = textDirection, Text = "你你", AutoSize = autoSize };
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+		}
+
+		tf.Size = new Size (1, 1);
+		if (autoSize) {
 			if (textDirection == TextDirection.LeftRight_TopBottom) {
 				Assert.Equal (4, tf.Size.Width);
 				Assert.Equal (1, tf.Size.Height);
@@ -132,1639 +157,1695 @@ namespace Terminal.Gui.TextTests {
 				Assert.Equal (2, tf.Size.Width);
 				Assert.Equal (2, tf.Size.Height);
 			}
-
-			tf.Size = new Size (1, 1);
-			if (autoSize) {
-				if (textDirection == TextDirection.LeftRight_TopBottom) {
-					Assert.Equal (4, tf.Size.Width);
-					Assert.Equal (1, tf.Size.Height);
-				} else {
-					Assert.Equal (2, tf.Size.Width);
-					Assert.Equal (2, tf.Size.Height);
-				}
-			} else {
-				Assert.Equal (1, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			}
+		} else {
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (TextAlignment.Left, false)]
-		[InlineData (TextAlignment.Centered, true)]
-		[InlineData (TextAlignment.Right, false)]
-		[InlineData (TextAlignment.Justified, true)]
-		public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
-				Assert.Equal (4, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-
-			tf.Size = new Size (1, 1);
-			if (autoSize && textAlignment != TextAlignment.Justified) {
-				Assert.Equal (4, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			} else {
-				Assert.Equal (1, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			}
+	[Theory]
+	[InlineData (TextAlignment.Left,      false)]
+	[InlineData (TextAlignment.Centered,  true)]
+	[InlineData (TextAlignment.Right,     false)]
+	[InlineData (TextAlignment.Justified, true)]
+	public void TestSize_SizeChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (4, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+
+		tf.Size = new Size (1, 1);
+		if (autoSize && textAlignment != TextAlignment.Justified) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (VerticalTextAlignment.Top, false)]
-		[InlineData (VerticalTextAlignment.Middle, true)]
-		[InlineData (VerticalTextAlignment.Bottom, false)]
-		[InlineData (VerticalTextAlignment.Justified, true)]
-		public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
-		{
-			var tf = new TextFormatter () { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+	[Theory]
+	[InlineData (VerticalTextAlignment.Top,       false)]
+	[InlineData (VerticalTextAlignment.Middle,    true)]
+	[InlineData (VerticalTextAlignment.Bottom,    false)]
+	[InlineData (VerticalTextAlignment.Justified, true)]
+	public void TestSize_SizeChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (2, tf.Size.Width);
+		Assert.Equal (2, tf.Size.Height);
+
+		tf.Size = new Size (1, 1);
+		if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
 			Assert.Equal (2, tf.Size.Width);
 			Assert.Equal (2, tf.Size.Height);
-
-			tf.Size = new Size (1, 1);
-			if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
-				Assert.Equal (2, tf.Size.Width);
-				Assert.Equal (2, tf.Size.Height);
-			} else {
-				Assert.Equal (1, tf.Size.Width);
-				Assert.Equal (1, tf.Size.Height);
-			}
+		} else {
+			Assert.Equal (1, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Fact]
-		public void NeedsFormat_Sets ()
-		{
-			var testText = "test";
-			var testBounds = new Rect (0, 0, 100, 1);
-			var tf = new TextFormatter ();
-
-			tf.Text = "test";
-			Assert.True (tf.NeedsFormat); // get_Lines causes a Format
-			Assert.NotEmpty (tf.Lines);
-			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
-			Assert.Equal (testText, tf.Text);
-			tf.Draw (testBounds, new Attribute (), new Attribute ());
-			Assert.False (tf.NeedsFormat);
-
-			tf.Size = new Size (1, 1);
-			Assert.True (tf.NeedsFormat);
-			Assert.NotEmpty (tf.Lines);
-			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
-
-			tf.Alignment = TextAlignment.Centered;
-			Assert.True (tf.NeedsFormat);
-			Assert.NotEmpty (tf.Lines);
-			Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+	[Theory]
+	[InlineData (TextAlignment.Left,      false)]
+	[InlineData (TextAlignment.Centered,  true)]
+	[InlineData (TextAlignment.Right,     false)]
+	[InlineData (TextAlignment.Justified, true)]
+	public void TestSize_DirectionChange_AutoSize_True_Or_False_Horizontal (TextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.LeftRight_TopBottom, Text = "你你", Alignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (4, tf.Size.Width);
+		Assert.Equal (1, tf.Size.Height);
+
+		tf.Direction = TextDirection.TopBottom_LeftRight;
+		if (autoSize && textAlignment != TextAlignment.Justified) {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
+		} else {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData (null)]
-		[InlineData ("")]
-		[InlineData ("no hotkey")]
-		[InlineData ("No hotkey, Upper Case")]
-		[InlineData ("Non-english: Сохранить")]
-		public void FindHotKey_Invalid_ReturnsFalse (string text)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-			bool supportFirstUpperCase = false;
-			int hotPos = 0;
-			Key hotKey = KeyCode.Null;
-			bool result = false;
-
-			result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey);
-			Assert.False (result);
-			Assert.Equal (-1, hotPos);
-			Assert.Equal (KeyCode.Null, hotKey);
+	[Theory]
+	[InlineData (VerticalTextAlignment.Top,       false)]
+	[InlineData (VerticalTextAlignment.Middle,    true)]
+	[InlineData (VerticalTextAlignment.Bottom,    false)]
+	[InlineData (VerticalTextAlignment.Justified, true)]
+	public void TestSize_DirectionChange_AutoSize_True_Or_False_Vertical (VerticalTextAlignment textAlignment, bool autoSize)
+	{
+		var tf = new TextFormatter { Direction = TextDirection.TopBottom_LeftRight, Text = "你你", VerticalAlignment = textAlignment, AutoSize = autoSize };
+		Assert.Equal (2, tf.Size.Width);
+		Assert.Equal (2, tf.Size.Height);
+
+		tf.Direction = TextDirection.LeftRight_TopBottom;
+		if (autoSize && textAlignment != VerticalTextAlignment.Justified) {
+			Assert.Equal (4, tf.Size.Width);
+			Assert.Equal (1, tf.Size.Height);
+		} else {
+			Assert.Equal (2, tf.Size.Width);
+			Assert.Equal (2, tf.Size.Height);
 		}
+	}
 
-		[Theory]
-		[InlineData ("_K Before", true, 0, (KeyCode)'K')]
-		[InlineData ("a_K Second", true, 1, (KeyCode)'K')]
-		[InlineData ("Last _K", true, 5, (KeyCode)'K')]
-		[InlineData ("After K_", false, -1, KeyCode.Null)]
-		[InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K')]
-		[InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
-		[InlineData ("_K Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
-		[InlineData ("a_K Second", true, 1, (KeyCode)'K', true)]
-		[InlineData ("Last _K", true, 5, (KeyCode)'K', true)]
-		[InlineData ("After K_", false, -1, KeyCode.Null, true)]
-		[InlineData ("Multiple _K and _R", true, 9, (KeyCode)'K', true)]
-		[InlineData ("Non-english: _Кдать", true, 13, (KeyCode)'К', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
-		}
+	[Fact]
+	public void NeedsFormat_Sets ()
+	{
+		var testText = "test";
+		var testBounds = new Rect (0, 0, 100, 1);
+		var tf = new TextFormatter ();
+
+		tf.Text = "test";
+		Assert.True (tf.NeedsFormat); // get_Lines causes a Format
+		Assert.NotEmpty (tf.Lines);
+		Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+		Assert.Equal (testText, tf.Text);
+		tf.Draw (testBounds, new Attribute (), new Attribute ());
+		Assert.False (tf.NeedsFormat);
+
+		tf.Size = new Size (1, 1);
+		Assert.True (tf.NeedsFormat);
+		Assert.NotEmpty (tf.Lines);
+		Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+
+		tf.Alignment = TextAlignment.Centered;
+		Assert.True (tf.NeedsFormat);
+		Assert.NotEmpty (tf.Lines);
+		Assert.False (tf.NeedsFormat); // get_Lines causes a Format
+	}
 
-		[Theory]
-		[InlineData ("_k Before", true, 0, (KeyCode)'K')] // lower case should return uppercase Hotkey
-		[InlineData ("a_k Second", true, 1, (KeyCode)'K')]
-		[InlineData ("Last _k", true, 5, (KeyCode)'K')]
-		[InlineData ("After k_", false, -1, KeyCode.Null)]
-		[InlineData ("Multiple _k and _R", true, 9, (KeyCode)'K')]
-		[InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к')] // Lower case Cryllic K (к)
-		[InlineData ("_k Before", true, 0, (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
-		[InlineData ("a_k Second", true, 1, (KeyCode)'K', true)]
-		[InlineData ("Last _k", true, 5, (KeyCode)'K', true)]
-		[InlineData ("After k_", false, -1, KeyCode.Null, true)]
-		[InlineData ("Multiple _k and _r", true, 9, (KeyCode)'K', true)]
-		[InlineData ("Non-english: _кдать", true, 13, (KeyCode)'к', true)] // Cryllic K (К)
-		public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
-		}
+	[Theory]
+	[InlineData (null)]
+	[InlineData ("")]
+	[InlineData ("no hotkey")]
+	[InlineData ("No hotkey, Upper Case")]
+	[InlineData ("Non-english: Сохранить")]
+	public void FindHotKey_Invalid_ReturnsFalse (string text)
+	{
+		var hotKeySpecifier = (Rune)'_';
+		var supportFirstUpperCase = false;
+		var hotPos = 0;
+		Key hotKey = KeyCode.Null;
+		var result = false;
+
+		result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out hotPos, out hotKey);
+		Assert.False (result);
+		Assert.Equal (-1,           hotPos);
+		Assert.Equal (KeyCode.Null, hotKey);
+	}
 
-		[Theory]
-		[InlineData ("_1 Before", true, 0, (KeyCode)'1')] // Digits 
-		[InlineData ("a_1 Second", true, 1, (KeyCode)'1')]
-		[InlineData ("Last _1", true, 5, (KeyCode)'1')]
-		[InlineData ("After 1_", false, -1, KeyCode.Null)]
-		[InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1')]
-		[InlineData ("_1 Before", true, 0, (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results
-		[InlineData ("a_1 Second", true, 1, (KeyCode)'1', true)]
-		[InlineData ("Last _1", true, 5, (KeyCode)'1', true)]
-		[InlineData ("After 1_", false, -1, KeyCode.Null, true)]
-		[InlineData ("Multiple _1 and _2", true, 9, (KeyCode)'1', true)]
-		public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
+	[Theory]
+	[InlineData ("_K Before",           true,  0,  (KeyCode)'K')]
+	[InlineData ("a_K Second",          true,  1,  (KeyCode)'K')]
+	[InlineData ("Last _K",             true,  5,  (KeyCode)'K')]
+	[InlineData ("After K_",            false, -1, KeyCode.Null)]
+	[InlineData ("Multiple _K and _R",  true,  9,  (KeyCode)'K')]
+	[InlineData ("Non-english: _Кдать", true,  13, (KeyCode)'К')]       // Cryllic K (К)
+	[InlineData ("_K Before",           true,  0,  (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
+	[InlineData ("a_K Second",          true,  1,  (KeyCode)'K', true)]
+	[InlineData ("Last _K",             true,  5,  (KeyCode)'K', true)]
+	[InlineData ("After K_",            false, -1, KeyCode.Null, true)]
+	[InlineData ("Multiple _K and _R",  true,  9,  (KeyCode)'K', true)]
+	[InlineData ("Non-english: _Кдать", true,  13, (KeyCode)'К', true)] // Cryllic K (К)
+	public void FindHotKey_AlphaUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
+			Assert.False (result);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData ("K Before", true, 0, (KeyCode)'K')]
-		[InlineData ("aK Second", true, 1, (KeyCode)'K')]
-		[InlineData ("last K", true, 5, (KeyCode)'K')]
-		[InlineData ("multiple K and R", true, 9, (KeyCode)'K')]
-		[InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
-		public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey)
-		{
-			var supportFirstUpperCase = true;
-
-			Rune hotKeySpecifier = (Rune)0;
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
-			if (expectedResult) {
-				Assert.True (result);
-			} else {
-				Assert.False (result);
-			}
-			Assert.Equal (expectedResult, result);
-			Assert.Equal (expectedHotPos, hotPos);
-			Assert.Equal ((Key)expectedKey, hotKey);
-		}
-		
-		[Theory]
-		[InlineData ("_\"k before", true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
-		[InlineData ("\"_k before", true, KeyCode.K)]
-		[InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
-		[InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
-		[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
-		[InlineData ("_ ~  s  gui.cs   master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
-		[InlineData (" ~  s  gui.cs  _ master ↑10", true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
-		[InlineData ("non-english: _кдать", true, (KeyCode)'к')] // Lower case Cryllic K (к)
-		public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
-		{
-			var hotKeySpecifier = (Rune)'_';
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out int _, out var hotKey);
-			Assert.Equal (found, result);
-			Assert.Equal ((Key)expected, hotKey);
+	[Theory]
+	[InlineData ("_k Before",           true,  0,  (KeyCode)'K')] // lower case should return uppercase Hotkey
+	[InlineData ("a_k Second",          true,  1,  (KeyCode)'K')]
+	[InlineData ("Last _k",             true,  5,  (KeyCode)'K')]
+	[InlineData ("After k_",            false, -1, KeyCode.Null)]
+	[InlineData ("Multiple _k and _R",  true,  9,  (KeyCode)'K')]
+	[InlineData ("Non-english: _кдать", true,  13, (KeyCode)'к')]       // Lower case Cryllic K (к)
+	[InlineData ("_k Before",           true,  0,  (KeyCode)'K', true)] // Turn on FirstUpperCase and verify same results
+	[InlineData ("a_k Second",          true,  1,  (KeyCode)'K', true)]
+	[InlineData ("Last _k",             true,  5,  (KeyCode)'K', true)]
+	[InlineData ("After k_",            false, -1, KeyCode.Null, true)]
+	[InlineData ("Multiple _k and _r",  true,  9,  (KeyCode)'K', true)]
+	[InlineData ("Non-english: _кдать", true,  13, (KeyCode)'к', true)] // Cryllic K (К)
+	public void FindHotKey_AlphaLowerCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
+			Assert.False (result);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData ("\"k before")]
-		[InlineData ("ak second")]
-		[InlineData ("last k")]
-		[InlineData ("multiple k and r")]
-		[InlineData ("12345")]
-		[InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation
-		[InlineData (" ~  s  gui.cs   master ↑10")] // ~IsLetterOrDigit + Unicode
-		[InlineData ("non-english: кдать")] // Lower case Cryllic K (к)
-		public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text)
-		{
-			bool supportFirstUpperCase = true;
-
-			var hotKeySpecifier = (Rune)0;
-
-			var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out int hotPos, out var hotKey);
+	[Theory]
+	[InlineData ("_1 Before",          true,  0,  (KeyCode)'1')] // Digits 
+	[InlineData ("a_1 Second",         true,  1,  (KeyCode)'1')]
+	[InlineData ("Last _1",            true,  5,  (KeyCode)'1')]
+	[InlineData ("After 1_",           false, -1, KeyCode.Null)]
+	[InlineData ("Multiple _1 and _2", true,  9,  (KeyCode)'1')]
+	[InlineData ("_1 Before",          true,  0,  (KeyCode)'1', true)] // Turn on FirstUpperCase and verify same results
+	[InlineData ("a_1 Second",         true,  1,  (KeyCode)'1', true)]
+	[InlineData ("Last _1",            true,  5,  (KeyCode)'1', true)]
+	[InlineData ("After 1_",           false, -1, KeyCode.Null, true)]
+	[InlineData ("Multiple _1 and _2", true,  9,  (KeyCode)'1', true)]
+	public void FindHotKey_Numeric_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey, bool supportFirstUpperCase = false)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
 			Assert.False (result);
-			Assert.Equal (-1, hotPos);
-			Assert.Equal (KeyCode.Null, hotKey);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData (null)]
-		[InlineData ("")]
-		[InlineData ("a")]
-		public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			if (text == null) {
-				Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier));
-				Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier));
-				Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
-			} else {
-				Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0, hotKeySpecifier));
-				Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1, hotKeySpecifier));
-				Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
-			}
+	[Theory]
+	[InlineData ("K Before",           true, 0,  (KeyCode)'K')]
+	[InlineData ("aK Second",          true, 1,  (KeyCode)'K')]
+	[InlineData ("last K",             true, 5,  (KeyCode)'K')]
+	[InlineData ("multiple K and R",   true, 9,  (KeyCode)'K')]
+	[InlineData ("non-english: Кдать", true, 13, (KeyCode)'К')] // Cryllic K (К)
+	public void FindHotKey_Legacy_FirstUpperCase_Succeeds (string text, bool expectedResult, int expectedHotPos, KeyCode expectedKey)
+	{
+		var supportFirstUpperCase = true;
+
+		var hotKeySpecifier = (Rune)0;
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		if (expectedResult) {
+			Assert.True (result);
+		} else {
+			Assert.False (result);
 		}
+		Assert.Equal (expectedResult, result);
+		Assert.Equal (expectedHotPos, hotPos);
+		Assert.Equal (expectedKey,    hotKey);
+	}
 
-		[Theory]
-		[InlineData ("_K Before", 0, "K Before")]
-		[InlineData ("a_K Second", 1, "aK Second")]
-		[InlineData ("Last _K", 5, "Last K")]
-		[InlineData ("After K_", 7, "After K")]
-		[InlineData ("Multiple _K and _R", 9, "Multiple K and _R")]
-		[InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")]
-		public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
-		}
+	[Theory]
+	[InlineData ("_\"k before",                         true, (KeyCode)'"')] // BUGBUG: Not sure why this fails. " is a normal char
+	[InlineData ("\"_k before",                         true, KeyCode.K)]
+	[InlineData ("_`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'`')]
+	[InlineData ("`_~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'~')]
+	[InlineData ("`~!@#$%^&*()-__=+[{]}\\|;:'\",<.>/?", true, (KeyCode)'=')] // BUGBUG: Not sure why this fails. Ignore the first and consider the second
+	[InlineData ("_ ~  s  gui.cs   master ↑10",    true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
+	[InlineData (" ~  s  gui.cs  _ master ↑10",    true, (KeyCode)'')] // ~IsLetterOrDigit + Unicode
+	[InlineData ("non-english: _кдать",                 true, (KeyCode)'к')] // Lower case Cryllic K (к)
+	public void FindHotKey_Symbols_Returns_Symbol (string text, bool found, KeyCode expected)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, false, out var _, out var hotKey);
+		Assert.Equal (found,    result);
+		Assert.Equal (expected, hotKey);
+	}
 
-		[Theory]
-		[InlineData ("all lower case", 0)]
-		[InlineData ("K Before", 0)]
-		[InlineData ("aK Second", 1)]
-		[InlineData ("Last K", 5)]
-		[InlineData ("fter K", 7)]
-		[InlineData ("Multiple K and R", 9)]
-		[InlineData ("Non-english: Кдать", 13)]
-		public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos)
-		{
-			Rune hotKeySpecifier = (Rune)'_';
-
-			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
-		}
+	[Theory]
+	[InlineData ("\"k before")]
+	[InlineData ("ak second")]
+	[InlineData ("last k")]
+	[InlineData ("multiple k and r")]
+	[InlineData ("12345")]
+	[InlineData ("`~!@#$%^&*()-_=+[{]}\\|;:'\",<.>/?")] // punctuation
+	[InlineData (" ~  s  gui.cs   master ↑10")]    // ~IsLetterOrDigit + Unicode
+	[InlineData ("non-english: кдать")]                 // Lower case Cryllic K (к)
+	public void FindHotKey_Legacy_FirstUpperCase_NotFound_Returns_False (string text)
+	{
+		var supportFirstUpperCase = true;
+
+		var hotKeySpecifier = (Rune)0;
+
+		var result = TextFormatter.FindHotKey (text, hotKeySpecifier, supportFirstUpperCase, out var hotPos, out var hotKey);
+		Assert.False (result);
+		Assert.Equal (-1,           hotPos);
+		Assert.Equal (KeyCode.Null, hotKey);
+	}
 
-		[Theory]
-		[InlineData (null)]
-		[InlineData ("")]
-		public void CalcRect_Invalid_Returns_Empty (string text)
-		{
-			Assert.Equal (Rect.Empty, TextFormatter.CalcRect (0, 0, text));
-			Assert.Equal (new Rect (new Point (1, 2), Size.Empty), TextFormatter.CalcRect (1, 2, text));
-			Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text));
+	[Theory]
+	[InlineData (null)]
+	[InlineData ("")]
+	[InlineData ("a")]
+	public void RemoveHotKeySpecifier_InValid_ReturnsOriginal (string text)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		if (text == null) {
+			Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 0,   hotKeySpecifier));
+			Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, -1,  hotKeySpecifier));
+			Assert.Null (TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
+		} else {
+			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 0,   hotKeySpecifier));
+			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, -1,  hotKeySpecifier));
+			Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, 100, hotKeySpecifier));
 		}
+	}
 
-		[Theory]
-		[InlineData ("test")]
-		[InlineData (" ~  s  gui.cs   master ↑10")]
-		public void CalcRect_SingleLine_Returns_1High (string text)
-		{
-			Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text));
-			Assert.Equal (new Rect (0, 0, text.GetColumns (), 1), TextFormatter.CalcRect (0, 0, text));
-		}
+	[Theory]
+	[InlineData ("_K Before",           0,  "K Before")]
+	[InlineData ("a_K Second",          1,  "aK Second")]
+	[InlineData ("Last _K",             5,  "Last K")]
+	[InlineData ("After K_",            7,  "After K")]
+	[InlineData ("Multiple _K and _R",  9,  "Multiple K and _R")]
+	[InlineData ("Non-english: _Кдать", 13, "Non-english: Кдать")]
+	public void RemoveHotKeySpecifier_Valid_ReturnsStripped (string text, int hotPos, string expectedText)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		Assert.Equal (expectedText, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
+	}
 
-		[Theory]
-		[InlineData ("line1\nline2", 5, 2)]
-		[InlineData ("\nline2", 5, 2)]
-		[InlineData ("\n\n", 0, 3)]
-		[InlineData ("\n\n\n", 0, 4)]
-		[InlineData ("line1\nline2\nline3long!", 10, 3)]
-		[InlineData ("line1\nline2\n\n", 5, 4)]
-		[InlineData ("line1\r\nline2", 5, 2)]
-		[InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)]
-		[InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)]
-		[InlineData (" ~  s  gui.cs   master\n↑10", 27, 2)]
-		public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines)
-		{
-			Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text));
-			var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n");
-			var maxWidth = lines.Max (s => s.GetColumns ());
-			var lineWider = 0;
-			for (int i = 0; i < lines.Length; i++) {
-				var w = lines [i].GetColumns ();
-				if (w == maxWidth) {
-					lineWider = i;
-				}
-			}
-			Assert.Equal (new Rect (0, 0, maxWidth, expectedLines), TextFormatter.CalcRect (0, 0, text));
-			Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text));
-		}
+	[Theory]
+	[InlineData ("all lower case",     0)]
+	[InlineData ("K Before",           0)]
+	[InlineData ("aK Second",          1)]
+	[InlineData ("Last K",             5)]
+	[InlineData ("fter K",             7)]
+	[InlineData ("Multiple K and R",   9)]
+	[InlineData ("Non-english: Кдать", 13)]
+	public void RemoveHotKeySpecifier_Valid_Legacy_ReturnsOriginal (string text, int hotPos)
+	{
+		var hotKeySpecifier = (Rune)'_';
+
+		Assert.Equal (text, TextFormatter.RemoveHotKeySpecifier (text, hotPos, hotKeySpecifier));
+	}
 
-		[Theory]
-		[InlineData ("")]
-		[InlineData (null)]
-		[InlineData ("test")]
-		public void ClipAndJustify_Invalid_Returns_Original (string text)
-		{
-			var expected = string.IsNullOrEmpty (text) ? text : "";
-			Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
-			Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
-			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left));
-		}
+	[Theory]
+	[InlineData (null)]
+	[InlineData ("")]
+	public void CalcRect_Invalid_Returns_Empty (string text)
+	{
+		Assert.Equal (Rect.Empty,                                TextFormatter.CalcRect (0,  0,  text));
+		Assert.Equal (new Rect (new Point (1,  2),  Size.Empty), TextFormatter.CalcRect (1,  2,  text));
+		Assert.Equal (new Rect (new Point (-1, -2), Size.Empty), TextFormatter.CalcRect (-1, -2, text));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
-		[InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Left;
-			var textDirection = TextDirection.LeftRight_BottomTop;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
-		}
+	[Theory]
+	[InlineData ("test")]
+	[InlineData (" ~  s  gui.cs   master ↑10")]
+	public void CalcRect_SingleLine_Returns_1High (string text)
+	{
+		Assert.Equal (new Rect (0, 0, text.GetRuneCount (), 1), TextFormatter.CalcRect (0, 0, text));
+		Assert.Equal (new Rect (0, 0, text.GetColumns (),   1), TextFormatter.CalcRect (0, 0, text));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
-		[InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Right;
-			var textDirection = TextDirection.LeftRight_BottomTop;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
+	[Theory]
+	[InlineData ("line1\nline2",                      5,  2)]
+	[InlineData ("\nline2",                           5,  2)]
+	[InlineData ("\n\n",                              0,  3)]
+	[InlineData ("\n\n\n",                            0,  4)]
+	[InlineData ("line1\nline2\nline3long!",          10, 3)]
+	[InlineData ("line1\nline2\n\n",                  5,  4)]
+	[InlineData ("line1\r\nline2",                    5,  2)]
+	[InlineData (" ~  s  gui.cs   master ↑10\n", 31, 2)]
+	[InlineData ("\n ~  s  gui.cs   master ↑10", 31, 2)]
+	[InlineData (" ~  s  gui.cs   master\n↑10",  27, 2)]
+	public void CalcRect_MultiLine_Returns_nHigh (string text, int expectedWidth, int expectedLines)
+	{
+		Assert.Equal (new Rect (0, 0, expectedWidth, expectedLines), TextFormatter.CalcRect (0, 0, text));
+		var lines = text.Split (text.Contains (Environment.NewLine) ? Environment.NewLine : "\n");
+		var maxWidth = lines.Max (s => s.GetColumns ());
+		var lineWider = 0;
+		for (var i = 0; i < lines.Length; i++) {
+			var w = lines [i].GetColumns ();
+			if (w == maxWidth) {
+				lineWider = i;
+			}
 		}
+		Assert.Equal (new Rect (0, 0, maxWidth,                                                                 expectedLines), TextFormatter.CalcRect (0, 0, text));
+		Assert.Equal (new Rect (0, 0, lines [lineWider].ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 0)), expectedLines), TextFormatter.CalcRect (0, 0, text));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)]
-		[InlineData ("A sentence has words.", "A sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", int.MaxValue)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-		[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Centered;
-			var textDirection = TextDirection.LeftRight_TopBottom;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
-		}
+	[Theory]
+	[InlineData ("")]
+	[InlineData (null)]
+	[InlineData ("test")]
+	public void ClipAndJustify_Invalid_Returns_Original (string text)
+	{
+		var expected = string.IsNullOrEmpty (text) ? text : "";
+		Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
+		Assert.Equal (expected, TextFormatter.ClipAndJustify (text, 0, TextAlignment.Left));
+		Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.ClipAndJustify (text, -1, TextAlignment.Left));
+	}
 
-		[Theory]
-		[InlineData ("test", "", 0)]
-		[InlineData ("test", "te", 2)]
-		[InlineData ("test", "test", int.MaxValue)] // This doesn't throw because it only create a word with length 1
-		[InlineData ("A sentence has words.", "A  sentence has words.", 22)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words.", 21)] // should fit
-		[InlineData ("A sentence has words.", "A                                                                                                                                                                 sentence                                                                                                                                                                 has                                                                                                                                                                words.", 500)] // should fit
-		[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
-		[InlineData ("A sentence has words.", "A sentence", 10)] // Should not fit
-									 // Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder
-									 //[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
-		[InlineData ("A\tsentence\thas\twords.", "A sentence", 10)]
-		[InlineData ("line1\nline2\nline3long!", "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1
-		[InlineData ("line1\nline2\nline3long!", "line1\nline", 10)]
-		[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ", 10)] // Unicode
-		[InlineData ("Ð ÑÐ", "Ð  ÑÐ", 5)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð ÑÐ", 4)] // should fit
-		[InlineData ("Ð ÑÐ", "Ð Ñ", 3)] // Should not fit
-		public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
-		{
-			var align = TextAlignment.Justified;
-			var textDirection = TextDirection.LeftRight_TopBottom;
-			var tabWidth = 1;
-
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
-			Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
-			Assert.True (justifiedText.GetRuneCount () <= maxWidth);
-			Assert.True (justifiedText.GetColumns () <= maxWidth);
-			Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
-			Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
-			Assert.True (expectedClippedWidth <= maxWidth);
-			Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [0..expectedClippedWidth]), justifiedText);
-
-			// see Justify_ tests below
-		}
+	[Theory]
+	[InlineData ("test",                            "",                         0)]
+	[InlineData ("test",                            "te",                       2)]
+	[InlineData ("test",                            "test",                     int.MaxValue)]
+	[InlineData ("A sentence has words.",           "A sentence has words.",    22)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    21)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    int.MaxValue)] // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words",     20)]           // Should not fit
+	[InlineData ("A sentence has words.",           "A sentence",               10)]           // Should not fit
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence has words.",    int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Left (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Left;
+		var textDirection = TextDirection.LeftRight_BottomTop;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+	}
 
-		[Theory]
-		[InlineData ("")]
-		[InlineData (null)]
-		[InlineData ("test")]
-		public void Justify_Invalid (string text)
-		{
-			Assert.Equal (text, TextFormatter.Justify (text, 0));
-			Assert.Equal (text, TextFormatter.Justify (text, 0));
-			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Justify (text, -1));
-		}
+	[Theory]
+	[InlineData ("test",                            "",                         0)]
+	[InlineData ("test",                            "te",                       2)]
+	[InlineData ("test",                            "test",                     int.MaxValue)]
+	[InlineData ("A sentence has words.",           "A sentence has words.",    22)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    21)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    int.MaxValue)] // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words",     20)]           // Should not fit
+	[InlineData ("A sentence has words.",           "A sentence",               10)]           // Should not fit
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence has words.",    int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Right (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Right;
+		var textDirection = TextDirection.LeftRight_BottomTop;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+	}
 
-		[Theory]
-		[InlineData ("word")] // Even # of chars
-		[InlineData ("word.")] // Odd # of chars
-		[InlineData ("пÑивеÑ")] // Unicode (even #)
-		[InlineData ("пÑивеÑ.")] // Unicode (odd # of chars)
-		public void Justify_SingleWord (string text)
-		{
-			var justifiedText = text;
-			char fillChar = '+';
-
-			int width = text.GetRuneCount ();
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 1;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 2;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 10;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-			width = text.GetRuneCount () + 11;
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
-		}
+	[Theory]
+	[InlineData ("test",                            "",                         0)]
+	[InlineData ("test",                            "te",                       2)]
+	[InlineData ("test",                            "test",                     int.MaxValue)]
+	[InlineData ("A sentence has words.",           "A sentence has words.",    22)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    21)]           // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words.",    int.MaxValue)] // should fit
+	[InlineData ("A sentence has words.",           "A sentence has words",     20)]           // Should not fit
+	[InlineData ("A sentence has words.",           "A sentence",               10)]           // Should not fit
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence has words.",    int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Centered (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Centered;
+		var textDirection = TextDirection.LeftRight_TopBottom;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+	}
 
-		[Theory]
-		// Even # of spaces
-		//            0123456789
-		[InlineData ("012 456 89", "012 456 89", 10, 0, "+", true)]
-		[InlineData ("012 456 89", "012++456+89", 11, 1)]
-		[InlineData ("012 456 89", "012 456 89", 12, 2, "++", true)]
-		[InlineData ("012 456 89", "012+++456++89", 13, 3)]
-		[InlineData ("012 456 89", "012 456 89", 14, 4, "+++", true)]
-		[InlineData ("012 456 89", "012++++456+++89", 15, 5)]
-		[InlineData ("012 456 89", "012 456 89", 16, 6, "++++", true)]
-		[InlineData ("012 456 89", "012 456 89", 30, 20, "+++++++++++", true)]
-		[InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)]
-		// Odd # of spaces
-		//            01234567890123
-		[InlineData ("012 456 89 end", "012 456 89 end", 14, 0, "+", true)]
-		[InlineData ("012 456 89 end", "012++456+89+end", 15, 1)]
-		[InlineData ("012 456 89 end", "012++456++89+end", 16, 2)]
-		[InlineData ("012 456 89 end", "012 456 89 end", 17, 3, "++", true)]
-		[InlineData ("012 456 89 end", "012+++456++89++end", 18, 4)]
-		[InlineData ("012 456 89 end", "012+++456+++89++end", 19, 5)]
-		[InlineData ("012 456 89 end", "012 456 89 end", 20, 6, "+++", true)]
-		[InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end", 34, 20)]
-		[InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)]
-		// Unicode
-		// Even # of chars
-		//            0123456789
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 10, 0, "+", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ", 11, 1)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 12, 2, "++", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ", 13, 3)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 14, 4, "+++", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ", 15, 5)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 16, 6, "++++", true)]
-		[InlineData ("пÑРвРÑ", "пÑРвРÑ", 30, 20, "+++++++++++", true)]
-		[InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)]
-		// Unicode
-		// Odd # of chars
-		//            0123456789
-		[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 10, 0, "+", true)]
-		[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ", 11, 1)]
-		[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ", 12, 2)]
-		[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 13, 3, "++", true)]
-		[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ", 14, 4)]
-		[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ", 15, 5)]
-		[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ", 16, 6, "+++", true)]
-		[InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ", 30, 20)]
-		[InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)]
-		public void Justify_Sentence (string text, string justifiedText, int forceToWidth, int widthOffset, string replaceWith = null, bool replace = false)
-		{
-			char fillChar = '+';
-
-			Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset);
-			if (replace) {
-				justifiedText = text.Replace (" ", replaceWith);
-			}
-			Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar));
-			Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' '));
-			Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' '));
-		}
+	[Theory]
+	[InlineData ("test",                  "",                       0)]
+	[InlineData ("test",                  "te",                     2)]
+	[InlineData ("test",                  "test",                   int.MaxValue)] // This doesn't throw because it only create a word with length 1
+	[InlineData ("A sentence has words.", "A  sentence has words.", 22)]           // should fit
+	[InlineData ("A sentence has words.", "A sentence has words.",  21)]           // should fit
+	[InlineData ("A sentence has words.",
+		"A                                                                                                                                                                 sentence                                                                                                                                                                 has                                                                                                                                                                words.",
+		500)]                                                      // should fit
+	[InlineData ("A sentence has words.", "A sentence has words", 20)] // Should not fit
+	[InlineData ("A sentence has words.", "A sentence",           10)] // Should not fit
+	// Now throw System.OutOfMemoryException. See https://stackoverflow.com/questions/20672920/maxcapacity-of-stringbuilder
+	//[InlineData ("A\tsentence\thas\twords.", "A sentence has words.", int.MaxValue)]
+	[InlineData ("A\tsentence\thas\twords.",        "A sentence",               10)]
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline2\nline3long!", int.MaxValue)] // This doesn't throw because it only create a line with length 1
+	[InlineData ("line1\nline2\nline3long!",        "line1\nline",              10)]
+	[InlineData (" ~  s  gui.cs   master ↑10", " ~  s  ",               10)] // Unicode
+	[InlineData ("Ð ÑÐ",                            "Ð  ÑÐ",                    5)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð ÑÐ",                     4)]  // should fit
+	[InlineData ("Ð ÑÐ",                            "Ð Ñ",                      3)]  // Should not fit
+	public void ClipAndJustify_Valid_Justified (string text, string justifiedText, int maxWidth)
+	{
+		var align = TextAlignment.Justified;
+		var textDirection = TextDirection.LeftRight_TopBottom;
+		var tabWidth = 1;
+
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		var expectedClippedWidth = Math.Min (justifiedText.GetRuneCount (), maxWidth);
+		Assert.Equal (justifiedText, TextFormatter.ClipAndJustify (text, maxWidth, align, textDirection, tabWidth));
+		Assert.True (justifiedText.GetRuneCount () <= maxWidth);
+		Assert.True (justifiedText.GetColumns () <= maxWidth);
+		Assert.Equal (expectedClippedWidth, justifiedText.GetRuneCount ());
+		Assert.Equal (expectedClippedWidth, justifiedText.ToRuneList ().Sum (r => Math.Max (r.GetColumns (), 1)));
+		Assert.True (expectedClippedWidth <= maxWidth);
+		Assert.Equal (StringExtensions.ToString (justifiedText.ToRunes () [..expectedClippedWidth]), justifiedText);
+
+		// see Justify_ tests below
+	}
 
-		[Fact]
-		public void WordWrap_Invalid ()
-		{
-			var text = string.Empty;
-			int width = 0;
+	[Theory]
+	[InlineData ("")]
+	[InlineData (null)]
+	[InlineData ("test")]
+	public void Justify_Invalid (string text)
+	{
+		Assert.Equal (text, TextFormatter.Justify (text, 0));
+		Assert.Equal (text, TextFormatter.Justify (text, 0));
+		Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Justify (text, -1));
+	}
 
-			Assert.Empty (TextFormatter.WordWrapText (null, width));
-			Assert.Empty (TextFormatter.WordWrapText (text, width));
-			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.WordWrapText (text, -1));
-		}
+	[Theory]
+	[InlineData ("word")]        // Even # of chars
+	[InlineData ("word.")]       // Odd # of chars
+	[InlineData ("пÑивеÑ")]  // Unicode (even #)
+	[InlineData ("пÑивеÑ.")] // Unicode (odd # of chars)
+	public void Justify_SingleWord (string text)
+	{
+		var justifiedText = text;
+		var fillChar = '+';
+
+		var width = text.GetRuneCount ();
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 1;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 2;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 10;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+		width = text.GetRuneCount () + 11;
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, width, fillChar));
+	}
 
-		[Fact]
-		public void WordWrap_BigWidth ()
-		{
-			List<string> wrappedLines;
+	[Theory]
+	// Even # of spaces
+	//            0123456789
+	[InlineData ("012 456 89", "012 456 89",                        10, 0, "+", true)]
+	[InlineData ("012 456 89", "012++456+89",                       11, 1)]
+	[InlineData ("012 456 89", "012 456 89",                        12, 2, "++", true)]
+	[InlineData ("012 456 89", "012+++456++89",                     13, 3)]
+	[InlineData ("012 456 89", "012 456 89",                        14, 4, "+++", true)]
+	[InlineData ("012 456 89", "012++++456+++89",                   15, 5)]
+	[InlineData ("012 456 89", "012 456 89",                        16, 6,  "++++",        true)]
+	[InlineData ("012 456 89", "012 456 89",                        30, 20, "+++++++++++", true)]
+	[InlineData ("012 456 89", "012+++++++++++++456++++++++++++89", 33, 23)]
+	// Odd # of spaces
+	//            01234567890123
+	[InlineData ("012 456 89 end", "012 456 89 end",                        14, 0, "+", true)]
+	[InlineData ("012 456 89 end", "012++456+89+end",                       15, 1)]
+	[InlineData ("012 456 89 end", "012++456++89+end",                      16, 2)]
+	[InlineData ("012 456 89 end", "012 456 89 end",                        17, 3, "++", true)]
+	[InlineData ("012 456 89 end", "012+++456++89++end",                    18, 4)]
+	[InlineData ("012 456 89 end", "012+++456+++89++end",                   19, 5)]
+	[InlineData ("012 456 89 end", "012 456 89 end",                        20, 6, "+++", true)]
+	[InlineData ("012 456 89 end", "012++++++++456++++++++89+++++++end",    34, 20)]
+	[InlineData ("012 456 89 end", "012+++++++++456+++++++++89++++++++end", 37, 23)]
+	// Unicode
+	// Even # of chars
+	//            0123456789
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        10, 0, "+", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ++вÐ+Ñ",                       11, 1)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        12, 2, "++", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ+++вÐ++Ñ",                     13, 3)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        14, 4, "+++", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ++++вÐ+++Ñ",                   15, 5)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        16, 6,  "++++",        true)]
+	[InlineData ("пÑРвРÑ", "пÑРвРÑ",                        30, 20, "+++++++++++", true)]
+	[InlineData ("пÑРвРÑ", "пÑÐ+++++++++++++вÐ++++++++++++Ñ", 33, 23)]
+	// Unicode
+	// Odd # of chars
+	//            0123456789
+	[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ",                        10, 0, "+", true)]
+	[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ+вÐ+Ñ",                       11, 1)]
+	[InlineData ("Ð ÑРвРÑ", "Ð++ÑÐ++вÐ+Ñ",                      12, 2)]
+	[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ",                        13, 3, "++", true)]
+	[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ++вÐ++Ñ",                    14, 4)]
+	[InlineData ("Ð ÑРвРÑ", "Ð+++ÑÐ+++вÐ++Ñ",                   15, 5)]
+	[InlineData ("Ð ÑРвРÑ", "Ð ÑРвРÑ",                        16, 6, "+++", true)]
+	[InlineData ("Ð ÑРвРÑ", "Ð++++++++ÑÐ++++++++вÐ+++++++Ñ",    30, 20)]
+	[InlineData ("Ð ÑРвРÑ", "Ð+++++++++ÑÐ+++++++++вÐ++++++++Ñ", 33, 23)]
+	public void Justify_Sentence (string text, string justifiedText, int forceToWidth, int widthOffset, string replaceWith = null, bool replace = false)
+	{
+		var fillChar = '+';
+
+		Assert.Equal (forceToWidth, text.GetRuneCount () + widthOffset);
+		if (replace) {
+			justifiedText = text.Replace (" ", replaceWith);
+		}
+		Assert.Equal (justifiedText, TextFormatter.Justify (text, forceToWidth, fillChar));
+		Assert.True (Math.Abs (forceToWidth - justifiedText.GetRuneCount ()) < text.Count (s => s == ' '));
+		Assert.True (Math.Abs (forceToWidth - justifiedText.GetColumns ()) < text.Count (s => s == ' '));
+	}
 
-			var text = "Constantinople";
-			wrappedLines = TextFormatter.WordWrapText (text, 100);
-			Assert.True (wrappedLines.Count == 1);
-			Assert.Equal ("Constantinople", wrappedLines [0]);
-		}
+	[Fact]
+	public void WordWrap_Invalid ()
+	{
+		var text = string.Empty;
+		var width = 0;
 
-		[Theory]
-		[InlineData ("Constantinople", 14, 0, new string [] { "Constantinople" })]
-		[InlineData ("Constantinople", 12, -2, new string [] { "Constantinop", "le" })]
-		[InlineData ("Constantinople", 9, -5, new string [] { "Constanti", "nople" })]
-		[InlineData ("Constantinople", 7, -7, new string [] { "Constan", "tinople" })]
-		[InlineData ("Constantinople", 5, -9, new string [] { "Const", "antin", "ople" })]
-		[InlineData ("Constantinople", 4, -10, new string [] { "Cons", "tant", "inop", "le" })]
-		[InlineData ("Constantinople", 1, -13, new string [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" })]
-		public void WordWrap_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+		Assert.Empty (TextFormatter.WordWrapText (null, width));
+		Assert.Empty (TextFormatter.WordWrapText (text, width));
+		Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.WordWrapText (text, -1));
+	}
 
-		[Theory]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new string [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new string [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
-		[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1, -50, new string [] { "ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร", "ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ" })]
-		public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
-			Assert.Single (zeroWidth);
-			Assert.Equal ('ั', zeroWidth.ElementAt (0).Value);
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Fact]
+	public void WordWrap_BigWidth ()
+	{
+		List<string> wrappedLines;
 
-		[Theory]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence", "." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2, new string [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5, new string [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9, new string [] { "This\u00A0is\u00A0a\u00A0", "sentence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7, -12, new string [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5, -14, new string [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })]
-		[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1, -18, new string [] { "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." })]
-		public void WordWrap_Unicode_LineWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+		var text = "Constantinople";
+		wrappedLines = TextFormatter.WordWrapText (text, 100);
+		Assert.True (wrappedLines.Count == 1);
+		Assert.Equal ("Constantinople", wrappedLines [0]);
+	}
 
-		[Theory]
-		[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 20, 0, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })]
-		[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.", 19, -1, new string [] { "This\u00A0is\u00A0a\u00A0sentence." })]
-		[InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0, new string [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." })]
-		public void WordWrap_Unicode_2LinesWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("Constantinople", 14, 0,   new [] { "Constantinople" })]
+	[InlineData ("Constantinople", 12, -2,  new [] { "Constantinop", "le" })]
+	[InlineData ("Constantinople", 9,  -5,  new [] { "Constanti", "nople" })]
+	[InlineData ("Constantinople", 7,  -7,  new [] { "Constan", "tinople" })]
+	[InlineData ("Constantinople", 5,  -9,  new [] { "Const", "antin", "ople" })]
+	[InlineData ("Constantinople", 4,  -10, new [] { "Cons", "tant", "inop", "le" })]
+	[InlineData ("Constantinople", 1,  -13, new [] { "C", "o", "n", "s", "t", "a", "n", "t", "i", "n", "o", "p", "l", "e" })]
+	public void WordWrap_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.", 21, 0, new string [] { "A sentence has words." })]
-		[InlineData ("A sentence has words.", 20, -1, new string [] { "A sentence has", "words." })]
-		[InlineData ("A sentence has words.", 15, -6, new string [] { "A sentence has", "words." })]
-		[InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence has", "words." })]
-		[InlineData ("A sentence has words.", 13, -8, new string [] { "A sentence", "has words." })]
-		// Unicode 
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7, new string [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8, new string [] { "A Unicode sentence (пÑивеÑ)", "has words." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new string [] { "A Unicode sentence", "(пÑивеÑ) has words." })]
-		public void WordWrap_NoNewLines_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 51, 0,   new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 50, -1,  new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 46, -5,  new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮ", "ฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 26, -25, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 17, -34, new [] { "กขฃคฅฆงจฉชซฌญฎฏฐฑ", "ฒณดตถทธนบปผฝพฟภมย", "รฤลฦวศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 13, -38, new [] { "กขฃคฅฆงจฉชซฌญ", "ฎฏฐฑฒณดตถทธนบ", "ปผฝพฟภมยรฤลฦว", "ศษสหฬอฮฯะัาำ" })]
+	[InlineData ("กขฃคฅฆงจฉชซฌญฎฏฐฑฒณดตถทธนบปผฝพฟภมยรฤลฦวศษสหฬอฮฯะัาำ", 1,  -50,
+		new [] {
+			"ก", "ข", "ฃ", "ค", "ฅ", "ฆ", "ง", "จ", "ฉ", "ช", "ซ", "ฌ", "ญ", "ฎ", "ฏ", "ฐ", "ฑ", "ฒ", "ณ", "ด", "ต", "ถ", "ท", "ธ", "น", "บ", "ป", "ผ", "ฝ", "พ", "ฟ", "ภ", "ม", "ย", "ร",
+			"ฤ", "ล", "ฦ", "ว", "ศ", "ษ", "ส", "ห", "ฬ", "อ", "ฮ", "ฯ", "ะั", "า", "ำ"
+		})]
+	public void WordWrap_Unicode_SingleWordLine (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		var zeroWidth = text.EnumerateRunes ().Where (r => r.GetColumns () == 0);
+		Assert.Single (zeroWidth);
+		Assert.Equal ('ั',      zeroWidth.ElementAt (0).Value);
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount () + zeroWidth.Count () - 1 + widthOffset) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		/// <summary>
-		/// WordWrap strips CRLF
-		/// </summary>
-		[Theory]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0, new string [] { "A sentence has words.A paragraph has lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1, new string [] { "A sentence has words.A paragraph has lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6, new string [] { "A sentence has words.A paragraph has", "lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new string [] { "A sentence has words.A paragraph", "has lines." })]
-		[InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new string [] { "A sentence has words.A", "paragraph has lines." })]
-		// Unicode 
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })]
-		[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new string [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." })]
-		public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 19, 0,   new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 18, -1,  new [] { "This\u00A0is\u00A0a\u00A0sentence", "." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 17, -2,  new [] { "This\u00A0is\u00A0a\u00A0sentenc", "e." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 14, -5,  new [] { "This\u00A0is\u00A0a\u00A0sent", "ence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 10, -9,  new [] { "This\u00A0is\u00A0a\u00A0", "sentence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 7,  -12, new [] { "This\u00A0is", "\u00A0a\u00A0sent", "ence." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 5,  -14, new [] { "This\u00A0", "is\u00A0a\u00A0", "sente", "nce." })]
+	[InlineData ("This\u00A0is\u00A0a\u00A0sentence.", 1,  -18, new [] { "T", "h", "i", "s", "\u00A0", "i", "s", "\u00A0", "a", "\u00A0", "s", "e", "n", "t", "e", "n", "c", "e", "." })]
+	public void WordWrap_Unicode_LineWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.", 3, -18, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })]
-		[InlineData ("A sentence has words.", 2, -19, new string [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })]
-		[InlineData ("A sentence has words.", 1, -20, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })]
-		public void WordWrap_Narrow_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.",              20, 0,  new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
+	[InlineData ("This\u00A0is\n\u00A0a\u00A0sentence.",              19, -1, new [] { "This\u00A0is\u00A0a\u00A0sentence." })]
+	[InlineData ("\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence.", 19, 0,  new [] { "\u00A0\u00A0\u00A0\u00A0\u00A0test\u00A0sentence." })]
+	public void WordWrap_Unicode_2LinesWithNonBreakingSpace (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.", 14, -7, new string [] { "A sentence ", "has words." })]
-		[InlineData ("A sentence has words.", 8, -13, new string [] { "A ", "sentence", " has ", "words." })]
-		[InlineData ("A sentence has words.", 6, -15, new string [] { "A ", "senten", "ce ", "has ", "words." })]
-		[InlineData ("A sentence has words.", 3, -18, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })]
-		[InlineData ("A sentence has words.", 2, -19, new string [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })]
-		[InlineData ("A sentence has words.", 1, -20, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
-		public void WordWrap_PreserveTrailingSpaces_True (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.", 21, 0,  new [] { "A sentence has words." })]
+	[InlineData ("A sentence has words.", 20, -1, new [] { "A sentence has", "words." })]
+	[InlineData ("A sentence has words.", 15, -6, new [] { "A sentence has", "words." })]
+	[InlineData ("A sentence has words.", 14, -7, new [] { "A sentence has", "words." })]
+	[InlineData ("A sentence has words.", 13, -8, new [] { "A sentence", "has words." })]
+	// Unicode 
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 42, 0,   new [] { "A Unicode sentence (пÑивеÑ) has words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 41, -1,  new [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 36, -6,  new [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 35, -7,  new [] { "A Unicode sentence (пÑивеÑ) has", "words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 34, -8,  new [] { "A Unicode sentence (пÑивеÑ)", "has words." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.", 25, -17, new [] { "A Unicode sentence", "(пÑивеÑ) has words." })]
+	public void WordWrap_NoNewLines_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉 ", "があり ます。" })]
-		[InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { })]
-		public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	/// <summary>
+	/// WordWrap strips CRLF
+	/// </summary>
+	[Theory]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 44, 0,   new [] { "A sentence has words.A paragraph has lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 43, -1,  new [] { "A sentence has words.A paragraph has lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 38, -6,  new [] { "A sentence has words.A paragraph has", "lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 34, -10, new [] { "A sentence has words.A paragraph", "has lines." })]
+	[InlineData ("A sentence has words.\nA paragraph has lines.", 27, -17, new [] { "A sentence has words.A", "paragraph has lines." })]
+	// Unicode 
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 69, 0,   new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 68, -1,  new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 63, -6,  new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт has", "Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 59, -10, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode Пункт", "has Линии." })]
+	[InlineData ("A Unicode sentence (пÑивеÑ) has words.\nA Unicode Пункт has Линии.", 52, -17, new [] { "A Unicode sentence (пÑивеÑ) has words.A Unicode", "Пункт has Линии." })]
+	public void WordWrap_WithNewLines (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("文に は言葉 があり ます。", 14, 0, new string [] { "文に は言葉", "があり ます。" })]
-		[InlineData ("文に は言葉 があり ます。", 3, -11, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 2, -12, new string [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
-		[InlineData ("文に は言葉 があり ます。", 1, -13, new string [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line
-		public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.", 3, -18, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })]
+	[InlineData ("A sentence has words.", 2, -19, new [] { "A", "se", "nt", "en", "ce", "ha", "s", "wo", "rd", "s." })]
+	[InlineData ("A sentence has words.", 1, -20, new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })]
+	public void WordWrap_Narrow_Default (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		// Calls WordWrapText (text, width) and thus preserveTrailingSpaces defaults to false
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words. ", 3, new string [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })]
-		[InlineData ("A   sentence          has  words.  ", 3, new string [] { "A  ", " ", "sen", "ten", "ce ", "   ", "   ", "   ", "has", "  ", "wor", "ds.", "  " })]
-		public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-
-			// Double space Complex example - this is how VS 2022 does it
-			//text = "A  sentence      has words.  ";
-			//breakLines = "";
-			//wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
-			//foreach (var line in wrappedLines) {
-			//	breakLines += $"{line}{Environment.NewLine}";
-			//}
-			//expected = "A  " + Environment.NewLine +
-			//	" se" + Environment.NewLine +
-			//	" nt" + Environment.NewLine +
-			//	" en" + Environment.NewLine +
-			//	" ce" + Environment.NewLine +
-			//	"  " + Environment.NewLine +
-			//	"  " + Environment.NewLine +
-			//	"  " + Environment.NewLine +
-			//	" ha" + Environment.NewLine +
-			//	" s " + Environment.NewLine +
-			//	" wo" + Environment.NewLine +
-			//	" rd" + Environment.NewLine +
-			//	" s." + Environment.NewLine;
-			//Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.", 14, -7,  new [] { "A sentence ", "has words." })]
+	[InlineData ("A sentence has words.", 8,  -13, new [] { "A ", "sentence", " has ", "words." })]
+	[InlineData ("A sentence has words.", 6,  -15, new [] { "A ", "senten", "ce ", "has ", "words." })]
+	[InlineData ("A sentence has words.", 3,  -18, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds." })]
+	[InlineData ("A sentence has words.", 2,  -19, new [] { "A ", "se", "nt", "en", "ce", " ", "ha", "s ", "wo", "rd", "s." })]
+	[InlineData ("A sentence has words.", 1,  -20, new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
+	public void WordWrap_PreserveTrailingSpaces_True (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData (null, 1, new string [] { })] // null input
-		[InlineData ("", 1, new string [] { })] // Empty input
-		[InlineData ("1 34", 1, new string [] { "1", "3", "4" })] // Single Spaces
-		[InlineData ("1", 1, new string [] { "1" })] // Short input
-		[InlineData ("12", 1, new string [] { "1", "2" })]
-		[InlineData ("123", 1, new string [] { "1", "2", "3" })]
-		[InlineData ("123456", 1, new string [] { "1", "2", "3", "4", "5", "6" })] // No spaces
-		[InlineData (" ", 1, new string [] { " " })] // Just Spaces; should result in a single space
-		[InlineData ("  ", 1, new string [] { " " })]
-		[InlineData ("   ", 1, new string [] { " ", " " })]
-		[InlineData ("    ", 1, new string [] { " ", " " })]
-		[InlineData ("12 456", 1, new string [] { "1", "2", "4", "5", "6" })] // Single Spaces
-		[InlineData (" 2 456", 1, new string [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved.
-		[InlineData (" 2 456 8", 1, new string [] { " ", "2", "4", "5", "6", "8" })]
-		[InlineData ("A sentence has words. ", 1, new string [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example
-		[InlineData ("12  567", 1, new string [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces
-		[InlineData ("  3 567", 1, new string [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved.
-		[InlineData ("  3  678  1", 1, new string [] { " ", "3", " ", "6", "7", "8", " ", "1" })]
-		[InlineData ("1  456", 1, new string [] { "1", " ", "4", "5", "6" })]
-		[InlineData ("A  sentence   has words.  ", 1, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " })] // Double space Complex example
-		public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("文に は言葉 があり ます。", 14, 0,   new [] { "文に は言葉 ", "があり ます。" })]
+	[InlineData ("文に は言葉 があり ます。", 3,  -11, new [] { "文", "に ", "は", "言", "葉 ", "が", "あ", "り ", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 2,  -12, new [] { "文", "に", " ", "は", "言", "葉", " ", "が", "あ", "り", " ", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 1,  -13, new string [] { })]
+	public void WordWrap_PreserveTrailingSpaces_True_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData (null, 3, new string [] { })] // null input
-		[InlineData ("", 3, new string [] { })] // Empty input
-		[InlineData ("1", 3, new string [] { "1" })] // Short input
-		[InlineData ("12", 3, new string [] { "12" })]
-		[InlineData ("123", 3, new string [] { "123" })]
-		[InlineData ("123456", 3, new string [] { "123", "456" })] // No spaces
-		[InlineData ("1234567", 3, new string [] { "123", "456", "7" })] // No spaces
-		[InlineData (" ", 3, new string [] { " " })] // Just Spaces; should result in a single space
-		[InlineData ("  ", 3, new string [] { "  " })]
-		[InlineData ("   ", 3, new string [] { "   " })]
-		[InlineData ("    ", 3, new string [] { "   " })]
-		[InlineData ("12 456", 3, new string [] { "12", "456" })] // Single Spaces
-		[InlineData (" 2 456", 3, new string [] { " 2", "456" })] // Leading spaces should be preserved.
-		[InlineData (" 2 456 8", 3, new string [] { " 2", "456", "8" })]
-		[InlineData ("A sentence has words. ", 3, new string [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example
-		[InlineData ("12  567", 3, new string [] { "12 ", "567" })] // Double Spaces
-		[InlineData ("  3 567", 3, new string [] { "  3", "567" })] // Double Leading spaces should be preserved.
-		[InlineData ("  3  678  1", 3, new string [] { "  3", " 67", "8 ", "1" })]
-		[InlineData ("1  456", 3, new string [] { "1 ", "456" })]
-		[InlineData ("A  sentence      has words.  ", 3, new string [] { "A ", "sen", "ten", "ce ", "   ", "has", "wor", "ds.", " " })] // Double space Complex example
-		public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("文に は言葉 があり ます。", 14, 0,   new [] { "文に は言葉", "があり ます。" })]
+	[InlineData ("文に は言葉 があり ます。", 3,  -11, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 2,  -12, new [] { "文", "に", "は", "言", "葉", "が", "あ", "り", "ま", "す", "。" })]
+	[InlineData ("文に は言葉 があり ます。", 1,  -13, new [] { " ", " ", " " })] // Just Spaces; should result in a single space for each line
+	public void WordWrap_PreserveTrailingSpaces_False_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData (null, 50, new string [] { })] // null input
-		[InlineData ("", 50, new string [] { })] // Empty input
-		[InlineData ("1", 50, new string [] { "1" })] // Short input
-		[InlineData ("12", 50, new string [] { "12" })]
-		[InlineData ("123", 50, new string [] { "123" })]
-		[InlineData ("123456", 50, new string [] { "123456" })] // No spaces
-		[InlineData ("1234567", 50, new string [] { "1234567" })] // No spaces
-		[InlineData (" ", 50, new string [] { " " })] // Just Spaces; should result in a single space
-		[InlineData ("  ", 50, new string [] { "  " })]
-		[InlineData ("   ", 50, new string [] { "   " })]
-		[InlineData ("12 456", 50, new string [] { "12 456" })] // Single Spaces
-		[InlineData (" 2 456", 50, new string [] { " 2 456" })] // Leading spaces should be preserved.
-		[InlineData (" 2 456 8", 50, new string [] { " 2 456 8" })]
-		[InlineData ("A sentence has words. ", 50, new string [] { "A sentence has words. " })] // Complex example
-		[InlineData ("12  567", 50, new string [] { "12  567" })] // Double Spaces
-		[InlineData ("  3 567", 50, new string [] { "  3 567" })] // Double Leading spaces should be preserved.
-		[InlineData ("  3  678  1", 50, new string [] { "  3  678  1" })]
-		[InlineData ("1  456", 50, new string [] { "1  456" })]
-		[InlineData ("A  sentence      has words.  ", 50, new string [] { "A  sentence      has words.  " })] // Double space Complex example
-		public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable<string> resultLines)
-		{
-			var wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.Equal (resultLines, wrappedLines);
-			var breakLines = "";
-			foreach (var line in wrappedLines) {
-				breakLines += $"{line}{Environment.NewLine}";
-			}
-			var expected = string.Empty;
-			foreach (var line in resultLines) {
-				expected += $"{line}{Environment.NewLine}";
-			}
-			Assert.Equal (expected, breakLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words. ",              3, new [] { "A ", "sen", "ten", "ce ", "has", " ", "wor", "ds.", " " })]
+	[InlineData ("A   sentence          has  words.  ", 3, new [] { "A  ", " ", "sen", "ten", "ce ", "   ", "   ", "   ", "has", "  ", "wor", "ds.", "  " })]
+	public void WordWrap_PreserveTrailingSpaces_True_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width, true);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+
+		// Double space Complex example - this is how VS 2022 does it
+		//text = "A  sentence      has words.  ";
+		//breakLines = "";
+		//wrappedLines = TextFormatter.WordWrapText (text, width, preserveTrailingSpaces: true);
+		//foreach (var line in wrappedLines) {
+		//	breakLines += $"{line}{Environment.NewLine}";
+		//}
+		//expected = "A  " + Environment.NewLine +
+		//	" se" + Environment.NewLine +
+		//	" nt" + Environment.NewLine +
+		//	" en" + Environment.NewLine +
+		//	" ce" + Environment.NewLine +
+		//	"  " + Environment.NewLine +
+		//	"  " + Environment.NewLine +
+		//	"  " + Environment.NewLine +
+		//	" ha" + Environment.NewLine +
+		//	" s " + Environment.NewLine +
+		//	" wo" + Environment.NewLine +
+		//	" rd" + Environment.NewLine +
+		//	" s." + Environment.NewLine;
+		//Assert.Equal (expected, breakLines);
+	}
 
-		[Theory]
-		[InlineData ("A sentence\t\t\t has words.", 14, -10, new string [] { "A sentence\t", "\t\t has ", "words." })]
-		[InlineData ("A sentence\t\t\t has words.", 8, -16, new string [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })]
-		[InlineData ("A sentence\t\t\t has words.", 3, -21, new string [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })]
-		[InlineData ("A sentence\t\t\t has words.", 2, -22, new string [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })]
-		[InlineData ("A sentence\t\t\t has words.", 1, -23, new string [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", "\t", "\t", "\t", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
-		public void WordWrap_PreserveTrailingSpaces_True_With_Tab (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines, int tabWidth = 4)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: true, tabWidth: tabWidth);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData (null,                         1, new string [] { })] // null input
+	[InlineData ("",                           1, new string [] { })] // Empty input
+	[InlineData ("1 34",                       1, new [] { "1", "3", "4" })] // Single Spaces
+	[InlineData ("1",                          1, new [] { "1" })] // Short input
+	[InlineData ("12",                         1, new [] { "1", "2" })]
+	[InlineData ("123",                        1, new [] { "1", "2", "3" })]
+	[InlineData ("123456",                     1, new [] { "1", "2", "3", "4", "5", "6" })] // No spaces
+	[InlineData (" ",                          1, new [] { " " })] // Just Spaces; should result in a single space
+	[InlineData ("  ",                         1, new [] { " " })]
+	[InlineData ("   ",                        1, new [] { " ", " " })]
+	[InlineData ("    ",                       1, new [] { " ", " " })]
+	[InlineData ("12 456",                     1, new [] { "1", "2", "4", "5", "6" })] // Single Spaces
+	[InlineData (" 2 456",                     1, new [] { " ", "2", "4", "5", "6" })] // Leading spaces should be preserved.
+	[InlineData (" 2 456 8",                   1, new [] { " ", "2", "4", "5", "6", "8" })]
+	[InlineData ("A sentence has words. ",     1, new [] { "A", "s", "e", "n", "t", "e", "n", "c", "e", "h", "a", "s", "w", "o", "r", "d", "s", "." })] // Complex example
+	[InlineData ("12  567",                    1, new [] { "1", "2", " ", "5", "6", "7" })] // Double Spaces
+	[InlineData ("  3 567",                    1, new [] { " ", "3", "5", "6", "7" })] // Double Leading spaces should be preserved.
+	[InlineData ("  3  678  1",                1, new [] { " ", "3", " ", "6", "7", "8", " ", "1" })]
+	[InlineData ("1  456",                     1, new [] { "1", " ", "4", "5", "6" })]
+	[InlineData ("A  sentence   has words.  ", 1,
+		new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", " ", "h", "a", "s", "w", "o", "r", "d", "s", ".", " " })] // Double space Complex example
+	public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_1 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+	}
 
-		[Theory]
-		[InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new string [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })]
-		public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
-		{
-			List<string> wrappedLines;
-
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			wrappedLines = TextFormatter.WordWrapText (text, maxWidth, preserveTrailingSpaces: false);
-			Assert.Equal (wrappedLines.Count, resultLines.Count ());
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
-			Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
-			Assert.Equal (resultLines, wrappedLines);
-		}
+	[Theory]
+	[InlineData (null,                            3, new string [] { })] // null input
+	[InlineData ("",                              3, new string [] { })] // Empty input
+	[InlineData ("1",                             3, new [] { "1" })]    // Short input
+	[InlineData ("12",                            3, new [] { "12" })]
+	[InlineData ("123",                           3, new [] { "123" })]
+	[InlineData ("123456",                        3, new [] { "123", "456" })]      // No spaces
+	[InlineData ("1234567",                       3, new [] { "123", "456", "7" })] // No spaces
+	[InlineData (" ",                             3, new [] { " " })]               // Just Spaces; should result in a single space
+	[InlineData ("  ",                            3, new [] { "  " })]
+	[InlineData ("   ",                           3, new [] { "   " })]
+	[InlineData ("    ",                          3, new [] { "   " })]
+	[InlineData ("12 456",                        3, new [] { "12", "456" })] // Single Spaces
+	[InlineData (" 2 456",                        3, new [] { " 2", "456" })] // Leading spaces should be preserved.
+	[InlineData (" 2 456 8",                      3, new [] { " 2", "456", "8" })]
+	[InlineData ("A sentence has words. ",        3, new [] { "A", "sen", "ten", "ce", "has", "wor", "ds." })] // Complex example
+	[InlineData ("12  567",                       3, new [] { "12 ", "567" })]                                 // Double Spaces
+	[InlineData ("  3 567",                       3, new [] { "  3", "567" })]                                 // Double Leading spaces should be preserved.
+	[InlineData ("  3  678  1",                   3, new [] { "  3", " 67", "8 ", "1" })]
+	[InlineData ("1  456",                        3, new [] { "1 ", "456" })]
+	[InlineData ("A  sentence      has words.  ", 3, new [] { "A ", "sen", "ten", "ce ", "   ", "has", "wor", "ds.", " " })] // Double space Complex example
+	public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_3 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+	}
 
-		[Theory]
-		[InlineData ("test", 0, 't', "test")]
-		[InlineData ("test", 1, 'e', "test")]
-		[InlineData ("Ok", 0, 'O', "Ok")]
-		[InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")]
-		[InlineData ("^k", 0, '^', "^k")]
-		public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected)
-		{
-			var tf = new TextFormatter ();
-			var runes = text.ToRuneList ();
-			Rune rune;
-			if (Rune.TryGetRuneAt (text, hotPos, out rune)) {
-				Assert.Equal (rune, (Rune)tag);
+	[Theory]
+	[InlineData (null,                            50, new string [] { })] // null input
+	[InlineData ("",                              50, new string [] { })] // Empty input
+	[InlineData ("1",                             50, new [] { "1" })]    // Short input
+	[InlineData ("12",                            50, new [] { "12" })]
+	[InlineData ("123",                           50, new [] { "123" })]
+	[InlineData ("123456",                        50, new [] { "123456" })]  // No spaces
+	[InlineData ("1234567",                       50, new [] { "1234567" })] // No spaces
+	[InlineData (" ",                             50, new [] { " " })]       // Just Spaces; should result in a single space
+	[InlineData ("  ",                            50, new [] { "  " })]
+	[InlineData ("   ",                           50, new [] { "   " })]
+	[InlineData ("12 456",                        50, new [] { "12 456" })] // Single Spaces
+	[InlineData (" 2 456",                        50, new [] { " 2 456" })] // Leading spaces should be preserved.
+	[InlineData (" 2 456 8",                      50, new [] { " 2 456 8" })]
+	[InlineData ("A sentence has words. ",        50, new [] { "A sentence has words. " })] // Complex example
+	[InlineData ("12  567",                       50, new [] { "12  567" })]                // Double Spaces
+	[InlineData ("  3 567",                       50, new [] { "  3 567" })]                // Double Leading spaces should be preserved.
+	[InlineData ("  3  678  1",                   50, new [] { "  3  678  1" })]
+	[InlineData ("1  456",                        50, new [] { "1  456" })]
+	[InlineData ("A  sentence      has words.  ", 50, new [] { "A  sentence      has words.  " })] // Double space Complex example
+	public void WordWrap_PreserveTrailingSpaces_False_With_Simple_Runes_Width_50 (string text, int width, IEnumerable<string> resultLines)
+	{
+		var wrappedLines = TextFormatter.WordWrapText (text, width);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.Equal (resultLines,        wrappedLines);
+		var breakLines = "";
+		foreach (var line in wrappedLines) {
+			breakLines += $"{line}{Environment.NewLine}";
+		}
+		var expected = string.Empty;
+		foreach (var line in resultLines) {
+			expected += $"{line}{Environment.NewLine}";
+		}
+		Assert.Equal (expected, breakLines);
+	}
 
-			}
-			var result = tf.ReplaceHotKeyWithTag (text, hotPos);
-			Assert.Equal (result, expected);
-			Assert.Equal ((Rune)tag, result.ToRunes () [hotPos]);
-			Assert.Equal (text.GetRuneCount (), runes.Count);
-			Assert.Equal (text, StringExtensions.ToString (runes));
-		}
+	[Theory]
+	[InlineData ("A sentence\t\t\t has words.", 14, -10, new [] { "A sentence\t", "\t\t has ", "words." })]
+	[InlineData ("A sentence\t\t\t has words.", 8,  -16, new [] { "A ", "sentence", "\t\t", "\t ", "has ", "words." })]
+	[InlineData ("A sentence\t\t\t has words.", 3,  -21, new [] { "A ", "sen", "ten", "ce", "\t", "\t", "\t", " ", "has", " ", "wor", "ds." })]
+	[InlineData ("A sentence\t\t\t has words.", 2,  -22, new [] { "A ", "se", "nt", "en", "ce", "\t", "\t", "\t", " ", "ha", "s ", "wo", "rd", "s." })]
+	[InlineData ("A sentence\t\t\t has words.", 1,  -23, new [] { "A", " ", "s", "e", "n", "t", "e", "n", "c", "e", "\t", "\t", "\t", " ", "h", "a", "s", " ", "w", "o", "r", "d", "s", "." })]
+	public void WordWrap_PreserveTrailingSpaces_True_With_Tab (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines, int tabWidth = 4)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth, true, tabWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("", -1, TextAlignment.Left, false, 0)]
-		[InlineData (null, 0, TextAlignment.Left, false, 1)]
-		[InlineData (null, 0, TextAlignment.Left, true, 1)]
-		[InlineData ("", 0, TextAlignment.Left, false, 1)]
-		[InlineData ("", 0, TextAlignment.Left, true, 1)]
-		public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount)
-		{
-			if (maxWidth < 0) {
-				Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap));
-			} else {
-				var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
-				Assert.NotEmpty (list);
-				Assert.True (list.Count == linesCount);
-				Assert.Equal (string.Empty, list [0]);
-			}
-		}
+	[Theory]
+	[InlineData ("これが最初の行です。 こんにちは世界。 これが2行目です。", 29, 0, new [] { "これが最初の行です。", "こんにちは世界。", "これが2行目です。" })]
+	public void WordWrap_PreserveTrailingSpaces_False_Unicode_Wide_Runes (string text, int maxWidth, int widthOffset, IEnumerable<string> resultLines)
+	{
+		List<string> wrappedLines;
+
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		wrappedLines = TextFormatter.WordWrapText (text, maxWidth);
+		Assert.Equal (wrappedLines.Count, resultLines.Count ());
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetRuneCount ()) : 0));
+		Assert.True (expectedClippedWidth >= (wrappedLines.Count > 0 ? wrappedLines.Max (l => l.GetColumns ()) : 0));
+		Assert.Equal (resultLines, wrappedLines);
+	}
 
-		[Theory]
-		[InlineData ("", 0, 0, TextAlignment.Left, false, 1, true)]
-		[InlineData ("", 1, 1, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.", 0, -21, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.", 1, -20, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.", 5, -16, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.", 20, -1, TextAlignment.Left, false, 1, false)]
-		// no clip
-		[InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)]
-		public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
-			Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]);
-		}
+	[Theory]
+	[InlineData ("test",     0, 't', "test")]
+	[InlineData ("test",     1, 'e', "test")]
+	[InlineData ("Ok",       0, 'O', "Ok")]
+	[InlineData ("[◦ Ok ◦]", 3, 'O', "[◦ Ok ◦]")]
+	[InlineData ("^k",       0, '^', "^k")]
+	public void ReplaceHotKeyWithTag (string text, int hotPos, uint tag, string expected)
+	{
+		var tf = new TextFormatter ();
+		var runes = text.ToRuneList ();
+		Rune rune;
+		if (Rune.TryGetRuneAt (text, hotPos, out rune)) {
+			Assert.Equal (rune, (Rune)tag);
+
+		}
+		var result = tf.ReplaceHotKeyWithTag (text, hotPos);
+		Assert.Equal (result,               expected);
+		Assert.Equal ((Rune)tag,            result.ToRunes () [hotPos]);
+		Assert.Equal (text.GetRuneCount (), runes.Count);
+		Assert.Equal (text,                 StringExtensions.ToString (runes));
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 1, false)]
-		// no clip
-		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 1, false, 1)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 1, false)]
-		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 1, false)]
-		public void Reformat_NoWordrap_NewLines_MultiLine_False (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, int clipWidthOffset = 0)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
+	[Theory]
+	[InlineData ("",   -1, TextAlignment.Left, false, 0)]
+	[InlineData (null, 0,  TextAlignment.Left, false, 1)]
+	[InlineData (null, 0,  TextAlignment.Left, true,  1)]
+	[InlineData ("",   0,  TextAlignment.Left, false, 1)]
+	[InlineData ("",   0,  TextAlignment.Left, true,  1)]
+	public void Reformat_Invalid (string text, int maxWidth, TextAlignment textAlignment, bool wrap, int linesCount)
+	{
+		if (maxWidth < 0) {
+			Assert.Throws<ArgumentOutOfRangeException> (() => TextFormatter.Format (text, maxWidth, textAlignment, wrap));
+		} else {
 			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
 			Assert.NotEmpty (list);
 			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
-			if (text.Contains ("\r\n") && maxWidth > 0) {
-				Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\r\n", " "), list [0]);
-			} else if (text.Contains ('\n') && maxWidth > 0) {
-				Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]).Replace ("\n", " "), list [0]);
-			} else {
-				Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]);
-			}
+			Assert.Equal (string.Empty, list [0]);
 		}
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		//// no clip
-		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		public void Reformat_NoWordrap_NewLines_MultiLine_True (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
+	[Theory]
+	[InlineData ("",                      0,  0,   TextAlignment.Left, false, 1, true)]
+	[InlineData ("",                      1,  1,   TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.", 0,  -21, TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.", 1,  -20, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.", 5,  -16, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.", 20, -1,  TextAlignment.Left, false, 1, false)]
+	// no clip
+	[InlineData ("A sentence has words.", 21, 0, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.", 22, 1, TextAlignment.Left, false, 1, false)]
+	public void Reformat_NoWordrap_SingleLine (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+		Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
+	}
 
-			Assert.Equal (list, resultLines);
+	[Theory]
+	[InlineData ("A sentence has words.\nLine 2.", 0,  -29, TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.\nLine 2.", 1,  -28, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\nLine 2.", 5,  -24, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\nLine 2.", 28, -1,  TextAlignment.Left, false, 1, false)]
+	// no clip
+	[InlineData ("A sentence has words.\nLine 2.",   29, 0,   TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\nLine 2.",   30, 1,   TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 0,  -30, TextAlignment.Left, false, 1, true)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 1,  -29, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 5,  -25, TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 29, -1,  TextAlignment.Left, false, 1, false, 1)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 30, 0,   TextAlignment.Left, false, 1, false)]
+	[InlineData ("A sentence has words.\r\nLine 2.", 31, 1,   TextAlignment.Left, false, 1, false)]
+	public void Reformat_NoWordrap_NewLines_MultiLine_False (string text,
+								 int maxWidth,
+								 int widthOffset,
+								 TextAlignment textAlignment,
+								 bool wrap,
+								 int linesCount,
+								 bool stringEmpty,
+								 int clipWidthOffset = 0)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth) + clipWidthOffset;
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+		if (text.Contains ("\r\n") && maxWidth > 0) {
+			Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]).Replace ("\r\n", " "), list [0]);
+		} else if (text.Contains ('\n') && maxWidth > 0) {
+			Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]).Replace ("\n", " "), list [0]);
+		} else {
+			Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
 		}
+	}
 
-		[Theory]
-		[InlineData ("A sentence has words.\nLine 2.", 0, -29, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\nLine 2.", 1, -28, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\nLine 2.", 5, -24, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\nLine 2.", 28, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		//// no clip
-		[InlineData ("A sentence has words.\nLine 2.", 29, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\nLine 2.", 30, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 0, -30, TextAlignment.Left, false, 1, true, new string [] { "" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 1, -29, TextAlignment.Left, false, 2, false, new string [] { "A", "L" })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 5, -25, TextAlignment.Left, false, 2, false, new string [] { "A sen", "Line " })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 29, -1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 30, 0, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		[InlineData ("A sentence has words.\r\nLine 2.", 31, 1, TextAlignment.Left, false, 2, false, new string [] { "A sentence has words.", "Line 2." })]
-		public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, int linesCount, bool stringEmpty, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == linesCount);
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
-			} else {
-				Assert.NotEqual (string.Empty, list [0]);
-			}
+	[Theory]
+	[InlineData ("A sentence has words.\nLine 2.", 0,  -29, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\nLine 2.", 1,  -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\nLine 2.", 5,  -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\nLine 2.", 28, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	//// no clip
+	[InlineData ("A sentence has words.\nLine 2.",   29, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\nLine 2.",   30, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 0,  -30, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 1,  -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 5,  -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 29, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 30, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 31, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	public void Reformat_NoWordrap_NewLines_MultiLine_True (string text,
+								int maxWidth,
+								int widthOffset,
+								TextAlignment textAlignment,
+								bool wrap,
+								int linesCount,
+								bool stringEmpty,
+								IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.LeftRight_TopBottom, true);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+
+		Assert.Equal (list, resultLines);
+	}
 
-			Assert.Equal (list, resultLines);
-		}
+	[Theory]
+	[InlineData ("A sentence has words.\nLine 2.", 0,  -29, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\nLine 2.", 1,  -28, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\nLine 2.", 5,  -24, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\nLine 2.", 28, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	//// no clip
+	[InlineData ("A sentence has words.\nLine 2.",   29, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\nLine 2.",   30, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 0,  -30, TextAlignment.Left, false, 1, true,  new [] { "" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 1,  -29, TextAlignment.Left, false, 2, false, new [] { "A", "L" })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 5,  -25, TextAlignment.Left, false, 2, false, new [] { "A sen", "Line " })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 29, -1,  TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 30, 0,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	[InlineData ("A sentence has words.\r\nLine 2.", 31, 1,   TextAlignment.Left, false, 2, false, new [] { "A sentence has words.", "Line 2." })]
+	public void Reformat_NoWordrap_NewLines_MultiLine_True_Vertical (string text,
+									 int maxWidth,
+									 int widthOffset,
+									 TextAlignment textAlignment,
+									 bool wrap,
+									 int linesCount,
+									 bool stringEmpty,
+									 IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, false, 0, TextDirection.TopBottom_LeftRight, true);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == linesCount);
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+
+		Assert.Equal (list, resultLines);
+	}
 
-		[Theory]
-		// Even # of spaces
-		//            0123456789
-		[InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true, new string [] { "" })]
-		[InlineData ("012 456 89", 1, -9, TextAlignment.Left, true, true, false, new string [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")]
-		[InlineData ("012 456 89", 5, -5, TextAlignment.Left, true, true, false, new string [] { "012 ", "456 ", "89" })]
-		[InlineData ("012 456 89", 9, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 ", "89" })]
-		// no clip
-		[InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })]
-		[InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89" })]
-		// Odd # of spaces
-		//            01234567890123
-		[InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 ", "end" })]
-		// no clip
-		[InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })]
-		[InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new string [] { "012 456 89 end" })]
-		public void Reformat_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, bool stringEmpty, IEnumerable<string> resultLines, string noSpaceText = "")
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.NotEmpty (list);
-			Assert.True (list.Count == resultLines.Count ());
-			if (stringEmpty) {
-				Assert.Equal (string.Empty, list [0]);
+	[Theory]
+	// Even # of spaces
+	//            0123456789
+	[InlineData ("012 456 89", 0, -10, TextAlignment.Left, true, true, true,  new [] { "" })]
+	[InlineData ("012 456 89", 1, -9,  TextAlignment.Left, true, true, false, new [] { "0", "1", "2", " ", "4", "5", "6", " ", "8", "9" }, "01245689")]
+	[InlineData ("012 456 89", 5, -5,  TextAlignment.Left, true, true, false, new [] { "012 ", "456 ", "89" })]
+	[InlineData ("012 456 89", 9, -1,  TextAlignment.Left, true, true, false, new [] { "012 456 ", "89" })]
+	// no clip
+	[InlineData ("012 456 89", 10, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89" })]
+	[InlineData ("012 456 89", 11, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89" })]
+	// Odd # of spaces
+	//            01234567890123
+	[InlineData ("012 456 89 end", 13, -1, TextAlignment.Left, true, true, false, new [] { "012 456 89 ", "end" })]
+	// no clip
+	[InlineData ("012 456 89 end", 14, 0, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })]
+	[InlineData ("012 456 89 end", 15, 1, TextAlignment.Left, true, true, false, new [] { "012 456 89 end" })]
+	public void Reformat_Wrap_Spaces_No_NewLines (string text,
+						      int maxWidth,
+						      int widthOffset,
+						      TextAlignment textAlignment,
+						      bool wrap,
+						      bool preserveTrailingSpaces,
+						      bool stringEmpty,
+						      IEnumerable<string> resultLines,
+						      string noSpaceText = "")
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.NotEmpty (list);
+		Assert.True (list.Count == resultLines.Count ());
+		if (stringEmpty) {
+			Assert.Equal (string.Empty, list [0]);
+		} else {
+			Assert.NotEqual (string.Empty, list [0]);
+		}
+		Assert.Equal (resultLines, list);
+
+		if (maxWidth > 0) {
+			// remove whitespace chars
+			if (maxWidth < 5) {
+				expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0);
 			} else {
-				Assert.NotEqual (string.Empty, list [0]);
+				expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth - text.Sum (r => r == ' ' ? 1 : 0));
 			}
-			Assert.Equal (resultLines, list);
-
-			if (maxWidth > 0) {
-				// remove whitespace chars
-				if (maxWidth < 5) {
-					expectedClippedWidth = text.GetRuneCount () - text.Sum (r => r == ' ' ? 1 : 0);
-				} else {
-					expectedClippedWidth = Math.Min (text.GetRuneCount (), maxWidth - text.Sum (r => r == ' ' ? 1 : 0));
-				}
-				list = TextFormatter.Format (text, maxWidth, TextAlignment.Left, wrap, preserveTrailingSpaces: false);
-				if (maxWidth == 1) {
-					Assert.Equal (expectedClippedWidth, list.Count);
-					Assert.Equal (noSpaceText, string.Concat (list.ToArray ()));
-				}
-				if (maxWidth > 1 && maxWidth < 10) {
-					Assert.Equal (StringExtensions.ToString (text.ToRunes () [0..expectedClippedWidth]), list [0]);
-				}
+			list = TextFormatter.Format (text, maxWidth, TextAlignment.Left, wrap);
+			if (maxWidth == 1) {
+				Assert.Equal (expectedClippedWidth, list.Count);
+				Assert.Equal (noSpaceText,          string.Concat (list.ToArray ()));
 			}
-		}
-
-		[Theory]
-		// Unicode
-		// Even # of chars
-		//       0123456789
-		[InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвÐ", "Ñ" })]
-		// no clip
-		[InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })]
-		[InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new string [] { "\u2660пÑРвРÑ" })]
-		// Unicode
-		// Odd # of chars
-		//            0123456789
-		[InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвÐ", "Ñ" })]
-		// no clip
-		[InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })]
-		[InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new string [] { "\u2660 ÑРвРÑ" })]
-		public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.Equal (list.Count, resultLines.Count ());
-			Assert.Equal (resultLines, list);
-		}
-
-		[Theory]
-		// Unicode
-		[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
-		// no clip
-		[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9, 0, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
-		[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new string [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
-		public void Reformat_Unicode_Wrap_Spaces_NewLines (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable<string> resultLines)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.Equal (list.Count, resultLines.Count ());
-			Assert.Equal (resultLines, list);
-		}
-
-		[Theory]
-		[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false, new string [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, " Asentencehaswords.  This isthesecondLine- 2.")]
-		[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, true, new string [] { " A ", "sent", "ence", " ", "has ", "word", "s. ", " ", "This", " is ", "the ", "seco", "nd ", "Line", " - ", "2. " }, " A sentence has words.  This is the second Line - 2. ")]
-		public void Format_WordWrap_PreserveTrailingSpaces (string text, int maxWidth, int widthOffset, TextAlignment textAlignment, bool wrap, bool preserveTrailingSpaces, IEnumerable<string> resultLines, string expectedWrappedText)
-		{
-			Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
-			var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
-			Assert.Equal (list.Count, resultLines.Count ());
-			Assert.Equal (resultLines, list);
-			string wrappedText = string.Empty;
-			foreach (var txt in list) wrappedText += txt;
-			Assert.Equal (expectedWrappedText, wrappedText);
-		}
-
-		[Fact]
-		public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True ()
-		{
-			var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, false, true));
-			Assert.Null (exception);
-		}
-
-		[Theory]
-		[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
-		public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (string text, int runeCount, int maxWidth, string justifiedText)
-		{
-			Assert.Equal (runeCount, text.GetRuneCount ());
-
-			var fmtText = string.Empty;
-			for (int i = text.GetRuneCount (); i < maxWidth; i++) {
-				fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0];
-				Assert.Equal (i, fmtText.GetRuneCount ());
-				var c = fmtText [^1];
-				Assert.True (text.EndsWith (c));
+			if (maxWidth > 1 && maxWidth < 10) {
+				Assert.Equal (StringExtensions.ToString (text.ToRunes () [..expectedClippedWidth]), list [0]);
 			}
-			Assert.Equal (justifiedText, fmtText);
 		}
+	}
 
-		[Theory]
-		[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
-		public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (string text, int runeCount, int maxWidth, string justifiedText)
-		{
-			Assert.Equal (runeCount, text.GetRuneCount ());
-
-			var fmtText = string.Empty;
-			for (int i = text.GetRuneCount (); i < maxWidth; i++) {
-				fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0];
-				Assert.Equal (i, fmtText.GetRuneCount ());
-				var c = fmtText [^1];
-				Assert.True (text.EndsWith (c));
-			}
-			Assert.Equal (justifiedText, fmtText);
-		}
+	[Theory]
+	// Unicode
+	// Even # of chars
+	//       0123456789
+	[InlineData ("\u2660пÑРвРÑ", 10, -1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвÐ", "Ñ" })]
+	// no clip
+	[InlineData ("\u2660пÑРвРÑ", 11, 0, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })]
+	[InlineData ("\u2660пÑРвРÑ", 12, 1, TextAlignment.Left, true, false, new [] { "\u2660пÑРвРÑ" })]
+	// Unicode
+	// Odd # of chars
+	//            0123456789
+	[InlineData ("\u2660 ÑРвРÑ", 9, -1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвÐ", "Ñ" })]
+	// no clip
+	[InlineData ("\u2660 ÑРвРÑ", 10, 0, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })]
+	[InlineData ("\u2660 ÑРвРÑ", 11, 1, TextAlignment.Left, true, false, new [] { "\u2660 ÑРвРÑ" })]
+	public void Reformat_Unicode_Wrap_Spaces_No_NewLines (string text,
+							      int maxWidth,
+							      int widthOffset,
+							      TextAlignment textAlignment,
+							      bool wrap,
+							      bool preserveTrailingSpaces,
+							      IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.Equal (list.Count,  resultLines.Count ());
+		Assert.Equal (resultLines, list);
+	}
 
-		[Theory]
-		[InlineData ("fff", 6, "fff   ")]
-		[InlineData ("Hello World", 16, "Hello World     ")]
-		public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText)
-		{
-			// word is short but we want it to fill # so it should be padded
-			Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
-		}
+	[Theory]
+	// Unicode
+	[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 8, -1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
+	// no clip
+	[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 9,  0, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
+	[InlineData ("\u2460\u2461\u2462\n\u2460\u2461\u2462\u2463\u2464", 10, 1, TextAlignment.Left, true, false, new [] { "\u2460\u2461\u2462", "\u2460\u2461\u2462\u2463\u2464" })]
+	public void Reformat_Unicode_Wrap_Spaces_NewLines (string text,
+							   int maxWidth,
+							   int widthOffset,
+							   TextAlignment textAlignment,
+							   bool wrap,
+							   bool preserveTrailingSpaces,
+							   IEnumerable<string> resultLines)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.Equal (list.Count,  resultLines.Count ());
+		Assert.Equal (resultLines, list);
+	}
 
-		[Theory]
-		[InlineData ("123456789", 3, "123")]
-		[InlineData ("Hello World", 8, "Hello Wo")]
-		public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText)
-		{
-			// word is long but we want it to fill # space only
-			Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
-		}
+	[Theory]
+	[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, false,
+		new [] { " A", "sent", "ence", "has", "word", "s. ", " Thi", "s is", "the", "seco", "nd", "Line", "- 2." }, " Asentencehaswords.  This isthesecondLine- 2.")]
+	[InlineData (" A sentence has words. \n This is the second Line - 2. ", 4, -50, TextAlignment.Left, true, true,
+		new [] { " A ", "sent", "ence", " ", "has ", "word", "s. ", " ", "This", " is ", "the ", "seco", "nd ", "Line", " - ", "2. " },
+		" A sentence has words.  This is the second Line - 2. ")]
+	public void Format_WordWrap_PreserveTrailingSpaces (string text,
+							    int maxWidth,
+							    int widthOffset,
+							    TextAlignment textAlignment,
+							    bool wrap,
+							    bool preserveTrailingSpaces,
+							    IEnumerable<string> resultLines,
+							    string expectedWrappedText)
+	{
+		Assert.Equal (maxWidth, text.GetRuneCount () + widthOffset);
+		var list = TextFormatter.Format (text, maxWidth, textAlignment, wrap, preserveTrailingSpaces);
+		Assert.Equal (list.Count,  resultLines.Count ());
+		Assert.Equal (resultLines, list);
+		var wrappedText = string.Empty;
+		foreach (var txt in list) {
+			wrappedText += txt;
+		}
+		Assert.Equal (expectedWrappedText, wrappedText);
+	}
 
-		[Fact]
-		public void Internal_Tests ()
-		{
-			var tf = new TextFormatter ();
-			Assert.Equal (KeyCode.Null, tf.HotKey);
-			tf.HotKey = KeyCode.CtrlMask | KeyCode.Q;
-			Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey);
-		}
+	[Fact]
+	public void Format_Dont_Throw_ArgumentException_With_WordWrap_As_False_And_Keep_End_Spaces_As_True ()
+	{
+		var exception = Record.Exception (() => TextFormatter.Format ("Some text", 4, TextAlignment.Left, false, true));
+		Assert.Null (exception);
+	}
 
-		[Theory]
-		[InlineData ("Hello World", 11)]
-		[InlineData ("こんにちは世界", 14)]
-		public void GetColumns_Simple_And_Wide_Runes (string text, int width)
-		{
-			Assert.Equal (width, text.GetColumns ());
-		}
+	[Theory]
+	[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
+	public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Horizontal (string text, int runeCount, int maxWidth, string justifiedText)
+	{
+		Assert.Equal (runeCount, text.GetRuneCount ());
 
-		[Theory]
-		[InlineData ("Hello World", 11, 6, 1, 1)]
-		[InlineData ("こんにちは 世界", 15, 6, 1, 2)]
-		public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth)
-		{
-			Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text));
-			Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length));
-		}
-
-		[Theory]
-		[InlineData (new string [] { "Hello", "World" }, 2, 1, 1, 1)]
-		[InlineData (new string [] { "こんにちは", "世界" }, 4, 1, 1, 2)]
-		public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable<string> text, int width, int index, int length, int indexWidth)
-		{
-			Assert.Equal (width, TextFormatter.GetSumMaxCharWidth (text.ToList ()));
-			Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length));
+		var fmtText = string.Empty;
+		for (var i = text.GetRuneCount (); i < maxWidth; i++) {
+			fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true) [0];
+			Assert.Equal (i, fmtText.GetRuneCount ());
+			var c = fmtText [^1];
+			Assert.True (text.EndsWith (c));
 		}
+		Assert.Equal (justifiedText, fmtText);
+	}
 
-		[Theory]
-		[InlineData ("test", 3, 3)]
-		[InlineData ("test", 4, 4)]
-		[InlineData ("test", 10, 4)]
-		public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength)
-		{
-			var runes = text.ToRuneList ();
+	[Theory]
+	[InlineData ("Hello world, how are you today? Pretty neat!", 44, 80, "Hello      world,      how      are      you      today?      Pretty      neat!")]
+	public void Format_Justified_Always_Returns_Text_Width_Equal_To_Passed_Width_Vertical (string text, int runeCount, int maxWidth, string justifiedText)
+	{
+		Assert.Equal (runeCount, text.GetRuneCount ());
 
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
+		var fmtText = string.Empty;
+		for (var i = text.GetRuneCount (); i < maxWidth; i++) {
+			fmtText = TextFormatter.Format (text, i, TextAlignment.Justified, false, true, 0, TextDirection.TopBottom_LeftRight) [0];
+			Assert.Equal (i, fmtText.GetRuneCount ());
+			var c = fmtText [^1];
+			Assert.True (text.EndsWith (c));
 		}
+		Assert.Equal (justifiedText, fmtText);
+	}
 
-		[Theory]
-		[InlineData ("test", 3, 3)]
-		[InlineData ("test", 4, 4)]
-		[InlineData ("test", 10, 4)]
-		[InlineData ("test", 1, 1)]
-		[InlineData ("test", 0, 0)]
-		[InlineData ("test", -1, 0)]
-		[InlineData (null, -1, 0)]
-		[InlineData ("", -1, 0)]
-		public void GetLengthThatFits_String (string text, int columns, int expectedLength)
-		{
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
-		}
+	[Theory]
+	[InlineData ("fff",         6,  "fff   ")]
+	[InlineData ("Hello World", 16, "Hello World     ")]
+	public void TestClipOrPad_ShortWord (string text, int fillPad, string expectedText) =>
+		// word is short but we want it to fill # so it should be padded
+		Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
+
+	[Theory]
+	[InlineData ("123456789",   3, "123")]
+	[InlineData ("Hello World", 8, "Hello Wo")]
+	public void TestClipOrPad_LongWord (string text, int fillPad, string expectedText) =>
+		// word is long but we want it to fill # space only
+		Assert.Equal (expectedText, TextFormatter.ClipOrPad (text, fillPad));
+
+	[Fact]
+	public void Internal_Tests ()
+	{
+		var tf = new TextFormatter ();
+		Assert.Equal (KeyCode.Null, tf.HotKey);
+		tf.HotKey = KeyCode.CtrlMask | KeyCode.Q;
+		Assert.Equal (KeyCode.CtrlMask | KeyCode.Q, tf.HotKey);
+	}
 
-		[Theory]
-		[InlineData ("Hello World", 6, 6)]
-		[InlineData ("こんにちは 世界", 6, 3)]
-		public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
-		{
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
-		}
+	[Theory]
+	[InlineData ("Hello World", 11)]
+	[InlineData ("こんにちは世界",     14)]
+	public void GetColumns_Simple_And_Wide_Runes (string text, int width) => Assert.Equal (width, text.GetColumns ());
+
+	[Theory]
+	[InlineData ("Hello World", 11, 6, 1, 1)]
+	[InlineData ("こんにちは 世界",    15, 6, 1, 2)]
+	public void GetSumMaxCharWidth_Simple_And_Wide_Runes (string text, int width, int index, int length, int indexWidth)
+	{
+		Assert.Equal (width,      TextFormatter.GetSumMaxCharWidth (text));
+		Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text, index, length));
+	}
 
-		[Theory]
-		[InlineData ("Hello World", 6, 6)]
-		[InlineData ("こんにちは 世界", 6, 3)]
-		[MemberData (nameof (CMGlyphs))]
-		public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
-		{
-			var runes = text.ToRuneList ();
-			Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
-		}
+	[Theory]
+	[InlineData (new [] { "Hello", "World" }, 2, 1, 1, 1)]
+	[InlineData (new [] { "こんにちは", "世界" },    4, 1, 1, 2)]
+	public void GetSumMaxCharWidth_List_Simple_And_Wide_Runes (IEnumerable<string> text, int width, int index, int length, int indexWidth)
+	{
+		Assert.Equal (width,      TextFormatter.GetSumMaxCharWidth (text.ToList ()));
+		Assert.Equal (indexWidth, TextFormatter.GetSumMaxCharWidth (text.ToList (), index, length));
+	}
 
-		public static IEnumerable<object []> CMGlyphs =>
-			new List<object []>
-			{
-			    new object[] { $"{CM.Glyphs.LeftBracket} Say Hello 你 {CM.Glyphs.RightBracket}", 16, 15 }
-			};
-
-		[Theory]
-		[InlineData ("Truncate", 3, "Tru")]
-		[InlineData ("デモエムポンズ", 3, "デ")]
-		public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected)
-		{
-			var list = TextFormatter.Format (text, width, false, false);
-			Assert.Equal (expected, list [^1]);
-		}
+	[Theory]
+	[InlineData ("test", 3,  3)]
+	[InlineData ("test", 4,  4)]
+	[InlineData ("test", 10, 4)]
+	public void GetLengthThatFits_Runelist (string text, int columns, int expectedLength)
+	{
+		var runes = text.ToRuneList ();
 
-		[Theory]
-		[MemberData (nameof (FormatEnvironmentNewLine))]
-		public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (string text, int width, IEnumerable<string> expected)
-		{
-			var preserveTrailingSpaces = false;
-			var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
-			Assert.Equal (expected, formated);
-
-			preserveTrailingSpaces = true;
-			formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
-			Assert.Equal (expected, formated);
-		}
+		Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
+	}
 
-		public static IEnumerable<object []> FormatEnvironmentNewLine =>
-			new List<object []>
-			{
-				new object[] { $"Line1{Environment.NewLine}Line2{Environment.NewLine}Line3{Environment.NewLine}", 60, new string [] { "Line1", "Line2", "Line3" } }
-			};
-
-		[Theory]
-		[MemberData (nameof (SplitEnvironmentNewLine))]
-		public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable<string> expected)
-		{
-			var splited = TextFormatter.SplitNewLine (text);
-			Assert.Equal (expected, splited);
-		}
+	[Theory]
+	[InlineData ("test", 3,  3)]
+	[InlineData ("test", 4,  4)]
+	[InlineData ("test", 10, 4)]
+	[InlineData ("test", 1,  1)]
+	[InlineData ("test", 0,  0)]
+	[InlineData ("test", -1, 0)]
+	[InlineData (null,   -1, 0)]
+	[InlineData ("",     -1, 0)]
+	public void GetLengthThatFits_String (string text, int columns, int expectedLength) => Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
+
+	[Theory]
+	[InlineData ("Hello World", 6, 6)]
+	[InlineData ("こんにちは 世界",    6, 3)]
+	public void GetLengthThatFits_Simple_And_Wide_Runes (string text, int columns, int expectedLength) => Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (text, columns));
+
+	[Theory]
+	[InlineData ("Hello World", 6, 6)]
+	[InlineData ("こんにちは 世界",    6, 3)]
+	[MemberData (nameof (CMGlyphs))]
+	public void GetLengthThatFits_List_Simple_And_Wide_Runes (string text, int columns, int expectedLength)
+	{
+		var runes = text.ToRuneList ();
+		Assert.Equal (expectedLength, TextFormatter.GetLengthThatFits (runes, columns));
+	}
 
-		public static IEnumerable<object []> SplitEnvironmentNewLine =>
-		new List<object []>
-		{
-			new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" } },
-			new object[] { $"First Line 界{Environment.NewLine}Second Line 界{Environment.NewLine}Third Line 界{Environment.NewLine}", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" } }
-		};
+	[Theory]
+	[InlineData ("Truncate", 3, "Tru")]
+	[InlineData ("デモエムポンズ",  3, "デ")]
+	public void Format_Truncate_Simple_And_Wide_Runes (string text, int width, string expected)
+	{
+		var list = TextFormatter.Format (text, width, false, false);
+		Assert.Equal (expected, list [^1]);
+	}
 
-		[Theory]
-		[InlineData ($"First Line 界\nSecond Line 界\nThird Line 界", new string [] { "First Line 界", "Second Line 界", "Third Line 界" })]
-		public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable<string> expected)
-		{
-			var splited = TextFormatter.SplitNewLine (text);
-			Assert.Equal (expected, splited);
-		}
+	[Theory]
+	[MemberData (nameof (FormatEnvironmentNewLine))]
+	public void Format_With_PreserveTrailingSpaces_And_Without_PreserveTrailingSpaces (string text, int width, IEnumerable<string> expected)
+	{
+		var preserveTrailingSpaces = false;
+		var formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
+		Assert.Equal (expected, formated);
+
+		preserveTrailingSpaces = true;
+		formated = TextFormatter.Format (text, width, false, true, preserveTrailingSpaces);
+		Assert.Equal (expected, formated);
+	}
 
-		[Theory]
-		[InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", new string [] { "First Line 界", "Second Line 界", "Third Line 界", "" })]
-		public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable<string> expected)
-		{
-			var splited = TextFormatter.SplitNewLine (text);
-			Assert.Equal (expected, splited);
-		}
+	[Theory]
+	[MemberData (nameof (SplitEnvironmentNewLine))]
+	public void SplitNewLine_Ending__With_Or_Without_NewLine_Probably_CRLF (string text, IEnumerable<string> expected)
+	{
+		var splited = TextFormatter.SplitNewLine (text);
+		Assert.Equal (expected, splited);
+	}
 
-		[Theory]
-		[InlineData ("Single Line 界", 14)]
-		[InlineData ($"First Line 界\nSecond Line 界\nThird Line 界\n", 14)]
-		public void MaxWidthLine_With_And_Without_Newlines (string text, int expected)
-		{
-			Assert.Equal (expected, TextFormatter.MaxWidthLine (text));
-		}
+	[Theory]
+	[InlineData ("First Line 界\nSecond Line 界\nThird Line 界", new [] { "First Line 界", "Second Line 界", "Third Line 界" })]
+	public void SplitNewLine_Ending_Without_NewLine_Only_LF (string text, IEnumerable<string> expected)
+	{
+		var splited = TextFormatter.SplitNewLine (text);
+		Assert.Equal (expected, splited);
+	}
 
-		[Theory]
-		[InlineData ("New Test 你", 10, 10, 20320, 20320, 9, "你")]
-		[InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")]
-		public void String_Array_Is_Not_Always_Equal_ToRunes_Array (string text, int runesLength, int stringLength, int runeValue, int stringValue, int index, string expected)
-		{
-			var usToRunes = text.ToRunes ();
-			Assert.Equal (runesLength, usToRunes.Length);
-			Assert.Equal (stringLength, text.Length);
-			Assert.Equal (runeValue, usToRunes [index].Value);
-			Assert.Equal (stringValue, text [index]);
-			Assert.Equal (expected, usToRunes [index].ToString ());
-			if (char.IsHighSurrogate (text [index])) {
-				// Rune array length isn't equal to string array
-				Assert.Equal (expected, new string (new char [] { text [index], text [index + 1] }));
-			} else {
-				// Rune array length is equal to string array
-				Assert.Equal (expected, text [index].ToString ());
-			}
-		}
+	[Theory]
+	[InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", new [] { "First Line 界", "Second Line 界", "Third Line 界", "" })]
+	public void SplitNewLine_Ending_With_NewLine_Only_LF (string text, IEnumerable<string> expected)
+	{
+		var splited = TextFormatter.SplitNewLine (text);
+		Assert.Equal (expected, splited);
+	}
 
-		[Fact]
-		public void GetLengthThatFits_With_Combining_Runes ()
-		{
-			var text = "Les Mise\u0328\u0301rables";
-			Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
+	[Theory]
+	[InlineData ("Single Line 界",                               14)]
+	[InlineData ("First Line 界\nSecond Line 界\nThird Line 界\n", 14)]
+	public void MaxWidthLine_With_And_Without_Newlines (string text, int expected) => Assert.Equal (expected, TextFormatter.MaxWidthLine (text));
+
+	[Theory]
+	[InlineData ("New Test 你",          10, 10, 20320,  20320, 9, "你")]
+	[InlineData ("New Test \U0001d539", 10, 11, 120121, 55349, 9, "𝔹")]
+	public void String_Array_Is_Not_Always_Equal_ToRunes_Array (string text, int runesLength, int stringLength, int runeValue, int stringValue, int index, string expected)
+	{
+		var usToRunes = text.ToRunes ();
+		Assert.Equal (runesLength,  usToRunes.Length);
+		Assert.Equal (stringLength, text.Length);
+		Assert.Equal (runeValue,    usToRunes [index].Value);
+		Assert.Equal (stringValue,  text [index]);
+		Assert.Equal (expected,     usToRunes [index].ToString ());
+		if (char.IsHighSurrogate (text [index])) {
+			// Rune array length isn't equal to string array
+			Assert.Equal (expected, new string (new [] { text [index], text [index + 1] }));
+		} else {
+			// Rune array length is equal to string array
+			Assert.Equal (expected, text [index].ToString ());
 		}
+	}
 
-		[Fact]
-		public void GetMaxColsForWidth_With_Combining_Runes ()
-		{
-			var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
-			Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
-		}
+	[Fact]
+	public void GetLengthThatFits_With_Combining_Runes ()
+	{
+		var text = "Les Mise\u0328\u0301rables";
+		Assert.Equal (16, TextFormatter.GetLengthThatFits (text, 14));
+	}
 
-		[Fact]
-		public void GetSumMaxCharWidth_With_Combining_Runes ()
-		{
-			var text = "Les Mise\u0328\u0301rables";
-			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
-		}
+	[Fact]
+	public void GetMaxColsForWidth_With_Combining_Runes ()
+	{
+		var text = new List<string> { "Les Mis", "e\u0328\u0301", "rables" };
+		Assert.Equal (1, TextFormatter.GetMaxColsForWidth (text, 1));
+	}
 
-		[Fact]
-		public void GetSumMaxCharWidth_List_With_Combining_Runes ()
-		{
-			var text = new List<string> () { "Les Mis", "e\u0328\u0301", "rables" };
-			Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
-		}
+	[Fact]
+	public void GetSumMaxCharWidth_With_Combining_Runes ()
+	{
+		var text = "Les Mise\u0328\u0301rables";
+		Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+	}
 
-		[Theory]
-		[InlineData (14, 1, TextDirection.LeftRight_TopBottom)]
-		[InlineData (1, 14, TextDirection.TopBottom_LeftRight)]
-		public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection)
-		{
-			var text = "Les Mise\u0328\u0301rables";
-			Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection));
-		}
+	[Fact]
+	public void GetSumMaxCharWidth_List_With_Combining_Runes ()
+	{
+		var text = new List<string> { "Les Mis", "e\u0328\u0301", "rables" };
+		Assert.Equal (1, TextFormatter.GetSumMaxCharWidth (text, 1, 1));
+	}
+
+	[Theory]
+	[InlineData (14, 1,  TextDirection.LeftRight_TopBottom)]
+	[InlineData (1,  14, TextDirection.TopBottom_LeftRight)]
+	public void CalcRect_With_Combining_Runes (int width, int height, TextDirection textDirection)
+	{
+		var text = "Les Mise\u0328\u0301rables";
+		Assert.Equal (new Rect (0, 0, width, height), TextFormatter.CalcRect (0, 0, text, textDirection));
+	}
 
-		[Theory]
-		[InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")]
-		[InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
-		[InlineData (4, 4, TextDirection.TopBottom_LeftRight, @"
+	[Theory]
+	[InlineData (14, 1,  TextDirection.LeftRight_TopBottom, "Les Misęrables")]
+	[InlineData (1,  14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
+	[InlineData (4,  4,  TextDirection.TopBottom_LeftRight, @"
 LMre
 eias
 ssb 
  ęl ")]
-		public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
+	public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
 
-			var text = "Les Mise\u0328\u0301rables";
+		var text = "Les Mise\u0328\u0301rables";
 
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.Text = text;
-
-			Assert.True (tf.WordWrap);
-			if (textDirection == TextDirection.LeftRight_TopBottom) {
-				Assert.Equal (new Size (width, height), tf.Size);
-			} else {
-				Assert.Equal (new Size (1, text.GetColumns ()), tf.Size);
-				tf.Size = new Size (width, height);
-			}
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
-
-			driver.End ();
-		}
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.Text = text;
 
-		[Theory]
-		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
-		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
-		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
-		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
-		public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
-
-			var text = "This is a \tTab";
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.TabWidth = tabWidth;
-			tf.Text = text;
-
-			Assert.True (tf.WordWrap);
-			Assert.False (tf.PreserveTrailingSpaces);
+		Assert.True (tf.WordWrap);
+		if (textDirection == TextDirection.LeftRight_TopBottom) {
 			Assert.Equal (new Size (width, height), tf.Size);
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
-
-			driver.End ();
+		} else {
+			Assert.Equal (new Size (1, text.GetColumns ()), tf.Size);
+			tf.Size = new Size (width, height);
 		}
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
 
-		[Theory]
-		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
-		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
-		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
-		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
-		public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
-
-			var text = "This is a \tTab";
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.TabWidth = tabWidth;
-			tf.PreserveTrailingSpaces = true;
-			tf.Text = text;
-
-			Assert.True (tf.WordWrap);
-			Assert.Equal (new Size (width, height), tf.Size);
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+		driver.End ();
+	}
 
-			driver.End ();
-		}
+	[Theory]
+	[InlineData (17, 1,  TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+	[InlineData (1,  17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+	[InlineData (13, 1,  TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+	[InlineData (1,  13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+	public void TabWith_PreserveTrailingSpaces_False (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
+
+		var text = "This is a \tTab";
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.TabWidth = tabWidth;
+		tf.Text = text;
+
+		Assert.True (tf.WordWrap);
+		Assert.False (tf.PreserveTrailingSpaces);
+		Assert.Equal (new Size (width, height), tf.Size);
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+		driver.End ();
+	}
 
-		[Theory]
-		[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
-		[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
-		[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
-		[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
-		public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
-		{
-			var driver = new FakeDriver ();
-			driver.Init ();
-
-			var text = "This is a \tTab";
-			var tf = new TextFormatter ();
-			tf.Direction = textDirection;
-			tf.TabWidth = tabWidth;
-			tf.WordWrap = true;
-			tf.Text = text;
-
-			Assert.False (tf.PreserveTrailingSpaces);
-			Assert.Equal (new Size (width, height), tf.Size);
-			tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+	[Theory]
+	[InlineData (17, 1,  TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+	[InlineData (1,  17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+	[InlineData (13, 1,  TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+	[InlineData (1,  13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+	public void TabWith_PreserveTrailingSpaces_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
+
+		var text = "This is a \tTab";
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.TabWidth = tabWidth;
+		tf.PreserveTrailingSpaces = true;
+		tf.Text = text;
+
+		Assert.True (tf.WordWrap);
+		Assert.Equal (new Size (width, height), tf.Size);
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+		driver.End ();
+	}
 
-			driver.End ();
-		}
+	[Theory]
+	[InlineData (17, 1,  TextDirection.LeftRight_TopBottom, 4, "This is a     Tab")]
+	[InlineData (1,  17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
+	[InlineData (13, 1,  TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
+	[InlineData (1,  13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
+	public void TabWith_WordWrap_True (int width, int height, TextDirection textDirection, int tabWidth, string expected)
+	{
+		var driver = new FakeDriver ();
+		driver.Init ();
+
+		var text = "This is a \tTab";
+		var tf = new TextFormatter ();
+		tf.Direction = textDirection;
+		tf.TabWidth = tabWidth;
+		tf.WordWrap = true;
+		tf.Text = text;
+
+		Assert.False (tf.PreserveTrailingSpaces);
+		Assert.Equal (new Size (width, height), tf.Size);
+		tf.Draw (new Rect (0, 0, width, height), new Attribute (ColorName.White, ColorName.Black), new Attribute (ColorName.Blue, ColorName.Black), default, true, driver);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, output, driver);
+
+		driver.End ();
 	}
 }

+ 3 - 3
UnitTests/UICatalog/ScenarioTests.cs

@@ -335,7 +335,7 @@ namespace UICatalog.Tests {
 
 			_computedCheckBox.Toggled += (s, e) => {
 				if (_curView != null) {
-					_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed;
+					//_curView.LayoutStyle = e.OldValue == true ? LayoutStyle.Absolute : LayoutStyle.Computed;
 					_hostPane.LayoutSubviews ();
 				}
 			};
@@ -419,7 +419,7 @@ namespace UICatalog.Tests {
 				var layout = view.LayoutStyle;
 
 				try {
-					view.LayoutStyle = LayoutStyle.Absolute;
+					//view.LayoutStyle = LayoutStyle.Absolute;
 
 					switch (_xRadioGroup.SelectedItem) {
 					case 0:
@@ -477,7 +477,7 @@ namespace UICatalog.Tests {
 				} catch (Exception e) {
 					MessageBox.ErrorQuery ("Exception", e.Message, "Ok");
 				} finally {
-					view.LayoutStyle = layout;
+					//view.LayoutStyle = layout;
 				}
 				UpdateTitle (view);
 			}

+ 18 - 9
UnitTests/View/DrawTests.cs

@@ -4,14 +4,16 @@ using Xunit;
 using Xunit.Abstractions;
 using Microsoft.VisualStudio.TestPlatform.Utilities;
 
-namespace Terminal.Gui.ViewsTests; 
+namespace Terminal.Gui.ViewsTests;
 
 public class DrawTests {
 	readonly ITestOutputHelper _output;
 
 	public DrawTests (ITestOutputHelper output) => _output = output;
 
-	[Fact] [AutoInitShutdown]
+	// TODO: Refactor this test to not depend on TextView etc... Make it as primitive as possible
+	[Fact]
+	[AutoInitShutdown]
 	public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
 	{
 		var tv = new TextView () {
@@ -29,7 +31,8 @@ public class DrawTests {
 		var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
 		win.Add (tv);
 		Application.Top.Add (win);
-		var lbl = new Label ("ワイドルーン。");
+		// Don't use Label. It sets AutoSize = true which is not what we're testing here.
+		var lbl = new View ("ワイドルーン。");
 		// Don't have unit tests use things that aren't absolutely critical for the test, like Dialog
 		var dg = new Window () { X = 2, Y = 2, Width = 14, Height = 3 };
 		dg.Add (lbl);
@@ -54,7 +57,8 @@ public class DrawTests {
 	}
 
 	// TODO: The tests below that use Label should use View instead.
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two ()
 	{
 		string us = "\U0001d539";
@@ -102,7 +106,8 @@ public class DrawTests {
 0000000000", Application.Driver, expectedColors);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
 	{
 		string us = "\U0000f900";
@@ -150,7 +155,8 @@ public class DrawTests {
 0000000000", Application.Driver, expectedColors);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Colors_On_TextAlignment_Right_And_Bottom ()
 	{
 		var labelRight = new Label ("Test") {
@@ -191,7 +197,8 @@ t     ", _output);
 0", Application.Driver, new Attribute [] { Colors.Base.Normal });
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Draw_Negative_Bounds_Horizontal_Without_New_Lines ()
 	{
 		// BUGBUG: This previously assumed the default height of a View was 1. 
@@ -235,7 +242,8 @@ t     ", _output);
 		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Draw_Negative_Bounds_Horizontal_With_New_Lines ()
 	{
 		var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "s\nu\nb\nV\ni\ne\nw" };
@@ -304,7 +312,8 @@ t     ", _output);
 		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Draw_Negative_Bounds_Vertical ()
 	{
 		var subView = new View () { Id = "subView", X = 1, Width = 1, Height = 7, Text = "subView", TextDirection = TextDirection.TopBottom_LeftRight };

+ 217 - 170
UnitTests/View/Layout/AbsoluteLayoutTests.cs

@@ -1,5 +1,6 @@
 using Xunit;
 using Xunit.Abstractions;
+
 //using GraphViewTests = Terminal.Gui.Views.GraphViewTests;
 
 // Alias Console to MockConsole so we don't accidentally use Console
@@ -9,193 +10,208 @@ namespace Terminal.Gui.ViewTests;
 public class AbsoluteLayoutTests {
 	readonly ITestOutputHelper _output;
 
-	public AbsoluteLayoutTests (ITestOutputHelper output) => this._output = output;
+	public AbsoluteLayoutTests (ITestOutputHelper output) => _output = output;
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void AbsoluteLayout_Constructor ()
 	{
-		var frame = new Rect (1, 2, 3, 4);
-		var v = new View (frame);
+		var v = new View ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+
+		var frame = Rect.Empty;
+		v = new View (frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (frame,                                      v.Frame);
+		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Equal (Pos.At (0),                                 v.X);
+		Assert.Equal (Pos.At (0),                                 v.Y);
+		Assert.Equal (Dim.Sized (0),                              v.Width);
+		Assert.Equal (Dim.Sized (0),                              v.Height);
+		v.Dispose ();
+
+		frame = new Rect (1, 2, 3, 4);
+		v = new View (frame);
 		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (frame, v.Frame);
+		Assert.Equal (frame,                                      v.Frame);
 		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (Pos.At (1),                                 v.X);
+		Assert.Equal (Pos.At (2),                                 v.Y);
+		Assert.Equal (Dim.Sized (3),                              v.Width);
+		Assert.Equal (Dim.Sized (4),                              v.Height);
 		v.Dispose ();
 
 		v = new View (frame, "v");
 		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (frame, v.Frame);
+		Assert.Equal (frame,                                      v.Frame);
 		Assert.Equal (new Rect (0, 0, frame.Width, frame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (Pos.At (1),                                 v.X);
+		Assert.Equal (Pos.At (2),                                 v.Y);
+		Assert.Equal (Dim.Sized (3),                              v.Width);
+		Assert.Equal (Dim.Sized (4),                              v.Height);
 		v.Dispose ();
 
 		v = new View (frame.X, frame.Y, "v");
 		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		// BUGBUG: v2 - I think the default size should be 0,0
+		// BUGBUG: v2 - I think the default size should be 0,0 not 1,1
 		Assert.Equal (new Rect (frame.X, frame.Y, 1, 1), v.Frame);
-		Assert.Equal (new Rect (0, 0, 1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (new Rect (0,       0,       1, 1), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Equal (Pos.At (1),                        v.X);
+		Assert.Equal (Pos.At (2),                        v.Y);
+		Assert.Equal (Dim.Sized (1),                     v.Width);
+		Assert.Equal (Dim.Sized (1),                     v.Height);
+		v.Dispose ();
+
+		v = new View ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (new Rect (0, 0, 0, 0), v.Frame);
+		Assert.Equal (new Rect (0, 0, 0, 0), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Equal (Pos.At (0),            v.X);
+		Assert.Equal (Pos.At (0),            v.Y);
+		Assert.Equal (Dim.Sized (0),         v.Width);
+		Assert.Equal (Dim.Sized (0),         v.Height);
 		v.Dispose ();
 
+		v = new View {
+			X = frame.X,
+			Y = frame.Y,
+			Width = frame.Width,
+			Height = frame.Height
+		};
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (new Rect (frame.X, frame.Y, 3, 4), v.Frame);
+		Assert.Equal (new Rect (0,       0,       3, 4), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
+		Assert.Equal (Pos.At (1),                        v.X);
+		Assert.Equal (Pos.At (2),                        v.Y);
+		Assert.Equal (Dim.Sized (3),                     v.Width);
+		Assert.Equal (Dim.Sized (4),                     v.Height);
+		v.Dispose ();
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void AbsoluteLayout_Change_Frame ()
 	{
-		var frame = new Rect (1, 2, 3, 4);
+		var frame = new Rect (1,    2, 3,  4);
 		var newFrame = new Rect (1, 2, 30, 40);
 
-		var v = new View (frame);
+		var v = new View ();
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		v.Dispose ();
+
+		v = new View (frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		
 		v.Frame = newFrame;
 		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (newFrame,                                         v.Frame);
 		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (Pos.At (1),                                       v.X);
+		Assert.Equal (Pos.At (2),                                       v.Y);
+		Assert.Equal (Dim.Sized (30),                                    v.Width);
+		Assert.Equal (Dim.Sized (40),                                    v.Height);
 		v.Dispose ();
 
 		v = new View (frame.X, frame.Y, "v");
 		v.Frame = newFrame;
-		Assert.Equal (newFrame, v.Frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newFrame,                                         v.Frame);
 		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (Pos.At (1),                                       v.X);
+		Assert.Equal (Pos.At (2),                                       v.Y);
+		Assert.Equal (Dim.Sized (30),                                   v.Width);
+		Assert.Equal (Dim.Sized (40),                                   v.Height);
 		v.Dispose ();
 
 		newFrame = new Rect (10, 20, 30, 40);
 		v = new View (frame);
 		v.Frame = newFrame;
-		Assert.Equal (newFrame, v.Frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newFrame,                                         v.Frame);
 		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (Pos.At (10),                                       v.X);
+		Assert.Equal (Pos.At (20),                                       v.Y);
+		Assert.Equal (Dim.Sized (30),                                   v.Width);
+		Assert.Equal (Dim.Sized (40),                                   v.Height);
 		v.Dispose ();
 
 		v = new View (frame.X, frame.Y, "v");
 		v.Frame = newFrame;
-		Assert.Equal (newFrame, v.Frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newFrame,                                         v.Frame);
 		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal (Pos.At (10),                                       v.X);
+		Assert.Equal (Pos.At (20),                                       v.Y);
+		Assert.Equal (Dim.Sized (30),                                   v.Width);
+		Assert.Equal (Dim.Sized (40),                                   v.Height);
 		v.Dispose ();
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void AbsoluteLayout_Change_Height_or_Width_Absolute ()
 	{
-		var frame = new Rect (1, 2, 3, 4);
+		var frame = new Rect (1,    2, 3,  4);
 		var newFrame = new Rect (1, 2, 30, 40);
 
 		var v = new View (frame);
 		v.Height = newFrame.Height;
 		v.Width = newFrame.Width;
 		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (newFrame,                                         v.Frame);
 		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Null (v.X);
-		Assert.Null (v.Y);
-		Assert.Equal ($"Absolute({newFrame.Height})", v.Height.ToString ());
-		Assert.Equal ($"Absolute({newFrame.Width})", v.Width.ToString ());
+		Assert.Equal (Pos.At (1),                                      v.X);
+		Assert.Equal (Pos.At (2),                                      v.Y);
+		Assert.Equal ($"Absolute({newFrame.Height})",                   v.Height.ToString ());
+		Assert.Equal ($"Absolute({newFrame.Width})",                    v.Width.ToString ());
 		v.Dispose ();
 	}
 
-	[Fact] [TestRespondersDisposed]
-	public void AbsoluteLayout_Change_Height_or_Width_NotAbsolute ()
+	[Fact]
+	[TestRespondersDisposed]
+	public void AbsoluteLayout_Change_Height_or_Width_MakesComputed ()
 	{
 		var v = new View (Rect.Empty);
 		v.Height = Dim.Fill ();
 		v.Width = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-		v.Dispose ();
-	}
-
-	[Fact] [TestRespondersDisposed]
-	public void AbsoluteLayout_Change_Height_or_Width_Null ()
-	{
-		var v = new View (Rect.Empty);
-		v.Height = null;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed); 
 		v.Dispose ();
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void AbsoluteLayout_Change_X_or_Y_Absolute ()
 	{
-		var frame = new Rect (1, 2, 3, 4);
+		var frame = new Rect (1,     2,  3, 4);
 		var newFrame = new Rect (10, 20, 3, 4);
 
 		var v = new View (frame);
 		v.X = newFrame.X;
 		v.Y = newFrame.Y;
 		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		Assert.Equal (newFrame, v.Frame);
+		Assert.Equal (newFrame,                                         v.Frame);
 		Assert.Equal (new Rect (0, 0, newFrame.Width, newFrame.Height), v.Bounds); // With Absolute Bounds *is* deterministic before Layout
-		Assert.Equal ($"Absolute({newFrame.X})", v.X.ToString ());
-		Assert.Equal ($"Absolute({newFrame.Y})", v.Y.ToString ());
-		Assert.Null (v.Height);
-		Assert.Null (v.Width);
+		Assert.Equal ($"Absolute({newFrame.X})",                        v.X.ToString ());
+		Assert.Equal ($"Absolute({newFrame.Y})",                        v.Y.ToString ());
+		Assert.Equal (Dim.Sized (3),                                   v.Width);
+		Assert.Equal (Dim.Sized (4),                                   v.Height);
 		v.Dispose ();
 	}
 
-	[Fact] [TestRespondersDisposed]
-	public void AbsoluteLayout_Change_X_or_Y_NotAbsolute ()
+	[Fact]
+	[TestRespondersDisposed]
+	public void AbsoluteLayout_Change_X_or_Y_MakesComputed ()
 	{
 		var v = new View (Rect.Empty);
 		v.X = Pos.Center ();
 		v.Y = Pos.Center ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-		v.Dispose ();
-	}
-
-	[Fact] [TestRespondersDisposed]
-	public void AbsoluteLayout_Change_X_or_Y_Null ()
-	{
-		var v = new View (Rect.Empty);
-		v.X = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.X = Pos.Center ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-		v.X = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.Y = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
-		v.Dispose ();
-
-		v = new View (Rect.Empty);
-		v.Y = Pos.Center ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-		v.Y = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 		v.Dispose ();
 	}
-
-	[Fact] [TestRespondersDisposed]
+	
+	[Fact]
+	[TestRespondersDisposed]
 	public void AbsoluteLayout_Change_X_Y_Height_Width_Absolute ()
 	{
 		var v = new View (Rect.Empty);
@@ -211,14 +227,7 @@ public class AbsoluteLayoutTests {
 		v.Y = Pos.Center ();
 		v.Width = Dim.Fill ();
 		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-		v.X = null;
-		v.Y = null;
-		v.Height = null;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 		v.Dispose ();
 
 		v = new View (Rect.Empty);
@@ -226,14 +235,10 @@ public class AbsoluteLayoutTests {
 		v.Y = Pos.Center ();
 		v.Width = Dim.Fill ();
 		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 
-		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
 		v.X = 1;
-		v.Y = null;
-		v.Height = null;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 		v.Dispose ();
 
 		v = new View (Rect.Empty);
@@ -241,14 +246,10 @@ public class AbsoluteLayoutTests {
 		v.Y = Pos.Center ();
 		v.Width = Dim.Fill ();
 		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-		v.X = null;
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+		
 		v.Y = 2;
-		v.Height = null;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 		v.Dispose ();
 
 		v = new View (Rect.Empty);
@@ -256,14 +257,10 @@ public class AbsoluteLayoutTests {
 		v.Y = Pos.Center ();
 		v.Width = Dim.Fill ();
 		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 
-		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-		v.X = null;
-		v.Y = null;
-		v.Height = 3;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Width = 3;
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed); 
 		v.Dispose ();
 
 		v = new View (Rect.Empty);
@@ -271,72 +268,122 @@ public class AbsoluteLayoutTests {
 		v.Y = Pos.Center ();
 		v.Width = Dim.Fill ();
 		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 
-		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-		v.X = null;
-		v.Y = null;
-		v.Height = null;
-		v.Width = 4;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		v.Height = 3;
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
 		v.Dispose ();
-	}
-
-	[Fact] [TestRespondersDisposed]
-	public void AbsoluteLayout_Change_X_Y_Height_Width_Null ()
-	{
-		var v = new View (Rect.Empty);
-		v.X = null;
-		v.Y = null;
-		v.Height = null;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
 
-		v.Dispose ();
 		v = new View (Rect.Empty);
 		v.X = Pos.Center ();
 		v.Y = Pos.Center ();
 		v.Width = Dim.Fill ();
 		v.Height = Dim.Fill ();
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // BUGBUG: v2 - Changing the Height or Width should change the LayoutStyle
-
-		// BUGBUG: v2 - If all of X, Y, Width, and Height are null or Absolute(n), isn't that the same as LayoutStyle.Absoulte?
-		v.X = null;
-		v.Y = null;
-		v.Height = null;
-		v.Width = null;
-		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); // We never automatically change to Absolute from Computed??
+		Assert.True (v.LayoutStyle == LayoutStyle.Computed);
+
+		v.X = 1;
+		v.Y = 2;
+		v.Height = 3;
+		v.Width = 4;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute); 
 		v.Dispose ();
 	}
-
-	[Fact] [TestRespondersDisposed]
-	public void AbsoluteLayout_Layout ()
+	
+	[Fact]
+	[TestRespondersDisposed]
+	public void AbsoluteLayout_LayoutSubviews ()
 	{
 		var superRect = new Rect (0, 0, 100, 100);
 		var super = new View (superRect, "super");
 		Assert.True (super.LayoutStyle == LayoutStyle.Absolute);
-		var v1 = new View () {
+		var v1 = new View {
 			X = 0,
 			Y = 0,
 			Width = 10,
 			Height = 10
 		};
-		// BUGBUG: v2 - This should be LayoutStyle.Absolute
-		Assert.True (v1.LayoutStyle == LayoutStyle.Computed);
+		Assert.True (v1.LayoutStyle == LayoutStyle.Absolute);
 
-		var v2 = new View () {
+		var v2 = new View {
 			X = 10,
 			Y = 10,
 			Width = 10,
 			Height = 10
 		};
-		// BUGBUG: v2 - This should be LayoutStyle.Absolute
-		Assert.True (v1.LayoutStyle == LayoutStyle.Computed);
+		Assert.True (v2.LayoutStyle == LayoutStyle.Absolute);
 
 		super.Add (v1, v2);
+		Assert.True (v1.LayoutStyle == LayoutStyle.Absolute);
+		Assert.True (v2.LayoutStyle == LayoutStyle.Absolute);
+
 		super.LayoutSubviews ();
-		Assert.Equal (new Rect (0, 0, 10, 10), v1.Frame);
+		Assert.Equal (new Rect (0,  0,  10, 10), v1.Frame);
 		Assert.Equal (new Rect (10, 10, 10, 10), v2.Frame);
 		super.Dispose ();
 	}
+
+	[Fact]
+	public void AbsoluteLayout_Setting_Bounds_Location_NotEmpty ()
+	{
+		// TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is
+		// TODO: correct behavior, but is silent. Perhaps an exception?
+		var frame = new Rect (1,     2, 3,  4);
+		var newBounds = new Rect (10, 20, 30, 40);
+		var view = new View (frame);
+		view.Bounds = newBounds;
+		Assert.Equal (new Rect (0, 0, 30, 40), view.Bounds);
+		Assert.Equal (new Rect (1, 2, 30, 40), view.Frame);
+	}
+
+	[Fact]
+	public void AbsoluteLayout_Setting_Bounds_Sets_Frame ()
+	{
+		var frame = new Rect (1, 2, 3, 4);
+		var newBounds = new Rect (0, 0, 30, 40);
+
+		var v = new View (frame);
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+
+		v.Bounds = newBounds;
+		Assert.True (v.LayoutStyle == LayoutStyle.Absolute);
+		Assert.Equal (newBounds,                                          v.Bounds);
+		Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame);
+		Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds); 
+		Assert.Equal (Pos.At (1),                                         v.X);
+		Assert.Equal (Pos.At (2),                                         v.Y);
+		Assert.Equal (Dim.Sized (30),                                     v.Width);
+		Assert.Equal (Dim.Sized (40),                                     v.Height);
+
+		newBounds = new Rect (0, 0, 3, 4);
+		v.Bounds  = newBounds;
+		Assert.Equal (newBounds,                                          v.Bounds);
+		Assert.Equal (new Rect (1, 2, newBounds.Width, newBounds.Height), v.Frame);
+		Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
+		Assert.Equal (Pos.At (1),                                        v.X);
+		Assert.Equal (Pos.At (2),                                        v.Y);
+		Assert.Equal (Dim.Sized (3),                                     v.Width);
+		Assert.Equal (Dim.Sized (4),                                     v.Height);
+
+		v.BorderStyle = LineStyle.Single;
+		// Bounds should shrink
+		Assert.Equal (new Rect (0, 0, 1,               2),                v.Bounds);
+		// Frame should not change
+		Assert.Equal (new Rect (1, 2, 3,               4),                v.Frame);
+		Assert.Equal (Pos.At (1),                                         v.X);
+		Assert.Equal (Pos.At (2),                                         v.Y);
+		Assert.Equal (Dim.Sized (3),                                      v.Width);
+		Assert.Equal (Dim.Sized (4),                                      v.Height);
+
+		// Now set bounds bigger as before
+		newBounds = new Rect (0, 0, 3, 4);
+		v.Bounds  = newBounds;
+		Assert.Equal (newBounds,                                          v.Bounds);
+		// Frame grows because there's now a border
+		Assert.Equal (new Rect (1, 2, 5, 6), v.Frame);
+		Assert.Equal (new Rect (0, 0, newBounds.Width, newBounds.Height), v.Bounds);
+		Assert.Equal (Pos.At (1),                                         v.X);
+		Assert.Equal (Pos.At (2),                                         v.Y);
+		Assert.Equal (Dim.Sized (5),                                      v.Width);
+		Assert.Equal (Dim.Sized (6),                                      v.Height);
+	}
 }

+ 0 - 838
UnitTests/View/Layout/AutoSizeTests.cs

@@ -1,838 +0,0 @@
-using System.Text;
-using System;
-using System.Collections.Generic;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Terminal.Gui.ViewTests {
-	public class AutoSizeTests {
-		readonly ITestOutputHelper output;
-
-		public AutoSizeTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_GetAutoSize_Horizontal ()
-		{
-			var text = "text";
-			var view = new View () {
-				Text = text,
-				AutoSize = true
-			};
-			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 (10, 4);
-
-			var size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 1), size);
-
-			view.Text = $"{text}\n{text}";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 2), size);
-
-			view.Text = $"{text}\n{text}\n{text}+";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length + 1, 3), size);
-
-			text = string.Empty;
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (0, 0), size);
-
-			text = "1";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (1, 1), size);
-
-			text = "界";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (2, 1), size);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_GetAutoSize_Vertical()
-		{
-			var text = "text";
-			var view = new View () {
-				Text = text,
-				TextDirection = TextDirection.TopBottom_LeftRight,
-				AutoSize = true
-			};
-			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 (10, 4);
-
-			var size = view.GetAutoSize ();
-			Assert.Equal (new Size (1, text.Length), size);
-
-			view.Text = $"{text}\n{text}";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (2, text.Length), size);
-
-			view.Text = $"{text}\n{text}\n{text}+";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (3, text.Length + 1), size);
-
-			text = string.Empty;
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (0, 0), size);
-
-			text = "1";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (1, 1), size);
-
-			text = "界";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (2, 1), size);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_GetAutoSize_Left()
-		{
-			var text = "This is some text.";
-			var view = new View () {
-				Text = text,
-				TextAlignment = TextAlignment.Left,
-				AutoSize = true
-			};
-			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 (10, 4);
-
-			var size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 1), size);
-
-			view.Text = $"{text}\n{text}";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 2), size);
-
-			view.Text = $"{text}\n{text}\n{text}+";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length + 1, 3), size);
-
-			text = string.Empty;
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (0, 0), size);
-
-			text = "1";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (1, 1), size);
-
-			text = "界";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (2, 1), size);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_GetAutoSize_Right ()
-		{
-			var text = "This is some text.";
-			var view = new View () {
-				Text = text,
-				TextAlignment = TextAlignment.Right,
-				AutoSize = true
-			};
-			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 (10, 4);
-
-			var size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 1), size);
-
-			view.Text = $"{text}\n{text}";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 2), size);
-
-			view.Text = $"{text}\n{text}\n{text}+";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length + 1, 3), size);
-
-			text = string.Empty;
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (0, 0), size);
-
-			text = "1";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (1, 1), size);
-
-			text = "界";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (2, 1), size);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_GetAutoSize_Centered ()
-		{
-			var text = "This is some text.";
-			var view = new View () {
-				Text = text,
-				TextAlignment = TextAlignment.Centered,
-				AutoSize = true
-			};
-			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 (10, 4);
-
-			var size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 1), size);
-
-			view.Text = $"{text}\n{text}";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length, 2), size);
-
-			view.Text = $"{text}\n{text}\n{text}+";
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (text.Length + 1, 3), size);
-
-			text = string.Empty;
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (0, 0), size);
-
-			text = "1";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (1, 1), size);
-
-			text = "界";
-			view.Text = text;
-			size = view.GetAutoSize ();
-			Assert.Equal (new Size (2, 1), size);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_False_View_IsEmpty_False_Return_Null_Lines ()
-		{
-			var text = "Views";
-			var view = new View () {
-				Width = Dim.Fill () - text.Length,
-				Height = 1,
-				Text = text
-			};
-			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 (10, 4);
-
-			Assert.Equal (5, text.Length);
-			Assert.False (view.AutoSize);
-			Assert.Equal (new Rect (0, 0, 3, 1), view.Frame);
-			Assert.Equal (new Size (3, 1), view.TextFormatter.Size);
-			Assert.Equal (new List<string> () { "Vie" }, view.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
-			var expected = @"
-┌────────┐
-│Vie     │
-│        │
-└────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			view.Width = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 0, 1), view.Frame);
-			Assert.Equal (new Size (0, 1), view.TextFormatter.Size);
-			Assert.Equal (new List<string> () { string.Empty }, view.TextFormatter.Lines);
-			expected = @"
-┌────────┐
-│        │
-│        │
-└────────┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_False_View_IsEmpty_True_Minimum_Height ()
-		{
-			var text = "Views";
-			var view = new View () {
-				Width = Dim.Fill () - text.Length,
-				Text = text
-			};
-			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 (10, 4);
-
-			Assert.Equal (5, text.Length);
-			Assert.False (view.AutoSize);
-			Assert.Equal (new Rect (0, 0, 3, 1), view.Frame);
-			Assert.Equal (new Size (3, 1), view.TextFormatter.Size);
-			Assert.Single (view.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
-			var expected = @"
-┌────────┐
-│Vie     │
-│        │
-└────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			view.Width = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 0, 1), view.Frame);
-			Assert.Equal (new Size (0, 1), view.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Equal (new List<string> () { string.Empty }, view.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌────────┐
-│        │
-│        │
-└────────┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_True_Label_IsEmpty_False_Never_Return_Null_Lines ()
-		{
-			var text = "Label";
-			var label = new Label () {
-				Width = Dim.Fill () - text.Length,
-				Height = 1,
-				Text = text
-			};
-			var win = new Window () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			win.Add (label);
-			Application.Top.Add (win);
-			Application.Begin (Application.Top);
-			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
-
-			Assert.Equal (5, text.Length);
-			Assert.True (label.AutoSize);
-			Assert.Equal (new Rect (0, 0, 5, 1), label.Frame);
-			Assert.Equal (new Size (5, 1), label.TextFormatter.Size);
-			Assert.Equal (new List<string> () { "Label" }, label.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
-			var expected = @"
-┌────────┐
-│Label   │
-│        │
-└────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			label.Width = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.True (label.AutoSize);
-			Assert.Equal (new Rect (0, 0, 5, 1), label.Frame);
-			Assert.Equal (new Size (5, 1), label.TextFormatter.Size);
-			Assert.Single (label.TextFormatter.Lines);
-			expected = @"
-┌────────┐
-│Label   │
-│        │
-└────────┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_False_Label_IsEmpty_True_Return_Null_Lines ()
-		{
-			var text = "Label";
-			var label = new Label () {
-				Width = Dim.Fill () - text.Length,
-				Height = 1,
-				Text = text,
-				AutoSize = false
-			};
-			var win = new Window () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			win.Add (label);
-			Application.Top.Add (win);
-			Application.Begin (Application.Top);
-			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
-
-			Assert.Equal (5, text.Length);
-			Assert.False (label.AutoSize);
-			Assert.Equal (new Rect (0, 0, 3, 1), label.Frame);
-			Assert.Equal (new Size (3, 1), label.TextFormatter.Size);
-			Assert.Equal (new List<string> () { "Lab" }, label.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
-			var expected = @"
-┌────────┐
-│Lab     │
-│        │
-└────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			label.Width = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.False (label.AutoSize);
-			Assert.Equal (new Rect (0, 0, 0, 1), label.Frame);
-			Assert.Equal (new Size (0, 1), label.TextFormatter.Size);
-			Assert.Equal (new List<string> { string.Empty }, label.TextFormatter.Lines);
-			expected = @"
-┌────────┐
-│        │
-│        │
-└────────┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_True_Label_IsEmpty_False_Minimum_Height ()
-		{
-			var text = "Label";
-			var label = new Label () {
-				Width = Dim.Fill () - text.Length,
-				Text = text
-			};
-			var win = new Window () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			win.Add (label);
-			Application.Top.Add (win);
-			Application.Begin (Application.Top);
-			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
-
-			Assert.Equal (5, text.Length);
-			Assert.True (label.AutoSize);
-			Assert.Equal (new Rect (0, 0, 5, 1), label.Frame);
-			Assert.Equal (new Size (5, 1), label.TextFormatter.Size);
-			Assert.Equal (new List<string> () { "Label" }, label.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
-			var expected = @"
-┌────────┐
-│Label   │
-│        │
-└────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			label.Width = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 5, 1), label.Frame);
-			Assert.Equal (new Size (5, 1), label.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Single (label.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌────────┐
-│Label   │
-│        │
-└────────┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_False_Label_Height_Zero_Returns_Minimum_Height ()
-		{
-			var text = "Label";
-			var label = new Label () {
-				Width = Dim.Fill () - text.Length,
-				Text = text,
-				AutoSize = false
-			};
-			var win = new Window () {
-				Width = Dim.Fill (),
-				Height = Dim.Fill ()
-			};
-			win.Add (label);
-			Application.Top.Add (win);
-			Application.Begin (Application.Top);
-			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
-
-			Assert.Equal (5, text.Length);
-			Assert.False (label.AutoSize);
-			Assert.Equal (new Rect (0, 0, 3, 1), label.Frame);
-			Assert.Equal (new Size (3, 1), label.TextFormatter.Size);
-			Assert.Single (label.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
-			Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
-			var expected = @"
-┌────────┐
-│Lab     │
-│        │
-└────────┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			label.Width = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 0, 1), label.Frame);
-			Assert.Equal (new Size (0, 1), label.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Equal (new List<string> () { string.Empty }, label.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌────────┐
-│        │
-│        │
-└────────┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 10, 4), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_True_View_IsEmpty_False_Minimum_Width ()
-		{
-			var text = "Views";
-			var view = new View () {
-				TextDirection = TextDirection.TopBottom_LeftRight,
-				Height = Dim.Fill () - text.Length,
-				Text = text,
-				AutoSize = true
-			};
-			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 (4, 10);
-
-			Assert.Equal (5, text.Length);
-			Assert.True (view.AutoSize);
-			Assert.Equal (new Rect (0, 0, 1, 5), view.Frame);
-			Assert.Equal (new Size (1, 5), view.TextFormatter.Size);
-			Assert.Equal (new List<string> () { "Views" }, view.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 4, 10), win.Frame);
-			Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame);
-			var expected = @"
-┌──┐
-│V │
-│i │
-│e │
-│w │
-│s │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			view.Height = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 1, 5), view.Frame);
-			Assert.Equal (new Size (1, 5), view.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Single (view.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌──┐
-│V │
-│i │
-│e │
-│w │
-│s │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_False_View_Width_Null_Returns_Host_Frame_Width ()
-		{
-			var text = "Views";
-			var view = new View () {
-				TextDirection = TextDirection.TopBottom_LeftRight,
-				Height = Dim.Fill () - text.Length,
-				Text = text
-			};
-			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 (4, 10);
-
-			Assert.Equal (5, text.Length);
-			Assert.False (view.AutoSize);
-			Assert.Equal (new Rect (0, 0, 1, 3), view.Frame);
-			Assert.Equal (new Size (1, 3), view.TextFormatter.Size);
-			Assert.Single (view.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 4, 10), win.Frame);
-			Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame);
-			var expected = @"
-┌──┐
-│V │
-│i │
-│e │
-│  │
-│  │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			view.Height = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 1, 0), view.Frame);
-			Assert.Equal (new Size (1, 0), view.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Equal (new List<string> () { string.Empty }, view.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌──┐
-│  │
-│  │
-│  │
-│  │
-│  │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_True_View_IsEmpty_False_Minimum_Width_Wide_Rune ()
-		{
-			var text = "界View";
-			var view = new View () {
-				TextDirection = TextDirection.TopBottom_LeftRight,
-				Height = Dim.Fill () - text.Length,
-				Text = text,
-				AutoSize = true
-			};
-			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 (4, 10);
-
-			Assert.Equal (5, text.Length);
-			Assert.True (view.AutoSize);
-			Assert.Equal (new Rect (0, 0, 2, 5), view.Frame);
-			Assert.Equal (new Size (2, 5), view.TextFormatter.Size);
-			Assert.Equal (new List<string> () { "界View" }, view.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 4, 10), win.Frame);
-			Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame);
-			var expected = @"
-┌──┐
-│界│
-│V │
-│i │
-│e │
-│w │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			view.Height = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 2, 5), view.Frame);
-			Assert.Equal (new Size (2, 5), view.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Equal (new List<string> () { "界View" }, view.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌──┐
-│界│
-│V │
-│i │
-│e │
-│w │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void AutoSize_False_View_Width_Zero_Returns_Minimum_Width_With_Wide_Rune ()
-		{
-			var text = "界View";
-			var view = new View () {
-				TextDirection = TextDirection.TopBottom_LeftRight,
-				Height = Dim.Fill () - text.Length,
-				Text = text
-			};
-			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 (4, 10);
-
-			Assert.Equal (5, text.Length);
-			Assert.False (view.AutoSize);
-			Assert.Equal (new Rect (0, 0, 2, 3), view.Frame);
-			Assert.Equal (new Size (2, 3), view.TextFormatter.Size);
-			Assert.Single (view.TextFormatter.Lines);
-			Assert.Equal (new Rect (0, 0, 4, 10), win.Frame);
-			Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame);
-			var expected = @"
-┌──┐
-│界│
-│V │
-│i │
-│  │
-│  │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-
-			text = "0123456789";
-			Assert.Equal (10, text.Length);
-			view.Height = Dim.Fill () - text.Length;
-			Application.Refresh ();
-
-			Assert.Equal (new Rect (0, 0, 2, 0), view.Frame);
-			Assert.Equal (new Size (2, 0), view.TextFormatter.Size);
-			var exception = Record.Exception (() => Assert.Equal (new List<string> () { string.Empty }, view.TextFormatter.Lines));
-			Assert.Null (exception);
-			expected = @"
-┌──┐
-│  │
-│  │
-│  │
-│  │
-│  │
-│  │
-│  │
-│  │
-└──┘
-";
-
-			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Assert.Equal (new Rect (0, 0, 4, 10), pos);
-		}
-	}
-}

+ 42 - 203
UnitTests/View/Layout/DimTests.cs

@@ -580,43 +580,8 @@ public class DimTests {
 		Assert.Throws<ArgumentException> (() => dim = Dim.Percent (1000001));
 	}
 
-	[Fact] [AutoInitShutdown]
-	public void ForceValidatePosDim_True_Dim_Validation_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_Throws ()
-	{
-		var t = Application.Top;
-
-		var w = new Window {
-			Width = Dim.Fill (),
-			Height = Dim.Sized (10)
-		};
-		var v = new View ("v") {
-			Width = Dim.Width (w) - 2,
-			Height = Dim.Percent (10),
-			ValidatePosDim = true
-		};
-
-		w.Add (v);
-		t.Add (w);
-
-		t.Ready += (s, e) => {
-			Assert.Equal (2, w.Width = 2);
-			Assert.Equal (2, w.Height = 2);
-			Assert.Throws<ArgumentException> (() => v.Width = 2);
-			Assert.Throws<ArgumentException> (() => v.Height = 2);
-			v.ValidatePosDim = 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 += (s, a) => Application.RequestStop ();
-
-		Application.Run ();
-	}
-
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [TestRespondersDisposed]
 	public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
 	{
@@ -631,6 +596,8 @@ public class DimTests {
 		t.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [TestRespondersDisposed]
 	public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
 	{
@@ -644,15 +611,21 @@ public class DimTests {
 			Width = Dim.Width (w) - 2,
 			Height = Dim.Percent (10)
 		};
-
+		
 		w.Add (v);
 		t.Add (w);
 
+		Assert.Equal (LayoutStyle.Absolute, t.LayoutStyle);
+		Assert.Equal (LayoutStyle.Computed, w.LayoutStyle);
+		Assert.Equal (LayoutStyle.Computed, v.LayoutStyle);
+
 		t.LayoutSubviews ();
 		Assert.Equal (2, v.Width = 2);
 		Assert.Equal (2, v.Height = 2);
 
-		v.LayoutStyle = LayoutStyle.Absolute;
+		// Force v to be LayoutStyle.Absolute;
+		v.Frame = new Rect (0, 1, 3, 4);
+		Assert.Equal (LayoutStyle.Absolute, v.LayoutStyle);
 		t.LayoutSubviews ();
 
 		Assert.Equal (2, v.Width = 2);
@@ -660,6 +633,8 @@ public class DimTests {
 		t.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [AutoInitShutdown]
 	public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height ()
 	{
@@ -871,6 +846,9 @@ public class DimTests {
 	//	Assert.Throws<InvalidOperationException> (() => super.LayoutSubviews ());
 	//}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
+
 	/// <summary>
 	/// This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461
 	/// </summary>
@@ -899,8 +877,6 @@ public class DimTests {
 		t.BeginInit ();
 		t.EndInit ();
 
-		// BUGBUG: v2 - f references t here; t is f's super-superview. This is supported!
-		// BUGBUG: v2 - f references v2 here; v2 is f's subview. This is not supported!
 		f.Width = Dim.Width (t) - Dim.Width (v2);    // 80 - 74 = 6
 		f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6
 
@@ -909,16 +885,17 @@ public class DimTests {
 		Assert.Equal (25, t.Frame.Height);
 		Assert.Equal (78, w.Frame.Width);
 		Assert.Equal (23, w.Frame.Height);
-		// BUGBUG: v2 - this no longer works - see above
-		//Assert.Equal (6, f.Frame.Width);
-		//Assert.Equal (6, f.Frame.Height);
-		//Assert.Equal (76, v1.Frame.Width);
-		//Assert.Equal (21, v1.Frame.Height);
-		//Assert.Equal (74, v2.Frame.Width);
-		//Assert.Equal (19, v2.Frame.Height);
+		Assert.Equal (6, f.Frame.Width);
+		Assert.Equal (6, f.Frame.Height);
+		Assert.Equal (76, v1.Frame.Width);
+		Assert.Equal (21, v1.Frame.Height);
+		Assert.Equal (74, v2.Frame.Width);
+		Assert.Equal (19, v2.Frame.Height);
 		t.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [TestRespondersDisposed]
 	public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView ()
 	{
@@ -963,12 +940,13 @@ public class DimTests {
 		t.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [TestRespondersDisposed]
 	public void PosCombine_View_Not_Added_Throws ()
 	{
 		var t = new View { Width = 80, Height = 50 };
 
-		// BUGBUG: v2 - super should not reference it's superview (t)
 		var super = new View {
 			Width = Dim.Width (t) - 2,
 			Height = Dim.Height (t) - 2
@@ -999,6 +977,8 @@ public class DimTests {
 		v2.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// A new test that does not depend on Application is needed.
 	[Fact] [AutoInitShutdown]
 	public void Dim_Add_Operator ()
 	{
@@ -1042,68 +1022,8 @@ public class DimTests {
 		Assert.Equal (20, count);
 	}
 
-	[Fact] [AutoInitShutdown]
-	public void Dim_Add_Operator_With_Text ()
-	{
-		var top = Application.Top;
-
-		// BUGBUG: v2 - If a View's height is zero, it should not be drawn.
-		//// 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 = 1 };
-		var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
-		var count = 0;
-		var listLabels = new List<Label> ();
-
-		field.KeyDown += (s, k) => {
-			if (k.KeyCode == KeyCode.Enter) {
-				((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
-				var pos = TestHelpers.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 ($"Absolute({count + 1})", label.Y.ToString ());
-					listLabels.Add (label);
-					//if (count == 0) {
-					//	Assert.Equal ($"Absolute({count})", view.Height.ToString ());
-					//	view.Height += 2;
-					//} else {
-					Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
-					view.Height += 1;
-					//}
-					count++;
-				}
-				Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
-			}
-		};
-
-		Application.Iteration += (s, a) => {
-			while (count < 21) {
-				field.NewKeyDownEvent (new Key (KeyCode.Enter));
-				if (count == 20) {
-					field.NewKeyDownEvent (new Key (KeyCode.Enter));
-					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);
-	}
-
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [AutoInitShutdown]
 	public void Dim_Subtract_Operator ()
 	{
@@ -1119,14 +1039,12 @@ public class DimTests {
 			var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 };
 			view.Add (label);
 			Assert.Equal ($"Label {i}", label.Text);
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i})", label.Y.ToString ());
+			Assert.Equal ($"Absolute({i})", label.Y.ToString ());
 			listLabels.Add (label);
 
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i})", view.Height.ToString ());
+			Assert.Equal ($"Absolute({i})", view.Height.ToString ());
 			view.Height += 1;
-			//Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
+			Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
 		}
 
 		field.KeyDown += (s, k) => {
@@ -1161,91 +1079,6 @@ public class DimTests {
 		Assert.Equal (0, count);
 	}
 
-	[Fact] [AutoInitShutdown]
-	public void Dim_Subtract_Operator_With_Text ()
-	{
-		var top = Application.Top;
-
-		// BUGBUG: v2 - If a View's height is zero, it should not be drawn.
-		//// 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 = 1 };
-		var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
-		var count = 20;
-		var listLabels = new List<Label> ();
-
-		for (var i = 0; i < count; i++) {
-			field.Text = $"Label {i}";
-			// BUGBUG: v2 - view has not been initialied yet; view.Bounds is indeterminate
-			var label = new Label (field.Text) { X = 0, Y = i + 1, Width = 10 };
-			view.Add (label);
-			Assert.Equal ($"Label {i}", label.Text);
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i + 1})", label.Y.ToString ());
-			listLabels.Add (label);
-
-			//if (i == 0) {
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i})", view.Height.ToString ());
-			//view.Height += 2;
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i + 2})", view.Height.ToString ());
-			//} else {
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
-			view.Height += 1;
-			// BUGBUG: Bogus test; views have not been initialized yet
-			//Assert.Equal ($"Absolute({i + 2})", view.Height.ToString ());
-			//}
-		}
-
-		field.KeyDown += (s, k) => {
-			if (k.KeyCode == KeyCode.Enter) {
-				((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
-				var pos = TestHelpers.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 [count - 1].Dispose ();
-					listLabels.RemoveAt (count - 1);
-					Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
-					view.Height -= 1;
-					count--;
-					if (listLabels.Count > 0) {
-						field.Text = listLabels [count - 1].Text;
-					} else {
-						field.Text = string.Empty;
-					}
-				}
-				Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
-			}
-		};
-
-		Application.Iteration += (s, a) => {
-			while (count > -1) {
-				field.NewKeyDownEvent (new Key (KeyCode.Enter));
-				if (count == 0) {
-					field.NewKeyDownEvent (new Key (KeyCode.Enter));
-					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);
-	}
-
 	[Fact] [TestRespondersDisposed]
 	public void Internal_Tests ()
 	{
@@ -1301,9 +1134,11 @@ public class DimTests {
 		Assert.NotEqual (dim1, dim2);
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Theory] [AutoInitShutdown]
-	[InlineData (0,  true)]
-	[InlineData (0,  false)]
+	[InlineData (0,   true)]
+	[InlineData (0,   false)]
 	[InlineData (50, true)]
 	[InlineData (50, false)]
 	public void DimPercentPlusOne (int startingDistance, bool testHorizontal)
@@ -1338,6 +1173,8 @@ public class DimTests {
 		}
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [TestRespondersDisposed]
 	public void Dim_Referencing_SuperView_Does_Not_Throw ()
 	{
@@ -1359,6 +1196,8 @@ public class DimTests {
 		super.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact] [TestRespondersDisposed]
 	public void Dim_SyperView_Referencing_SubView_Throws ()
 	{

Fișier diff suprimat deoarece este prea mare
+ 66 - 1243
UnitTests/View/Layout/LayoutTests.cs


+ 45 - 340
UnitTests/View/Layout/PosTests.cs

@@ -22,7 +22,7 @@ public class PosTests {
 	[Fact]
 	public void AnchorEnd_SetsValue ()
 	{
-		int n = 0;
+		var n = 0;
 		var pos = Pos.AnchorEnd ();
 		Assert.Equal ($"AnchorEnd({n})", pos.ToString ());
 
@@ -34,8 +34,8 @@ public class PosTests {
 	[Fact]
 	public void AnchorEnd_Equal ()
 	{
-		int n1 = 0;
-		int n2 = 0;
+		var n1 = 0;
+		var n2 = 0;
 
 		var pos1 = Pos.AnchorEnd (n1);
 		var pos2 = Pos.AnchorEnd (n2);
@@ -47,13 +47,15 @@ public class PosTests {
 		Assert.NotEqual (pos1, pos2);
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	[AutoInitShutdown]
 	public void AnchorEnd_Equal_Inside_Window ()
 	{
-		int viewWidth = 10;
-		int viewHeight = 1;
-		var tv = new TextView () {
+		var viewWidth = 10;
+		var viewHeight = 1;
+		var tv = new TextView {
 			X = Pos.AnchorEnd (viewWidth),
 			Y = Pos.AnchorEnd (viewHeight),
 			Width = viewWidth,
@@ -74,13 +76,15 @@ public class PosTests {
 		Application.End (rs);
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	[AutoInitShutdown]
 	public void AnchorEnd_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
 	{
-		int viewWidth = 10;
-		int viewHeight = 1;
-		var tv = new TextView () {
+		var viewWidth = 10;
+		var viewHeight = 1;
+		var tv = new TextView {
 			X = Pos.AnchorEnd (viewWidth),
 			Y = Pos.AnchorEnd (viewHeight),
 			Width = viewWidth,
@@ -106,212 +110,11 @@ public class PosTests {
 		Application.End (rs);
 	}
 
-	[Fact]
-	[AutoInitShutdown]
-	public void Bottom_Equal_Inside_Window ()
-	{
-		var win = new Window ();
-
-		var label = new Label ("This should be the last line.") {
-			ColorScheme = Colors.Menu,
-			Width = Dim.Fill (),
-			X = 0,
-			Y = Pos.Bottom (win) - 3 // two lines top and bottom borders more one line above the bottom border
-		};
-
-		win.Add (label);
-
-		var top = Application.Top;
-		top.Add (win);
-		var rs = 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 (0, 7, 38, 1), label.Frame);
-		string expected = @"
-┌──────────────────────────────────────┐
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│This should be the last line.         │
-└──────────────────────────────────────┘
-";
-
-		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Application.End (rs);
-	}
-
-	[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.") {
-			ColorScheme = Colors.Menu,
-			Width = Dim.Fill (),
-			X = 0, // keep unit test focused; don't use Center here
-			Y = Pos.AnchorEnd (1)
-		};
-
-		win.Add (label);
-
-		var top = Application.Top;
-		top.Add (win);
-		var rs = 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 (0, 7, 38, 1), label.Frame);
-		string expected = @"
-┌──────────────────────────────────────┐
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│This should be the last line.         │
-└──────────────────────────────────────┘
-";
-
-		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Application.End (rs);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void Bottom_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
-	{
-		var win = new Window ();
-
-		var label = new Label ("This should be the last line.") {
-			ColorScheme = Colors.Menu,
-			Width = Dim.Fill (),
-			X = 0,
-			Y = Pos.Bottom (win) - 4 // two lines top and bottom borders more two lines above border
-		};
-
-		win.Add (label);
-
-		var menu = new MenuBar (new MenuBarItem [] { new ("Menu", "", null) });
-		var status = new StatusBar (new StatusItem [] { new (KeyCode.F1, "~F1~ Help", null) });
-		var top = Application.Top;
-		top.Add (win, menu, status);
-		var rs = 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 (0, 20, 78, 1), label.Frame);
-		string expected = @"
- Menu                                                                           
-┌──────────────────────────────────────────────────────────────────────────────┐
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│This should be the last line.                                                 │
-└──────────────────────────────────────────────────────────────────────────────┘
- F1 Help                                                                        
-";
-
-		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Application.End (rs);
-	}
-
-	[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.") {
-			ColorScheme = Colors.Menu,
-			Width = Dim.Fill (),
-			X = 0,
-			Y = Pos.AnchorEnd (1)
-		};
-
-		win.Add (label);
-
-		var menu = new MenuBar (new MenuBarItem [] { new ("Menu", "", null) });
-		var status = new StatusBar (new StatusItem [] { new (KeyCode.F1, "~F1~ Help", null) });
-		var top = Application.Top;
-		top.Add (win, menu, status);
-		var rs = 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 (0, 20, 78, 1), label.Frame);
-		string expected = @"
- Menu                                                                           
-┌──────────────────────────────────────────────────────────────────────────────┐
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│                                                                              │
-│This should be the last line.                                                 │
-└──────────────────────────────────────────────────────────────────────────────┘
- F1 Help                                                                        
-";
-
-		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Application.End (rs);
-	}
-
 	[Fact]
 	public void AnchorEnd_Negative_Throws ()
 	{
 		Pos pos;
-		int n = -1;
+		var n = -1;
 		Assert.Throws<ArgumentException> (() => pos = Pos.AnchorEnd (n));
 	}
 
@@ -331,8 +134,8 @@ public class PosTests {
 	[Fact]
 	public void At_Equal ()
 	{
-		int n1 = 0;
-		int n2 = 0;
+		var n1 = 0;
+		var n2 = 0;
 
 		var pos1 = Pos.At (n1);
 		var pos2 = Pos.At (n2);
@@ -372,7 +175,7 @@ public class PosTests {
 	{
 		string side; // used in format string
 		var testRect = Rect.Empty;
-		int testInt = 0;
+		var testInt = 0;
 		Pos pos;
 
 		// Pos.Left
@@ -521,6 +324,8 @@ public class PosTests {
 #endif
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	// See: https://github.com/gui-cs/Terminal.Gui/issues/504
 	[Fact]
 	[TestRespondersDisposed]
@@ -533,7 +338,7 @@ public class PosTests {
 			Application.Iteration += (s, a) => {
 				Application.RequestStop ();
 			};
-			var win = new Window () {
+			var win = new Window {
 				X = 0,
 				Y = 0,
 				Width = Dim.Fill (),
@@ -671,39 +476,8 @@ public class PosTests {
 		Assert.Throws<ArgumentException> (() => pos = Pos.Percent (1000001));
 	}
 
-	[Fact]
-	public void ForceValidatePosDim_True_Pos_Validation_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type ()
-	{
-		Application.Init (new FakeDriver ());
-
-		var t = Application.Top;
-
-		var w = new Window () {
-			X = Pos.Left (t) + 2,
-			Y = Pos.At (2)
-		};
-		var v = new View () {
-			X = Pos.Center (),
-			Y = Pos.Percent (10),
-			ValidatePosDim = true
-		};
-
-		w.Add (v);
-		t.Add (w);
-
-		t.Ready += (s, e) => {
-			Assert.Equal (2, w.X = 2);
-			Assert.Equal (2, w.Y = 2);
-			Assert.Throws<ArgumentException> (() => v.X = 2);
-			Assert.Throws<ArgumentException> (() => v.Y = 2);
-		};
-
-		Application.Iteration += (s, a) => Application.RequestStop ();
-
-		Application.Run ();
-		Application.Shutdown ();
-	}
-
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
 	{
@@ -726,85 +500,8 @@ public class PosTests {
 
 	}
 
-	[Fact]
-	public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
-	{
-		Application.Init (new FakeDriver ());
-
-		var t = Application.Top;
-
-		var w = new Window () {
-			X = Pos.Left (t) + 2,
-			Y = Pos.At (2)
-		};
-		var v = new View () {
-			X = Pos.Center (),
-			Y = Pos.Percent (10)
-		};
-
-		w.Add (v);
-		t.Add (w);
-
-		t.Ready += (s, e) => {
-			v.LayoutStyle = LayoutStyle.Absolute;
-			Assert.Equal (2, v.X = 2);
-			Assert.Equal (2, v.Y = 2);
-		};
-
-		Application.Iteration += (s, a) => Application.RequestStop ();
-
-		Application.Run ();
-		Application.Shutdown ();
-	}
-
-	// DONE: Test PosCombine
-	// DONE: Test operators
-	// BUGBUG: v2 - This test is bogus. v1 and references it's superview's (f) superview (w). 
-	//[Fact]
-	//public void PosCombine_Do_Not_Throws ()
-	//{
-	//	Application.Init (new FakeDriver ());
-
-	//	var w = new Window () {
-	//		X = Pos.Left (Application.Top) + 2,
-	//		Y = Pos.Top (Application.Top) + 2
-	//	};
-	//	var f = new FrameView ();
-	//	var v1 = new View () {
-	//		X = Pos.Left (w) + 2,
-	//		Y = Pos.Top (w) + 2
-	//	};
-	//	var v2 = new View () {
-	//		X = Pos.Left (v1) + 2,
-	//		Y = Pos.Top (v1) + 2
-	//	};
-
-	//	f.Add (v1, v2);
-	//	w.Add (f);
-	//	Application.Top.Add (w);
-
-	//	f.X = Pos.X (Application.Top) + Pos.X (v2) - Pos.X (v1);
-	//	f.Y = Pos.Y (Application.Top) + Pos.Y (v2) - Pos.Y (v1);
-
-	//	Application.Top.LayoutComplete += (s, e) => {
-	//		Assert.Equal (0, Application.Top.Frame.X);
-	//		Assert.Equal (0, Application.Top.Frame.Y);
-	//		Assert.Equal (2, w.Frame.X);
-	//		Assert.Equal (2, w.Frame.Y);
-	//		Assert.Equal (2, f.Frame.X);
-	//		Assert.Equal (2, f.Frame.Y);
-	//		Assert.Equal (4, v1.Frame.X);
-	//		Assert.Equal (4, v1.Frame.Y);
-	//		Assert.Equal (6, v2.Frame.X);
-	//		Assert.Equal (6, v2.Frame.Y);
-	//	};
-
-	//	Application.Iteration += (s, a) => Application.RequestStop ();
-
-	//	Application.Run ();
-	//	Application.Shutdown ();
-	//}
-
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	[TestRespondersDisposed]
 	public void PosCombine_Will_Throws ()
@@ -813,16 +510,16 @@ public class PosTests {
 
 		var t = Application.Top;
 
-		var w = new Window () {
+		var w = new Window {
 			X = Pos.Left (t) + 2,
 			Y = Pos.Top (t) + 2
 		};
 		var f = new FrameView ();
-		var v1 = new View () {
+		var v1 = new View {
 			X = Pos.Left (w) + 2,
 			Y = Pos.Top (w) + 2
 		};
-		var v2 = new View () {
+		var v2 = new View {
 			X = Pos.Left (v1) + 2,
 			Y = Pos.Top (v1) + 2
 		};
@@ -840,6 +537,8 @@ public class PosTests {
 		v2.Dispose ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	[TestRespondersDisposed]
 	public void Pos_Add_Operator ()
@@ -848,9 +547,9 @@ public class PosTests {
 
 		var top = Application.Top;
 
-		var view = new View () { X = 0, Y = 0, Width = 20, Height = 20 };
-		var field = new TextField () { X = 0, Y = 0, Width = 20 };
-		int count = 0;
+		var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
+		var field = new TextField { X = 0, Y = 0, Width = 20 };
+		var count = 0;
 
 		field.KeyDown += (s, k) => {
 			if (k.KeyCode == KeyCode.Enter) {
@@ -889,6 +588,8 @@ public class PosTests {
 		Application.Shutdown ();
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	[TestRespondersDisposed]
 	public void Pos_Subtract_Operator ()
@@ -897,12 +598,12 @@ public class PosTests {
 
 		var top = Application.Top;
 
-		var view = new View () { X = 0, Y = 0, Width = 20, Height = 20 };
-		var field = new TextField () { X = 0, Y = 0, Width = 20 };
-		int count = 20;
+		var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
+		var field = new TextField { X = 0, Y = 0, Width = 20 };
+		var count = 20;
 		var listLabels = new List<Label> ();
 
-		for (int i = 0; i < count; i++) {
+		for (var i = 0; i < count; i++) {
 			field.Text = $"Label {i}";
 			var label = new Label (field.Text) { X = 0, Y = field.Y, Width = 20 };
 			view.Add (label);
@@ -993,7 +694,7 @@ public class PosTests {
 	[Fact]
 	public void Function_SetsValue ()
 	{
-		string text = "Test";
+		var text = "Test";
 		var pos = Pos.Function (() => text.Length);
 		Assert.Equal ("PosFunc(4)", pos.ToString ());
 
@@ -1019,6 +720,8 @@ public class PosTests {
 		Assert.NotEqual (pos1, pos2);
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Theory]
 	[AutoInitShutdown]
 	[InlineData (true)]
@@ -1053,6 +756,8 @@ public class PosTests {
 		}
 	}
 
+	// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+	// TODO: A new test that calls SetRelativeLayout directly is needed.
 	[Fact]
 	public void PosCombine_Referencing_Same_View ()
 	{
@@ -1086,7 +791,7 @@ public class PosTests {
 	[Fact]
 	public void DoNotReturnPosCombine ()
 	{
-		var v = new View () { Id = "V" };
+		var v = new View { Id = "V" };
 
 		var pos = Pos.Left (v);
 		Assert.Equal (
@@ -1118,5 +823,5 @@ public class PosTests {
 			"View(side=bottom,target=View(V)(0,0,0,0))",
 			pos.ToString ());
 	}
-}	
 
+}

+ 70 - 124
UnitTests/View/Layout/SetRelativeLayoutTests.cs

@@ -12,131 +12,42 @@ public class SetRelativeLayoutTests {
 	public SetRelativeLayoutTests (ITestOutputHelper output) => _output = output;
 
 	[Fact]
-	public void Null_Pos_Is_Same_As_PosAbsolute0 ()
+	public void ComputedPosDim_StayComputed ()
 	{
+		var screen = new Rect (0, 0, 10, 15);
 		var view = new View () {
-			X = null,
-			Y = null,
-		};
-
-		// Default layout style is Computed
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.Null (view.X);
-		Assert.Null (view.Y);
-
-		view.BeginInit(); view.EndInit();
-
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.Null (view.X);
-		Assert.Null (view.Y);
-
-		view.SetRelativeLayout (new Rect (5, 5, 10, 10));
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.Null (view.X);
-		Assert.Null (view.Y);
-
-		Assert.Equal (0, view.Frame.X);
-		Assert.Equal (0, view.Frame.Y);
-	}
-
-	[Theory]
-	[InlineData (1, 1)]
-	[InlineData (0, 0)]
-	public void NonNull_Pos (int pos, int expectedPos)
-	{
-		var view = new View () {
-			X = pos,
-			Y = pos,
+			X = 1,
+			Y = 2,
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
 		};
 
-		// Default layout style is Computed
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.NotNull (view.X);
-		Assert.NotNull (view.Y);
-
-		view.BeginInit (); view.EndInit ();
-
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.NotNull (view.X);
-		Assert.NotNull (view.Y);
-
-		view.SetRelativeLayout (new Rect (5, 5, 10, 10));
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.NotNull (view.X);
-		Assert.NotNull (view.Y);
-
-		Assert.Equal (expectedPos, view.Frame.X);
-		Assert.Equal (expectedPos, view.Frame.Y);
+		Assert.Equal ("Absolute(1)", view.X.ToString ());
+		Assert.Equal ("Absolute(2)", view.Y.ToString ());
+		Assert.Equal ("Fill(0)", view.Width.ToString ());
+		Assert.Equal ("Fill(0)", view.Height.ToString ());
+		view.SetRelativeLayout (screen);
+		Assert.Equal ("Fill(0)", view.Width.ToString ());
+		Assert.Equal ("Fill(0)", view.Height.ToString ());
 	}
 
 	[Fact]
-	public void Null_Dim_Is_Same_As_DimFill0 ()
+	public void AbsolutePosDim_DontChange ()
 	{
+		var screen = new Rect (0, 0, 10, 15);
 		var view = new View () {
-			Width = null,
-			Height = null,
-		};
-
-		// Default layout style is Computed
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.Null (view.Width);
-		Assert.Null (view.Height);
-		view.BeginInit (); view.EndInit ();
-
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.Null (view.Width);
-		Assert.Null (view.Height);
-
-		view.SetRelativeLayout (new Rect (5, 5, 10, 10));
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.Null (view.Width);
-		Assert.Null (view.Height);
-		
-		Assert.Equal (0, view.Frame.X);
-		Assert.Equal (0, view.Frame.Y);
-
-		Assert.Equal (10, view.Frame.Width);
-		Assert.Equal (10, view.Frame.Height);
-
-		view.Width = Dim.Fill (0);
-		view.Height = Dim.Fill (0);
-		view.SetRelativeLayout (new Rect (5, 5, 10, 10));
-		Assert.Equal (10, view.Frame.Width);
-		Assert.Equal (10, view.Frame.Height);
-
-	}
-
-
-	[Theory]
-	[InlineData(1, 1)]
-	[InlineData (0, 0)]
-	public void NonNull_Dim (int dim, int expectedDim)
-	{
-		var view = new View () {
-			Width = dim,
-			Height = dim,
+			X = 1,  // outside of screen +10
+			Y = 2, // outside of screen -10
+			Width = 3,
+			Height = 4
 		};
 
-		// Default layout style is Computed
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.NotNull (view.Width);
-		Assert.NotNull (view.Height);
-		view.BeginInit (); view.EndInit ();
-
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.NotNull (view.Width);
-		Assert.NotNull (view.Height);
-
-		view.SetRelativeLayout (new Rect (5, 5, 10, 10));
-		Assert.Equal (LayoutStyle.Computed, view.LayoutStyle);
-		Assert.NotNull (view.Width);
-		Assert.NotNull (view.Height);
-		
-		Assert.Equal (0, view.Frame.X);
-		Assert.Equal (0, view.Frame.Y);
-		// BUGBUG: Width == null is same as Dim.Absolute (0) (or should be). Thus this is a bug.
-		Assert.Equal (expectedDim, view.Frame.Width);
-		Assert.Equal (expectedDim, view.Frame.Height);
+		// Layout is Absolute. So the X and Y are not changed.
+		view.SetRelativeLayout (screen);
+		Assert.Equal (1, view.Frame.X);
+		Assert.Equal (2, view.Frame.Y);
+		Assert.Equal (3, view.Frame.Width);
+		Assert.Equal (4, view.Frame.Height);
 	}
 
 	[Fact]
@@ -192,7 +103,7 @@ public class SetRelativeLayoutTests {
 	}
 
 	[Fact]
-	public void FIll_Pos_Outside_Bounds ()
+	public void Fill_Pos_Outside_Bounds ()
 	{
 		var screen = new Rect (0, 0, 80, 25);
 		var view = new View () {
@@ -202,6 +113,7 @@ public class SetRelativeLayoutTests {
 			Height = 15
 		};
 
+		// Layout is Absolute. So the X and Y are not changed.
 		view.SetRelativeLayout (screen);
 		Assert.Equal (90, view.Frame.X);
 		Assert.Equal (-10, view.Frame.Y);
@@ -253,28 +165,28 @@ public class SetRelativeLayoutTests {
 		Assert.Equal (80, view.Frame.Width);
 		Assert.Equal (25, view.Frame.Height);
 
-		view.Width = Dim.Fill (); 
+		view.Width = Dim.Fill ();
 		view.Height = Dim.Fill ();
 		view.SetRelativeLayout (screen);
-		Assert.Equal (-41, view.Frame.X); 
+		Assert.Equal (-41, view.Frame.X);
 		Assert.Equal (-13, view.Frame.Y);
 		Assert.Equal (121, view.Frame.Width);  // 121 = screen.Width - (-Center - 41)
 		Assert.Equal (38, view.Frame.Height);
 	}
 
 	[Fact]
-	public void FIll_And_PosCenter ()
+	public void Fill_And_PosCenter ()
 	{
 		var screen = new Rect (0, 0, 80, 25);
 		var view = new View () {
 			X = Pos.Center (),
 			Y = Pos.Center (),
-			Width = Dim.Fill(),
-			Height = Dim.Fill()
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
 		};
 
 		view.SetRelativeLayout (screen);
-		Assert.Equal (0, view.Frame.X); 
+		Assert.Equal (0, view.Frame.X);
 		Assert.Equal (0, view.Frame.Y);
 		Assert.Equal (80, view.Frame.Width);
 		Assert.Equal (25, view.Frame.Height);
@@ -304,14 +216,14 @@ public class SetRelativeLayoutTests {
 		view.SetRelativeLayout (screen);
 		Assert.Equal (-1, view.Frame.X);
 		Assert.Equal (0, view.Frame.Y);
-		Assert.Equal (81, view.Frame.Width); 
+		Assert.Equal (81, view.Frame.Width);
 		Assert.Equal (25, view.Frame.Height);
 
 		view.X = Pos.Center () - 2; // Fill means all the way to right. So width will be 82. (dim gets calc'd before pos).
 		view.SetRelativeLayout (screen);
 		Assert.Equal (-2, view.Frame.X);
 		Assert.Equal (0, view.Frame.Y);
-		Assert.Equal (82, view.Frame.Width); 
+		Assert.Equal (82, view.Frame.Width);
 		Assert.Equal (25, view.Frame.Height);
 
 		view.X = Pos.Center () - 3; // Fill means all the way to right. So width will be 83. (dim gets calc'd before pos).
@@ -345,7 +257,8 @@ public class SetRelativeLayoutTests {
 		Assert.Equal (23, view.Frame.Y);
 	}
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void PosCombine_Plus_Absolute ()
 	{
 		var superView = new View () {
@@ -453,4 +366,37 @@ public class SetRelativeLayoutTests {
 		superView.Dispose ();
 
 	}
+
+
+	[Fact]
+	public void PosDimFunction ()
+	{
+		var screen = new Rect (0, 0, 30, 1);
+		var view = new View ("abc");
+		view.X = Pos.AnchorEnd () - Pos.Function (GetViewWidth);
+
+		int GetViewWidth ()
+		{
+			return view.Frame.Width;
+		}
+
+		// view will be 3 chars wide. It's X will be 27 (30 - 3).
+		view.SetRelativeLayout (screen);
+		Assert.Equal (27, view.Frame.X);
+		Assert.Equal (0,  view.Frame.Y);
+		Assert.Equal (3,  view.Frame.Width);
+		Assert.Equal (1,  view.Frame.Height);
+
+		var tf = new TextField ("01234567890123456789");
+		tf.Width = Dim.Fill (1) - Dim.Function (GetViewWidth);
+
+		// tf will fill the screen minus 1 minus the width of view (3).
+		// so it's width will be 26 (30 - 1 - 3).
+		tf.SetRelativeLayout (screen);
+		Assert.Equal (0,  tf.Frame.X);
+		Assert.Equal (0,  tf.Frame.Y);
+		Assert.Equal (26, tf.Frame.Width);
+		Assert.Equal (1,  tf.Frame.Height);
+
+	}
 }

+ 2643 - 0
UnitTests/View/Text/AutoSizeTextTests.cs

@@ -0,0 +1,2643 @@
+using Microsoft.VisualStudio.TestPlatform.Utilities;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+/// <summary>
+/// Tests of the  <see cref="View.AutoSize"/> property which auto sizes Views based on <see cref="Text"/>.
+/// </summary>
+public class AutoSizeTextTests {
+	readonly ITestOutputHelper _output;
+
+
+	readonly 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            │
+└────────────────────┘"
+						       };
+
+	public AutoSizeTextTests (ITestOutputHelper output) => _output = output;
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_GetAutoSize_Horizontal ()
+	{
+		var text = "text";
+		var view = new View {
+					    Text     = text,
+					    AutoSize = true
+				    };
+		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 (10, 4);
+
+		var size = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 1), size);
+
+		view.Text = $"{text}\n{text}";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 2), size);
+
+		view.Text = $"{text}\n{text}\n{text}+";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length + 1, 3), size);
+
+		text      = string.Empty;
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (0, 0), size);
+
+		text      = "1";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (1, 1), size);
+
+		text      = "界";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (2, 1), size);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_GetAutoSize_Vertical ()
+	{
+		var text = "text";
+		var view = new View {
+					    Text          = text,
+					    TextDirection = TextDirection.TopBottom_LeftRight,
+					    AutoSize      = true
+				    };
+		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 (10, 4);
+
+		var size = view.GetAutoSize ();
+		Assert.Equal (new Size (1, text.Length), size);
+
+		view.Text = $"{text}\n{text}";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (2, text.Length), size);
+
+		view.Text = $"{text}\n{text}\n{text}+";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (3, text.Length + 1), size);
+
+		text      = string.Empty;
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (0, 0), size);
+
+		text      = "1";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (1, 1), size);
+
+		text      = "界";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (2, 1), size);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_GetAutoSize_Left ()
+	{
+		var text = "This is some text.";
+		var view = new View {
+					    Text          = text,
+					    TextAlignment = TextAlignment.Left,
+					    AutoSize      = true
+				    };
+		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 (10, 4);
+
+		var size = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 1), size);
+
+		view.Text = $"{text}\n{text}";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 2), size);
+
+		view.Text = $"{text}\n{text}\n{text}+";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length + 1, 3), size);
+
+		text      = string.Empty;
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (0, 0), size);
+
+		text      = "1";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (1, 1), size);
+
+		text      = "界";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (2, 1), size);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_GetAutoSize_Right ()
+	{
+		var text = "This is some text.";
+		var view = new View {
+					    Text          = text,
+					    TextAlignment = TextAlignment.Right,
+					    AutoSize      = true
+				    };
+		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 (10, 4);
+
+		var size = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 1), size);
+
+		view.Text = $"{text}\n{text}";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 2), size);
+
+		view.Text = $"{text}\n{text}\n{text}+";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length + 1, 3), size);
+
+		text      = string.Empty;
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (0, 0), size);
+
+		text      = "1";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (1, 1), size);
+
+		text      = "界";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (2, 1), size);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_GetAutoSize_Centered ()
+	{
+		var text = "This is some text.";
+		var view = new View {
+					    Text          = text,
+					    TextAlignment = TextAlignment.Centered,
+					    AutoSize      = true
+				    };
+		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 (10, 4);
+
+		var size = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 1), size);
+
+		view.Text = $"{text}\n{text}";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length, 2), size);
+
+		view.Text = $"{text}\n{text}\n{text}+";
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (text.Length + 1, 3), size);
+
+		text      = string.Empty;
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (0, 0), size);
+
+		text      = "1";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (1, 1), size);
+
+		text      = "界";
+		view.Text = text;
+		size      = view.GetAutoSize ();
+		Assert.Equal (new Size (2, 1), size);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Label_IsEmpty_False_Never_Return_Null_Lines ()
+	{
+		var text = "Label";
+		var label = new Label {
+					      Width  = Dim.Fill () - text.Length,
+					      Height = 1,
+					      Text   = text
+				      };
+		var win = new Window {
+					     Width  = Dim.Fill (),
+					     Height = Dim.Fill ()
+				     };
+		win.Add (label);
+		Application.Top.Add (win);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+
+		Assert.Equal (5, text.Length);
+		Assert.True (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 5, 1),        label.Frame);
+		Assert.Equal (new Size (5, 1),              label.TextFormatter.Size);
+		Assert.Equal (new List<string> { "Label" }, label.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 10, 4),       win.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 4),       Application.Top.Frame);
+		var expected = @"
+┌────────┐
+│Label   │
+│        │
+└────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		label.Width = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.True (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 5, 1), label.Frame);
+		Assert.Equal (new Size (5, 1),       label.TextFormatter.Size);
+		Assert.Single (label.TextFormatter.Lines);
+		expected = @"
+┌────────┐
+│Label   │
+│        │
+└────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Label_IsEmpty_False_Minimum_Height ()
+	{
+		var text = "Label";
+		var label = new Label {
+					      Width = Dim.Fill () - text.Length,
+					      Text  = text
+				      };
+		var win = new Window {
+					     Width  = Dim.Fill (),
+					     Height = Dim.Fill ()
+				     };
+		win.Add (label);
+		Application.Top.Add (win);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+
+		Assert.Equal (5, text.Length);
+		Assert.True (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 5, 1),        label.Frame);
+		Assert.Equal (new Size (5, 1),              label.TextFormatter.Size);
+		Assert.Equal (new List<string> { "Label" }, label.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 10, 4),       win.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 4),       Application.Top.Frame);
+		var expected = @"
+┌────────┐
+│Label   │
+│        │
+└────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		label.Width = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 5, 1), label.Frame);
+		Assert.Equal (new Size (5, 1),       label.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Single (label.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌────────┐
+│Label   │
+│        │
+└────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_View_IsEmpty_False_Minimum_Width ()
+	{
+		var text = "Views";
+		var view = new View {
+					    TextDirection = TextDirection.TopBottom_LeftRight,
+					    Height        = Dim.Fill () - text.Length,
+					    Text          = text,
+					    AutoSize      = true
+				    };
+		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 (4, 10);
+
+		Assert.Equal (5, text.Length);
+		Assert.True (view.AutoSize);
+		Assert.Equal (new Rect (0, 0, 1, 5),        view.Frame);
+		Assert.Equal (new Size (1, 5),              view.TextFormatter.Size);
+		Assert.Equal (new List<string> { "Views" }, view.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 4, 10),       win.Frame);
+		Assert.Equal (new Rect (0, 0, 4, 10),       Application.Top.Frame);
+		var expected = @"
+┌──┐
+│V │
+│i │
+│e │
+│w │
+│s │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		view.Height = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 1, 5), view.Frame);
+		Assert.Equal (new Size (1, 5),       view.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Single (view.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌──┐
+│V │
+│i │
+│e │
+│w │
+│s │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_View_IsEmpty_False_Minimum_Width_Wide_Rune ()
+	{
+		var text = "界View";
+		var view = new View {
+					    TextDirection = TextDirection.TopBottom_LeftRight,
+					    Height        = Dim.Fill () - text.Length,
+					    Text          = text,
+					    AutoSize      = true
+				    };
+		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 (4, 10);
+
+		Assert.Equal (5, text.Length);
+		Assert.True (view.AutoSize);
+		Assert.Equal (new Rect (0, 0, 2, 5),        view.Frame);
+		Assert.Equal (new Size (2, 5),              view.TextFormatter.Size);
+		Assert.Equal (new List<string> { "界View" }, view.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 4, 10),       win.Frame);
+		Assert.Equal (new Rect (0, 0, 4, 10),       Application.Top.Frame);
+		var expected = @"
+┌──┐
+│界│
+│V │
+│i │
+│e │
+│w │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		view.Height = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 2, 5), view.Frame);
+		Assert.Equal (new Size (2, 5),       view.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Equal (new List<string> { "界View" }, view.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌──┐
+│界│
+│V │
+│i │
+│e │
+│w │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+	}
+
+	[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);
+		label1.Dispose ();
+		label2.Dispose ();
+		label3.Dispose ();
+	}
+
+	[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);
+		label1.Dispose ();
+		label2.Dispose ();
+		label3.Dispose ();
+	}
+
+	[Fact]
+	public void AutoSize_True_ResizeView_With_Dim_Absolute ()
+	{
+		var super = new View ();
+		var label = new Label ();
+
+		label.Text = "New text";
+		// BUGBUG: v2 - label was never added to super, so it was never laid out.
+		super.Add (label);
+		super.LayoutSubviews ();
+
+		Assert.True (label.AutoSize);
+		Assert.Equal ("(0,0,8,1)", label.Bounds.ToString ());
+		super.Dispose ();
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Setting_With_Height_Horizontal ()
+	{
+		var label = new Label ("Hello") { Width = 10, Height = 2, ValidatePosDim = true };
+		var viewX = new View ("X") { X          = Pos.Right (label) };
+		var viewY = new View ("Y") { Y          = Pos.Bottom (label) };
+
+		Application.Top.Add (label, viewX, viewY);
+		var rs = Application.Begin (Application.Top);
+
+		Assert.True (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
+
+		var expected = @"
+Hello     X
+           
+Y          
+"
+			;
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 11, 3), pos);
+
+		label.AutoSize = false;
+		Application.Refresh ();
+
+		Assert.False (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 2), label.Frame);
+
+		expected = @"
+Hello     X
+           
+Y          
+"
+			;
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 11, 3), pos);
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Setting_With_Height_Vertical ()
+	{
+		var label = new Label { Width  = 2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight, ValidatePosDim = true };
+		var viewX = new View ("X") { X = Pos.Right (label) };
+		var viewY = new View ("Y") { Y = Pos.Bottom (label) };
+
+		Application.Top.Add (label, viewX, viewY);
+		var rs = Application.Begin (Application.Top);
+
+		Assert.True (label.AutoSize);
+		label.Text = "Hello";
+		Application.Refresh ();
+
+		// #3127: Label.Text is "Hello" - It's Vertical. So the width should be 2 (honoring Width = 2)
+		// and the height is should be 10 (because 10 is greater than length of Hello).
+		Assert.Equal (new Rect (0, 0, 2, 10), label.Frame);
+
+		var expected = @"
+H X
+e  
+l  
+l  
+o  
+   
+   
+   
+   
+   
+Y  
+";
+
+		var pos = TestHelpers.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);
+
+		expected = @"
+H X
+e  
+l  
+l  
+o  
+   
+   
+   
+   
+   
+Y  
+"
+			;
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 3, 11), pos);
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void Excess_Text_Is_Erased_When_The_Width_Is_Reduced ()
+	{
+		var lbl = new Label ("123");
+		Application.Top.Add (lbl);
+		var rs = Application.Begin (Application.Top);
+
+		Assert.True (lbl.AutoSize);
+		Assert.Equal ("123 ", GetContents ());
+
+		lbl.Text = "12";
+		// Here the AutoSize ensuring the right size with width 3 (Dim.Absolute)
+		// that was set on the OnAdded method with the text length of 3
+		// and height 1 because wasn't set and the text has 1 line
+		Assert.Equal (new Rect (0, 0, 3, 1), lbl.Frame);
+		Assert.Equal (new Rect (0, 0, 3, 1), lbl._needsDisplayRect);
+		Assert.Equal (new Rect (0, 0, 0, 0), lbl.SuperView._needsDisplayRect);
+		Assert.True (lbl.SuperView.LayoutNeeded);
+		lbl.SuperView.Draw ();
+		Assert.Equal ("12  ", GetContents ());
+
+		string GetContents ()
+		{
+			var text = "";
+			for (var i = 0; i < 4; i++) {
+				text += Application.Driver.Contents [0, i].Rune;
+			}
+			return text;
+		}
+		Application.End (rs);
+	}
+
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Equal_Before_And_After_IsInitialized_With_Different_Orders ()
+	{
+		var view1 = new View { Text     = "Say Hello view1 你", AutoSize = true, Width = 10, Height  = 5, ValidatePosDim                   = true };
+		var view2 = new View { Text     = "Say Hello view2 你", Width    = 10, Height  = 5, AutoSize = true, ValidatePosDim                = true };
+		var view3 = new View { AutoSize = true, Width                   = 10, Height  = 5, Text     = "Say Hello view3 你", ValidatePosDim = true };
+		var view4 = new View {
+					     Text           = "Say Hello view4 你",
+					     AutoSize       = true,
+					     Width          = 10,
+					     Height         = 5,
+					     TextDirection  = TextDirection.TopBottom_LeftRight,
+					     ValidatePosDim = true
+		};
+		var view5 = new View {
+					     Text           = "Say Hello view5 你",
+					     Width          = 10,
+					     Height         = 5,
+					     AutoSize       = true,
+					     TextDirection  = TextDirection.TopBottom_LeftRight,
+					     ValidatePosDim = true
+		};
+		var view6 = new View {
+					     AutoSize       = true,
+					     Width          = 10,
+					     Height         = 5,
+					     TextDirection  = TextDirection.TopBottom_LeftRight,
+					     Text           = "Say Hello view6 你",
+					     ValidatePosDim = true
+		};
+		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 ("Absolute(18)",         view1.Width.ToString ());
+		Assert.Equal ("Absolute(5)",          view1.Height.ToString ());
+		Assert.True (view2.AutoSize);
+		Assert.Equal ("Say Hello view2 你".GetColumns (), view2.Width);
+		Assert.Equal (18,                                view2.Width);
+		Assert.Equal (new Rect (0, 0, 18, 5),            view2.Frame);
+		Assert.Equal ("Absolute(18)",                    view2.Width.ToString ());
+		Assert.Equal ("Absolute(5)",                     view2.Height.ToString ());
+		Assert.True (view3.AutoSize);
+		Assert.Equal (new Rect (0, 0, 18, 5), view3.Frame);
+		Assert.Equal ("Absolute(18)",         view2.Width.ToString ());
+		Assert.Equal ("Absolute(5)",          view3.Height.ToString ());
+		Assert.True (view4.AutoSize);
+
+		Assert.Equal ("Say Hello view4 你".GetColumns (), view2.Width);
+		Assert.Equal (18,                                view2.Width);
+
+		Assert.Equal (new Rect (0, 0, 18, 17), view4.Frame);
+		Assert.Equal ("Absolute(18)",          view4.Width.ToString ());
+		Assert.Equal ("Absolute(17)",          view4.Height.ToString ());
+		Assert.True (view5.AutoSize);
+		Assert.Equal (new Rect (0, 0, 18, 17), view5.Frame);
+		Assert.True (view6.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 17), view6.Frame);
+
+		var rs = 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 ("Absolute(18)",         view1.Width.ToString ());
+		Assert.Equal ("Absolute(5)",          view1.Height.ToString ());
+		Assert.True (view2.AutoSize);
+
+		Assert.Equal (new Rect (0, 0, 18, 5), view2.Frame);
+		Assert.Equal ("Absolute(18)",         view2.Width.ToString ());
+		Assert.Equal ("Absolute(5)",          view2.Height.ToString ());
+		Assert.True (view3.AutoSize);
+		Assert.Equal (new Rect (0, 0, 18, 5), view3.Frame);
+		Assert.Equal ("Absolute(18)",         view5.Width.ToString ());
+		Assert.Equal ("Absolute(5)",          view3.Height.ToString ());
+		Assert.True (view4.AutoSize);
+		Assert.Equal (new Rect (0, 0, 18, 17), view4.Frame);
+		Assert.Equal ("Absolute(18)",          view5.Width.ToString ());
+		Assert.Equal ("Absolute(17)",           view4.Height.ToString ());
+		Assert.True (view5.AutoSize);
+		Assert.Equal (new Rect (0, 0, 18, 17), view5.Frame);
+		Assert.Equal ("Absolute(18)",           view5.Width.ToString ());
+		Assert.Equal ("Absolute(17)",           view5.Height.ToString ());
+		Assert.True (view6.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 17), view6.Frame);
+		Assert.Equal ("Absolute(10)",          view6.Width.ToString ());
+		Assert.Equal ("Absolute(17)",          view6.Height.ToString ());
+		Application.End (rs);
+	}
+
+	[Fact]
+	public void SetRelativeLayout_Respects_AutoSize ()
+	{
+		var view = new View (new Rect (0, 0, 10, 0)) {
+								     AutoSize = true
+							     };
+		view.Text = "01234567890123456789";
+
+		Assert.True (view.AutoSize);
+		Assert.Equal (LayoutStyle.Absolute,   view.LayoutStyle);
+		Assert.Equal (new Rect (0, 0, 20, 1), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(20)",         view.Width.ToString ());
+		Assert.Equal ("Absolute(1)",          view.Height.ToString ());
+
+		view.SetRelativeLayout (new Rect (0, 0, 25, 5));
+
+		Assert.True (view.AutoSize);
+		Assert.Equal (LayoutStyle.Absolute,   view.LayoutStyle);
+		Assert.Equal (new Rect (0, 0, 20, 1), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(20)",         view.Width.ToString ());
+		Assert.Equal ("Absolute(1)",          view.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 viewTopBottom_LeftRight = new View (new Rect (0, 0, 0, 10)) {
+											Text          = "Say Hello view2 你",
+											AutoSize      = true,
+											TextDirection = TextDirection.TopBottom_LeftRight
+										};
+		Application.Top.Add (view1, viewTopBottom_LeftRight);
+
+		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 ("Absolute(0)",          view1.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view1.Y.ToString ());
+		Assert.Equal ("Absolute(18)",         view1.Width.ToString ());
+		Assert.Equal ("Absolute(1)",          view1.Height.ToString ());
+
+		Assert.True (viewTopBottom_LeftRight.AutoSize);
+		Assert.Equal (LayoutStyle.Absolute,    viewTopBottom_LeftRight.LayoutStyle);
+		Assert.Equal (new Rect (0, 0, 18, 17), viewTopBottom_LeftRight.Frame);
+		Assert.Equal ("Absolute(0)",           viewTopBottom_LeftRight.X.ToString ());
+		Assert.Equal ("Absolute(0)",           viewTopBottom_LeftRight.Y.ToString ());
+		Assert.Equal ("Absolute(18)",          viewTopBottom_LeftRight.Width.ToString ());
+		Assert.Equal ("Absolute(17)",          viewTopBottom_LeftRight.Height.ToString ());
+
+		view1.Frame = new Rect (0, 0, 25, 4);
+		var firstIteration = false;
+		Application.RunIteration (ref rs, ref firstIteration);
+
+		Assert.True (view1.AutoSize);
+		Assert.Equal (LayoutStyle.Absolute,   view1.LayoutStyle);
+		Assert.Equal (new Rect (0, 0, 25, 4), view1.Frame);
+		Assert.Equal ("Absolute(0)",          view1.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view1.Y.ToString ());
+		Assert.Equal ("Absolute(25)",         view1.Width.ToString ());
+		Assert.Equal ("Absolute(4)",          view1.Height.ToString ());
+
+		viewTopBottom_LeftRight.Frame = new Rect (0, 0, 1, 25);
+		Application.RunIteration (ref rs, ref firstIteration);
+
+		Assert.True (viewTopBottom_LeftRight.AutoSize);
+		Assert.Equal (LayoutStyle.Absolute,   viewTopBottom_LeftRight.LayoutStyle);
+		Assert.Equal (new Rect (0, 0, 2, 25), viewTopBottom_LeftRight.Frame);
+		Assert.Equal ("Absolute(0)",          viewTopBottom_LeftRight.X.ToString ());
+		Assert.Equal ("Absolute(0)",          viewTopBottom_LeftRight.Y.ToString ());
+		Assert.Equal ("Absolute(2)",          viewTopBottom_LeftRight.Width.ToString ());
+		Assert.Equal ("Absolute(25)",         viewTopBottom_LeftRight.Height.ToString ());
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_Stays_True_Center_HotKeySpecifier ()
+	{
+		var label = new Label {
+					      X    = Pos.Center (),
+					      Y    = Pos.Center (),
+					      Text = "Say Hello 你"
+				      };
+
+		var win = new Window {
+					     Width  = Dim.Fill (),
+					     Height = Dim.Fill (),
+					     Title  = "Test Demo 你"
+				     };
+		win.Add (label);
+		Application.Top.Add (win);
+
+		Assert.True (label.AutoSize);
+
+		var rs = Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (30, 5);
+		var expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│        Say Hello 你        │
+│                            │
+└────────────────────────────┘
+";
+
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		Assert.True (label.AutoSize);
+		label.Text = "Say Hello 你 changed";
+		Assert.True (label.AutoSize);
+		Application.Refresh ();
+		expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│    Say Hello 你 changed    │
+│                            │
+└────────────────────────────┘
+";
+
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_Dim_Add_Operator_With_Text ()
+	{
+		var top = Application.Top;
+
+		var view = new View ("View with long text") { X = 0, Y = 0, Width                 = 20, Height = 1 };
+		var field = new TextField { X                   = 0, Y = Pos.Bottom (view), Width = 20 };
+		var count = 0;
+		// Label is AutoSize == true
+		var listLabels = new List<Label> ();
+
+		field.KeyDown += (s, k) => {
+			if (k.KeyCode == KeyCode.Enter) {
+				((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
+				var pos = TestHelpers.AssertDriverContentsWithFrameAre (expecteds [count], _output);
+				Assert.Equal (new Rect (0, 0, 22, count + 4), pos);
+
+				if (count < 20) {
+					field.Text = $"Label {count}";
+					// Label is AutoSize = true
+					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 ($"Absolute({count + 1})", label.Y.ToString ());
+					listLabels.Add (label);
+					//if (count == 0) {
+					//	Assert.Equal ($"Absolute({count})", view.Height.ToString ());
+					//	view.Height += 2;
+					//} else {
+					Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
+					view.Height += 1;
+					//}
+					count++;
+				}
+				Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
+			}
+		};
+
+		Application.Iteration += (s, a) => {
+			while (count < 21) {
+				field.NewKeyDownEvent (new Key (KeyCode.Enter));
+				if (count == 20) {
+					field.NewKeyDownEvent (new Key (KeyCode.Enter));
+					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);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_Dim_Subtract_Operator_With_Text ()
+	{
+		var top = Application.Top;
+		var view = new View ("View with long text") { X = 0, Y = 0, Width                 = 20, Height = 1 };
+		var field = new TextField { X                   = 0, Y = Pos.Bottom (view), Width = 20 };
+		var count = 20;
+		// Label is AutoSize == true
+		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 = i + 1, Width = 10 };
+			view.Add (label);
+			Assert.Equal ($"Label {i}", label.Text);
+			Assert.Equal ($"Absolute({i + 1})", label.Y.ToString ());
+			listLabels.Add (label);
+
+			if (i == 0) {
+				Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
+				view.Height += 1;
+				Assert.Equal ($"Absolute({i + 2})", view.Height.ToString ());
+			} else {
+				Assert.Equal ($"Absolute({i + 1})", view.Height.ToString ());
+				view.Height += 1;
+				Assert.Equal ($"Absolute({i + 2})", view.Height.ToString ());
+			}
+		}
+
+		field.KeyDown += (s, k) => {
+			if (k.KeyCode == KeyCode.Enter) {
+				((FakeDriver)Application.Driver).SetBufferSize (22, count + 4);
+				var pos = TestHelpers.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 [count - 1].Dispose ();
+					listLabels.RemoveAt (count - 1);
+					Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
+					view.Height -= 1;
+					count--;
+					if (listLabels.Count > 0) {
+						field.Text = listLabels [count - 1].Text;
+					} else {
+						field.Text = string.Empty;
+					}
+				}
+				Assert.Equal ($"Absolute({count + 1})", view.Height.ToString ());
+			}
+		};
+
+		Application.Iteration += (s, a) => {
+			while (count > -1) {
+				field.NewKeyDownEvent (new Key (KeyCode.Enter));
+				if (count == 0) {
+					field.NewKeyDownEvent (new Key (KeyCode.Enter));
+					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);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_AnchorEnd_Better_Than_Bottom_Equal_Inside_Window ()
+	{
+		var win = new Window ();
+
+		// Label is AutoSize == true
+		var label = new Label ("This should be the last line.") {
+										ColorScheme = Colors.Menu,
+										Width       = Dim.Fill (),
+										X           = 0, // keep unit test focused; don't use Center here
+										Y           = Pos.AnchorEnd (1)
+									};
+
+		win.Add (label);
+
+		var top = Application.Top;
+		top.Add (win);
+		var rs = 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 (0, 7, 38, 1),  label.Frame);
+		var expected = @"
+┌──────────────────────────────────────┐
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│This should be the last line.         │
+└──────────────────────────────────────┘
+";
+
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_Bottom_Equal_Inside_Window ()
+	{
+		var win = new Window ();
+
+		// Label is AutoSize == true
+		var label = new Label ("This should be the last line.") {
+										ColorScheme = Colors.Menu,
+										Width       = Dim.Fill (),
+										X           = 0,
+										Y           = Pos.Bottom (win) - 3 // two lines top and bottom borders more one line above the bottom border
+									};
+
+		win.Add (label);
+
+		var top = Application.Top;
+		top.Add (win);
+		var rs = 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 (0, 7, 38, 1),  label.Frame);
+		var expected = @"
+┌──────────────────────────────────────┐
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│                                      │
+│This should be the last line.         │
+└──────────────────────────────────────┘
+";
+
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_Bottom_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
+	{
+		var win = new Window ();
+
+		// Label is AutoSize == true
+		var label = new Label ("This should be the last line.") {
+										ColorScheme = Colors.Menu,
+										Width       = Dim.Fill (),
+										X           = 0,
+										Y           = Pos.Bottom (win) - 4 // two lines top and bottom borders more two lines above border
+									};
+
+		win.Add (label);
+
+		var menu = new MenuBar (new MenuBarItem [] { new ("Menu", "", null) });
+		var status = new StatusBar (new StatusItem [] { new (KeyCode.F1, "~F1~ Help", null) });
+		var top = Application.Top;
+		top.Add (win, menu, status);
+		var rs = 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 (0, 20, 78, 1),  label.Frame);
+		var expected = @"
+ Menu                                                                           
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│This should be the last line.                                                 │
+└──────────────────────────────────────────────────────────────────────────────┘
+ F1 Help                                                                        
+";
+
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_AnchorEnd_Better_Than_Bottom_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
+	{
+		var win = new Window ();
+
+		// Label is AutoSize == true
+		var label = new Label ("This should be the last line.") {
+										ColorScheme = Colors.Menu,
+										Width       = Dim.Fill (),
+										X           = 0,
+										Y           = Pos.AnchorEnd (1)
+									};
+
+		win.Add (label);
+
+		var menu = new MenuBar (new MenuBarItem [] { new ("Menu", "", null) });
+		var status = new StatusBar (new StatusItem [] { new (KeyCode.F1, "~F1~ Help", null) });
+		var top = Application.Top;
+		top.Add (win, menu, status);
+		var rs = 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 (0, 20, 78, 1),  label.Frame);
+		var expected = @"
+ Menu                                                                           
+┌──────────────────────────────────────────────────────────────────────────────┐
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│                                                                              │
+│This should be the last line.                                                 │
+└──────────────────────────────────────────────────────────────────────────────┘
+ F1 Help                                                                        
+";
+
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_TextDirection_Toggle ()
+	{
+		var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
+		// View is AutoSize == true
+		var view = new View ();
+		win.Add (view);
+		Application.Top.Add (win);
+
+		var rs = Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (15, 15);
+
+		Assert.Equal (new Rect (0, 0, 15, 15), win.Frame);
+		Assert.Equal (new Rect (0, 0, 15, 15), win.Margin.Frame);
+		Assert.Equal (new Rect (0, 0, 15, 15), win.Border.Frame);
+		Assert.Equal (new Rect (1, 1, 13, 13), win.Padding.Frame);
+		Assert.False (view.AutoSize);
+		Assert.Equal (TextDirection.LeftRight_TopBottom, view.TextDirection);
+		Assert.Equal (Rect.Empty,                        view.Frame);
+		Assert.Equal ("Absolute(0)",                     view.X.ToString ());
+		Assert.Equal ("Absolute(0)",                     view.Y.ToString ());
+		Assert.Equal ("Absolute(0)",                     view.Width.ToString ());
+		Assert.Equal ("Absolute(0)",                     view.Height.ToString ());
+		var expected = @"
+┌─────────────┐
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		view.Text   = "Hello World";
+		view.Width  = 11;
+		view.Height = 1;
+		win.LayoutSubviews ();
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 11, 1), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(11)",         view.Width.ToString ());
+		Assert.Equal ("Absolute(1)",          view.Height.ToString ());
+		expected = @"
+┌─────────────┐
+│Hello World  │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		view.AutoSize = true;
+		view.Text     = "Hello Worlds";
+		Application.Refresh ();
+		var len = "Hello Worlds".Length;
+		Assert.Equal (12,                      len);
+		Assert.Equal (new Rect (0, 0, len, 1), view.Frame);
+		Assert.Equal ("Absolute(0)",           view.X.ToString ());
+		Assert.Equal ("Absolute(0)",           view.Y.ToString ());
+		Assert.Equal ("Absolute(12)",          view.Width.ToString ());
+		Assert.Equal ("Absolute(1)",           view.Height.ToString ());
+		expected = @"
+┌─────────────┐
+│Hello Worlds │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		view.TextDirection = TextDirection.TopBottom_LeftRight;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 12, 12), view.Frame);
+		Assert.Equal ("Absolute(0)",           view.X.ToString ());
+		Assert.Equal ("Absolute(0)",           view.Y.ToString ());
+		Assert.Equal ("Absolute(12)",          view.Width.ToString ());
+		Assert.Equal ("Absolute(12)",          view.Height.ToString ());
+		expected = @"
+┌─────────────┐
+│H            │
+│e            │
+│l            │
+│l            │
+│o            │
+│             │
+│W            │
+│o            │
+│r            │
+│l            │
+│d            │
+│s            │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		view.AutoSize = false;
+		view.Height   = 1;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 12, 1), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(12)",         view.Width.ToString ());
+		Assert.Equal ("Absolute(1)",          view.Height.ToString ());
+		// TextDirection.TopBottom_LeftRight - Height of 1 and Width of 12 means 
+		// that the text will be spread "vertically" across 1 line.
+		// Hence no space.
+		expected = @"
+┌─────────────┐
+│HelloWorlds  │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		view.PreserveTrailingSpaces = true;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 12, 1), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(12)",         view.Width.ToString ());
+		Assert.Equal ("Absolute(1)",          view.Height.ToString ());
+		expected = @"
+┌─────────────┐
+│Hello Worlds │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		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, 12), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(1)",          view.Width.ToString ());
+		Assert.Equal ("Absolute(12)",         view.Height.ToString ());
+		expected = @"
+┌─────────────┐
+│H            │
+│e            │
+│l            │
+│l            │
+│o            │
+│             │
+│W            │
+│o            │
+│r            │
+│l            │
+│d            │
+│s            │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		view.AutoSize = true;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 1, 12), view.Frame);
+		Assert.Equal ("Absolute(0)",          view.X.ToString ());
+		Assert.Equal ("Absolute(0)",          view.Y.ToString ());
+		Assert.Equal ("Absolute(1)",          view.Width.ToString ());
+		Assert.Equal ("Absolute(12)",         view.Height.ToString ());
+		expected = @"
+┌─────────────┐
+│H            │
+│e            │
+│l            │
+│l            │
+│o            │
+│             │
+│W            │
+│o            │
+│r            │
+│l            │
+│d            │
+│s            │
+│             │
+└─────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Width_Height_Stay_True_If_TextFormatter_Size_Fit ()
+	{
+		var text = "Fi_nish 終";
+		var horizontalView = new View {
+						      Id              = "horizontalView",
+						      AutoSize        = true,
+						      HotKeySpecifier = (Rune)'_',
+						      Text            = text
+					      };
+		var verticalView = new View {
+						    Id              = "verticalView",
+						    Y               = 3,
+						    AutoSize        = true,
+						    HotKeySpecifier = (Rune)'_',
+						    Text            = text,
+						    TextDirection   = TextDirection.TopBottom_LeftRight
+					    };
+		var win = new Window {
+					     Id     = "win",
+					     Width  = Dim.Fill (),
+					     Height = Dim.Fill (),
+					     Text   = "Window"
+				     };
+		win.Add (horizontalView, verticalView);
+		Application.Top.Add (win);
+		var rs = Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (22, 22);
+
+		Assert.True (horizontalView.AutoSize);
+		Assert.True (verticalView.AutoSize);
+		Assert.Equal (new Size (text.GetColumns (), 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 ("Absolute(0)",                    horizontalView.X.ToString ());
+		Assert.Equal ("Absolute(0)",                    horizontalView.Y.ToString ());
+
+		// BUGBUG - v2 - With v1 AutoSize = true Width/Height should always grow or keep initial value, 
+
+		Assert.Equal ("Absolute(9)",         horizontalView.Width.ToString ());
+		Assert.Equal ("Absolute(1)",         horizontalView.Height.ToString ());
+		Assert.Equal (new Rect (0, 3, 9, 8), verticalView.Frame);
+		Assert.Equal ("Absolute(0)",         verticalView.X.ToString ());
+		Assert.Equal ("Absolute(3)",         verticalView.Y.ToString ());
+		Assert.Equal ("Absolute(9)",         verticalView.Width.ToString ());
+		Assert.Equal ("Absolute(8)",         verticalView.Height.ToString ());
+		var expected = @"
+┌────────────────────┐
+│Finish 終           │
+│                    │
+│                    │
+│F                   │
+│i                   │
+│n                   │
+│i                   │
+│s                   │
+│h                   │
+│                    │
+│終                  │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+		verticalView.Text = "最初_の行二行目";
+		Application.Top.Draw ();
+		Assert.True (horizontalView.AutoSize);
+		Assert.True (verticalView.AutoSize);
+		// height was initialized with 8 and can only grow or keep initial value
+		Assert.Equal (new Rect (0, 3, 9, 8), verticalView.Frame);
+		Assert.Equal ("Absolute(0)",         verticalView.X.ToString ());
+		Assert.Equal ("Absolute(3)",         verticalView.Y.ToString ());
+		Assert.Equal ("Absolute(9)",         verticalView.Width.ToString ());
+		Assert.Equal ("Absolute(8)",         verticalView.Height.ToString ());
+		expected = @"
+┌────────────────────┐
+│Finish 終           │
+│                    │
+│                    │
+│最                  │
+│初                  │
+│の                  │
+│行                  │
+│二                  │
+│行                  │
+│目                  │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+│                    │
+└────────────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 22, 22), pos);
+		Application.End (rs);
+	}
+
+
+	[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, 50));
+		var label = new Label { Width = Dim.Fill () };
+		win.Add (label);
+		Application.Top.Add (win);
+
+		Assert.True (label.IsAdded);
+
+		Assert.True (label.AutoSize);
+
+		// #3127: Before: 
+		//		 Text is empty but height=1 by default, see Label view
+		//		 BUGBUG: LayoutSubviews has not been called, so this test is not really valid (pos/dim are indeterminate, not 0)
+		//		 Not really a bug because View call OnResizeNeeded method on the SetInitialProperties method
+		// #3127: After: Text is empty Width=Dim.Fill is honored. 
+		//        LayoutSubViews has not been called, and OnResizeNeeded ends up using Application.Top.Bounds
+		//        Which has a width of 80.
+		Assert.Equal ("(0,0,80,1)", label.Bounds.ToString ());
+
+		label.Text = "First line\nSecond line";
+		Application.Top.LayoutSubviews ();
+
+		Assert.True (label.AutoSize);
+		Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ());
+		Assert.False (label.IsInitialized);
+
+		var rs = Application.Begin (Application.Top);
+
+		Assert.True (label.AutoSize);
+		Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ());
+		Assert.True (label.IsInitialized);
+
+		label.AutoSize = false;
+
+		// Width should still be Dim.Fill
+		Assert.Equal ("Fill(0)", label.Width.ToString ());
+
+		// Height should be 2
+		Assert.Equal ("Absolute(2)", label.Height.ToString ());
+		Assert.Equal (2,             label.Frame.Height);
+
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ());
+		Application.End (rs);
+	}
+
+	[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 but height=1 by default. 
+		Assert.True (label.AutoSize);
+		Assert.Equal ("(0,0,80,1)", label.Bounds.ToString ());
+
+		var rs = Application.Begin (Application.Top);
+
+		Assert.True (label.AutoSize);
+		// Here the AutoSize ensuring the right size with width 28 (Dim.Fill)
+		// and height 0 because wasn't set and the text is empty
+		// BUGBUG: Because of #2450, this test is bogus: pos/dim is indeterminate!
+		//Assert.Equal ("(0,0,28,0)", label.Bounds.ToString ());
+
+		label.Text = "First line\nSecond line";
+		Application.Refresh ();
+
+		// Here the AutoSize ensuring the right size with width 28 (Dim.Fill)
+		// and height 2 because wasn't set and the text has 2 lines
+		Assert.True (label.AutoSize);
+		Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ());
+
+		label.AutoSize = false;
+		Application.Refresh ();
+
+		// Here the SetMinWidthHeight ensuring the minimum height
+		// #3127: After: (0,0,28,2) because turning off AutoSize leaves
+		// Height set to 2.
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ());
+
+		label.Text = "First changed line\nSecond changed line\nNew line";
+		Application.Refresh ();
+
+		// Here the AutoSize is false and the width 28 (Dim.Fill) and
+		// #3127: Before: height 1 because it wasn't set and SetMinWidthHeight ensuring the minimum height
+		// #3127: After: (0,0,28,2) because setting Text leaves Height set to 2..
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,28,2)", label.Bounds.ToString ());
+
+		label.AutoSize = true;
+		Application.Refresh ();
+
+		// Here the AutoSize ensuring the right size with width 19 (width of longest line)
+		// and height 3 because the text has 3 lines
+		Assert.True (label.AutoSize);
+		Assert.Equal ("(0,0,19,3)", label.Bounds.ToString ());
+
+		Application.End (rs);
+	}
+
+
+	//	[Fact]
+	//	[AutoInitShutdown]
+	//	public void AutoSize_False_TextDirection_Toggle ()
+	//	{
+	//		var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
+	//		// View is AutoSize == true
+	//		var view = new View ();
+	//		win.Add (view);
+	//		Application.Top.Add (win);
+
+	//		var rs = Application.Begin (Application.Top);
+	//		((FakeDriver)Application.Driver).SetBufferSize (22, 22);
+
+	//		Assert.Equal (new Rect (0, 0, 22, 22), win.Frame);
+	//		Assert.Equal (new Rect (0, 0, 22, 22), win.Margin.Frame);
+	//		Assert.Equal (new Rect (0, 0, 22, 22), win.Border.Frame);
+	//		Assert.Equal (new Rect (1, 1, 20, 20), win.Padding.Frame);
+	//		Assert.False (view.AutoSize);
+	//		Assert.Equal (TextDirection.LeftRight_TopBottom, view.TextDirection);
+	//		Assert.Equal (Rect.Empty, view.Frame);
+	//		Assert.Equal ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Height.ToString ());
+	//		var expected = @"
+	//┌────────────────────┐
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+	//		Assert.Equal (new Rect (0, 0, 22, 22), pos);
+
+	//		view.Text = "Hello World";
+	//		view.Width = 11;
+	//		view.Height = 1;
+	//		win.LayoutSubviews ();
+	//		Application.Refresh ();
+
+	//		Assert.Equal (new Rect (0, 0, 11, 1), view.Frame);
+	//		Assert.Equal ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(11)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│Hello World         │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.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 ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(11)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│Hello Worlds        │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.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 ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(11)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│H                   │
+	//│e                   │
+	//│l                   │
+	//│l                   │
+	//│o                   │
+	//│                    │
+	//│W                   │
+	//│o                   │
+	//│r                   │
+	//│l                   │
+	//│d                   │
+	//│s                   │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.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 ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(11)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│HelloWorlds         │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.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 ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(11)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│Hello World         │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.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 ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(11)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│H                   │
+	//│e                   │
+	//│l                   │
+	//│l                   │
+	//│o                   │
+	//│                    │
+	//│W                   │
+	//│o                   │
+	//│r                   │
+	//│l                   │
+	//│d                   │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.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 ("Absolute(0)", view.X.ToString ());
+	//		Assert.Equal ("Absolute(0)", view.Y.ToString ());
+	//		Assert.Equal ("Absolute(1)", view.Width.ToString ());
+	//		Assert.Equal ("Absolute(12)", view.Height.ToString ());
+	//		expected = @"
+	//┌────────────────────┐
+	//│H                   │
+	//│e                   │
+	//│l                   │
+	//│l                   │
+	//│o                   │
+	//│                    │
+	//│W                   │
+	//│o                   │
+	//│r                   │
+	//│l                   │
+	//│d                   │
+	//│s                   │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//│                    │
+	//└────────────────────┘
+	//";
+
+	//		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+	//		Assert.Equal (new Rect (0, 0, 22, 22), pos);
+	//		Application.End (rs);
+	//	}
+
+
+	[Fact] [AutoInitShutdown]
+	public void GetTextFormatterBoundsSize_GetSizeNeededForText_HotKeySpecifier ()
+	{
+		var text = "Say Hello 你";
+
+		// Frame: 0, 0, 12, 1
+		var horizontalView = new View {
+						      AutoSize        = true,
+						      HotKeySpecifier = (Rune)'_'
+					      };
+		horizontalView.Text = text;
+
+		// Frame: 0, 0, 1, 12
+		var verticalView = new View {
+						    AutoSize        = true,
+						    HotKeySpecifier = (Rune)'_',
+						    TextDirection   = TextDirection.TopBottom_LeftRight
+					    };
+		verticalView.Text = text;
+
+		Application.Top.Add (horizontalView, verticalView);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (50, 50);
+
+		Assert.True (horizontalView.AutoSize);
+		Assert.Equal (new Rect (0, 0, 12, 1),    horizontalView.Frame);
+		Assert.Equal (new Size (12, 1),          horizontalView.GetSizeNeededForTextWithoutHotKey ());
+		Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ());
+
+		Assert.True (verticalView.AutoSize);
+		Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame);
+		Assert.Equal (new Size (2, 11),       verticalView.GetSizeNeededForTextWithoutHotKey ());
+		Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ());
+
+		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.GetSizeNeededForTextWithoutHotKey ());
+		Assert.Equal (horizontalView.Frame.Size, horizontalView.GetSizeNeededForTextWithoutHotKey ());
+
+		Assert.True (verticalView.AutoSize);
+		Assert.Equal (new Rect (0, 0, 2, 11), verticalView.Frame);
+		Assert.Equal (new Size (2, 11), verticalView.GetSizeNeededForTextWithoutHotKey ());
+		Assert.Equal (verticalView.Frame.Size, verticalView.GetSizeNeededForTextWithoutHotKey ());
+	}
+
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_True_Width_Height_SetMinWidthHeight_Narrow_Wide_Runes ()
+	{
+		var text = $"First line{Environment.NewLine}Second line";
+		var horizontalView = new View {
+						      AutoSize = true,
+						      Width    = 20,
+						      Height   = 1,
+						      Text     = text
+					      };
+		var verticalView = new View {
+						    AutoSize      = true,
+						    Y             = 3,
+						    Height        = 20,
+						    Width         = 1,
+						    Text          = text,
+						    TextDirection = TextDirection.TopBottom_LeftRight
+					    };
+		var win = new Window {
+					     AutoSize = true,
+					     Width    = Dim.Fill (),
+					     Height   = Dim.Fill (),
+					     Text     = "Window"
+				     };
+		win.Add (horizontalView, verticalView);
+		Application.Top.Add (win);
+		var rs = Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (20, 20);
+
+		Assert.True (horizontalView.AutoSize);
+		Assert.True (verticalView.AutoSize);
+		Assert.Equal (new Rect (0, 0, 20, 2),  horizontalView.Frame);
+		Assert.Equal (new Rect (0, 3, 11,  20), verticalView.Frame);
+		var expected = @"
+┌──────────────────┐
+│First line        │
+│Second line       │
+│                  │
+│FS                │
+│ie                │
+│rc                │
+│so                │
+│tn                │
+│ d                │
+│l                 │
+│il                │
+│ni                │
+│en                │
+│ e                │
+│                  │
+│                  │
+│                  │
+│                  │
+└──────────────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		verticalView.Text = $"最初の行{Environment.NewLine}二行目";
+		Application.Top.Draw ();
+		Assert.Equal (new Rect (0, 3, 11, 20), verticalView.Frame);
+		expected = @"
+┌──────────────────┐
+│First line        │
+│Second line       │
+│                  │
+│最二              │
+│初行              │
+│の目              │
+│行                │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+│                  │
+└──────────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+
+	[Theory, AutoInitShutdown]
+	[InlineData (true)]
+	[InlineData (false)]
+	public void View_Draw_Horizontal_Simple_TextAlignments (bool autoSize)
+	{
+		var text = "Hello World";
+		var width = 20;
+		var lblLeft = new View (text) { Width = width, AutoSize = autoSize };
+		var lblCenter = new View (text) { Y = 1, Width = width, TextAlignment = TextAlignment.Centered, AutoSize = autoSize };
+		var lblRight = new View (text) { Y = 2, Width = width, TextAlignment = TextAlignment.Right, AutoSize = autoSize };
+		var lblJust = new View (text) { Y = 3, Width = width, TextAlignment = TextAlignment.Justified, AutoSize = autoSize };
+		var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+		frame.Add (lblLeft, lblCenter, lblRight, lblJust);
+		Application.Top.Add (frame);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (width + 2, 6);
+
+		Assert.True (lblLeft.AutoSize == autoSize);
+		Assert.True (lblCenter.AutoSize == autoSize);
+		Assert.True (lblRight.AutoSize == autoSize);
+		Assert.True (lblJust.AutoSize == autoSize);
+		Assert.True (lblLeft.TextFormatter.AutoSize == autoSize);
+		Assert.True (lblCenter.TextFormatter.AutoSize == autoSize);
+		Assert.True (lblRight.TextFormatter.AutoSize == autoSize);
+		Assert.True (lblJust.TextFormatter.AutoSize == autoSize);
+		Assert.Equal (new Rect (0, 0, width, 1), lblLeft.Frame);
+		Assert.Equal (new Rect (0, 1, width, 1), lblCenter.Frame);
+		Assert.Equal (new Rect (0, 2, width, 1), lblRight.Frame);
+		Assert.Equal (new Rect (0, 3, width, 1), lblJust.Frame);
+		if (autoSize) {
+			Assert.Equal (new Size (11, 1), lblLeft.TextFormatter.Size);
+			Assert.Equal (new Size (11, 1), lblCenter.TextFormatter.Size);
+			Assert.Equal (new Size (11, 1), lblRight.TextFormatter.Size);
+		} else {
+			Assert.Equal (new Size (width, 1), lblLeft.TextFormatter.Size);
+			Assert.Equal (new Size (width, 1), lblCenter.TextFormatter.Size);
+			Assert.Equal (new Size (width, 1), lblRight.TextFormatter.Size);
+		}
+		Assert.Equal (new Size (width, 1), lblJust.TextFormatter.Size);
+		Assert.Equal (new Rect (0, 0, width + 2, 6), frame.Frame);
+
+		var expected = @"
+┌────────────────────┐
+│Hello World         │
+│    Hello World     │
+│         Hello World│
+│Hello          World│
+└────────────────────┘
+"
+		;
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, width + 2, 6), pos);
+	}
+
+	[Theory, AutoInitShutdown]
+	[InlineData (true)]
+	[InlineData (false)]
+	public void View_Draw_Vertical_Simple_TextAlignments (bool autoSize)
+	{
+		var text = "Hello World";
+		var height = 20;
+		var lblLeft = new View (text) { Height = height, TextDirection = TextDirection.TopBottom_LeftRight, AutoSize = autoSize };
+		var lblCenter = new View (text) { X = 2, Height = height, TextDirection = TextDirection.TopBottom_LeftRight, AutoSize = autoSize, VerticalTextAlignment = VerticalTextAlignment.Middle };
+		var lblRight = new View (text) { X = 4, Height = height, TextDirection = TextDirection.TopBottom_LeftRight, AutoSize = autoSize, VerticalTextAlignment = VerticalTextAlignment.Bottom };
+		var lblJust = new View (text) { X = 6, Height = height, TextDirection = TextDirection.TopBottom_LeftRight, AutoSize = autoSize, VerticalTextAlignment = VerticalTextAlignment.Justified };
+		var frame = new FrameView () { Width = Dim.Fill (), Height = Dim.Fill () };
+
+		frame.Add (lblLeft, lblCenter, lblRight, lblJust);
+		Application.Top.Add (frame);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (9, height + 2);
+
+		Assert.True (lblLeft.AutoSize == autoSize);
+		Assert.True (lblCenter.AutoSize == autoSize);
+		Assert.True (lblRight.AutoSize == autoSize);
+		Assert.True (lblJust.AutoSize == autoSize);
+		Assert.True (lblLeft.TextFormatter.AutoSize == autoSize);
+		Assert.True (lblCenter.TextFormatter.AutoSize == autoSize);
+		Assert.True (lblRight.TextFormatter.AutoSize == autoSize);
+		Assert.True (lblJust.TextFormatter.AutoSize == autoSize);
+		Assert.Equal (new Rect (0, 0, 11, height), lblLeft.Frame);
+		Assert.Equal (new Rect (2, 0, 11, height), lblCenter.Frame);
+		Assert.Equal (new Rect (4, 0, 11, height), lblRight.Frame);
+		Assert.Equal (new Rect (6, 0, 11, height), lblJust.Frame);
+		if (autoSize) {
+			Assert.Equal (new Size (1, 11), lblLeft.TextFormatter.Size);
+			Assert.Equal (new Size (1, 11), lblCenter.TextFormatter.Size);
+			Assert.Equal (new Size (1, 11), lblRight.TextFormatter.Size);
+		} else {
+			Assert.Equal (new Size (11, height), lblLeft.TextFormatter.Size);
+			Assert.Equal (new Size (11, height), lblCenter.TextFormatter.Size);
+			Assert.Equal (new Size (11, height), lblRight.TextFormatter.Size);
+		}
+		Assert.Equal (new Size (11, height), lblJust.TextFormatter.Size);
+		Assert.Equal (new Rect (0, 0, 9, height + 2), frame.Frame);
+
+		var expected = @"
+┌───────┐
+│H     H│
+│e     e│
+│l     l│
+│l     l│
+│o H   o│
+│  e    │
+│W l    │
+│o l    │
+│r o    │
+│l   H  │
+│d W e  │
+│  o l  │
+│  r l  │
+│  l o  │
+│  d    │
+│    W W│
+│    o o│
+│    r r│
+│    l l│
+│    d d│
+└───────┘
+"
+		;
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 9, height + 2), pos);
+	}
+}

+ 652 - 0
UnitTests/View/Text/TextTests.cs

@@ -0,0 +1,652 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Terminal.Gui.ViewTests;
+
+/// <summary>
+/// Tests of the  <see cref="View.Text"/> property with <see cref="View.AutoSize"/> set to false.
+/// </summary>
+public class TextTests {
+	readonly ITestOutputHelper _output;
+
+	public TextTests (ITestOutputHelper output) => _output = output;
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_View_IsEmpty_False_Return_Null_Lines ()
+	{
+		var text = "Views";
+		var view = new View {
+			Width = Dim.Fill () - text.Length,
+			Height = 1,
+			Text = text
+		};
+		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 (10, 4);
+
+		Assert.Equal (5, text.Length);
+		Assert.False (view.AutoSize);
+		Assert.Equal (new Rect (0, 0, 3, 1), view.Frame);
+		Assert.Equal (new Size (3, 1), view.TextFormatter.Size);
+		Assert.Equal (new List<string> { "Vie" }, view.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
+		var expected = @"
+┌────────┐
+│Vie     │
+│        │
+└────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		view.Width = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 0, 1), view.Frame);
+		Assert.Equal (new Size (0, 1), view.TextFormatter.Size);
+		Assert.Equal (new List<string> { string.Empty }, view.TextFormatter.Lines);
+		expected = @"
+┌────────┐
+│        │
+│        │
+└────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_View_IsEmpty_True_Minimum_Height ()
+	{
+		var text = "Views";
+		var view = new View {
+			Width = Dim.Fill () - text.Length,
+			Text = text
+		};
+		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 (10, 4);
+
+		Assert.Equal (5, text.Length);
+		Assert.False (view.AutoSize);
+		Assert.Equal (new Rect (0, 0, 3, 1), view.Frame);
+		Assert.Equal (new Size (3, 1), view.TextFormatter.Size);
+		Assert.Single (view.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
+		var expected = @"
+┌────────┐
+│Vie     │
+│        │
+└────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		view.Width = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 0, 1), view.Frame);
+		Assert.Equal (new Size (0, 1), view.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Equal (new List<string> { string.Empty }, view.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌────────┐
+│        │
+│        │
+└────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_Label_IsEmpty_True_Return_Null_Lines ()
+	{
+		var text = "Label";
+		var label = new Label {
+			Width = Dim.Fill () - text.Length,
+			Height = 1,
+			Text = text,
+			AutoSize = false
+		};
+		var win = new Window {
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		win.Add (label);
+		Application.Top.Add (win);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+
+		Assert.Equal (5, text.Length);
+		Assert.False (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 3, 1), label.Frame);
+		Assert.Equal (new Size (3, 1), label.TextFormatter.Size);
+		Assert.Equal (new List<string> { "Lab" }, label.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
+		var expected = @"
+┌────────┐
+│Lab     │
+│        │
+└────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		label.Width = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.False (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 0, 1), label.Frame);
+		Assert.Equal (new Size (0, 1), label.TextFormatter.Size);
+		Assert.Equal (new List<string> { string.Empty }, label.TextFormatter.Lines);
+		expected = @"
+┌────────┐
+│        │
+│        │
+└────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_Label_Height_Zero_Returns_Minimum_Height ()
+	{
+		var text = "Label";
+		var label = new Label {
+			Width = Dim.Fill () - text.Length,
+			Text = text,
+			AutoSize = false
+		};
+		var win = new Window {
+			Width = Dim.Fill (),
+			Height = Dim.Fill ()
+		};
+		win.Add (label);
+		Application.Top.Add (win);
+		Application.Begin (Application.Top);
+		((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+
+		Assert.Equal (5, text.Length);
+		Assert.False (label.AutoSize);
+		Assert.Equal (new Rect (0, 0, 3, 1), label.Frame);
+		Assert.Equal (new Size (3, 1), label.TextFormatter.Size);
+		Assert.Single (label.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 10, 4), win.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 4), Application.Top.Frame);
+		var expected = @"
+┌────────┐
+│Lab     │
+│        │
+└────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		label.Width = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 0, 1), label.Frame);
+		Assert.Equal (new Size (0, 1), label.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Equal (new List<string> { string.Empty }, label.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌────────┐
+│        │
+│        │
+└────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 10, 4), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_View_Width_Null_Returns_Host_Frame_Width ()
+	{
+		var text = "Views";
+		var view = new View {
+			TextDirection = TextDirection.TopBottom_LeftRight,
+			Height = Dim.Fill () - text.Length,
+			Text = text
+		};
+		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 (4, 10);
+
+		Assert.Equal (5, text.Length);
+		Assert.False (view.AutoSize);
+		Assert.Equal (new Rect (0, 0, 1, 3), view.Frame);
+		Assert.Equal (new Size (1, 3), view.TextFormatter.Size);
+		Assert.Single (view.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 4, 10), win.Frame);
+		Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame);
+		var expected = @"
+┌──┐
+│V │
+│i │
+│e │
+│  │
+│  │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		view.Height = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 1, 0), view.Frame);
+		Assert.Equal (new Size (1, 0), view.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Equal (new List<string> { string.Empty }, view.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌──┐
+│  │
+│  │
+│  │
+│  │
+│  │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_View_Width_Zero_Returns_Minimum_Width_With_Wide_Rune ()
+	{
+		var text = "界View";
+		var view = new View {
+			TextDirection = TextDirection.TopBottom_LeftRight,
+			Height = Dim.Fill () - text.Length,
+			Text = text
+		};
+		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 (4, 10);
+
+		Assert.Equal (5, text.Length);
+		Assert.False (view.AutoSize);
+		Assert.Equal (new Rect (0, 0, 2, 3), view.Frame);
+		Assert.Equal (new Size (2, 3), view.TextFormatter.Size);
+		Assert.Single (view.TextFormatter.Lines);
+		Assert.Equal (new Rect (0, 0, 4, 10), win.Frame);
+		Assert.Equal (new Rect (0, 0, 4, 10), Application.Top.Frame);
+		var expected = @"
+┌──┐
+│界│
+│V │
+│i │
+│  │
+│  │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+
+		text = "0123456789";
+		Assert.Equal (10, text.Length);
+		view.Height = Dim.Fill () - text.Length;
+		Application.Refresh ();
+
+		Assert.Equal (new Rect (0, 0, 2, 0), view.Frame);
+		Assert.Equal (new Size (2, 0), view.TextFormatter.Size);
+		var exception = Record.Exception (() => Assert.Equal (new List<string> { string.Empty }, view.TextFormatter.Lines));
+		Assert.Null (exception);
+		expected = @"
+┌──┐
+│  │
+│  │
+│  │
+│  │
+│  │
+│  │
+│  │
+│  │
+└──┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 4, 10), pos);
+	}
+
+
+	[Fact]
+	public void AutoSize_False_If_Text_Empty ()
+	{
+		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);
+		view1.Dispose ();
+		view2.Dispose ();
+		view3.Dispose ();
+	}
+
+	[Fact]
+	public void AutoSize_False_If_Text_Is_Not_Empty ()
+	{
+		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);
+		view1.Dispose ();
+		view2.Dispose ();
+		view3.Dispose ();
+	}
+
+	[Fact]
+	public void AutoSize_False_ResizeView_Is_Always_False ()
+	{
+		var super = new View ();
+		var label = new Label { AutoSize = false };
+		super.Add (label);
+
+		label.Text = "New text";
+		super.LayoutSubviews ();
+
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,0,1)", label.Bounds.ToString ());
+		super.Dispose ();
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_ResizeView_With_Dim_Fill_After_IsInitialized ()
+	{
+		var win = new Window (new Rect (0, 0, 30, 80));
+		var label = new Label { AutoSize = false, Width = Dim.Fill (), Height = Dim.Fill () };
+		win.Add (label);
+		Application.Top.Add (win);
+
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,80,25)", label.Bounds.ToString ());
+
+		label.Text = "New text\nNew line";
+		Application.Top.LayoutSubviews ();
+
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,28,78)", label.Bounds.ToString ());
+		Assert.False (label.IsInitialized);
+
+		var rs = Application.Begin (Application.Top);
+		Assert.True (label.IsInitialized);
+		Assert.False (label.AutoSize);
+		Assert.Equal ("(0,0,28,78)", label.Bounds.ToString ());
+		Application.End (rs);
+	}
+
+	[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 ("Absolute(10)", view1.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view1.Height.ToString ());
+		Assert.False (view2.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view2.Frame);
+		Assert.Equal ("Absolute(10)", view2.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view2.Height.ToString ());
+		Assert.False (view3.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view3.Frame);
+		Assert.Equal ("Absolute(10)", view3.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view3.Height.ToString ());
+		Assert.False (view4.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view4.Frame);
+		Assert.Equal ("Absolute(10)", view4.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view4.Height.ToString ());
+		Assert.False (view5.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view5.Frame);
+		Assert.Equal ("Absolute(10)", view5.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view5.Height.ToString ());
+		Assert.False (view6.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view6.Frame);
+		Assert.Equal ("Absolute(10)", view6.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view6.Height.ToString ());
+
+		var rs = 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 ("Absolute(10)", view1.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view1.Height.ToString ());
+		Assert.False (view2.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view2.Frame);
+		Assert.Equal ("Absolute(10)", view2.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view2.Height.ToString ());
+		Assert.False (view3.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view3.Frame);
+		Assert.Equal ("Absolute(10)", view3.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view3.Height.ToString ());
+		Assert.False (view4.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view4.Frame);
+		Assert.Equal ("Absolute(10)", view4.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view4.Height.ToString ());
+		Assert.False (view5.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view5.Frame);
+		Assert.Equal ("Absolute(10)", view5.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view5.Height.ToString ());
+		Assert.False (view6.AutoSize);
+		Assert.Equal (new Rect (0, 0, 10, 5), view6.Frame);
+		Assert.Equal ("Absolute(10)", view6.Width.ToString ());
+		Assert.Equal ("Absolute(5)", view6.Height.ToString ());
+		Application.End (rs);
+	}
+
+	[Fact]
+	[AutoInitShutdown]
+	public void AutoSize_False_Width_Height_SetMinWidthHeight_Narrow_Wide_Runes ()
+	{
+		string text = $"First line{Environment.NewLine}Second line";
+		var horizontalView = new View () {
+			Width = 20,
+			Height = 1,
+			Text = text
+		};
+		var verticalView = new View () {
+			Y = 3,
+			Height = 20,
+			Width = 1,
+			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);
+		var rs = 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);
+		string expected = @"
+┌──────────────────────────────┐
+│First line Second li          │
+│                              │
+│                              │
+│F                             │
+│i                             │
+│r                             │
+│s                             │
+│t                             │
+│                              │
+│l                             │
+│i                             │
+│n                             │
+│e                             │
+│                              │
+│S                             │
+│e                             │
+│c                             │
+│o                             │
+│n                             │
+│d                             │
+│                              │
+│l                             │
+│i                             │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+└──────────────────────────────┘
+";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+
+		verticalView.Text = $"最初の行{Environment.NewLine}二行目";
+		Application.Top.Draw ();
+		// BUGBUG: #3127 - If AutoSize == false, setting text should NOT change the size of the view.
+		Assert.Equal (new Rect (0, 3, 2, 20), verticalView.Frame);
+		expected = @"
+┌──────────────────────────────┐
+│First line Second li          │
+│                              │
+│                              │
+│最                            │
+│初                            │
+│の                            │
+│行                            │
+│                              │
+│二                            │
+│行                            │
+│目                            │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+│                              │
+└──────────────────────────────┘
+";
+
+		pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
+}

Fișier diff suprimat deoarece este prea mare
+ 630 - 619
UnitTests/View/ViewTests.cs


+ 17 - 19
UnitTests/Views/AppendAutocompleteTests.cs

@@ -1,12 +1,10 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.TextTests; 
+namespace Terminal.Gui.TextTests;
 
 public class AppendAutocompleteTests {
 	readonly ITestOutputHelper output;
@@ -26,7 +24,7 @@ public class AppendAutocompleteTests {
 		tf.PositionCursor ();
 		TestHelpers.AssertDriverContentsAre ("", output);
 
-		tf.NewKeyDownEvent (new ('f'));
+		tf.NewKeyDownEvent (new Key ('f'));
 
 		tf.Draw ();
 		tf.PositionCursor ();
@@ -93,7 +91,7 @@ public class AppendAutocompleteTests {
 		// When cancelling autocomplete
 		Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false);
 
-		// Suggestion should disapear
+		// Suggestion should disappear
 		tf.Draw ();
 		TestHelpers.AssertDriverContentsAre ("f", output);
 		Assert.Equal ("f", tf.Text);
@@ -107,7 +105,7 @@ public class AppendAutocompleteTests {
 	}
 
 	[Fact] [AutoInitShutdown]
-	public void TestAutoAppend_AfterCloseKey_ReapearsOnLetter ()
+	public void TestAutoAppend_AfterCloseKey_ReappearsOnLetter ()
 	{
 		var tf = GetTextFieldsInViewSuggesting ("fish");
 
@@ -119,25 +117,25 @@ public class AppendAutocompleteTests {
 		Assert.Equal ("f", tf.Text);
 
 		// When cancelling autocomplete
-		Application.Driver.SendKeys ('e', ConsoleKey.Escape, false, false, false);
+		Application.Driver.SendKeys ('\0', ConsoleKey.Escape, false, false, false);
 
-		// Suggestion should disapear
+		// Suggestion should disappear
 		tf.Draw ();
 		TestHelpers.AssertDriverContentsAre ("f", output);
 		Assert.Equal ("f", tf.Text);
 
-		// Should reapear when you press next letter
+		// Should reappear when you press next letter
 		Application.Driver.SendKeys ('i', ConsoleKey.I, false, false, false);
 		tf.Draw ();
-		// BUGBUG: v2 - I broke this test and don't have time to figure out why. @tznind - help!
-		//TestHelpers.AssertDriverContentsAre ("fish", output);
+		tf.PositionCursor ();
+		TestHelpers.AssertDriverContentsAre ("fish", output);
 		Assert.Equal ("fi", tf.Text);
 	}
 
 	[Theory] [AutoInitShutdown]
 	[InlineData ("ffffffffffffffffffffffffff", "ffffffffff")]
-	[InlineData ("f234567890", "f234567890")]
-	[InlineData ("fisérables", "fisérables")]
+	[InlineData ("f234567890",                 "f234567890")]
+	[InlineData ("fisérables",                 "fisérables")]
 	public void TestAutoAppendRendering_ShouldNotOverspill (string overspillUsing, string expectRender)
 	{
 		var tf = GetTextFieldsInViewSuggesting (overspillUsing);
@@ -212,7 +210,7 @@ public class AppendAutocompleteTests {
 		Assert.Equal ("f", tf.Text);
 
 		// add a space then go back 1
-		Application.Driver.SendKeys (' ', ConsoleKey.Spacebar, false, false, false);
+		Application.Driver.SendKeys (' ', ConsoleKey.Spacebar,  false, false, false);
 		Application.Driver.SendKeys ('<', ConsoleKey.LeftArrow, false, false, false);
 
 		tf.Draw ();
@@ -239,12 +237,12 @@ public class AppendAutocompleteTests {
 	TextField GetTextFieldsInView ()
 	{
 		var tf = new TextField {
-			Width = 10
-		};
+					       Width = 10
+				       };
 		var tf2 = new TextField {
-			Y = 1,
-			Width = 10
-		};
+						Y     = 1,
+						Width = 10
+					};
 
 		var top = Application.Top;
 		top.Add (tf);

+ 44 - 45
UnitTests/Views/CheckBoxTests.cs

@@ -139,53 +139,52 @@ namespace Terminal.Gui.ViewsTests {
 			checkBox.Checked = true;
 			Assert.Equal ($"{CM.Glyphs.Checked} Check this out 你", checkBox.TextFormatter.Text);
 
-			//checkBox.AutoSize = false;
+			checkBox.AutoSize = false;
+			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, ref first);
+			checkBox.Text = "Check this out 你 changed";
+			var firstIteration = false;
+			Application.RunIteration (ref runstate, ref firstIteration);
 			// BUGBUG - v2 - Autosize is busted; disabling tests for now
-//			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
-//			expected = @"
-//┌┤Test Demo 你├──────────────┐
-//│                            │
-//│ √ Check this out 你        │
-//│                            │
-//└────────────────────────────┘
-//";
-
-//			pos = TestHelpers.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, ref first);
-//			Assert.False (checkBox.AutoSize);
-//			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
-//			expected = @"
-//┌┤Test Demo 你├──────────────┐
-//│                            │
-//│ √ Check this out 你        │
-//│                            │
-//└────────────────────────────┘
-//";
-
-//			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//			Assert.Equal (new Rect (0, 0, 30, 5), pos);
-
-//			checkBox.AutoSize = true;
-//			Application.RunMainLoopIteration (ref runstate, ref first);
-//			Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame);
-//			expected = @"
-//┌┤Test Demo 你├──────────────┐
-//│                            │
-//│ √ Check this out 你 changed│
-//│                            │
-//└────────────────────────────┘
-//";
-
-//			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-//			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			var expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│ ☑ Check this out 你        │
+│                            │
+└────────────────────────────┘";
+
+			var pos = TestHelpers.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.RunIteration (ref runstate, ref firstIteration);
+			Assert.False (checkBox.AutoSize);
+			Assert.Equal (new Rect (1, 1, 19, 1), checkBox.Frame);
+			expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│ ☑ Check this out 你        │
+│                            │
+└────────────────────────────┘";
+
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
+
+			checkBox.AutoSize = true;
+			Application.RunIteration (ref runstate, ref firstIteration);
+			Assert.Equal (new Rect (1, 1, 27, 1), checkBox.Frame);
+			expected = @"
+┌┤Test Demo 你├──────────────┐
+│                            │
+│ ☑ Check this out 你 changed│
+│                            │
+└────────────────────────────┘";
+
+			pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 0, 30, 5), pos);
 		}
 
 		[Fact, AutoInitShutdown]

+ 1 - 1
UnitTests/Views/ContextMenuTests.cs

@@ -302,7 +302,7 @@ namespace Terminal.Gui.ViewsTests {
 			Application.Begin (Application.Top);
 
 			Assert.Equal (new Rect (70, 24, 10, 1), view.Frame);
-			Assert.Equal (new Point (0, 0), cm.Position);
+			//Assert.Equal (new Point (0, 0), cm.Position);
 
 			cm.Show ();
 			Assert.Equal (new Point (70, 24), cm.Position);

+ 82 - 0
UnitTests/Views/DateFieldTests.cs

@@ -57,6 +57,31 @@ namespace Terminal.Gui.ViewsTests {
 			Assert.Equal (8, df.CursorPosition);
 		}
 
+		[Fact]
+		public void CursorPosition_Min_Is_Always_One_Max_Is_Always_Max_Format_After_Selection ()
+		{
+			var df = new DateField ();
+			// Start selection
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft | KeyCode.ShiftMask)));
+			Assert.Equal (1, df.SelectedStart);
+			Assert.Equal (1, df.SelectedLength);
+			Assert.Equal (0, df.CursorPosition);
+			// Without selection
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorLeft)));
+			Assert.Equal (-1, df.SelectedStart);
+			Assert.Equal (0, df.SelectedLength);
+			Assert.Equal (1, df.CursorPosition);
+			df.CursorPosition = 10;
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight | KeyCode.ShiftMask)));
+			Assert.Equal (10, df.SelectedStart);
+			Assert.Equal (1, df.SelectedLength);
+			Assert.Equal (11, df.CursorPosition);
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight)));
+			Assert.Equal (-1, df.SelectedStart);
+			Assert.Equal (0, df.SelectedLength);
+			Assert.Equal (10, df.CursorPosition);
+		}
+
 		[Fact]
 		public void KeyBindings_Command ()
 		{
@@ -97,6 +122,63 @@ namespace Terminal.Gui.ViewsTests {
 			df.ReadOnly = false;
 			Assert.True (df.NewKeyDownEvent (new (KeyCode.D1)));
 			Assert.Equal (" 12/02/1971", df.Text);
+			Assert.Equal (2, df.CursorPosition);
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.D | KeyCode.AltMask)));
+			Assert.Equal (" 10/02/1971", df.Text);
+			CultureInfo.CurrentCulture = cultureBackup;
+		}
+
+		[Fact]
+		public void Typing_With_Selection_Normalize_Format ()
+		{
+			CultureInfo cultureBackup = CultureInfo.CurrentCulture;
+			CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+			DateField df = new DateField (DateTime.Parse ("12/12/1971"));
+			// Start selection at before the first separator /
+			df.CursorPosition = 2;
+			// Now select the separator /
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.CursorRight | KeyCode.ShiftMask)));
+			Assert.Equal (2, df.SelectedStart);
+			Assert.Equal (1, df.SelectedLength);
+			Assert.Equal (3, df.CursorPosition);
+			// Type 3 over the separator
+			Assert.True (df.NewKeyDownEvent (new (KeyCode.D3)));
+			// The format was normalized and replaced again with /
+			Assert.Equal (" 12/12/1971", df.Text);
+			Assert.Equal (4, df.CursorPosition);
+			CultureInfo.CurrentCulture = cultureBackup;
+		}
+
+		[Fact, AutoInitShutdown]
+		public void Copy_Paste ()
+		{
+			CultureInfo cultureBackup = CultureInfo.CurrentCulture;
+			CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+			DateField df1 = new DateField (DateTime.Parse ("12/12/1971"));
+			DateField df2 = new DateField (DateTime.Parse ("12/31/2023"));
+			// Select all text
+			Assert.True (df2.NewKeyDownEvent (new (KeyCode.End | KeyCode.ShiftMask)));
+			Assert.Equal (1, df2.SelectedStart);
+			Assert.Equal (10, df2.SelectedLength);
+			Assert.Equal (11, df2.CursorPosition);
+			// Copy from df2
+			Assert.True (df2.NewKeyDownEvent (new (KeyCode.C | KeyCode.CtrlMask)));
+			// Paste into df1
+			Assert.True (df1.NewKeyDownEvent (new (KeyCode.V | KeyCode.CtrlMask)));
+			Assert.Equal (" 12/31/2023", df1.Text);
+			Assert.Equal (11, df1.CursorPosition);
+			CultureInfo.CurrentCulture = cultureBackup;
+		}
+
+		[Fact]
+		public void Date_Start_From_01_01_0001_And_End_At_12_31_9999 ()
+		{
+			CultureInfo cultureBackup = CultureInfo.CurrentCulture;
+			CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
+			DateField df = new DateField (DateTime.Parse ("01/01/0001"));
+			Assert.Equal (" 01/01/0001", df.Text);
+			df.Date = DateTime.Parse ("12/31/9999");
+			Assert.Equal (" 12/31/9999", df.Text);
 			CultureInfo.CurrentCulture = cultureBackup;
 		}
 	}

+ 17 - 0
UnitTests/Views/HexViewTests.cs

@@ -51,6 +51,8 @@ namespace Terminal.Gui.ViewsTests {
 				Width = 20,
 				Height = 20
 			};
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
 
 			Assert.Empty (hv.Edits);
 			hv.AllowEdits = false;
@@ -88,6 +90,8 @@ namespace Terminal.Gui.ViewsTests {
 				Width = 20,
 				Height = 20
 			};
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
 
 			Assert.Equal (0, hv.DisplayStart);
 
@@ -105,6 +109,9 @@ namespace Terminal.Gui.ViewsTests {
 		public void Edited_Event ()
 		{
 			var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
+
 			KeyValuePair<long, byte> keyValuePair = default;
 			hv.Edited += (s,e) => keyValuePair = new KeyValuePair<long, byte>(e.Position,e.NewValue);
 
@@ -120,6 +127,9 @@ namespace Terminal.Gui.ViewsTests {
 		public void DiscardEdits_Method ()
 		{
 			var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
+
 			Assert.True (hv.NewKeyDownEvent (new (KeyCode.D4)));
 			Assert.True (hv.NewKeyDownEvent (new (KeyCode.D1)));
 			Assert.Single (hv.Edits);
@@ -135,6 +145,8 @@ namespace Terminal.Gui.ViewsTests {
 		public void Position_Using_Encoding_Unicode ()
 		{
 			var hv = new HexView (LoadStream (true)) { Width = 20, Height = 20 };
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
 			Assert.Equal (126, hv.Source.Length);
 			Assert.Equal (126, hv.Source.Position);
 			Assert.Equal (1, hv.Position);
@@ -166,6 +178,8 @@ namespace Terminal.Gui.ViewsTests {
 		public void Position_Using_Encoding_Default ()
 		{
 			var hv = new HexView (LoadStream ()) { Width = 20, Height = 20 };
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
 			Assert.Equal (63, hv.Source.Length);
 			Assert.Equal (63, hv.Source.Position);
 			Assert.Equal (1, hv.Position);
@@ -376,6 +390,9 @@ namespace Terminal.Gui.ViewsTests {
 			original.CopyTo (copy);
 			copy.Flush ();
 			var hv = new HexView (copy) { Width = Dim.Fill (), Height = Dim.Fill () };
+			// Needed because HexView relies on LayoutComplete to calc sizes
+			hv.LayoutSubviews ();
+
 			byte [] readBuffer = new byte [hv.Source.Length];
 			hv.Source.Position = 0;
 			hv.Source.Read (readBuffer);

Fișier diff suprimat deoarece este prea mare
+ 440 - 435
UnitTests/Views/LabelTests.cs


+ 5 - 10
UnitTests/Views/Toplevel/OverlappedTests.cs → UnitTests/Views/OverlappedTests.cs

@@ -1,9 +1,8 @@
 using System;
-using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace TerminalGui.ViewsTests;
+namespace Terminal.Gui.ViewsTests;
 
 public class OverlappedTests {
 	readonly ITestOutputHelper _output;
@@ -17,8 +16,7 @@ public class OverlappedTests {
 #endif
 	}
 
-	[Fact]
-	[TestRespondersDisposed]
+	[Fact] [TestRespondersDisposed]
 	public void Dispose_Toplevel_IsOverlappedContainer_False_With_Begin_End ()
 	{
 		Application.Init (new FakeDriver ());
@@ -37,8 +35,7 @@ public class OverlappedTests {
 #endif
 	}
 
-	[Fact]
-	[TestRespondersDisposed]
+	[Fact] [TestRespondersDisposed]
 	public void Dispose_Toplevel_IsOverlappedContainer_True_With_Begin ()
 	{
 		Application.Init (new FakeDriver ());
@@ -50,8 +47,7 @@ public class OverlappedTests {
 		Application.Shutdown ();
 	}
 
-	[Fact]
-	[AutoInitShutdown]
+	[Fact] [AutoInitShutdown]
 	public void Application_RequestStop_With_Params_On_A_Not_OverlappedContainer_Always_Use_Application_Current ()
 	{
 		var top1 = new Toplevel ();
@@ -687,8 +683,7 @@ public class OverlappedTests {
 	[Fact]
 	public void MoveToOverlappedChild_Throw_NullReferenceException_Passing_Null_Parameter () => Assert.Throws<NullReferenceException> (delegate { Application.MoveToOverlappedChild (null); });
 
-	[Fact]
-	[AutoInitShutdown]
+	[Fact] [AutoInitShutdown]
 	public void Visible_False_Does_Not_Clear ()
 	{
 		var overlapped = new Overlapped ();

+ 0 - 16
UnitTests/Views/RadioGroupTests.cs

@@ -16,20 +16,12 @@ public class RadioGroupTests {
 		var rg = new RadioGroup ();
 		Assert.True (rg.CanFocus);
 		Assert.Empty (rg.RadioLabels);
-		Assert.Null (rg.X);
-		Assert.Null (rg.Y);
-		Assert.Null (rg.Width);
-		Assert.Null (rg.Height);
 		Assert.Equal (Rect.Empty, rg.Frame);
 		Assert.Equal (0, rg.SelectedItem);
 
 		rg = new RadioGroup (new string [] { "Test" });
 		Assert.True (rg.CanFocus);
 		Assert.Single (rg.RadioLabels);
-		Assert.Null (rg.X);
-		Assert.Null (rg.Y);
-		Assert.Null (rg.Width);
-		Assert.Null (rg.Height);
 		Assert.Equal (new Rect (0, 0, 0, 0), rg.Frame);
 		Assert.Equal (0, rg.SelectedItem);
 
@@ -37,10 +29,6 @@ public class RadioGroupTests {
 		Assert.True (rg.CanFocus);
 		Assert.Single (rg.RadioLabels);
 		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);
 
@@ -48,10 +36,6 @@ public class RadioGroupTests {
 		Assert.True (rg.CanFocus);
 		Assert.Single (rg.RadioLabels);
 		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, 6, 1), rg.Frame);
 		Assert.Equal (0, rg.SelectedItem);
 	}

Fișier diff suprimat deoarece este prea mare
+ 661 - 717
UnitTests/Views/ScrollBarViewTests.cs


+ 224 - 228
UnitTests/Views/ScrollViewTests.cs

@@ -16,14 +16,10 @@ namespace Terminal.Gui.ViewsTests {
 		public void Constructors_Defaults ()
 		{
 			var sv = new ScrollView ();
-			Assert.Equal (LayoutStyle.Computed, sv.LayoutStyle);
+			Assert.Equal (LayoutStyle.Absolute, sv.LayoutStyle);
 			Assert.True (sv.CanFocus);
 			Assert.Equal (new Rect (0, 0, 0, 0), sv.Frame);
 			Assert.Equal (Rect.Empty, sv.Frame);
-			Assert.Null (sv.X);
-			Assert.Null (sv.Y);
-			Assert.Null (sv.Width);
-			Assert.Null (sv.Height);
 			Assert.Equal (Point.Empty, sv.ContentOffset);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.True (sv.AutoHideScrollBars);
@@ -33,10 +29,6 @@ namespace Terminal.Gui.ViewsTests {
 			Assert.Equal (LayoutStyle.Absolute, sv.LayoutStyle);
 			Assert.True (sv.CanFocus);
 			Assert.Equal (new Rect (1, 2, 20, 10), sv.Frame);
-			Assert.Null (sv.X);
-			Assert.Null (sv.Y);
-			Assert.Null (sv.Width);
-			Assert.Null (sv.Height);
 			Assert.Equal (Point.Empty, sv.ContentOffset);
 			Assert.Equal (Size.Empty, sv.ContentSize);
 			Assert.True (sv.AutoHideScrollBars);
@@ -196,6 +188,8 @@ namespace Terminal.Gui.ViewsTests {
 			Application.Top.Add (sv);
 			Application.Begin (Application.Top);
 
+			Assert.Equal (new Rect (0, 0, 10, 10), sv.Bounds);
+
 			Assert.False (sv.AutoHideScrollBars);
 			Assert.True (sv.ShowHorizontalScrollIndicator);
 			Assert.True (sv.ShowVerticalScrollIndicator);
@@ -214,7 +208,9 @@ namespace Terminal.Gui.ViewsTests {
 ", output);
 
 			sv.ShowHorizontalScrollIndicator = false;
+			Assert.Equal (new Rect (0, 0, 10, 10), sv.Bounds);
 			sv.ShowVerticalScrollIndicator = true;
+			Assert.Equal (new Rect (0, 0, 10, 10), sv.Bounds);
 
 			Assert.False (sv.AutoHideScrollBars);
 			Assert.False (sv.ShowHorizontalScrollIndicator);
@@ -228,6 +224,7 @@ namespace Terminal.Gui.ViewsTests {
+         │
 ", output);
@@ -249,7 +246,7 @@ namespace Terminal.Gui.ViewsTests {
          
          
          
-◄├─────┤► 
+◄├─────┤► 
 ", output);
 
 			sv.ShowHorizontalScrollIndicator = false;
@@ -373,236 +370,235 @@ namespace Terminal.Gui.ViewsTests {
 ", output);
 		}
 
-		// BUGBUG: v2 - I can't figure out what this test is trying to test and it fails in weird ways
-		// Disabling for now
-		//[Fact, AutoInitShutdown]
-		//public void Frame_And_Labels_Does_Not_Overspill_ScrollView ()
-		//{
-		//	var sv = new ScrollView {
-		//		X = 3,
-		//		Y = 3,
-		//		Width = 10,
-		//		Height = 10,
-		//		ContentSize = new Size (50, 50)
-		//	};
-		//	for (int i = 0; i < 8; i++) {
-		//		sv.Add (new CustomButton ("█", $"Button {i}", 20, 3) { Y = i * 3 });
-		//	}
-		//	Application.Top.Add (sv);
-		//	Application.Begin (Application.Top);
-
-		//	TestHelpers.AssertDriverContentsWithFrameAre (@"
-		// █████████▲
-		// ██████But┬
-		// █████████┴
-		// ┌────────░
-		// │     But░
-		// └────────░
-		// ┌────────░
-		// │     But░
-		// └────────▼
-		// ◄├┤░░░░░► ", output);
-
-		//	sv.ContentOffset = new Point (5, 5);
-		//	sv.LayoutSubviews ();
-		//	Application.Refresh ();
-		//	TestHelpers.AssertDriverContentsWithFrameAre (@"
-		// ─────────▲
-		// ─────────┬
-		//  Button 2│
-		// ─────────┴
-		// ─────────░
-		//  Button 3░
-		// ─────────░
-		// ─────────░
-		//  Button 4▼
-		// ◄├─┤░░░░► ", output);
-		//}
-
-		//private class CustomButton : FrameView {
-		//	private Label labelFill;
-		//	private Label labelText;
-
-		//	public CustomButton (string fill, string text, int width, int height) : base ()
-		//	{
-		//		Width = width;
-		//		Height = height;
-		//		labelFill = new Label () { AutoSize = false, X = Pos.Center (), Y = Pos.Center (), Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
-		//		labelFill.LayoutComplete += (s, e) => {
-		//			var fillText = new System.Text.StringBuilder ();
-		//			for (int i = 0; i < labelFill.Bounds.Height; i++) {
-		//				if (i > 0) {
-		//					fillText.AppendLine ("");
-		//				}
-		//				for (int j = 0; j < labelFill.Bounds.Width; j++) {
-		//					fillText.Append (fill);
-		//				}
-		//			}
-		//			labelFill.Text = fillText;
-		//		};
-
-		//		labelText = new Label (text) { X = Pos.Center (), Y = Pos.Center () };
-		//		Add (labelFill, labelText);
-		//		CanFocus = true;
-		//	}
-
-		//	public override bool OnEnter (View view)
-		//	{
-		//		Border.BorderStyle = BorderStyle.None;
-		//		Border.DrawMarginFrame = false;
-		//		labelFill.Visible = true;
-		//		view = this;
-		//		return base.OnEnter (view);
-		//	}
-
-		//	public override bool OnLeave (View view)
-		//	{
-		//		Border.BorderStyle = BorderStyle.Single;
-		//		Border.DrawMarginFrame = true;
-		//		labelFill.Visible = false;
-		//		if (view == null)
-		//			view = this;
-		//		return base.OnLeave (view);
-		//	}
-		//}
-
-		// BUGBUG: Broke this test with #2483 - @bdisp I need your help figuring out why
-//		[Fact, AutoInitShutdown]
-//		public void Clear_Window_Inside_ScrollView ()
-//		{
-//			var topLabel = new Label ("At 15,0") { X = 15 };
-//			var sv = new ScrollView {
-//				X = 3,
-//				Y = 3,
-//				Width = 10,
-//				Height = 10,
-//				ContentSize = new Size (23, 23),
-//				KeepContentAlwaysInViewport = false
-//			};
-//			var bottomLabel = new Label ("At 15,15") { X = 15, Y = 15 };
-//			Application.Top.Add (topLabel, sv, bottomLabel);
-//			Application.Begin (Application.Top);
-
-//			TestHelpers.AssertDriverContentsWithFrameAre (@"
-//               At 15,0 
+		// There still have an issue with lower right corner of the scroll view
+		[Fact, AutoInitShutdown]
+		public void Frame_And_Labels_Does_Not_Overspill_ScrollView ()
+		{
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (50, 50)
+			};
+			for (int i = 0; i < 8; i++) {
+				sv.Add (new CustomButton ("█", $"Button {i}", 20, 3) { Y = i * 3 });
+			}
+			Application.Top.Add (sv);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   █████████▲
+   ██████But┬
+   █████████┴
+   ┌────────░
+   │     But░
+   └────────░
+   ┌────────░
+   │     But░
+   └────────▼
+   ◄├┤░░░░░►─", output);
+
+			sv.ContentOffset = new Point (5, 5);
+			sv.LayoutSubviews ();
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+   ─────────▲
+   ─────────┬
+    Button 2│
+   ─────────┴
+   ─────────░
+    Button 3░
+   ─────────░
+   ─────────░
+    Button 4▼
+   ◄├─┤░░░░►─", output);
+		}
+
+		private class CustomButton : FrameView {
+			private Label labelFill;
+			private Label labelText;
+
+			public CustomButton (string fill, string text, int width, int height) : base ()
+			{
+				Width = width;
+				Height = height;
+				//labelFill = new Label () { AutoSize = false, X = Pos.Center (), Y = Pos.Center (), Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
+				labelFill = new Label () { AutoSize = false, Width = Dim.Fill (), Height = Dim.Fill (), Visible = false };
+				labelFill.LayoutComplete += (s, e) => {
+					var fillText = new System.Text.StringBuilder ();
+					for (int i = 0; i < labelFill.Bounds.Height; i++) {
+						if (i > 0) {
+							fillText.AppendLine ("");
+						}
+						for (int j = 0; j < labelFill.Bounds.Width; j++) {
+							fillText.Append (fill);
+						}
+					}
+					labelFill.Text = fillText.ToString ();
+				};
+
+				labelText = new Label (text) { X = Pos.Center (), Y = Pos.Center () };
+				Add (labelFill, labelText);
+				CanFocus = true;
+			}
+
+			public override bool OnEnter (View view)
+			{
+				Border.BorderStyle = LineStyle.None;
+				Border.Thickness = new Thickness (0);
+				labelFill.Visible = true;
+				view = this;
+				return base.OnEnter (view);
+			}
+
+			public override bool OnLeave (View view)
+			{
+				Border.BorderStyle = LineStyle.Single;
+				Border.Thickness = new Thickness (1);
+				labelFill.Visible = false;
+				if (view == null)
+					view = this;
+				return base.OnLeave (view);
+			}
+		}
+		// There are still issue with the lower right corner of the scroll view
+		[Fact, AutoInitShutdown]
+		public void Clear_Window_Inside_ScrollView ()
+		{
+			var topLabel = new Label ("At 15,0") { X = 15 };
+			var sv = new ScrollView {
+				X = 3,
+				Y = 3,
+				Width = 10,
+				Height = 10,
+				ContentSize = new Size (23, 23),
+				KeepContentAlwaysInViewport = false
+			};
+			var bottomLabel = new Label ("At 15,15") { X = 15, Y = 15 };
+			Application.Top.Add (topLabel, sv, bottomLabel);
+			Application.Begin (Application.Top);
+
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
                        
                        
-//
-//
-//
-//
-//
-//
-//
-//
-//
-//   ◄├┤░░░░░►           
+            ▲          
+            ┬          
+            ┴          
+            ░          
+            ░          
+            ░          
+            ░          
+            ░          
+            ▼          
+   ◄├┤░░░░░►           
                        
                        
-//               At 15,15", output);
-
-//			var attributes = new Attribute [] {
-//				Colors.TopLevel.Normal,
-//				Colors.TopLevel.Focus,
-//				Colors.Base.Normal
-//			};
-
-//			TestHelpers.AssertDriverColorsAre (@"
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00011111111110000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000", attributes);
-
-//			sv.Add (new Window { X = 3, Y = 3, Width = 20, Height = 20 });
-
-//			Application.Refresh ();
-//			TestHelpers.AssertDriverContentsWithFrameAre (@"
-//               At 15,0 
+               At 15,15", output);
+
+			var attributes = new Attribute [] {
+						Colors.TopLevel.Normal,
+						Colors.TopLevel.Focus,
+						Colors.Base.Normal
+					};
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111100000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", null, attributes);
+
+			sv.Add (new Window { X = 3, Y = 3, Width = 20, Height = 20 });
+
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
                        
                        
-//
-//
-//
-//      ┌─────░          
-//      │     ░          
-//      │     ░          
-//      │     ░          
-//      │     ░          
-//      │     ▼          
-//   ◄├┤░░░░░►           
+            ▲          
+            ┬          
+            ┴          
+      ┌─────░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ░          
+      │     ▼          
+   ◄├┤░░░░░►           
                        
                        
-//               At 15,15", output);
-
-//			TestHelpers.AssertDriverColorsAre (@"
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00000022222210000000000
-//00011111111110000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000", attributes);
-
-//			sv.ContentOffset = new Point (20, 20);
-//			Application.Refresh ();
-//			TestHelpers.AssertDriverContentsWithFrameAre (@"
-//               At 15,0 
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00000022222210000000000
+00011111111120000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", null, attributes);
+
+			sv.ContentOffset = new Point (20, 20);
+			Application.Refresh ();
+			TestHelpers.AssertDriverContentsWithFrameAre (@"
+               At 15,0 
                        
                        
-//     │      ▲          
-//     │      ░          
-//   ──┘      ░          
-//
-//
-//
-//
-//
-//
-//   ◄░░░░├─┤►           
+     │      ▲          
+     │      ░          
+   ──┘      ░          
+            ░          
+            ░          
+            ┬          
+            │          
+            ┴          
+            ▼          
+   ◄░░░░├─┤►           
                        
                        
-//               At 15,15", output);
-
-//			TestHelpers.AssertDriverColorsAre (@"
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000
-//00022200000010000000000
-//00022200000010000000000
-//00022200000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00000000000010000000000
-//00011111111110000000000
-//00000000000000000000000
-//00000000000000000000000
-//00000000000000000000000", attributes);
-//		}
-
+               At 15,15", output);
+
+			TestHelpers.AssertDriverColorsAre (@"
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000
+00022200000010000000000
+00022200000010000000000
+00022200000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00000000000010000000000
+00011111111100000000000
+00000000000000000000000
+00000000000000000000000
+00000000000000000000000", null, attributes);
+		}
+		
 		[Fact, AutoInitShutdown]
 		public void DrawTextFormatter_Respects_The_Clip_Bounds ()
 		{

Fișier diff suprimat deoarece este prea mare
+ 433 - 444
UnitTests/Views/TabViewTests.cs


+ 1 - 0
UnitTests/Views/TextViewTests.cs

@@ -6200,6 +6200,7 @@ This is the second line.
 ", _output);
 
 		((FakeDriver)Application.Driver).SetBufferSize (6, 25);
+		tv.SetRelativeLayout (Application.Driver.Bounds);
 		tv.Draw ();
 		Assert.Equal (new Point (4, 2), tv.CursorPosition);
 		Assert.Equal (new Point (12, 0), cp);

Fișier diff suprimat deoarece este prea mare
+ 434 - 436
UnitTests/Views/TileViewTests.cs


+ 61 - 88
UnitTests/Views/Toplevel/ToplevelTests.cs → UnitTests/Views/ToplevelTests.cs

@@ -1,9 +1,8 @@
 using System;
-using Terminal.Gui;
 using Xunit;
 using Xunit.Abstractions;
 
-namespace TerminalGui.ViewsTests;
+namespace Terminal.Gui.ViewsTests;
 
 public class ToplevelTests {
 	readonly ITestOutputHelper _output;
@@ -16,28 +15,6 @@ public class ToplevelTests {
 	{
 		var top = new Toplevel ();
 
-		Assert.Equal (Colors.TopLevel, top.ColorScheme);
-		Assert.Equal ("Fill(0)", top.Width.ToString ());
-		Assert.Equal ("Fill(0)", top.Height.ToString ());
-		Assert.False (top.Running);
-		Assert.False (top.Modal);
-		Assert.Null (top.MenuBar);
-		Assert.Null (top.StatusBar);
-		Assert.False (top.IsOverlappedContainer);
-		Assert.False (top.IsOverlapped);
-
-		// Because Toplevel is LayoutStyle.Computed, SetRelativeLayout needs to be called
-		// to set the Frame.
-		top.SetRelativeLayout (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
-		Assert.Equal (new Rect (0,          0, Application.Driver.Cols, Application.Driver.Rows), top.Frame);
-	}
-
-	[Fact]
-	[AutoInitShutdown]
-	public void Create_Toplevel ()
-	{
-		var top = new Toplevel ();
-
 		Assert.Equal (Colors.TopLevel, top.ColorScheme);
 		Assert.Equal ("Fill(0)",       top.Width.ToString ());
 		Assert.Equal ("Fill(0)",       top.Height.ToString ());
@@ -47,13 +24,9 @@ public class ToplevelTests {
 		Assert.Null (top.StatusBar);
 		Assert.False (top.IsOverlappedContainer);
 		Assert.False (top.IsOverlapped);
-
-		// Because Toplevel is LayoutStyle.Computed, SetRelativeLayout needs to be called
-		// to set the Frame.
-		top.SetRelativeLayout (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows));
-		Assert.Equal (new Rect (0,          0, Application.Driver.Cols, Application.Driver.Rows), top.Frame);
 	}
 
+
 #if BROKE_IN_2927
 		// BUGBUG: The name of this test does not match what it does. 
 		[Fact]
@@ -226,8 +199,8 @@ public class ToplevelTests {
 		// Application.Top without menu and status bar.
 		var supView = top.GetLocationThatFits (top, 2, 2, out var nx, out var ny, out var mb, out var sb);
 		Assert.Equal (Application.Top, supView);
-		Assert.Equal (0, nx);
-		Assert.Equal (0, ny);
+		Assert.Equal (0,               nx);
+		Assert.Equal (0,               ny);
 		Assert.Null (mb);
 		Assert.Null (sb);
 
@@ -341,7 +314,7 @@ public class ToplevelTests {
 		// Application.Top with a menu and status bar.
 		top.GetLocationThatFits (win, 30, 20, out nx, out ny, out mb, out sb);
 		Assert.Equal (20, nx); // 20+60=80
-		Assert.Equal (9, ny); // 9+15+1(mb)=25
+		Assert.Equal (9,  ny); // 9+15+1(mb)=25
 		Assert.NotNull (mb);
 		Assert.NotNull (sb);
 
@@ -389,10 +362,10 @@ public class ToplevelTests {
 		Application.Begin (top);
 		top.Running = true;
 
-		Assert.Equal (new Rect (0, 0, 40, 25), win1.Frame);
+		Assert.Equal (new Rect (0,  0, 40, 25), win1.Frame);
 		Assert.Equal (new Rect (41, 0, 40, 25), win2.Frame);
-		Assert.Equal (win1, top.Focused);
-		Assert.Equal (tf1W1, top.MostFocused);
+		Assert.Equal (win1,                     top.Focused);
+		Assert.Equal (tf1W1,                    top.MostFocused);
 
 		Assert.True (isRunning);
 		Assert.True (Application.OnKeyDown (Application.QuitKey));
@@ -409,13 +382,13 @@ public class ToplevelTests {
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.Tab | KeyCode.ShiftMask)));
 		Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.Tab | KeyCode.CtrlMask)));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf2W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.Tab)));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf1W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorRight)));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf1W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorDown)));
 		Assert.Equal (win1, top.Focused);
@@ -429,22 +402,22 @@ public class ToplevelTests {
 		Assert.Equal (win1, top.Focused);
 		Assert.Equal (tvW1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorLeft)));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf1W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorUp)));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf2W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.Tab | KeyCode.CtrlMask)));
-		Assert.Equal (win2, top.Focused);
+		Assert.Equal (win2,  top.Focused);
 		Assert.Equal (tf1W2, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask)));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf2W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (Application.AlternateForwardKey));
-		Assert.Equal (win2, top.Focused);
+		Assert.Equal (win2,  top.Focused);
 		Assert.Equal (tf1W2, top.MostFocused);
 		Assert.True (Application.OnKeyDown (Application.AlternateBackwardKey));
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf2W1, top.MostFocused);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorUp)));
 		Assert.Equal (win1, top.Focused);
@@ -454,23 +427,23 @@ public class ToplevelTests {
 #else
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorLeft)));
 #endif
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf1W1, top.MostFocused);
 
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorDown)));
-		Assert.Equal (win1, top.Focused);
-		Assert.Equal (tvW1, top.MostFocused);
+		Assert.Equal (win1,             top.Focused);
+		Assert.Equal (tvW1,             top.MostFocused);
 		Assert.Equal (new Point (0, 0), tvW1.CursorPosition);
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.End | KeyCode.CtrlMask)));
-		Assert.Equal (win1, top.Focused);
-		Assert.Equal (tvW1, top.MostFocused);
+		Assert.Equal (win1,              top.Focused);
+		Assert.Equal (tvW1,              top.MostFocused);
 		Assert.Equal (new Point (16, 1), tvW1.CursorPosition);
 #if UNIX_KEY_BINDINGS
 			Assert.True (Application.OnKeyDown (new (Key.F | Key.CtrlMask)));
 #else
 		Assert.True (Application.OnKeyDown (new Key (KeyCode.CursorRight)));
 #endif
-		Assert.Equal (win1, top.Focused);
+		Assert.Equal (win1,  top.Focused);
 		Assert.Equal (tf2W1, top.MostFocused);
 
 #if UNIX_KEY_BINDINGS
@@ -537,7 +510,7 @@ public class ToplevelTests {
 		Assert.Null (top.Focused);
 		Assert.Null (top.MostFocused);
 		Assert.Equal (tf1W2, win2.MostFocused);
-		Assert.Equal (2, Application.OverlappedChildren.Count);
+		Assert.Equal (2,     Application.OverlappedChildren.Count);
 
 		Application.MoveToOverlappedChild (win1);
 		Assert.Equal (win1, Application.Current);
@@ -559,13 +532,13 @@ public class ToplevelTests {
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.Tab | KeyCode.ShiftMask)));
 		Assert.Equal ($"First line Win1{Environment.NewLine}Second line Win1", tvW1.Text);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.Tab | KeyCode.CtrlMask)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf2W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.Tab)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorRight)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorDown)));
 		Assert.Equal (win1, Application.OverlappedChildren [0]);
@@ -579,27 +552,27 @@ public class ToplevelTests {
 		Assert.Equal (win1, Application.OverlappedChildren [0]);
 		Assert.Equal (tvW1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorLeft)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorUp)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf2W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.Tab)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.Tab | KeyCode.CtrlMask)));
-		Assert.Equal (win2, Application.OverlappedChildren [0]);
+		Assert.Equal (win2,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W2, win2.MostFocused);
 		tf2W2.SetFocus ();
 		Assert.True (tf2W2.HasFocus);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.Tab | KeyCode.CtrlMask | KeyCode.ShiftMask)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Application.AlternateForwardKey));
-		Assert.Equal (win2, Application.OverlappedChildren [0]);
+		Assert.Equal (win2,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf2W2, win2.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (Application.AlternateBackwardKey));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorDown)));
 		Assert.Equal (win1, Application.OverlappedChildren [0]);
@@ -609,22 +582,22 @@ public class ToplevelTests {
 #else
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorLeft)));
 #endif
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf1W1, win1.MostFocused);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorDown)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
-		Assert.Equal (tvW1, win1.MostFocused);
+		Assert.Equal (win1,             Application.OverlappedChildren [0]);
+		Assert.Equal (tvW1,             win1.MostFocused);
 		Assert.Equal (new Point (0, 0), tvW1.CursorPosition);
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.End | KeyCode.CtrlMask)));
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
-		Assert.Equal (tvW1, win1.MostFocused);
+		Assert.Equal (win1,              Application.OverlappedChildren [0]);
+		Assert.Equal (tvW1,              win1.MostFocused);
 		Assert.Equal (new Point (16, 1), tvW1.CursorPosition);
 #if UNIX_KEY_BINDINGS
 			Assert.True (Application.OverlappedChildren [0].ProcessKeyDown (new (Key.F | Key.CtrlMask)));
 #else
 		Assert.True (Application.OverlappedChildren [0].NewKeyDownEvent (new Key (KeyCode.CursorRight)));
 #endif
-		Assert.Equal (win1, Application.OverlappedChildren [0]);
+		Assert.Equal (win1,  Application.OverlappedChildren [0]);
 		Assert.Equal (tf2W1, win1.MostFocused);
 
 #if UNIX_KEY_BINDINGS
@@ -693,16 +666,16 @@ public class ToplevelTests {
 		Assert.Equal (KeyCode.Null, quitKey);
 
 		Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey);
-		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey);
-		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey);
+		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask,   Application.AlternateBackwardKey);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask,        Application.QuitKey);
 
 		Application.AlternateForwardKey = KeyCode.A;
 		Application.AlternateBackwardKey = KeyCode.B;
 		Application.QuitKey = KeyCode.C;
 
 		Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, alternateForwardKey);
-		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, alternateBackwardKey);
-		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, quitKey);
+		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask,   alternateBackwardKey);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask,        quitKey);
 
 		Assert.Equal (KeyCode.A, Application.AlternateForwardKey);
 		Assert.Equal (KeyCode.B, Application.AlternateBackwardKey);
@@ -714,8 +687,8 @@ public class ToplevelTests {
 		Application.QuitKey = KeyCode.Q | KeyCode.CtrlMask;
 
 		Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey);
-		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey);
-		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey);
+		Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask,   Application.AlternateBackwardKey);
+		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask,        Application.QuitKey);
 	}
 
 	[Fact]
@@ -761,7 +734,7 @@ public class ToplevelTests {
 					Flags = MouseFlags.Button1Pressed
 				}));
 
-				Assert.Equal (Application.Current, Application.MouseGrabView);
+				Assert.Equal (Application.Current,    Application.MouseGrabView);
 				Assert.Equal (new Rect (2, 2, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 3) {
@@ -774,7 +747,7 @@ public class ToplevelTests {
 				}));
 				Application.Refresh ();
 
-				Assert.Equal (Application.Current, Application.MouseGrabView);
+				Assert.Equal (Application.Current,    Application.MouseGrabView);
 				Assert.Equal (new Rect (1, 2, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 4) {
@@ -800,7 +773,7 @@ public class ToplevelTests {
 				}));
 				Application.Refresh ();
 
-				Assert.Equal (Application.Current, Application.MouseGrabView);
+				Assert.Equal (Application.Current,    Application.MouseGrabView);
 				Assert.Equal (new Rect (1, 1, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 6) {
@@ -815,7 +788,7 @@ public class ToplevelTests {
 │             │
 └─────────────┘", _output);
 
-				Assert.Equal (Application.Current, Application.MouseGrabView);
+				Assert.Equal (Application.Current,    Application.MouseGrabView);
 				Assert.Equal (new Rect (1, 1, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 7) {
@@ -875,7 +848,7 @@ public class ToplevelTests {
 					Flags = MouseFlags.Button1Pressed
 				}));
 
-				Assert.Equal (win, Application.MouseGrabView);
+				Assert.Equal (win,      Application.MouseGrabView);
 				Assert.Equal (location, Application.MouseGrabView.Frame);
 			} else if (iterations == 2) {
 				Assert.Equal (win, Application.MouseGrabView);
@@ -1168,10 +1141,10 @@ public class ToplevelTests {
 		top.Add (scrollView);
 		Application.Begin (top);
 
-		Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
-		Assert.Equal (new Rect (3, 3, 40, 16), scrollView.Frame);
+		Assert.Equal (new Rect (0, 0, 80,  25),  top.Frame);
+		Assert.Equal (new Rect (3, 3, 40,  16),  scrollView.Frame);
 		Assert.Equal (new Rect (0, 0, 200, 100), scrollView.Subviews [0].Frame);
-		Assert.Equal (new Rect (3, 3, 194, 94), win.Frame);
+		Assert.Equal (new Rect (3, 3, 194, 94),  win.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -1195,7 +1168,7 @@ public class ToplevelTests {
 			Y = 6,
 			Flags = MouseFlags.Button1Pressed
 		}));
-		Assert.Equal (win, Application.MouseGrabView);
+		Assert.Equal (win,                      Application.MouseGrabView);
 		Assert.Equal (new Rect (3, 3, 194, 94), win.Frame);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent {
@@ -1280,7 +1253,7 @@ public class ToplevelTests {
 		Application.Begin (window);
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 40, 10), top.Frame);
-		Assert.Equal (new Rect (0, 0, 20, 3), window.Frame);
+		Assert.Equal (new Rect (0, 0, 20, 3),  window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────────┐
 │                  │
@@ -1305,7 +1278,7 @@ public class ToplevelTests {
 
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 40, 10), top.Frame);
-		Assert.Equal (new Rect (0, 0, 20, 3), window.Frame);
+		Assert.Equal (new Rect (0, 0, 20, 3),  window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
 ┌──────────────────┐
 │                  │
@@ -1338,7 +1311,7 @@ public class ToplevelTests {
 		}));
 
 		Application.Refresh ();
-		Assert.Equal (new Rect (0, 0, 19, 2), top.Frame);
+		Assert.Equal (new Rect (0,  0, 19, 2), top.Frame);
 		Assert.Equal (new Rect (-1, 0, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
 ──────────────────┐
@@ -1352,7 +1325,7 @@ public class ToplevelTests {
 		}));
 
 		Application.Refresh ();
-		Assert.Equal (new Rect (0, 0, 19, 2), top.Frame);
+		Assert.Equal (new Rect (0,  0, 19, 2), top.Frame);
 		Assert.Equal (new Rect (18, 1, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
                   ┌", _output);
@@ -1365,7 +1338,7 @@ public class ToplevelTests {
 		}));
 
 		Application.Refresh ();
-		Assert.Equal (new Rect (0, 0, 19, 2), top.Frame);
+		Assert.Equal (new Rect (0,  0, 19, 2), top.Frame);
 		Assert.Equal (new Rect (19, 2, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
 	}
@@ -1420,7 +1393,7 @@ public class ToplevelTests {
 
 		firstIteration = false;
 		Application.RunIteration (ref rs, ref firstIteration);
-		Assert.Equal (window, Application.MouseGrabView);
+		Assert.Equal (window,                 Application.MouseGrabView);
 		Assert.Equal (new Rect (1, 1, 10, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
  ┌────────┐

+ 26 - 25
UnitTests/Views/Toplevel/WindowTests.cs → UnitTests/Views/WindowTests.cs

@@ -1,8 +1,7 @@
-using Terminal.Gui;
-using Xunit;
+using Xunit;
 using Xunit.Abstractions;
 
-namespace TerminalGui.ViewsTests;
+namespace Terminal.Gui.ViewsTests; 
 
 public class WindowTests {
 	readonly ITestOutputHelper _output;
@@ -16,18 +15,20 @@ public class WindowTests {
 		var r = new Window ();
 		Assert.NotNull (r);
 		Assert.Equal (string.Empty, r.Title);
+		// Toplevels have Width/Height set to Dim.Fill
 		Assert.Equal (LayoutStyle.Computed, r.LayoutStyle);
-		Assert.Equal ("Window()(0,0,0,0)", r.ToString ());
+		// If there's no SuperView, Top, or Driver, the default Fill width is int.MaxValue
+		Assert.Equal ("Window()(0,0,2147483647,2147483647)", 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.Equal (new Rect (0, 0, 2147483645, 2147483645), r.Bounds);
+		Assert.Equal (new Rect (0, 0, 2147483647, 2147483647), r.Frame);
 		Assert.Null (r.Focused);
 		Assert.NotNull (r.ColorScheme);
+		Assert.Equal (0,           r.X);
+		Assert.Equal (0,           r.Y);
 		Assert.Equal (Dim.Fill (), r.Width);
 		Assert.Equal (Dim.Fill (), r.Height);
-		Assert.Null (r.X);
-		Assert.Null (r.Y);
 		Assert.False (r.IsCurrentTop);
 		Assert.Empty (r.Id);
 		Assert.False (r.WantContinuousButtonPressed);
@@ -39,8 +40,10 @@ public class WindowTests {
 		// Empty Rect
 		r = new Window (Rect.Empty) { Title = "title" };
 		Assert.NotNull (r);
-		Assert.Equal ("title", r.Title);
-		Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+		Assert.Equal ("title",                  r.Title);
+		Assert.Equal (LayoutStyle.Absolute,     r.LayoutStyle);
+		Assert.Equal ("title",                  r.Title);
+		Assert.Equal (LayoutStyle.Absolute,     r.LayoutStyle);
 		Assert.Equal ("Window(title)(0,0,0,0)", r.ToString ());
 		Assert.True (r.CanFocus);
 		Assert.False (r.HasFocus);
@@ -48,10 +51,10 @@ public class WindowTests {
 		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.Equal (0, r.X);
+		Assert.Equal (0, r.Y);
+		Assert.Equal (0, r.Width);
+		Assert.Equal (0, r.Height);
 		Assert.False (r.IsCurrentTop);
 		Assert.Equal (r.Title, r.Id);
 		Assert.False (r.WantContinuousButtonPressed);
@@ -64,7 +67,8 @@ public class WindowTests {
 		r = new Window (new Rect (1, 2, 3, 4)) { Title = "title" };
 		Assert.Equal ("title", r.Title);
 		Assert.NotNull (r);
-		Assert.Equal (LayoutStyle.Absolute, r.LayoutStyle);
+		Assert.Equal (LayoutStyle.Absolute,     r.LayoutStyle);
+		Assert.Equal (LayoutStyle.Absolute,     r.LayoutStyle);
 		Assert.Equal ("Window(title)(1,2,3,4)", r.ToString ());
 		Assert.True (r.CanFocus);
 		Assert.False (r.HasFocus);
@@ -72,10 +76,10 @@ public class WindowTests {
 		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.Equal (1, r.X);
+		Assert.Equal (2, r.Y);
+		Assert.Equal (3, r.Width);
+		Assert.Equal (4, r.Height);
 		Assert.False (r.IsCurrentTop);
 		Assert.Equal (r.Title, r.Id);
 		Assert.False (r.WantContinuousButtonPressed);
@@ -86,8 +90,7 @@ public class WindowTests {
 		r.Dispose ();
 	}
 
-	[Fact]
-	[AutoInitShutdown]
+	[Fact] [AutoInitShutdown]
 	public void MenuBar_And_StatusBar_Inside_Window ()
 	{
 		var menu = new MenuBar (new MenuBarItem [] {
@@ -169,8 +172,7 @@ public class WindowTests {
 └──────────────────┘", _output);
 	}
 
-	[Fact]
-	[AutoInitShutdown]
+	[Fact] [AutoInitShutdown]
 	public void OnCanFocusChanged_Only_Must_ContentView_Forces_SetFocus_After_IsInitialized_Is_True ()
 	{
 		var win1 = new Window { Id = "win1", Width = 10, Height = 1 };
@@ -188,8 +190,7 @@ public class WindowTests {
 		Assert.False (view2.HasFocus);
 	}
 
-	[Fact]
-	[AutoInitShutdown]
+	[Fact] [AutoInitShutdown]
 	public void Activating_MenuBar_By_Alt_Key_Does_Not_Throw ()
 	{
 		var menu = new MenuBar (new MenuBarItem [] {

+ 1 - 9
pull_request_template.md

@@ -1,12 +1,4 @@
-## Fixes:
-
-(Include a list of issues that this PR fixes. If this PR is a work in progress, include a list of issues that this PR is related to. Use the format `Fixes #issue` to automatically close the issue when this PR is merged.)
-
-- Fixes #____ 
-
-## Todos:
-
-- [ ] - Include a list of tasks that need to be completed for this PR to be considered complete.
+Fixes #_____ - Include a terse summary of the change or which issue is fixed.
 
 ## Pull Request checklist:
 

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff