Browse Source

Merged.
Fixed merge issues. Added more tests.

Tig 1 year ago
parent
commit
ef4c52c6bc

+ 5 - 0
Directory.Build.targets

@@ -0,0 +1,5 @@
+<Project>
+  <PropertyGroup>
+    <DefineConstants>$(DefineConstants);DIMAUTO</DefineConstants>
+  </PropertyGroup>
+</Project>

+ 0 - 1
Terminal.Gui/Directory.Build.props

@@ -7,5 +7,4 @@
     -->
     <Authors>Miguel de Icaza, Charlie Kindel (@tig), @BDisp</Authors>
   </PropertyGroup>
-
 </Project>

+ 180 - 36
Terminal.Gui/View/Layout/PosDim.cs

@@ -1,4 +1,7 @@
-using static Terminal.Gui.Dialog;
+using System.Diagnostics;
+using static System.Net.Mime.MediaTypeNames;
+using static Terminal.Gui.Dialog;
+using static Terminal.Gui.Dim;
 
 namespace Terminal.Gui;
 
@@ -346,7 +349,10 @@ public class Pos
     ///     that
     ///     is used.
     /// </returns>
-    internal virtual int Calculate (int superviewDimension, Dim dim, int autosize, bool autoSize) { return Anchor (superviewDimension); }
+    internal virtual int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension, int autosize, bool autoSize)
+    {
+        return Anchor (superviewDimension);
+    }
 
     internal class PosAbsolute (int n) : Pos
     {
@@ -382,7 +388,7 @@ public class Pos
             return width - _offset;
         }
 
-        internal override int Calculate (int superviewDimension, Dim dim, int autosize, bool autoSize)
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension, int autosize, bool autoSize)
         {
             int newLocation = Anchor (superviewDimension);
 
@@ -400,9 +406,9 @@ public class Pos
         public override string ToString () { return "Center"; }
         internal override int Anchor (int width) { return width / 2; }
 
-        internal override int Calculate (int superviewDimension, Dim dim, int autosize, bool autoSize)
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension, int autosize, bool autoSize)
         {
-            int newDimension = Math.Max (dim.Calculate (0, superviewDimension, autosize, autoSize), 0);
+            int newDimension = Math.Max (dim.Calculate (0, superviewDimension, us, dimension, autosize, autoSize), 0);
 
             return Anchor (superviewDimension - newDimension);
         }
@@ -428,11 +434,11 @@ public class Pos
             return la - ra;
         }
 
-        internal override int Calculate (int superviewDimension, Dim dim, int autosize, bool autoSize)
+        internal override int Calculate (int superviewDimension, Dim dim, View us, Dimension dimension, int autosize, bool autoSize)
         {
-            int newDimension = dim.Calculate (0, superviewDimension, autosize, autoSize);
-            int left = _left.Calculate (superviewDimension, dim, autosize, autoSize);
-            int right = _right.Calculate (superviewDimension, dim, autosize, autoSize);
+            int newDimension = dim.Calculate (0, superviewDimension, us, dimension, autosize, autoSize);
+            int left = _left.Calculate (superviewDimension, dim, us, dimension, autosize, autoSize);
+            int right = _right.Calculate (superviewDimension, dim, us, dimension, autosize, autoSize);
 
             if (_add)
             {
@@ -548,6 +554,15 @@ public class Pos
 ///             </listheader>
 ///             <item>
 ///                 <term>
+///                     <see cref="Dim.Auto"/>
+///                 </term>
+///                 <description>
+///                     Creates a <see cref="Dim"/> object that automatically sizes the view to fit
+///                     the view's SubViews.
+///                 </description>
+///             </item>
+///             <item>
+///                 <term>
 ///                     <see cref="Dim.Function(Func{int})"/>
 ///                 </term>
 ///                 <description>
@@ -597,6 +612,87 @@ public class Pos
 /// </remarks>
 public class Dim
 {
+    /// <summary>
+    ///     Specifies how <see cref="DimAuto"/> will compute the dimension.
+    /// </summary>
+    public enum DimAutoStyle
+    {
+        /// <summary>
+        ///     The dimension will be computed using both the view's <see cref="View.Text"/> and
+        ///     <see cref="View.Subviews"/>.
+        ///     The larger of the corresponding text dimension or Subview in <see cref="View.Subviews"/>
+        ///     with the largest corresponding position plus dimension will determine the dimension.
+        /// </summary>
+        Auto,
+
+        /// <summary>
+        ///     The Subview in <see cref="View.Subviews"/> with the largest corresponding position plus dimension
+        ///     will determine the dimension.
+        ///     The corresponding dimension of the view's <see cref="View.Text"/> will be ignored.
+        /// </summary>
+        Subviews,
+
+        /// <summary>
+        ///     The corresponding dimension of the view's <see cref="View.Text"/>, formatted using the
+        ///     <see cref="View.TextFormatter"/> settings,
+        ///     will be used to determine the dimension.
+        ///     The corresponding dimensions of the <see cref="View.Subviews"/> will be ignored.
+        /// </summary>
+        Text
+    }
+
+
+    /// <summary>
+    /// 
+    /// </summary>
+    public enum Dimension
+    {
+        /// <summary>
+        /// No dimension specified.
+        /// </summary>
+        None = 0,
+
+        /// <summary>
+        /// The height dimension.
+        /// </summary>
+        Height = 1,
+
+        /// <summary>
+        /// The width dimension.
+        /// </summary>
+        Width = 2
+    }
+
+
+    /// <summary>
+    ///     Creates a <see cref="Dim"/> object that automatically sizes the view to fit all of the view's SubViews and/or Text.
+    /// </summary>
+    /// <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>
+    /// <returns>The AutoSize <see cref="Dim"/> object.</returns>
+    /// <param name="style">
+    ///     Specifies how <see cref="DimAuto"/> will compute the dimension. The default is <see cref="DimAutoStyle.Auto"/>.
+    /// </param>
+    /// <param name="min">Specifies the minimum dimension that view will be automatically sized to.</param>
+    /// <param name="max">Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED.</param>
+    public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim min = null, Dim max = null)
+    {
+        if (max != null)
+        {
+            throw new NotImplementedException (@"max is not implemented");
+        }
+
+        return new DimAuto (style, min, max);
+    }
+
     /// <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>
@@ -727,22 +823,22 @@ public class Dim
 
     /// <summary>
     ///     Calculates and returns the dimension of a <see cref="View"/> object. It takes into account the location of the
-    ///     <see cref="View"/>, its current size, and whether it should automatically adjust its size based on its content.
+    ///     <see cref="View"/>, it's SuperView's ContentSize, and whether it should automatically adjust its size based on its content.
     /// </summary>
     /// <param name="location">
     ///     The starting point from where the size calculation begins. It could be the left edge for width calculation or the
     ///     top edge for height calculation.
     /// </param>
-    /// <param name="dimension">The current size of the View. It could be the current width or height.</param>
+    /// <param name="superviewContentSize">The size of the SuperView's content. It could be width or height.</param>
     /// <param name="autosize">Obsolete; To be deprecated.</param>
     /// <param name="autoSize">Obsolete; To be deprecated.</param>
     /// <returns>
     ///     The calculated size of the View. The way this size is calculated depends on the specific subclass of Dim that
     ///     is used.
     /// </returns>
-    internal virtual int Calculate (int location, int dimension, int autosize, bool autoSize)
+    internal virtual int Calculate (int location, int superviewContentSize, View us, Dimension dimension, int autosize, bool autoSize)
     {
-        int newDimension = Math.Max (Anchor (dimension - location), 0);
+        int newDimension = Math.Max (Anchor (superviewContentSize - location), 0);
 
         return autoSize && autosize > newDimension ? autosize : newDimension;
     }
@@ -755,7 +851,7 @@ public class Dim
         public override string ToString () { return $"Absolute({_n})"; }
         internal override int Anchor (int width) { return _n; }
 
-        internal override int Calculate (int location, int dimension, int autosize, bool autoSize)
+        internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension, int autosize, bool autoSize)
         {
             // DimAbsolute.Anchor (int width) ignores width and returns n
             int newDimension = Math.Max (Anchor (0), 0);
@@ -764,6 +860,69 @@ public class Dim
         }
     }
 
+    internal class DimAuto (DimAutoStyle style, Dim min, Dim max) : Dim
+    {
+        internal readonly Dim _max = max;
+        internal readonly Dim _min = min;
+        internal readonly DimAutoStyle _style = style;
+        internal int Size;
+
+        public override bool Equals (object other) { return other is DimAuto auto && auto._min == _min && auto._max == _max && auto._style == _style; }
+        public override int GetHashCode () { return HashCode.Combine (base.GetHashCode (), _min, _max, _style); }
+        public override string ToString () { return $"Auto({_style},{_min},{_max})"; }
+
+        internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension, int autosize, bool autoSize)
+        {
+            if (us == null)
+            {
+                return _max?.Anchor (0) ?? 0;
+            }
+
+            var textSize = 0;
+            var subviewsSize = 0;
+
+            int autoMin = _min?.Anchor (superviewContentSize) ?? 0;
+
+            if (superviewContentSize < autoMin)
+            {
+                Debug.WriteLine ($"WARNING: DimAuto specifies a min size ({autoMin}), but the SuperView's bounds are smaller ({superviewContentSize}).");
+
+                return superviewContentSize;
+            }
+
+            if (_style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto)
+            {
+                textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height);
+            }
+
+            if (_style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto)
+            {
+                subviewsSize = us.Subviews.Count == 0
+                                   ? 0
+                                   : us.Subviews
+                                         .Where (v => dimension == Dimension.Width ? v.X is not Pos.PosAnchorEnd : v.Y is not Pos.PosAnchorEnd)
+                                         .Max (v => dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height);
+            }
+
+            int max = int.Max (textSize, subviewsSize);
+
+            Thickness thickness = us.GetAdornmentsThickness ();
+
+            if (dimension == Dimension.Width)
+            {
+                max += thickness.Horizontal;
+            }
+            else
+            {
+                max += thickness.Vertical;
+            }
+
+            max = int.Max (max, autoMin);
+            return int.Min (max, _max?.Anchor (superviewContentSize) ?? superviewContentSize);
+        }
+
+    }
+
     internal class DimCombine (bool add, Dim left, Dim right) : Dim
     {
         internal bool _add = add;
@@ -784,10 +943,10 @@ public class Dim
             return la - ra;
         }
 
-        internal override int Calculate (int location, int dimension, int autosize, bool autoSize)
+        internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension, int autosize, bool autoSize)
         {
-            int leftNewDim = _left.Calculate (location, dimension, autosize, autoSize);
-            int rightNewDim = _right.Calculate (location, dimension, autosize, autoSize);
+            int leftNewDim = _left.Calculate (location, superviewContentSize, us, dimension, autosize, autoSize);
+            int rightNewDim = _right.Calculate (location, superviewContentSize, us, dimension, autosize, autoSize);
 
             int newDimension;
 
@@ -802,6 +961,7 @@ public class Dim
 
             return autoSize && autosize > newDimension ? autosize : newDimension;
         }
+
     }
 
     internal class DimFactor (float factor, bool remaining = false) : Dim
@@ -815,9 +975,9 @@ public class Dim
         public override string ToString () { return $"Factor({_factor},{_remaining})"; }
         internal override int Anchor (int width) { return (int)(width * _factor); }
 
-        internal override int Calculate (int location, int dimension, int autosize, bool autoSize)
+        internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension, int autosize, bool autoSize)
         {
-            int newDimension = _remaining ? Math.Max (Anchor (dimension - location), 0) : Anchor (dimension);
+            int newDimension = _remaining ? Math.Max (Anchor (superviewContentSize - location), 0) : Anchor (superviewContentSize);
 
             return autoSize && autosize > newDimension ? autosize : newDimension;
         }
@@ -842,22 +1002,6 @@ public class Dim
         internal override int Anchor (int width) { return _function (); }
     }
 
-    /// <summary>
-    /// 
-    /// </summary>
-    public enum Dimension
-    {
-        /// <summary>
-        /// The height dimension.
-        /// </summary>
-        Height = 0,
-
-        /// <summary>
-        /// The width dimension.
-        /// </summary>
-        Width = 1
-    }
-
     internal class DimView : Dim
     {
         private readonly Dimension _side;
@@ -899,4 +1043,4 @@ public class Dim
             };
         }
     }
-}
+}

+ 119 - 35
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -26,9 +26,12 @@ public enum LayoutStyle
 
     /// <summary>
     ///     Indicates one or more of the <see cref="View.X"/>, <see cref="View.Y"/>, <see cref="View.Width"/>, or
-    ///     <see cref="View.Height"/> objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.
-    ///     The position and size of the view will be computed based on these objects at layout time. <see cref="View.Frame"/>
-    ///     will provide the absolute computed values.
+    ///     <see cref="View.Height"/>
+    ///     objects are relative to the <see cref="View.SuperView"/> and are computed at layout time.  The position and size of
+    ///     the
+    ///     view
+    ///     will be computed based on these objects at layout time. <see cref="View.Frame"/> will provide the absolute computed
+    ///     values.
     /// </summary>
     Computed
 }
@@ -397,6 +400,7 @@ public partial class View
         }
     }
 
+
     /// <summary>If <paramref name="autoSize"/> is true, resizes the view.</summary>
     /// <param name="autoSize"></param>
     /// <returns></returns>
@@ -408,7 +412,7 @@ public partial class View
         }
 
         var boundsChanged = true;
-        Size newFrameSize = GetAutoSize ();
+        Size newFrameSize = GetTextAutoSize ();
 
         if (IsInitialized && newFrameSize != Frame.Size)
         {
@@ -856,8 +860,8 @@ public partial class View
     public event EventHandler<LayoutEventArgs> LayoutStarted;
 
     /// <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.
+    ///     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>
     ///     <para>
@@ -870,9 +874,7 @@ public partial class View
     {
         if (!IsInitialized)
         {
-            Debug.WriteLine (
-                             $"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}"
-                            );
+            Debug.WriteLine ($"WARNING: LayoutSubviews called before view has been initialized. This is likely a bug in {this}");
         }
 
         if (!LayoutNeeded)
@@ -880,6 +882,8 @@ public partial class View
             return;
         }
 
+        CheckDimAuto ();
+
         LayoutAdornments ();
 
         OnLayoutStarted (new (ContentSize));
@@ -894,7 +898,24 @@ public partial class View
 
         foreach (View v in ordered)
         {
-            LayoutSubview (v, ContentSize);
+            if (v.Width is Dim.DimAuto || v.Height is Dim.DimAuto)
+            {
+                // If the view is auto-sized...
+                Rectangle f = v.Frame;
+                v._frame = new (v.Frame.X, v.Frame.Y, 0, 0);
+                LayoutSubview (v, Viewport.Size);
+
+                if (v.Frame != f)
+                {
+                    // The subviews changed; do it again
+                    v.LayoutNeeded = true;
+                    LayoutSubview (v, Viewport.Size);
+                }
+            }
+            else
+            {
+                LayoutSubview (v, Viewport.Size);
+            }
         }
 
         // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case.
@@ -912,6 +933,75 @@ public partial class View
         OnLayoutComplete (new (ContentSize));
     }
 
+
+    /// <summary>
+    ///     Throws an <see cref="InvalidOperationException"/> if any SubViews are using Dim objects that depend on this
+    ///     Views dimensions.
+    /// </summary>
+    /// <exception cref="InvalidOperationException"></exception>
+    private void CheckDimAuto ()
+    {
+        if (!ValidatePosDim || !IsInitialized || (Width is not Dim.DimAuto && Height is not Dim.DimAuto))
+        {
+            return;
+        }
+
+        void ThrowInvalid (View view, object checkPosDim, string name)
+        {
+            // TODO: Figure out how to make CheckDimAuto deal with PosCombine
+            object bad = null;
+
+            switch (checkPosDim)
+            {
+                case Pos pos and not Pos.PosAbsolute and not Pos.PosView and not Pos.PosCombine:
+                    bad = pos;
+
+                    break;
+
+                case Pos pos and Pos.PosCombine:
+                    // Recursively check for not Absolute or not View
+                    ThrowInvalid (view, (pos as Pos.PosCombine)._left, name);
+                    ThrowInvalid (view, (pos as Pos.PosCombine)._right, name);
+
+                    break;
+
+                case Dim dim and not Dim.DimAbsolute and not Dim.DimView and not Dim.DimCombine:
+                    bad = dim;
+
+                    break;
+
+                case Dim dim and Dim.DimCombine:
+                    // Recursively check for not Absolute or not View
+                    ThrowInvalid (view, (dim as Dim.DimCombine)._left, name);
+                    ThrowInvalid (view, (dim as Dim.DimCombine)._right, name);
+
+                    break;
+            }
+
+            if (bad != null)
+            {
+                throw new InvalidOperationException (
+                                                     @$"{view.GetType ().Name}.{name} = {bad.GetType ().Name} which depends on the SuperView's dimensions and the SuperView uses Dim.Auto.");
+            }
+        }
+
+        // Verify none of the subviews are using Dim objects that depend on the SuperView's dimensions.
+        foreach (View view in Subviews)
+        {
+            if (Width is Dim.DimAuto { _min: null })
+            {
+                ThrowInvalid (view, view.Width, nameof (view.Width));
+                ThrowInvalid (view, view.X, nameof (view.X));
+            }
+
+            if (Height is Dim.DimAuto { _min: null })
+            {
+                ThrowInvalid (view, view.Height, nameof (view.Height));
+                ThrowInvalid (view, view.Y, nameof (view.Y));
+            }
+        }
+    }
+
     private void LayoutSubview (View v, Size contentSize)
     {
         v.SetRelativeLayout (contentSize);
@@ -1017,17 +1107,19 @@ public partial class View
         Debug.Assert (_width is { });
         Debug.Assert (_height is { });
 
-        var autoSize = Size.Empty;
+        CheckDimAuto ();
 
+        var autoSize = Size.Empty;
+        
         if (AutoSize)
         {
-            autoSize = GetAutoSize ();
+            autoSize = GetTextAutoSize ();
         }
 
-        int newX = _x.Calculate (superviewContentSize.Width, _width, autoSize.Width, AutoSize);
-        int newW = _width.Calculate (newX, superviewContentSize.Width, autoSize.Width, AutoSize);
-        int newY = _y.Calculate (superviewContentSize.Height, _height, autoSize.Height, AutoSize);
-        int newH = _height.Calculate (newY, superviewContentSize.Height, autoSize.Height, AutoSize);
+        int newX = _x.Calculate (superviewContentSize.Width, _width,  this, Dim.Dimension.Width, autoSize.Width, AutoSize);
+        int newW = _width.Calculate (newX, superviewContentSize.Width, this, Dim.Dimension.Width, autoSize.Width, AutoSize);
+        int newY = _y.Calculate (superviewContentSize.Height, _height, this, Dim.Dimension.Height, autoSize.Height, AutoSize);
+        int newH = _height.Calculate (newY, superviewContentSize.Height, this, Dim.Dimension.Height, autoSize.Height, AutoSize);
 
         Rectangle newFrame = new (newX, newY, newW, newH);
 
@@ -1248,34 +1340,28 @@ public partial class View
         return result;
     } // TopologicalSort
 
-    #region Diagnostics
-
-    // Diagnostics to highlight when Width or Height is read before the view has been initialized
-    private Dim VerifyIsInitialized (Dim dim, string member)
+    // Diagnostics to highlight when X or Y is read before the view has been initialized
+    private Pos VerifyIsInitialized (Pos pos, string member)
     {
 #if DEBUG
-        if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
+        if (pos is not Pos.PosAbsolute && LayoutStyle == LayoutStyle.Computed && !IsInitialized)
         {
-            Debug.WriteLine (
-                             $"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: {dim}. This is potentially a bug."
-                            );
+            Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate ({pos}). This is potentially a bug.");
         }
-#endif // DEBUG		
-        return dim;
+#endif // DEBUG
+        return pos;
     }
 
-    // Diagnostics to highlight when X or Y is read before the view has been initialized
-    private Pos VerifyIsInitialized (Pos pos, string member)
+    // Diagnostics to highlight when Width or Height is read before the view has been initialized
+    private Dim VerifyIsInitialized (Dim dim, string member)
     {
 #if DEBUG
-        if (LayoutStyle == LayoutStyle.Computed && !IsInitialized)
+        if (dim is not Dim.DimAbsolute && LayoutStyle == LayoutStyle.Computed && !IsInitialized)
         {
-            Debug.WriteLine (
-                             $"WARNING: \"{this}\" has not been initialized; {member} is indeterminate {pos}. This is potentially a bug."
-                            );
+            Debug.WriteLine ($"WARNING: \"{this}\" has not been initialized; {member} is indeterminate: ({dim}). This is potentially a bug.");
         }
 #endif // DEBUG
-        return pos;
+        return dim;
     }
 
     /// <summary>Gets or sets whether validation of <see cref="Pos"/> and <see cref="Dim"/> occurs.</summary>
@@ -1286,6 +1372,4 @@ public partial class View
     ///     thus should only be used for debugging.
     /// </remarks>
     public bool ValidatePosDim { get; set; }
-
-    #endregion
 }

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

@@ -91,6 +91,7 @@ public partial class View
             view.EndInit ();
         }
 
+        CheckDimAuto ();
         SetNeedsLayout ();
         SetNeedsDisplay ();
     }

+ 115 - 86
Terminal.Gui/View/ViewText.cs

@@ -1,13 +1,16 @@
-namespace Terminal.Gui;
+using static Terminal.Gui.SpinnerStyle;
+
+namespace Terminal.Gui;
 
 public partial class View
 {
     private string _text;
 
     /// <summary>
-    ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved or not when
-    ///     <see cref="TextFormatter.WordWrap"/> is enabled. If <see langword="true"/> trailing spaces at the end of wrapped
-    ///     lines will be removed when <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
+    ///     Gets or sets whether trailing spaces at the end of word-wrapped lines are preserved
+    ///     or not when <see cref="TextFormatter.WordWrap"/> is enabled.
+    ///     If <see langword="true"/> trailing spaces at the end of wrapped lines will be removed when
+    ///     <see cref="Text"/> is formatted for display. The default is <see langword="false"/>.
     /// </summary>
     public virtual bool PreserveTrailingSpaces
     {
@@ -22,12 +25,16 @@ public partial class View
         }
     }
 
-    /// <summary>The text displayed by the <see cref="View"/>.</summary>
+    /// <summary>
+    ///     The text displayed by the <see cref="View"/>.
+    /// </summary>
     /// <remarks>
-    ///     <para>The text will be drawn before any subviews are drawn.</para>
     ///     <para>
-    ///         The text will be drawn starting at the view origin (0, 0) and will be formatted according to
-    ///         <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
+    ///         The text will be drawn before any subviews are drawn.
+    ///     </para>
+    ///     <para>
+    ///         The text will be drawn starting at the view origin (0, 0) and will be formatted according
+    ///         to <see cref="TextAlignment"/> and <see cref="TextDirection"/>.
     ///     </para>
     ///     <para>
     ///         The text will word-wrap to additional lines if it does not fit horizontally. If <see cref="Viewport"/>'s height
@@ -112,12 +119,15 @@ public partial class View
         }
     }
 
-    /// <summary>Gets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.</summary>
+    /// <summary>
+    ///     Gets or sets the <see cref="Gui.TextFormatter"/> used to format <see cref="Text"/>.
+    /// </summary>
     public TextFormatter TextFormatter { get; init; } = new ();
 
     /// <summary>
     ///     Gets or sets how the View's <see cref="Text"/> is aligned vertically when drawn. Changing this property will
-    ///     redisplay the <see cref="View"/>.
+    ///     redisplay
+    ///     the <see cref="View"/>.
     /// </summary>
     /// <remarks>
     ///     <para>If <see cref="AutoSize"/> is <c>true</c>, the <see cref="Viewport"/> will be adjusted to fit the text.</para>
@@ -133,13 +143,46 @@ public partial class View
         }
     }
 
+    /// <summary>
+    ///     Gets the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> characters
+    ///     in the <see cref="Text"/> property.
+    /// </summary>
+    /// <remarks>
+    ///     Only the first HotKey specifier found in <see cref="Text"/> is supported.
+    /// </remarks>
+    /// <param name="isWidth">
+    ///     If <see langword="true"/> (the default) the width required for the HotKey specifier is returned. Otherwise the
+    ///     height
+    ///     is returned.
+    /// </param>
+    /// <returns>
+    ///     The number of characters required for the <see cref="TextFormatter.HotKeySpecifier"/>. If the text
+    ///     direction specified
+    ///     by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is returned.
+    /// </returns>
+    public int GetHotKeySpecifierLength (bool isWidth = true)
+    {
+        if (isWidth)
+        {
+            return TextFormatter.IsHorizontalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+                       ? Math.Max (HotKeySpecifier.GetColumns (), 0)
+                       : 0;
+        }
+
+        return TextFormatter.IsVerticalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+                   ? Math.Max (HotKeySpecifier.GetColumns (), 0)
+                   : 0;
+    }
+
     /// <summary>
     ///     Gets the Frame dimensions required to fit <see cref="Text"/> within <see cref="Viewport"/> using the text
     ///     <see cref="NavigationDirection"/> specified by the <see cref="TextFormatter"/> property and accounting for any
     ///     <see cref="HotKeySpecifier"/> characters.
     /// </summary>
-    /// <returns>The <see cref="Size"/> the <see cref="Frame"/> needs to be set to fit the text.</returns>
-    public Size GetAutoSize ()
+    /// <remarks>
+    /// </remarks>
+    /// <returns>The <see cref="Size"/> of the <see cref="Frame"/> required to fit the formatted text.</returns>
+    public Size GetTextAutoSize ()
     {
         var x = 0;
         var y = 0;
@@ -154,58 +197,19 @@ public partial class View
 
         int newWidth = rect.Size.Width
                        - GetHotKeySpecifierLength ()
-                       + (Margin == null
-                              ? 0
-                              : Margin.Thickness.Horizontal
-                                + Border.Thickness.Horizontal
-                                + Padding.Thickness.Horizontal);
+                       + (Margin == null ? 0 : Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal);
 
         int newHeight = rect.Size.Height
                         - GetHotKeySpecifierLength (false)
-                        + (Margin == null
-                               ? 0
-                               : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
+                        + (Margin == null ? 0 : Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical);
 
         return new (newWidth, newHeight);
     }
 
     /// <summary>
-    ///     Gets the width or height of the <see cref="TextFormatter.HotKeySpecifier"/> characters in the
-    ///     <see cref="Text"/> property.
+    ///     Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has
+    ///     different format than the default.
     /// </summary>
-    /// <remarks>
-    ///     <para>
-    ///         This is for <see cref="Text"/>, not <see cref="Title"/>. For <see cref="Text"/> to show the hotkey,
-    ///         set <c>View.</c><see cref="TextFormatter.HotKeySpecifier"/> to the desired character.
-    ///     </para>
-    ///     <para>
-    ///         Only the first HotKey specifier found in <see cref="Text"/> is supported.
-    ///     </para>
-    /// </remarks>
-    /// <param name="isWidth">
-    ///     If <see langword="true"/> (the default) the width required for the HotKey specifier is returned.
-    ///     Otherwise the height is returned.
-    /// </param>
-    /// <returns>
-    ///     The number of characters required for the <see cref="TextFormatter.HotKeySpecifier"/>. If the text direction
-    ///     specified by <see cref="TextDirection"/> does not match the <paramref name="isWidth"/> parameter, <c>0</c> is
-    ///     returned.
-    /// </returns>
-    public int GetHotKeySpecifierLength (bool isWidth = true)
-    {
-        if (isWidth)
-        {
-            return TextFormatter.IsHorizontalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)TextFormatter.HotKeySpecifier.Value) == true
-                       ? Math.Max (TextFormatter.HotKeySpecifier.GetColumns (), 0)
-                       : 0;
-        }
-
-        return TextFormatter.IsVerticalDirection (TextDirection) && TextFormatter.Text?.Contains ((char)TextFormatter.HotKeySpecifier.Value) == true
-                   ? Math.Max (TextFormatter.HotKeySpecifier.GetColumns (), 0)
-                   : 0;
-    }
-
-    /// <summary>Can be overridden if the <see cref="Terminal.Gui.TextFormatter.Text"/> has different format than the default.</summary>
     protected virtual void UpdateTextFormatterText ()
     {
         if (TextFormatter is { })
@@ -214,14 +218,15 @@ public partial class View
         }
     }
 
-    /// <summary>Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.</summary>
+    /// <summary>
+    ///     Gets the dimensions required for <see cref="Text"/> ignoring a <see cref="TextFormatter.HotKeySpecifier"/>.
+    /// </summary>
     /// <returns></returns>
     internal Size GetSizeNeededForTextWithoutHotKey ()
     {
-        return new (
-                    TextFormatter.Size.Width - GetHotKeySpecifierLength (),
-                    TextFormatter.Size.Height - GetHotKeySpecifierLength (false)
-                   );
+        return new Size (
+                         TextFormatter.Size.Width - GetHotKeySpecifierLength (),
+                         TextFormatter.Size.Height - GetHotKeySpecifierLength (false));
     }
 
     /// <summary>
@@ -229,8 +234,9 @@ public partial class View
     ///     <see cref="TextFormatter.HotKeySpecifier"/>.
     /// </summary>
     /// <remarks>
-    ///     Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the size required to
-    ///     fit the text has changed. changes.
+    ///     Use this API to set <see cref="TextFormatter.Size"/> when the view has changed such that the
+    ///     size required to fit the text has changed.
+    ///     changes.
     /// </remarks>
     /// <returns></returns>
     internal void SetTextFormatterSize ()
@@ -248,21 +254,46 @@ public partial class View
 
             return;
         }
+        
+        int w = Viewport.Size.Width + GetHotKeySpecifierLength ();
 
-        TextFormatter.Size = new (
-                                  ContentSize.Width + GetHotKeySpecifierLength (),
-                                  ContentSize.Height + GetHotKeySpecifierLength (false)
-                                 );
+        if (Width is Dim.DimAuto widthAuto && widthAuto._style != Dim.DimAutoStyle.Subviews)
+        {
+            if (Height is Dim.DimAuto)
+            {
+                // Both are auto. 
+                TextFormatter.Size = new Size (SuperView?.Viewport.Width ?? 0, SuperView?.Viewport.Height ?? 0);
+            }
+            else
+            {
+                TextFormatter.Size = new Size (SuperView?.Viewport.Width ?? 0, Viewport.Size.Height + GetHotKeySpecifierLength ());
+            }
+
+            w = TextFormatter.FormatAndGetSize ().Width;
+        }
+        else
+        {
+            TextFormatter.Size = new Size (w, SuperView?.Viewport.Height ?? 0);
+        }
+
+        int h = Viewport.Size.Height + GetHotKeySpecifierLength ();
+
+        if (Height is Dim.DimAuto heightAuto && heightAuto._style != Dim.DimAutoStyle.Subviews)
+        {
+            TextFormatter.NeedsFormat = true;
+            h = TextFormatter.FormatAndGetSize ().Height;
+        }
+
+        TextFormatter.Size = new Size (w, h);
     }
 
     private bool IsValidAutoSize (out Size autoSize)
     {
         Rectangle rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection);
 
-        autoSize = new (
-                        rect.Size.Width - GetHotKeySpecifierLength (),
-                        rect.Size.Height - GetHotKeySpecifierLength (false)
-                       );
+        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 ()
@@ -274,8 +305,7 @@ public partial class View
         Rectangle 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));
+        return !((ValidatePosDim && !(height is Dim.DimAbsolute)) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false));
     }
 
     private bool IsValidAutoSizeWidth (Dim width)
@@ -283,19 +313,21 @@ public partial class View
         Rectangle 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 ());
+        return !((ValidatePosDim && !(width is Dim.DimAbsolute)) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ());
     }
 
-    /// <summary>Sets the size of the View to the minimum width or height required to fit <see cref="Text"/>.</summary>
+    /// <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.
+    ///     <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.
+    ///     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>
     private bool SetFrameToFitText ()
     {
@@ -334,7 +366,7 @@ public partial class View
             switch (TextFormatter.IsVerticalDirection (TextDirection))
             {
                 case true:
-                    int colWidth = TextFormatter.GetWidestLineLength (new List<string> { TextFormatter.Text }, 0, 1);
+                    int colWidth = TextFormatter.GetSumMaxCharWidth (TextFormatter.Text, 0, 1);
 
                     // TODO: v2 - This uses frame.Width; it should only use Viewport
                     if (_frame.Width < colWidth
@@ -374,23 +406,20 @@ public partial class View
         return false;
     }
 
-    // only called from EndInit
     private void UpdateTextDirection (TextDirection newDirection)
     {
-        bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction)
-                                != TextFormatter.IsHorizontalDirection (newDirection);
+        bool directionChanged = TextFormatter.IsHorizontalDirection (TextFormatter.Direction) != TextFormatter.IsHorizontalDirection (newDirection);
         TextFormatter.Direction = newDirection;
 
         bool isValidOldAutoSize = AutoSize && IsValidAutoSize (out Size _);
 
         UpdateTextFormatterText ();
 
-        if ((!ValidatePosDim && directionChanged && AutoSize)
-            || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize))
+        if ((!ValidatePosDim && directionChanged && AutoSize) || (ValidatePosDim && directionChanged && AutoSize && isValidOldAutoSize))
         {
             OnResizeNeeded ();
         }
-        else if (AutoSize && directionChanged && IsAdded)
+        else if (directionChanged && IsAdded)
         {
             ResizeViewportToFit (Viewport.Size);
         }

+ 2 - 3
Terminal.Gui/Views/Dialog.cs

@@ -61,9 +61,8 @@ public class Dialog : Window
         Y = Pos.Center ();
         ValidatePosDim = true;
 
-        Width = Dim.Percent (85); // Dim.Auto (min: Dim.Percent (10));
-        Height = Dim.Percent (85); //Dim.Auto (min: Dim.Percent (50));
-
+        Width = Dim.Percent (85); 
+        Height = Dim.Percent (85);
         ColorScheme = Colors.ColorSchemes ["Dialog"];
 
         Modal = true;

+ 199 - 0
UICatalog/Scenarios/DimAutoDemo.cs

@@ -0,0 +1,199 @@
+using System;
+using Terminal.Gui;
+using static Terminal.Gui.Dim;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("DimAuto", "Demonstrates Dim.Auto")]
+[ScenarioCategory ("Layout")]
+public class DimAutoDemo : Scenario
+{
+    public override void Main ()
+    {
+        Application.Init ();
+        // Setup - Create a top-level application window and configure it.
+        Window appWindow = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
+        };
+
+        var view = new FrameView
+        {
+            Title = "Type to make View grow",
+            X = 1,
+            Y = 1,
+            Width = Auto (DimAutoStyle.Subviews, 40),
+            Height = Auto (DimAutoStyle.Subviews, 10)
+        };
+        view.ValidatePosDim = true;
+
+        var textEdit = new TextView { Text = "", X = 1, Y = 0, Width = 20, Height = 4 };
+        view.Add (textEdit);
+
+        var hlabel = new Label
+        {
+            Text = textEdit.Text,
+            X = Pos.Left (textEdit) + 1,
+            Y = Pos.Bottom (textEdit),
+            AutoSize = false,
+            Width = Auto (DimAutoStyle.Text, 20),
+            Height = 1,
+            ColorScheme = Colors.ColorSchemes ["Error"]
+        };
+        view.Add (hlabel);
+
+        var vlabel = new Label
+        {
+            Text = textEdit.Text,
+            X = Pos.Left (textEdit),
+            Y = Pos.Bottom (textEdit) + 1,
+            AutoSize = false,
+            Width = 1,
+            Height = Auto (DimAutoStyle.Text, 8),
+            ColorScheme = Colors.ColorSchemes ["Error"]
+
+            //TextDirection = TextDirection.TopBottom_LeftRight
+        };
+        vlabel.Id = "vlabel";
+        view.Add (vlabel);
+
+        var heightAuto = new View
+        {
+            X = Pos.Right (vlabel) + 1,
+            Y = Pos.Bottom (hlabel) + 1,
+            Width = 20,
+            Height = Auto (),
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            Title = "W: 20, H: Auto",
+            BorderStyle = LineStyle.Rounded
+        };
+        heightAuto.Id = "heightAuto";
+        view.Add (heightAuto);
+
+        var widthAuto = new View
+        {
+            X = Pos.Right (heightAuto) + 1,
+            Y = Pos.Bottom (hlabel) + 1,
+            Width = Auto (),
+            Height = 5,
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            Title = "W: Auto, H: 5",
+            BorderStyle = LineStyle.Rounded
+        };
+        widthAuto.Id = "widthAuto";
+        view.Add (widthAuto);
+
+        var bothAuto = new View
+        {
+            X = Pos.Right (widthAuto) + 1,
+            Y = Pos.Bottom (hlabel) + 1,
+            Width = Auto (),
+            Height = Auto (),
+            ColorScheme = Colors.ColorSchemes ["Error"],
+            Title = "W: Auto, H: Auto",
+            BorderStyle = LineStyle.Rounded
+        };
+        bothAuto.Id = "bothAuto";
+        view.Add (bothAuto);
+
+        textEdit.ContentsChanged += (s, e) =>
+                                    {
+                                        hlabel.Text = textEdit.Text;
+                                        vlabel.Text = textEdit.Text;
+                                        heightAuto.Text = textEdit.Text;
+                                        widthAuto.Text = textEdit.Text;
+                                        bothAuto.Text = textEdit.Text;
+                                    };
+
+        var movingButton = new Button
+        {
+            Text = "_Move down",
+            X = Pos.Right (vlabel),
+            Y = Pos.Bottom (vlabel),
+        };
+        movingButton.Accept += (s, e) => { movingButton.Y = movingButton.Frame.Y + 1; };
+        view.Add (movingButton);
+
+        var resetButton = new Button
+        {
+            Text = "_Reset Button",
+            X = Pos.Right (movingButton),
+            Y = Pos.Top (movingButton)
+        };
+
+        resetButton.Accept += (s, e) => { movingButton.Y = Pos.Bottom (hlabel); };
+        view.Add (resetButton);
+
+        var dlgButton = new Button
+        {
+            Text = "Open Test _Dialog",
+            X = Pos.Right (view),
+            Y = Pos.Top (view)
+        };
+        dlgButton.Accept += DlgButton_Clicked;
+
+        appWindow.Add (view, dlgButton);
+
+        // Run - Start the application.
+        Application.Run (appWindow);
+        appWindow.Dispose ();
+
+        // Shutdown - Calling Application.Shutdown is required.
+        Application.Shutdown ();
+
+    }
+
+    private void DlgButton_Clicked (object sender, EventArgs e)
+    {
+        var dlg = new Dialog
+        {
+            Title = "Test Dialog",
+            Width = Dim.Auto (min: Dim.Percent (10)),
+            //Height = Dim.Auto (min: Dim.Percent (50))
+        };
+
+        //var ok = new Button ("Bye") { IsDefault = true };
+        //ok.Clicked += (s, _) => Application.RequestStop (dlg);
+        //dlg.AddButton (ok);
+
+        //var cancel = new Button ("Abort") { };
+        //cancel.Clicked += (s, _) => Application.RequestStop (dlg);
+        //dlg.AddButton (cancel);
+
+        //var label = new Label
+        //{
+        //    ValidatePosDim = true,
+        //    Text = "This is a label (AutoSize = false; Dim.Auto(3/20). Press Esc to close. Even more text.",
+        //    AutoSize = false,
+        //    X = Pos.Center (),
+        //    Y = 0,
+        //    Height = Auto (min: 3),
+        //    Width = Auto (min: 20),
+        //    ColorScheme = Colors.ColorSchemes ["Menu"]
+        //};
+
+        var text = new TextField
+        {
+            ValidatePosDim = true,
+            Text = "TextField: X=1; Y=Pos.Bottom (label)+1, Width=Dim.Fill (0); Height=1",
+            TextFormatter = new TextFormatter { WordWrap = true },
+            X = 0,
+            Y = 0, //Pos.Bottom (label) + 1,
+            Width = Fill (10),
+            Height = 1
+        };
+
+        //var btn = new Button
+        //{
+        //    Text = "AnchorEnd", Y = Pos.AnchorEnd (1)
+        //};
+
+        //// TODO: We should really fix AnchorEnd to do this automatically. 
+        //btn.X = Pos.AnchorEnd () - (Pos.Right (btn) - Pos.Left (btn));
+        //dlg.Add (label);
+        dlg.Add (text);
+        //dlg.Add (btn);
+        Application.Run (dlg);
+        dlg.Dispose ();
+    }
+}

+ 1 - 1
UICatalog/UICatalog.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TargetFramework>net8.0</TargetFramework>

File diff suppressed because it is too large
+ 291 - 311
UnitTests/Dialogs/DialogTests.cs


+ 542 - 0
UnitTests/View/Layout/DimAutoTests.cs

@@ -0,0 +1,542 @@
+using System.Globalization;
+using System.Text;
+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 DimAutoTests
+{
+    private readonly ITestOutputHelper _output;
+
+    public DimAutoTests (ITestOutputHelper output)
+    {
+        _output = output;
+        Console.OutputEncoding = Encoding.Default;
+
+        // Change current culture
+        var culture = CultureInfo.CreateSpecificCulture ("en-US");
+        Thread.CurrentThread.CurrentCulture = culture;
+        Thread.CurrentThread.CurrentUICulture = culture;
+    }
+
+    // Test min - ensure that if min is specified in the DimAuto constructor it is honored
+    [Fact]
+    public void DimAuto_Min ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: 10),
+            Height = Dim.Auto (min: 10),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 5,
+            Height = 5
+        };
+
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.SetRelativeLayout (new  (10, 10));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+    }
+
+    // what happens if DimAuto (min: 10) and the subview moves to a negative coord?
+    [Fact]
+    public void DimAuto_Min_Resets_If_Subview_Moves_Negative ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: 10),
+            Height = Dim.Auto (min: 10),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 5,
+            Height = 5
+        };
+
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.SetRelativeLayout (new ( 10, 10));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+
+        subView.X = -1;
+        subView.Y = -1;
+        superView.SetRelativeLayout (new ( 10, 10));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (5, subView.Frame.Width);
+        Assert.Equal (5, subView.Frame.Height);
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+    }
+
+    [Fact]
+    public void DimAuto_Min_Resets_If_Subview_Shrinks ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: 10),
+            Height = Dim.Auto (min: 10),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 5,
+            Height = 5
+        };
+
+        superView.Add (subView);
+        superView.BeginInit ();
+        superView.EndInit ();
+
+        superView.SetRelativeLayout (new ( 10, 10));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+
+        subView.Width = 3;
+        subView.Height = 3;
+        superView.SetRelativeLayout (new ( 10, 10));
+        superView.LayoutSubviews (); // no throw
+
+        Assert.Equal (3, subView.Frame.Width);
+        Assert.Equal (3, subView.Frame.Height);
+
+        Assert.Equal (10, superView.Frame.Width);
+        Assert.Equal (10, superView.Frame.Height);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0)]
+    [InlineData (0, 0, 5, 0, 0)]
+    [InlineData (0, 0, 0, 5, 5)]
+    [InlineData (0, 0, 5, 5, 5)]
+    [InlineData (1, 0, 5, 0, 0)]
+    [InlineData (1, 0, 0, 5, 5)]
+    [InlineData (1, 0, 5, 5, 5)]
+    [InlineData (1, 1, 5, 5, 6)]
+    [InlineData (-1, 0, 5, 0, 0)]
+    [InlineData (-1, 0, 0, 5, 5)]
+    [InlineData (-1, 0, 5, 5, 5)]
+    [InlineData (-1, -1, 5, 5, 4)]
+    public void Height_Auto_Width_NotChanged (int subX, int subY, int subWidth, int subHeight, int expectedHeight)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = subY,
+            Width = subWidth,
+            Height = subHeight,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 10, 10));
+        Assert.Equal (new Rectangle (0, 0, 10, expectedHeight), superView.Frame);
+    }
+
+    [Fact]
+    public void NoSubViews_Does_Nothing ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 10, 10));
+        Assert.Equal (new Rectangle (0, 0, 0, 0), superView.Frame);
+
+        superView.SetRelativeLayout (new ( 10, 10));
+        Assert.Equal (new Rectangle (0, 0, 0, 0), superView.Frame);
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0, 0)]
+    [InlineData (0, 0, 5, 0, 5, 0)]
+    [InlineData (0, 0, 0, 5, 0, 5)]
+    [InlineData (0, 0, 5, 5, 5, 5)]
+    [InlineData (1, 0, 5, 0, 6, 0)]
+    [InlineData (1, 0, 0, 5, 1, 5)]
+    [InlineData (1, 0, 5, 5, 6, 5)]
+    [InlineData (1, 1, 5, 5, 6, 6)]
+    [InlineData (-1, 0, 5, 0, 4, 0)]
+    [InlineData (-1, 0, 0, 5, 0, 5)]
+    [InlineData (-1, 0, 5, 5, 4, 5)]
+    [InlineData (-1, -1, 5, 5, 4, 4)]
+    public void SubView_ChangesSuperViewSize (int subX, int subY, int subWidth, int subHeight, int expectedWidth, int expectedHeight)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = subY,
+            Width = subWidth,
+            Height = subHeight,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 10, 10));
+        Assert.Equal (new Rectangle (0, 0, expectedWidth, expectedHeight), superView.Frame);
+    }
+
+    // Test validation
+    [Fact]
+    public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Fill (),
+            Height = 10,
+            ValidatePosDim = true
+        };
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        Assert.Throws<InvalidOperationException> (() => superView.Add (subView));
+
+        subView.Width = 10;
+        superView.Add (subView);
+        superView.SetRelativeLayout (new ( 10, 10));
+        superView.LayoutSubviews (); // no throw
+
+        subView.Width = Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Width = 10;
+
+        subView.Height = Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Height = 10;
+
+        subView.Height = Dim.Percent (50);
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Height = 10;
+
+        subView.X = Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+
+        subView.Y = Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Y = 0;
+
+        subView.Width = 10;
+        subView.Height = 10;
+        subView.X = 0;
+        subView.Y = 0;
+        superView.SetRelativeLayout (new ( 0, 0));
+        superView.LayoutSubviews ();
+    }
+
+    // Test validation
+    [Fact]
+    public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Dims_Combine ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        var subView2 = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        superView.Add (subView, subView2);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.Height = Dim.Fill () + 3;
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Height = 0;
+
+        subView.Height = 3 + Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Height = 0;
+
+        subView.Height = 3 + 5 + Dim.Fill ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Height = 0;
+
+        subView.Height = 3 + 5 + Dim.Percent (10);
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.Height = 0;
+
+        // Tests nested Combine
+        subView.Height = 5 + new Dim.DimCombine (true, 3, new Dim.DimCombine (true, Dim.Percent (10), 9));
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+    }
+
+    [Fact]
+    public void ValidatePosDim_True_Throws_When_SubView_Uses_SuperView_Pos_Combine ()
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = Dim.Auto (),
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        var subView2 = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = 10,
+            Height = 10
+        };
+
+        superView.Add (subView, subView2);
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.X = Pos.Right (subView2);
+        superView.SetRelativeLayout (new ( 0, 0));
+        superView.LayoutSubviews (); // no throw
+
+        subView.X = Pos.Right (subView2) + 3;
+        superView.SetRelativeLayout (new ( 0, 0)); // no throw
+        superView.LayoutSubviews (); // no throw
+
+        subView.X = new Pos.PosCombine (true, Pos.Right (subView2), new Pos.PosCombine (true, 7, 9));
+        superView.SetRelativeLayout (new ( 0, 0)); // no throw
+
+        subView.X = Pos.Center () + 3;
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+
+        subView.X = 3 + Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+
+        subView.X = 3 + 5 + Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+
+        subView.X = 3 + 5 + Pos.Percent (10);
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+
+        subView.X = Pos.Percent (10) + Pos.Center ();
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+
+        // Tests nested Combine
+        subView.X = 5 + new Pos.PosCombine (true, Pos.Right (subView2), new Pos.PosCombine (true, Pos.Center (), 9));
+        Assert.Throws<InvalidOperationException> (() => superView.SetRelativeLayout (new ( 0, 0)));
+        subView.X = 0;
+    }
+
+    [Theory]
+    [InlineData (0, 0, 0, 0, 0)]
+    [InlineData (0, 0, 5, 0, 5)]
+    [InlineData (0, 0, 0, 5, 0)]
+    [InlineData (0, 0, 5, 5, 5)]
+    [InlineData (1, 0, 5, 0, 6)]
+    [InlineData (1, 0, 0, 5, 1)]
+    [InlineData (1, 0, 5, 5, 6)]
+    [InlineData (1, 1, 5, 5, 6)]
+    [InlineData (-1, 0, 5, 0, 4)]
+    [InlineData (-1, 0, 0, 5, 0)]
+    [InlineData (-1, 0, 5, 5, 4)]
+    [InlineData (-1, -1, 5, 5, 4)]
+    public void Width_Auto_Height_NotChanged (int subX, int subY, int subWidth, int subHeight, int expectedWidth)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (),
+            Height = 10,
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = subY,
+            Width = subWidth,
+            Height = subHeight,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 10, 10));
+        Assert.Equal (new Rectangle (0, 0, expectedWidth, 10), superView.Frame);
+    }
+
+    // Test that when a view has Width set to DimAuto (min: x) the width is never < x even if SetRelativeLayout is called with smaller bounds
+    [Theory]
+    [InlineData (0, 0)]
+    [InlineData (1, 1)]
+    [InlineData (3, 3)]
+    [InlineData (4, 4)]
+    [InlineData (5, 4)] // This is clearly invalid, but we choose to not throw but log a debug message
+    public void Width_Auto_Min (int min, int expectedWidth)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min: min),
+            Height = 1,
+            ValidatePosDim = true
+        };
+        
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 4, 1));
+        Assert.Equal (expectedWidth, superView.Frame.Width);
+    }
+
+    // Test Dim.Fill - Fill should not impact width of the DimAuto superview
+    [Theory]
+    [InlineData (0, 0, 0, 10, 10)]
+    [InlineData (0, 1, 0, 10, 10)]
+    [InlineData (0, 11, 0, 10, 10)]
+    [InlineData (0, 10, 0, 10, 10)]
+    [InlineData (0, 5, 0, 10, 10)]
+    [InlineData (1, 5, 0, 10, 9)]
+    [InlineData (1, 10, 0, 10, 9)]
+    [InlineData (0, 0, 1, 10, 9)]
+    [InlineData (0, 10, 1, 10, 9)]
+    [InlineData (0, 5, 1, 10, 9)]
+    [InlineData (1, 5, 1, 10, 8)]
+    [InlineData (1, 10, 1, 10, 8)]
+    public void Width_Fill_Fills (int subX, int superMinWidth, int fill, int expectedSuperWidth, int expectedSubWidth)
+    {
+        var superView = new View
+        {
+            X = 0,
+            Y = 0,
+            Width = Dim.Auto (min:superMinWidth),
+            Height = 1,
+            ValidatePosDim = true
+        };
+
+        var subView = new View
+        {
+            X = subX,
+            Y = 0,
+            Width = Dim.Fill (fill),
+            Height = 1,
+            ValidatePosDim = true
+        };
+
+        superView.Add (subView);
+
+        superView.BeginInit ();
+        superView.EndInit ();
+        superView.SetRelativeLayout (new ( 10, 1));
+        Assert.Equal (expectedSuperWidth, superView.Frame.Width);
+        superView.LayoutSubviews ();
+        Assert.Equal (expectedSubWidth, subView.Frame.Width);
+        Assert.Equal (expectedSuperWidth, superView.Frame.Width);
+    }
+
+    // Test variations of Frame
+}

+ 413 - 119
UnitTests/View/Layout/DimTests.cs

@@ -25,53 +25,53 @@ public class DimTests
     }
 
     [Fact]
-    public void DimAbsolute_GetDimension_ReturnsCorrectValue ()
+    public void DimAbsolute_Calculate_ReturnsCorrectValue ()
     {
         var dim = new DimAbsolute (10);
-        var result = dim.Calculate (0, 100, 50, false);
+        var result = dim.Calculate (0, 100, null, Dim.Dimension.None, 50, false);
         Assert.Equal (10, result);
     }
 
     [Fact]
-    public void DimCombine_GetDimension_ReturnsCorrectValue ()
+    public void DimCombine_Calculate_ReturnsCorrectValue ()
     {
         var dim1 = new DimAbsolute (10);
         var dim2 = new DimAbsolute (20);
         var dim = dim1 + dim2;
-        var result = dim.Calculate (0, 100, 50, false);
+        var result = dim.Calculate (0, 100, null, Dim.Dimension.None, 50, false);
         Assert.Equal (30, result);
     }
 
     [Fact]
-    public void DimFactor_GetDimension_ReturnsCorrectValue ()
+    public void DimFactor_Calculate_ReturnsCorrectValue ()
     {
         var dim = new DimFactor (0.5f);
-        var result = dim.Calculate (0, 100, 50, false);
+        var result = dim.Calculate (0, 100, null, Dim.Dimension.None, 50, false);
         Assert.Equal (50, result);
     }
 
     [Fact]
-    public void DimFill_GetDimension_ReturnsCorrectValue ()
+    public void DimFill_Calculate_ReturnsCorrectValue ()
     {
         var dim = Dim.Fill ();
-        var result = dim.Calculate (0, 100, 50, false);
+        var result = dim.Calculate (0, 100, null, Dim.Dimension.None, 50, false);
         Assert.Equal (100, result);
     }
 
     [Fact]
-    public void DimFunc_GetDimension_ReturnsCorrectValue ()
+    public void DimFunc_Calculate_ReturnsCorrectValue ()
     {
         var dim = new DimFunc (() => 10);
-        var result = dim.Calculate (0, 100, 50, false);
+        var result = dim.Calculate (0, 100, null, Dim.Dimension.None, 50, false);
         Assert.Equal (10, result);
     }
 
     [Fact]
-    public void DimView_GetDimension_ReturnsCorrectValue ()
+    public void DimView_Calculate_ReturnsCorrectValue ()
     {
         var view = new View { Width = 10 };
         var dim = new DimView (view, Dimension.Width);
-        var result = dim.Calculate (0, 100, 50, false);
+        var result = dim.Calculate (0, 100, null, Dim.Dimension.None, 50, false);
         Assert.Equal (10, result);
     }
 
@@ -79,7 +79,7 @@ public class DimTests
     // A new test that does not depend on Application is needed.
     [Fact]
     [AutoInitShutdown]
-    public void Dim_Add_Operator ()
+    public void Add_Operator ()
     {
         Toplevel top = new ();
 
@@ -372,7 +372,7 @@ public class DimTests
     /// <summary>This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461</summary>
     [Fact]
     [TestRespondersDisposed]
-    public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView ()
+    public void Combine_ObtuseScenario_Throw_If_SuperView_Refs_SubView ()
     {
         var t = new View { Width = 80, Height = 25 };
 
@@ -420,55 +420,159 @@ public class DimTests
 
     // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
     // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Combine_View_Not_Added_Throws ()
+    {
+        var t = new View { Width = 80, Height = 50 };
+
+        var super = new View { Width = Dim.Width (t) - 2, Height = Dim.Height (t) - 2 };
+        t.Add (super);
+
+        var sub = new View ();
+        super.Add (sub);
+
+        var v1 = new View { Width = Dim.Width (super) - 2, Height = Dim.Height (super) - 2 };
+        var v2 = new View { Width = Dim.Width (v1) - 2, Height = Dim.Height (v1) - 2 };
+        sub.Add (v1);
+
+        // v2 not added to sub; should cause exception on Layout since it's referenced by sub.
+        sub.Width = Dim.Fill () - Dim.Width (v2);
+        sub.Height = Dim.Fill () - Dim.Height (v2);
+
+        t.BeginInit ();
+        t.EndInit ();
+
+        Assert.Throws<InvalidOperationException> (() => t.LayoutSubviews ());
+        t.Dispose ();
+        v2.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void
+        Dim_Validation_Does_Not_Throw_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
+    {
+        var t = new View { Width = 80, Height = 25, Text = "top" };
+
+        var w = new Window { Width = Dim.Fill (), Height = Dim.Sized (10) };
+        var v = new View { Width = Dim.Width (w) - 2, Height = Dim.Percent (10), Text = "v" };
+
+        w.Add (v);
+        t.Add (w);
+
+        Assert.Equal (LayoutStyle.Absolute, t.LayoutStyle);
+        Assert.Equal (LayoutStyle.Computed, w.LayoutStyle);
+        Assert.Equal (LayoutStyle.Computed, v.LayoutStyle);
+
+        t.LayoutSubviews ();
+        Assert.Equal (2, v.Width = 2);
+        Assert.Equal (2, v.Height = 2);
+
+        // Force v to be LayoutStyle.Absolute;
+        v.Frame = new Rectangle (0, 1, 3, 4);
+        Assert.Equal (LayoutStyle.Absolute, v.LayoutStyle);
+        t.LayoutSubviews ();
+
+        Assert.Equal (2, v.Width = 2);
+        Assert.Equal (2, v.Height = 2);
+        t.Dispose ();
+    }
+
+    [Fact]
+    public void Fill_Equal ()
+    {
+        var margin1 = 0;
+        var margin2 = 0;
+        Dim dim1 = Dim.Fill (margin1);
+        Dim dim2 = Dim.Fill (margin2);
+        Assert.Equal (dim1, dim2);
+    }
+
+    // Tests that Dim.Fill honors the margin parameter correctly
     [Theory]
-    [AutoInitShutdown]
-    [InlineData (0, true)]
-    [InlineData (0, false)]
-    [InlineData (50, true)]
-    [InlineData (50, false)]
-    public void DimPercentPlusOne (int startingDistance, bool testHorizontal)
+    [InlineData (0, true, 25)]
+    [InlineData (0, false, 25)]
+    [InlineData (1, true, 24)]
+    [InlineData (1, false, 24)]
+    [InlineData (2, true, 23)]
+    [InlineData (2, false, 23)]
+    [InlineData (-2, true, 27)]
+    [InlineData (-2, false, 27)]
+    public void Fill_Margin (int margin, bool width, int expected)
     {
-        var container = new View { Width = 100, Height = 100 };
+        var super = new View { Width = 25, Height = 25 };
 
-        var label = new Label
+        var view = new View
         {
-            AutoSize = false,
-            X = testHorizontal ? startingDistance : 0,
-            Y = testHorizontal ? 0 : startingDistance,
-            Width = testHorizontal ? Dim.Percent (50) + 1 : 1,
-            Height = testHorizontal ? 1 : Dim.Percent (50) + 1
+            X = 0,
+            Y = 0,
+            Width = width ? Dim.Fill (margin) : 1,
+            Height = width ? 1 : Dim.Fill (margin)
         };
 
-        container.Add (label);
-        var top = new Toplevel ();
-        top.Add (container);
-        top.BeginInit ();
-        top.EndInit ();
-        top.LayoutSubviews ();
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+        super.LayoutSubviews ();
 
-        Assert.Equal (100, container.Frame.Width);
-        Assert.Equal (100, container.Frame.Height);
+        Assert.Equal (25, super.Frame.Width);
+        Assert.Equal (25, super.Frame.Height);
 
-        if (testHorizontal)
+        if (width)
         {
-            Assert.Equal (51, label.Frame.Width);
-            Assert.Equal (1, label.Frame.Height);
+            Assert.Equal (expected, view.Frame.Width);
+            Assert.Equal (1, view.Frame.Height);
         }
         else
         {
-            Assert.Equal (1, label.Frame.Width);
-            Assert.Equal (51, label.Frame.Height);
+            Assert.Equal (1, view.Frame.Width);
+            Assert.Equal (expected, view.Frame.Height);
         }
     }
 
-    [Fact]
-    public void Fill_Equal ()
+    // Tests that Dim.Fill fills the dimension REMAINING from the View's X position to the end of the super view's width
+    [Theory]
+    [InlineData (0, true, 25)]
+    [InlineData (0, false, 25)]
+    [InlineData (1, true, 24)]
+    [InlineData (1, false, 24)]
+    [InlineData (2, true, 23)]
+    [InlineData (2, false, 23)]
+    [InlineData (-2, true, 27)]
+    [InlineData (-2, false, 27)]
+    public void Fill_Offset (int offset, bool width, int expected)
     {
-        var margin1 = 0;
-        var margin2 = 0;
-        Dim dim1 = Dim.Fill (margin1);
-        Dim dim2 = Dim.Fill (margin2);
-        Assert.Equal (dim1, dim2);
+        var super = new View { Width = 25, Height = 25 };
+
+        var view = new View
+        {
+            X = width ? offset : 0,
+            Y = width ? 0 : offset,
+            Width = width ? Dim.Fill () : 1,
+            Height = width ? 1 : Dim.Fill ()
+        };
+
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+        super.LayoutSubviews ();
+
+        Assert.Equal (25, super.Frame.Width);
+        Assert.Equal (25, super.Frame.Height);
+
+        if (width)
+        {
+            Assert.Equal (expected, view.Frame.Width);
+            Assert.Equal (1, view.Frame.Height);
+        }
+        else
+        {
+            Assert.Equal (1, view.Frame.Width);
+            Assert.Equal (expected, view.Frame.Height);
+        }
     }
 
     // TODO: Other Dim.Height tests (e.g. Equal?)
@@ -670,45 +774,45 @@ public class DimTests
 
         t.Ready += (s, e) =>
                    {
-                                            Assert.Equal ("Absolute(100)", w.Width.ToString ());
-                                            Assert.Equal ("Absolute(100)", w.Height.ToString ());
-                                            Assert.Equal (100, w.Frame.Width);
-                                            Assert.Equal (100, w.Frame.Height);
-
-                                            Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ());
-                                            Assert.Equal ("Absolute(5)", f1.Height.ToString ());
-                                            Assert.Equal (49, f1.Frame.Width); // 50-1=49
-                                            Assert.Equal (5, f1.Frame.Height);
-
-                                            Assert.Equal ("Fill(0)", f2.Width.ToString ());
-                                            Assert.Equal ("Absolute(5)", f2.Height.ToString ());
-                                            Assert.Equal (49, f2.Frame.Width); // 50-1=49
-                                            Assert.Equal (5, f2.Frame.Height);
-
-                    #if DEBUG
+                       Assert.Equal ("Absolute(100)", w.Width.ToString ());
+                       Assert.Equal ("Absolute(100)", w.Height.ToString ());
+                       Assert.Equal (100, w.Frame.Width);
+                       Assert.Equal (100, w.Frame.Height);
+
+                       Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ());
+                       Assert.Equal ("Absolute(5)", f1.Height.ToString ());
+                       Assert.Equal (49, f1.Frame.Width); // 50-1=49
+                       Assert.Equal (5, f1.Frame.Height);
+
+                       Assert.Equal ("Fill(0)", f2.Width.ToString ());
+                       Assert.Equal ("Absolute(5)", f2.Height.ToString ());
+                       Assert.Equal (49, f2.Frame.Width); // 50-1=49
+                       Assert.Equal (5, f2.Frame.Height);
+
+#if DEBUG
                        Assert.Equal ($"Combine(View(Width,FrameView(f1){f1.Border.Frame})-Absolute(2))", v1.Width.ToString ());
-                    #else
+#else
                        Assert.Equal ($"Combine(View(Width,FrameView(){f1.Border.Frame})-Absolute(2))", v1.Width.ToString ());
-                    #endif
+#endif
                        Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ());
                        Assert.Equal (47, v1.Frame.Width); // 49-2=47
                        Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89
 
-                   #if DEBUG
+#if DEBUG
                        Assert.Equal (
                                      $"Combine(View(Width,FrameView(f2){f2.Frame})-Absolute(2))",
                                      v2.Width.ToString ()
-                   #else
+#else
                        Assert.Equal (
                                      $"Combine(View(Width,FrameView(){f2.Frame})-Absolute(2))",
                                      v2.Width.ToString ()
-                   #endif
+#endif
                                     );
-                   #if DEBUG
+#if DEBUG
                        Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
-                   #else
+#else
                        Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
-                   #endif
+#endif
                        Assert.Equal (47, v2.Frame.Width); // 49-2=47
                        Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89
 
@@ -757,22 +861,22 @@ public class DimTests
                        Assert.Equal (5, f2.Frame.Height);
 
                        v1.Text = "Button1";
-                   #if DEBUG
+#if DEBUG
                        Assert.Equal ($"Combine(View(Width,FrameView(f1){f1.Frame})-Absolute(2))", v1.Width.ToString ());
-                   #else
+#else
                        Assert.Equal ($"Combine(View(Width,FrameView(){f1.Frame})-Absolute(2))", v1.Width.ToString ());
-                   #endif
+#endif
                        Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ());
                        Assert.Equal (97, v1.Frame.Width);   // 99-2=97
                        Assert.Equal (189, v1.Frame.Height); // 198-2-7=189
 
                        v2.Text = "Button2";
 
-                   #if DEBUG
-                   Assert.Equal ( $"Combine(View(Width,FrameView(f2){f2.Frame})-Absolute(2))", v2.Width.ToString ());
-                   #else
+#if DEBUG
+                       Assert.Equal ($"Combine(View(Width,FrameView(f2){f2.Frame})-Absolute(2))", v2.Width.ToString ());
+#else
                        Assert.Equal ($"Combine(View(Width,FrameView(){f2.Frame})-Absolute(2))", v2.Width.ToString ());
-                   #endif
+#endif
                        Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ());
                        Assert.Equal (97, v2.Frame.Width);   // 99-2=97
                        Assert.Equal (189, v2.Frame.Height); // 198-2-7=189
@@ -782,7 +886,7 @@ public class DimTests
                        Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ());
 
                        // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width
-                       Assert.Equal (19, v3.Frame.Width );
+                       Assert.Equal (19, v3.Frame.Width);
                        // 199*10%=19
                        Assert.Equal (19, v3.Frame.Height);
 
@@ -800,13 +904,13 @@ public class DimTests
 
                        v5.Text = "Button5";
 
-                   #if DEBUG
+#if DEBUG
                        Assert.Equal ($"Combine(View(Width,Button(v1){v1.Frame})-View(Width,Button(v3){v3.Frame}))", v5.Width.ToString ());
                        Assert.Equal ($"Combine(View(Height,Button(v1){v1.Frame})-View(Height,Button(v3){v3.Frame}))", v5.Height.ToString ());
-                   #else
+#else
                        Assert.Equal ($"Combine(View(Width,Button(){v1.Frame})-View(Width,Button(){v3.Frame}))", v5.Width.ToString ());
                        Assert.Equal ($"Combine(View(Height,Button(){v1.Frame})-View(Height,Button(){v3.Frame}))", v5.Height.ToString ());
-                   #endif
+#endif
 
                        Assert.Equal (78, v5.Frame.Width);   // 97-9=78
                        Assert.Equal (170, v5.Frame.Height); // 189-19=170
@@ -880,6 +984,89 @@ public class DimTests
         Assert.Throws<ArgumentException> (() => dim = Dim.Percent (1000001));
     }
 
+    [Theory]
+    [InlineData (0, false, true, 12)]
+    [InlineData (0, false, false, 12)]
+    [InlineData (1, false, true, 12)]
+    [InlineData (1, false, false, 12)]
+    [InlineData (2, false, true, 12)]
+    [InlineData (2, false, false, 12)]
+
+    [InlineData (0, true, true, 12)]
+    [InlineData (0, true, false, 12)]
+    [InlineData (1, true, true, 12)]
+    [InlineData (1, true, false, 12)]
+    [InlineData (2, true, true, 11)]
+    [InlineData (2, true, false, 11)]
+    public void Percent_Position (int position, bool usePosition, bool width, int expected)
+    {
+        var super = new View { Width = 25, Height = 25 };
+
+        var view = new View
+        {
+            X = width ? position : 0,
+            Y = width ? 0 : position,
+            Width = width ? Dim.Percent (50, usePosition) : 1,
+            Height = width ? 1 : Dim.Percent (50, usePosition)
+        };
+
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+        super.LayoutSubviews ();
+
+        Assert.Equal (25, super.Frame.Width);
+        Assert.Equal (25, super.Frame.Height);
+
+        if (width)
+        {
+            Assert.Equal (expected, view.Frame.Width);
+            Assert.Equal (1, view.Frame.Height);
+        }
+        else
+        {
+            Assert.Equal (1, view.Frame.Width);
+            Assert.Equal (expected, view.Frame.Height);
+        }
+    }
+
+    [Theory]
+    [InlineData (0, true)]
+    [InlineData (0, false)]
+    [InlineData (50, true)]
+    [InlineData (50, false)]
+    public void Percent_PlusOne (int startingDistance, bool testHorizontal)
+    {
+        var super = new View { Width = 100, Height = 100 };
+
+        var view = new View
+        {
+            X = testHorizontal ? startingDistance : 0,
+            Y = testHorizontal ? 0 : startingDistance,
+            Width = testHorizontal ? Dim.Percent (50) + 1 : 1,
+            Height = testHorizontal ? 1 : Dim.Percent (50) + 1
+        };
+
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+        super.LayoutSubviews ();
+
+        Assert.Equal (100, super.Frame.Width);
+        Assert.Equal (100, super.Frame.Height);
+
+        if (testHorizontal)
+        {
+            Assert.Equal (51, view.Frame.Width);
+            Assert.Equal (1, view.Frame.Height);
+        }
+        else
+        {
+            Assert.Equal (1, view.Frame.Width);
+            Assert.Equal (51, view.Frame.Height);
+        }
+    }
+
     [Fact]
     public void Percent_SetsValue ()
     {
@@ -898,47 +1085,24 @@ public class DimTests
     // TODO: A new test that calls SetRelativeLayout directly is needed.
     [Fact]
     [TestRespondersDisposed]
-    public void PosCombine_View_Not_Added_Throws ()
+    public void Referencing_SuperView_Does_Not_Throw ()
     {
-        var t = new View { Width = 80, Height = 50 };
-
-        var super = new View { Width = Dim.Width (t) - 2, Height = Dim.Height (t) - 2 };
-        t.Add (super);
-
-        var sub = new View ();
-        super.Add (sub);
-
-        var v1 = new View { Width = Dim.Width (super) - 2, Height = Dim.Height (super) - 2 };
-        var v2 = new View { Width = Dim.Width (v1) - 2, Height = Dim.Height (v1) - 2 };
-        sub.Add (v1);
-
-        // v2 not added to sub; should cause exception on Layout since it's referenced by sub.
-        sub.Width = Dim.Fill () - Dim.Width (v2);
-        sub.Height = Dim.Fill () - Dim.Height (v2);
-
-        t.BeginInit ();
-        t.EndInit ();
+        var super = new View { Width = 10, Height = 10, Text = "super" };
 
-        Assert.Throws<InvalidOperationException> (() => t.LayoutSubviews ());
-        t.Dispose ();
-        v2.Dispose ();
-    }
+        var view = new View
+        {
+            Width = Dim.Width (super), // this is allowed
+            Height = Dim.Height (super), // this is allowed
+            Text = "view"
+        };
 
-    [Fact]
-    [TestRespondersDisposed]
-    public void SetsValue ()
-    {
-        var testVal = Rectangle.Empty;
-        var testValView = new View { Frame = testVal };
-        Dim dim = Dim.Width (testValView);
-        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
-        testValView.Dispose ();
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
 
-        testVal = new Rectangle (1, 2, 3, 4);
-        testValView = new View { Frame = testVal };
-        dim = Dim.Width (testValView);
-        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
-        testValView.Dispose ();
+        Exception exception = Record.Exception (super.LayoutSubviews);
+        Assert.Null (exception);
+        super.Dispose ();
     }
 
     [Fact]
@@ -982,6 +1146,119 @@ public class DimTests
         Assert.Equal ($"Absolute({testVal})", dim.ToString ());
     }
 
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [AutoInitShutdown]
+    public void Subtract_Operator ()
+    {
+        Toplevel top = new Toplevel ();
+
+        var view = new View { X = 0, Y = 0, Width = 20, Height = 0 };
+        var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 };
+        var count = 20;
+        List<Label> listLabels = new ();
+
+        for (var i = 0; i < count; i++)
+        {
+            field.Text = $"Label {i}";
+            var label = new Label { X = 0, Y = view.Viewport.Height, /*Width = 20,*/ Text = field.Text };
+            view.Add (label);
+            Assert.Equal ($"Label {i}", label.Text);
+            Assert.Equal ($"Absolute({i})", label.Y.ToString ());
+            listLabels.Add (label);
+
+            Assert.Equal ($"Absolute({i})", view.Height.ToString ());
+            view.Height += 1;
+            Assert.Equal ($"Absolute({i + 1})", view.Height.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})", view.Height.ToString ());
+                                 view.Height -= 1;
+                                 count--;
+                                 Assert.Equal ($"Absolute({count})", view.Height.ToString ());
+                             }
+                         };
+
+        Application.Iteration += (s, a) =>
+                                 {
+                                     while (count > 0)
+                                     {
+                                         field.NewKeyDownEvent (new Key (KeyCode.Enter));
+                                     }
+
+                                     Application.RequestStop ();
+                                 };
+
+        var win = new Window ();
+        win.Add (view);
+        win.Add (field);
+
+        top.Add (win);
+
+        Application.Run (top);
+        top.Dispose ();
+
+        Assert.Equal (0, count);
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void SyperView_Referencing_SubView_Throws ()
+    {
+        var super = new View { Width = 10, Height = 10, Text = "super" };
+        var view2 = new View { Width = 10, Height = 10, Text = "view2" };
+
+        var view = new View
+        {
+            Width = Dim.Width (view2), // this is not allowed
+            Height = Dim.Height (view2), // this is not allowed
+            Text = "view"
+        };
+
+        view.Add (view2);
+        super.Add (view);
+        super.BeginInit ();
+        super.EndInit ();
+
+        Assert.Throws<InvalidOperationException> (super.LayoutSubviews);
+        super.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Validation_Does_Not_Throw_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
+    {
+        var t = new View { Width = 80, Height = 25, Text = "top" };
+
+        var w = new Window
+        {
+            X = 1,
+            Y = 2,
+            Width = 4,
+            Height = 5,
+            Title = "w"
+        };
+        t.Add (w);
+        t.LayoutSubviews ();
+
+        Assert.Equal (3, w.Width = 3);
+        Assert.Equal (4, w.Height = 4);
+        t.Dispose ();
+    }
+
     [Fact]
     [TestRespondersDisposed]
     public void Width_Equals ()
@@ -1039,4 +1316,21 @@ public class DimTests
         Dim dim = Dim.Width (null);
         Assert.Throws<NullReferenceException> (() => dim.ToString ());
     }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void Width_SetsValue ()
+    {
+        var testVal = Rectangle.Empty;
+        var testValView = new View { Frame = testVal };
+        Dim dim = Dim.Width (testValView);
+        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
+        testValView.Dispose ();
+
+        testVal = new Rectangle (1, 2, 3, 4);
+        testValView = new View { Frame = testVal };
+        dim = Dim.Width (testValView);
+        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
+        testValView.Dispose ();
+    }
 }

+ 180 - 35
UnitTests/View/Layout/PosTests.cs

@@ -7,7 +7,7 @@ namespace Terminal.Gui.ViewTests;
 public class PosTests (ITestOutputHelper output)
 {
     [Fact]
-    public void PosAbsolute_GetLocation_ReturnsExpectedValue ()
+    public void PosAbsolute_Calculate_ReturnsExpectedValue ()
     {
         var posAbsolute = new PosAbsolute (5);
         var result = posAbsolute.Calculate (10, new DimAbsolute (2), 1, false);
@@ -15,7 +15,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void PosAnchorEnd_GetLocation_ReturnsExpectedValue ()
+    public void PosAnchorEnd_Calculate_ReturnsExpectedValue ()
     {
         var posAnchorEnd = new PosAnchorEnd (5);
         var result = posAnchorEnd.Calculate (10, new DimAbsolute (2), 1, false);
@@ -23,7 +23,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void PosCenter_GetLocation_ReturnsExpectedValue ()
+    public void PosCenter_Calculate_ReturnsExpectedValue ()
     {
         var posCenter = new PosCenter ();
         var result = posCenter.Calculate (10, new DimAbsolute (2), 1, false);
@@ -31,7 +31,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void PosCombine_GetLocation_ReturnsExpectedValue ()
+    public void PosCombine_Calculate_ReturnsExpectedValue ()
     {
         var posCombine = new PosCombine (true, new PosAbsolute (5), new PosAbsolute (3));
         var result = posCombine.Calculate (10, new DimAbsolute (2), 1, false);
@@ -39,7 +39,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void PosFactor_GetLocation_ReturnsExpectedValue ()
+    public void PosFactor_Calculate_ReturnsExpectedValue ()
     {
         var posFactor = new PosFactor (0.5f);
         var result = posFactor.Calculate (10, new DimAbsolute (2), 1, false);
@@ -47,7 +47,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void PosFunc_GetLocation_ReturnsExpectedValue ()
+    public void PosFunc_Calculate_ReturnsExpectedValue ()
     {
         var posFunc = new PosFunc (() => 5);
         var result = posFunc.Calculate (10, new DimAbsolute (2), 1, false);
@@ -55,7 +55,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void PosView_GetLocation_ReturnsExpectedValue ()
+    public void PosView_Calculate_ReturnsExpectedValue ()
     {
         var posView = new PosView (new View { Frame = new Rectangle (5, 5, 10, 10) }, 0);
         var result = posView.Calculate (10, new DimAbsolute (2), 1, false);
@@ -93,8 +93,60 @@ public class PosTests (ITestOutputHelper output)
         Assert.Equal ("Center", pos.ToString ());
     }
 
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    public void Combine_Referencing_Same_View ()
+    {
+        var super = new View { Width = 10, Height = 10, Text = "super" };
+        var view1 = new View { Width = 2, Height = 2, Text = "view1" };
+        var view2 = new View { Width = 2, Height = 2, Text = "view2" };
+        view2.X = Pos.AnchorEnd () - (Pos.Right (view2) - Pos.Left (view2));
+
+        super.Add (view1, view2);
+        super.BeginInit ();
+        super.EndInit ();
+
+        Exception exception = Record.Exception (super.LayoutSubviews);
+        Assert.Null (exception);
+        Assert.Equal (new Rectangle (0, 0, 10, 10), super.Frame);
+        Assert.Equal (new Rectangle (0, 0, 2, 2), view1.Frame);
+        Assert.Equal (new Rectangle (8, 0, 2, 2), view2.Frame);
+
+        super.Dispose ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Combine_WHY_Throws ()
+    {
+        Application.Init (new FakeDriver ());
+
+        Toplevel t = new Toplevel();
+
+        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 (t));
+        t.Dispose ();
+        Application.Shutdown ();
+
+        v2.Dispose ();
+    }
+
     [Fact]
-    public void DoNotReturnPosCombine ()
+    public void DoesNotReturnPosCombine ()
     {
         var v = new View { Id = "V" };
 
@@ -338,30 +390,6 @@ public class PosTests (ITestOutputHelper output)
         Assert.NotEqual (pos1, pos2);
     }
 
-    [Fact]
-    public void Percent_SetsValue ()
-    {
-        float f = 0;
-        Pos 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_ThrowsOnIvalid ()
-    {
-        Pos 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));
-    }
-
     // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
     // TODO: A new test that calls SetRelativeLayout directly is needed.
     [Fact]
@@ -572,7 +600,7 @@ public class PosTests (ITestOutputHelper output)
     [AutoInitShutdown]
     [InlineData (true)]
     [InlineData (false)]
-    public void PosPercentPlusOne (bool testHorizontal)
+    public void Percent_PlusOne (bool testHorizontal)
     {
         var container = new View { Width = 100, Height = 100 };
 
@@ -604,12 +632,36 @@ public class PosTests (ITestOutputHelper output)
         }
     }
 
+    [Fact]
+    public void Percent_SetsValue ()
+    {
+        float f = 0;
+        Pos 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_ThrowsOnIvalid ()
+    {
+        Pos 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));
+    }
+
     // 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 ()
+    public void Side_SetsValue ()
     {
         string side; // used in format string
         var testRect = Rectangle.Empty;
@@ -834,7 +886,7 @@ public class PosTests (ITestOutputHelper output)
     }
 
     [Fact]
-    public void SetSide_Null_Throws ()
+    public void Side_SetToNull_Throws ()
     {
         Pos pos = Pos.Left (null);
         Assert.Throws<NullReferenceException> (() => pos.ToString ());
@@ -854,4 +906,97 @@ public class PosTests (ITestOutputHelper output)
         pos = Pos.Right (null);
         Assert.Throws<NullReferenceException> (() => pos.ToString ());
     }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    [TestRespondersDisposed]
+    public void Subtract_Operator ()
+    {
+        Application.Init (new FakeDriver ());
+
+        Toplevel top = new Toplevel ();
+
+        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;
+        List<View> listViews = new ();
+
+        for (var i = 0; i < count; i++)
+        {
+            field.Text = $"View {i}";
+            var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text };
+            view.Add (view2);
+            Assert.Equal ($"View {i}", view2.Text);
+            Assert.Equal ($"Absolute({i})", field.Y.ToString ());
+            listViews.Add (view2);
+
+            Assert.Equal ($"Absolute({i})", field.Y.ToString ());
+            field.Y += 1;
+            Assert.Equal ($"Absolute({i + 1})", field.Y.ToString ());
+        }
+
+        field.KeyDown += (s, k) =>
+                         {
+                             if (k.KeyCode == KeyCode.Enter)
+                             {
+                                 Assert.Equal ($"View {count - 1}", listViews [count - 1].Text);
+                                 view.Remove (listViews [count - 1]);
+                                 listViews [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 Key (KeyCode.Enter));
+                                     }
+
+                                     Application.RequestStop ();
+                                 };
+
+        var win = new Window ();
+        win.Add (view);
+        win.Add (field);
+
+        top.Add (win);
+
+        Application.Run (top);
+        top.Dispose ();
+        Assert.Equal (0, count);
+
+        // Shutdown must be called to safely clean up Application if Init has been called
+        Application.Shutdown ();
+    }
+
+    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
+    // TODO: A new test that calls SetRelativeLayout directly is needed.
+    [Fact]
+    public void Validation_Does_Not_Throw_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
+    {
+        Application.Init (new FakeDriver ());
+
+        Toplevel t = new Toplevel ();
+
+        var w = new Window { X = 1, Y = 2, Width = 3, Height = 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 ();
+
+        Application.Run (t);
+        t.Dispose ();
+        Application.Shutdown ();
+    }
 }

+ 70 - 70
UnitTests/View/Text/AutoSizeTrueTests.cs

@@ -930,31 +930,31 @@ public class AutoSizeTrueTests
         Application.Begin (top);
         ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-        Size size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 1), size);
+        Size size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 1), size);
 
         view.Text = $"{text}\n{text}";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 2), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 2), size);
 
         view.Text = $"{text}\n{text}\n{text}+";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length + 1, 3), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length + 1, 3), size);
 
         text = string.Empty;
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (Size.Empty, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (0, 0), size);
 
         text = "1";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (_size1x1, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (1, 1), size);
 
         text = "界";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (new (2, 1), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (2, 1), size);
     }
 
     [Fact]
@@ -970,31 +970,31 @@ public class AutoSizeTrueTests
         Application.Begin (top);
         ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-        Size size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 1), size);
+        Size size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 1), size);
 
         view.Text = $"{text}\n{text}";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 2), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 2), size);
 
         view.Text = $"{text}\n{text}\n{text}+";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length + 1, 3), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length + 1, 3), size);
 
         text = string.Empty;
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (Size.Empty, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (0, 0), size);
 
         text = "1";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (_size1x1, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (1, 1), size);
 
         text = "界";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (new (2, 1), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (2, 1), size);
     }
 
     [Fact]
@@ -1010,31 +1010,31 @@ public class AutoSizeTrueTests
         Application.Begin (top);
         ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-        Size size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 1), size);
+        Size size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 1), size);
 
         view.Text = $"{text}\n{text}";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 2), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 2), size);
 
         view.Text = $"{text}\n{text}\n{text}+";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length + 1, 3), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length + 1, 3), size);
 
         text = string.Empty;
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (Size.Empty, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (0, 0), size);
 
         text = "1";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (_size1x1, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (1, 1), size);
 
         text = "界";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (new (2, 1), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (2, 1), size);
     }
 
     [Fact]
@@ -1050,31 +1050,31 @@ public class AutoSizeTrueTests
         Application.Begin (top);
         ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-        Size size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 1), size);
+        Size size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 1), size);
 
         view.Text = $"{text}\n{text}";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length, 2), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length, 2), size);
 
         view.Text = $"{text}\n{text}\n{text}+";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (text.Length + 1, 3), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (text.Length + 1, 3), size);
 
         text = string.Empty;
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (Size.Empty, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (0, 0), size);
 
         text = "1";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (_size1x1, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (1, 1), size);
 
         text = "界";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (new (2, 1), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (2, 1), size);
     }
 
     [Fact]
@@ -1090,31 +1090,31 @@ public class AutoSizeTrueTests
         Application.Begin (top);
         ((FakeDriver)Application.Driver).SetBufferSize (10, 4);
 
-        Size size = view.GetAutoSize ();
-        Assert.Equal (new (1, text.Length), size);
+        Size size = view.GetTextAutoSize();
+        Assert.Equal (new Size (1, text.Length), size);
 
         view.Text = $"{text}\n{text}";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (2, text.Length), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (2, text.Length), size);
 
         view.Text = $"{text}\n{text}\n{text}+";
-        size = view.GetAutoSize ();
-        Assert.Equal (new (3, text.Length + 1), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (3, text.Length + 1), size);
 
         text = string.Empty;
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (Size.Empty, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (0, 0), size);
 
         text = "1";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (_size1x1, size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (1, 1), size);
 
         text = "界";
         view.Text = text;
-        size = view.GetAutoSize ();
-        Assert.Equal (new (2, 1), size);
+        size = view.GetTextAutoSize();
+        Assert.Equal (new Size (2, 1), size);
     }
 
     [Fact]
@@ -2134,7 +2134,7 @@ Y
     [AutoInitShutdown]
     public void AutoSize_True_Width_Height_Stay_True_If_TextFormatter_Size_Fit ()
     {
-        var text = "Fi_nish 終";
+        var text = "Finish 終";
 
         var horizontalView = new View
         {
@@ -2159,18 +2159,17 @@ Y
         Assert.True (horizontalView.AutoSize);
         Assert.True (verticalView.AutoSize);
         Assert.Equal (new (text.GetColumns (), 1), horizontalView.TextFormatter.Size);
-        Assert.Equal (new (2, 9), verticalView.TextFormatter.Size);
-        Assert.Equal (new (0, 0, 10, 1), horizontalView.Frame);
-        Assert.Equal (new (0, 3, 10, 9), verticalView.Frame);
+        Assert.Equal (new (2, 8), verticalView.TextFormatter.Size);
+        //Assert.Equal (new (0, 0, 10, 1), horizontalView.Frame);
+        //Assert.Equal (new (0, 3, 10, 9), verticalView.Frame);
 
         var expected = @"
 ┌────────────────────┐
-│Fi_nish 終          │
+│Finish 終 
 │                    │
 │                    │
 │F                   │
 │i                   │
-│_                   │
 │n                   │
 │i                   │
 │s                   │
@@ -2185,27 +2184,27 @@ Y
 │                    │
 │                    │
 │                    │
+│                    │
 └────────────────────┘
 ";
 
         Rectangle pos = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
 
-        verticalView.Text = "最初_の行二行目";
+        verticalView.Text = "最初の行二行目";
         Application.Top.Draw ();
         Assert.True (horizontalView.AutoSize);
         Assert.True (verticalView.AutoSize);
 
         // height was initialized with 8 and can only grow or keep initial value
-        Assert.Equal (new Rectangle (0, 3, 10, 9), verticalView.Frame);
+        Assert.Equal (new Rectangle (0, 3, 9, 8), verticalView.Frame);
 
         expected = @"
 ┌────────────────────┐
-│Fi_nish 終          │
+│Finish 終 
 │                    │
 │                    │
 │最                  │
 │初                  │
-│_                   │
 │の                  │
 │行                  │
 │二                  │
@@ -2220,6 +2219,7 @@ Y
 │                    │
 │                    │
 │                    │
+│                    │
 └────────────────────┘
 ";
 

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