diff --git a/Terminal.Gui/View/Layout/PosDim.cs b/Terminal.Gui/View/Layout/PosDim.cs
index adeda1840..79b7acd79 100644
--- a/Terminal.Gui/View/Layout/PosDim.cs
+++ b/Terminal.Gui/View/Layout/PosDim.cs
@@ -6,695 +6,635 @@
//
using System;
-namespace Terminal.Gui {
- ///
- /// Describes the position of a which can be an absolute value, a percentage, centered, or
- /// relative to the ending dimension. Integer values are implicitly convertible to
- /// an absolute . These objects are created using the static methods Percent,
- /// AnchorEnd, and Center. The objects can be combined with the addition and
- /// subtraction operators.
- ///
- ///
- ///
- /// Use the objects on the X or Y properties of a view to control the position.
- ///
- ///
- /// These can be used to set the absolute position, when merely assigning an
- /// integer value (via the implicit integer to conversion), and they can be combined
- /// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
- /// of the 3 characters to the left after centering for example.
- ///
- ///
- /// It is possible to reference coordinates of another view by using the methods
- /// Left(View), Right(View), Bottom(View), Top(View). The X(View) and Y(View) are
- /// aliases to Left(View) and Top(View) respectively.
- ///
- ///
- public class Pos {
- internal virtual int Anchor (int width)
- {
- return 0;
- }
- // Helper class to provide dynamic value by the execution of a function that returns an integer.
- internal class PosFunc : Pos {
- Func function;
+namespace Terminal.Gui;
- public PosFunc (Func n)
- {
- this.function = n;
- }
+///
+/// Describes the position of a which can be an absolute value, a percentage, centered, or
+/// relative to the ending dimension. Integer values are implicitly convertible to
+/// an absolute . These objects are created using the static methods Percent,
+/// AnchorEnd, and Center. The objects can be combined with the addition and
+/// subtraction operators.
+///
+///
+///
+/// Use the objects on the X or Y properties of a view to control the position.
+///
+///
+/// These can be used to set the absolute position, when merely assigning an
+/// integer value (via the implicit integer to conversion), and they can be combined
+/// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
+/// of the 3 characters to the left after centering for example.
+///
+///
+/// It is possible to reference coordinates of another view by using the methods
+/// Left(View), Right(View), Bottom(View), Top(View). The X(View) and Y(View) are
+/// aliases to Left(View) and Top(View) respectively.
+///
+///
+public class Pos {
+ internal virtual int Anchor (int width) => 0;
- internal override int Anchor (int width)
- {
- return function ();
- }
+ // Helper class to provide dynamic value by the execution of a function that returns an integer.
+ internal class PosFunc : Pos {
+ Func function;
- public override string ToString ()
- {
- return $"PosFunc({function ()})";
- }
+ public PosFunc (Func n) => function = n;
- public override int GetHashCode () => function.GetHashCode ();
+ internal override int Anchor (int width) => function ();
- public override bool Equals (object other) => other is PosFunc f && f.function () == function ();
- }
+ public override string ToString () => $"PosFunc({function ()})";
- ///
- /// Creates a "PosFunc" from the specified function.
- ///
- /// The function to be executed.
- /// The returned from the function.
- public static Pos Function (Func function)
- {
- return new PosFunc (function);
- }
+ public override int GetHashCode () => function.GetHashCode ();
- internal class PosFactor : Pos {
- float factor;
-
- public PosFactor (float n)
- {
- this.factor = n;
- }
-
- internal override int Anchor (int width)
- {
- return (int)(width * factor);
- }
-
- public override string ToString ()
- {
- return $"Factor({factor})";
- }
-
- public override int GetHashCode () => factor.GetHashCode ();
-
- public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
- }
-
- ///
- /// Creates a percentage object
- ///
- /// The percent object.
- /// A value between 0 and 100 representing the percentage.
- ///
- /// This creates a that is centered horizontally, is 50% of the way down,
- /// is 30% the height, and is 80% the width of the it added to.
- ///
- /// var textView = new TextView () {
- /// X = Pos.Center (),
- /// Y = Pos.Percent (50),
- /// Width = Dim.Percent (80),
- /// Height = Dim.Percent (30),
- /// };
- ///
- ///
- public static Pos Percent (float n)
- {
- if (n < 0 || n > 100)
- throw new ArgumentException ("Percent value must be between 0 and 100");
-
- return new PosFactor (n / 100);
- }
-
- internal class PosAnchorEnd : Pos {
- int n;
-
- public PosAnchorEnd (int n)
- {
- this.n = n;
- }
-
- internal override int Anchor (int width)
- {
- return width - n;
- }
-
- public override string ToString ()
- {
- return $"AnchorEnd({n})";
- }
-
- public override int GetHashCode () => n.GetHashCode ();
-
- public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd.n == n;
- }
-
- ///
- /// Creates a object that is anchored to the end (right side or bottom) of the dimension,
- /// useful to flush the layout from the right or bottom.
- ///
- /// The object anchored to the end (the bottom or the right side).
- /// Optional margin to place to the right or below.
- ///
- /// This sample shows how align a to the bottom-right of a .
- ///
- /// // See Issue #502
- /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
- /// anchorButton.Y = Pos.AnchorEnd (1);
- ///
- ///
- public static Pos AnchorEnd (int margin = 0)
- {
- if (margin < 0)
- throw new ArgumentException ("Margin must be positive");
-
- return new PosAnchorEnd (margin);
- }
-
- internal class PosCenter : Pos {
- internal override int Anchor (int width)
- {
- return width / 2;
- }
-
- public override string ToString ()
- {
- return "Center";
- }
- }
-
- ///
- /// Returns a object that can be used to center the
- ///
- /// The center Pos.
- ///
- /// This creates a that is centered horizontally, is 50% of the way down,
- /// is 30% the height, and is 80% the width of the it added to.
- ///
- /// var textView = new TextView () {
- /// X = Pos.Center (),
- /// Y = Pos.Percent (50),
- /// Width = Dim.Percent (80),
- /// Height = Dim.Percent (30),
- /// };
- ///
- ///
- public static Pos Center ()
- {
- return new PosCenter ();
- }
-
- internal class PosAbsolute : Pos {
- int n;
- public PosAbsolute (int n) { this.n = n; }
-
- public override string ToString ()
- {
- return $"Absolute({n})";
- }
-
- internal override int Anchor (int width)
- {
- return n;
- }
-
- public override int GetHashCode () => n.GetHashCode ();
-
- public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the .
- public static implicit operator Pos (int n)
- {
- return new PosAbsolute (n);
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the .
- public static Pos At (int n)
- {
- return new PosAbsolute (n);
- }
-
- internal class PosCombine : Pos {
- internal Pos left, right;
- internal bool add;
- public PosCombine (bool add, Pos left, Pos right)
- {
- this.left = left;
- this.right = right;
- this.add = add;
- }
-
- internal override int Anchor (int width)
- {
- var la = left.Anchor (width);
- var ra = right.Anchor (width);
- if (add)
- return la + ra;
- else
- return la - ra;
- }
-
- public override string ToString ()
- {
- return $"Combine({left}{(add ? '+' : '-')}{right})";
- }
-
- }
-
- ///
- /// Adds a to a , yielding a new .
- ///
- /// The first to add.
- /// The second to add.
- /// The that is the sum of the values of left and right.
- public static Pos operator + (Pos left, Pos right)
- {
- if (left is PosAbsolute && right is PosAbsolute) {
- return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
- }
- PosCombine newPos = new PosCombine (true, left, right);
- SetPosCombine (left, newPos);
- return newPos;
- }
-
- ///
- /// Subtracts a from a , yielding a new .
- ///
- /// The to subtract from (the minuend).
- /// The to subtract (the subtrahend).
- /// The that is the left minus right.
- public static Pos operator - (Pos left, Pos right)
- {
- if (left is PosAbsolute && right is PosAbsolute) {
- return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
- }
- PosCombine newPos = new PosCombine (false, left, right);
- SetPosCombine (left, newPos);
- return newPos;
- }
-
- static void SetPosCombine (Pos left, PosCombine newPos)
- {
- var view = left as PosView;
- if (view != null) {
- view.Target.SetNeedsLayout ();
- }
- }
-
- internal class PosView : Pos {
- public View Target;
- int side;
- public PosView (View view, int side)
- {
- Target = view;
- this.side = side;
- }
- internal override int Anchor (int width)
- {
- switch (side) {
- case 0: return Target.Frame.X;
- case 1: return Target.Frame.Y;
- case 2: return Target.Frame.Right;
- case 3: return Target.Frame.Bottom;
- default:
- return 0;
- }
- }
-
- public override string ToString ()
- {
- string tside;
- switch (side) {
- case 0: tside = "x"; break;
- case 1: tside = "y"; break;
- case 2: tside = "right"; break;
- case 3: tside = "bottom"; break;
- default: tside = "unknown"; break;
- }
- // Note: We do not checkt `Target` for null here to intentionally throw if so
- return $"View({tside},{Target.ToString ()})";
- }
-
- public override int GetHashCode () => Target.GetHashCode ();
-
- public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
- }
-
- ///
- /// Returns a object tracks the Left (X) position of the specified .
- ///
- /// The that depends on the other view.
- /// The that will be tracked.
- public static Pos Left (View view) => new PosCombine (true, new PosView (view, 0), new Pos.PosAbsolute (0));
-
- ///
- /// Returns a object tracks the Left (X) position of the specified .
- ///
- /// The that depends on the other view.
- /// The that will be tracked.
- public static Pos X (View view) => new PosCombine (true, new PosView (view, 0), new Pos.PosAbsolute (0));
-
- ///
- /// Returns a object tracks the Top (Y) position of the specified .
- ///
- /// The that depends on the other view.
- /// The that will be tracked.
- public static Pos Top (View view) => new PosCombine (true, new PosView (view, 1), new Pos.PosAbsolute (0));
-
- ///
- /// Returns a object tracks the Top (Y) position of the specified .
- ///
- /// The that depends on the other view.
- /// The that will be tracked.
- public static Pos Y (View view) => new PosCombine (true, new PosView (view, 1), new Pos.PosAbsolute (0));
-
- ///
- /// Returns a object tracks the Right (X+Width) coordinate of the specified .
- ///
- /// The that depends on the other view.
- /// The that will be tracked.
- public static Pos Right (View view) => new PosCombine (true, new PosView (view, 2), new Pos.PosAbsolute (0));
-
- ///
- /// Returns a object tracks the Bottom (Y+Height) coordinate of the specified
- ///
- /// The that depends on the other view.
- /// The that will be tracked.
- public static Pos Bottom (View view) => new PosCombine (true, new PosView (view, 3), new Pos.PosAbsolute (0));
-
- /// Serves as the default hash function.
- /// A hash code for the current object.
- public override int GetHashCode () => Anchor (0).GetHashCode ();
-
- /// Determines whether the specified object is equal to the current object.
- /// The object to compare with the current object.
- ///
- /// if the specified object is equal to the current object; otherwise, .
- public override bool Equals (object other) => other is Pos abs && abs == this;
+ public override bool Equals (object other) => other is PosFunc f && f.function () == function ();
}
///
- /// Dim properties of a to control the position.
+ /// Creates a "PosFunc" from the specified function.
///
- ///
- ///
- /// Use the Dim objects on the Width or Height properties of a to control the position.
- ///
- ///
- /// These can be used to set the absolute position, when merely assigning an
- /// integer value (via the implicit integer to Pos conversion), and they can be combined
- /// to produce more useful layouts, like: Pos.Center - 3, which would shift the position
- /// of the 3 characters to the left after centering for example.
- ///
- ///
- public class Dim {
- internal virtual int Anchor (int width)
- {
- return 0;
- }
+ /// The function to be executed.
+ /// The returned from the function.
+ public static Pos Function (Func function) => new PosFunc (function);
- // Helper class to provide dynamic value by the execution of a function that returns an integer.
- internal class DimFunc : Dim {
- Func function;
+ internal class PosFactor : Pos {
+ float factor;
- public DimFunc (Func n)
- {
- this.function = n;
- }
+ public PosFactor (float n) => factor = n;
- internal override int Anchor (int width)
- {
- return function ();
- }
+ internal override int Anchor (int width) => (int)(width * factor);
- public override string ToString ()
- {
- return $"DimFunc({function ()})";
- }
+ public override string ToString () => $"Factor({factor})";
- public override int GetHashCode () => function.GetHashCode ();
+ public override int GetHashCode () => factor.GetHashCode ();
- public override bool Equals (object other) => other is DimFunc f && f.function () == function ();
- }
-
- ///
- /// Creates a "DimFunc" from the specified function.
- ///
- /// The function to be executed.
- /// The returned from the function.
- public static Dim Function (Func function)
- {
- return new DimFunc (function);
- }
-
- internal class DimFactor : Dim {
- float factor;
- bool remaining;
-
- public DimFactor (float n, bool r = false)
- {
- factor = n;
- remaining = r;
- }
-
- internal override int Anchor (int width)
- {
- return (int)(width * factor);
- }
-
- public bool IsFromRemaining ()
- {
- return remaining;
- }
-
- public override string ToString ()
- {
- return $"Factor({factor},{remaining})";
- }
-
- public override int GetHashCode () => factor.GetHashCode ();
-
- public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
- }
-
- ///
- /// Creates a percentage object
- ///
- /// The percent object.
- /// A value between 0 and 100 representing the percentage.
- /// If true the Percent is computed based on the remaining space after the X/Y anchor positions. If false is computed based on the whole original space.
- ///
- /// This initializes a that is centered horizontally, is 50% of the way down,
- /// is 30% the height, and is 80% the width of the it added to.
- ///
- /// var textView = new TextView () {
- /// X = Pos.Center (),
- /// Y = Pos.Percent (50),
- /// Width = Dim.Percent (80),
- /// Height = Dim.Percent (30),
- /// };
- ///
- ///
- public static Dim Percent (float n, bool r = false)
- {
- if (n < 0 || n > 100)
- throw new ArgumentException ("Percent value must be between 0 and 100");
-
- return new DimFactor (n / 100, r);
- }
-
- internal class DimAbsolute : Dim {
- int n;
- public DimAbsolute (int n) { this.n = n; }
-
- public override string ToString ()
- {
- return $"Absolute({n})";
- }
-
- internal override int Anchor (int width)
- {
- return n;
- }
-
- public override int GetHashCode () => n.GetHashCode ();
-
- public override bool Equals (object other) => other is DimAbsolute abs && abs.n == n;
- }
-
- internal class DimFill : Dim {
- int margin;
- public DimFill (int margin) { this.margin = margin; }
-
- public override string ToString ()
- {
- return $"Fill({margin})";
- }
-
- internal override int Anchor (int width)
- {
- return width - margin;
- }
-
- public override int GetHashCode () => margin.GetHashCode ();
-
- public override bool Equals (object other) => other is DimFill fill && fill.margin == margin;
- }
-
- ///
- /// Initializes a new instance of the class that fills the dimension, but leaves the specified number of colums for a margin.
- ///
- /// The Fill dimension.
- /// Margin to use.
- public static Dim Fill (int margin = 0)
- {
- return new DimFill (margin);
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the pos.
- public static implicit operator Dim (int n)
- {
- return new DimAbsolute (n);
- }
-
- ///
- /// Creates an Absolute from the specified integer value.
- ///
- /// The Absolute .
- /// The value to convert to the .
- public static Dim Sized (int n)
- {
- return new DimAbsolute (n);
- }
-
- internal class DimCombine : Dim {
- internal Dim left, right;
- internal bool add;
- public DimCombine (bool add, Dim left, Dim right)
- {
- this.left = left;
- this.right = right;
- this.add = add;
- }
-
- internal override int Anchor (int width)
- {
- var la = left.Anchor (width);
- var ra = right.Anchor (width);
- if (add)
- return la + ra;
- else
- return la - ra;
- }
-
- public override string ToString ()
- {
- return $"Combine({left}{(add ? '+' : '-')}{right})";
- }
-
- }
-
- ///
- /// Adds a to a , yielding a new .
- ///
- /// The first to add.
- /// The second to add.
- /// The that is the sum of the values of left and right.
- public static Dim operator + (Dim left, Dim right)
- {
- if (left is DimAbsolute && right is DimAbsolute) {
- return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
- }
- DimCombine newDim = new DimCombine (true, left, right);
- SetDimCombine (left, newDim);
- return newDim;
- }
-
- ///
- /// Subtracts a from a , yielding a new .
- ///
- /// The to subtract from (the minuend).
- /// The to subtract (the subtrahend).
- /// The that is the left minus right.
- public static Dim operator - (Dim left, Dim right)
- {
- if (left is DimAbsolute && right is DimAbsolute) {
- return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
- }
- DimCombine newDim = new DimCombine (false, left, right);
- SetDimCombine (left, newDim);
- return newDim;
- }
-
- static void SetDimCombine (Dim left, DimCombine newPos)
- {
- var view = left as DimView;
- if (view != null) {
- view.Target.SetNeedsLayout ();
- }
- }
-
- internal class DimView : Dim {
- public View Target;
- int side;
- public DimView (View view, int side)
- {
- Target = view;
- this.side = side;
- }
-
- internal override int Anchor (int width)
- {
- switch (side) {
- case 0: return Target.Frame.Height;
- case 1: return Target.Frame.Width;
- default:
- return 0;
- }
- }
-
- public override string ToString ()
- {
- string tside;
- switch (side) {
- case 0: tside = "Height"; break;
- case 1: tside = "Width"; break;
- default: tside = "unknown"; break;
- }
- return $"View({tside},{Target.ToString ()})";
- }
-
- public override int GetHashCode () => Target.GetHashCode ();
-
- public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
- }
- ///
- /// Returns a object tracks the Width of the specified .
- ///
- /// The of the other .
- /// The view that will be tracked.
- public static Dim Width (View view) => new DimView (view, 1);
-
- ///
- /// Returns a object tracks the Height of the specified .
- ///
- /// The of the other .
- /// The view that will be tracked.
- public static Dim Height (View view) => new DimView (view, 0);
-
- /// Serves as the default hash function.
- /// A hash code for the current object.
- public override int GetHashCode () => Anchor (0).GetHashCode ();
-
- /// Determines whether the specified object is equal to the current object.
- /// The object to compare with the current object.
- ///
- /// if the specified object is equal to the current object; otherwise, .
- public override bool Equals (object other) => other is Dim abs && abs == this;
+ public override bool Equals (object other) => other is PosFactor f && f.factor == factor;
}
+
+ ///
+ /// Creates a percentage object
+ ///
+ /// The percent object.
+ /// A value between 0 and 100 representing the percentage.
+ ///
+ /// This creates a that is centered horizontally, is 50% of the way down,
+ /// is 30% the height, and is 80% the width of the it added to.
+ ///
+ /// var textView = new TextView () {
+ /// X = Pos.Center (),
+ /// Y = Pos.Percent (50),
+ /// Width = Dim.Percent (80),
+ /// Height = Dim.Percent (30),
+ /// };
+ ///
+ ///
+ public static Pos Percent (float n)
+ {
+ if (n < 0 || n > 100) {
+ throw new ArgumentException ("Percent value must be between 0 and 100");
+ }
+
+ return new PosFactor (n / 100);
+ }
+
+ internal class PosAnchorEnd : Pos {
+ int n;
+
+ public PosAnchorEnd (int n) => this.n = n;
+
+ internal override int Anchor (int width) => width - n;
+
+ public override string ToString () => $"AnchorEnd({n})";
+
+ public override int GetHashCode () => n.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd.n == n;
+ }
+
+ ///
+ /// Creates a object that is anchored to the end (right side or bottom) of the dimension,
+ /// useful to flush the layout from the right or bottom.
+ ///
+ /// The object anchored to the end (the bottom or the right side).
+ /// Optional margin to place to the right or below.
+ ///
+ /// This sample shows how align a to the bottom-right of a .
+ ///
+ /// // See Issue #502
+ /// anchorButton.X = Pos.AnchorEnd () - (Pos.Right (anchorButton) - Pos.Left (anchorButton));
+ /// anchorButton.Y = Pos.AnchorEnd (1);
+ ///
+ ///
+ public static Pos AnchorEnd (int margin = 0)
+ {
+ if (margin < 0) {
+ throw new ArgumentException ("Margin must be positive");
+ }
+
+ return new PosAnchorEnd (margin);
+ }
+
+ internal class PosCenter : Pos {
+ internal override int Anchor (int width) => width / 2;
+
+ public override string ToString () => "Center";
+ }
+
+ ///
+ /// Returns a object that can be used to center the
+ ///
+ /// The center Pos.
+ ///
+ /// This creates a that is centered horizontally, is 50% of the way down,
+ /// is 30% the height, and is 80% the width of the it added to.
+ ///
+ /// var textView = new TextView () {
+ /// X = Pos.Center (),
+ /// Y = Pos.Percent (50),
+ /// Width = Dim.Percent (80),
+ /// Height = Dim.Percent (30),
+ /// };
+ ///
+ ///
+ public static Pos Center () => new PosCenter ();
+
+ internal class PosAbsolute : Pos {
+ int n;
+ public PosAbsolute (int n) => this.n = n;
+
+ public override string ToString () => $"Absolute({n})";
+
+ internal override int Anchor (int width) => n;
+
+ public override int GetHashCode () => n.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosAbsolute abs && abs.n == n;
+ }
+
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the .
+ public static implicit operator Pos (int n) => new PosAbsolute (n);
+
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the .
+ public static Pos At (int n) => new PosAbsolute (n);
+
+ internal class PosCombine : Pos {
+ internal Pos left, right;
+ internal bool add;
+
+ public PosCombine (bool add, Pos left, Pos right)
+ {
+ this.left = left;
+ this.right = right;
+ this.add = add;
+ }
+
+ internal override int Anchor (int width)
+ {
+ int la = left.Anchor (width);
+ int ra = right.Anchor (width);
+ if (add) {
+ return la + ra;
+ } else {
+ return la - ra;
+ }
+ }
+
+ public override string ToString () => $"Combine({left}{(add ? '+' : '-')}{right})";
+ }
+
+ ///
+ /// Adds a to a , yielding a new .
+ ///
+ /// The first to add.
+ /// The second to add.
+ /// The that is the sum of the values of left and right.
+ public static Pos operator + (Pos left, Pos right)
+ {
+ if (left is PosAbsolute && right is PosAbsolute) {
+ return new PosAbsolute (left.Anchor (0) + right.Anchor (0));
+ }
+ var newPos = new PosCombine (true, left, right);
+ SetPosCombine (left, newPos);
+ return newPos;
+ }
+
+ ///
+ /// Subtracts a from a , yielding a new .
+ ///
+ /// The to subtract from (the minuend).
+ /// The to subtract (the subtrahend).
+ /// The that is the left minus right.
+ public static Pos operator - (Pos left, Pos right)
+ {
+ if (left is PosAbsolute && right is PosAbsolute) {
+ return new PosAbsolute (left.Anchor (0) - right.Anchor (0));
+ }
+ var newPos = new PosCombine (false, left, right);
+ SetPosCombine (left, newPos);
+ return newPos;
+ }
+
+ static void SetPosCombine (Pos left, PosCombine newPos)
+ {
+ var view = left as PosView;
+ if (view != null) {
+ view.Target.SetNeedsLayout ();
+ }
+ }
+
+ internal class PosView : Pos {
+ public View Target;
+ int side;
+
+ public PosView (View view, int side)
+ {
+ Target = view;
+ this.side = side;
+ }
+
+ internal override int Anchor (int width)
+ {
+ switch (side) {
+ case 0: return Target.Frame.X;
+ case 1: return Target.Frame.Y;
+ case 2: return Target.Frame.Right;
+ case 3: return Target.Frame.Bottom;
+ default:
+ return 0;
+ }
+ }
+
+ public override string ToString ()
+ {
+ string tside;
+ switch (side) {
+ case 0:
+ tside = "x";
+ break;
+ case 1:
+ tside = "y";
+ break;
+ case 2:
+ tside = "right";
+ break;
+ case 3:
+ tside = "bottom";
+ break;
+ default:
+ tside = "unknown";
+ break;
+ }
+ // Note: We do not checkt `Target` for null here to intentionally throw if so
+ return $"View({tside},{Target.ToString ()})";
+ }
+
+ public override int GetHashCode () => Target.GetHashCode ();
+
+ public override bool Equals (object other) => other is PosView abs && abs.Target == Target;
+ }
+
+ ///
+ /// Returns a object tracks the Left (X) position of the specified .
+ ///
+ /// The that depends on the other view.
+ /// The that will be tracked.
+ public static Pos Left (View view) => new PosCombine (true, new PosView (view, 0), new PosAbsolute (0));
+
+ ///
+ /// Returns a object tracks the Left (X) position of the specified .
+ ///
+ /// The that depends on the other view.
+ /// The that will be tracked.
+ public static Pos X (View view) => new PosCombine (true, new PosView (view, 0), new PosAbsolute (0));
+
+ ///
+ /// Returns a object tracks the Top (Y) position of the specified .
+ ///
+ /// The that depends on the other view.
+ /// The that will be tracked.
+ public static Pos Top (View view) => new PosCombine (true, new PosView (view, 1), new PosAbsolute (0));
+
+ ///
+ /// Returns a object tracks the Top (Y) position of the specified .
+ ///
+ /// The that depends on the other view.
+ /// The that will be tracked.
+ public static Pos Y (View view) => new PosCombine (true, new PosView (view, 1), new PosAbsolute (0));
+
+ ///
+ /// Returns a object tracks the Right (X+Width) coordinate of the specified .
+ ///
+ /// The that depends on the other view.
+ /// The that will be tracked.
+ public static Pos Right (View view) => new PosCombine (true, new PosView (view, 2), new PosAbsolute (0));
+
+ ///
+ /// Returns a object tracks the Bottom (Y+Height) coordinate of the specified
+ ///
+ /// The that depends on the other view.
+ /// The that will be tracked.
+ public static Pos Bottom (View view) => new PosCombine (true, new PosView (view, 3), new PosAbsolute (0));
+
+ /// Serves as the default hash function.
+ /// A hash code for the current object.
+ public override int GetHashCode () => Anchor (0).GetHashCode ();
+
+ /// Determines whether the specified object is equal to the current object.
+ /// The object to compare with the current object.
+ ///
+ /// if the specified object is equal to the current object; otherwise, .
+ public override bool Equals (object other) => other is Pos abs && abs == this;
}
+
+///
+/// Dim properties of a to control the dimensions.
+///
+///
+///
+/// Use the Dim objects on the Width or Height properties of a to control the dimensions.
+///
+///
+///
+///
+public class Dim {
+ internal virtual int Anchor (int width) => 0;
+
+ // Helper class to provide dynamic value by the execution of a function that returns an integer.
+ internal class DimFunc : Dim {
+ Func function;
+
+ public DimFunc (Func n) => function = n;
+
+ internal override int Anchor (int width) => function ();
+
+ public override string ToString () => $"DimFunc({function ()})";
+
+ public override int GetHashCode () => function.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimFunc f && f.function () == function ();
+ }
+
+ ///
+ /// Creates a "DimFunc" from the specified function.
+ ///
+ /// The function to be executed.
+ /// The returned from the function.
+ public static Dim Function (Func function) => new DimFunc (function);
+
+ internal class DimFactor : Dim {
+ float factor;
+ bool remaining;
+
+ public DimFactor (float n, bool r = false)
+ {
+ factor = n;
+ remaining = r;
+ }
+
+ internal override int Anchor (int width) => (int)(width * factor);
+
+ public bool IsFromRemaining () => remaining;
+
+ public override string ToString () => $"Factor({factor},{remaining})";
+
+ public override int GetHashCode () => factor.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimFactor f && f.factor == factor && f.remaining == remaining;
+ }
+
+ ///
+ /// Creates a percentage object that is a percentage of the width or height of the SuperView.
+ ///
+ /// The percent object.
+ /// A value between 0 and 100 representing the percentage.
+ /// If true the Percent is computed based on the remaining space after the X/Y anchor positions.
+ /// If false is computed based on the whole original space.
+ ///
+ /// This initializes a that is centered horizontally, is 50% of the way down,
+ /// is 30% the height, and is 80% the width of the it added to.
+ ///
+ /// var textView = new TextView () {
+ /// X = Pos.Center (),
+ /// Y = Pos.Percent (50),
+ /// Width = Dim.Percent (80),
+ /// Height = Dim.Percent (30),
+ /// };
+ ///
+ ///
+ public static Dim Percent (float n, bool r = false)
+ {
+ if (n is < 0 or > 100) {
+ throw new ArgumentException ("Percent value must be between 0 and 100");
+ }
+
+ return new DimFactor (n / 100, r);
+ }
+
+ internal class DimAbsolute : Dim {
+ readonly int _n;
+ public DimAbsolute (int n) => _n = n;
+
+ public override string ToString () => $"Absolute({_n})";
+
+ internal override int Anchor (int width) => _n;
+
+ public override int GetHashCode () => _n.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimAbsolute abs && abs._n == _n;
+ }
+
+ internal class DimFill : Dim {
+ readonly int _margin;
+ public DimFill (int margin) => _margin = margin;
+
+ public override string ToString () => $"Fill({_margin})";
+
+ internal override int Anchor (int width) => width - _margin;
+
+ public override int GetHashCode () => _margin.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimFill fill && fill._margin == _margin;
+ }
+
+ ///
+ /// Initializes a new instance of the class that fills the dimension, but leaves the specified number of columns for a margin.
+ ///
+ /// The Fill dimension.
+ /// Margin to use.
+ public static Dim Fill (int margin = 0) => new DimFill (margin);
+
+ internal class DimAutoSize : Dim {
+ readonly int _margin;
+ public DimAutoSize (int margin)
+ {
+ _margin = margin;
+ }
+
+ public override string ToString () => $"AutoSize({_margin})";
+
+ internal override int Anchor (int width)
+ {
+ return width - _margin;
+ }
+
+ public override int GetHashCode () => _margin.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimAutoSize autoSize && autoSize._margin == _margin;
+ }
+
+ ///
+ /// Creates an AutoSize object that is the size required to fit all of the view's SubViews.
+ ///
+ /// The AutoSize object.
+ /// Margin to use.
+ ///
+ /// This initializes a with two SubViews. The view will be automatically sized to fit the two SubViews.
+ ///
+ /// 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);
+ ///
+ ///
+ public static Dim AutoSize (int margin = 0)
+ {
+ return new DimAutoSize (margin);
+ }
+
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the pos.
+ public static implicit operator Dim (int n) => new DimAbsolute (n);
+
+ ///
+ /// Creates an Absolute from the specified integer value.
+ ///
+ /// The Absolute .
+ /// The value to convert to the .
+ public static Dim Sized (int n) => new DimAbsolute (n);
+
+ internal class DimCombine : Dim {
+ internal Dim _left, _right;
+ internal bool _add;
+
+ public DimCombine (bool add, Dim left, Dim right)
+ {
+ _left = left;
+ _right = right;
+ _add = add;
+ }
+
+ internal override int Anchor (int width)
+ {
+ int la = _left.Anchor (width);
+ int ra = _right.Anchor (width);
+ if (_add) {
+ return la + ra;
+ } else {
+ return la - ra;
+ }
+ }
+
+ public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})";
+ }
+
+ ///
+ /// Adds a to a , yielding a new .
+ ///
+ /// The first to add.
+ /// The second to add.
+ /// The that is the sum of the values of left and right.
+ public static Dim operator + (Dim left, Dim right)
+ {
+ if (left is DimAbsolute && right is DimAbsolute) {
+ return new DimAbsolute (left.Anchor (0) + right.Anchor (0));
+ }
+ var newDim = new DimCombine (true, left, right);
+ SetDimCombine (left, newDim);
+ return newDim;
+ }
+
+ ///
+ /// Subtracts a from a , yielding a new .
+ ///
+ /// The to subtract from (the minuend).
+ /// The to subtract (the subtrahend).
+ /// The that is the left minus right.
+ public static Dim operator - (Dim left, Dim right)
+ {
+ if (left is DimAbsolute && right is DimAbsolute) {
+ return new DimAbsolute (left.Anchor (0) - right.Anchor (0));
+ }
+ var newDim = new DimCombine (false, left, right);
+ SetDimCombine (left, newDim);
+ return newDim;
+ }
+
+ // BUGBUG: newPos is never used.
+ static void SetDimCombine (Dim left, DimCombine newPos) => (left as DimView)?.Target.SetNeedsLayout ();
+
+ internal class DimView : Dim {
+ public View Target { get; init; }
+ readonly int _side;
+
+ public DimView (View view, int side)
+ {
+ Target = view;
+ _side = side;
+ }
+
+ internal override int Anchor (int width) => _side switch {
+ 0 => Target.Frame.Height,
+ 1 => Target.Frame.Width,
+ _ => 0
+ };
+
+ public override string ToString ()
+ {
+ string tside = _side switch {
+ 0 => "Height",
+ 1 => "Width",
+ _ => "unknown"
+ };
+ return $"View({tside},{Target})";
+ }
+
+ public override int GetHashCode () => Target.GetHashCode ();
+
+ public override bool Equals (object other) => other is DimView abs && abs.Target == Target;
+ }
+
+ ///
+ /// Returns a object tracks the Width of the specified .
+ ///
+ /// The of the other .
+ /// The view that will be tracked.
+ public static Dim Width (View view) => new DimView (view, 1);
+
+ ///
+ /// Returns a object tracks the Height of the specified .
+ ///
+ /// The of the other .
+ /// The view that will be tracked.
+ public static Dim Height (View view) => new DimView (view, 0);
+
+ /// Serves as the default hash function.
+ /// A hash code for the current object.
+ public override int GetHashCode () => Anchor (0).GetHashCode ();
+
+ /// Determines whether the specified object is equal to the current object.
+ /// The object to compare with the current object.
+ ///
+ /// if the specified object is equal to the current object; otherwise, .
+ public override bool Equals (object other) => other is Dim abs && abs == this;
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Text/ViewLayout.cs b/Terminal.Gui/View/ViewLayout.cs
similarity index 95%
rename from Terminal.Gui/Text/ViewLayout.cs
rename to Terminal.Gui/View/ViewLayout.cs
index faeb8e174..0485569ef 100644
--- a/Terminal.Gui/Text/ViewLayout.cs
+++ b/Terminal.Gui/View/ViewLayout.cs
@@ -652,7 +652,7 @@ namespace Terminal.Gui {
// the superview's width or height
// the current Pos (View.X or View.Y)
// the current Dim (View.Width or View.Height)
- (int newLocation, int newDimension) GetNewLocationAndDimension (int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension)
+ (int newLocation, int newDimension) GetNewLocationAndDimension (bool horiz, int superviewLocation, int superviewDimension, Pos pos, Dim dim, int autosizeDimension)
{
int newDimension, newLocation;
@@ -669,14 +669,14 @@ namespace Terminal.Gui {
case Pos.PosCombine combine:
int left, right;
- (left, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.left, dim, autosizeDimension);
- (right, newDimension) = GetNewLocationAndDimension (superviewLocation, superviewDimension, combine.right, dim, autosizeDimension);
+ (left, newDimension) = GetNewLocationAndDimension (horiz, superviewLocation, superviewDimension, combine.left, dim, autosizeDimension);
+ (right, newDimension) = GetNewLocationAndDimension (horiz, superviewLocation, superviewDimension, combine.right, dim, autosizeDimension);
if (combine.add) {
newLocation = left + right;
} else {
newLocation = left - right;
}
- newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+ newDimension = Math.Max (CalculateNewDimension (horiz, dim, newLocation, superviewDimension, autosizeDimension), 0);
break;
case Pos.PosAbsolute:
@@ -686,7 +686,7 @@ namespace Terminal.Gui {
case Pos.PosView:
default:
newLocation = pos?.Anchor (superviewDimension) ?? 0;
- newDimension = Math.Max (CalculateNewDimension (dim, newLocation, superviewDimension, autosizeDimension), 0);
+ newDimension = Math.Max (CalculateNewDimension (horiz, dim, newLocation, superviewDimension, autosizeDimension), 0);
break;
}
return (newLocation, newDimension);
@@ -695,7 +695,7 @@ namespace Terminal.Gui {
// Recursively calculates the new dimension (width or height) of the given Dim given:
// the current location (x or y)
// the current dimension (width or height)
- int CalculateNewDimension (Dim d, int location, int dimension, int autosize)
+ int CalculateNewDimension (bool horiz, Dim d, int location, int dimension, int autosize)
{
int newDimension;
switch (d) {
@@ -703,20 +703,33 @@ namespace Terminal.Gui {
newDimension = AutoSize ? autosize : dimension;
break;
case Dim.DimCombine combine:
- int leftNewDim = CalculateNewDimension (combine.left, location, dimension, autosize);
- int rightNewDim = CalculateNewDimension (combine.right, location, dimension, autosize);
- if (combine.add) {
+ int leftNewDim = CalculateNewDimension (horiz, combine._left, location, dimension, autosize);
+ int rightNewDim = CalculateNewDimension (horiz, combine._right, location, dimension, autosize);
+ if (combine._add) {
newDimension = leftNewDim + rightNewDim;
} else {
newDimension = leftNewDim - rightNewDim;
}
newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
break;
-
+
case Dim.DimFactor factor when !factor.IsFromRemaining ():
newDimension = d.Anchor (dimension);
newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
break;
+
+ case Dim.DimAutoSize:
+ var thickness = GetFramesThickness ();
+ if (horiz) {
+ var furthestLeft = Subviews.Where (v => v.Frame.X >= 0).Min (v => v.Frame.X);
+ var furthestRight = Subviews.Where (v => v.Frame.X >= 0).Max (v => v.Frame.X + v.Frame.Width);
+ newDimension = furthestLeft + furthestRight + thickness.Left + thickness.Right;
+ } else {
+ var furthestTop = Subviews.Where (v => v.Frame.Y >= 0).Min (v => v.Frame.Y);
+ var furthestBottom = Subviews.Where (v => v.Frame.Y >= 0).Max (v => v.Frame.Y + v.Frame.Height);
+ newDimension = furthestTop + furthestBottom + thickness.Top + thickness.Bottom;
+ }
+ break;
case Dim.DimFill:
default:
@@ -729,10 +742,10 @@ namespace Terminal.Gui {
}
// horizontal
- (newX, newW) = GetNewLocationAndDimension (superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
+ (newX, newW) = GetNewLocationAndDimension (true, superviewFrame.X, superviewFrame.Width, _x, _width, autosize.Width);
// vertical
- (newY, newH) = GetNewLocationAndDimension (superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
+ (newY, newH) = GetNewLocationAndDimension (false, superviewFrame.Y, superviewFrame.Height, _y, _height, autosize.Height);
var r = new Rect (newX, newY, newW, newH);
if (Frame != r) {
@@ -815,8 +828,8 @@ namespace Terminal.Gui {
}
return;
case Dim.DimCombine dc:
- CollectDim (dc.left, from, ref nNodes, ref nEdges);
- CollectDim (dc.right, from, ref nNodes, ref nEdges);
+ CollectDim (dc._left, from, ref nNodes, ref nEdges);
+ CollectDim (dc._right, from, ref nNodes, ref nEdges);
break;
}
}
diff --git a/UICatalog/Scenarios/DimAutoSize.cs b/UICatalog/Scenarios/DimAutoSize.cs
new file mode 100644
index 000000000..34d428c74
--- /dev/null
+++ b/UICatalog/Scenarios/DimAutoSize.cs
@@ -0,0 +1,67 @@
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("DimAutoSize", "Demonstrates Dim.AutoSize")]
+[ScenarioCategory ("Layout")]
+public class DimAutoSize : Scenario {
+ public override void Init ()
+ {
+ // The base `Scenario.Init` implementation:
+ // - Calls `Application.Init ()`
+ // - Adds a full-screen Window to Application.Top with a title
+ // that reads "Press to Quit". Access this Window with `this.Win`.
+ // - Sets the Theme & the ColorScheme property of `this.Win` to `colorScheme`.
+ // To override this, implement an override of `Init`.
+
+ //base.Init ();
+
+ // A common, alternate, implementation where `this.Win` is not used is below. This code
+ // leverages ConfigurationManager to borrow the color scheme settings from UICatalog:
+
+ Application.Init ();
+ ConfigurationManager.Themes.Theme = Theme;
+ ConfigurationManager.Apply ();
+ Application.Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme];
+ }
+
+ public override void Setup ()
+ {
+ // Put scenario code here (in a real app, this would be the code
+ // that would setup the app before `Application.Run` is called`).
+ // With a Scenario, after UI Catalog calls `Scenario.Setup` it calls
+ // `Scenario.Run` which calls `Application.Run`. Example:
+
+ var textField = new TextField { Text = "Type here", X = 1, Y = 0, Width = 20, Height = 1 };
+
+ var label = new Label {
+ X = Pos.Left (textField),
+ Y = Pos.Bottom (textField),
+ AutoSize = true,
+ };
+
+ textField.TextChanged += (s, e) => {
+ label.Text = textField.Text;
+ };
+
+ var button = new Button () { Text = "Press to make button move down.",
+ X = Pos.Center(),
+ Y = Pos.Bottom (label),
+ };
+ button.Clicked += (s, e) => {
+ button.Y = button.Frame.Y + 1;
+ };
+
+ var view = new FrameView () {
+ Title = "Type in the TextField to make it grow.",
+ X = 3,
+ Y = 3,
+ Width = Dim.AutoSize (),
+ Height = Dim.AutoSize ()
+ };
+
+ view.Add (textField, label, button);
+
+ Application.Top.Add (view);
+ }
+}
\ No newline at end of file
diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs
index 356934744..1dbc82115 100644
--- a/UnitTests/View/Layout/DimTests.cs
+++ b/UnitTests/View/Layout/DimTests.cs
@@ -1251,8 +1251,8 @@ namespace Terminal.Gui.ViewTests {
Assert.Equal (99, dimFill.Anchor (100));
var dimCombine = new Dim.DimCombine (true, dimFactor, dimAbsolute);
- Assert.Equal (dimCombine.left, dimFactor);
- Assert.Equal (dimCombine.right, dimAbsolute);
+ Assert.Equal (dimCombine._left, dimFactor);
+ Assert.Equal (dimCombine._right, dimAbsolute);
Assert.Equal (20, dimCombine.Anchor (100));
var view = new View (new Rect (20, 10, 20, 1));