Browse Source

Initial work in progress

Tig Kindel 1 year ago
parent
commit
1f42e4ceeb

+ 526 - 586
Terminal.Gui/View/Layout/PosDim.cs

@@ -6,695 +6,635 @@
 //
 
 using System;
-namespace Terminal.Gui {
+
+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>
+///     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) => 0;
+
+	// Helper class to provide dynamic value by the execution of a function that returns an integer.
+	internal class PosFunc : Pos {
+		Func<int> function;
+
+		public PosFunc (Func<int> n) => function = n;
+
+		internal override int Anchor (int width) => function ();
+
+		public override string ToString () => $"PosFunc({function ()})";
+
+		public override int GetHashCode () => function.GetHashCode ();
+
+		public override bool Equals (object other) => other is PosFunc f && f.function () == function ();
+	}
+
 	/// <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 "PosFunc" from the specified function.
 	/// </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 {
+		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 ();
-		}
-
-		/// <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);
-		}
+		public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
+	}
 
-		internal class PosFactor : Pos {
-			float 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 < 0 || n > 100) {
+			throw new ArgumentException ("Percent value must be between 0 and 100");
+		}
+
+		return new PosFactor (n / 100);
+	}
 
-			public PosFactor (float n)
-			{
-				this.factor = n;
-			}
+	internal class PosAnchorEnd : Pos {
+		int n;
 
-			internal override int Anchor (int width)
-			{
-				return (int)(width * factor);
-			}
+		public PosAnchorEnd (int n) => this.n = n;
 
-			public override string ToString ()
-			{
-				return $"Factor({factor})";
-			}
+		internal override int Anchor (int width) => width - n;
 
-			public override int GetHashCode () => factor.GetHashCode ();
+		public override string ToString () => $"AnchorEnd({n})";
 
-			public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
-		}
+		public override int GetHashCode () => n.GetHashCode ();
 
-		/// <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");
+		public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd.n == n;
+	}
 
-			return new PosFactor (n / 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="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");
+		}
+
+		return new PosAnchorEnd (margin);
+	}
 
-		internal class PosAnchorEnd : Pos {
-			int n;
+	internal class PosCenter : Pos {
+		internal override int Anchor (int width) => width / 2;
 
-			public PosAnchorEnd (int n)
-			{
-				this.n = n;
-			}
+		public override string ToString () => "Center";
+	}
 
-			internal override int Anchor (int width)
-			{
-				return width - n;
-			}
+	/// <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 () => new PosCenter ();
+
+	internal class PosAbsolute : Pos {
+		int n;
+		public PosAbsolute (int n) => this.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;
+	}
 
-			public override string ToString ()
-			{
-				return $"AnchorEnd({n})";
-			}
+	/// <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);
 
-			public override int GetHashCode () => n.GetHashCode ();
+	/// <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) => new PosAbsolute (n);
 
-			public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd.n == n;
-		}
+	internal class PosCombine : Pos {
+		internal Pos left, right;
+		internal bool add;
 
-		/// <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)
+		public PosCombine (bool add, Pos left, Pos right)
 		{
-			if (margin < 0)
-				throw new ArgumentException ("Margin must be positive");
-
-			return new PosAnchorEnd (margin);
+			this.left = left;
+			this.right = right;
+			this.add = add;
 		}
 
-		internal class PosCenter : Pos {
-			internal override int Anchor (int width)
-			{
-				return width / 2;
-			}
-
-			public override string ToString ()
-			{
-				return "Center";
+		internal override int Anchor (int width)
+		{
+			int la = left.Anchor (width);
+			int ra = right.Anchor (width);
+			if (add) {
+				return la + ra;
+			} else {
+				return la - ra;
 			}
 		}
 
-		/// <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 ()
-		{
-			return new PosCenter ();
-		}
+		public override string ToString () => $"Combine({left}{(add ? '+' : '-')}{right})";
+	}
 
-		internal class PosAbsolute : Pos {
-			int n;
-			public PosAbsolute (int n) { this.n = n; }
+	/// <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 $"Absolute({n})";
-			}
+	/// <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;
+	}
 
-			internal override int Anchor (int width)
-			{
-				return n;
-			}
+	static void SetPosCombine (Pos left, PosCombine newPos)
+	{
+		var view = left as PosView;
+		if (view != null) {
+			view.Target.SetNeedsLayout ();
+		}
+	}
 
-			public override int GetHashCode () => n.GetHashCode ();
+	internal class PosView : Pos {
+		public View Target;
+		int side;
 
-			public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
+		public PosView (View view, int side)
+		{
+			Target = view;
+			this.side = side;
 		}
 
-		/// <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);
+			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;
+			}
 		}
 
-		/// <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)
+		public override string ToString ()
 		{
-			return new PosAbsolute (n);
-		}
+			string tside;
+			switch (side) {
+			case 0:
+				tside = "x";
+				break;
+			case 1:
+				tside = "y";
+				break;
+			case 2:
+				tside = "right";
+				break;
+			case 3:
+				tside = "bottom";
+				break;
+			default:
+				tside = "unknown";
+				break;
+			}
+			// Note: We do not checkt `Target` for null here to intentionally throw if so
+			return $"View({tside},{Target.ToString ()})";
+		}
+
+		public override int GetHashCode () => Target.GetHashCode ();
+
+		public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
+	}
 
-		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>
+	/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 0), new PosAbsolute (0));
 
-			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>
+	/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 0), new PosAbsolute (0));
 
-			public override string ToString ()
-			{
-				return $"Combine({left}{(add ? '+' : '-')}{right})";
-			}
+	/// <summary>
+	/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 1), new PosAbsolute (0));
 
-		}
+	/// <summary>
+	/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 1), new PosAbsolute (0));
 
-		/// <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;
-		}
+	/// <summary>
+	/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 2), new PosAbsolute (0));
 
-		/// <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));
-			}
-			PosCombine newPos = new PosCombine (false, left, right);
-			SetPosCombine (left, newPos);
-			return newPos;
-		}
+	/// <summary>
+	/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 3), new PosAbsolute (0));
+
+	/// <summary>Serves as the default hash function. </summary>
+	/// <returns>A hash code for the current object.</returns>
+	public override int GetHashCode () => Anchor (0).GetHashCode ();
+
+	/// <summary>Determines whether the specified object is equal to the current object.</summary>
+	/// <param name="other">The object to compare with the current object. </param>
+	/// <returns>
+	///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
+	public override bool Equals (object other) => other is Pos abs && abs == this;
+}
 
-		static void SetPosCombine (Pos left, PosCombine newPos)
-		{
-			var view = left as PosView;
-			if (view != null) {
-				view.Target.SetNeedsLayout ();
-			}
-		}
+/// <summary>
+/// Dim properties of a <see cref="View"/> to control the dimensions.
+/// </summary>
+/// <remarks>
+///   <para>
+///     Use the Dim objects on the Width or Height properties of a <see cref="View"/> to control the dimensions.
+///   </para>
+///   <para>
+///   </para>
+/// </remarks>
+public class Dim {
+	internal virtual int Anchor (int width) => 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;
-				}
-			}
+	// Helper class to provide dynamic value by the execution of a function that returns an integer.
+	internal class DimFunc : Dim {
+		Func<int> function;
 
-			public override string ToString ()
-			{
-				string tside;
-				switch (side) {
-				case 0: tside = "x"; break;
-				case 1: tside = "y"; break;
-				case 2: tside = "right"; break;
-				case 3: tside = "bottom"; break;
-				default: tside = "unknown"; break;
-				}
-				// Note: We do not checkt `Target` for null here to intentionally throw if so
-				return $"View({tside},{Target.ToString ()})";
-			}
+		public DimFunc (Func<int> n) => function = n;
 
-			public override int GetHashCode () => Target.GetHashCode ();
+		internal override int Anchor (int width) => function ();
 
-			public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
-		}
+		public override string ToString () => $"DimFunc({function ()})";
+
+		public override int GetHashCode () => function.GetHashCode ();
 
-		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 0), new Pos.PosAbsolute (0));
-
-		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 0), new Pos.PosAbsolute (0));
-
-		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 1), new Pos.PosAbsolute (0));
-
-		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 1), new Pos.PosAbsolute (0));
-
-		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 2), new Pos.PosAbsolute (0));
-
-		/// <summary>
-		/// Returns a <see cref="Pos"/> object 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 PosCombine (true, new PosView (view, 3), new Pos.PosAbsolute (0));
-
-		/// <summary>Serves as the default hash function. </summary>
-		/// <returns>A hash code for the current object.</returns>
-		public override int GetHashCode () => Anchor (0).GetHashCode ();
-
-		/// <summary>Determines whether the specified object is equal to the current object.</summary>
-		/// <param name="other">The object to compare with the current object. </param>
-		/// <returns>
-		///     <see langword="true" /> if the specified object  is equal to the current object; otherwise, <see langword="false" />.</returns>
-		public override bool Equals (object other) => other is Pos abs && abs == this;
+		public override bool Equals (object other) => other is DimFunc f && f.function () == function ();
 	}
 
 	/// <summary>
-	/// Dim properties of a <see cref="View"/> to control the position.
+	/// Creates a "DimFunc" from the specified function.
 	/// </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)
+	/// <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);
+
+	internal class DimFactor : Dim {
+		float factor;
+		bool remaining;
+
+		public DimFactor (float n, bool r = false)
 		{
-			return 0;
+			factor = n;
+			remaining = r;
 		}
 
-		// Helper class to provide dynamic value by the execution of a function that returns an integer.
-		internal class DimFunc : Dim {
-			Func<int> function;
+		internal override int Anchor (int width) => (int)(width * factor);
 
-			public DimFunc (Func<int> n)
-			{
-				this.function = n;
-			}
+		public bool IsFromRemaining () => remaining;
 
-			internal override int Anchor (int width)
-			{
-				return function ();
-			}
+		public override string ToString () => $"Factor({factor},{remaining})";
 
-			public override string ToString ()
-			{
-				return $"DimFunc({function ()})";
-			}
+		public override int GetHashCode () => factor.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 DimFactor f && f.factor == factor && f.remaining == remaining;
+	}
 
-		/// <summary>
-		/// Creates a "DimFunc" from the specified function.
-		/// </summary>
-		/// <param name="function">The function to be executed.</param>
-		/// <returns>The <see cref="Dim"/> returned from the function.</returns>
-		public static Dim Function (Func<int> function)
-		{
-			return new DimFunc (function);
-		}
+	/// <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 {
-			float factor;
-			bool remaining;
+	internal class DimAbsolute : Dim {
+		readonly int _n;
+		public DimAbsolute (int n) => _n = n;
 
-			public DimFactor (float n, bool r = false)
-			{
-				factor = n;
-				remaining = r;
-			}
+		public override string ToString () => $"Absolute({_n})";
 
-			internal override int Anchor (int width)
-			{
-				return (int)(width * factor);
-			}
+		internal override int Anchor (int width) => _n;
 
-			public bool IsFromRemaining ()
-			{
-				return remaining;
-			}
+		public override int GetHashCode () => _n.GetHashCode ();
 
-			public override string ToString ()
-			{
-				return $"Factor({factor},{remaining})";
-			}
+		public override bool Equals (object other) => other is DimAbsolute abs && abs._n == _n;
+	}
 
-			public override int GetHashCode () => factor.GetHashCode ();
+	internal class DimFill : Dim {
+		readonly int _margin;
+		public DimFill (int margin) => _margin = margin;
 
-			public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
-		}
+		public override string ToString () => $"Fill({_margin})";
 
-		/// <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");
+		internal override int Anchor (int width) => width - _margin;
 
-			return new DimFactor (n / 100, r);
-		}
+		public override int GetHashCode () => _margin.GetHashCode ();
 
-		internal class DimAbsolute : Dim {
-			int n;
-			public DimAbsolute (int n) { this.n = n; }
+		public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin;
+	}
 
-			public override string ToString ()
-			{
-				return $"Absolute({n})";
-			}
+	/// <summary>
+	/// Initializes a new instance of the <see cref="Dim"/> class that fills the dimension, but leaves 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);
 
-			internal override int Anchor (int width)
-			{
-				return n;
-			}
+	internal class DimAutoSize : Dim {
+		readonly int _margin;
+		public DimAutoSize (int margin)
+		{
+			_margin = margin;
+		}
 
-			public override int GetHashCode () => n.GetHashCode ();
+		public override string ToString () => $"AutoSize({_margin})";
 
-			public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
+		internal override int Anchor (int width)
+		{
+			return 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 DimAutoSize autoSize && autoSize._margin == _margin;
+	}
 
-			internal override int Anchor (int width)
-			{
-				return width - margin;
-			}
+	/// <summary>
+	/// Creates an AutoSize <see cref="Dim"/> object that is the size required to fit all of the view's SubViews.
+	/// </summary>
+	/// <returns>The AutoSize <see cref="Dim"/> object.</returns>
+	/// <param name="margin">Margin to use.</param>
+	/// <example>
+	/// This initializes a <see cref="View"/> with two SubViews. The view will be automatically sized to fit the two SubViews.
+	/// <code>
+	/// var button = new Button () { Text = "Click Me!"; X = 1, Y = 1, Width = 10, Height = 1 };
+	/// var textField = new TextField { Text = "Type here", X = 1, Y = 2, Width = 20, Height = 1 };
+	/// var view = new Window () { Title = "MyWindow", X = 0, Y = 0, Width = Dim.AutoSize (), Height = Dim.AutoSize () };
+	/// view.Add (button, textField);
+	/// </code>
+	/// </example>
+	public static Dim AutoSize (int margin = 0)
+	{
+		return new DimAutoSize (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 ();
-			}
+			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 int GetHashCode () => Target.GetHashCode ();
 
-			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 bool Equals (object other) => other is DimView abs && abs.Target == Target;
+	}
 
-			public override int GetHashCode () => Target.GetHashCode ();
+	/// <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);
 
-			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>
-		/// 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>
+	/// 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;
+}

+ 27 - 14
Terminal.Gui/Text/ViewLayout.cs → Terminal.Gui/View/ViewLayout.cs

@@ -652,7 +652,7 @@ namespace Terminal.Gui {
 			//   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 newLocation, int newDimension) GetNewLocationAndDimension (bool horiz, int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension)
 			{
 				int newDimension, newLocation;
 
@@ -669,14 +669,14 @@ namespace Terminal.Gui {
 
 				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);
+					(left, newDimension) = GetNewLocationAndDimension (horiz, superviewLocation, superviewDimension, combine.left, dim, autosizeDimension);
+					(right, newDimension) = GetNewLocationAndDimension (horiz, 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);
+					newDimension = Math.Max (CalculateNewDimension (horiz, dim, newLocation, superviewDimension, autosizeDimension), 0);
 					break;
 
 				case Pos.PosAbsolute:
@@ -686,7 +686,7 @@ namespace Terminal.Gui {
 				case Pos.PosView:
 				default:
 					newLocation = pos?.Anchor (superviewDimension) ?? 0;
-					newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+					newDimension = Math.Max (CalculateNewDimension (horiz, dim, newLocation, superviewDimension, autosizeDimension), 0);
 					break;
 				}
 				return (newLocation, newDimension);
@@ -695,7 +695,7 @@ namespace Terminal.Gui {
 			// 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 CalculateNewDimension (bool horiz, Dim d, int location, int dimension, int autosize)
 			{
 				int newDimension;
 				switch (d) {
@@ -703,20 +703,33 @@ namespace Terminal.Gui {
 					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) {
+					int leftNewDim = CalculateNewDimension (horiz, combine._left, location, dimension, autosize);
+					int rightNewDim = CalculateNewDimension (horiz, 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.DimAutoSize:
+					var thickness = GetFramesThickness ();
+					if (horiz) {
+						var furthestLeft = Subviews.Where (v => v.Frame.X >= 0).Min (v => v.Frame.X);
+						var furthestRight = Subviews.Where (v => v.Frame.X >= 0).Max (v => v.Frame.X + v.Frame.Width);
+						newDimension = furthestLeft + furthestRight + thickness.Left + thickness.Right;
+					} else {
+						var furthestTop = Subviews.Where (v => v.Frame.Y >= 0).Min (v => v.Frame.Y);
+						var furthestBottom = Subviews.Where (v => v.Frame.Y >= 0).Max (v => v.Frame.Y + v.Frame.Height);
+						newDimension = furthestTop + furthestBottom + thickness.Top + thickness.Bottom;
+					}
+					break;
 
 				case Dim.DimFill:
 				default:
@@ -729,10 +742,10 @@ namespace Terminal.Gui {
 			}
 
 			// horizontal
-			(newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
+			(newX, newW) = GetNewLocationAndDimension (true, superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
 
 			// vertical
-			(newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
+			(newY, newH) = GetNewLocationAndDimension (false, superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
 
 			var r = new Rect (newX, newY, newW, newH);
 			if (Frame != r) {
@@ -815,8 +828,8 @@ namespace Terminal.Gui {
 				}
 				return;
 			case Dim.DimCombine dc:
-				CollectDim (dc.left, from, ref nNodes, ref nEdges);
-				CollectDim (dc.right, from, ref nNodes, ref nEdges);
+				CollectDim (dc._left, from, ref nNodes, ref nEdges);
+				CollectDim (dc._right, from, ref nNodes, ref nEdges);
 				break;
 			}
 		}

+ 67 - 0
UICatalog/Scenarios/DimAutoSize.cs

@@ -0,0 +1,67 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("DimAutoSize", "Demonstrates Dim.AutoSize")]
+[ScenarioCategory ("Layout")]
+public class DimAutoSize : Scenario {
+	public override void Init ()
+	{
+		// The base `Scenario.Init` implementation:
+		//  - Calls `Application.Init ()`
+		//  - Adds a full-screen Window to Application.Top with a title
+		//    that reads "Press <hotkey> to Quit". Access this Window with `this.Win`.
+		//  - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`.
+		// To override this, implement an override of `Init`.
+
+		//base.Init ();
+
+		// A common, alternate, implementation where `this.Win` is not used is below. This code
+		// leverages ConfigurationManager to borrow the color scheme settings from UICatalog:
+
+		Application.Init ();
+		ConfigurationManager.Themes.Theme = Theme;
+		ConfigurationManager.Apply ();
+		Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+	}
+
+	public override void Setup ()
+	{
+		// Put scenario code here (in a real app, this would be the code
+		// that would setup the app before `Application.Run` is called`).
+		// With a Scenario, after UI Catalog calls `Scenario.Setup` it calls
+		// `Scenario.Run` which calls `Application.Run`. Example:
+
+		var textField = new TextField { Text = "Type here", X = 1, Y = 0, Width = 20, Height = 1 };
+
+		var label = new Label {
+			X = Pos.Left (textField),
+			Y = Pos.Bottom (textField),
+			AutoSize = true,
+		};
+
+		textField.TextChanged += (s, e) => {
+			label.Text = textField.Text;
+		};
+
+		var button = new Button () { Text = "Press to make button move down.", 
+			X = Pos.Center(), 
+			Y = Pos.Bottom (label), 
+		};
+		button.Clicked += (s, e) => {
+			button.Y = button.Frame.Y + 1;
+		};
+
+		var view = new FrameView () {
+			Title = "Type in the TextField to make it grow.",
+			X = 3,
+			Y = 3,
+			Width = Dim.AutoSize (),
+			Height = Dim.AutoSize ()
+		};
+
+		view.Add (textField, label, button);
+
+		Application.Top.Add (view);
+	}
+}

+ 2 - 2
UnitTests/View/Layout/DimTests.cs

@@ -1251,8 +1251,8 @@ namespace Terminal.Gui.ViewTests {
 			Assert.Equal (99, dimFill.Anchor (100));
 
 			var dimCombine = new Dim.DimCombine (true, dimFactor, dimAbsolute);
-			Assert.Equal (dimCombine.left, dimFactor);
-			Assert.Equal (dimCombine.right, dimAbsolute);
+			Assert.Equal (dimCombine._left, dimFactor);
+			Assert.Equal (dimCombine._right, dimAbsolute);
 			Assert.Equal (20, dimCombine.Anchor (100));
 
 			var view = new View (new Rect (20, 10, 20, 1));