瀏覽代碼

Improved DimPos tests

Tig Kindel 1 年之前
父節點
當前提交
cf9b8eda93

+ 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>

+ 125 - 174
Terminal.Gui/View/Layout/PosDim.cs

@@ -25,15 +25,6 @@
 ///             </listheader>
 ///             <item>
 ///                 <term>
-///                     <see cref="Dim.Auto"/>
-///                 </term>
-///                 <description>
-///                     Creates a <see cref="Dim"/> object that automatically sizes the view to fit
-///                     all of the view's SubViews.
-///                 </description>
-///             </item>
-///             <item>
-///                 <term>
 ///                     <see cref="Pos.Function(Func{int})"/>
 ///                 </term>
 ///                 <description>
@@ -52,7 +43,7 @@
 ///             </item>
 ///             <item>
 ///                 <term>
-///                     <see cref="Pos.Anchor(int)"/>
+///                     <see cref="Pos.AnchorEnd(int)"/>
 ///                 </term>
 ///                 <description>
 ///                     Creates a <see cref="Pos"/> object that is anchored to the end (right side or bottom) of
@@ -141,8 +132,7 @@ public class Pos
     /// <param name="offset">The view will be shifted left or up by the amount specified.</param>
     /// <example>
     ///     This sample shows how align a <see cref="Button"/> to the bottom-right of a <see cref="View"/>.
-    ///     <code>
-    /// // See Issue #502 
+    /// <code>
     /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
     /// anchorButton.Y = Pos.AnchorEnd (1);
     /// </code>
@@ -168,19 +158,19 @@ public class Pos
     /// </summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Bottom (View view) { return new PosView (view, 3); }
+    public static Pos Bottom (View view) { return new PosView (view, Side.Bottom); }
 
     /// <summary>Creates a <see cref="Pos"/> object that can be used to center the <see cref="View"/>.</summary>
     /// <returns>The center Pos.</returns>
     /// <example>
-    ///     This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, is 30% the height, and
+    ///     This creates a <see cref="TextView"/> centered horizontally, is 50% of the way down, is 30% the height, and
     ///     is 80% the width of the <see cref="View"/> it added to.
-    ///     <code>
+    ///  <code>
     ///  var textView = new TextView () {
-    /// 	X = Pos.Center (),
-    /// 	Y = Pos.Percent (50),
-    /// 	Width = Dim.Percent (80),
-    ///  	Height = Dim.Percent (30),
+    ///     X = Pos.Center (),
+    ///     Y = Pos.Percent (50),
+    ///     Width = Dim.Percent (80),
+    ///     Height = Dim.Percent (30),
     ///  };
     ///  </code>
     /// </example>
@@ -209,7 +199,7 @@ public class Pos
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Left (View view) { return new PosView (view, 0); }
+    public static Pos Left (View view) { return new PosView (view, Side.X); }
 
     /// <summary>Adds a <see cref="Terminal.Gui.Pos"/> to a <see cref="Terminal.Gui.Pos"/>, yielding a new <see cref="Pos"/>.</summary>
     /// <param name="left">The first <see cref="Terminal.Gui.Pos"/> to add.</param>
@@ -255,27 +245,27 @@ public class Pos
 
     /// <summary>Creates a percentage <see cref="Pos"/> object</summary>
     /// <returns>The percent <see cref="Pos"/> object.</returns>
-    /// <param name="n">A value between 0 and 100 representing the percentage.</param>
+    /// <param name="percent">A value between 0 and 100 representing the percentage.</param>
     /// <example>
-    ///     This creates a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, is 30% the height, and
+    ///     This creates a <see cref="TextField"/> centered horizontally, is 50% of the way down, is 30% the height, and
     ///     is 80% the width of the <see cref="View"/> it added to.
-    ///     <code>
-    ///  var textView = new TextView () {
-    /// 	X = Pos.Center (),
-    /// 	Y = Pos.Percent (50),
-    /// 	Width = Dim.Percent (80),
-    ///  	Height = Dim.Percent (30),
+    ///  <code>
+    ///  var textView = new TextField {
+    ///      X = Pos.Center (),
+    ///      Y = Pos.Percent (50),
+    ///      Width = Dim.Percent (80),
+    ///      Height = Dim.Percent (30),
     ///  };
     ///  </code>
     /// </example>
-    public static Pos Percent (float n)
+    public static Pos Percent (float percent)
     {
-        if (n is < 0 or > 100)
+        if (percent is < 0 or > 100)
         {
-            throw new ArgumentException ("Percent value must be between 0 and 100");
+            throw new ArgumentException ("Percent value must be between 0 and 100.");
         }
 
-        return new PosFactor (n / 100);
+        return new PosFactor (percent / 100);
     }
 
     /// <summary>
@@ -284,49 +274,46 @@ public class Pos
     /// </summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Right (View view) { return new PosView (view, 2); }
+    public static Pos Right (View view) { return new PosView (view, Side.Right); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Top (View view) { return new PosView (view, 1); }
+    public static Pos Top (View view) { return new PosView (view, Side.Y); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Left (X) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos X (View view) { return new PosView (view, 0); }
+    public static Pos X (View view) { return new PosView (view, Side.X); }
 
     /// <summary>Creates a <see cref="Pos"/> object that tracks the Top (Y) position of the specified <see cref="View"/>.</summary>
     /// <returns>The <see cref="Pos"/> that depends on the other view.</returns>
     /// <param name="view">The <see cref="View"/>  that will be tracked.</param>
-    public static Pos Y (View view) { return new PosView (view, 1); }
+    public static Pos Y (View view) { return new PosView (view, Side.Y); }
 
     internal virtual int Anchor (int width) { return 0; }
 
+    // BUGBUG: newPos is never used
     private static void SetPosCombine (Pos left, PosCombine newPos)
     {
-        var view = left as PosView;
-
-        if (view != null)
+        if (left is PosView view)
         {
             view.Target.SetNeedsLayout ();
         }
     }
 
-    internal class PosAbsolute : Pos
+    internal class PosAbsolute (int n) : Pos
     {
-        private readonly int _n;
-        public PosAbsolute (int n) { _n = n; }
+        private readonly int _n = n;
         public override bool Equals (object other) { return other is PosAbsolute abs && abs._n == _n; }
         public override int GetHashCode () { return _n.GetHashCode (); }
         public override string ToString () { return $"Absolute({_n})"; }
         internal override int Anchor (int width) { return _n; }
     }
 
-    internal class PosAnchorEnd : Pos
+    internal class PosAnchorEnd (int offset) : Pos
     {
-        private readonly int _offset;
-        public PosAnchorEnd (int offset) { _offset = offset; }
+        private readonly int _offset = offset;
         public override bool Equals (object other) { return other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; }
         public override int GetHashCode () { return _offset.GetHashCode (); }
         public override string ToString () { return $"AnchorEnd({_offset})"; }
@@ -339,17 +326,10 @@ public class Pos
         internal override int Anchor (int width) { return width / 2; }
     }
 
-    internal class PosCombine : Pos
+    internal class PosCombine (bool add, Pos left, Pos right) : Pos
     {
-        internal bool _add;
-        internal Pos _left, _right;
-
-        public PosCombine (bool add, Pos left, Pos right)
-        {
-            _left = left;
-            _right = right;
-            _add = add;
-        }
+        internal bool _add = add;
+        internal Pos _left = left, _right = right;
 
         public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; }
 
@@ -367,10 +347,9 @@ public class Pos
         }
     }
 
-    internal class PosFactor : Pos
+    internal class PosFactor (float factor) : Pos
     {
-        private readonly float _factor;
-        public PosFactor (float n) { _factor = n; }
+        private readonly float _factor = factor;
         public override bool Equals (object other) { return other is PosFactor f && f._factor == _factor; }
         public override int GetHashCode () { return _factor.GetHashCode (); }
         public override string ToString () { return $"Factor({_factor})"; }
@@ -378,75 +357,57 @@ public class Pos
     }
 
     // Helper class to provide dynamic value by the execution of a function that returns an integer.
-    internal class PosFunc : Pos
+    internal class PosFunc (Func<int> n) : Pos
     {
-        private readonly Func<int> _function;
-        public PosFunc (Func<int> n) { _function = n; }
+        private readonly Func<int> _function = n;
         public override bool Equals (object other) { return other is PosFunc f && f._function () == _function (); }
         public override int GetHashCode () { return _function.GetHashCode (); }
         public override string ToString () { return $"PosFunc({_function ()})"; }
         internal override int Anchor (int width) { return _function (); }
     }
 
-    internal class PosView : Pos
+    internal enum Side
     {
-        public readonly View Target;
-
-        private readonly int side;
+        X = 0,
+        Y = 1,
+        Right = 2,
+        Bottom = 3,
+    }
 
-        public PosView (View view, int side)
-        {
-            Target = view;
-            this.side = side;
-        }
+    internal class PosView (View view, Side side) : Pos
+    {
+        public readonly View Target = view;
 
         public override bool Equals (object other) { return other is PosView abs && abs.Target == Target; }
         public override int GetHashCode () { return Target.GetHashCode (); }
 
         public override string ToString ()
         {
-            string tside;
-
-            switch (side)
-            {
-                case 0:
-                    tside = "x";
-
-                    break;
-                case 1:
-                    tside = "y";
-
-                    break;
-                case 2:
-                    tside = "right";
-
-                    break;
-                case 3:
-                    tside = "bottom";
-
-                    break;
-                default:
-                    tside = "unknown";
-
-                    break;
-            }
+            string side1 = side switch
+                           {
+                               Side.X => "x",
+                               Side.Y => "y",
+                               Side.Right => "right",
+                               Side.Bottom => "bottom",
+                               _ => "unknown"
+                           };
 
             if (Target == null)
             {
                 throw new NullReferenceException (nameof (Target));
             }
 
-            return $"View(side={tside},target={Target})";
+            return $"View(side={side1},target={Target})";
         }
 
         internal override int Anchor (int width)
         {
             switch (side)
             {
-                case 0: return Target.Frame.X;
-                case 1: return Target.Frame.Y;
-                case 2: return Target.Frame.Right;
-                case 3: return Target.Frame.Bottom;
+                case Side.X: return Target.Frame.X;
+                case Side.Y: return Target.Frame.Y;
+                case Side.Right: return Target.Frame.Right;
+                case Side.Bottom: return Target.Frame.Bottom;
                 default:
                     return 0;
             }
@@ -474,6 +435,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>
@@ -495,8 +465,8 @@ public class Pos
 ///                     <see cref="Dim.Fill(int)"/>
 ///                 </term>
 ///                 <description>
-///                     Creates a <see cref="Dim"/> object that fills the dimension, leaving the specified number
-///                     of columns for a margin.
+///                     Creates a <see cref="Dim"/> object that fills the dimension from the View's X position
+///                     to the end of the super view's width, leaving the specified number of columns for a margin.
 ///                 </description>
 ///             </item>
 ///             <item>
@@ -612,11 +582,11 @@ public class Dim
     /// <summary>Creates a <see cref="Dim"/> object that tracks the Height of the specified <see cref="View"/>.</summary>
     /// <returns>The height <see cref="Dim"/> of the other <see cref="View"/>.</returns>
     /// <param name="view">The view that will be tracked.</param>
-    public static Dim Height (View view) { return new DimView (view, 0); }
+    public static Dim Height (View view) { return new DimView (view, Side.Height); }
 
-    /// <summary>Adds a <see cref="Terminal.Gui.Dim"/> to a <see cref="Terminal.Gui.Dim"/>, yielding a new <see cref="Dim"/>.</summary>
-    /// <param name="left">The first <see cref="Terminal.Gui.Dim"/> to add.</param>
-    /// <param name="right">The second <see cref="Terminal.Gui.Dim"/> to add.</param>
+    /// <summary>Adds a <see cref="Dim"/> to a <see cref="Dim"/>, yielding a new <see cref="Dim"/>.</summary>
+    /// <param name="left">The first <see cref="Dim"/> to add.</param>
+    /// <param name="right">The second <see cref="Dim"/> to add.</param>
     /// <returns>The <see cref="Dim"/> that is the sum of the values of <c>left</c> and <c>right</c>.</returns>
     public static Dim operator + (Dim left, Dim right)
     {
@@ -637,11 +607,11 @@ public class Dim
     public static implicit operator Dim (int n) { return new DimAbsolute (n); }
 
     /// <summary>
-    ///     Subtracts a <see cref="Terminal.Gui.Dim"/> from a <see cref="Terminal.Gui.Dim"/>, yielding a new
+    ///     Subtracts a <see cref="Dim"/> from a <see cref="Dim"/>, yielding a new
     ///     <see cref="Dim"/>.
     /// </summary>
-    /// <param name="left">The <see cref="Terminal.Gui.Dim"/> to subtract from (the minuend).</param>
-    /// <param name="right">The <see cref="Terminal.Gui.Dim"/> to subtract (the subtrahend).</param>
+    /// <param name="left">The <see cref="Dim"/> to subtract from (the minuend).</param>
+    /// <param name="right">The <see cref="Dim"/> to subtract (the subtrahend).</param>
     /// <returns>The <see cref="Dim"/> that is the <c>left</c> minus <c>right</c>.</returns>
     public static Dim operator - (Dim left, Dim right)
     {
@@ -658,31 +628,31 @@ public class Dim
 
     /// <summary>Creates a percentage <see cref="Dim"/> object that is a percentage of the width or height of the SuperView.</summary>
     /// <returns>The percent <see cref="Dim"/> object.</returns>
-    /// <param name="n">A value between 0 and 100 representing the percentage.</param>
-    /// <param name="r">
-    ///     If <c>true</c> the Percent is computed based on the remaining space after the X/Y anchor positions. If
-    ///     <c>false</c> is computed based on the whole original space.
+    /// <param name="percent">A value between 0 and 100 representing the percentage.</param>
+    /// <param name="usePosition">
+    ///     If <see langword="true"/> the dimension is computed using the View's position (<see cref="View.X"/> or <see cref="View.Y"/>).
+    ///     If <see langword="false"/> the dimension is computed using the View's <see cref="View.Bounds"/>.
     /// </param>
     /// <example>
-    ///     This initializes a <see cref="TextField"/>that is centered horizontally, is 50% of the way down, is 30% the height,
-    ///     and is 80% the width of the <see cref="View"/> it added to.
-    ///     <code>
-    ///  var textView = new TextView () {
-    /// 	X = Pos.Center (),
-    /// 	Y = Pos.Percent (50),
-    /// 	Width = Dim.Percent (80),
-    ///  	Height = Dim.Percent (30),
+    ///     This initializes a <see cref="TextField"/> that will be centered horizontally, is 50% of the way down, is 30% the height,
+    ///     and is 80% the width of the SuperView.
+    ///  <code>
+    ///  var textView = new TextField {
+    ///     X = Pos.Center (),
+    ///     Y = Pos.Percent (50),
+    ///     Width = Dim.Percent (80),
+    ///     Height = Dim.Percent (30),
     ///  };
     ///  </code>
     /// </example>
-    public static Dim Percent (float n, bool r = false)
+    public static Dim Percent (float percent, bool usePosition = false)
     {
-        if (n is < 0 or > 100)
+        if (percent is < 0 or > 100)
         {
             throw new ArgumentException ("Percent value must be between 0 and 100");
         }
 
-        return new DimFactor (n / 100, r);
+        return new DimFactor (percent / 100, usePosition);
     }
 
     /// <summary>Creates an Absolute <see cref="Dim"/> from the specified integer value.</summary>
@@ -693,54 +663,37 @@ public class Dim
     /// <summary>Creates a <see cref="Dim"/> object that tracks the Width of the specified <see cref="View"/>.</summary>
     /// <returns>The width <see cref="Dim"/> of the other <see cref="View"/>.</returns>
     /// <param name="view">The view that will be tracked.</param>
-    public static Dim Width (View view) { return new DimView (view, 1); }
+    public static Dim Width (View view) { return new DimView (view, Side.Width); }
 
     internal virtual int Anchor (int width) { return 0; }
 
     // BUGBUG: newPos is never used.
     private static void SetDimCombine (Dim left, DimCombine newPos) { (left as DimView)?.Target.SetNeedsLayout (); }
 
-    internal class DimAbsolute : Dim
+    internal class DimAbsolute (int n) : Dim
     {
-        private readonly int _n;
-        public DimAbsolute (int n) { _n = n; }
+        private readonly int _n = n;
         public override bool Equals (object other) { return other is DimAbsolute abs && abs._n == _n; }
         public override int GetHashCode () { return _n.GetHashCode (); }
         public override string ToString () { return $"Absolute({_n})"; }
         internal override int Anchor (int width) { return _n; }
     }
 
-    internal class DimAuto : Dim
+    internal class DimAuto (DimAutoStyle style, Dim min, Dim max) : Dim
     {
-        internal readonly Dim _max;
-        internal readonly Dim _min;
-        internal readonly DimAutoStyle _style;
-
-        public DimAuto (DimAutoStyle style, Dim min, Dim max)
-        {
-            _min = min;
-            _max = max;
-            _style = style;
-        }
+        internal readonly Dim _max = max;
+        internal readonly Dim _min = min;
+        internal readonly DimAutoStyle _style = style;
 
         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 class DimCombine : Dim
+    internal class DimCombine (bool add, Dim left, Dim right) : Dim
     {
-        internal bool _add;
-        internal Dim _left, _right;
-
-        public DimCombine (bool add, Dim left, Dim right)
-        {
-            _left = left;
-            _right = right;
-            _add = add;
-        }
+        internal bool _add = add;
+        internal Dim _left = left, _right = right;
 
         public override string ToString () { return $"Combine({_left}{(_add ? '+' : '-')}{_right})"; }
 
@@ -758,16 +711,10 @@ public class Dim
         }
     }
 
-    internal class DimFactor : Dim
+    internal class DimFactor (float n, bool usePosition = false) : Dim
     {
-        private readonly float _factor;
-        private readonly bool _remaining;
-
-        public DimFactor (float n, bool r = false)
-        {
-            _factor = n;
-            _remaining = r;
-        }
+        private readonly float _factor = n;
+        private readonly bool _remaining = usePosition;
 
         public override bool Equals (object other) { return other is DimFactor f && f._factor == _factor && f._remaining == _remaining; }
         public override int GetHashCode () { return _factor.GetHashCode (); }
@@ -776,10 +723,9 @@ public class Dim
         internal override int Anchor (int width) { return (int)(width * _factor); }
     }
 
-    internal class DimFill : Dim
+    internal class DimFill (int margin) : Dim
     {
-        private readonly int _margin;
-        public DimFill (int margin) { _margin = margin; }
+        private readonly int _margin = margin;
         public override bool Equals (object other) { return other is DimFill fill && fill._margin == _margin; }
         public override int GetHashCode () { return _margin.GetHashCode (); }
         public override string ToString () { return $"Fill({_margin})"; }
@@ -787,21 +733,26 @@ public class Dim
     }
 
     // Helper class to provide dynamic value by the execution of a function that returns an integer.
-    internal class DimFunc : Dim
+    internal class DimFunc (Func<int> n) : Dim
     {
-        private readonly Func<int> _function;
-        public DimFunc (Func<int> n) { _function = n; }
+        private readonly Func<int> _function = n;
         public override bool Equals (object other) { return other is DimFunc f && f._function () == _function (); }
         public override int GetHashCode () { return _function.GetHashCode (); }
         public override string ToString () { return $"DimFunc({_function ()})"; }
         internal override int Anchor (int width) { return _function (); }
     }
 
+    internal enum Side
+    {
+        Height = 0,
+        Width = 1
+    }
+
     internal class DimView : Dim
     {
-        private readonly int _side;
+        private readonly Side _side;
 
-        public DimView (View view, int side)
+        internal DimView (View view, Side side)
         {
             Target = view;
             _side = side;
@@ -818,22 +769,22 @@ public class Dim
                 throw new NullReferenceException ();
             }
 
-            string tside = _side switch
+            string side = _side switch
                            {
-                               0 => "Height",
-                               1 => "Width",
+                               Side.Height => "Height",
+                               Side.Width => "Width",
                                _ => "unknown"
                            };
 
-            return $"View({tside},{Target})";
+            return $"View({side},{Target})";
         }
 
         internal override int Anchor (int width)
         {
             return _side switch
                    {
-                       0 => Target.Frame.Height,
-                       1 => Target.Frame.Width,
+                       Side.Height => Target.Frame.Height,
+                       Side.Width => Target.Frame.Width,
                        _ => 0
                    };
         }

+ 24 - 13
Terminal.Gui/View/Layout/ViewLayout.cs

@@ -1064,14 +1064,17 @@ public partial class View
                         var text = 0;
                         var subviews = 0;
 
-                        if (auto._style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto)
+                        int superviewSize = width ? superviewBounds.Width : superviewBounds.Height;
+                        int autoMin = auto._min?.Anchor (superviewSize) ?? 0;
+
+                        if (superviewSize < autoMin)
                         {
-                            if (Id == "vlabel")
-                            { }
+                            Debug.WriteLine ($"WARNING: DimAuto specifies a min size ({autoMin}), but the SuperView's bounds are smaller ({superviewSize}).");
+                        }
 
-                            text = int.Max (
-                                            width ? TextFormatter.Size.Width : TextFormatter.Size.Height,
-                                            auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0);
+                        if (auto._style is Dim.DimAutoStyle.Text or Dim.DimAutoStyle.Auto)
+                        {
+                            text = int.Max (width ? TextFormatter.Size.Width : TextFormatter.Size.Height, autoMin);
                         }
 
                         if (auto._style is Dim.DimAutoStyle.Subviews or Dim.DimAutoStyle.Auto)
@@ -1085,9 +1088,9 @@ public partial class View
 
                         int max = int.Max (text, subviews);
 
-                        newDimension = int.Max (
-                                                width ? max + thickness.Left + thickness.Right : max + thickness.Top + thickness.Bottom,
-                                                auto._min?.Anchor (width ? superviewBounds.Width : superviewBounds.Height) ?? 0);
+                        newDimension = int.Max (width ? max + thickness.Left + thickness.Right : max + thickness.Top + thickness.Bottom, autoMin);
+                        // If _max is set, use it. Otherwise, use superview;'s size to constrain
+                        newDimension = int.Min (newDimension, auto._max?.Anchor (superviewSize) ?? superviewSize);
 
                         break;
 
@@ -1101,6 +1104,12 @@ public partial class View
                         break;
 
                     case Dim.DimFill:
+                        // Fills the remaining space. 
+                        newDimension = Math.Max (d.Anchor (dimension - location), 0 /* width ? superviewBounds.Width : superviewBounds.Height*/);
+                        newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
+
+                        break;
+
                     default:
                         newDimension = Math.Max (d.Anchor (dimension - location), 0);
                         newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
@@ -1468,6 +1477,7 @@ public partial class View
                     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);
@@ -1479,6 +1489,7 @@ public partial class View
                     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);
@@ -1590,9 +1601,9 @@ public partial class View
     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 {pos}. 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 pos;
@@ -1602,9 +1613,9 @@ public partial class View
     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: {dim}. 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 dim;

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

@@ -60,9 +60,13 @@ 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));
-
+#if DIMAUTO
+        Width = Dim.Auto (min: Dim.Percent (10));
+        Height = Dim.Auto (min: Dim.Percent (50));
+#else
+        Width = Dim.Percent (85); 
+        Height = Dim.Percent (85);
+#endif
         ColorScheme = Colors.ColorSchemes ["Dialog"];
 
         Modal = true;

+ 14 - 14
UICatalog/Scenarios/DimAutoDemo.cs

@@ -111,8 +111,6 @@ public class DimAutoDemo : Scenario
             Text = "_Move down",
             X = Pos.Right (vlabel),
             Y = Pos.Bottom (vlabel),
-            AutoSize = false,
-            Width = 10
         };
         movingButton.Clicked += (s, e) => { movingButton.Y = movingButton.Frame.Y + 1; };
         view.Add (movingButton);
@@ -155,6 +153,7 @@ public class DimAutoDemo : Scenario
 
         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 (),
@@ -166,24 +165,25 @@ public class DimAutoDemo : Scenario
 
         var text = new TextField
         {
-            Text = "TextField... X = 1; Y = Pos.Bottom (label), Width = Dim.Fill (1); Height = Dim.Fill(1)",
+            ValidatePosDim = true,
+            Text = "TextField: X=1; Y=Pos.Bottom (label)+1, Width=Dim.Fill (0); Height=1",
             TextFormatter = new TextFormatter { WordWrap = true },
-            X = 20,
-            Y = Pos.Bottom (label),
-            Width = Fill (20),
-            Height = Fill (10)
+            X = 0,
+            Y = Pos.Bottom (label) + 1,
+            Width = Fill (10),
+            Height = 1
         };
 
-        var btn = new Button
-        {
-            Text = "AnchorEnd", Y = Pos.AnchorEnd (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));
+        //// 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);
+        //dlg.Add (btn);
         Application.Run (dlg);
     }
 }

+ 79 - 10
UnitTests/View/Layout/DimAutoTests.cs

@@ -47,7 +47,7 @@ public class DimAutoTests
         superView.BeginInit ();
         superView.EndInit ();
 
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         superView.LayoutSubviews (); // no throw
 
         Assert.Equal (10, superView.Frame.Width);
@@ -79,7 +79,7 @@ public class DimAutoTests
         superView.BeginInit ();
         superView.EndInit ();
 
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         superView.LayoutSubviews (); // no throw
 
         Assert.Equal (10, superView.Frame.Width);
@@ -87,7 +87,7 @@ public class DimAutoTests
 
         subView.X = -1;
         subView.Y = -1;
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         superView.LayoutSubviews (); // no throw
 
         Assert.Equal (5, subView.Frame.Width);
@@ -121,7 +121,7 @@ public class DimAutoTests
         superView.BeginInit ();
         superView.EndInit ();
 
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         superView.LayoutSubviews (); // no throw
 
         Assert.Equal (10, superView.Frame.Width);
@@ -129,7 +129,7 @@ public class DimAutoTests
 
         subView.Width = 3;
         subView.Height = 3;
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         superView.LayoutSubviews (); // no throw
 
         Assert.Equal (3, subView.Frame.Width);
@@ -176,7 +176,7 @@ public class DimAutoTests
 
         superView.BeginInit ();
         superView.EndInit ();
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         Assert.Equal (new Rect (0, 0, 10, expectedHeight), superView.Frame);
     }
 
@@ -194,7 +194,7 @@ public class DimAutoTests
 
         superView.BeginInit ();
         superView.EndInit ();
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         Assert.Equal (new Rect (0, 0, 0, 0), superView.Frame);
 
         superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
@@ -241,7 +241,7 @@ public class DimAutoTests
 
         superView.BeginInit ();
         superView.EndInit ();
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         Assert.Equal (new Rect (0, 0, expectedWidth, expectedHeight), superView.Frame);
     }
 
@@ -273,7 +273,7 @@ public class DimAutoTests
 
         subView.Width = 10;
         superView.Add (subView);
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         superView.LayoutSubviews (); // no throw
 
         subView.Width = Dim.Fill ();
@@ -468,9 +468,78 @@ public class DimAutoTests
 
         superView.BeginInit ();
         superView.EndInit ();
-        superView.SetRelativeLayout (new Rect (0, 0, 0, 0));
+        superView.SetRelativeLayout (new Rect (0, 0, 10, 10));
         Assert.Equal (new Rect (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 Rect (0, 0, 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 Rect (0, 0, 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
 }

+ 369 - 246
UnitTests/View/Layout/DimTests.cs

@@ -1,9 +1,7 @@
 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;
+using static Terminal.Gui.Dim;
 
 namespace Terminal.Gui.ViewTests;
 
@@ -26,7 +24,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 = Application.Top;
 
@@ -76,176 +74,7 @@ public class DimTests
     // TODO: A new test that calls SetRelativeLayout directly is needed.
     [Fact]
     [TestRespondersDisposed]
-    public void Dim_Referencing_SuperView_Does_Not_Throw ()
-    {
-        var super = new View { Width = 10, Height = 10, Text = "super" };
-
-        var view = new View
-        {
-            Width = Dim.Width (super), // this is allowed
-            Height = Dim.Height (super), // this is allowed
-            Text = "view"
-        };
-
-        super.Add (view);
-        super.BeginInit ();
-        super.EndInit ();
-
-        Exception exception = Record.Exception (super.LayoutSubviews);
-        Assert.Null (exception);
-        super.Dispose ();
-    }
-
-    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-    // TODO: A new test that calls SetRelativeLayout directly is needed.
-    [Fact]
-    [AutoInitShutdown]
-    public void Dim_Subtract_Operator ()
-    {
-        Toplevel top = Application.Top;
-
-        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.Bounds.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);
-
-        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 Dim_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
-        Dim_Validation_Do_Not_Throws_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 Rect (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 ();
-    }
-
-    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-    // TODO: A new test that calls SetRelativeLayout directly is needed.
-    [Fact]
-    [TestRespondersDisposed]
-    public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null ()
-    {
-        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 ();
-    }
-
-    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-    // TODO: A new test that calls SetRelativeLayout directly is needed.
-    [Fact]
-    [TestRespondersDisposed]
-    public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView ()
+    public void Combine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView ()
     {
         var t = new View { Width = 80, Height = 25, Text = "top" };
 
@@ -318,7 +147,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 };
 
@@ -366,54 +195,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 Rect (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);
-        Application.Top.Add (container);
-        Application.Top.BeginInit ();
-        Application.Top.EndInit ();
-        Application.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?)
@@ -506,9 +440,9 @@ public class DimTests
         Assert.Equal (20, dimCombine.Anchor (100));
 
         var view = new View { Frame = new Rect (20, 10, 20, 1) };
-        var dimViewHeight = new Dim.DimView (view, 0);
+        var dimViewHeight = new Dim.DimView (view, Side.Height);
         Assert.Equal (1, dimViewHeight.Anchor (0));
-        var dimViewWidth = new Dim.DimView (view, 1);
+        var dimViewWidth = new Dim.DimView (view, Side.Width);
         Assert.Equal (20, dimViewWidth.Anchor (0));
 
         view.Dispose ();
@@ -814,6 +748,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 ()
     {
@@ -832,47 +849,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 = Rect.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 Rect (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]
@@ -916,6 +910,118 @@ 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 = Application.Top;
+
+        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.Bounds.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);
+
+        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 ()
@@ -973,4 +1079,21 @@ public class DimTests
         Dim dim = Dim.Width (null);
         Assert.Throws<NullReferenceException> (() => dim.ToString ());
     }
+
+    [Fact]
+    [TestRespondersDisposed]
+    public void Width_SetsValue ()
+    {
+        var testVal = Rect.Empty;
+        var testValView = new View { Frame = testVal };
+        Dim dim = Dim.Width (testValView);
+        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
+        testValView.Dispose ();
+
+        testVal = new Rect (1, 2, 3, 4);
+        testValView = new View { Frame = testVal };
+        dim = Dim.Width (testValView);
+        Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ());
+        testValView.Dispose ();
+    }
 }

+ 232 - 236
UnitTests/View/Layout/PosTests.cs

@@ -1,13 +1,64 @@
 using Xunit.Abstractions;
-
-// Alias Console to MockConsole so we don't accidentally use Console
+using static Terminal.Gui.Pos;
 
 namespace Terminal.Gui.ViewTests;
 
-public class PosTests
+public class PosTests (ITestOutputHelper output)
 {
-    private readonly ITestOutputHelper _output;
-    public PosTests (ITestOutputHelper output) { _output = output; }
+    // 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 Add_Operator ()
+    {
+        Application.Init (new FakeDriver ());
+
+        Toplevel top = Application.Top;
+
+        var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
+        var field = new TextField { X = 0, Y = 0, Width = 20 };
+        var count = 0;
+
+        field.KeyDown += (s, k) =>
+                         {
+                             if (k.KeyCode == KeyCode.Enter)
+                             {
+                                 field.Text = $"View {count}";
+                                 var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text };
+                                 view.Add (view2);
+                                 Assert.Equal ($"View {count}", view2.Text);
+                                 Assert.Equal ($"Absolute({count})", view2.Y.ToString ());
+
+                                 Assert.Equal ($"Absolute({count})", field.Y.ToString ());
+                                 field.Y += 1;
+                                 count++;
+                                 Assert.Equal ($"Absolute({count})", field.Y.ToString ());
+                             }
+                         };
+
+        Application.Iteration += (s, a) =>
+                                 {
+                                     while (count < 20)
+                                     {
+                                         field.NewKeyDownEvent (new Key (KeyCode.Enter));
+                                     }
+
+                                     Application.RequestStop ();
+                                 };
+
+        var win = new Window ();
+        win.Add (view);
+        win.Add (field);
+
+        top.Add (win);
+
+        Application.Run (top);
+
+        Assert.Equal (20, count);
+
+        // Shutdown must be called to safely clean up Application if Init has been called
+        Application.Shutdown ();
+    }
 
     [Fact]
     public void AnchorEnd_Equal ()
@@ -159,7 +210,7 @@ public class PosTests
 }│
 └────────────────┘
 ";
-        _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
+        _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, output);
     }
 
     [Fact]
@@ -193,8 +244,59 @@ public class PosTests
         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 Rect (0, 0, 10, 10), super.Frame);
+        Assert.Equal (new Rect (0, 0, 2, 2), view1.Frame);
+        Assert.Equal (new Rect (8, 0, 2, 2), view2.Frame);
+
+        super.Dispose ();
+    }
+
+    // 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 = Application.Top;
+
+        var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Top (t) + 2 };
+        var f = new FrameView ();
+        var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 };
+        var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 };
+
+        f.Add (v1); // v2 not added
+        w.Add (f);
+        t.Add (w);
+
+        f.X = Pos.X (v2) - Pos.X (v1);
+        f.Y = Pos.Y (v2) - Pos.Y (v1);
+
+        Assert.Throws<InvalidOperationException> (() => Application.Run ());
+        Application.Shutdown ();
+
+        v2.Dispose ();
+    }
+
     [Fact]
-    public void DoNotReturnPosCombine ()
+    public void DoesNotReturnPosCombine ()
     {
         var v = new View { Id = "V" };
 
@@ -297,13 +399,13 @@ public class PosTests
         Assert.Equal (20, posCombine.Anchor (100));
 
         var view = new View { Frame = new Rect (20, 10, 20, 1) };
-        var posViewX = new Pos.PosView (view, 0);
+        var posViewX = new Pos.PosView (view, Pos.Side.X);
         Assert.Equal (20, posViewX.Anchor (0));
-        var posViewY = new Pos.PosView (view, 1);
+        var posViewY = new Pos.PosView (view, Pos.Side.Y);
         Assert.Equal (10, posViewY.Anchor (0));
-        var posRight = new Pos.PosView (view, 2);
+        var posRight = new Pos.PosView (view, Pos.Side.Right);
         Assert.Equal (40, posRight.Anchor (0));
-        var posViewBottom = new Pos.PosView (view, 3);
+        var posViewBottom = new Pos.PosView (view, Side.Bottom);
         Assert.Equal (11, posViewBottom.Anchor (0));
 
         view.Dispose ();
@@ -435,235 +537,13 @@ public class PosTests
         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]
-    [TestRespondersDisposed]
-    public void Pos_Add_Operator ()
-    {
-        Application.Init (new FakeDriver ());
-
-        Toplevel top = Application.Top;
-
-        var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
-        var field = new TextField { X = 0, Y = 0, Width = 20 };
-        var count = 0;
-
-        field.KeyDown += (s, k) =>
-                         {
-                             if (k.KeyCode == KeyCode.Enter)
-                             {
-                                 field.Text = $"View {count}";
-                                 var view2 = new View { X = 0, Y = field.Y, Width = 20, Text = field.Text };
-                                 view.Add (view2);
-                                 Assert.Equal ($"View {count}", view2.Text);
-                                 Assert.Equal ($"Absolute({count})", view2.Y.ToString ());
-
-                                 Assert.Equal ($"Absolute({count})", field.Y.ToString ());
-                                 field.Y += 1;
-                                 count++;
-                                 Assert.Equal ($"Absolute({count})", field.Y.ToString ());
-                             }
-                         };
-
-        Application.Iteration += (s, a) =>
-                                 {
-                                     while (count < 20)
-                                     {
-                                         field.NewKeyDownEvent (new Key (KeyCode.Enter));
-                                     }
-
-                                     Application.RequestStop ();
-                                 };
-
-        var win = new Window ();
-        win.Add (view);
-        win.Add (field);
-
-        top.Add (win);
-
-        Application.Run (top);
-
-        Assert.Equal (20, 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]
-    [TestRespondersDisposed]
-    public void Pos_Subtract_Operator ()
-    {
-        Application.Init (new FakeDriver ());
-
-        Toplevel top = Application.Top;
-
-        var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
-        var field = new TextField { X = 0, Y = 0, Width = 20 };
-        var count = 20;
-        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);
-
-        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 Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Null ()
-    {
-        Application.Init (new FakeDriver ());
-
-        Toplevel t = Application.Top;
-
-        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 ();
-        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 PosCombine_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 Rect (0, 0, 10, 10), super.Frame);
-        Assert.Equal (new Rect (0, 0, 2, 2), view1.Frame);
-        Assert.Equal (new Rect (8, 0, 2, 2), view2.Frame);
-
-        super.Dispose ();
-    }
-
-    // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
-    // TODO: A new test that calls SetRelativeLayout directly is needed.
-    [Fact]
-    [TestRespondersDisposed]
-    public void PosCombine_Will_Throws ()
-    {
-        Application.Init (new FakeDriver ());
-
-        Toplevel t = Application.Top;
-
-        var w = new Window { X = Pos.Left (t) + 2, Y = Pos.Top (t) + 2 };
-        var f = new FrameView ();
-        var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 };
-        var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 };
-
-        f.Add (v1); // v2 not added
-        w.Add (f);
-        t.Add (w);
-
-        f.X = Pos.X (v2) - Pos.X (v1);
-        f.Y = Pos.Y (v2) - Pos.Y (v1);
-
-        Assert.Throws<InvalidOperationException> (() => Application.Run ());
-        Application.Shutdown ();
-
-        v2.Dispose ();
-    }
-
     // TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
     // TODO: A new test that calls SetRelativeLayout directly is needed.
     [Theory]
     [AutoInitShutdown]
     [InlineData (true)]
     [InlineData (false)]
-    public void PosPercentPlusOne (bool testHorizontal)
+    public void Percent_PlusOne (bool testHorizontal)
     {
         var container = new View { Width = 100, Height = 100 };
 
@@ -694,12 +574,36 @@ public class PosTests
         }
     }
 
+    [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 = Rect.Empty;
@@ -924,7 +828,7 @@ public class PosTests
     }
 
     [Fact]
-    public void SetSide_Null_Throws ()
+    public void Side_SetToNull_Throws ()
     {
         Pos pos = Pos.Left (null);
         Assert.Throws<NullReferenceException> (() => pos.ToString ());
@@ -944,4 +848,96 @@ public class PosTests
         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 = Application.Top;
+
+        var view = new View { X = 0, Y = 0, Width = 20, Height = 20 };
+        var field = new TextField { X = 0, Y = 0, Width = 20 };
+        var count = 20;
+        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);
+
+        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 = Application.Top;
+
+        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 ();
+        Application.Shutdown ();
+    }
 }