Browse Source

Improves robustness of `Dim`, `Pos`, and `SetRelativeLayout` (#3077)

* Updated overview docs

* Updated toc

* Updated docs more

* Updated yml via dependabot

* Initial work in progress

* Fixed some autosize things

* Revamped Pos / Dim API docs

* Removed margin

* horiz->width

* Updated MessageBoxes and Dialogs Scenarios to use AutoSize

* AutoSize->Auxo

* Adds validation

* prep for Dialog to use Dim.Auto - Simplify unit tests to not depend on things not important to the unit test (like Dialog)

* prep for Dialog to use Dim.Auto - Simplify unit tests

* prep for Dialog to use Dim.Auto - Simplify unit tests

* prep for Dialog to use Dim.Auto - Make Dialog tests not depend on MessageBox

* Started on DimAuto unit tests

* started impl on min/max.

* started impl on min/max.

* Added DimAutoStyle

* Added arg checking for not implemented features

* Temporarily made DimAutoStyle.Subviews default

* Removed unneeded override of Anchor

* Fixed GethashCode warning

* Implemented DimAuto(min)

* Fixed unit tests

* renamed scenario

* WIP

* Moved ViewLayout.cs into Layout folder

* Clean up cocde formatting

* Renamed and moved SetFrameToFitText

* Fixed API docs for SetRelativeLayout

* Factored out SetRelativeLayout tests

* Better documented existing SetRelativeLayout behavior + unit tess

* Debugging Pos.Center + x in SetRelativeLayout - WIP

* Progress on low level unit tess

* Initial commit

* Restored unmodified scenarios

* Bump deps
Tig 1 year ago
parent
commit
a7209bcd88

+ 2 - 2
.github/workflows/api-docs.yml

@@ -36,14 +36,14 @@ jobs:
       
     - name: Upload artifact
       if: github.ref_name == 'main' ||  github.ref_name == 'develop'
-      uses: actions/upload-pages-artifact@v2
+      uses: actions/upload-pages-artifact@v3
       with:
         path: docfx/_site
        
     - name: Deploy to GitHub Pages
       if: github.ref_name == 'main' ||  github.ref_name == 'develop'
       id: deployment
-      uses: actions/deploy-pages@v3
+      uses: actions/deploy-pages@v4
       with:
         token: ${{ secrets.GITHUB_TOKEN }}
 

+ 8 - 0
Terminal.Gui/Application.cs

@@ -317,6 +317,8 @@ public static partial class Application {
 			} else if (Top != null && Toplevel != Top && _topLevels.Contains (Top)) {
 				Top.OnLeave (Toplevel);
 			}
+			// BUGBUG: We should not depend on `Id` internally. 
+			// BUGBUG: It is super unclear what this code does anyway.
 			if (string.IsNullOrEmpty (Toplevel.Id)) {
 				int count = 1;
 				string id = (_topLevels.Count + count).ToString ();
@@ -836,6 +838,12 @@ public static partial class Application {
 	#endregion Run (Begin, Run, End)
 
 	#region Toplevel handling
+
+	/// <summary>
+	/// Holds the stack of TopLevel views.
+	/// </summary>
+	// BUGBUG: Techncally, this is not the full lst of TopLevels. THere be dragons hwre. E.g. see how Toplevel.Id is used. What
+	// about TopLevels that are just a SubView of another View?
 	static readonly Stack<Toplevel> _topLevels = new ();
 
 	/// <summary>

+ 1 - 1
Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

@@ -1077,7 +1077,7 @@ internal class WindowsDriver : ConsoleDriver {
 				inputEvent.KeyEvent = FromVKPacketToKeyEventRecord (inputEvent.KeyEvent);
 			}
 			var keyInfo = ToConsoleKeyInfoEx (inputEvent.KeyEvent);
-			Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
+			//Debug.WriteLine ($"event: {inputEvent.ToString ()} {keyInfo.ToString (keyInfo)}");
 
 
 			var map = MapKey (keyInfo);

+ 0 - 1232
Terminal.Gui/Text/ViewLayout.cs

@@ -1,1232 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-
-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.
-	/// </summary>
-	public enum LayoutStyle {
-		/// <summary>
-		/// The position and size of the view are based <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.
-		/// </summary>
-		Computed
-	}
-
-	public partial class View {
-
-		// The frame for the object. Relative to the SuperView's Bounds.
-		Rect _frame;
-
-		/// <summary>
-		/// Gets or sets the frame for the view. The frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
-		/// </summary>
-		/// <value>The frame.</value>
-		/// <remarks>
-		/// <para>
-		///    Change the Frame when using the <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout style to move or resize views. 
-		/// </para>
-		/// <para>
-		///    Altering the Frame of a view will trigger the redrawing of the
-		///    view as well as the redrawing of the affected regions of the <see cref="SuperView"/>.
-		/// </para>
-		/// </remarks>
-		public virtual Rect Frame {
-			get => _frame;
-			set {
-				_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
-				if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
-					LayoutFrames ();
-					TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-					SetNeedsLayout ();
-					SetNeedsDisplay ();
-				}
-			}
-		}
-
-		/// <summary>
-		/// The frame (specified as a <see cref="Thickness"/>) that separates a View from other SubViews of the same SuperView. 
-		/// The margin offsets the <see cref="Bounds"/> from the <see cref="Frame"/>. 
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// 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>
-		/// Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-		/// will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
-		/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
-		/// </para>
-		/// </remarks>
-		public Frame Margin { get; private set; }
-
-		/// <summary>
-		/// The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the <see cref="Margin"/>. 
-		///  The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. 
-		///  The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and 
-		///  title will take up the first row and the second row will be filled with spaces. 
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// <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
-		/// and are not clipped by the View's Clip Area.
-		/// </para>
-		/// <para>
-		/// Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-		/// will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
-		/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
-		/// </para>
-		/// </remarks>
-		public Frame Border { get; private set; }
-
-		/// <summary>
-		/// Gets or sets whether the view has a one row/col thick border.
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// This is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other than
-		/// <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/> 
-		/// to `1` and <see cref="BorderStyle"/> to the value. 
-		/// </para>
-		/// <para>
-		/// Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/> 
-		/// to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>. 
-		/// </para>
-		/// <para>
-		/// For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.
-		/// </para>
-		/// </remarks>
-		public LineStyle BorderStyle {
-			get {
-				return Border?.BorderStyle ?? LineStyle.None;
-			}
-			set {
-				if (Border == null) {
-					throw new InvalidOperationException ("Border is null; this is likely a bug.");
-				}
-				if (value != LineStyle.None) {
-					Border.Thickness = new Thickness (1);
-				} else {
-					Border.Thickness = new Thickness (0);
-				}
-				Border.BorderStyle = value;
-				LayoutFrames ();
-				SetNeedsLayout ();
-			}
-		}
-
-		/// <summary>
-		/// The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the <see cref="Border"/>. 
-		/// </summary>
-		/// <remarks>
-		/// <para>
-		/// 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>
-		/// Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
-		/// will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
-		/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
-		/// </para>
-		/// </remarks>
-		public Frame Padding { get; private set; }
-
-		/// <summary>
-		/// Helper to get the total thickness of the <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>. 
-		/// </summary>
-		/// <returns>A thickness that describes the sum of the Frames' thicknesses.</returns>
-		public Thickness GetFramesThickness ()
-		{
-			var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
-			var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
-			var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
-			var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
-			return new Thickness (left, top, right, bottom);
-		}
-
-		/// <summary>
-		/// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
-		/// <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
-		/// </summary>
-		public Point GetBoundsOffset () => new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
-
-		/// <summary>
-		/// Creates the view's <see cref="Frame"/> objects. This internal method is overridden by Frame to do nothing
-		/// to prevent recursion during View construction.
-		/// </summary>
-		internal virtual void CreateFrames ()
-		{
-			void ThicknessChangedHandler (object sender, EventArgs e)
-			{
-				LayoutFrames ();
-				SetNeedsLayout ();
-				SetNeedsDisplay ();
-			}
-
-			if (Margin != null) {
-				Margin.ThicknessChanged -= ThicknessChangedHandler;
-				Margin.Dispose ();
-			}
-			Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) };
-			Margin.ThicknessChanged += ThicknessChangedHandler;
-			Margin.Parent = this;
-
-			if (Border != null) {
-				Border.ThicknessChanged -= ThicknessChangedHandler;
-				Border.Dispose ();
-			}
-			Border = new Frame () { Id = "Border", Thickness = new Thickness (0) };
-			Border.ThicknessChanged += ThicknessChangedHandler;
-			Border.Parent = this;
-
-			// TODO: Create View.AddAdornment
-
-			if (Padding != null) {
-				Padding.ThicknessChanged -= ThicknessChangedHandler;
-				Padding.Dispose ();
-			}
-			Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) };
-			Padding.ThicknessChanged += ThicknessChangedHandler;
-			Padding.Parent = this;
-		}
-
-		LayoutStyle _layoutStyle;
-
-		/// <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>
-		/// <value>The layout style.</value>
-		public LayoutStyle LayoutStyle {
-			get => _layoutStyle;
-			set {
-				_layoutStyle = value;
-				SetNeedsLayout ();
-			}
-		}
-
-		/// <summary>
-		/// The view's content area.
-		/// <para>
-		/// SubViews are positioned relative to Bounds.
-		/// </para>
-		/// <para>
-		/// Drawing is clipped to Bounds (<see cref="Draw()"/> clips drawing to Bounds.<see cref="Rect.Size">Size</see>).
-		/// </para>
-		/// <para>
-		/// Mouse events are reported relative to Bounds.
-		/// </para>
-		/// </summary>
-		/// <value>The view's content area.</value>
-		/// <remarks>
-		/// <para>
-		/// The <see cref="Rect.Location"/> of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use 
-		/// <see cref="GetBoundsOffset"/>.
-		/// </para>
-		/// <para>
-		/// When using <see cref="LayoutStyle.Computed"/>, Bounds is not valid until after the view has been initialized (after <see cref="EndInit"/> has been called and <see cref="Initialized"/>
-		/// has fired). Accessing this property before the view is initialized is considered an error./>
-		/// </para>
-		/// </remarks>
-		public virtual Rect Bounds {
-			get {
-#if DEBUG
-				if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
-					Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug. View: {this}");
-					Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
-				}
-#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))
-				Frame = new Rect (Frame.Location,
-					new Size (
-						value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
-						value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
-						)
-					);
-			}
-		}
-
-		private Rect FrameGetInsideBounds ()
-		{
-			if (Margin == null || Border == null || Padding == null) {
-				return new Rect (default, Frame.Size);
-			}
-			var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
-			var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
-			return new Rect (Point.Empty, new Size (width, height));
-		}
-
-		// Diagnostics to highlight when X or Y is read before the view has been initialized
-		Pos VerifyIsInitialized (Pos pos)
-		{
-#if DEBUG
-			if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) {
-				Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug.");
-			}
-#endif // DEBUG
-			return pos;
-		}
-
-		// Diagnostics to highlight when Width or Height is read before the view has been initialized
-		Dim VerifyIsInitialized (Dim dim)
-		{
-#if DEBUG
-			if (LayoutStyle == LayoutStyle.Computed && (!IsInitialized)) {
-				Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug.");
-			}
-#endif // DEBUG		
-			return dim;
-		}
-
-		Pos _x, _y;
-
-		/// <summary>
-		/// Gets or sets the X position for the view (the column). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
-		/// </summary>
-		/// <value>The X Position.</value>
-		/// <remarks>
-		/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate. 
-		/// </remarks>
-		public Pos X {
-			get => VerifyIsInitialized (_x);
-			set {
-				if (ForceValidatePosDim && !ValidatePosDim (_x, value)) {
-					throw new ArgumentException ();
-				}
-
-				_x = value;
-
-				OnResizeNeeded ();
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets the Y position for the view (the row). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
-		/// </summary>
-		/// <value>The y position (line).</value>
-		/// <remarks>
-		/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate. 
-		/// </remarks>
-		public Pos Y {
-			get => VerifyIsInitialized (_y);
-			set {
-				if (ForceValidatePosDim && !ValidatePosDim (_y, value)) {
-					throw new ArgumentException ();
-				}
-
-				_y = value;
-
-				OnResizeNeeded ();
-			}
-		}
-		Dim _width, _height;
-
-		/// <summary>
-		/// Gets or sets the width of the view. Only used the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
-		/// </summary>
-		/// <value>The width.</value>
-		/// <remarks>
-		/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate. 
-		/// </remarks>
-		public Dim Width {
-			get => VerifyIsInitialized (_width);
-			set {
-				if (ForceValidatePosDim && !ValidatePosDim (_width, value)) {
-					throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Width));
-				}
-
-				_width = value;
-
-				if (ForceValidatePosDim) {
-					var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
-
-					if (IsAdded && AutoSize && !isValidNewAutSize) {
-						throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
-					}
-				}
-				OnResizeNeeded ();
-			}
-		}
-
-		/// <summary>
-		/// Gets or sets the height of the view. Only used the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
-		/// </summary>
-		/// <value>The height.</value>
-		/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate. 
-		public Dim Height {
-			get => VerifyIsInitialized (_height);
-			set {
-				if (ForceValidatePosDim && !ValidatePosDim (_height, value)) {
-					throw new ArgumentException ("ForceValidatePosDim is enabled", nameof (Height));
-				}
-
-				_height = value;
-
-				if (ForceValidatePosDim) {
-					var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
-
-					if (IsAdded && AutoSize && !isValidNewAutSize) {
-						throw new InvalidOperationException ("Must set AutoSize to false before set the Height.");
-					}
-				}
-				OnResizeNeeded ();
-			}
-		}
-
-		/// <summary>
-		/// Forces validation with <see cref="Terminal.Gui.LayoutStyle.Computed"/> layout
-		///  to avoid breaking the <see cref="Pos"/> and <see cref="Dim"/> settings.
-		/// </summary>
-		public bool ForceValidatePosDim { get; set; }
-
-		bool ValidatePosDim (object oldValue, object newValue)
-		{
-			if (!IsInitialized || _layoutStyle == LayoutStyle.Absolute || oldValue == null || oldValue.GetType () == newValue.GetType () || this is Toplevel) {
-				return true;
-			}
-			if (_layoutStyle == LayoutStyle.Computed) {
-				if (oldValue.GetType () != newValue.GetType () && !(newValue is Pos.PosAbsolute || newValue is Dim.DimAbsolute)) {
-					return true;
-				}
-			}
-			return false;
-		}
-
-		// BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
-		// BUGBUG: this function does not belong in ViewLayout.cs - it should be in ViewText.cs
-		/// <summary>
-		/// Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
-		/// </summary>
-		/// <param name="size">The minimum dimensions required.</param>
-		/// <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</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.
-		/// Does not take into account word wrapping.
-		/// </remarks>
-		bool GetMinimumBoundsForFrame (out Size size)
-		{
-			if (!IsInitialized) {
-				size = new Size (0, 0);
-				return false;
-			}
-			size = Bounds.Size;
-
-			if (!AutoSize && !string.IsNullOrEmpty (TextFormatter.Text)) {
-				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))) {
-						size = new Size (colWidth, Bounds.Height);
-						return true;
-					}
-					break;
-				default:
-					if (_frame.Height < 1 &&
-						(Height == null ||
-							(Height is Dim.DimAbsolute &&
-								Height.Anchor (0) == 0))) {
-						size = new Size (Bounds.Width, 1);
-						return true;
-					}
-					break;
-				}
-			}
-			return false;
-		}
-
-		// BUGBUG: this function does not belong in ViewLayout.cs - it should be in ViewText.cs
-		/// <summary>
-		/// Sets the size of the View to the minimum width or height required to fit <see cref="Text"/> (see <see cref="GetMinimumBoundsForFrame"/>.
-		/// </summary>
-		/// <returns><see langword="true"/> if the size was changed, <see langword="false"/> if <see cref="Text"/>
-		/// will not fit.</returns>
-		bool SetBoundsToFitFrame ()
-		{
-			if (GetMinimumBoundsForFrame (out Size size)) {
-				_frame = new Rect (_frame.Location, size);
-				return true;
-			}
-			return false;
-		}
-
-		/// <summary>
-		/// Called whenever the view needs to be resized. Sets <see cref="Frame"/> and
-		/// triggers a <see cref="LayoutSubviews()"/> call.
-		/// </summary>
-		/// <remarks>
-		/// Can be overridden if the view resize behavior is different than the default.
-		/// </remarks>
-		protected virtual 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) {
-				SetBoundsToFitFrame ();
-				LayoutFrames ();
-				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-				SetNeedsLayout ();
-				SetNeedsDisplay ();
-			}
-		}
-
-		internal bool LayoutNeeded { get; private set; } = true;
-
-		internal void SetNeedsLayout ()
-		{
-			if (LayoutNeeded) {
-				return;
-			}
-			LayoutNeeded = true;
-			foreach (var view in Subviews) {
-				view.SetNeedsLayout ();
-			}
-			TextFormatter.NeedsFormat = true;
-			SuperView?.SetNeedsLayout ();
-		}
-
-		/// <summary>
-		/// Indicates that the view does not need to be laid out.
-		/// </summary>
-		protected void ClearLayoutNeeded ()
-		{
-			LayoutNeeded = false;
-		}
-
-		/// <summary>
-		/// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means
-		/// relative to the View's <see cref="SuperView"/>'s <see cref="Bounds"/>.
-		/// </summary>
-		/// <returns>The coordinate relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</returns>
-		/// <param name="x">Screen-relative column.</param>
-		/// <param name="y">Screen-relative row.</param>
-		public Point ScreenToFrame (int x, int y)
-		{
-			Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
-			var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y);
-			if (SuperView != null) {
-				var superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
-				ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y);
-			}
-			return ret;
-		}
-
-		/// <summary>
-		/// Converts a screen-relative coordinate to a bounds-relative coordinate. 
-		/// </summary>
-		/// <returns>The coordinate relative to this view's <see cref="Bounds"/>.</returns>
-		/// <param name="x">Screen-relative column.</param>
-		/// <param name="y">Screen-relative row.</param>
-		public Point ScreenToBounds (int x, int y)
-		{
-			var screen = ScreenToFrame (x, y);
-			var boundsOffset = GetBoundsOffset ();
-			return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y);
-		}
-
-		/// <summary>
-		/// Converts a <see cref="Bounds"/>-relative coordinate to a screen-relative coordinate. The output is optionally clamped to the screen dimensions.
-		/// </summary>
-		/// <param name="x"><see cref="Bounds"/>-relative column.</param>
-		/// <param name="y"><see cref="Bounds"/>-relative row.</param>
-		/// <param name="rx">Absolute column; screen-relative.</param>
-		/// <param name="ry">Absolute row; screen-relative.</param>
-		/// <param name="clamped">If <see langword="true"/>, <paramref name="rx"/> and <paramref name="ry"/> will be clamped to the 
-		/// screen dimensions (will never be negative and will always be less than <see cref="ConsoleDriver.Cols"/> and
-		/// <see cref="ConsoleDriver.Rows"/>, respectively.</param>
-		public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true)
-		{
-			var boundsOffset = GetBoundsOffset ();
-			rx = x + Frame.X + boundsOffset.X;
-			ry = y + Frame.Y + boundsOffset.Y;
-
-			var super = SuperView;
-			while (super != null) {
-				boundsOffset = super.GetBoundsOffset ();
-				rx += super.Frame.X + boundsOffset.X;
-				ry += super.Frame.Y + boundsOffset.Y;
-				super = super.SuperView;
-			}
-
-			// The following ensures that the cursor is always in the screen boundaries.
-			if (clamped) {
-				ry = Math.Min (ry, Driver.Rows - 1);
-				rx = Math.Min (rx, Driver.Cols - 1);
-			}
-		}
-
-		/// <summary>
-		/// Converts a <see cref="Bounds"/>-relative region to a screen-relative region. 
-		/// </summary>
-		public Rect BoundsToScreen (Rect region)
-		{
-			BoundsToScreen (region.X, region.Y, out var x, out var y, clamped: false);
-			return new Rect (x, y, region.Width, region.Height);
-		}
-
-		/// <summary>
-		/// Gets the <see cref="Frame"/> with a screen-relative location. 
-		/// </summary>
-		/// <returns>The location and size of the view in screen-relative coordinates.</returns>
-		public virtual Rect FrameToScreen ()
-		{
-			var ret = Frame;
-			var super = SuperView;
-			while (super != null) {
-				var boundsOffset = super.GetBoundsOffset ();
-				ret.X += super.Frame.X + boundsOffset.X;
-				ret.Y += super.Frame.Y + boundsOffset.Y;
-				super = super.SuperView;
-			}
-			return ret;
-		}
-
-		/// <summary>
-		/// Sets the View's <see cref="Frame"/> to the frame-relative coordinates if its container. The
-		/// container size and location are specified by <paramref name="superviewFrame"/> and are relative to the
-		/// View's superview.
-		/// </summary>
-		/// <param name="superviewFrame">The SuperView-relative rectangle describing View's container (nominally the 
-		/// same as <c>this.SuperView.Frame</c>).</param>
-		internal void SetRelativeLayout (Rect superviewFrame)
-		{
-			int newX, newW, newY, newH;
-			var autosize = Size.Empty;
-
-			if (AutoSize) {
-				// Note this is global to this function and used as such within the local functions defined
-				// below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
-				autosize = GetAutoSize ();
-			}
-
-			// Returns the new dimension (width or height) and location (x or y) for the View given
-			//   the superview's Frame.X or Frame.Y
-			//   the superview's width or height
-			//   the current Pos (View.X or View.Y)
-			//   the current Dim (View.Width or View.Height)
-			(int newLocation, int newDimension) GetNewLocationAndDimension (int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension)
-			{
-				int newDimension, newLocation;
-
-				switch (pos) {
-				case Pos.PosCenter:
-					if (dim == null) {
-						newDimension = AutoSize ? autosizeDimension : superviewDimension;
-					} else {
-						newDimension = dim.Anchor (superviewDimension);
-						newDimension = AutoSize && autosizeDimension > newDimension ? autosizeDimension : newDimension;
-					}
-					newLocation = pos.Anchor (superviewDimension - newDimension);
-					break;
-
-				case Pos.PosCombine combine:
-					int left, right;
-					(left, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.left, dim, autosizeDimension);
-					(right, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.right, dim, autosizeDimension);
-					if (combine.add) {
-						newLocation = left + right;
-					} else {
-						newLocation = left - right;
-					}
-					newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
-					break;
-
-				case Pos.PosAbsolute:
-				case Pos.PosAnchorEnd:
-				case Pos.PosFactor:
-				case Pos.PosFunc:
-				case Pos.PosView:
-				default:
-					newLocation = pos?.Anchor (superviewDimension) ?? 0;
-					newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
-					break;
-				}
-				return (newLocation, newDimension);
-			}
-
-			// Recursively calculates the new dimension (width or height) of the given Dim given:
-			//   the current location (x or y)
-			//   the current dimension (width or height)
-			int CalculateNewDimension (Dim d, int location, int dimension, int autosize)
-			{
-				int newDimension;
-				switch (d) {
-				case null:
-					newDimension = AutoSize ? autosize : dimension;
-					break;
-				case Dim.DimCombine combine:
-					int leftNewDim = CalculateNewDimension (combine.left, location, dimension, autosize);
-					int rightNewDim = CalculateNewDimension (combine.right, location, dimension, autosize);
-					if (combine.add) {
-						newDimension = leftNewDim + rightNewDim;
-					} else {
-						newDimension = leftNewDim - rightNewDim;
-					}
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-
-				case Dim.DimFactor factor when !factor.IsFromRemaining ():
-					newDimension = d.Anchor (dimension);
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-
-				case Dim.DimFill:
-				default:
-					newDimension = Math.Max (d.Anchor (dimension - location), 0);
-					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
-					break;
-				}
-
-				return newDimension;
-			}
-
-			// horizontal
-			(newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
-
-			// vertical
-			(newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
-
-			var r = new Rect (newX, newY, newW, newH);
-			if (Frame != r) {
-				Frame = r;
-				// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
-				if (!SetBoundsToFitFrame ()) {
-					TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-				}
-			}
-		}
-
-		/// <summary>
-		/// Fired after the View's <see cref="LayoutSubviews"/> method has completed. 
-		/// </summary>
-		/// <remarks>
-		/// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise changed.
-		/// </remarks>
-		public event EventHandler<LayoutEventArgs> LayoutStarted;
-
-		/// <summary>
-		/// Raises the <see cref="LayoutStarted"/> event. Called from  <see cref="LayoutSubviews"/> before any subviews have been laid out.
-		/// </summary>
-		internal virtual void OnLayoutStarted (LayoutEventArgs args)
-		{
-			LayoutStarted?.Invoke (this, args);
-		}
-
-		/// <summary>
-		/// Fired after the View's <see cref="LayoutSubviews"/> method has completed. 
-		/// </summary>
-		/// <remarks>
-		/// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise changed.
-		/// </remarks>
-		public event EventHandler<LayoutEventArgs> LayoutComplete;
-
-		/// <summary>
-		/// Event called only once when the <see cref="View"/> is being initialized for the first time.
-		/// Allows configurations and assignments to be performed before the <see cref="View"/> being shown.
-		/// This derived from <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being initialized.
-		/// </summary>
-		public event EventHandler Initialized;
-
-		/// <summary>
-		/// 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);
-		}
-
-		internal void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
-		{
-			switch (pos) {
-			case Pos.PosView pv:
-				// See #2461
-				//if (!from.InternalSubviews.Contains (pv.Target)) {
-				//	throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
-				//}
-				if (pv.Target != this) {
-					nEdges.Add ((pv.Target, from));
-				}
-				return;
-			case Pos.PosCombine pc:
-				CollectPos (pc.left, from, ref nNodes, ref nEdges);
-				CollectPos (pc.right, from, ref nNodes, ref nEdges);
-				break;
-			}
-		}
-
-		internal void CollectDim (Dim dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
-		{
-			switch (dim) {
-			case Dim.DimView dv:
-				// See #2461
-				//if (!from.InternalSubviews.Contains (dv.Target)) {
-				//	throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
-				//}
-				if (dv.Target != this) {
-					nEdges.Add ((dv.Target, from));
-				}
-				return;
-			case Dim.DimCombine dc:
-				CollectDim (dc.left, from, ref nNodes, ref nEdges);
-				CollectDim (dc.right, from, ref nNodes, ref nEdges);
-				break;
-			}
-		}
-
-		internal void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
-		{
-			foreach (var v in from.InternalSubviews) {
-				nNodes.Add (v);
-				if (v._layoutStyle != LayoutStyle.Computed) {
-					continue;
-				}
-				CollectPos (v.X, v, ref nNodes, ref nEdges);
-				CollectPos (v.Y, v, ref nNodes, ref nEdges);
-				CollectDim (v.Width, v, ref nNodes, ref nEdges);
-				CollectDim (v.Height, v, ref nNodes, ref nEdges);
-			}
-		}
-
-		// https://en.wikipedia.org/wiki/Topological_sorting
-		internal static List<View> TopologicalSort (View superView, IEnumerable<View> nodes, ICollection<(View From, View To)> edges)
-		{
-			var result = new List<View> ();
-
-			// Set of all nodes with no incoming edges
-			var noEdgeNodes = new HashSet<View> (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
-
-			while (noEdgeNodes.Any ()) {
-				//  remove a node n from S
-				var n = noEdgeNodes.First ();
-				noEdgeNodes.Remove (n);
-
-				// add n to tail of L
-				if (n != superView)
-					result.Add (n);
-
-				// for each node m with an edge e from n to m do
-				foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
-					var m = e.To;
-
-					// remove edge e from the graph
-					edges.Remove (e);
-
-					// if m has no other incoming edges then
-					if (edges.All (me => !me.To.Equals (m)) && m != superView) {
-						// insert m into S
-						noEdgeNodes.Add (m);
-					}
-				}
-			}
-
-			if (edges.Any ()) {
-				foreach ((var from, var to) in edges) {
-					if (from == to) {
-						// if not yet added to the result, add it and remove from edge
-						if (result.Find (v => v == from) == null) {
-							result.Add (from);
-						}
-						edges.Remove ((from, to));
-					} else if (from.SuperView == to.SuperView) {
-						// if 'from' is not yet added to the result, add it
-						if (result.Find (v => v == from) == null) {
-							result.Add (from);
-						}
-						// if 'to' is not yet added to the result, add it
-						if (result.Find (v => v == to) == null) {
-							result.Add (to);
-						}
-						// remove from edge
-						edges.Remove ((from, to));
-					} else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) {
-						if (ReferenceEquals (from.SuperView, to)) {
-							throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
-						} else {
-							throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
-						}
-					}
-				}
-			}
-			// return L (a topologically sorted order)
-			return result;
-		} // TopologicalSort
-
-		/// <summary>
-		/// Overriden by <see cref="Frame"/> to do nothing, as the <see cref="Frame"/> does not have frames.
-		/// </summary>
-		internal virtual void LayoutFrames ()
-		{
-			if (Margin == null) return; // CreateFrames() has not been called yet
-
-			if (Margin.Frame.Size != Frame.Size) {
-				Margin._frame = new Rect (Point.Empty, Frame.Size);
-				Margin.X = 0;
-				Margin.Y = 0;
-				Margin.Width = Frame.Size.Width;
-				Margin.Height = Frame.Size.Height;
-				Margin.SetNeedsLayout ();
-				Margin.LayoutSubviews ();
-				Margin.SetNeedsDisplay ();
-			}
-
-			var border = Margin.Thickness.GetInside (Margin.Frame);
-			if (border != Border.Frame) {
-				Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
-				Border.X = border.Location.X;
-				Border.Y = border.Location.Y;
-				Border.Width = border.Size.Width;
-				Border.Height = border.Size.Height;
-				Border.SetNeedsLayout ();
-				Border.LayoutSubviews ();
-				Border.SetNeedsDisplay ();
-			}
-
-			var padding = Border.Thickness.GetInside (Border.Frame);
-			if (padding != Padding.Frame) {
-				Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
-				Padding.X = padding.Location.X;
-				Padding.Y = padding.Location.Y;
-				Padding.Width = padding.Size.Width;
-				Padding.Height = padding.Size.Height;
-				Padding.SetNeedsLayout ();
-				Padding.LayoutSubviews ();
-				Padding.SetNeedsDisplay ();
-			}
-		}
-
-		/// <summary>
-		/// Invoked when a view starts executing or when the dimensions of the view have changed, for example in
-		/// response to the container view or terminal resizing.
-		/// </summary>
-		/// <remarks>
-		/// Raises the <see cref="LayoutComplete"/> event) before it returns.
-		/// </remarks>
-		public virtual void LayoutSubviews ()
-		{
-			if (!LayoutNeeded) {
-				return;
-			}
-
-			LayoutFrames ();
-
-			var oldBounds = Bounds;
-			OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
-
-			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-
-			// Sort out the dependencies of the X, Y, Width, Height properties
-			var nodes = new HashSet<View> ();
-			var edges = new HashSet<(View, View)> ();
-			CollectAll (this, ref nodes, ref edges);
-			var ordered = View.TopologicalSort (SuperView, nodes, edges);
-			foreach (var v in ordered) {
-				LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
-			}
-
-			// If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
-			// Use LayoutSubview with the Frame of the 'from' 
-			if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) {
-				foreach ((var from, var to) in edges) {
-					LayoutSubview (to, from.Frame);
-				}
-			}
-
-			LayoutNeeded = false;
-
-			OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
-		}
-
-		private void LayoutSubview (View v, Rect contentArea)
-		{
-			if (v.LayoutStyle == LayoutStyle.Computed) {
-				v.SetRelativeLayout (contentArea);
-			}
-
-			v.LayoutSubviews ();
-			v.LayoutNeeded = false;
-		}
-
-		bool _autoSize;
-
-		/// <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"/>
-		/// <para>
-		/// The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If <see langword="true"/> then
-		/// <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can fit; 
-		/// if <see cref="Text"/> won't fit the view will be resized as needed.
-		/// </para>
-		/// <para>
-		/// In addition, if <see cref="ForceValidatePosDim"/> 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.
-		/// </para>
-		/// </summary>
-		public virtual bool AutoSize {
-			get => _autoSize;
-			set {
-				var v = ResizeView (value);
-				TextFormatter.AutoSize = v;
-				if (_autoSize != v) {
-					_autoSize = v;
-					TextFormatter.NeedsFormat = true;
-					UpdateTextFormatterText ();
-					OnResizeNeeded ();
-				}
-			}
-		}
-
-		bool ResizeView (bool autoSize)
-		{
-			if (!autoSize) {
-				return false;
-			}
-
-			var boundsChanged = true;
-			var newFrameSize = GetAutoSize ();
-			if (IsInitialized && newFrameSize != Frame.Size) {
-				if (ForceValidatePosDim) {
-					// BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
-					boundsChanged = ResizeBoundsToFit (newFrameSize);
-				} else {
-					Height = newFrameSize.Height;
-					Width = newFrameSize.Width; 
-				}
-			}
-			// BUGBUG: This call may be redundant
-			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
-			return boundsChanged;
-		}
-
-		/// <summary>
-		/// Resizes the View to fit the specified size. Factors in the HotKey.
-		/// </summary>
-		/// <param name="size"></param>
-		/// <returns>whether the Bounds was changed or not</returns>
-		bool ResizeBoundsToFit (Size size)
-		{
-			var boundsChanged = false;
-			var canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out var rW);
-			var canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out var rH);
-			if (canSizeW) {
-				boundsChanged = true;
-				_width = rW;
-			}
-			if (canSizeH) {
-				boundsChanged = true;
-				_height = rH;
-			}
-			if (boundsChanged) {
-				Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
-			}
-
-			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 ()
-		{
-			int x = 0;
-			int 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 !(ForceValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute))
-			    || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength ()
-			    || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false));
-		}
-
-		bool IsValidAutoSizeWidth (Dim width)
-		{
-			var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
-			var dimValue = width.Anchor (0);
-			return !(ForceValidatePosDim && (!(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width
-			    - GetHotKeySpecifierLength ());
-		}
-
-		bool IsValidAutoSizeHeight (Dim height)
-		{
-			var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
-			var dimValue = height.Anchor (0);
-			return !(ForceValidatePosDim && (!(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height
-			    - GetHotKeySpecifierLength (false));
-		}
-
-		/// <summary>
-		/// Determines if the View's <see cref="Width"/> can be set to a new value.
-		/// </summary>
-		/// <param name="desiredWidth"></param>
-		/// <param name="resultWidth">Contains the width that would result if <see cref="Width"/> were set to <paramref name="desiredWidth"/>"/> </param>
-		/// <returns><see langword="true"/> if the View's <see cref="Width"/> can be changed to the specified value. False otherwise.</returns>
-		internal bool TrySetWidth (int desiredWidth, out int resultWidth)
-		{
-			var w = desiredWidth;
-			bool canSetWidth;
-			switch (Width) {
-			case Dim.DimCombine _:
-			case Dim.DimView _:
-			case Dim.DimFill _:
-				// It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
-				w = Width.Anchor (w);
-				canSetWidth = !ForceValidatePosDim;
-				break;
-			case Dim.DimFactor factor:
-				// Tries to get the SuperView Width otherwise the view Width.
-				var sw = SuperView != null ? SuperView.Frame.Width : w;
-				if (factor.IsFromRemaining ()) {
-					sw -= Frame.X;
-				}
-				w = Width.Anchor (sw);
-				canSetWidth = !ForceValidatePosDim;
-				break;
-			default:
-				canSetWidth = true;
-				break;
-			}
-			resultWidth = w;
-
-			return canSetWidth;
-		}
-
-		/// <summary>
-		/// Determines if the View's <see cref="Height"/> can be set to a new value.
-		/// </summary>
-		/// <param name="desiredHeight"></param>
-		/// <param name="resultHeight">Contains the width that would result if <see cref="Height"/> were set to <paramref name="desiredHeight"/>"/> </param>
-		/// <returns><see langword="true"/> if the View's <see cref="Height"/> can be changed to the specified value. False otherwise.</returns>
-		internal bool TrySetHeight (int desiredHeight, out int resultHeight)
-		{
-			var h = desiredHeight;
-			bool canSetHeight;
-			switch (Height) {
-			case Dim.DimCombine _:
-			case Dim.DimView _:
-			case Dim.DimFill _:
-				// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
-				h = Height.Anchor (h);
-				canSetHeight = !ForceValidatePosDim;
-				break;
-			case Dim.DimFactor factor:
-				// Tries to get the SuperView height otherwise the view height.
-				var sh = SuperView != null ? SuperView.Frame.Height : h;
-				if (factor.IsFromRemaining ()) {
-					sh -= Frame.Y;
-				}
-				h = Height.Anchor (sh);
-				canSetHeight = !ForceValidatePosDim;
-				break;
-			default:
-				canSetHeight = true;
-				break;
-			}
-			resultHeight = h;
-
-			return canSetHeight;
-		}
-
-		/// <summary>
-		/// Finds which view that belong to the <paramref name="start"/> superview at the provided location.
-		/// </summary>
-		/// <param name="start">The superview where to look for.</param>
-		/// <param name="x">The column location in the superview.</param>
-		/// <param name="y">The row location in the superview.</param>
-		/// <param name="resx">The found view screen relative column location.</param>
-		/// <param name="resy">The found view screen relative row location.</param>
-		/// <returns>
-		///  The view that was found at the <praramref name="x"/> and <praramref name="y"/> coordinates.
-		///  <see langword="null"/> if no view was found.
-		/// </returns>
-		public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
-		{
-			resy = resx = 0;
-			if (start == null || !start.Frame.Contains (x, y)) {
-				return null;
-			}
-			
-			var startFrame = start.Frame;
-			if (start.InternalSubviews != null) {
-				int count = start.InternalSubviews.Count;
-				if (count > 0) {
-					var boundsOffset = start.GetBoundsOffset ();
-					var rx = x - (startFrame.X + boundsOffset.X);
-					var ry = y - (startFrame.Y + boundsOffset.Y);
-					for (int i = count - 1; i >= 0; i--) {
-						View v = start.InternalSubviews [i];
-						if (v.Visible && v.Frame.Contains (rx, ry)) {
-							var deep = FindDeepestView (v, rx, ry, out resx, out resy);
-							if (deep == null)
-								return v;
-							return deep;
-						}
-					}
-				}
-			}
-			resx = x - startFrame.X;
-			resy = y - startFrame.Y;
-			return start;
-		}
-	}
-}

+ 555 - 546
Terminal.Gui/View/Layout/PosDim.cs

@@ -1,327 +1,346 @@
-//
-// PosDim.cs: Pos and Dim objects for view dimensions.
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-
-using System;
-namespace Terminal.Gui {
+using System;
+using static Terminal.Gui.Dim;
+
+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.
+/// </summary>
+/// <remarks>
+///   <para>
+///     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.
+///   </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.
+///   </para>
+/// <para>
+/// <list type="table">
+///	<listheader>
+///	  <term>Pos Object</term>
+///       <description>Description</description>
+///	</listheader>
+///	<item>
+///	  <term><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.
+///	  </description>
+///	</item>
+///	<item>
+///	  <term><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.
+///	  </description>
+///	</item>
+///	<item>
+///	  <term><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. 
+///	  </description>
+///	</item>
+///	<item>
+///	  <term><see cref="Pos.Center"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  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"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  Creates a <see cref="Pos"/> object that tracks the Bottom (Y+Height) coordinate of the specified <see cref="View"/> 
+///	  </description>
+///	</item>
+///  
+/// </list>
+/// </para>
+/// </remarks>
+public class Pos {
+	internal virtual int Anchor (int width) => 0;
+
 	/// <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.
+	/// 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>
-	/// <remarks>
-	///   <para>
-	///     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.
-	///   </para>
-	///   <para>
-	///     It is possible to 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>
-	/// </remarks>
-	public class Pos {
-		internal virtual int Anchor (int width)
-		{
-			return 0;
-		}
+	/// <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);
 
-		// Helper class to provide dynamic value by the execution of a function that returns an integer.
-		internal class PosFunc : Pos {
-			Func<int> function;
+	internal class PosFactor : Pos {
+		readonly float _factor;
 
-			public PosFunc (Func<int> n)
-			{
-				this.function = n;
-			}
+		public PosFactor (float n) => _factor = n;
 
-			internal override int Anchor (int width)
-			{
-				return function ();
-			}
+		internal override int Anchor (int width) => (int)(width * _factor);
 
-			public override string ToString ()
-			{
-				return $"PosFunc({function ()})";
-			}
+		public override string ToString () => $"Factor({_factor})";
 
-			public override int GetHashCode () => function.GetHashCode ();
+		public override int GetHashCode () => _factor.GetHashCode ();
 
-			public override bool Equals (object other) => other is PosFunc f && f.function () == function ();
-		}
+		public override bool Equals (object other) => other is PosFactor f && f._factor == _factor;
+	}
 
-		/// <summary>
-		/// Creates a "PosFunc" from the specified function.
-		/// </summary>
-		/// <param name="function">The function to be executed.</param>
-		/// <returns>The <see cref="Pos"/> returned from the function.</returns>
-		public static Pos Function (Func<int> function)
-		{
-			return new PosFunc (function);
-		}
+	// Helper class to provide dynamic value by the execution of a function that returns an integer.
+	internal class PosFunc : Pos {
+		readonly Func<int> _function;
 
-		internal class PosFactor : Pos {
-			float factor;
+		public PosFunc (Func<int> n) => _function = n;
 
-			public PosFactor (float n)
-			{
-				this.factor = n;
-			}
+		internal override int Anchor (int width) => _function ();
 
-			internal override int Anchor (int width)
-			{
-				return (int)(width * factor);
-			}
+		public override string ToString () => $"PosFunc({_function ()})";
 
-			public override string ToString ()
-			{
-				return $"Factor({factor})";
-			}
+		public override int GetHashCode () => _function.GetHashCode ();
 
-			public override int GetHashCode () => factor.GetHashCode ();
+		public override bool Equals (object other) => other is PosFunc f && f._function () == _function ();
+	}
 
-			public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
-		}
+	/// <summary>
+	/// Creates a percentage <see cref="Pos"/> object
+	/// </summary>
+	/// <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>
+	/// var textView = new TextView () {
+	///	X = Pos.Center (),
+	///	Y = Pos.Percent (50),
+	///	Width = Dim.Percent (80),
+	/// 	Height = Dim.Percent (30),
+	/// };
+	/// </code>
+	/// </example>
+	public static Pos Percent (float n)
+	{
+		if (n is < 0 or > 100) {
+			throw new ArgumentException ("Percent value must be between 0 and 100");
+		}
+
+		return new PosFactor (n / 100);
+	}
 
-		/// <summary>
-		/// Creates a percentage <see cref="Pos"/> object
-		/// </summary>
-		/// <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>
-		/// var textView = new TextView () {
-		///	X = Pos.Center (),
-		///	Y = Pos.Percent (50),
-		///	Width = Dim.Percent (80),
-		/// 	Height = Dim.Percent (30),
-		/// };
-		/// </code>
-		/// </example>
-		public static Pos Percent (float n)
-		{
-			if (n < 0 || n > 100)
-				throw new ArgumentException ("Percent value must be between 0 and 100");
+	/// <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.
+	/// </summary>
+	/// <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>
+	/// // See Issue #502 
+	/// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
+	/// anchorButton.Y = Pos.AnchorEnd (1);
+	/// </code>
+	/// </example>
+	public static Pos AnchorEnd (int offset = 0)
+	{
+		if (offset < 0) {
+			throw new ArgumentException (@"Must be positive", nameof(offset));
+		}
+
+		return new PosAnchorEnd (offset);
+	}
 
-			return new PosFactor (n / 100);
-		}
+	internal class PosAnchorEnd : Pos {
+		readonly int _offset;
 
-		internal class PosAnchorEnd : Pos {
-			int n;
+		public PosAnchorEnd (int offset) => _offset = offset;
 
-			public PosAnchorEnd (int n)
-			{
-				this.n = n;
-			}
+		internal override int Anchor (int width) => width - _offset;
 
-			internal override int Anchor (int width)
-			{
-				return width - n;
-			}
+		public override string ToString () => $"AnchorEnd({_offset})";
 
-			public override string ToString ()
-			{
-				return $"AnchorEnd({n})";
-			}
+		public override int GetHashCode () => _offset.GetHashCode ();
 
-			public override int GetHashCode () => n.GetHashCode ();
+		public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset;
+	}
 
-			public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd.n == n;
-		}
+	/// <summary>
+	/// 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>
+	/// var textView = new TextView () {
+	///	X = Pos.Center (),
+	///	Y = Pos.Percent (50),
+	///	Width = Dim.Percent (80),
+	/// 	Height = Dim.Percent (30),
+	/// };
+	/// </code>
+	/// </example>
+	public static Pos Center () => new PosCenter ();
+
+	internal class PosAbsolute : Pos {
+		readonly int _n;
+		public PosAbsolute (int n) => _n = n;
+
+		public override string ToString () => $"Absolute({_n})";
+
+		internal override int Anchor (int width) => _n;
+
+		public override int GetHashCode () => _n.GetHashCode ();
+
+		public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n;
+	}
 
-		/// <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.
-		/// </summary>
-		/// <returns>The <see cref="Pos"/> object anchored to the end (the bottom or the right side).</returns>
-		/// <param name="margin">Optional margin to place to the right or below.</param>
-		/// <example>
-		/// 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);
-		/// </code>
-		/// </example>
-		public static Pos AnchorEnd (int margin = 0)
-		{
-			if (margin < 0)
-				throw new ArgumentException ("Margin must be positive");
+	internal class PosCenter : Pos {
+		internal override int Anchor (int width) => width / 2;
 
-			return new PosAnchorEnd (margin);
-		}
+		public override string ToString () => "Center";
+	}
 
-		internal class PosCenter : Pos {
-			internal override int Anchor (int width)
-			{
-				return width / 2;
-			}
+	/// <summary>
+	/// 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>
+	public static Pos At (int n) => new PosAbsolute (n);
 
-			public override string ToString ()
-			{
-				return "Center";
-			}
-		}
+	internal class PosCombine : Pos {
+		internal Pos _left, _right;
+		internal bool _add;
 
-		/// <summary>
-		/// Returns 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>
-		/// var textView = new TextView () {
-		///	X = Pos.Center (),
-		///	Y = Pos.Percent (50),
-		///	Width = Dim.Percent (80),
-		/// 	Height = Dim.Percent (30),
-		/// };
-		/// </code>
-		/// </example>
-		public static Pos Center ()
+		public PosCombine (bool add, Pos left, Pos right)
 		{
-			return new PosCenter ();
-		}
-
-		internal class PosAbsolute : Pos {
-			int n;
-			public PosAbsolute (int n) { this.n = n; }
-
-			public override string ToString ()
-			{
-				return $"Absolute({n})";
-			}
-
-			internal override int Anchor (int width)
-			{
-				return n;
-			}
-
-			public override int GetHashCode () => n.GetHashCode ();
-
-			public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
+			_left = left;
+			_right = right;
+			_add = add;
 		}
 
-		/// <summary>
-		/// 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>
-		public static implicit operator Pos (int n)
+		internal override int Anchor (int width)
 		{
-			return new PosAbsolute (n);
+			int la = _left.Anchor (width);
+			int ra = _right.Anchor (width);
+			if (_add) {
+				return la + ra;
+			} else {
+				return la - ra;
+			}
 		}
 
-		/// <summary>
-		/// 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>
-		public static Pos At (int n)
-		{
-			return new PosAbsolute (n);
-		}
+		public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
+	}
 
-		internal class PosCombine : Pos {
-			internal Pos left, right;
-			internal bool add;
-			public PosCombine (bool add, Pos left, Pos right)
-			{
-				this.left = left;
-				this.right = right;
-				this.add = add;
-			}
+	/// <summary>
+	/// 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>
+	public static implicit operator Pos (int n) => new PosAbsolute (n);
 
-			internal override int Anchor (int width)
-			{
-				var la = left.Anchor (width);
-				var ra = right.Anchor (width);
-				if (add)
-					return la + ra;
-				else
-					return la - ra;
-			}
+	/// <summary>
+	/// 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>
+	public static Pos operator + (Pos left, Pos right)
+	{
+		if (left is PosAbsolute && right is PosAbsolute) {
+			return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
+		}
+		var newPos = new PosCombine (true, left, right);
+		SetPosCombine (left, newPos);
+		return newPos;
+	}
 
-			public override string ToString ()
-			{
-				return $"Combine({left}{(add ? '+' : '-')}{right})";
-			}
+	/// <summary>
+	/// 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>
+	public static Pos operator - (Pos left, Pos right)
+	{
+		if (left is PosAbsolute && right is PosAbsolute) {
+			return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
+		}
+		var newPos = new PosCombine (false, left, right);
+		SetPosCombine (left, newPos);
+		return newPos;
+	}
 
+	static void SetPosCombine (Pos left, PosCombine newPos)
+	{
+		var view = left as PosView;
+		if (view != null) {
+			view.Target.SetNeedsLayout ();
 		}
+	}
 
-		/// <summary>
-		/// 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>
-		public static Pos operator + (Pos left, Pos right)
-		{
-			if (left is PosAbsolute && right is PosAbsolute) {
-				return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
-			}
-			PosCombine newPos = new PosCombine (true, left, right);
-			SetPosCombine (left, newPos);
-			return newPos;
-		}
+	internal class PosView : Pos {
+		public readonly View Target;
+		int side;
 
-		/// <summary>
-		/// 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>
-		public static Pos operator - (Pos left, Pos right)
+		public PosView (View view, int side)
 		{
-			if (left is PosAbsolute && right is PosAbsolute) {
-				return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
-			}
-			PosCombine newPos = new PosCombine (false, left, right);
-			SetPosCombine (left, newPos);
-			return newPos;
+			Target = view;
+			this.side = side;
 		}
 
-		static void SetPosCombine (Pos left, PosCombine newPos)
+		internal override int Anchor (int width)
 		{
-			var view = left as PosView;
-			if (view != null) {
-				view.Target.SetNeedsLayout ();
+			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;
 			}
 		}
 
-		internal class PosView : Pos {
-			public View Target;
-			int side;
-			public PosView (View view, int side)
-			{
-				Target = view;
-				this.side = side;
-			}
-			internal override int Anchor (int width)
-			{
-				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;
@@ -336,365 +355,355 @@ namespace Terminal.Gui {
 				return $"View(side={tside},target={Target.ToString ()})";
 			}
 
-			public override int GetHashCode () => Target.GetHashCode ();
+		public override int GetHashCode () => Target.GetHashCode ();
 
-			public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
-		}
+		public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
+	}
 
 		/// <summary>
-		/// Returns a <see cref="Pos"/> object tracks the Left (X) position of the specified <see cref="View"/>.
-		/// </summary>
+		/// 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>
 		public static Pos Left (View view) => new PosView (view, 0);
 
 		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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>
 		public static Pos X (View view) => new PosView (view, 0);
 
 		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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>
 		public static Pos Top (View view) => new PosView (view, 1);
 
 		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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>
 		public static Pos Y (View view) => new PosView(view, 1);
 
 		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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>
 		public static Pos Right (View view) => new PosView (view, 2);
 
 		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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>
 		public static Pos Bottom (View view) => new PosView (view, 3);
 
-		/// <summary>Serves as the default hash function. </summary>
-		/// <returns>A hash code for the current object.</returns>
-		public override int GetHashCode () => Anchor (0).GetHashCode ();
+	/// <summary>Serves as the default hash function. </summary>
+	/// <returns>A hash code for the current object.</returns>
+	public override int GetHashCode () => Anchor (0).GetHashCode ();
 
-		/// <summary>Determines whether the specified object is equal to the current object.</summary>
-		/// <param name="other">The object to compare with the current object. </param>
-		/// <returns>
-		///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
-		public override bool Equals (object other) => other is Pos abs && abs == this;
-	}
+	/// <summary>Determines whether the specified object is equal to the current object.</summary>
+	/// <param name="other">The object to compare with the current object. </param>
+	/// <returns>
+	///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
+	public override bool Equals (object other) => other is Pos abs && abs == this;
+}
+
+/// <summary>
+/// <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.
+/// </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.
+/// </para>
+/// </summary>
+/// <remarks>
+/// <para>
+/// <list type="table">
+///	<listheader>
+///	  <term>Dim Object</term>
+///       <description>Description</description>
+///	</listheader>
+///	<item>
+///	  <term><see cref="Dim.Function(Func{int})"/></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.
+///	  </description>
+///	</item>
+///	<item>
+///	  <term><see cref="Dim.Percent(float, bool)"/></term>
+///	  <description>
+///	  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.Fill(int)"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  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)"/></term>
+///	  <description>
+///	  Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>. 
+///	  </description>
+///	</item>
+/// </list>
+/// </para>
+/// <para>
+/// </para>
+/// </remarks>
+public class Dim {
+	internal virtual int Anchor (int width) => 0;
 
 	/// <summary>
-	/// Dim properties of a <see cref="View"/> to control the position.
+	/// 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>
-	/// <remarks>
-	///   <para>
-	///     Use the Dim objects on the Width or Height properties of a <see cref="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 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>
-	/// </remarks>
-	public class Dim {
-		internal virtual int Anchor (int width)
-		{
-			return 0;
-		}
+	/// <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);
 
-		// Helper class to provide dynamic value by the execution of a function that returns an integer.
-		internal class DimFunc : Dim {
-			Func<int> function;
+	// Helper class to provide dynamic value by the execution of a function that returns an integer.
+	internal class DimFunc : Dim {
+		readonly Func<int> _function;
 
-			public DimFunc (Func<int> n)
-			{
-				this.function = n;
-			}
+		public DimFunc (Func<int> n) => _function = n;
 
-			internal override int Anchor (int width)
-			{
-				return function ();
-			}
+		internal override int Anchor (int width) => _function ();
 
-			public override string ToString ()
-			{
-				return $"DimFunc({function ()})";
-			}
+		public override string ToString () => $"DimFunc({_function ()})";
 
-			public override int GetHashCode () => function.GetHashCode ();
+		public override int GetHashCode () => _function.GetHashCode ();
 
-			public override bool Equals (object other) => other is DimFunc f && f.function () == function ();
-		}
+		public override bool Equals (object other) => other is DimFunc f && f._function () == _function ();
+	}
 
-		/// <summary>
-		/// Creates a "DimFunc" from the specified function.
-		/// </summary>
-		/// <param name="function">The function to be executed.</param>
-		/// <returns>The <see cref="Dim"/> returned from the function.</returns>
-		public static Dim Function (Func<int> function)
+	/// <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);
+	}
+
+	internal class DimFactor : Dim {
+		readonly float _factor;
+		readonly bool _remaining;
+
+		public DimFactor (float n, bool r = false)
 		{
-			return new DimFunc (function);
+			_factor = n;
+			_remaining = r;
 		}
 
-		internal class DimFactor : Dim {
-			float factor;
-			bool remaining;
+		internal override int Anchor (int width) => (int)(width * _factor);
 
-			public DimFactor (float n, bool r = false)
-			{
-				factor = n;
-				remaining = r;
-			}
+		public bool IsFromRemaining () => _remaining;
 
-			internal override int Anchor (int width)
-			{
-				return (int)(width * factor);
-			}
+		public override string ToString () => $"Factor({_factor},{_remaining})";
 
-			public bool IsFromRemaining ()
-			{
-				return remaining;
-			}
+		public override int GetHashCode () => _factor.GetHashCode ();
 
-			public override string ToString ()
-			{
-				return $"Factor({factor},{remaining})";
-			}
+		public override bool Equals (object other) => other is DimFactor f && f._factor == _factor && f._remaining == _remaining;
+	}
 
-			public override int GetHashCode () => factor.GetHashCode ();
 
-			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
-		}
+	internal class DimAbsolute : Dim {
+		readonly int _n;
+		public DimAbsolute (int n) => _n = n;
 
-		/// <summary>
-		/// Creates a percentage <see cref="Dim"/> object
-		/// </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 < 0 || n > 100)
-				throw new ArgumentException ("Percent value must be between 0 and 100");
+		public override string ToString () => $"Absolute({_n})";
 
-			return new DimFactor (n / 100, r);
-		}
+		internal override int Anchor (int width) => _n;
 
-		internal class DimAbsolute : Dim {
-			int n;
-			public DimAbsolute (int n) { this.n = n; }
+		public override int GetHashCode () => _n.GetHashCode ();
 
-			public override string ToString ()
-			{
-				return $"Absolute({n})";
-			}
+		public override bool Equals (object other) => other is DimAbsolute abs && abs._n == _n;
+	}
 
-			internal override int Anchor (int width)
-			{
-				return n;
-			}
+	internal class DimFill : Dim {
+		readonly int _margin;
+		public DimFill (int margin) => _margin = margin;
 
-			public override int GetHashCode () => n.GetHashCode ();
+		public override string ToString () => $"Fill({_margin})";
 
-			public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
-		}
+		internal override int Anchor (int width) => width - _margin;
 
-		internal class DimFill : Dim {
-			int margin;
-			public DimFill (int margin) { this.margin = margin; }
+		public override int GetHashCode () => _margin.GetHashCode ();
 
-			public override string ToString ()
-			{
-				return $"Fill({margin})";
-			}
+		public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin;
+	}
 
-			internal override int Anchor (int width)
-			{
-				return width - margin;
-			}
+	/// <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);
 
-			public override int GetHashCode () => margin.GetHashCode ();
+	/// <summary>
+	/// 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 pos.</param>
+	public static implicit operator Dim (int n) => new DimAbsolute (n);
 
-			public override bool Equals (object other) => other is DimFill fill && fill.margin == margin;
-		}
+	/// <summary>
+	/// 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>
+	public static Dim Sized (int n) => new DimAbsolute (n);
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Dim"/> class that fills the dimension, but leaves the specified number of colums for a margin.
-		/// </summary>
-		/// <returns>The Fill dimension.</returns>
-		/// <param name="margin">Margin to use.</param>
-		public static Dim Fill (int margin = 0)
-		{
-			return new DimFill (margin);
-		}
+	internal class DimCombine : Dim {
+		internal Dim _left, _right;
+		internal bool _add;
 
-		/// <summary>
-		/// 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 pos.</param>
-		public static implicit operator Dim (int n)
+		public DimCombine (bool add, Dim left, Dim right)
 		{
-			return new DimAbsolute (n);
+			_left = left;
+			_right = right;
+			_add = add;
 		}
 
-		/// <summary>
-		/// 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>
-		public static Dim Sized (int n)
+		internal override int Anchor (int width)
 		{
-			return new DimAbsolute (n);
+			int la = _left.Anchor (width);
+			int ra = _right.Anchor (width);
+			if (_add) {
+				return la + ra;
+			} else {
+				return la - ra;
+			}
 		}
 
-		internal class DimCombine : Dim {
-			internal Dim left, right;
-			internal bool add;
-			public DimCombine (bool add, Dim left, Dim right)
-			{
-				this.left = left;
-				this.right = right;
-				this.add = add;
-			}
+		public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
+	}
 
-			internal override int Anchor (int width)
-			{
-				var la = left.Anchor (width);
-				var ra = right.Anchor (width);
-				if (add)
-					return la + ra;
-				else
-					return la - ra;
-			}
+	/// <summary>
+	/// 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>
+	public static Dim operator + (Dim left, Dim right)
+	{
+		if (left is DimAbsolute && right is DimAbsolute) {
+			return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
+		}
+		var newDim = new DimCombine (true, left, right);
+		SetDimCombine (left, newDim);
+		return newDim;
+	}
 
-			public override string ToString ()
-			{
-				return $"Combine({left}{(add ? '+' : '-')}{right})";
-			}
+	/// <summary>
+	/// 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>
+	public static Dim operator - (Dim left, Dim right)
+	{
+		if (left is DimAbsolute && right is DimAbsolute) {
+			return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
+		}
+		var newDim = new DimCombine (false, left, right);
+		SetDimCombine (left, newDim);
+		return newDim;
+	}
 
-		}
+	// BUGBUG: newPos is never used.
+	static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout ();
 
-		/// <summary>
-		/// 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>
-		public static Dim operator + (Dim left, Dim right)
-		{
-			if (left is DimAbsolute && right is DimAbsolute) {
-				return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
-			}
-			DimCombine newDim = new DimCombine (true, left, right);
-			SetDimCombine (left, newDim);
-			return newDim;
-		}
+	internal class DimView : Dim {
+		public View Target { get; init; }
+		readonly int _side;
 
-		/// <summary>
-		/// 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>
-		public static Dim operator - (Dim left, Dim right)
+		public DimView (View view, int side)
 		{
-			if (left is DimAbsolute && right is DimAbsolute) {
-				return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
-			}
-			DimCombine newDim = new DimCombine (false, left, right);
-			SetDimCombine (left, newDim);
-			return newDim;
+			Target = view;
+			_side = side;
 		}
 
-		static void SetDimCombine (Dim left, DimCombine newPos)
+		internal override int Anchor (int width) => _side switch {
+			0 => Target.Frame.Height,
+			1 => Target.Frame.Width,
+			_ => 0
+		};
+
+		public override string ToString ()
 		{
-			var view = left as DimView;
-			if (view != null) {
-				view.Target.SetNeedsLayout ();
+			if (Target == null) {
+				throw new NullReferenceException ();
 			}
+			string tside = _side switch {
+				0 => "Height",
+				1 => "Width",
+				_ => "unknown"
+			};
+			return $"View({tside},{Target})";
 		}
 
-		internal class DimView : Dim {
-			public View Target;
-			int side;
-			public DimView (View view, int side)
-			{
-				Target = view;
-				this.side = side;
-			}
-
-			internal override int Anchor (int width)
-			{
-				switch (side) {
-				case 0: return Target.Frame.Height;
-				case 1: return Target.Frame.Width;
-				default:
-					return 0;
-				}
-			}
-
-			public override string ToString ()
-			{
-				string tside;
-				switch (side) {
-				case 0: tside = "Height"; break;
-				case 1: tside = "Width"; break;
-				default: tside = "unknown"; break;
-				}
-				return $"View({tside},{Target.ToString ()})";
-			}
+		public override int GetHashCode () => Target.GetHashCode ();
 
-			public override int GetHashCode () => Target.GetHashCode ();
+		public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
+	}
 
-			public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
-		}
-		/// <summary>
-		/// Returns a <see cref="Dim"/> object tracks the Width of the specified <see cref="View"/>.
-		/// </summary>
-		/// <returns>The <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 Width of the specified <see cref="View"/>.
+	/// </summary>
+	/// <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>
-		/// Returns a <see cref="Dim"/> object tracks the Height of the specified <see cref="View"/>.
-		/// </summary>
-		/// <returns>The <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);
-
-		/// <summary>Serves as the default hash function. </summary>
-		/// <returns>A hash code for the current object.</returns>
-		public override int GetHashCode () => Anchor (0).GetHashCode ();
-
-		/// <summary>Determines whether the specified object is equal to the current object.</summary>
-		/// <param name="other">The object to compare with the current object. </param>
-		/// <returns>
-		///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
-		public override bool Equals (object other) => other is Dim abs && abs == this;
-	}
-}
+	/// <summary>
+	/// 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>
+	/// <param name="view">The view that will be tracked.</param>
+	public static Dim Height (View view) => new DimView (view, 0);
+
+	/// <summary>Serves as the default hash function. </summary>
+	/// <returns>A hash code for the current object.</returns>
+	public override int GetHashCode () => Anchor (0).GetHashCode ();
+
+	/// <summary>Determines whether the specified object is equal to the current object.</summary>
+	/// <param name="other">The object to compare with the current object. </param>
+	/// <returns>
+	///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
+	public override bool Equals (object other) => other is Dim abs && abs == this;
+}

+ 1219 - 0
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -0,0 +1,1219 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+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.
+/// </summary>
+public enum LayoutStyle {
+	/// <summary>
+	/// The position and size of the view are based <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.
+	/// </summary>
+	Computed
+}
+
+public partial class View {
+	// The frame for the object. Relative to the SuperView's Bounds.
+	Rect _frame;
+
+	/// <summary>
+	/// Gets or sets the frame for the view. The frame is relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.
+	/// </summary>
+	/// <value>The frame.</value>
+	/// <remarks>
+	/// <para>
+	///    Change the Frame when using the <see cref="Terminal.Gui.LayoutStyle.Absolute"/> layout style to move or resize views. 
+	/// </para>
+	/// <para>
+	///    Altering the Frame of a view will trigger the redrawing of the
+	///    view as well as the redrawing of the affected regions of the <see cref="SuperView"/>.
+	/// </para>
+	/// </remarks>
+	public virtual Rect Frame {
+		get => _frame;
+		set {
+			_frame = new Rect (value.X, value.Y, Math.Max (value.Width, 0), Math.Max (value.Height, 0));
+			if (IsInitialized || LayoutStyle == LayoutStyle.Absolute) {
+				LayoutFrames ();
+				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+				SetNeedsLayout ();
+				SetNeedsDisplay ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// The frame (specified as a <see cref="Thickness"/>) that separates a View from other SubViews of the same SuperView. 
+	/// The margin offsets the <see cref="Bounds"/> from the <see cref="Frame"/>. 
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// 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>
+	/// Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
+	/// will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+	/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
+	/// </para>
+	/// </remarks>
+	public Frame Margin { get; private set; }
+
+	/// <summary>
+	/// The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the <see cref="Margin"/>. 
+	///  The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. 
+	///  The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and 
+	///  title will take up the first row and the second row will be filled with spaces. 
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// <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
+	/// and are not clipped by the View's Clip Area.
+	/// </para>
+	/// <para>
+	/// Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
+	/// will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+	/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
+	/// </para>
+	/// </remarks>
+	public Frame Border { get; private set; }
+
+	/// <summary>
+	/// Gets or sets whether the view has a one row/col thick border.
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// This is a helper for manipulating the view's <see cref="Border"/>. Setting this property to any value other than
+	/// <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/> 
+	/// to `1` and <see cref="BorderStyle"/> to the value. 
+	/// </para>
+	/// <para>
+	/// Setting this property to <see cref="LineStyle.None"/> is equivalent to setting <see cref="Border"/>'s <see cref="Frame.Thickness"/> 
+	/// to `0` and <see cref="BorderStyle"/> to <see cref="LineStyle.None"/>. 
+	/// </para>
+	/// <para>
+	/// For more advanced customization of the view's border, manipulate see <see cref="Border"/> directly.
+	/// </para>
+	/// </remarks>
+	public LineStyle BorderStyle {
+		get => Border?.BorderStyle ?? LineStyle.None;
+		set {
+			if (Border == null) {
+				throw new InvalidOperationException ("Border is null; this is likely a bug.");
+			}
+			if (value != LineStyle.None) {
+				Border.Thickness = new Thickness (1);
+			} else {
+				Border.Thickness = new Thickness (0);
+			}
+			Border.BorderStyle = value;
+			LayoutFrames ();
+			SetNeedsLayout ();
+		}
+	}
+
+	/// <summary>
+	/// The frame (specified as a <see cref="Thickness"/>) inside of the view that offsets the <see cref="Bounds"/> from the <see cref="Border"/>. 
+	/// </summary>
+	/// <remarks>
+	/// <para>
+	/// 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>
+	/// Changing the size of a frame (<see cref="Margin"/>, <see cref="Border"/>, or <see cref="Padding"/>)
+	/// will change the size of the <see cref="Frame"/> and trigger <see cref="LayoutSubviews"/> to update the layout of the
+	/// <see cref="SuperView"/> and its <see cref="Subviews"/>.
+	/// </para>
+	/// </remarks>
+	public Frame Padding { get; private set; }
+
+	/// <summary>
+	/// Helper to get the total thickness of the <see cref="Margin"/>, <see cref="Border"/>, and <see cref="Padding"/>. 
+	/// </summary>
+	/// <returns>A thickness that describes the sum of the Frames' thicknesses.</returns>
+	public Thickness GetFramesThickness ()
+	{
+		int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left;
+		int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top;
+		int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right;
+		int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom;
+		return new Thickness (left, top, right, bottom);
+	}
+
+	/// <summary>
+	/// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties of
+	/// <see cref="Margin"/>, <see cref="Border"/> and <see cref="Padding"/>.
+	/// </summary>
+	public Point GetBoundsOffset () => new Point (Padding?.Thickness.GetInside (Padding.Frame).X ?? 0, Padding?.Thickness.GetInside (Padding.Frame).Y ?? 0);
+
+	/// <summary>
+	/// Creates the view's <see cref="Frame"/> objects. This internal method is overridden by Frame to do nothing
+	/// to prevent recursion during View construction.
+	/// </summary>
+	internal virtual void CreateFrames ()
+	{
+		void ThicknessChangedHandler (object sender, EventArgs e)
+		{
+			LayoutFrames ();
+			SetNeedsLayout ();
+			SetNeedsDisplay ();
+		}
+
+		if (Margin != null) {
+			Margin.ThicknessChanged -= ThicknessChangedHandler;
+			Margin.Dispose ();
+		}
+		Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) };
+		Margin.ThicknessChanged += ThicknessChangedHandler;
+		Margin.Parent = this;
+
+		if (Border != null) {
+			Border.ThicknessChanged -= ThicknessChangedHandler;
+			Border.Dispose ();
+		}
+		Border = new Frame () { Id = "Border", Thickness = new Thickness (0) };
+		Border.ThicknessChanged += ThicknessChangedHandler;
+		Border.Parent = this;
+
+		// TODO: Create View.AddAdornment
+
+		if (Padding != null) {
+			Padding.ThicknessChanged -= ThicknessChangedHandler;
+			Padding.Dispose ();
+		}
+		Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) };
+		Padding.ThicknessChanged += ThicknessChangedHandler;
+		Padding.Parent = this;
+	}
+
+	LayoutStyle _layoutStyle;
+
+	/// <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>
+	/// <value>The layout style.</value>
+	public LayoutStyle LayoutStyle {
+		get => _layoutStyle;
+		set {
+			_layoutStyle = value;
+			SetNeedsLayout ();
+		}
+	}
+
+	/// <summary>
+	/// The view's content area.
+	/// <para>
+	/// SubViews are positioned relative to Bounds.
+	/// </para>
+	/// <para>
+	/// Drawing is clipped to Bounds (<see cref="Draw()"/> clips drawing to Bounds.<see cref="Rect.Size">Size</see>).
+	/// </para>
+	/// <para>
+	/// Mouse events are reported relative to Bounds.
+	/// </para>
+	/// </summary>
+	/// <value>The view's content area.</value>
+	/// <remarks>
+	/// <para>
+	/// The <see cref="Rect.Location"/> of Bounds is always (0, 0). To obtain the offset of the Bounds from the Frame use 
+	/// <see cref="GetBoundsOffset"/>.
+	/// </para>
+	/// <para>
+	/// When using <see cref="LayoutStyle.Computed"/>, Bounds is not valid until after the view has been initialized (after <see cref="EndInit"/> has been called and <see cref="Initialized"/>
+	/// has fired). Accessing this property before the view is initialized is considered an error./>
+	/// </para>
+	/// </remarks>
+	public virtual Rect Bounds {
+		get {
+#if DEBUG
+			if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
+				Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}");
+				Debug.WriteLine ($"The Frame is set before the View has been initialized. So it isn't a bug.Is by design.");
+			}
+#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))
+			Frame = new Rect (Frame.Location,
+				new Size (
+					value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal,
+					value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical
+				)
+			);
+		}
+	}
+
+	Rect FrameGetInsideBounds ()
+	{
+		if (Margin == null || Border == null || Padding == null) {
+			return new Rect (default, Frame.Size);
+		}
+		int width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal);
+		int height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical);
+		return new Rect (Point.Empty, new Size (width, height));
+	}
+
+	Pos _x, _y;
+
+	/// <summary>
+	/// Gets or sets the X position for the view (the column). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// </summary>
+	/// <value>The X Position.</value>
+	/// <remarks>
+	/// <para>
+	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
+	/// </para>
+	/// <para>
+	/// <see langword="null"/> is the same as <c>Pos.Absolute(0)</c>.
+	/// </para>
+	/// </remarks>
+	public Pos X {
+		get => VerifyIsInitialized (_x);
+		set {
+			// BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it?
+
+			if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) {
+				CheckAbsolute (nameof (X), _x, value);
+			}
+
+			_x = value;
+
+			OnResizeNeeded ();
+		}
+	}
+
+
+	/// <summary>
+	/// Gets or sets the Y position for the view (the row). Only used if the <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// </summary>
+	/// <value>The X Position.</value>
+	/// <remarks>
+	/// <para>
+	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property has no effect and its value is indeterminate.
+	/// </para>
+	/// <para>
+	/// <see langword="null"/> is the same as <c>Pos.Absolute(0)</c>.
+	/// </para>
+	/// </remarks>
+	public Pos Y {
+		get => VerifyIsInitialized (_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;
+
+			OnResizeNeeded ();
+		}
+	}
+	Dim _width, _height;
+
+	/// <summary>
+	/// Gets or sets the width of the view. Only used when <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// </summary>
+	/// <value>The width.</value>
+	/// <remarks>
+	/// <para>
+	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property
+	/// has no effect and its value is indeterminate. 
+	/// </para>
+	/// <para>
+	/// <see langword="null"/> is the same as <c>Dim.Fill (0)</c>.
+	/// </para>
+	/// </remarks>
+	public Dim Width {
+		get => VerifyIsInitialized (_width);
+		set {
+			// BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
+			if (ValidatePosDim) {
+				if (LayoutStyle == LayoutStyle.Computed) {
+					CheckAbsolute (nameof (Width), _width, value);
+				}
+			}
+
+			_width = value;
+
+			if (ValidatePosDim) {
+				bool isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width);
+
+				if (IsAdded && AutoSize && !isValidNewAutSize) {
+					throw new InvalidOperationException ("Must set AutoSize to false before set the Width.");
+				}
+			}
+			OnResizeNeeded ();
+		}
+	}
+
+	/// <summary>
+	/// Gets or sets the height of the view. Only used when <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Computed"/>.
+	/// </summary>
+	/// <value>The width.</value>
+	/// <remarks>
+	/// <para>
+	/// If <see cref="LayoutStyle"/> is <see cref="Terminal.Gui.LayoutStyle.Absolute"/> changing this property
+	/// has no effect and its value is indeterminate. 
+	/// </para>
+	/// <para>
+	/// <see langword="null"/> is the same as <c>Dim.Fill (0)</c>.
+	/// </para>
+	/// </remarks>
+	public Dim Height {
+		get => VerifyIsInitialized (_height);
+		set {
+			// BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it?
+			if (ValidatePosDim) {
+				if (LayoutStyle == LayoutStyle.Computed) {
+					CheckAbsolute (nameof (Height), _height, value);
+				}
+			}
+
+			_height = value;
+
+			if (ValidatePosDim) {
+				bool isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height);
+
+				if (IsAdded && AutoSize && !isValidNewAutSize) {
+					throw new InvalidOperationException ("Must set AutoSize to false before setting the Height.");
+				}
+			}
+			OnResizeNeeded ();
+		}
+	}
+
+	// Diagnostics to highlight when X or Y is read before the view has been initialized
+	Pos VerifyIsInitialized (Pos pos)
+	{
+#if DEBUG
+		if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
+			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; position is indeterminate {pos}. This is likely a bug.");
+		}
+#endif // DEBUG
+		return pos;
+	}
+
+	// Diagnostics to highlight when Width or Height is read before the view has been initialized
+	Dim VerifyIsInitialized (Dim dim)
+	{
+#if DEBUG
+		if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) {
+			Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; dimension is indeterminate: {dim}. This is likely a bug.");
+		}
+#endif // DEBUG		
+		return dim;
+	}
+
+	/// <summary>
+	/// 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 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="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.
+	/// </summary>
+	/// <remarks>
+	/// Can be overridden if the view resize behavior is different than the default.
+	/// </remarks>
+	protected virtual void OnResizeNeeded ()
+	{
+		int actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X;
+		int 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 ();
+			int w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width;
+			int 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 {
+			int w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width;
+			int 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) {
+			SetFrameToFitText ();
+			LayoutFrames ();
+			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+			SetNeedsLayout ();
+			SetNeedsDisplay ();
+		}
+	}
+
+	internal bool LayoutNeeded { get; private set; } = true;
+
+	internal void SetNeedsLayout ()
+	{
+		if (LayoutNeeded) {
+			return;
+		}
+		LayoutNeeded = true;
+		foreach (var view in Subviews) {
+			view.SetNeedsLayout ();
+		}
+		TextFormatter.NeedsFormat = true;
+		SuperView?.SetNeedsLayout ();
+	}
+
+	/// <summary>
+	/// Indicates that the view does not need to be laid out.
+	/// </summary>
+	protected void ClearLayoutNeeded () => LayoutNeeded = false;
+
+	/// <summary>
+	/// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means
+	/// relative to the View's <see cref="SuperView"/>'s <see cref="Bounds"/>.
+	/// </summary>
+	/// <returns>The coordinate relative to the <see cref="SuperView"/>'s <see cref="Bounds"/>.</returns>
+	/// <param name="x">Screen-relative column.</param>
+	/// <param name="y">Screen-relative row.</param>
+	public Point ScreenToFrame (int x, int y)
+	{
+		var superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty;
+		var ret = new Point (x - Frame.X - superViewBoundsOffset.X, y - Frame.Y - superViewBoundsOffset.Y);
+		if (SuperView != null) {
+			var superFrame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y);
+			ret = new Point (superFrame.X - Frame.X, superFrame.Y - Frame.Y);
+		}
+		return ret;
+	}
+
+	/// <summary>
+	/// Converts a screen-relative coordinate to a bounds-relative coordinate. 
+	/// </summary>
+	/// <returns>The coordinate relative to this view's <see cref="Bounds"/>.</returns>
+	/// <param name="x">Screen-relative column.</param>
+	/// <param name="y">Screen-relative row.</param>
+	public Point ScreenToBounds (int x, int y)
+	{
+		var screen = ScreenToFrame (x, y);
+		var boundsOffset = GetBoundsOffset ();
+		return new Point (screen.X - boundsOffset.X, screen.Y - boundsOffset.Y);
+	}
+
+	/// <summary>
+	/// Converts a <see cref="Bounds"/>-relative coordinate to a screen-relative coordinate. The output is optionally clamped to the screen dimensions.
+	/// </summary>
+	/// <param name="x"><see cref="Bounds"/>-relative column.</param>
+	/// <param name="y"><see cref="Bounds"/>-relative row.</param>
+	/// <param name="rx">Absolute column; screen-relative.</param>
+	/// <param name="ry">Absolute row; screen-relative.</param>
+	/// <param name="clamped">If <see langword="true"/>, <paramref name="rx"/> and <paramref name="ry"/> will be clamped to the 
+	/// screen dimensions (will never be negative and will always be less than <see cref="ConsoleDriver.Cols"/> and
+	/// <see cref="ConsoleDriver.Rows"/>, respectively.</param>
+	public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true)
+	{
+		var boundsOffset = GetBoundsOffset ();
+		rx = x + Frame.X + boundsOffset.X;
+		ry = y + Frame.Y + boundsOffset.Y;
+
+		var super = SuperView;
+		while (super != null) {
+			boundsOffset = super.GetBoundsOffset ();
+			rx += super.Frame.X + boundsOffset.X;
+			ry += super.Frame.Y + boundsOffset.Y;
+			super = super.SuperView;
+		}
+
+		// The following ensures that the cursor is always in the screen boundaries.
+		if (clamped) {
+			ry = Math.Min (ry, Driver.Rows - 1);
+			rx = Math.Min (rx, Driver.Cols - 1);
+		}
+	}
+
+	/// <summary>
+	/// Converts a <see cref="Bounds"/>-relative region to a screen-relative region. 
+	/// </summary>
+	public Rect BoundsToScreen (Rect region)
+	{
+		BoundsToScreen (region.X, region.Y, out int x, out int y, false);
+		return new Rect (x, y, region.Width, region.Height);
+	}
+
+	/// <summary>
+	/// Gets the <see cref="Frame"/> with a screen-relative location. 
+	/// </summary>
+	/// <returns>The location and size of the view in screen-relative coordinates.</returns>
+	public virtual Rect FrameToScreen ()
+	{
+		var ret = Frame;
+		var super = SuperView;
+		while (super != null) {
+			var boundsOffset = super.GetBoundsOffset ();
+			ret.X += super.Frame.X + boundsOffset.X;
+			ret.Y += super.Frame.Y + boundsOffset.Y;
+			super = super.SuperView;
+		}
+		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
+	/// <see cref="Frame"/>, given a rectangle describing the SuperView's Bounds (nominally the same as <c>this.SuperView.Bounds</c>).
+	/// </summary>
+	/// <param name="superviewBounds">The rectangle describing the SuperView's Bounds (nominally the same as <c>this.SuperView.Bounds</c>).</param>
+	internal void SetRelativeLayout (Rect superviewBounds)
+	{
+		int newX, newW, newY, newH;
+		var autosize = Size.Empty;
+
+		if (AutoSize) {
+			// Note this is global to this function and used as such within the local functions defined
+			// below. In v2 AutoSize will be re-factored to not need to be dealt with in this function.
+			autosize = GetAutoSize ();
+		}
+
+		// 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)
+		//   the current Dim (View.Width or View.Height)
+		// This method is called recursively if pos is Pos.PosCombine
+		(int newLocation, int newDimension) GetNewLocationAndDimension (bool width, Rect superviewBounds, Pos pos, Dim dim, int autosizeDimension)
+		{
+			// 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)
+			//   autosize: the size to use if autosize = true
+			// This mehod 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:
+					int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
+					int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
+					if (combine._add) {
+						newDimension = leftNewDim + rightNewDim;
+					} else {
+						newDimension = leftNewDim - rightNewDim;
+					}
+					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+					break;
+
+				case Dim.DimFactor factor when !factor.IsFromRemaining ():
+					newDimension = d.Anchor (dimension);
+					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+					break;
+					
+				case Dim.DimFill:
+				default:
+					newDimension = Math.Max (d.Anchor (dimension - location), 0);
+					newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+					break;
+				}
+
+				return newDimension;
+			}
+
+			int newDimension, newLocation;
+			int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height;
+
+			// Determine new location
+			switch (pos) {
+			case Pos.PosCenter posCenter:
+				// For Center, the dimension is dependent on location, but we need to force getting the dimension first
+				// using a location of 0
+				newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0);
+				newLocation = posCenter.Anchor (superviewDimension - newDimension);
+				newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+				break;
+
+			case Pos.PosCombine combine:
+				int left, right;
+				(left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension);
+				(right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension);
+				if (combine._add) {
+					newLocation = left + right;
+				} else {
+					newLocation = left - right;
+				}
+				newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+				break;
+
+			case Pos.PosAnchorEnd:
+			case Pos.PosAbsolute:
+			case null:
+			case Pos.PosFactor:
+			case Pos.PosFunc:
+			case Pos.PosView:
+			default:
+				newLocation = pos?.Anchor (superviewDimension) ?? 0;
+				newDimension = Math.Max (GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+				break;
+			}
+
+
+			return (newLocation, newDimension);
+		}
+
+
+		// horizontal/width
+		(newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width);
+
+		// vertical/height
+		(newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height);
+
+		var r = new Rect (newX, newY, newW, newH);
+		if (Frame != r) {
+			Frame = r;
+			// BUGBUG: Why is this AFTER setting Frame? Seems duplicative.
+			if (!SetFrameToFitText ()) {
+				TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+			}
+		}
+	}
+
+	/// <summary>
+	/// Fired after the View's <see cref="LayoutSubviews"/> method has completed. 
+	/// </summary>
+	/// <remarks>
+	/// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise changed.
+	/// </remarks>
+	public event EventHandler<LayoutEventArgs> LayoutStarted;
+
+	/// <summary>
+	/// Raises the <see cref="LayoutStarted"/> event. Called from  <see cref="LayoutSubviews"/> before any subviews have been laid out.
+	/// </summary>
+	internal virtual void OnLayoutStarted (LayoutEventArgs args) => LayoutStarted?.Invoke (this, args);
+
+	/// <summary>
+	/// Fired after the View's <see cref="LayoutSubviews"/> method has completed. 
+	/// </summary>
+	/// <remarks>
+	/// Subscribe to this event to perform tasks when the <see cref="View"/> has been resized or the layout has otherwise changed.
+	/// </remarks>
+	public event EventHandler<LayoutEventArgs> LayoutComplete;
+
+	/// <summary>
+	/// Event called only once when the <see cref="View"/> is being initialized for the first time.
+	/// Allows configurations and assignments to be performed before the <see cref="View"/> being shown.
+	/// This derived from <see cref="ISupportInitializeNotification"/> to allow notify all the views that are being initialized.
+	/// </summary>
+	public event EventHandler Initialized;
+
+	/// <summary>
+	/// 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);
+
+	internal void CollectPos (Pos pos, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+	{
+		switch (pos) {
+		case Pos.PosView pv:
+			// See #2461
+			//if (!from.InternalSubviews.Contains (pv.Target)) {
+			//	throw new InvalidOperationException ($"View {pv.Target} is not a subview of {from}");
+			//}
+			if (pv.Target != this) {
+				nEdges.Add ((pv.Target, from));
+			}
+			return;
+		case Pos.PosCombine pc:
+			CollectPos (pc._left, from, ref nNodes, ref nEdges);
+			CollectPos (pc._right, from, ref nNodes, ref nEdges);
+			break;
+		}
+	}
+
+	internal void CollectDim (Dim dim, View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+	{
+		switch (dim) {
+		case Dim.DimView dv:
+			// See #2461
+			//if (!from.InternalSubviews.Contains (dv.Target)) {
+			//	throw new InvalidOperationException ($"View {dv.Target} is not a subview of {from}");
+			//}
+			if (dv.Target != this) {
+				nEdges.Add ((dv.Target, from));
+			}
+			return;
+		case Dim.DimCombine dc:
+			CollectDim (dc._left, from, ref nNodes, ref nEdges);
+			CollectDim (dc._right, from, ref nNodes, ref nEdges);
+			break;
+		}
+	}
+
+	internal void CollectAll (View from, ref HashSet<View> nNodes, ref HashSet<(View, View)> nEdges)
+	{
+		// BUGBUG: This should really only work on initialized subviews
+		foreach (var v in from.InternalSubviews /*.Where(v => v.IsInitialized)*/) {
+			nNodes.Add (v);
+			if (v._layoutStyle != LayoutStyle.Computed) {
+				continue;
+			}
+			CollectPos (v.X, v, ref nNodes, ref nEdges);
+			CollectPos (v.Y, v, ref nNodes, ref nEdges);
+			CollectDim (v.Width, v, ref nNodes, ref nEdges);
+			CollectDim (v.Height, v, ref nNodes, ref nEdges);
+		}
+	}
+
+	// https://en.wikipedia.org/wiki/Topological_sorting
+	internal static List<View> TopologicalSort (View superView, IEnumerable<View> nodes, ICollection<(View From, View To)> edges)
+	{
+		var result = new List<View> ();
+
+		// Set of all nodes with no incoming edges
+		var noEdgeNodes = new HashSet<View> (nodes.Where (n => edges.All (e => !e.To.Equals (n))));
+
+		while (noEdgeNodes.Any ()) {
+			//  remove a node n from S
+			var n = noEdgeNodes.First ();
+			noEdgeNodes.Remove (n);
+
+			// add n to tail of L
+			if (n != superView) {
+				result.Add (n);
+			}
+
+			// for each node m with an edge e from n to m do
+			foreach (var e in edges.Where (e => e.From.Equals (n)).ToArray ()) {
+				var m = e.To;
+
+				// remove edge e from the graph
+				edges.Remove (e);
+
+				// if m has no other incoming edges then
+				if (edges.All (me => !me.To.Equals (m)) && m != superView) {
+					// insert m into S
+					noEdgeNodes.Add (m);
+				}
+			}
+		}
+
+		if (!edges.Any ()) {
+			return result;
+		}
+
+		foreach ((var from, var to) in edges) {
+			if (from == to) {
+				// if not yet added to the result, add it and remove from edge
+				if (result.Find (v => v == from) == null) {
+					result.Add (from);
+				}
+				edges.Remove ((from, to));
+			} else if (from.SuperView == to.SuperView) {
+				// if 'from' is not yet added to the result, add it
+				if (result.Find (v => v == from) == null) {
+					result.Add (from);
+				}
+				// if 'to' is not yet added to the result, add it
+				if (result.Find (v => v == to) == null) {
+					result.Add (to);
+				}
+				// remove from edge
+				edges.Remove ((from, to));
+			} else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) {
+				if (ReferenceEquals (from.SuperView, to)) {
+					throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\").");
+				} else {
+					throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?");
+				}
+			}
+		}
+		// return L (a topologically sorted order)
+		return result;
+	} // TopologicalSort
+
+	/// <summary>
+	/// Overriden by <see cref="Frame"/> to do nothing, as the <see cref="Frame"/> does not have frames.
+	/// </summary>
+	internal virtual void LayoutFrames ()
+	{
+		if (Margin == null) {
+			return; // CreateFrames() has not been called yet
+		}
+
+		if (Margin.Frame.Size != Frame.Size) {
+			Margin._frame = new Rect (Point.Empty, Frame.Size);
+			Margin.X = 0;
+			Margin.Y = 0;
+			Margin.Width = Frame.Size.Width;
+			Margin.Height = Frame.Size.Height;
+			Margin.SetNeedsLayout ();
+			Margin.LayoutSubviews ();
+			Margin.SetNeedsDisplay ();
+		}
+
+		var border = Margin.Thickness.GetInside (Margin.Frame);
+		if (border != Border.Frame) {
+			Border._frame = new Rect (new Point (border.Location.X, border.Location.Y), border.Size);
+			Border.X = border.Location.X;
+			Border.Y = border.Location.Y;
+			Border.Width = border.Size.Width;
+			Border.Height = border.Size.Height;
+			Border.SetNeedsLayout ();
+			Border.LayoutSubviews ();
+			Border.SetNeedsDisplay ();
+		}
+
+		var padding = Border.Thickness.GetInside (Border.Frame);
+		if (padding != Padding.Frame) {
+			Padding._frame = new Rect (new Point (padding.Location.X, padding.Location.Y), padding.Size);
+			Padding.X = padding.Location.X;
+			Padding.Y = padding.Location.Y;
+			Padding.Width = padding.Size.Width;
+			Padding.Height = padding.Size.Height;
+			Padding.SetNeedsLayout ();
+			Padding.LayoutSubviews ();
+			Padding.SetNeedsDisplay ();
+		}
+	}
+
+	/// <summary>
+	/// Invoked when a view starts executing or when the dimensions of the view have changed, for example in
+	/// response to the container view or terminal resizing.
+	/// </summary>
+	/// <remarks>
+	/// Raises the <see cref="LayoutComplete"/> event) before it returns.
+	/// </remarks>
+	public virtual void LayoutSubviews ()
+	{
+		if (!IsInitialized) {
+			Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}");
+		}
+
+		if (!LayoutNeeded) {
+			return;
+		}
+
+		LayoutFrames ();
+
+		var oldBounds = Bounds;
+		OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds });
+
+		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+
+		// Sort out the dependencies of the X, Y, Width, Height properties
+		var nodes = new HashSet<View> ();
+		var edges = new HashSet<(View, View)> ();
+		CollectAll (this, ref nodes, ref edges);
+		var ordered = TopologicalSort (SuperView, nodes, edges);
+		foreach (var v in ordered) {
+			LayoutSubview (v, new Rect (GetBoundsOffset (), Bounds.Size));
+		}
+
+		// If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
+		// Use LayoutSubview with the Frame of the 'from' 
+		if (SuperView != null && GetTopSuperView () != null && LayoutNeeded && edges.Count > 0) {
+			foreach ((var from, var to) in edges) {
+				LayoutSubview (to, from.Frame);
+			}
+		}
+
+		LayoutNeeded = false;
+
+		OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds });
+	}
+
+	void LayoutSubview (View v, Rect contentArea)
+	{
+		if (v.LayoutStyle == LayoutStyle.Computed) {
+			v.SetRelativeLayout (contentArea);
+		}
+
+		v.LayoutSubviews ();
+		v.LayoutNeeded = false;
+	}
+
+	bool _autoSize;
+
+	/// <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"/>
+	/// <para>
+	/// The default is <see langword="false"/>. Set to <see langword="true"/> to turn on AutoSize. If <see langword="true"/> then
+	/// <see cref="Width"/> and <see cref="Height"/> will be used if <see cref="Text"/> can fit; 
+	/// 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.
+	/// </para>
+	/// </summary>
+	public virtual bool AutoSize {
+		get => _autoSize;
+		set {
+			bool v = ResizeView (value);
+			TextFormatter.AutoSize = v;
+			if (_autoSize != v) {
+				_autoSize = v;
+				TextFormatter.NeedsFormat = true;
+				UpdateTextFormatterText ();
+				OnResizeNeeded ();
+			}
+		}
+	}
+
+	bool ResizeView (bool autoSize)
+	{
+		if (!autoSize) {
+			return false;
+		}
+
+		bool boundsChanged = true;
+		var newFrameSize = GetAutoSize ();
+		if (IsInitialized && newFrameSize != Frame.Size) {
+			if (ValidatePosDim) {
+				// BUGBUG: This ain't right, obviously.  We need to figure out how to handle this.
+				boundsChanged = ResizeBoundsToFit (newFrameSize);
+			} else {
+				Height = newFrameSize.Height;
+				Width = newFrameSize.Width;
+			}
+		}
+		// BUGBUG: This call may be redundant
+		TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
+		return boundsChanged;
+	}
+
+	/// <summary>
+	/// Resizes the View to fit the specified size. Factors in the HotKey.
+	/// </summary>
+	/// <param name="size"></param>
+	/// <returns>whether the Bounds was changed or not</returns>
+	bool ResizeBoundsToFit (Size size)
+	{
+		bool boundsChanged = false;
+		bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW);
+		bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH);
+		if (canSizeW) {
+			boundsChanged = true;
+			_width = rW;
+		}
+		if (canSizeH) {
+			boundsChanged = true;
+			_height = rH;
+		}
+		if (boundsChanged) {
+			Bounds = new Rect (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height);
+		}
+
+		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 ()
+	{
+		int x = 0;
+		int 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.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal;
+		int 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);
+		int 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);
+		int 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.
+	/// </summary>
+	/// <param name="desiredWidth"></param>
+	/// <param name="resultWidth">Contains the width that would result if <see cref="Width"/> were set to <paramref name="desiredWidth"/>"/> </param>
+	/// <returns><see langword="true"/> if the View's <see cref="Width"/> can be changed to the specified value. False otherwise.</returns>
+	internal bool TrySetWidth (int desiredWidth, out int resultWidth)
+	{
+		int w = desiredWidth;
+		bool canSetWidth;
+		switch (Width) {
+		case Dim.DimCombine _:
+		case Dim.DimView _:
+		case Dim.DimFill _:
+			// It's a Dim.DimCombine and so can't be assigned. Let it have it's Width anchored.
+			w = Width.Anchor (w);
+			canSetWidth = !ValidatePosDim;
+			break;
+		case Dim.DimFactor factor:
+			// Tries to get the SuperView Width otherwise the view Width.
+			int sw = SuperView != null ? SuperView.Frame.Width : w;
+			if (factor.IsFromRemaining ()) {
+				sw -= Frame.X;
+			}
+			w = Width.Anchor (sw);
+			canSetWidth = !ValidatePosDim;
+			break;
+		default:
+			canSetWidth = true;
+			break;
+		}
+		resultWidth = w;
+
+		return canSetWidth;
+	}
+
+	/// <summary>
+	/// Determines if the View's <see cref="Height"/> can be set to a new value.
+	/// </summary>
+	/// <param name="desiredHeight"></param>
+	/// <param name="resultHeight">Contains the width that would result if <see cref="Height"/> were set to <paramref name="desiredHeight"/>"/> </param>
+	/// <returns><see langword="true"/> if the View's <see cref="Height"/> can be changed to the specified value. False otherwise.</returns>
+	internal bool TrySetHeight (int desiredHeight, out int resultHeight)
+	{
+		int h = desiredHeight;
+		bool canSetHeight;
+		switch (Height) {
+		case Dim.DimCombine _:
+		case Dim.DimView _:
+		case Dim.DimFill _:
+			// It's a Dim.DimCombine and so can't be assigned. Let it have it's height anchored.
+			h = Height.Anchor (h);
+			canSetHeight = !ValidatePosDim;
+			break;
+		case Dim.DimFactor factor:
+			// Tries to get the SuperView height otherwise the view height.
+			int sh = SuperView != null ? SuperView.Frame.Height : h;
+			if (factor.IsFromRemaining ()) {
+				sh -= Frame.Y;
+			}
+			h = Height.Anchor (sh);
+			canSetHeight = !ValidatePosDim;
+			break;
+		default:
+			canSetHeight = true;
+			break;
+		}
+		resultHeight = h;
+
+		return canSetHeight;
+	}
+
+	/// <summary>
+	/// Finds which view that belong to the <paramref name="start"/> superview at the provided location.
+	/// </summary>
+	/// <param name="start">The superview where to look for.</param>
+	/// <param name="x">The column location in the superview.</param>
+	/// <param name="y">The row location in the superview.</param>
+	/// <param name="resx">The found view screen relative column location.</param>
+	/// <param name="resy">The found view screen relative row location.</param>
+	/// <returns>
+	///  The view that was found at the <praramref name="x"/> and <praramref name="y"/> coordinates.
+	///  <see langword="null"/> if no view was found.
+	/// </returns>
+	public static View FindDeepestView (View start, int x, int y, out int resx, out int resy)
+	{
+		resy = resx = 0;
+		if (start == null || !start.Frame.Contains (x, y)) {
+			return null;
+		}
+
+		var startFrame = start.Frame;
+		if (start.InternalSubviews != null) {
+			int count = start.InternalSubviews.Count;
+			if (count > 0) {
+				var boundsOffset = start.GetBoundsOffset ();
+				int rx = x - (startFrame.X + boundsOffset.X);
+				int ry = y - (startFrame.Y + boundsOffset.Y);
+				for (int i = count - 1; i >= 0; i--) {
+					var v = start.InternalSubviews [i];
+					if (v.Visible && v.Frame.Contains (rx, ry)) {
+						var deep = FindDeepestView (v, rx, ry, out resx, out resy);
+						if (deep == null) {
+							return v;
+						}
+						return deep;
+					}
+				}
+			}
+		}
+		resx = x - startFrame.X;
+		resy = y - startFrame.Y;
+		return start;
+	}
+}

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

@@ -280,6 +280,7 @@ namespace Terminal.Gui {
 				_oldCanFocus = CanFocus;
 				_oldTabIndex = _tabIndex;
 
+				// BUGBUG: These should move to EndInit as they access Bounds causing debug spew.
 				UpdateTextDirection (TextDirection);
 				UpdateTextFormatterText ();
 				SetHotKey ();

+ 73 - 4
Terminal.Gui/View/ViewText.cs

@@ -1,5 +1,6 @@
 using System.Text;
 using System;
+using System.Collections.Generic;
 
 namespace Terminal.Gui {
 
@@ -122,20 +123,88 @@ namespace Terminal.Gui {
 
 			UpdateTextFormatterText ();
 
-			if ((!ForceValidatePosDim && directionChanged && AutoSize)
-			    || (ForceValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) {
+			if ((!ValidatePosDim && directionChanged && AutoSize)
+			    || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize)) {
 				OnResizeNeeded ();
 			} else if (directionChanged && IsAdded) {
 				ResizeBoundsToFit (Bounds.Size);
 				// BUGBUG: I think this call is redundant.
-				SetBoundsToFitFrame ();
+				SetFrameToFitText ();
 			} else {
-				SetBoundsToFitFrame ();
+				SetFrameToFitText ();
 			}
 			TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey ();
 			SetNeedsDisplay ();
 		}
 
+
+		/// <summary>
+		/// 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>
+		/// <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.
+		/// Does not take into account word wrapping.
+		/// </remarks>
+		bool SetFrameToFitText ()
+		{
+			// BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height
+			// <summary>
+			// Gets the minimum dimensions required to fit the View's <see cref="Text"/>, factoring in <see cref="TextDirection"/>.
+			// </summary>
+			// <param name="sizeRequired">The minimum dimensions required.</param>
+			// <returns><see langword="true"/> if the dimensions fit within the View's <see cref="Bounds"/>, <see langword="false"/> otherwise.</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.
+			// Does not take into account word wrapping.
+			// </remarks>
+			bool GetMinimumSizeOfText (out Size sizeRequired)
+			{
+				if (!IsInitialized) {
+					sizeRequired = new Size (0, 0);
+					return false;
+				}
+				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;
+					}
+				}
+				return false;
+			}
+
+			if (GetMinimumSizeOfText (out var size)) {
+				_frame = new Rect (_frame.Location, size);
+				return true;
+			}
+			return false;
+		}
+
 		/// <summary>
 		/// Gets the width or height of the <see cref="Terminal.Gui.TextFormatter.HotKeySpecifier"/> characters 
 		/// in the <see cref="Text"/> property.

+ 201 - 201
Terminal.Gui/Views/Dialog.cs

@@ -1,243 +1,243 @@
-//
-// Dialog.cs: Dialog box
-//
-// Authors:
-//   Miguel de Icaza ([email protected])
-//
-using System;
+using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text.Json.Serialization;
-using System.Text;
-using Terminal.Gui;
-using static Terminal.Gui.ConfigurationManager;
 
-namespace Terminal.Gui {
+namespace Terminal.Gui;
+
+/// <summary>
+/// The <see cref="Dialog"/> <see cref="View"/> is a <see cref="Window"/> that by default is centered and contains one 
+/// or more <see cref="Button"/>s. It defaults to the <see cref="Colors.Dialog"/> color scheme and has a 1 cell padding around the edges.
+/// </summary>
+/// <remarks>
+///  To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to <see cref="Application.Run(Func{Exception, bool})"/>. 
+///  This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views
+///  or buttons added to the dialog calls <see cref="Application.RequestStop"/>.
+/// </remarks>
+public class Dialog : Window {
 	/// <summary>
-	/// The <see cref="Dialog"/> <see cref="View"/> is a <see cref="Window"/> that by default is centered and contains one 
-	/// or more <see cref="Button"/>s. It defaults to the <see cref="Colors.Dialog"/> color scheme and has a 1 cell padding around the edges.
+	/// The default <see cref="ButtonAlignments"/> for <see cref="Dialog"/>. 
 	/// </summary>
 	/// <remarks>
-	///  To run the <see cref="Dialog"/> modally, create the <see cref="Dialog"/>, and pass it to <see cref="Application.Run(Func{Exception, bool})"/>. 
-	///  This will execute the dialog until it terminates via the [ESC] or [CTRL-Q] key, or when one of the views
-	///  or buttons added to the dialog calls <see cref="Application.RequestStop"/>.
+	/// This property can be set in a Theme.
 	/// </remarks>
-	public class Dialog : Window {
-		/// <summary>
-		/// The default <see cref="ButtonAlignments"/> for <see cref="Dialog"/>. 
-		/// </summary>
-		/// <remarks>
-		/// This property can be set in a Theme.
-		/// </remarks>
-		[SerializableConfigurationProperty (Scope = typeof (ThemeScope)), JsonConverter (typeof (JsonStringEnumConverter))]
-		public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center;
-
-		// TODO: Reenable once border/borderframe design is settled
-		/// <summary>
-		/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via <see cref="ConfigurationManager"/>.
-		/// </summary>
-		//[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
-		//public static Border DefaultBorder { get; set; } = new Border () {
-		//	LineStyle = LineStyle.Single,
-		//};
+	[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+	[JsonConverter (typeof (JsonStringEnumConverter))]
+	public static ButtonAlignments DefaultButtonAlignment { get; set; } = ButtonAlignments.Center;
 
-		internal List<Button> buttons = new List<Button> ();
+	// TODO: Reenable once border/borderframe design is settled
+	/// <summary>
+	/// Defines the default border styling for <see cref="Dialog"/>. Can be configured via <see cref="ConfigurationManager"/>.
+	/// </summary>
+	//[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+	//public static Border DefaultBorder { get; set; } = new Border () {
+	//	LineStyle = LineStyle.Single,
+	//};
+	internal List<Button> buttons = new ();
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Dialog"/> class using <see cref="LayoutStyle.Computed"/> positioning 
-		/// with no <see cref="Button"/>s.
-		/// </summary>
-		/// <remarks>
-		/// By default, <see cref="View.X"/> and <see cref="View.Y"/> are set to <c>Pos.Center ()</c> and <see cref="View.Width"/> and <see cref="View.Height"/> are set 
-		/// to <c>Width = Dim.Percent (85)</c>, centering the Dialog vertically and horizontally. 
-		/// </remarks>
-		public Dialog () : this (null) { }
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Dialog"/> class using <see cref="LayoutStyle.Computed"/> positioning 
+	/// with no <see cref="Button"/>s.
+	/// </summary>
+	/// <remarks>
+	/// By default, <see cref="View.X"/> and <see cref="View.Y"/> are set to <c>Pos.Center ()</c> and <see cref="View.Width"/> and <see cref="View.Height"/> are set 
+	/// to <c>Width = Dim.Percent (85)</c>, centering the Dialog vertically and horizontally. 
+	/// </remarks>
+	public Dialog () : this (null) { }
 
-		/// <summary>
-		/// Initializes a new instance of the <see cref="Dialog"/> class using <see cref="LayoutStyle.Computed"/> positioning 
-		/// and an optional set of <see cref="Button"/>s to display
-		/// </summary>
-		/// <param name="buttons">Optional buttons to lay out at the bottom of the dialog.</param>
-		/// <remarks>
-		/// By default, <see cref="View.X"/> and <see cref="View.Y"/> are set to <c>Pos.Center ()</c> and <see cref="View.Width"/> and <see cref="View.Height"/> are set 
-		/// to <c>Width = Dim.Percent (85)</c>, centering the Dialog vertically and horizontally. 
-		/// </remarks>
-		public Dialog (params Button [] buttons) : base ()
-		{
-			SetInitialProperties (buttons);
-		}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Dialog"/> class using <see cref="LayoutStyle.Computed"/> positioning 
+	/// and an optional set of <see cref="Button"/>s to display
+	/// </summary>
+	/// <param name="buttons">Optional buttons to lay out at the bottom of the dialog.</param>
+	/// <remarks>
+	/// By default, <see cref="View.X"/> and <see cref="View.Y"/> are set to <c>Pos.Center ()</c> and <see cref="View.Width"/> and <see cref="View.Height"/> are set 
+	/// to <c>Width = Dim.Percent (85)</c>, centering the Dialog vertically and horizontally. 
+	/// </remarks>
+	public Dialog (params Button [] buttons) : base () => SetInitialProperties (buttons);
 
-		private void SetInitialProperties (Button [] buttons)
-		{
-			X = Pos.Center ();
-			Y = Pos.Center ();
+	void SetInitialProperties (Button [] buttons)
+	{
+		X = Pos.Center ();
+		Y = Pos.Center ();
+		ValidatePosDim = true;
 
-			Width = Dim.Percent (85);
-			Height = Dim.Percent (85);
+		Width = Dim.Percent (85);// Dim.Auto (min: Dim.Percent (10));
+		Height = Dim.Percent (85);//Dim.Auto (min: Dim.Percent (50));
 
-			ColorScheme = Colors.Dialog;
+		ColorScheme = Colors.Dialog;
 
-			Modal = true;
-			ButtonAlignment = DefaultButtonAlignment;
+		Modal = true;
+		ButtonAlignment = DefaultButtonAlignment;
 
-			if (buttons != null) {
-				foreach (var b in buttons) {
-					AddButton (b);
-				}
+		if (buttons != null) {
+			foreach (var b in buttons) {
+				AddButton (b);
 			}
+		}
+	}
 
-			LayoutComplete += (s, args) => {
-				LayoutButtons ();
-			};
+	bool inLayout = false;
 
+	/// <inheritdoc />
+	public override void LayoutSubviews ()
+	{
+		if (inLayout) {
+			return;
 		}
+		inLayout = true;
+		LayoutButtons ();
+		base.LayoutSubviews ();
+		inLayout = false;
+	}
 
-		/// <summary>
-		/// Adds a <see cref="Button"/> to the <see cref="Dialog"/>, its layout will be controlled by the <see cref="Dialog"/>
-		/// </summary>
-		/// <param name="button">Button to add.</param>
-		public void AddButton (Button button)
-		{
-			if (button == null) {
-				return;
-			}
+	/// <summary>
+	/// Adds a <see cref="Button"/> to the <see cref="Dialog"/>, its layout will be controlled by the <see cref="Dialog"/>
+	/// </summary>
+	/// <param name="button">Button to add.</param>
+	public void AddButton (Button button)
+	{
+		if (button == null) {
+			return;
+		}
 
-			//button.AutoSize = false; // BUGBUG: v2 - Hack to get around autosize not accounting for Margin?
-			buttons.Add (button);
-			Add (button);
-			SetNeedsDisplay ();
-			if (IsInitialized) {
-				LayoutSubviews ();
-			}
+		//button.AutoSize = false; // BUGBUG: v2 - Hack to get around autosize not accounting for Margin?
+		buttons.Add (button);
+		Add (button);
+		SetNeedsDisplay ();
+		if (IsInitialized) {
+			LayoutSubviews ();
 		}
+	}
 
-		// Get the width of all buttons, not including any Margin.
-		internal int GetButtonsWidth ()
-		{
-			if (buttons.Count == 0) {
-				return 0;
-			}
-			//var widths = buttons.Select (b => b.TextFormatter.GetFormattedSize ().Width + b.BorderFrame.Thickness.Horizontal + b.Padding.Thickness.Horizontal);
-			var widths = buttons.Select (b => b.Frame.Width);
-			return widths.Sum ();
+	// Get the width of all buttons, not including any Margin.
+	internal int GetButtonsWidth ()
+	{
+		if (buttons.Count == 0) {
+			return 0;
 		}
+		//var widths = buttons.Select (b => b.TextFormatter.GetFormattedSize ().Width + b.BorderFrame.Thickness.Horizontal + b.Padding.Thickness.Horizontal);
+		var widths = buttons.Select (b => b.Frame.Width);
+		return widths.Sum ();
+	}
 
+	/// <summary>
+	/// Determines the horizontal alignment of the Dialog buttons.
+	/// </summary>
+	public enum ButtonAlignments {
 		/// <summary>
-		/// Determines the horizontal alignment of the Dialog buttons.
+		/// Center-aligns the buttons (the default).
 		/// </summary>
-		public enum ButtonAlignments {
-			/// <summary>
-			/// Center-aligns the buttons (the default).
-			/// </summary>
-			Center = 0,
-
-			/// <summary>
-			/// Justifies the buttons
-			/// </summary>
-			Justify,
-
-			/// <summary>
-			/// Left-aligns the buttons
-			/// </summary>
-			Left,
-
-			/// <summary>
-			/// Right-aligns the buttons
-			/// </summary>
-			Right
-		}
+		Center = 0,
 
 		/// <summary>
-		/// Determines how the <see cref="Dialog"/> <see cref="Button"/>s are aligned along the 
-		/// bottom of the dialog. 
+		/// Justifies the buttons
 		/// </summary>
-		public ButtonAlignments ButtonAlignment { get; set; }
-
-		void LayoutButtons ()
-		{
-			if (buttons.Count == 0 || !IsInitialized) return;
-
-			int shiftLeft = 0;
-
-			int buttonsWidth = GetButtonsWidth ();
-			switch (ButtonAlignment) {
-			case ButtonAlignments.Center:
-				// Center Buttons
-				shiftLeft = (Bounds.Width - buttonsWidth - buttons.Count - 1) / 2 + 1;
-				for (int i = buttons.Count - 1; i >= 0; i--) {
-					Button button = buttons [i];
-					shiftLeft += button.Frame.Width + (i == buttons.Count - 1 ? 0 : 1);
-					if (shiftLeft > -1) {
-						button.X = Pos.AnchorEnd (shiftLeft);
-					} else {
-						button.X = Bounds.Width - shiftLeft;
-					}
-					button.Y = Pos.AnchorEnd (1);
+		Justify,
+
+		/// <summary>
+		/// Left-aligns the buttons
+		/// </summary>
+		Left,
+
+		/// <summary>
+		/// Right-aligns the buttons
+		/// </summary>
+		Right
+	}
+
+	/// <summary>
+	/// Determines how the <see cref="Dialog"/> <see cref="Button"/>s are aligned along the 
+	/// bottom of the dialog. 
+	/// </summary>
+	public ButtonAlignments ButtonAlignment { get; set; }
+
+	void LayoutButtons ()
+	{
+		if (buttons.Count == 0 || !IsInitialized) {
+			return;
+		}
+
+		int shiftLeft = 0;
+
+		int buttonsWidth = GetButtonsWidth ();
+		switch (ButtonAlignment) {
+		case ButtonAlignments.Center:
+			// Center Buttons
+			shiftLeft = (Bounds.Width - buttonsWidth - buttons.Count - 1) / 2 + 1;
+			for (int i = buttons.Count - 1; i >= 0; i--) {
+				var button = buttons [i];
+				shiftLeft += button.Frame.Width + (i == buttons.Count - 1 ? 0 : 1);
+				if (shiftLeft > -1) {
+					button.X = Pos.AnchorEnd (shiftLeft);
+				} else {
+					button.X = Bounds.Width - shiftLeft;
 				}
-				break;
+				button.Y = Pos.AnchorEnd (1);
+			}
+			break;
 
-			case ButtonAlignments.Justify:
-				// Justify Buttons
-				// leftmost and rightmost buttons are hard against edges. The rest are evenly spaced.
+		case ButtonAlignments.Justify:
+			// Justify Buttons
+			// leftmost and rightmost buttons are hard against edges. The rest are evenly spaced.
 
-				var spacing = (int)Math.Ceiling ((double)(Bounds.Width - buttonsWidth) / (buttons.Count - 1));
-				for (int i = buttons.Count - 1; i >= 0; i--) {
-					Button button = buttons [i];
-					if (i == buttons.Count - 1) {
-						shiftLeft += button.Frame.Width;
-						button.X = Pos.AnchorEnd (shiftLeft);
+			int spacing = (int)Math.Ceiling ((double)(Bounds.Width - buttonsWidth) / (buttons.Count - 1));
+			for (int i = buttons.Count - 1; i >= 0; i--) {
+				var button = buttons [i];
+				if (i == buttons.Count - 1) {
+					shiftLeft += button.Frame.Width;
+					button.X = Pos.AnchorEnd (shiftLeft);
+				} else {
+					if (i == 0) {
+						// first (leftmost) button 
+						int left = Bounds.Width;
+						button.X = Pos.AnchorEnd (left);
 					} else {
-						if (i == 0) {
-							// first (leftmost) button 
-							var left = Bounds.Width;
-							button.X = Pos.AnchorEnd (left);
-						} else {
-							shiftLeft += button.Frame.Width + (spacing);
-							button.X = Pos.AnchorEnd (shiftLeft);
-						}
+						shiftLeft += button.Frame.Width + spacing;
+						button.X = Pos.AnchorEnd (shiftLeft);
 					}
-					button.Y = Pos.AnchorEnd (1);
-				}
-				break;
-
-			case ButtonAlignments.Left:
-				// Left Align Buttons
-				var prevButton = buttons [0];
-				prevButton.X = 0;
-				prevButton.Y = Pos.AnchorEnd (1);
-				for (int i = 1; i < buttons.Count; i++) {
-					Button button = buttons [i];
-					button.X = Pos.Right (prevButton) + 1;
-					button.Y = Pos.AnchorEnd (1);
-					prevButton = button;
-				}
-				break;
-
-			case ButtonAlignments.Right:
-				// Right align buttons
-				shiftLeft = buttons [buttons.Count - 1].Frame.Width;
-				buttons [buttons.Count - 1].X = Pos.AnchorEnd (shiftLeft);
-				buttons [buttons.Count - 1].Y = Pos.AnchorEnd (1);
-				for (int i = buttons.Count - 2; i >= 0; i--) {
-					Button button = buttons [i];
-					shiftLeft += button.Frame.Width + 1;
-					button.X = Pos.AnchorEnd (shiftLeft);
-					button.Y = Pos.AnchorEnd (1);
 				}
-				break;
+				button.Y = Pos.AnchorEnd (1);
 			}
+			break;
+
+		case ButtonAlignments.Left:
+			// Left Align Buttons
+			var prevButton = buttons [0];
+			prevButton.X = 0;
+			prevButton.Y = Pos.AnchorEnd (1);
+			for (int i = 1; i < buttons.Count; i++) {
+				var button = buttons [i];
+				button.X = Pos.Right (prevButton) + 1;
+				button.Y = Pos.AnchorEnd (1);
+				prevButton = button;
+			}
+			break;
+
+		case ButtonAlignments.Right:
+			// Right align buttons
+			shiftLeft = buttons [buttons.Count - 1].Frame.Width;
+			buttons [buttons.Count - 1].X = Pos.AnchorEnd (shiftLeft);
+			buttons [buttons.Count - 1].Y = Pos.AnchorEnd (1);
+			for (int i = buttons.Count - 2; i >= 0; i--) {
+				var button = buttons [i];
+				shiftLeft += button.Frame.Width + 1;
+				button.X = Pos.AnchorEnd (shiftLeft);
+				button.Y = Pos.AnchorEnd (1);
+			}
+			break;
 		}
+	}
 
-		// BUGBUG: Why is this not handled by a key binding???
-		///<inheritdoc/>
-		public override bool OnProcessKeyDown (Key a)
-		{
-			switch (a.KeyCode) {
-			case KeyCode.Esc:
-				Application.RequestStop (this);
-				return true;
-			}
-			return false;
+	// BUGBUG: Why is this not handled by a key binding???
+	///<inheritdoc/>
+	public override bool OnProcessKeyDown (Key a)
+	{
+		switch (a.KeyCode) {
+		case KeyCode.Esc:
+			Application.RequestStop (this);
+			return true;
 		}
+		return false;
 	}
-}
+}

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

@@ -290,7 +290,7 @@ namespace Terminal.Gui {
 				X = 0,
 				Y = 0,
 				Width = Dim.Fill (0),
-				Height = Dim.Fill (1)
+				Height = Dim.Fill (1),
 			};
 			messageLabel.TextFormatter.WordWrap = wrapMessage;
 			messageLabel.TextFormatter.MultiLine = wrapMessage ? false : true;
@@ -304,7 +304,7 @@ namespace Terminal.Gui {
 				var maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds;
 				if (wrapMessage) {
 					messageLabel.TextFormatter.Size = new Size (maxBounds.Size.Width - d.GetFramesThickness ().Horizontal, maxBounds.Size.Height - d.GetFramesThickness ().Vertical);
-				} 
+				}
 				var msg = messageLabel.TextFormatter.Format ();
 				var messageSize = messageLabel.TextFormatter.GetFormattedSize ();
 

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

@@ -178,7 +178,7 @@ public class RadioGroup : View {
 					AddKeyBindingsForHotKey (KeyCode.Null, hotKey);
 				}
 			}
-			if (prevCount != _radioLabels.Count) {
+			if (IsInitialized && prevCount != _radioLabels.Count) {
 				SetWidthHeight (_radioLabels);
 			}
 			SelectedItem = 0;

+ 13 - 0
UICatalog/Scenarios/ComputedLayout.cs

@@ -163,6 +163,19 @@ namespace UICatalog.Scenarios {
 			};
 			Application.Top.Add (oddballButton);
 
+			oddballButton = new Button ("Center - 1") {
+				X = Pos.Center () - 1,
+				Y = Pos.Bottom (oddballButton)
+			};
+			Application.Top.Add (oddballButton);
+
+			// Won't be visible:
+			//oddballButton = new Button ("1 - Center") {
+			//	X = 1 - Pos.Center (),
+			//	Y = Pos.Bottom (oddballButton)
+			//};
+			//Application.Top.Add (oddballButton);
+
 			// This demonstrates nonsense: it the same as using Pos.AnchorEnd (100/2=50 + 100/2=50 = 100 - 50)
 			// The `- Pos.Percent(5)` is there so at least something is visible
 			oddballButton = new Button ("Center + Center - Percent(50)") {

+ 1 - 1
UICatalog/Scenarios/Dialogs.cs

@@ -114,7 +114,7 @@ namespace UICatalog.Scenarios {
 			};
 			frame.Add (styleRadioGroup);
 
-			frame.ForceValidatePosDim = true;
+			frame.ValidatePosDim = true;
 			void Top_Loaded (object sender, EventArgs args)
 			{
 				frame.Height =

+ 1 - 1
UICatalog/Scenarios/MessageBoxes.cs

@@ -146,7 +146,7 @@ namespace UICatalog.Scenarios {
 			};
 			frame.Add (ckbWrapMessage);
 
-			frame.ForceValidatePosDim = true;
+			frame.ValidatePosDim = true;
 			void Top_Loaded (object sender, EventArgs args)
 			{
 				frame.Height =

+ 23 - 25
UnitTests/Application/ApplicationTests.cs

@@ -420,11 +420,13 @@ public class ApplicationTests {
 		Assert.Equal (3, count);
 	}
 
+	// TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
 	[Fact]
 	public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty ()
 	{
 		Init ();
 		var count = 0;
+		// Don't use Dialog here as it has more layout logic. Use Window instead.
 		Dialog d = null;
 		var top = Application.Top;
 		top.DrawContent += (s, a) => count++;
@@ -432,6 +434,7 @@ public class ApplicationTests {
 		Application.Iteration += (s, a) => {
 			iteration++;
 			if (iteration == 0) {
+				// TODO: Don't use Dialog here as it has more layout logic. Use Window instead.
 				d = new Dialog ();
 				d.DrawContent += (s, a) => count++;
 				Application.Run (d);
@@ -453,44 +456,42 @@ public class ApplicationTests {
 		Assert.Equal (3, count);
 	}
 
+	// TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
 	[Fact]
 	public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving ()
 	{
 		Init ();
-		var d = new Dialog () { Width = 5, Height = 5 };
+		// Don't use Dialog here as it has more layout logic. Use Window instead.
+		var w = new Window () { Width = 5, Height = 5 };
 		((FakeDriver)Application.Driver).SetBufferSize (10, 10);
-		var rs = Application.Begin (d);
+		var rs = Application.Begin (w);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
-  ┌───┐
-  │   │
-  │   │
-  │   │
-  └───┘", _output);
+┌───┐
+│   │
+│   │
+│   │
+└───┘", _output);
 
 		var attributes = new Attribute [] {
 			// 0
 			new Attribute (ColorName.White, ColorName.Black),
 			// 1
-			Colors.Dialog.Normal
+			Colors.Base.Normal
 		};
 		TestHelpers.AssertDriverColorsAre (@"
-0000000000
-0000000000
-0011111000
-0011111000
-0011111000
-0011111000
-0011111000
-0000000000
-0000000000
-0000000000
+1111100000
+1111100000
+1111100000
+1111100000
+1111100000
 ", null, attributes);
 
 		// TODO: In PR #2920 this breaks because the mouse is not grabbed anymore.
 		// TODO: Move the mouse grap/drag mode from Toplevel to Border.
-		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { X = 2, Y = 2, Flags = MouseFlags.Button1Pressed }));
-		Assert.Equal (d, Application.MouseGrabView);
+		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }));
+		Assert.Equal (w, Application.MouseGrabView);
 
+		// Move down and to the right.
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { X = 1, Y = 1, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }));
 		Application.Refresh ();
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
@@ -504,7 +505,7 @@ public class ApplicationTests {
 			// 0
 			new Attribute (ColorName.White, ColorName.Black),
 			// 1
-			Colors.Dialog.Normal
+			Colors.Base.Normal
 		};
 		TestHelpers.AssertDriverColorsAre (@"
 0000000000
@@ -513,10 +514,6 @@ public class ApplicationTests {
 0111110000
 0111110000
 0111110000
-0000000000
-0000000000
-0000000000
-0000000000
 ", null, attributes);
 
 		Application.End (rs);
@@ -578,6 +575,7 @@ public class ApplicationTests {
 		var t1 = new Toplevel ();
 		var t2 = new Toplevel ();
 		var t3 = new Toplevel ();
+		// Don't use Dialog here as it has more layout logic. Use Window instead.
 		var d = new Dialog ();
 		var t4 = new Toplevel ();
 

+ 25 - 43
UnitTests/Dialogs/DialogTests.cs

@@ -780,6 +780,8 @@ namespace Terminal.Gui.DialogTests {
 		[Fact, AutoInitShutdown]
 		public void Dialog_Opened_From_Another_Dialog ()
 		{
+			((FakeDriver)Application.Driver).SetBufferSize (30, 10);
+
 			var btn1 = new Button ("press me 1");
 			Button btn2 = null;
 			Button btn3 = null;
@@ -788,7 +790,13 @@ namespace Terminal.Gui.DialogTests {
 				btn2 = new Button ("Show Sub");
 				btn3 = new Button ("Close");
 				btn3.Clicked += (s, e) => Application.RequestStop ();
-				btn2.Clicked += (s, e) => { MessageBox.Query (string.Empty, "ya", "Ok"); };
+				btn2.Clicked += (s, e) => {
+					// Don't test MessageBox in Dialog unit tests!
+					var subBtn = new Button ("Ok") { IsDefault = true };
+					var subDlg = new Dialog (subBtn) { Text = "ya", Width = 20, Height = 5 };
+					subBtn.Clicked += (s, e) => Application.RequestStop (subDlg);
+					Application.Run (subDlg);
+				};
 				var dlg = new Dialog (btn2, btn3);
 
 				Application.Run (dlg);
@@ -802,53 +810,27 @@ namespace Terminal.Gui.DialogTests {
 					Assert.True (btn1.NewKeyDownEvent (new (KeyCode.Space)));
 				} else if (iterations == 1) {
 					expected = @$"
-      ┌──────────────────────────────────────────────────────────────────┐
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                      {CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket}                      │
-      └──────────────────────────────────────────────────────────────────┘";
+  ┌───────────────────────┐
+  │                       │
+  │                       │
+  │                       │
+  │                       │
+  │                       │
+  │{CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket} │
+  └───────────────────────┘";
 					TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
 
 					Assert.True (btn2.NewKeyDownEvent (new (KeyCode.Space)));
 				} else if (iterations == 2) {
 					TestHelpers.AssertDriverContentsWithFrameAre (@$"
-      ┌──────────────────────────────────────────────────────────────────┐
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │         ┌──────────────────────────────────────────────┐         │
-      │         │                      ya                      │         │
-      │         │                                              │         │
-      │         │                   {btn}                   │         │
-      │         └──────────────────────────────────────────────┘         │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                                                                  │
-      │                      {CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket}                      │
-      └──────────────────────────────────────────────────────────────────┘", output);
+  ┌───────────────────────┐
+  │  ┌──────────────────┐ │
+  │  │ya                │ │
+  │  │                  │ │
+  │  │     {btn}     │ │
+  │  └──────────────────┘ │
+  │{CM.Glyphs.LeftBracket} Show Sub {CM.Glyphs.RightBracket} {CM.Glyphs.LeftBracket} Close {CM.Glyphs.RightBracket} │
+  └───────────────────────┘", output);
 
 					Assert.True (Application.Current.NewKeyDownEvent (new (KeyCode.Enter)));
 				} else if (iterations == 3) {

+ 0 - 57
UnitTests/Text/UnicodeTests.cs

@@ -1,57 +0,0 @@
-using Microsoft.VisualStudio.TestPlatform.Utilities;
-using Xunit;
-using Xunit.Abstractions;
-
-// Alias Console to MockConsole so we don't accidentally use Console
-
-namespace Terminal.Gui.TextTests;
-public class UnicodeTests {
-	readonly ITestOutputHelper _output;
-
-	public UnicodeTests (ITestOutputHelper output)
-	{
-		this._output = output;
-	}
-
-	[Fact, AutoInitShutdown]
-	public void AddRune_On_Clip_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
-	{
-		var tv = new TextView () {
-			Width = Dim.Fill (),
-			Height = Dim.Fill (),
-			Text = @"これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。
-これは広いルーンラインです。"
-		};
-		var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
-		win.Add (tv);
-		Application.Top.Add (win);
-		var lbl = new Label ("ワイドルーン。");
-		var dg = new Dialog (new Button ("選ぶ")) { Width = 14, Height = 4 };
-		dg.Add (lbl);
-		Application.Begin (Application.Top);
-		Application.Begin (dg);
-		((FakeDriver)Application.Driver).SetBufferSize (30, 10);
-
-		var expected = @$"
-┌────────────────────────────┐
-│これは広いルーンラインです。│
-│これは広いルーンラインです。│
-│これは�┌────────────┐�です。│
-│これは�│ワイドルーン│�です。│
-│これは�│  {CM.Glyphs.LeftBracket} 選ぶ {CM.Glyphs.RightBracket}  │�です。│
-│これは�└────────────┘�です。│
-│これは広いルーンラインです。│
-│これは広いルーンラインです。│
-└────────────────────────────┘
-";
-
-		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
-		Assert.Equal (new Rect (0, 0, 30, 10), pos);
-	}
-}

+ 2 - 2
UnitTests/UnitTests.csproj

@@ -25,8 +25,8 @@
     <PackageReference Include="ReportGenerator" Version="5.2.0" />
     <PackageReference Include="System.Collections" Version="4.3.0" />
     <PackageReference Include="TestableIO.System.IO.Abstractions.TestingHelpers" Version="20.0.4" />
-    <PackageReference Include="xunit" Version="2.6.3" />
-    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
+    <PackageReference Include="xunit" Version="2.6.4" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>

+ 282 - 246
UnitTests/View/DrawTests.cs

@@ -2,338 +2,374 @@
 using System;
 using Xunit;
 using Xunit.Abstractions;
+using Microsoft.VisualStudio.TestPlatform.Utilities;
+
+namespace Terminal.Gui.ViewsTests; 
+
+public class DrawTests {
+	readonly ITestOutputHelper _output;
+
+	public DrawTests (ITestOutputHelper output) => _output = output;
+
+	[Fact] [AutoInitShutdown]
+	public void Clipping_AddRune_Left_Or_Right_Replace_Previous_Or_Next_Wide_Rune_With_Space ()
+	{
+		var tv = new TextView () {
+			Width = Dim.Fill (),
+			Height = Dim.Fill (),
+			Text = @"これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。
+これは広いルーンラインです。"
+		};
+		var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () };
+		win.Add (tv);
+		Application.Top.Add (win);
+		var lbl = new Label ("ワイドルーン。");
+		// 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);
+		Application.Begin (Application.Top);
+		Application.Begin (dg);
+		((FakeDriver)Application.Driver).SetBufferSize (30, 10);
+
+		string expected = @$"
+┌────────────────────────────┐
+│これは広いルーンラインです。│
+│�┌────────────┐�ラインです。│
+│�│ワイドルーン│�ラインです。│
+│�└────────────┘�ラインです。│
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+│これは広いルーンラインです。│
+└────────────────────────────┘";
+
+		var pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Assert.Equal (new Rect (0, 0, 30, 10), pos);
+	}
 
-namespace Terminal.Gui.ViewsTests {
-	public class DrawTests {
-		readonly ITestOutputHelper output;
-
-		public DrawTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-
-		// TODO: The tests below that use Label should use View instead.
-		[Fact, AutoInitShutdown]
-		public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two ()
-		{
-			string us = "\U0001d539";
-			Rune r = (Rune)0x1d539;
+	// TODO: The tests below that use Label should use View instead.
+	[Fact] [AutoInitShutdown]
+	public void Non_Bmp_ConsoleWidth_ColumnWidth_Equal_Two ()
+	{
+		string us = "\U0001d539";
+		var r = (Rune)0x1d539;
 
-			Assert.Equal ("𝔹", us);
-			Assert.Equal ("𝔹", r.ToString ());
-			Assert.Equal (us, r.ToString ());
+		Assert.Equal ("𝔹", us);
+		Assert.Equal ("𝔹", r.ToString ());
+		Assert.Equal (us, r.ToString ());
 
-			Assert.Equal (1, us.GetColumns ());
-			Assert.Equal (1, r.GetColumns ());
+		Assert.Equal (1, us.GetColumns ());
+		Assert.Equal (1, r.GetColumns ());
 
-			var win = new Window () { Title = us };
-			var label = new Label (r.ToString ());
-			var tf = new TextField (us) { Y = 1, Width = 3 };
-			win.Add (label, tf);
-			var top = Application.Top;
-			top.Add (win);
+		var win = new Window () { Title = us };
+		var label = new Label (r.ToString ());
+		var tf = new TextField (us) { Y = 1, Width = 3 };
+		win.Add (label, tf);
+		var top = Application.Top;
+		top.Add (win);
 
-			Application.Begin (top);
-			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+		Application.Begin (top);
+		((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-			var expected = @"
+		string expected = @"
 ┌┤𝔹├─────┐
 │𝔹       │
 │𝔹       │
 └────────┘";
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
 
-			TestHelpers.AssertDriverContentsAre (expected, output);
+		TestHelpers.AssertDriverContentsAre (expected, _output);
 
-			var expectedColors = new Attribute [] {
-				// 0
-				Colors.Base.Normal,
-				// 1
-				Colors.Base.Focus,
-				// 2
-				Colors.Base.HotNormal
-			};
+		var expectedColors = new Attribute [] {
+			// 0
+			Colors.Base.Normal,
+			// 1
+			Colors.Base.Focus,
+			// 2
+			Colors.Base.HotNormal
+		};
 
-			TestHelpers.AssertDriverColorsAre (@"
+		TestHelpers.AssertDriverColorsAre (@"
 0020000000
 0000000000
 0111000000
-0000000000", driver: Application.Driver, expectedColors);
-		}
+0000000000", Application.Driver, expectedColors);
+	}
 
-		[Fact, AutoInitShutdown]
-		public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
-		{
-			string us = "\U0000f900";
-			Rune r = (Rune)0xf900;
+	[Fact] [AutoInitShutdown]
+	public void CJK_Compatibility_Ideographs_ConsoleWidth_ColumnWidth_Equal_Two ()
+	{
+		string us = "\U0000f900";
+		var r = (Rune)0xf900;
 
-			Assert.Equal ("豈", us);
-			Assert.Equal ("豈", r.ToString ());
-			Assert.Equal (us, r.ToString ());
+		Assert.Equal ("豈", us);
+		Assert.Equal ("豈", r.ToString ());
+		Assert.Equal (us, r.ToString ());
 
-			Assert.Equal (2, us.GetColumns ());
-			Assert.Equal (2, r.GetColumns ());
+		Assert.Equal (2, us.GetColumns ());
+		Assert.Equal (2, r.GetColumns ());
 
-			var win = new Window () { Title = us };
-			var label = new Label (r.ToString ());
-			var tf = new TextField (us) { Y = 1, Width = 3 };
-			win.Add (label, tf);
-			var top = Application.Top;
-			top.Add (win);
+		var win = new Window () { Title = us };
+		var label = new Label (r.ToString ());
+		var tf = new TextField (us) { Y = 1, Width = 3 };
+		win.Add (label, tf);
+		var top = Application.Top;
+		top.Add (win);
 
-			Application.Begin (top);
-			((FakeDriver)Application.Driver).SetBufferSize (10, 4);
+		Application.Begin (top);
+		((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-			var expected = @"
+		string expected = @"
 ┌┤豈├────┐
 │豈      │
 │豈      │
 └────────┘";
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
 
-			TestHelpers.AssertDriverContentsAre (expected, output);
+		TestHelpers.AssertDriverContentsAre (expected, _output);
 
-			var expectedColors = new Attribute [] {
-				// 0
-				Colors.Base.Normal,
-				// 1
-				Colors.Base.Focus,
-				// 2
-				Colors.Base.HotNormal
-			};
+		var expectedColors = new Attribute [] {
+			// 0
+			Colors.Base.Normal,
+			// 1
+			Colors.Base.Focus,
+			// 2
+			Colors.Base.HotNormal
+		};
 
-			TestHelpers.AssertDriverColorsAre (@"
+		TestHelpers.AssertDriverColorsAre (@"
 0022000000
 0000000000
 0111000000
-0000000000", driver: Application.Driver, expectedColors);
-		}
-
-		[Fact, AutoInitShutdown]
-		public void Colors_On_TextAlignment_Right_And_Bottom ()
-		{
-			var labelRight = new Label ("Test") {
-				Width = 6,
-				Height = 1,
-				TextAlignment = TextAlignment.Right,
-				ColorScheme = Colors.Base
-			};
-			var labelBottom = new Label ("Test", TextDirection.TopBottom_LeftRight) {
-				Y = 1,
-				Width = 1,
-				Height = 6,
-				VerticalTextAlignment = VerticalTextAlignment.Bottom,
-				ColorScheme = Colors.Base
-			};
-			var top = Application.Top;
-			top.Add (labelRight, labelBottom);
-
-			Application.Begin (top);
-			((FakeDriver)Application.Driver).SetBufferSize (7, 7);
-
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+0000000000", Application.Driver, expectedColors);
+	}
+
+	[Fact] [AutoInitShutdown]
+	public void Colors_On_TextAlignment_Right_And_Bottom ()
+	{
+		var labelRight = new Label ("Test") {
+			Width = 6,
+			Height = 1,
+			TextAlignment = TextAlignment.Right,
+			ColorScheme = Colors.Base
+		};
+		var labelBottom = new Label ("Test", TextDirection.TopBottom_LeftRight) {
+			Y = 1,
+			Width = 1,
+			Height = 6,
+			VerticalTextAlignment = VerticalTextAlignment.Bottom,
+			ColorScheme = Colors.Base
+		};
+		var top = Application.Top;
+		top.Add (labelRight, labelBottom);
+
+		Application.Begin (top);
+		((FakeDriver)Application.Driver).SetBufferSize (7, 7);
+
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
   Test
       
       
 T     
 e     
 s     
-t     ", output);
+t     ", _output);
 
-			TestHelpers.AssertDriverColorsAre (@"
+		TestHelpers.AssertDriverColorsAre (@"
 000000
 0
 0
 0
 0
 0
-0", driver: Application.Driver, new Attribute [] { Colors.Base.Normal });
-		}
-
-		[Fact, AutoInitShutdown]
-		public void Draw_Negative_Bounds_Horizontal_Without_New_Lines ()
-		{
-			// BUGBUG: This previously assumed the default height of a View was 1. 
-			var subView = new View () { Id = "subView", Y = 1, Width = 7, Height = 1, Text = "subView" };
-			var view = new View () { Id = "view", Width = 20, Height = 2, Text = "01234567890123456789" };
-			view.Add (subView);
-			var content = new View () { Id = "content", Width = 20, Height = 20 };
-			content.Add (view);
-			var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 };
-			container.Add (content);
-			var top = Application.Top;
-			top.Add (container);
-			// BUGBUG: v2 - it's bogus to reference .Frame before BeginInit. And why is the clip being set anyway???
-
-			void Top_LayoutComplete (object sender, LayoutEventArgs e)
-			{
-				Application.Driver.Clip = container.Frame;
-			}
-			top.LayoutComplete += Top_LayoutComplete;
-			Application.Begin (top);
-
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+0", Application.Driver, new Attribute [] { Colors.Base.Normal });
+	}
+
+	[Fact] [AutoInitShutdown]
+	public void Draw_Negative_Bounds_Horizontal_Without_New_Lines ()
+	{
+		// BUGBUG: This previously assumed the default height of a View was 1. 
+		var subView = new View () { Id = "subView", Y = 1, Width = 7, Height = 1, Text = "subView" };
+		var view = new View () { Id = "view", Width = 20, Height = 2, Text = "01234567890123456789" };
+		view.Add (subView);
+		var content = new View () { Id = "content", Width = 20, Height = 20 };
+		content.Add (view);
+		var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 };
+		container.Add (content);
+		var top = Application.Top;
+		top.Add (container);
+		// BUGBUG: v2 - it's bogus to reference .Frame before BeginInit. And why is the clip being set anyway???
+
+		void Top_LayoutComplete (object sender, LayoutEventArgs e) => Application.Driver.Clip = container.Frame;
+		top.LayoutComplete += Top_LayoutComplete;
+		Application.Begin (top);
+
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  01234
- subVi", output);
+ subVi", _output);
 
-			content.X = -1;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.X = -1;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  12345
- ubVie", output);
-
-			content.Y = -1;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
- ubVie", output);
-
-			content.Y = -2;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-
-			content.X = -20;
-			content.Y = 0;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-		}
-
-		[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" };
-			var view = new View () { Id = "view", Width = 2, Height = 20, Text = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" };
-			view.Add (subView);
-			var content = new View () { Id = "content", Width = 20, Height = 20 };
-			content.Add (view);
-			var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 };
-			container.Add (content);
-			var top = Application.Top;
-			top.Add (container);
-			Application.Driver.Clip = container.Frame;
-			Application.Begin (top);
-
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ ubVie", _output);
+
+		content.Y = -1;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
+ ubVie", _output);
+
+		content.Y = -2;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+
+		content.X = -20;
+		content.Y = 0;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+	}
+
+	[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" };
+		var view = new View () { Id = "view", Width = 2, Height = 20, Text = "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n0\n1\n2\n3\n4\n5\n6\n7\n8\n9" };
+		view.Add (subView);
+		var content = new View () { Id = "content", Width = 20, Height = 20 };
+		content.Add (view);
+		var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 };
+		container.Add (content);
+		var top = Application.Top;
+		top.Add (container);
+		Application.Driver.Clip = container.Frame;
+		Application.Begin (top);
+
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  0s
  1u
  2b
  3V
- 4i", output);
+ 4i", _output);
 
-			content.X = -1;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.X = -1;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  s
  u
  b
  V
- i", output);
+ i", _output);
 
-			content.X = -2;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
+		content.X = -2;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
 
-			content.X = 0;
-			content.Y = -1;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.X = 0;
+		content.Y = -1;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  1u
  2b
  3V
  4i
- 5e", output);
+ 5e", _output);
 
-			content.Y = -6;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.Y = -6;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  6w
  7 
  8 
  9 
- 0 ", output);
-
-			content.Y = -19;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
- 9", output);
-
-			content.Y = -20;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-
-			content.X = -2;
-			content.Y = 0;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-		}
-
-		[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 };
-			var view = new View () { Id = "view", Width = 2, Height = 20, Text = "01234567890123456789", TextDirection = TextDirection.TopBottom_LeftRight };
-			view.Add (subView);
-			var content = new View () { Id = "content", Width = 20, Height = 20 };
-			content.Add (view);
-			var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 };
-			container.Add (content);
-			var top = Application.Top;
-			top.Add (container);
-			Application.Driver.Clip = container.Frame;
-			Application.Begin (top);
-
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+ 0 ", _output);
+
+		content.Y = -19;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
+ 9", _output);
+
+		content.Y = -20;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+
+		content.X = -2;
+		content.Y = 0;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+	}
+
+	[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 };
+		var view = new View () { Id = "view", Width = 2, Height = 20, Text = "01234567890123456789", TextDirection = TextDirection.TopBottom_LeftRight };
+		view.Add (subView);
+		var content = new View () { Id = "content", Width = 20, Height = 20 };
+		content.Add (view);
+		var container = new View () { Id = "container", X = 1, Y = 1, Width = 5, Height = 5 };
+		container.Add (content);
+		var top = Application.Top;
+		top.Add (container);
+		Application.Driver.Clip = container.Frame;
+		Application.Begin (top);
+
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  0s
  1u
  2b
  3V
- 4i", output);
+ 4i", _output);
 
-			content.X = -1;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.X = -1;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  s
  u
  b
  V
- i", output);
+ i", _output);
 
-			content.X = -2;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
+		content.X = -2;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"", _output);
 
-			content.X = 0;
-			content.Y = -1;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.X = 0;
+		content.Y = -1;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  1u
  2b
  3V
  4i
- 5e", output);
+ 5e", _output);
 
-			content.Y = -6;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
+		content.Y = -6;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
  6w
  7 
  8 
  9 
- 0 ", output);
-
-			content.Y = -19;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre (@"
- 9", output);
-
-			content.Y = -20;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-
-			content.X = -2;
-			content.Y = 0;
-			Application.Refresh ();
-			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-		}
-	}
-}
+ 0 ", _output);
+
+		content.Y = -19;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre (@"
+ 9", _output);
 
+		content.Y = -20;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+
+		content.X = -2;
+		content.Y = 0;
+		Application.Refresh ();
+		TestHelpers.AssertDriverContentsWithFrameAre ("", _output);
+	}
+}

File diff suppressed because it is too large
+ 608 - 602
UnitTests/View/Layout/DimTests.cs


File diff suppressed because it is too large
+ 519 - 521
UnitTests/View/Layout/LayoutTests.cs


+ 940 - 942
UnitTests/View/Layout/PosTests.cs

@@ -1,144 +1,136 @@
 using System;
 using System.Collections.Generic;
-using System.ComponentModel;
-using System.Data;
-using System.IO;
-using System.Linq;
 using Xunit;
 using Xunit.Abstractions;
 
 // Alias Console to MockConsole so we don't accidentally use Console
-using Console = Terminal.Gui.FakeConsole;
 
-namespace Terminal.Gui.ViewTests {
-	public class LayoutTests_PosTests {
-		readonly ITestOutputHelper output;
+namespace Terminal.Gui.ViewTests;
 
-		public LayoutTests_PosTests (ITestOutputHelper output)
-		{
-			this.output = output;
-		}
-
-		[Fact]
-		public void New_Works ()
-		{
-			var pos = new Pos ();
-			Assert.Equal ("Terminal.Gui.Pos", pos.ToString ());
-		}
-
-		[Fact]
-		public void AnchorEnd_SetsValue ()
-		{
-			var n = 0;
-			var pos = Pos.AnchorEnd ();
-			Assert.Equal ($"AnchorEnd({n})", pos.ToString ());
+public class PosTests {
+	readonly ITestOutputHelper _output;
 
-			n = 5;
-			pos = Pos.AnchorEnd (n);
-			Assert.Equal ($"AnchorEnd({n})", pos.ToString ());
-		}
-
-		[Fact]
-		public void AnchorEnd_Equal ()
-		{
-			var n1 = 0;
-			var n2 = 0;
-
-			var pos1 = Pos.AnchorEnd (n1);
-			var pos2 = Pos.AnchorEnd (n2);
-			Assert.Equal (pos1, pos2);
-
-			// Test inequality
-			n2 = 5;
-			pos2 = Pos.AnchorEnd (n2);
-			Assert.NotEqual (pos1, pos2);
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void AnchorEnd_Equal_Inside_Window ()
-		{
-			var viewWidth = 10;
-			var viewHeight = 1;
-			var tv = new TextView () {
-				X = Pos.AnchorEnd (viewWidth),
-				Y = Pos.AnchorEnd (viewHeight),
-				Width = viewWidth,
-				Height = viewHeight
-			};
-
-			var win = new Window ();
-
-			win.Add (tv);
-
-			var top = Application.Top;
-			top.Add (win);
-			var rs = Application.Begin (top);
-
-			Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
-			Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
-			Assert.Equal (new Rect (68, 22, 10, 1), tv.Frame);
-			Application.End (rs);
-		}
-
-		[Fact]
-		[AutoInitShutdown]
-		public void AnchorEnd_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
-		{
-			var viewWidth = 10;
-			var viewHeight = 1;
-			var tv = new TextView () {
-				X = Pos.AnchorEnd (viewWidth),
-				Y = Pos.AnchorEnd (viewHeight),
-				Width = viewWidth,
-				Height = viewHeight
-			};
+	public PosTests (ITestOutputHelper output) => _output = output;
 
-			var win = new Window ();
-
-			win.Add (tv);
+	[Fact]
+	public void New_Works ()
+	{
+		var pos = new Pos ();
+		Assert.Equal ("Terminal.Gui.Pos", pos.ToString ());
+	}
 
-			var menu = new MenuBar ();
-			var status = new StatusBar ();
-			var top = Application.Top;
-			top.Add (win, menu, status);
-			var rs = Application.Begin (top);
+	[Fact]
+	public void AnchorEnd_SetsValue ()
+	{
+		int n = 0;
+		var pos = Pos.AnchorEnd ();
+		Assert.Equal ($"AnchorEnd({n})", pos.ToString ());
 
-			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 (68, 20, 10, 1), tv.Frame);
+		n = 5;
+		pos = Pos.AnchorEnd (n);
+		Assert.Equal ($"AnchorEnd({n})", pos.ToString ());
+	}
 
-			Application.End (rs);
-		}
+	[Fact]
+	public void AnchorEnd_Equal ()
+	{
+		int n1 = 0;
+		int n2 = 0;
 
-		[Fact]
-		[AutoInitShutdown]
-		public void Bottom_Equal_Inside_Window ()
-		{
-			var win = new Window ();
+		var pos1 = Pos.AnchorEnd (n1);
+		var pos2 = Pos.AnchorEnd (n2);
+		Assert.Equal (pos1, pos2);
 
-			var label = new Label ("This should be the last line.") {
-				TextAlignment = TextAlignment.Centered,
-				ColorScheme = Colors.Menu,
-				Width = Dim.Fill (),
-				X = Pos.Center (),
-				Y = Pos.Bottom (win) - 3  // two lines top and bottom borders more one line above the bottom border
-			};
+		// Test inequality
+		n2 = 5;
+		pos2 = Pos.AnchorEnd (n2);
+		Assert.NotEqual (pos1, pos2);
+	}
 
-			win.Add (label);
+	[Fact]
+	[AutoInitShutdown]
+	public void AnchorEnd_Equal_Inside_Window ()
+	{
+		int viewWidth = 10;
+		int viewHeight = 1;
+		var tv = new TextView () {
+			X = Pos.AnchorEnd (viewWidth),
+			Y = Pos.AnchorEnd (viewHeight),
+			Width = viewWidth,
+			Height = viewHeight
+		};
+
+		var win = new Window ();
+
+		win.Add (tv);
+
+		var top = Application.Top;
+		top.Add (win);
+		var rs = Application.Begin (top);
+
+		Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
+		Assert.Equal (new Rect (0, 0, 80, 25), win.Frame);
+		Assert.Equal (new Rect (68, 22, 10, 1), tv.Frame);
+		Application.End (rs);
+	}
 
-			var top = Application.Top;
-			top.Add (win);
-			var rs = Application.Begin (top);
-			((FakeDriver)Application.Driver).SetBufferSize (40, 10);
+	[Fact]
+	[AutoInitShutdown]
+	public void AnchorEnd_Equal_Inside_Window_With_MenuBar_And_StatusBar_On_Toplevel ()
+	{
+		int viewWidth = 10;
+		int viewHeight = 1;
+		var tv = new TextView () {
+			X = Pos.AnchorEnd (viewWidth),
+			Y = Pos.AnchorEnd (viewHeight),
+			Width = viewWidth,
+			Height = viewHeight
+		};
+
+		var win = new Window ();
+
+		win.Add (tv);
+
+		var menu = new MenuBar ();
+		var status = new StatusBar ();
+		var top = Application.Top;
+		top.Add (win, menu, status);
+		var rs = Application.Begin (top);
+
+		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 (68, 20, 10, 1), tv.Frame);
+
+		Application.End (rs);
+	}
 
-			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 = @"
+	[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 = @"
 ┌──────────────────────────────────────┐
 │                                      │
 │                                      │
@@ -147,41 +139,40 @@ namespace Terminal.Gui.ViewTests {
 │                                      │
 │                                      │
 │                                      │
-│    This should be the last line.     │
+│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.") {
-				TextAlignment = TextAlignment.Centered,
-				ColorScheme = Colors.Menu,
-				Width = Dim.Fill (),
-				X = Pos.Center (),
-				Y = Pos.AnchorEnd (1)
-			};
-
-			win.Add (label);
-
-			var top = Application.Top;
-			top.Add (win);
-			var rs = Application.Begin (top);
-			((FakeDriver)Application.Driver).SetBufferSize (40, 10);
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
 
-			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 = @"
+	[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 = @"
 ┌──────────────────────────────────────┐
 │                                      │
 │                                      │
@@ -190,43 +181,42 @@ namespace Terminal.Gui.ViewTests {
 │                                      │
 │                                      │
 │                                      │
-│    This should be the last line.     │
+│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.") {
-				TextAlignment = TextAlignment.Centered,
-				ColorScheme = Colors.Menu,
-				Width = Dim.Fill (),
-				X = Pos.Center (),
-				Y = Pos.Bottom (win) - 4  // two lines top and bottom borders more two lines above border
-			};
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
 
-			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 = @"
+	[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                                                                           
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │                                                                              │
@@ -249,44 +239,43 @@ namespace Terminal.Gui.ViewTests {
 │                                                                              │
 │                                                                              │
 │                                                                              │
-│                        This should be the last line.                         │
+│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.") {
-				TextAlignment = TextAlignment.Centered,
-				ColorScheme = Colors.Menu,
-				Width = Dim.Fill (),
-				X = Pos.Center (),
-				Y = Pos.AnchorEnd (1)
-			};
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
 
-			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 = @"
+	[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                                                                           
 ┌──────────────────────────────────────────────────────────────────────────────┐
 │                                                                              │
@@ -309,816 +298,825 @@ namespace Terminal.Gui.ViewTests {
 │                                                                              │
 │                                                                              │
 │                                                                              │
-│                        This should be the last line.                         │
+│This should be the last line.                        
 └──────────────────────────────────────────────────────────────────────────────┘
  F1 Help                                                                        
 ";
 
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
-			Application.End (rs);
-		}
-
-		[Fact]
-		public void AnchorEnd_Negative_Throws ()
-		{
-			Pos pos;
-			var n = -1;
-			Assert.Throws<ArgumentException> (() => pos = Pos.AnchorEnd (n));
-		}
+		TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+		Application.End (rs);
+	}
 
-		[Fact]
-		public void At_SetsValue ()
-		{
-			var pos = Pos.At (0);
-			Assert.Equal ("Absolute(0)", pos.ToString ());
+	[Fact]
+	public void AnchorEnd_Negative_Throws ()
+	{
+		Pos pos;
+		int n = -1;
+		Assert.Throws<ArgumentException> (() => pos = Pos.AnchorEnd (n));
+	}
 
-			pos = Pos.At (5);
-			Assert.Equal ("Absolute(5)", pos.ToString ());
+	[Fact]
+	public void At_SetsValue ()
+	{
+		var pos = Pos.At (0);
+		Assert.Equal ("Absolute(0)", pos.ToString ());
 
-			pos = Pos.At (-1);
-			Assert.Equal ("Absolute(-1)", pos.ToString ());
-		}
+		pos = Pos.At (5);
+		Assert.Equal ("Absolute(5)", pos.ToString ());
 
-		[Fact]
-		public void At_Equal ()
-		{
-			var n1 = 0;
-			var n2 = 0;
+		pos = Pos.At (-1);
+		Assert.Equal ("Absolute(-1)", pos.ToString ());
+	}
 
-			var pos1 = Pos.At (n1);
-			var pos2 = Pos.At (n2);
-			Assert.Equal (pos1, pos2);
-		}
+	[Fact]
+	public void At_Equal ()
+	{
+		int n1 = 0;
+		int n2 = 0;
 
-		[Fact]
-		public void SetSide_Null_Throws ()
-		{
-			var pos = Pos.Left (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+		var pos1 = Pos.At (n1);
+		var pos2 = Pos.At (n2);
+		Assert.Equal (pos1, pos2);
+	}
 
-			pos = Pos.X (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+	[Fact]
+	public void SetSide_Null_Throws ()
+	{
+		var pos = Pos.Left (null);
+		Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
-			pos = Pos.Top (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+		pos = Pos.X (null);
+		Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
-			pos = Pos.Y (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+		pos = Pos.Top (null);
+		Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
-			pos = Pos.Bottom (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
+		pos = Pos.Y (null);
+		Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
-			pos = Pos.Right (null);
-			Assert.Throws<NullReferenceException> (() => pos.ToString ());
-		}
+		pos = Pos.Bottom (null);
+		Assert.Throws<NullReferenceException> (() => pos.ToString ());
 
-		// TODO: Test Left, Top, Right bottom Equal
+		pos = Pos.Right (null);
+		Assert.Throws<NullReferenceException> (() => pos.ToString ());
+	}
 
-		/// <summary>
-		/// Tests Pos.Left, Pos.X, Pos.Top, Pos.Y, Pos.Right, and Pos.Bottom set operations
-		/// </summary>
-		[Fact, TestRespondersDisposed]
-		public void PosSide_SetsValue ()
-		{
-			string side; // used in format string
-			var testRect = Rect.Empty;
-			var testInt = 0;
-			Pos pos;
-
-			// Pos.Left
-			side = "x";
-			testInt = 0;
-			testRect = Rect.Empty;
-			pos = Pos.Left (new View ());
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			pos = Pos.Left (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			testRect = new Rect (1, 2, 3, 4);
-			pos = Pos.Left (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			// Pos.Left(win) + 0
-			pos = Pos.Left (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = 1;
-			// Pos.Left(win) +1
-			pos = Pos.Left (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = -1;
-			// Pos.Left(win) -1
-			pos = Pos.Left (new View (testRect)) - testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			// Pos.X
-			side = "x";
-			testInt = 0;
-			testRect = Rect.Empty;
-			pos = Pos.X (new View ());
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			pos = Pos.X (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			testRect = new Rect (1, 2, 3, 4);
-			pos = Pos.X (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			// Pos.X(win) + 0
-			pos = Pos.X (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = 1;
-			// Pos.X(win) +1
-			pos = Pos.X (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = -1;
-			// Pos.X(win) -1
-			pos = Pos.X (new View (testRect)) - testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			// Pos.Top
-			side = "y";
-			testInt = 0;
-			testRect = Rect.Empty;
-			pos = Pos.Top (new View ());
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			pos = Pos.Top (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			testRect = new Rect (1, 2, 3, 4);
-			pos = Pos.Top (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			// Pos.Top(win) + 0
-			pos = Pos.Top (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = 1;
-			// Pos.Top(win) +1
-			pos = Pos.Top (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = -1;
-			// Pos.Top(win) -1
-			pos = Pos.Top (new View (testRect)) - testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			// Pos.Y
-			side = "y";
-			testInt = 0;
-			testRect = Rect.Empty;
-			pos = Pos.Y (new View ());
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			pos = Pos.Y (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			testRect = new Rect (1, 2, 3, 4);
-			pos = Pos.Y (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			// Pos.Y(win) + 0
-			pos = Pos.Y (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = 1;
-			// Pos.Y(win) +1
-			pos = Pos.Y (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = -1;
-			// Pos.Y(win) -1
-			pos = Pos.Y (new View (testRect)) - testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			// Pos.Bottom
-			side = "bottom";
-			testRect = Rect.Empty;
-			testInt = 0;
-			pos = Pos.Bottom (new View ());
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			pos = Pos.Bottom (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			testRect = new Rect (1, 2, 3, 4);
-			pos = Pos.Bottom (new View (testRect));
-			Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
-
-			// Pos.Bottom(win) + 0
-			pos = Pos.Bottom (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = 1;
-			// Pos.Bottom(win) +1
-			pos = Pos.Bottom (new View (testRect)) + testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
-
-			testInt = -1;
-			// Pos.Bottom(win) -1
-			pos = Pos.Bottom (new View (testRect)) - testInt;
-			Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+	// TODO: Test Left, Top, Right bottom Equal
+
+	/// <summary>
+	/// Tests Pos.Left, Pos.X, Pos.Top, Pos.Y, Pos.Right, and Pos.Bottom set operations
+	/// </summary>
+	[Fact]
+	[TestRespondersDisposed]
+	public void PosSide_SetsValue ()
+	{
+		string side; // used in format string
+		var testRect = Rect.Empty;
+		int testInt = 0;
+		Pos pos;
+
+		// Pos.Left
+		side = "x";
+		testInt = 0;
+		testRect = Rect.Empty;
+		pos = Pos.Left (new View ());
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		pos = Pos.Left (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		testRect = new Rect (1, 2, 3, 4);
+		pos = Pos.Left (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		// Pos.Left(win) + 0
+		pos = Pos.Left (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = 1;
+		// Pos.Left(win) +1
+		pos = Pos.Left (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = -1;
+		// Pos.Left(win) -1
+		pos = Pos.Left (new View (testRect)) - testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		// Pos.X
+		side = "x";
+		testInt = 0;
+		testRect = Rect.Empty;
+		pos = Pos.X (new View ());
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		pos = Pos.X (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		testRect = new Rect (1, 2, 3, 4);
+		pos = Pos.X (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		// Pos.X(win) + 0
+		pos = Pos.X (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = 1;
+		// Pos.X(win) +1
+		pos = Pos.X (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = -1;
+		// Pos.X(win) -1
+		pos = Pos.X (new View (testRect)) - testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		// Pos.Top
+		side = "y";
+		testInt = 0;
+		testRect = Rect.Empty;
+		pos = Pos.Top (new View ());
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		pos = Pos.Top (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		testRect = new Rect (1, 2, 3, 4);
+		pos = Pos.Top (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		// Pos.Top(win) + 0
+		pos = Pos.Top (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = 1;
+		// Pos.Top(win) +1
+		pos = Pos.Top (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = -1;
+		// Pos.Top(win) -1
+		pos = Pos.Top (new View (testRect)) - testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		// Pos.Y
+		side = "y";
+		testInt = 0;
+		testRect = Rect.Empty;
+		pos = Pos.Y (new View ());
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		pos = Pos.Y (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		testRect = new Rect (1, 2, 3, 4);
+		pos = Pos.Y (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		// Pos.Y(win) + 0
+		pos = Pos.Y (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = 1;
+		// Pos.Y(win) +1
+		pos = Pos.Y (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = -1;
+		// Pos.Y(win) -1
+		pos = Pos.Y (new View (testRect)) - testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		// Pos.Bottom
+		side = "bottom";
+		testRect = Rect.Empty;
+		testInt = 0;
+		pos = Pos.Bottom (new View ());
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		pos = Pos.Bottom (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		testRect = new Rect (1, 2, 3, 4);
+		pos = Pos.Bottom (new View (testRect));
+		Assert.Equal ($"View(side={side},target=View(){testRect})", pos.ToString ());
+
+		// Pos.Bottom(win) + 0
+		pos = Pos.Bottom (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = 1;
+		// Pos.Bottom(win) +1
+		pos = Pos.Bottom (new View (testRect)) + testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
+
+		testInt = -1;
+		// Pos.Bottom(win) -1
+		pos = Pos.Bottom (new View (testRect)) - testInt;
+		Assert.Equal ($"Combine(View(side={side},target=View(){testRect}){(testInt < 0 ? '-' : '+')}Absolute({testInt}))", pos.ToString ());
 
 #if DEBUG_IDISPOSABLE
-			// HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.
-			Responder.Instances.Clear ();
+		// HACK: Force clean up of Responders to avoid having to Dispose all the Views created above.
+		Responder.Instances.Clear ();
 #endif
-		}
-
-		// See: https://github.com/gui-cs/Terminal.Gui/issues/504
-		[Fact, TestRespondersDisposed]
-		public void LeftTopBottomRight_Win_ShouldNotThrow ()
-		{
-			// Setup Fake driver
-			(Window win, Button button) setup ()
-			{
-				Application.Init (new FakeDriver ());
-				Application.Iteration += (s, a) => {
-					Application.RequestStop ();
-				};
-				var win = new Window () {
-					X = 0,
-					Y = 0,
-					Width = Dim.Fill (),
-					Height = Dim.Fill (),
-				};
-				Application.Top.Add (win);
-
-				var button = new Button ("button") {
-					X = Pos.Center (),
-				};
-				win.Add (button);
-
-				return (win, button);
-			}
-
-			RunState rs;
-
-			void cleanup (RunState rs)
-			{
-				// Cleanup
-				Application.End (rs);
-				// Shutdown must be called to safely clean up Application if Init has been called
-				Application.Shutdown ();
-			}
-
-			// Test cases:
-			var app = setup ();
-			app.button.Y = Pos.Left (app.win);
-			rs = Application.Begin (Application.Top);
-			// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
-			Application.RunLoop (rs);
-			cleanup (rs);
-
-			app = setup ();
-			app.button.Y = Pos.X (app.win);
-			rs = Application.Begin (Application.Top);
-			// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
-			Application.RunLoop (rs);
-			cleanup (rs);
-
-			app = setup ();
-			app.button.Y = Pos.Top (app.win);
-			rs = Application.Begin (Application.Top);
-			// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
-			Application.RunLoop (rs);
-			cleanup (rs);
-
-			app = setup ();
-			app.button.Y = Pos.Y (app.win);
-			rs = Application.Begin (Application.Top);
-			// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
-			Application.RunLoop (rs);
-			cleanup (rs);
-
-			app = setup ();
-			app.button.Y = Pos.Bottom (app.win);
-			rs = Application.Begin (Application.Top);
-			// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
-			Application.RunLoop (rs);
-			cleanup (rs);
-
-			app = setup ();
-			app.button.Y = Pos.Right (app.win);
-			rs = Application.Begin (Application.Top);
-			// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
-			Application.RunLoop (rs);
-			cleanup (rs);
-		}
-
-		[Fact]
-		public void Center_SetsValue ()
-		{
-			var pos = Pos.Center ();
-			Assert.Equal ("Center", pos.ToString ());
-		}
-
-		[Fact]
-		public void Percent_SetsValue ()
-		{
-			float f = 0;
-			var pos = Pos.Percent (f);
-			Assert.Equal ($"Factor({f / 100:0.###})", pos.ToString ());
-			f = 0.5F;
-			pos = Pos.Percent (f);
-			Assert.Equal ($"Factor({f / 100:0.###})", pos.ToString ());
-			f = 100;
-			pos = Pos.Percent (f);
-			Assert.Equal ($"Factor({f / 100:0.###})", pos.ToString ());
-		}
-
-		[Fact]
-		public void Percent_Equal ()
-		{
-			float n1 = 0;
-			float n2 = 0;
-			var pos1 = Pos.Percent (n1);
-			var pos2 = Pos.Percent (n2);
-			Assert.Equal (pos1, pos2);
-
-			n1 = n2 = 1;
-			pos1 = Pos.Percent (n1);
-			pos2 = Pos.Percent (n2);
-			Assert.Equal (pos1, pos2);
-
-			n1 = n2 = 0.5f;
-			pos1 = Pos.Percent (n1);
-			pos2 = Pos.Percent (n2);
-			Assert.Equal (pos1, pos2);
-
-			n1 = n2 = 100f;
-			pos1 = Pos.Percent (n1);
-			pos2 = Pos.Percent (n2);
-			Assert.Equal (pos1, pos2);
-
-			n1 = 0;
-			n2 = 1;
-			pos1 = Pos.Percent (n1);
-			pos2 = Pos.Percent (n2);
-			Assert.NotEqual (pos1, pos2);
-
-			n1 = 0.5f;
-			n2 = 1.5f;
-			pos1 = Pos.Percent (n1);
-			pos2 = Pos.Percent (n2);
-			Assert.NotEqual (pos1, pos2);
-		}
-
-		[Fact]
-		public void Percent_ThrowsOnIvalid ()
-		{
-			var pos = Pos.Percent (0);
-			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (-1));
-			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (101));
-			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (100.0001F));
-			Assert.Throws<ArgumentException> (() => pos = Pos.Percent (1000001));
-		}
+	}
 
-		[Fact]
-		public void ForceValidatePosDim_True_Pos_Validation_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type ()
+	// See: https://github.com/gui-cs/Terminal.Gui/issues/504
+	[Fact]
+	[TestRespondersDisposed]
+	public void LeftTopBottomRight_Win_ShouldNotThrow ()
+	{
+		// Setup Fake driver
+		(Window win, Button button) setup ()
 		{
 			Application.Init (new FakeDriver ());
-
-			var t = Application.Top;
-
-			var w = new Window () {
-				X = Pos.Left (t) + 2,
-				Y = Pos.At (2)
+			Application.Iteration += (s, a) => {
+				Application.RequestStop ();
 			};
-			var v = new View () {
-				X = Pos.Center (),
-				Y = Pos.Percent (10),
-				ForceValidatePosDim = true
+			var win = new Window () {
+				X = 0,
+				Y = 0,
+				Width = Dim.Fill (),
+				Height = Dim.Fill ()
 			};
+			Application.Top.Add (win);
 
-			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);
+			var button = new Button ("button") {
+				X = Pos.Center ()
 			};
+			win.Add (button);
 
-			Application.Iteration += (s, a) => Application.RequestStop ();
-
-			Application.Run ();
-			Application.Shutdown ();
+			return (win, button);
 		}
 
-		[Fact]
-		public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
-		{
-			Application.Init (new FakeDriver ());
-
-			var t = Application.Top;
-
-			var w = new Window (new Rect (1, 2, 4, 5));
-			t.Add (w);
-
-			t.Ready += (s, e) => {
-				Assert.Equal (2, w.X = 2);
-				Assert.Equal (2, w.Y = 2);
-			};
-
-			Application.Iteration += (s, a) => Application.RequestStop ();
+		RunState rs;
 
-			Application.Run ();
+		void cleanup (RunState rs)
+		{
+			// Cleanup
+			Application.End (rs);
+			// Shutdown must be called to safely clean up Application if Init has been called
 			Application.Shutdown ();
-
 		}
 
-		[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 ());
+		// Test cases:
+		var app = setup ();
+		app.button.Y = Pos.Left (app.win);
+		rs = Application.Begin (Application.Top);
+		// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
+		Application.RunLoop (rs);
+		cleanup (rs);
+
+		app = setup ();
+		app.button.Y = Pos.X (app.win);
+		rs = Application.Begin (Application.Top);
+		// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
+		Application.RunLoop (rs);
+		cleanup (rs);
+
+		app = setup ();
+		app.button.Y = Pos.Top (app.win);
+		rs = Application.Begin (Application.Top);
+		// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
+		Application.RunLoop (rs);
+		cleanup (rs);
+
+		app = setup ();
+		app.button.Y = Pos.Y (app.win);
+		rs = Application.Begin (Application.Top);
+		// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
+		Application.RunLoop (rs);
+		cleanup (rs);
+
+		app = setup ();
+		app.button.Y = Pos.Bottom (app.win);
+		rs = Application.Begin (Application.Top);
+		// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
+		Application.RunLoop (rs);
+		cleanup (rs);
+
+		app = setup ();
+		app.button.Y = Pos.Right (app.win);
+		rs = Application.Begin (Application.Top);
+		// If Application.RunState is used then we must use Application.RunLoop with the rs parameter
+		Application.RunLoop (rs);
+		cleanup (rs);
+	}
 
-			var t = Application.Top;
+	[Fact]
+	public void Center_SetsValue ()
+	{
+		var pos = Pos.Center ();
+		Assert.Equal ("Center", pos.ToString ());
+	}
 
-			var w = new Window () {
-				X = Pos.Left (t) + 2,
-				Y = Pos.At (2)
-			};
-			var v = new View () {
-				X = Pos.Center (),
-				Y = Pos.Percent (10)
-			};
+	[Fact]
+	public void Percent_SetsValue ()
+	{
+		float f = 0;
+		var pos = Pos.Percent (f);
+		Assert.Equal ($"Factor({f / 100:0.###})", pos.ToString ());
+		f = 0.5F;
+		pos = Pos.Percent (f);
+		Assert.Equal ($"Factor({f / 100:0.###})", pos.ToString ());
+		f = 100;
+		pos = Pos.Percent (f);
+		Assert.Equal ($"Factor({f / 100:0.###})", pos.ToString ());
+	}
 
-			w.Add (v);
-			t.Add (w);
+	[Fact]
+	public void Percent_Equal ()
+	{
+		float n1 = 0;
+		float n2 = 0;
+		var pos1 = Pos.Percent (n1);
+		var pos2 = Pos.Percent (n2);
+		Assert.Equal (pos1, pos2);
+
+		n1 = n2 = 1;
+		pos1 = Pos.Percent (n1);
+		pos2 = Pos.Percent (n2);
+		Assert.Equal (pos1, pos2);
+
+		n1 = n2 = 0.5f;
+		pos1 = Pos.Percent (n1);
+		pos2 = Pos.Percent (n2);
+		Assert.Equal (pos1, pos2);
+
+		n1 = n2 = 100f;
+		pos1 = Pos.Percent (n1);
+		pos2 = Pos.Percent (n2);
+		Assert.Equal (pos1, pos2);
+
+		n1 = 0;
+		n2 = 1;
+		pos1 = Pos.Percent (n1);
+		pos2 = Pos.Percent (n2);
+		Assert.NotEqual (pos1, pos2);
+
+		n1 = 0.5f;
+		n2 = 1.5f;
+		pos1 = Pos.Percent (n1);
+		pos2 = Pos.Percent (n2);
+		Assert.NotEqual (pos1, pos2);
+	}
 
-			t.Ready += (s, e) => {
-				v.LayoutStyle = LayoutStyle.Absolute;
-				Assert.Equal (2, v.X = 2);
-				Assert.Equal (2, v.Y = 2);
-			};
+	[Fact]
+	public void Percent_ThrowsOnIvalid ()
+	{
+		var pos = Pos.Percent (0);
+		Assert.Throws<ArgumentException> (() => pos = Pos.Percent (-1));
+		Assert.Throws<ArgumentException> (() => pos = Pos.Percent (101));
+		Assert.Throws<ArgumentException> (() => pos = Pos.Percent (100.0001F));
+		Assert.Throws<ArgumentException> (() => pos = Pos.Percent (1000001));
+	}
 
-			Application.Iteration += (s, a) => Application.RequestStop ();
+	[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 ();
+	}
 
-			Application.Run ();
-			Application.Shutdown ();
-		}
+	[Fact]
+	public void Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
+	{
+		Application.Init (new FakeDriver ());
 
-		// 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 ();
-		//}
-
-		[Fact, TestRespondersDisposed]
-		public void PosCombine_Will_Throws ()
-		{
-			Application.Init (new FakeDriver ());
+		var t = Application.Top;
 
-			var t = Application.Top;
+		var w = new Window (new Rect (1, 2, 4, 5));
+		t.Add (w);
 
-			var w = new Window () {
-				X = Pos.Left (t) + 2,
-				Y = Pos.Top (t) + 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
-			};
+		t.Ready += (s, e) => {
+			Assert.Equal (2, w.X = 2);
+			Assert.Equal (2, w.Y = 2);
+		};
 
-			f.Add (v1); // v2 not added
-			w.Add (f);
-			t.Add (w);
+		Application.Iteration += (s, a) => Application.RequestStop ();
 
-			f.X = Pos.X (v2) - Pos.X (v1);
-			f.Y = Pos.Y (v2) - Pos.Y (v1);
+		Application.Run ();
+		Application.Shutdown ();
 
-			Assert.Throws<InvalidOperationException> (() => Application.Run ());
-			Application.Shutdown ();
+	}
 
-			v2.Dispose ();
-		}
+	[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 ());
 
-		[Fact, TestRespondersDisposed]
-		public void Pos_Add_Operator ()
-		{
-			Application.Init (new FakeDriver ());
+		var t = Application.Top;
 
-			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 };
-			var count = 0;
-
-			field.KeyDown += (s, k) => {
-				if (k.KeyCode == KeyCode.Enter) {
-					field.Text = $"Label {count}";
-					var label = new Label (field.Text) { X = 0, Y = field.Y, Width = 20 };
-					view.Add (label);
-					Assert.Equal ($"Label {count}", label.Text);
-					Assert.Equal ($"Absolute({count})", label.Y.ToString ());
-
-					Assert.Equal ($"Absolute({count})", field.Y.ToString ());
-					field.Y += 1;
-					count++;
-					Assert.Equal ($"Absolute({count})", field.Y.ToString ());
-				}
-			};
+		var w = new Window () {
+			X = Pos.Left (t) + 2,
+			Y = Pos.At (2)
+		};
+		var v = new View () {
+			X = Pos.Center (),
+			Y = Pos.Percent (10)
+		};
 
-			Application.Iteration += (s, a) => {
-				while (count < 20) field.NewKeyDownEvent (new (KeyCode.Enter));
+		w.Add (v);
+		t.Add (w);
 
-				Application.RequestStop ();
-			};
-
-			var win = new Window ();
-			win.Add (view);
-			win.Add (field);
-
-			top.Add (win);
+		t.Ready += (s, e) => {
+			v.LayoutStyle = LayoutStyle.Absolute;
+			Assert.Equal (2, v.X = 2);
+			Assert.Equal (2, v.Y = 2);
+		};
 
-			Application.Run (top);
+		Application.Iteration += (s, a) => Application.RequestStop ();
 
-			Assert.Equal (20, count);
+		Application.Run ();
+		Application.Shutdown ();
+	}
 
-			// Shutdown must be called to safely clean up Application if Init has been called
-			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 ();
+	//}
+
+	[Fact]
+	[TestRespondersDisposed]
+	public void PosCombine_Will_Throws ()
+	{
+		Application.Init (new FakeDriver ());
+
+		var t = Application.Top;
+
+		var w = new Window () {
+			X = Pos.Left (t) + 2,
+			Y = Pos.Top (t) + 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 not added
+		w.Add (f);
+		t.Add (w);
+
+		f.X = Pos.X (v2) - Pos.X (v1);
+		f.Y = Pos.Y (v2) - Pos.Y (v1);
+
+		Assert.Throws<InvalidOperationException> (() => Application.Run ());
+		Application.Shutdown ();
+
+		v2.Dispose ();
+	}
 
-		[Fact, TestRespondersDisposed]
-		public void Pos_Subtract_Operator ()
-		{
-			Application.Init (new FakeDriver ());
+	[Fact]
+	[TestRespondersDisposed]
+	public void Pos_Add_Operator ()
+	{
+		Application.Init (new FakeDriver ());
 
-			var top = Application.Top;
+		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 };
-			var count = 20;
-			var listLabels = new List<Label> ();
+		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;
 
-			for (int i = 0; i < count; i++) {
-				field.Text = $"Label {i}";
+		field.KeyDown += (s, k) => {
+			if (k.KeyCode == KeyCode.Enter) {
+				field.Text = $"Label {count}";
 				var label = new Label (field.Text) { X = 0, Y = field.Y, Width = 20 };
 				view.Add (label);
-				Assert.Equal ($"Label {i}", label.Text);
-				Assert.Equal ($"Absolute({i})", field.Y.ToString ());
-				listLabels.Add (label);
+				Assert.Equal ($"Label {count}", label.Text);
+				Assert.Equal ($"Absolute({count})", label.Y.ToString ());
 
-				Assert.Equal ($"Absolute({i})", field.Y.ToString ());
+				Assert.Equal ($"Absolute({count})", field.Y.ToString ());
 				field.Y += 1;
-				Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ());
+				count++;
+				Assert.Equal ($"Absolute({count})", field.Y.ToString ());
 			}
+		};
 
-			field.KeyDown += (s, k) => {
-				if (k.KeyCode == KeyCode.Enter) {
-					Assert.Equal ($"Label {count - 1}", listLabels [count - 1].Text);
-					view.Remove (listLabels [count - 1]);
-					listLabels [count - 1].Dispose ();
-
-					Assert.Equal ($"Absolute({count})", field.Y.ToString ());
-					field.Y -= 1;
-					count--;
-					Assert.Equal ($"Absolute({count})", field.Y.ToString ());
-				}
-			};
-
-			Application.Iteration += (s, a) => {
-				while (count > 0) {
-					field.NewKeyDownEvent (new (KeyCode.Enter));
-				}
+		Application.Iteration += (s, a) => {
+			while (count < 20) {
+				field.NewKeyDownEvent (new Key (KeyCode.Enter));
+			}
 
-				Application.RequestStop ();
-			};
+			Application.RequestStop ();
+		};
 
-			var win = new Window ();
-			win.Add (view);
-			win.Add (field);
+		var win = new Window ();
+		win.Add (view);
+		win.Add (field);
 
-			top.Add (win);
+		top.Add (win);
 
-			Application.Run (top);
+		Application.Run (top);
 
-			Assert.Equal (0, count);
+		Assert.Equal (20, count);
 
-			// Shutdown must be called to safely clean up Application if Init has been called
-			Application.Shutdown ();
+		// Shutdown must be called to safely clean up Application if Init has been called
+		Application.Shutdown ();
+	}
 
+	[Fact]
+	[TestRespondersDisposed]
+	public void Pos_Subtract_Operator ()
+	{
+		Application.Init (new FakeDriver ());
+
+		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 listLabels = new List<Label> ();
+
+		for (int 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);
+			Assert.Equal ($"Label {i}", label.Text);
+			Assert.Equal ($"Absolute({i})", field.Y.ToString ());
+			listLabels.Add (label);
+
+			Assert.Equal ($"Absolute({i})", field.Y.ToString ());
+			field.Y += 1;
+			Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ());
 		}
 
-		[Fact, TestRespondersDisposed]
-		public void Internal_Tests ()
-		{
-			var posFactor = new Pos.PosFactor (0.10F);
-			Assert.Equal (10, posFactor.Anchor (100));
-
-			var posAnchorEnd = new Pos.PosAnchorEnd (1);
-			Assert.Equal (99, posAnchorEnd.Anchor (100));
-
-			var posCenter = new Pos.PosCenter ();
-			Assert.Equal (50, posCenter.Anchor (100));
-
-			var posAbsolute = new Pos.PosAbsolute (10);
-			Assert.Equal (10, posAbsolute.Anchor (0));
-
-			var posCombine = new Pos.PosCombine (true, posFactor, posAbsolute);
-			Assert.Equal (posCombine.left, posFactor);
-			Assert.Equal (posCombine.right, posAbsolute);
-			Assert.Equal (20, posCombine.Anchor (100));
-
-			posCombine = new Pos.PosCombine (true, posAbsolute, posFactor);
-			Assert.Equal (posCombine.left, posAbsolute);
-			Assert.Equal (posCombine.right, posFactor);
-			Assert.Equal (20, posCombine.Anchor (100));
-
-			var view = new View (new Rect (20, 10, 20, 1));
-			var posViewX = new Pos.PosView (view, 0);
-			Assert.Equal (20, posViewX.Anchor (0));
-			var posViewY = new Pos.PosView (view, 1);
-			Assert.Equal (10, posViewY.Anchor (0));
-			var posRight = new Pos.PosView (view, 2);
-			Assert.Equal (40, posRight.Anchor (0));
-			var posViewBottom = new Pos.PosView (view, 3);
-			Assert.Equal (11, posViewBottom.Anchor (0));
-			
-			view.Dispose ();
-		}
+		field.KeyDown += (s, k) => {
+			if (k.KeyCode == KeyCode.Enter) {
+				Assert.Equal ($"Label {count - 1}", listLabels [count - 1].Text);
+				view.Remove (listLabels [count - 1]);
+				listLabels [count - 1].Dispose ();
 
-		[Fact]
-		public void Function_SetsValue ()
-		{
-			var text = "Test";
-			var pos = Pos.Function (() => text.Length);
-			Assert.Equal ("PosFunc(4)", pos.ToString ());
+				Assert.Equal ($"Absolute({count})", field.Y.ToString ());
+				field.Y -= 1;
+				count--;
+				Assert.Equal ($"Absolute({count})", field.Y.ToString ());
+			}
+		};
+
+		Application.Iteration += (s, a) => {
+			while (count > 0) {
+				field.NewKeyDownEvent (new Key (KeyCode.Enter));
+			}
 
-			text = "New Test";
-			Assert.Equal ("PosFunc(8)", pos.ToString ());
+			Application.RequestStop ();
+		};
 
-			text = "";
-			Assert.Equal ("PosFunc(0)", pos.ToString ());
-		}
+		var win = new Window ();
+		win.Add (view);
+		win.Add (field);
 
-		[Fact]
-		public void Function_Equal ()
-		{
-			var f1 = () => 0;
-			var f2 = () => 0;
+		top.Add (win);
 
-			var pos1 = Pos.Function (f1);
-			var pos2 = Pos.Function (f2);
-			Assert.Equal (pos1, pos2);
+		Application.Run (top);
 
-			f2 = () => 1;
-			pos2 = Pos.Function (f2);
-			Assert.NotEqual (pos1, pos2);
-		}
+		Assert.Equal (0, count);
 
-		[Theory, AutoInitShutdown]
-		[InlineData (true)]
-		[InlineData (false)]
-		public void PosPercentPlusOne (bool testHorizontal)
-		{
-			var container = new View {
-				Width = 100,
-				Height = 100,
-			};
+		// Shutdown must be called to safely clean up Application if Init has been called
+		Application.Shutdown ();
 
-			var label = new Label {
-				X = testHorizontal ? Pos.Percent (50) + Pos.Percent (10) + 1 : 1,
-				Y = testHorizontal ? 1 : Pos.Percent (50) + Pos.Percent (10) + 1,
-				Width = 10,
-				Height = 10,
-			};
+	}
 
-			container.Add (label);
-			Application.Top.Add (container);
-			Application.Top.LayoutSubviews ();
+	[Fact]
+	[TestRespondersDisposed]
+	public void Internal_Tests ()
+	{
+		var posFactor = new Pos.PosFactor (0.10F);
+		Assert.Equal (10, posFactor.Anchor (100));
+
+		var posAnchorEnd = new Pos.PosAnchorEnd (1);
+		Assert.Equal (99, posAnchorEnd.Anchor (100));
+
+		var posCenter = new Pos.PosCenter ();
+		Assert.Equal (50, posCenter.Anchor (100));
+
+		var posAbsolute = new Pos.PosAbsolute (10);
+		Assert.Equal (10, posAbsolute.Anchor (0));
+
+		var posCombine = new Pos.PosCombine (true, posFactor, posAbsolute);
+		Assert.Equal (posCombine._left, posFactor);
+		Assert.Equal (posCombine._right, posAbsolute);
+		Assert.Equal (20, posCombine.Anchor (100));
+
+		posCombine = new Pos.PosCombine (true, posAbsolute, posFactor);
+		Assert.Equal (posCombine._left, posAbsolute);
+		Assert.Equal (posCombine._right, posFactor);
+		Assert.Equal (20, posCombine.Anchor (100));
+
+		var view = new View (new Rect (20, 10, 20, 1));
+		var posViewX = new Pos.PosView (view, 0);
+		Assert.Equal (20, posViewX.Anchor (0));
+		var posViewY = new Pos.PosView (view, 1);
+		Assert.Equal (10, posViewY.Anchor (0));
+		var posRight = new Pos.PosView (view, 2);
+		Assert.Equal (40, posRight.Anchor (0));
+		var posViewBottom = new Pos.PosView (view, 3);
+		Assert.Equal (11, posViewBottom.Anchor (0));
+
+		view.Dispose ();
+	}
 
-			Assert.Equal (100, container.Frame.Width);
-			Assert.Equal (100, container.Frame.Height);
+	[Fact]
+	public void Function_SetsValue ()
+	{
+		string text = "Test";
+		var pos = Pos.Function (() => text.Length);
+		Assert.Equal ("PosFunc(4)", pos.ToString ());
 
-			if (testHorizontal) {
-				Assert.Equal (61, label.Frame.X);
-				Assert.Equal (1, label.Frame.Y);
-			} else {
-				Assert.Equal (1, label.Frame.X);
-				Assert.Equal (61, label.Frame.Y);
-			}
-		}
+		text = "New Test";
+		Assert.Equal ("PosFunc(8)", pos.ToString ());
 
-		[Fact]
-		public void PosCombine_Referencing_Same_View ()
-		{
-			var super = new View ("super") {
-				Width = 10,
-				Height = 10
-			};
-			var view1 = new View ("view1") {
-				Width = 2,
-				Height = 2,
-			};
-			var view2 = new View ("view2") {
-				Width = 2,
-				Height = 2,
-			};
-			view2.X = Pos.AnchorEnd () - (Pos.Right (view2) - Pos.Left (view2));
+		text = "";
+		Assert.Equal ("PosFunc(0)", pos.ToString ());
+	}
 
-			super.Add (view1, view2);
-			super.BeginInit ();
-			super.EndInit ();
+	[Fact]
+	public void Function_Equal ()
+	{
+		var f1 = () => 0;
+		var f2 = () => 0;
 
-			var exception = Record.Exception (super.LayoutSubviews);
-			Assert.Null (exception);
-			Assert.Equal (new Rect (0, 0, 10, 10), super.Frame);
-			Assert.Equal (new Rect (0, 0, 2, 2), view1.Frame);
-			Assert.Equal (new Rect (8, 0, 2, 2), view2.Frame);
+		var pos1 = Pos.Function (f1);
+		var pos2 = Pos.Function (f2);
+		Assert.Equal (pos1, pos2);
 
-			super.Dispose ();
-		}
+		f2 = () => 1;
+		pos2 = Pos.Function (f2);
+		Assert.NotEqual (pos1, pos2);
+	}
 
-		[Fact]
-		public void DoNotReturnPosCombine ()
-		{
-			var v = new View () { Id = "V" };
-
-			var pos = Pos.Left (v);
-			Assert.Equal (
-				"View(side=x,target=View(V)(0,0,0,0))",
-				pos.ToString ());
-
-			pos = Pos.X (v);
-			Assert.Equal (
-				"View(side=x,target=View(V)(0,0,0,0))",
-				pos.ToString ());
-
-			pos = Pos.Top (v);
-			Assert.Equal (
-				"View(side=y,target=View(V)(0,0,0,0))",
-				pos.ToString ());
-
-			pos = Pos.Y (v);
-			Assert.Equal (
-				"View(side=y,target=View(V)(0,0,0,0))",
-				pos.ToString ());
-
-			pos = Pos.Right (v);
-			Assert.Equal (
-				"View(side=right,target=View(V)(0,0,0,0))",
-				pos.ToString ());
-
-			pos = Pos.Bottom (v);
-			Assert.Equal (
-				"View(side=bottom,target=View(V)(0,0,0,0))",
-				pos.ToString ());
+	[Theory]
+	[AutoInitShutdown]
+	[InlineData (true)]
+	[InlineData (false)]
+	public void PosPercentPlusOne (bool testHorizontal)
+	{
+		var container = new View {
+			Width = 100,
+			Height = 100
+		};
+
+		var label = new Label {
+			X = testHorizontal ? Pos.Percent (50) + Pos.Percent (10) + 1 : 1,
+			Y = testHorizontal ? 1 : Pos.Percent (50) + Pos.Percent (10) + 1,
+			Width = 10,
+			Height = 10
+		};
+
+		container.Add (label);
+		Application.Top.Add (container);
+		Application.Top.LayoutSubviews ();
+
+		Assert.Equal (100, container.Frame.Width);
+		Assert.Equal (100, container.Frame.Height);
+
+		if (testHorizontal) {
+			Assert.Equal (61, label.Frame.X);
+			Assert.Equal (1, label.Frame.Y);
+		} else {
+			Assert.Equal (1, label.Frame.X);
+			Assert.Equal (61, label.Frame.Y);
 		}
 	}
-}
+
+	[Fact]
+	public void PosCombine_Referencing_Same_View ()
+	{
+		var super = new View ("super") {
+			Width = 10,
+			Height = 10
+		};
+		var view1 = new View ("view1") {
+			Width = 2,
+			Height = 2
+		};
+		var view2 = new View ("view2") {
+			Width = 2,
+			Height = 2
+		};
+		view2.X = Pos.AnchorEnd () - (Pos.Right (view2) - Pos.Left (view2));
+
+		super.Add (view1, view2);
+		super.BeginInit ();
+		super.EndInit ();
+
+		var exception = Record.Exception (super.LayoutSubviews);
+		Assert.Null (exception);
+		Assert.Equal (new Rect (0, 0, 10, 10), super.Frame);
+		Assert.Equal (new Rect (0, 0, 2, 2), view1.Frame);
+		Assert.Equal (new Rect (8, 0, 2, 2), view2.Frame);
+
+		super.Dispose ();
+	}
+
+	[Fact]
+	public void DoNotReturnPosCombine ()
+	{
+		var v = new View () { Id = "V" };
+
+		var pos = Pos.Left (v);
+		Assert.Equal (
+			"View(side=x,target=View(V)(0,0,0,0))",
+			pos.ToString ());
+
+		pos = Pos.X (v);
+		Assert.Equal (
+			"View(side=x,target=View(V)(0,0,0,0))",
+			pos.ToString ());
+
+		pos = Pos.Top (v);
+		Assert.Equal (
+			"View(side=y,target=View(V)(0,0,0,0))",
+			pos.ToString ());
+
+		pos = Pos.Y (v);
+		Assert.Equal (
+			"View(side=y,target=View(V)(0,0,0,0))",
+			pos.ToString ());
+
+		pos = Pos.Right (v);
+		Assert.Equal (
+			"View(side=right,target=View(V)(0,0,0,0))",
+			pos.ToString ());
+
+		pos = Pos.Bottom (v);
+		Assert.Equal (
+			"View(side=bottom,target=View(V)(0,0,0,0))",
+			pos.ToString ());
+	}
+}	
+

+ 456 - 0
UnitTests/View/Layout/SetRelativeLayoutTests.cs

@@ -0,0 +1,456 @@
+using System;
+using System.Text;
+using Xunit;
+using Xunit.Abstractions;
+using static Terminal.Gui.SpinnerStyle;
+
+namespace Terminal.Gui.ViewTests;
+
+public class SetRelativeLayoutTests {
+	readonly ITestOutputHelper _output;
+
+	public SetRelativeLayoutTests (ITestOutputHelper output) => _output = output;
+
+	[Fact]
+	public void Null_Pos_Is_Same_As_PosAbsolute0 ()
+	{
+		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,
+		};
+
+		// 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);
+	}
+
+	[Fact]
+	public void Null_Dim_Is_Same_As_DimFill0 ()
+	{
+		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,
+		};
+
+		// 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);
+	}
+
+	[Fact]
+	public void Fill_Pos_Within_Bounds ()
+	{
+		var screen = new Rect (0, 0, 80, 25);
+		var view = new View () {
+			X = 1,
+			Y = 1,
+			Width = 5,
+			Height = 4
+		};
+
+		view.SetRelativeLayout (screen);
+		Assert.Equal (1, view.Frame.X);
+		Assert.Equal (1, view.Frame.Y);
+		Assert.Equal (5, view.Frame.Width);
+		Assert.Equal (4, view.Frame.Height);
+
+		view.Width = 80;
+		view.Height = 25;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (1, view.Frame.X);
+		Assert.Equal (1, view.Frame.Y);
+		Assert.Equal (80, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.Width = Dim.Fill ();
+		view.Height = Dim.Fill ();
+		view.SetRelativeLayout (screen);
+		Assert.Equal (1, view.Frame.X);
+		Assert.Equal (1, view.Frame.Y);
+		Assert.Equal (79, view.Frame.Width); // proof (80 - 1)
+		Assert.Equal (24, view.Frame.Height); // proof (25 - 1)
+
+		view.X = 79;
+		view.Width = Dim.Fill ();
+		view.Height = Dim.Fill ();
+		view.SetRelativeLayout (screen);
+		Assert.Equal (79, view.Frame.X);
+		Assert.Equal (1, view.Frame.Y);
+		Assert.Equal (1, view.Frame.Width); // proof (80 - 79)
+		Assert.Equal (24, view.Frame.Height);
+
+		view.X = 80;
+		view.Width = Dim.Fill ();
+		view.Height = Dim.Fill ();
+		view.SetRelativeLayout (screen);
+		Assert.Equal (80, view.Frame.X);
+		Assert.Equal (1, view.Frame.Y);
+		Assert.Equal (0, view.Frame.Width);  // proof (80 - 80)
+		Assert.Equal (24, view.Frame.Height);
+	}
+
+	[Fact]
+	public void FIll_Pos_Outside_Bounds ()
+	{
+		var screen = new Rect (0, 0, 80, 25);
+		var view = new View () {
+			X = 90,  // outside of screen +10
+			Y = -10,   // outside of screen -10
+			Width = 15,
+			Height = 15
+		};
+
+		view.SetRelativeLayout (screen);
+		Assert.Equal (90, view.Frame.X);
+		Assert.Equal (-10, view.Frame.Y);
+		Assert.Equal (15, view.Frame.Width);
+		Assert.Equal (15, view.Frame.Height);
+
+		// prove Width=Height= same as screen size
+		view.Width = 80;
+		view.Height = 25;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (90, view.Frame.X);
+		Assert.Equal (-10, view.Frame.Y);
+		Assert.Equal (80, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.Width = Dim.Fill ();
+		view.Height = Dim.Fill ();
+		view.SetRelativeLayout (screen);
+		Assert.Equal (90, view.Frame.X);
+		Assert.Equal (-10, view.Frame.Y);
+		Assert.Equal (0, view.Frame.Width);    // proof: 15x15 view is placed beyond right side of screen, so fill width is 0
+		Assert.Equal (35, view.Frame.Height);  // proof: 15x15 view is placed beyond top of screen 10 rows, screen is 25 rows. so fill height is 25 + 10 = 35
+	}
+
+	[Fact]
+	public void PosCombine_PosCenter_Minus_Absolute ()
+	{
+		// This test used to be in ViewTests.cs Internal_Tests. It was moved here because it is testing
+		// SetRelativeLayout. In addition, the old test was bogus because it was testing the wrong thing (and 
+		// because in v1 Pos.Center was broken in this regard!
+
+		var screen = new Rect (0, 0, 80, 25);
+		var view = new View () {
+			X = Pos.Center () - 41,  // -2 off left edge of screen
+			Y = Pos.Center () - 13,  // -1 off top edge of screen
+			Width = 1,
+			Height = 1
+		};
+
+		view.SetRelativeLayout (screen);
+		Assert.Equal (-2, view.Frame.X); // proof: 1x1 view centered in 80x25 screen has x of 39, so -41 is -2
+		Assert.Equal (-1, view.Frame.Y); // proof: 1x1 view centered in 80x25 screen has y of 12, so -13 is -1
+
+		view.Width = 80;
+		view.Height = 25;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (-41, view.Frame.X);
+		Assert.Equal (-13, view.Frame.Y);
+		Assert.Equal (80, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.Width = Dim.Fill (); 
+		view.Height = Dim.Fill ();
+		view.SetRelativeLayout (screen);
+		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 ()
+	{
+		var screen = new Rect (0, 0, 80, 25);
+		var view = new View () {
+			X = Pos.Center (),
+			Y = Pos.Center (),
+			Width = Dim.Fill(),
+			Height = Dim.Fill()
+		};
+
+		view.SetRelativeLayout (screen);
+		Assert.Equal (0, view.Frame.X); 
+		Assert.Equal (0, view.Frame.Y);
+		Assert.Equal (80, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.X = Pos.Center () + 1;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (1, view.Frame.X);
+		Assert.Equal (0, view.Frame.Y);
+		Assert.Equal (79, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.X = Pos.Center () + 79;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (79, view.Frame.X);
+		Assert.Equal (0, view.Frame.Y);
+		Assert.Equal (1, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.X = Pos.Center () + 80;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (80, view.Frame.X);
+		Assert.Equal (0, view.Frame.Y);
+		Assert.Equal (0, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.X = Pos.Center () - 1;
+		view.SetRelativeLayout (screen);
+		Assert.Equal (-1, view.Frame.X);
+		Assert.Equal (0, view.Frame.Y);
+		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 (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).
+		view.SetRelativeLayout (screen);
+		Assert.Equal (-3, view.Frame.X);
+		Assert.Equal (0, view.Frame.Y);
+		Assert.Equal (83, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+		view.X = Pos.Center () - 41; // Fill means all the way to right. So width will be . (dim gets calc'd before pos).
+		view.SetRelativeLayout (screen);
+		Assert.Equal (-41, view.Frame.X);
+		Assert.Equal (0, view.Frame.Y);
+		Assert.Equal (121, view.Frame.Width);
+		Assert.Equal (25, view.Frame.Height);
+
+	}
+	[Fact]
+	public void PosCombine_PosCenter_Plus_Absolute ()
+	{
+		var screen = new Rect (0, 0, 80, 25);
+		var view = new View () {
+			X = Pos.Center () + 41,  // ((80 / 2) - (5 / 2)) + 41 = (40 - 3 + 41) = 78
+			Y = Pos.Center () + 13,  // ((25 / 2) - (4 / 2)) + 13 = (12 - 2 + 13) = 23
+			Width = 5,
+			Height = 4
+		};
+
+		view.SetRelativeLayout (screen);
+		Assert.Equal (78, view.Frame.X);
+		Assert.Equal (23, view.Frame.Y);
+	}
+
+	[Fact] [TestRespondersDisposed]
+	public void PosCombine_Plus_Absolute ()
+	{
+		var superView = new View () {
+			AutoSize = false,
+			Width = 10,
+			Height = 10
+		};
+
+		var testView = new View () {
+			AutoSize = false,
+			X = Pos.Center (),
+			Y = Pos.Center (),
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (4, testView.Frame.X);
+		Assert.Equal (4, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = Pos.Center () + 1, // ((10 / 2) - (1 / 2)) + 1 = 5 - 1 + 1 = 5
+			Y = Pos.Center () + 1,
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (5, testView.Frame.X);
+		Assert.Equal (5, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = 1 + Pos.Center (),
+			Y = 1 + Pos.Center (),
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (5, testView.Frame.X);
+		Assert.Equal (5, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = 1 + Pos.Percent (50),
+			Y = Pos.Percent (50) + 1,
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (6, testView.Frame.X);
+		Assert.Equal (6, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = Pos.Percent (10) + Pos.Percent (40),
+			Y = Pos.Percent (10) + Pos.Percent (40),
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (5, testView.Frame.X);
+		Assert.Equal (5, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = 1 + Pos.Percent (10) + Pos.Percent (40) - 1,
+			Y = 5 + Pos.Percent (10) + Pos.Percent (40) - 5,
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (5, testView.Frame.X);
+		Assert.Equal (5, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = Pos.Left (testView),
+			Y = Pos.Left (testView),
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (5, testView.Frame.X);
+		Assert.Equal (5, testView.Frame.Y);
+
+		testView = new View () {
+			AutoSize = false,
+			X = 1 + Pos.Left (testView),
+			Y = Pos.Top (testView) + 1,
+			Width = 1,
+			Height = 1
+		};
+		superView.Add (testView);
+		testView.SetRelativeLayout (superView.Frame);
+		Assert.Equal (6, testView.Frame.X);
+		Assert.Equal (6, testView.Frame.Y);
+
+		superView.Dispose ();
+
+	}
+}

+ 14 - 19
UnitTests/View/ViewTests.cs

@@ -504,6 +504,7 @@ namespace Terminal.Gui.ViewTests {
 
 			var runState = Application.Begin (top);
 
+			// BUGBUG: This is a SetRelativeLayout test. It should be moved to SetRelativeLayoutTests.cs
 			view.Width = Dim.Fill ();
 			view.Height = Dim.Fill ();
 			Assert.Equal (10, view.Bounds.Width);
@@ -519,6 +520,7 @@ namespace Terminal.Gui.ViewTests {
 			Assert.Equal (79, view.Bounds.Width);
 			Assert.Equal (24, view.Bounds.Height);
 
+			// BUGBUG: This is a SetRelativeLayout test. It should be moved to SetRelativeLayoutTests.cs
 			view.X = 0;
 			view.Y = 0;
 			Assert.Equal ("Absolute(0)", view.X.ToString ());
@@ -532,6 +534,8 @@ namespace Terminal.Gui.ViewTests {
 			Assert.Equal (0, view.Bounds.Y);
 			Assert.Equal (80, view.Bounds.Width);
 			Assert.Equal (25, view.Bounds.Height);
+
+			// BUGBUG: This is a layout test. It should be moved to LayoutTests.cs
 			bool layoutStarted = false;
 			view.LayoutStarted += (s, e) => layoutStarted = true;
 			view.OnLayoutStarted (null);
@@ -539,6 +543,10 @@ namespace Terminal.Gui.ViewTests {
 			view.LayoutComplete += (s, e) => layoutStarted = false;
 			view.OnLayoutComplete (null);
 			Assert.False (layoutStarted);
+
+			// This test has been moved to SetRlativeLayoutTests because it is testing
+			// SetRelativeLayout. In addition, the old test was bogus because it was testing the wrong thing (and 
+			// because in v1 Pos.Center was broken in this regard!
 			view.X = Pos.Center () - 41;
 			view.Y = Pos.Center () - 13;
 			view.SetRelativeLayout (top.Bounds);
@@ -546,7 +554,7 @@ namespace Terminal.Gui.ViewTests {
 			view.BoundsToScreen (0, 0, out rcol, out rrow);
 			Assert.Equal (-41, rcol);
 			Assert.Equal (-13, rrow);
-			
+
 			Application.End (runState);
 		}
 
@@ -1340,16 +1348,15 @@ At 0,0
 			var frame = new FrameView ();
 
 			var label = new Label ("This should be the first line.") {
-				TextAlignment = Terminal.Gui.TextAlignment.Centered,
 				ColorScheme = Colors.Menu,
 				Width = Dim.Fill (),
-				X = Pos.Center (),
-				Y = Pos.Center () - 2  // center minus 2 minus two lines top and bottom borders equal to zero (4-2-2=0)
+				X = 0, // don't overcomplicate unit tests
+				Y = 0 
 			};
 
 			var button = new Button ("Press me!") {
-				X = Pos.Center (),
-				Y = Pos.Center ()
+				X = 0, // don't overcomplicate unit tests
+				Y = 1
 			};
 
 			frame.Add (label, button);
@@ -1388,19 +1395,7 @@ At 0,0
 				frame.Frame.Left, frame.Frame.Top,
 				frame.Frame.Right, frame.Frame.Bottom));
 			Assert.Equal (new Rect (0, 0, 38, 1), label.Frame);
-			Assert.Equal (new Rect (12, 2, 13, 1), button.Frame);
-			var expected = @$"
-                    ┌──────────────────────────────────────┐
-                    │    This should be the first line.    │
-                    │                                      │
-                    │            {CM.Glyphs.LeftBracket} Press me! {CM.Glyphs.RightBracket}             │
-                    │                                      │
-                    │                                      │
-                    │                                      │
-                    └──────────────────────────────────────┘
-";
-
-			TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
+			Assert.Equal (new Rect (0, 1, 13, 1), button.Frame); // this proves frame was set
 			Application.End (runState);
 		}
 

+ 5 - 3
UnitTests/Views/ContextMenuTests.cs

@@ -935,7 +935,8 @@ namespace Terminal.Gui.ViewsTests {
 │                  │
 └──────────────────┘", output);
 
-			var dialog = new Dialog () { X = 2, Y = 2, Width = 15, Height = 4 };
+			// Don't use Dialog here as it has more layout logic. Use Window instead.
+			var dialog = new Window () { X = 2, Y = 2, Width = 15, Height = 4 };
 			dialog.Add (new TextField ("Test") { X = Pos.Center (), Width = 10 });
 			var rs = Application.Begin (dialog);
 
@@ -992,8 +993,9 @@ namespace Terminal.Gui.ViewsTests {
 
 			Assert.Equal (new Rect (0, 0, 20, 15), Application.Driver.Clip);
 			TestHelpers.AssertDriverContentsWithFrameAre ("", output);
-
-			var dialog = new Dialog () { X = 2, Y = 2, Width = 15, Height = 4 };
+			
+			// Don't use Dialog here as it has more layout logic. Use Window instead.
+			var dialog = new Window () { X = 2, Y = 2, Width = 15, Height = 4 };
 			dialog.Add (new TextField ("Test") { X = Pos.Center (), Width = 10 });
 			var rs = Application.Begin (dialog);
 

+ 1 - 1
UnitTests/Views/LabelTests.cs

@@ -662,7 +662,7 @@ e
 				Width = Dim.Fill (),
 				Height = Dim.Percent (50f),
 				TextDirection = TextDirection.TopBottom_LeftRight,
-				ForceValidatePosDim = true
+				ValidatePosDim = true
 			};
 			Application.Top.Add (label);
 			Application.Begin (Application.Top);

+ 161 - 176
UnitTests/Views/ToplevelTests.cs

@@ -2,7 +2,7 @@
 using Xunit;
 using Xunit.Abstractions;
 
-namespace Terminal.Gui.ViewsTests; 
+namespace Terminal.Gui.ViewsTests;
 
 public class ToplevelTests {
 	readonly ITestOutputHelper output;
@@ -700,126 +700,115 @@ public class ToplevelTests {
 		Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Mouse_Drag_On_Top_With_Superview_Null ()
 	{
 		var win = new Window ();
 		var top = Application.Top;
 		top.Add (win);
 		int iterations = -1;
+		Window testWindow;
 
 		Application.Iteration += (s, a) => {
 			iterations++;
 			if (iterations == 0) {
-				((FakeDriver)Application.Driver).SetBufferSize (40, 15);
-				MessageBox.Query ("", "Hello Word", "Ok");
+				((FakeDriver)Application.Driver).SetBufferSize (15, 7);
+				// Don't use MessageBox here; it's too complicated for this unit test; just use Window
+				testWindow = new Window () {
+					Text = "Hello",
+					X = 2,
+					Y = 2,
+					Width = 10,
+					Height = 3
+				};
+				Application.Run (testWindow);
 
 			} else if (iterations == 1) {
 				TestHelpers.AssertDriverContentsWithFrameAre (@$"
-┌──────────────────────────────────────┐
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│       ┌──────────────────────┐       │
-│       │      Hello Word      │       │
-│       │                      │       │
-│       │       {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}       │       │
-│       └──────────────────────┘       │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-└──────────────────────────────────────┘
+┌─────────────┐
+│             │
+│ ┌────────┐  │
+│ │Hello   │  │
+│ └────────┘  │
+│             │
+└─────────────┘
 ", output);
 			} else if (iterations == 2) {
 				Assert.Null (Application.MouseGrabView);
 				// Grab the mouse
 				Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-					X = 8,
-					Y = 5,
+					X = 3,
+					Y = 2,
 					Flags = MouseFlags.Button1Pressed
 				}));
 
 				Assert.Equal (Application.Current, Application.MouseGrabView);
-				Assert.Equal (new Rect (8, 5, 24, 5), Application.MouseGrabView.Frame);
+				Assert.Equal (new Rect (2, 2, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 3) {
 				Assert.Equal (Application.Current, Application.MouseGrabView);
 				// Drag to left
 				Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-					X = 7,
-					Y = 5,
+					X = 2,
+					Y = 2,
 					Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
 				}));
+				Application.Refresh ();
 
 				Assert.Equal (Application.Current, Application.MouseGrabView);
-				Assert.Equal (new Rect (7, 5, 24, 5), Application.MouseGrabView.Frame);
+				Assert.Equal (new Rect (1, 2, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 4) {
 				Assert.Equal (Application.Current, Application.MouseGrabView);
 
 				TestHelpers.AssertDriverContentsWithFrameAre (@$"
-┌──────────────────────────────────────┐
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│      ┌──────────────────────┐        │
-│      │      Hello Word      │        │
-│      │                      │        │
-│      │       {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}       │        │
-│      └──────────────────────┘        │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-└──────────────────────────────────────┘", output);
+┌─────────────┐
+│             │
+│┌────────┐   │
+││Hello   │   │
+│└────────┘   │
+│             │
+└─────────────┘", output);
 
 				Assert.Equal (Application.Current, Application.MouseGrabView);
 			} else if (iterations == 5) {
 				Assert.Equal (Application.Current, Application.MouseGrabView);
 				// Drag up
 				Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-					X = 7,
-					Y = 4,
+					X = 2,
+					Y = 1,
 					Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
 				}));
+				Application.Refresh ();
 
 				Assert.Equal (Application.Current, Application.MouseGrabView);
-				Assert.Equal (new Rect (7, 4, 24, 5), Application.MouseGrabView.Frame);
+				Assert.Equal (new Rect (1, 1, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 6) {
 				Assert.Equal (Application.Current, Application.MouseGrabView);
 
 				TestHelpers.AssertDriverContentsWithFrameAre (@$"
-┌──────────────────────────────────────┐
-│                                      │
-│                                      │
-│                                      │
-│      ┌──────────────────────┐        │
-│      │      Hello Word      │        │
-│      │                      │        │
-│      │       {CM.Glyphs.LeftBracket}{CM.Glyphs.LeftDefaultIndicator} Ok {CM.Glyphs.RightDefaultIndicator}{CM.Glyphs.RightBracket}       │        │
-│      └──────────────────────┘        │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-│                                      │
-└──────────────────────────────────────┘", output);
+┌─────────────┐
+│┌────────┐   │
+││Hello   │   │
+│└────────┘   │
+│             │
+│             │
+└─────────────┘", output);
 
 				Assert.Equal (Application.Current, Application.MouseGrabView);
-				Assert.Equal (new Rect (7, 4, 24, 5), Application.MouseGrabView.Frame);
+				Assert.Equal (new Rect (1, 1, 10, 3), Application.MouseGrabView.Frame);
 
 			} else if (iterations == 7) {
 				Assert.Equal (Application.Current, Application.MouseGrabView);
 				// Ungrab the mouse
 				Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-					X = 7,
-					Y = 4,
+					X = 2,
+					Y = 1,
 					Flags = MouseFlags.Button1Released
 				}));
+				Application.Refresh ();
 
 				Assert.Null (Application.MouseGrabView);
 
@@ -833,7 +822,8 @@ public class ToplevelTests {
 		Application.Run ();
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Mouse_Drag_On_Top_With_Superview_Not_Null ()
 	{
 		var win = new Window () {
@@ -928,7 +918,8 @@ public class ToplevelTests {
 		Application.Run ();
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void GetLocationThatFits_With_Border_Null_Not_Throws ()
 	{
 		var top = new Toplevel ();
@@ -941,7 +932,8 @@ public class ToplevelTests {
 		Assert.Null (exception);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void OnEnter_OnLeave_Triggered_On_Application_Begin_End ()
 	{
 		bool isEnter = false;
@@ -979,7 +971,8 @@ public class ToplevelTests {
 		Assert.True (v.HasFocus);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void OnEnter_OnLeave_Triggered_On_Application_Begin_End_With_More_Toplevels ()
 	{
 		int iterations = 0;
@@ -1069,7 +1062,8 @@ public class ToplevelTests {
 		Assert.Equal (5, steps [^1]);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void PositionCursor_SetCursorVisibility_To_Invisible_If_Focused_Is_Null ()
 	{
 		var tf = new TextField ("test") { Width = 5 };
@@ -1089,7 +1083,8 @@ public class ToplevelTests {
 		Assert.Equal (CursorVisibility.Invisible, cursor);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void IsLoaded_Application_Begin ()
 	{
 		var top = Application.Top;
@@ -1099,7 +1094,8 @@ public class ToplevelTests {
 		Assert.True (top.IsLoaded);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void IsLoaded_With_Sub_Toplevel_Application_Begin_NeedDisplay ()
 	{
 		var top = Application.Top;
@@ -1137,7 +1133,8 @@ public class ToplevelTests {
 	}
 
 	// BUGBUG: Broke this test with #2483 - @bdisp I need your help figuring out why
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Toplevel_Inside_ScrollView_MouseGrabView ()
 	{
 		var scrollView = new ScrollView () {
@@ -1254,32 +1251,33 @@ public class ToplevelTests {
 		Assert.Equal (scrollView, Application.MouseGrabView);
 	}
 
-	[Fact] [AutoInitShutdown]
-	public void Dialog_Bounds_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_Left_Right_And_Bottom ()
+	[Fact]
+	[AutoInitShutdown]
+	public void Window_Bounds_Bigger_Than_Driver_Cols_And_Rows_Allow_Drag_Beyond_Left_Right_And_Bottom ()
 	{
 		var top = Application.Top;
-		var dialog = new Dialog (new Button ("Ok")) { Width = 20, Height = 3 };
+		var window = new Window () { Width = 20, Height = 3 };
 		Application.Begin (top);
 		((FakeDriver)Application.Driver).SetBufferSize (40, 10);
-		Application.Begin (dialog);
+		Application.Begin (window);
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 40, 10), top.Frame);
-		Assert.Equal (new Rect (10, 3, 20, 3), dialog.Frame);
+		Assert.Equal (new Rect (0, 0, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@$"
-          ┌──────────────────┐
-                {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}
-          └──────────────────┘
+┌──────────────────┐
+
+└──────────────────┘
 ", output);
 
 		Assert.Null (Application.MouseGrabView);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-			X = 10,
-			Y = 3,
+			X = 0,
+			Y = 0,
 			Flags = MouseFlags.Button1Pressed
 		}));
 
-		Assert.Equal (dialog, Application.MouseGrabView);
+		Assert.Equal (window, Application.MouseGrabView);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
 			X = -11,
@@ -1289,10 +1287,10 @@ public class ToplevelTests {
 
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 40, 10), top.Frame);
-		Assert.Equal (new Rect (0, 0, 20, 3), dialog.Frame);
+		Assert.Equal (new Rect (0, 0, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ┌──────────────────┐
-│      {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}
+│            
 └──────────────────┘
 ", output);
 
@@ -1306,10 +1304,10 @@ public class ToplevelTests {
 
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 20, 3), top.Frame);
-		Assert.Equal (new Rect (0, 0, 20, 3), dialog.Frame);
+		Assert.Equal (new Rect (0, 0, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ┌──────────────────┐
-│      {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}
+│            
 └──────────────────┘
 ", output);
 
@@ -1323,10 +1321,10 @@ public class ToplevelTests {
 
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 19, 2), top.Frame);
-		Assert.Equal (new Rect (-1, 0, 20, 3), dialog.Frame);
+		Assert.Equal (new Rect (-1, 0, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ──────────────────┐
-      {CM.Glyphs.LeftBracket} Ok {CM.Glyphs.RightBracket}
+            
 ", output);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
@@ -1337,7 +1335,7 @@ public class ToplevelTests {
 
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 19, 2), top.Frame);
-		Assert.Equal (new Rect (18, 1, 20, 3), dialog.Frame);
+		Assert.Equal (new Rect (18, 1, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
                   ┌", output);
 
@@ -1350,17 +1348,18 @@ public class ToplevelTests {
 
 		Application.Refresh ();
 		Assert.Equal (new Rect (0, 0, 19, 2), top.Frame);
-		Assert.Equal (new Rect (19, 2, 20, 3), dialog.Frame);
+		Assert.Equal (new Rect (19, 2, 20, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"", output);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Modal_As_Top_Will_Drag_Cleanly ()
 	{
-		var dialog = new Dialog () { Width = 30, Height = 10 };
-		dialog.Add (new Label (
-			"How should I've to react. Cleaning all chunk trails or setting the 'Cols' and 'Rows' to this dialog length?\n" +
-			"Cleaning is more easy to fix this.") {
+		// Don't use Dialog as a Top, use a Window instead - dialog has complex layout behavior that is not needed here.
+		var window = new Window () { Width = 10, Height = 3};
+		window.Add (new Label (
+			"Test") {
 			X = Pos.Center (),
 			Y = Pos.Center (),
 			Width = Dim.Fill (),
@@ -1370,73 +1369,53 @@ public class ToplevelTests {
 			AutoSize = false
 		});
 
-		var rs = Application.Begin (dialog);
+		var rs = Application.Begin (window);
 
 		Assert.Null (Application.MouseGrabView);
-		Assert.Equal (new Rect (25, 7, 30, 10), dialog.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
-                         ┌────────────────────────────┐
-                         │ How should I've to react.  │
-                         │Cleaning all chunk trails or│
-                         │   setting the 'Cols' and   │
-                         │   'Rows' to this dialog    │
-                         │          length?           │
-                         │Cleaning is more easy to fix│
-                         │           this.            │
-                         │                            │
-                         └────────────────────────────┘", output);
+┌────────┐
+│  Test  │
+└────────┘", output);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-			X = 25,
-			Y = 7,
+			X = 0,
+			Y = 0,
 			Flags = MouseFlags.Button1Pressed
 		}));
 
 		bool firstIteration = false;
 		Application.RunIteration (ref rs, ref firstIteration);
-		Assert.Equal (dialog, Application.MouseGrabView);
+		Assert.Equal (window, Application.MouseGrabView);
 
-		Assert.Equal (new Rect (25, 7, 30, 10), dialog.Frame);
+		Assert.Equal (new Rect (0, 0, 10, 3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
-                         ┌────────────────────────────┐
-                         │ How should I've to react.  │
-                         │Cleaning all chunk trails or│
-                         │   setting the 'Cols' and   │
-                         │   'Rows' to this dialog    │
-                         │          length?           │
-                         │Cleaning is more easy to fix│
-                         │           this.            │
-                         │                            │
-                         └────────────────────────────┘", output);
+┌────────┐
+│  Test  │
+└────────┘", output);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-			X = 20,
-			Y = 10,
+			X = 1,
+			Y = 1,
 			Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
 		}));
 
 		firstIteration = false;
 		Application.RunIteration (ref rs, ref firstIteration);
-		Assert.Equal (dialog, Application.MouseGrabView);
-		Assert.Equal (new Rect (20, 10, 30, 10), dialog.Frame);
+		Assert.Equal (window, Application.MouseGrabView);
+		Assert.Equal (new Rect (1, 1, 10,3), window.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@"
-                    ┌────────────────────────────┐
-                    │ How should I've to react.  │
-                    │Cleaning all chunk trails or│
-                    │   setting the 'Cols' and   │
-                    │   'Rows' to this dialog    │
-                    │          length?           │
-                    │Cleaning is more easy to fix│
-                    │           this.            │
-                    │                            │
-                    └────────────────────────────┘", output);
+ ┌────────┐
+ │  Test  │
+ └────────┘", output);
 
 		Application.End (rs);
 	}
 
-	// BUGBUG: Broke this test with #2483 - @bdisp I need your help figuring out why
-	[Fact] [AutoInitShutdown]
-	public void Draw_A_Top_Subview_On_A_Dialog ()
+	// Don't use Dialog as a Top, use a Window instead - dialog has complex layout behavior that is not needed here.
+	[Fact]
+	[AutoInitShutdown]
+	public void Draw_A_Top_Subview_On_A_Window ()
 	{
 		var top = Application.Top;
 		var win = new Window ();
@@ -1468,25 +1447,29 @@ public class ToplevelTests {
 └──────────────────┘", output);
 
 		var btnPopup = new Button ("Popup");
+		var testWindow = new Window () { X = 2, Y = 1, Width = 15, Height = 10 };
+		testWindow.Add (btnPopup);
 		btnPopup.Clicked += (s, e) => {
 			var viewToScreen = btnPopup.BoundsToScreen (top.Frame);
-			var view = new View () {
+			var viewAddedToTop = new View () {
+				Text = "viewAddedToTop",
 				X = 1,
 				Y = viewToScreen.Y + 1,
 				Width = 18,
-				Height = 5,
+				Height = 16,
 				BorderStyle = LineStyle.Single
 			};
-			Application.Current.DrawContentComplete += Current_DrawContentComplete;
-			top.Add (view);
+			Assert.Equal (testWindow, Application.Current);
+			Application.Current.DrawContentComplete += testWindow_DrawContentComplete;
+			top.Add (viewAddedToTop);
 
-			void Current_DrawContentComplete (object sender, DrawEventArgs e)
+			void testWindow_DrawContentComplete (object sender, DrawEventArgs e)
 			{
-				Assert.Equal (new Rect (1, 14, 18, 5), view.Frame);
+				Assert.Equal (new Rect (1, 3, 18, 16), viewAddedToTop.Frame);
 
 				var savedClip = Application.Driver.Clip;
 				Application.Driver.Clip = top.Frame;
-				view.Draw ();
+				viewAddedToTop.Draw ();
 				top.Move (2, 15);
 				View.Driver.AddStr ("One");
 				top.Move (2, 16);
@@ -1495,20 +1478,16 @@ public class ToplevelTests {
 				View.Driver.AddStr ("Three");
 				Application.Driver.Clip = savedClip;
 
-				Application.Current.DrawContentComplete -= Current_DrawContentComplete;
+				Application.Current.DrawContentComplete -= testWindow_DrawContentComplete;
 			}
 		};
-		var dialog = new Dialog (btnPopup) { Width = 15, Height = 10 };
-		var rs = Application.Begin (dialog);
+		var rs = Application.Begin (testWindow);
 
-		Assert.Equal (new Rect (2, 5, 15, 10), dialog.Frame);
+		Assert.Equal (new Rect (2, 1, 15, 10), testWindow.Frame);
 		TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ┌──────────────────┐
-│                  │
-│                  │
-│                  │
-│                  │
 │ ┌─────────────┐  │
+│ │{CM.Glyphs.LeftBracket} Popup {CM.Glyphs.RightBracket}    │  │
 │ │             │  │
 │ │             │  │
 │ │             │  │
@@ -1516,38 +1495,42 @@ public class ToplevelTests {
 │ │             │  │
 │ │             │  │
 │ │             │  │
-│ │  {CM.Glyphs.LeftBracket} Popup {CM.Glyphs.RightBracket}  │  │
 │ └─────────────┘  │
 │                  │
 │                  │
 │                  │
 │                  │
+│                  │
+│                  │
+│                  │
+│                  │
 └──────────────────┘", output);
 
 		Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () {
-			X = 9,
-			Y = 13,
+			X = 5,
+			Y = 2,
 			Flags = MouseFlags.Button1Clicked
 		}));
+		Application.Top.Draw ();
 
 		bool firstIteration = false;
 		Application.RunIteration (ref rs, ref firstIteration);
 		TestHelpers.AssertDriverContentsWithFrameAre (@$"
 ┌──────────────────┐
-│                  │
-│                  │
-│                  │
-│                  │
 │ ┌─────────────┐  │
-│ │             │  │
-│ │             │  │
-│ │             │  │
-│ │             │  │
-│ │             │  │
-│ │             │  │
-│ │             │  │
-│ │  {CM.Glyphs.LeftBracket} Popup {CM.Glyphs.RightBracket}  │  │
+│ │{CM.Glyphs.LeftBracket} Popup {CM.Glyphs.RightBracket}    │  │
 │┌────────────────┐│
+││viewAddedToTop  ││
+││                ││
+││                ││
+││                ││
+││                ││
+││                ││
+││                ││
+││                ││
+││                ││
+││                ││
+││                ││
 ││One             ││
 ││Two             ││
 ││Three           ││
@@ -1557,7 +1540,8 @@ public class ToplevelTests {
 		Application.End (rs);
 	}
 
-	[Fact] [AutoInitShutdown]
+	[Fact]
+	[AutoInitShutdown]
 	public void Activating_MenuBar_By_Alt_Key_Does_Not_Throw ()
 	{
 		var menu = new MenuBar (new MenuBarItem [] {
@@ -1575,7 +1559,8 @@ public class ToplevelTests {
 	}
 
 
-	[Fact] [TestRespondersDisposed]
+	[Fact]
+	[TestRespondersDisposed]
 	public void Multi_Thread_Toplevels ()
 	{
 		Application.Init (new FakeDriver ());
@@ -1621,12 +1606,12 @@ public class ToplevelTests {
 			return true;
 		});
 
-		t.Ready += FirstDialogToplevel;
+		t.Ready += FirstWindow;
 
-		void FirstDialogToplevel (object sender, EventArgs args)
+		void FirstWindow (object sender, EventArgs args)
 		{
-			var od = new OpenDialog ();
-			od.Ready += SecondDialogToplevel;
+			var firstWindow = new Window ();
+			firstWindow.Ready += SecondWindow;
 
 			Application.AddTimeout (TimeSpan.FromMilliseconds (100), () => {
 				count1++;
@@ -1644,12 +1629,12 @@ public class ToplevelTests {
 				return true;
 			});
 
-			Application.Run (od);
+			Application.Run (firstWindow);
 		}
 
-		void SecondDialogToplevel (object sender, EventArgs args)
+		void SecondWindow (object sender, EventArgs args)
 		{
-			var d = new Dialog ();
+			var testWindow = new Window ();
 
 			Application.AddTimeout (TimeSpan.FromMilliseconds (100), () => {
 				count2++;
@@ -1664,7 +1649,7 @@ public class ToplevelTests {
 				return true;
 			});
 
-			Application.Run (d);
+			Application.Run (testWindow);
 		}
 
 		Application.Run ();

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