// // PosDim.cs: Pos and Dim objects for view dimensions. // // Authors: // Miguel de Icaza (miguel@gnome.org) // using System; using static Terminal.Gui.Dim; 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. /// /// /// 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. /// /// /// /// /// Pos Object /// Description /// /// /// /// /// Creates a object that computes the position by executing the provided function. The function will be called every time the position is needed. /// /// /// /// /// /// Creates a object that is a percentage of the width or height of the SuperView. /// /// /// /// /// /// 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. /// /// /// /// /// /// Creates a object that can be used to center the . /// /// /// /// /// /// Creates a object that is an absolute position based on the specified integer value. /// /// /// /// /// /// Creates a object that tracks the Left (X) position of the specified . /// /// /// /// /// /// Creates a object that tracks the Left (X) position of the specified . /// /// /// /// /// /// Creates a object that tracks the Top (Y) position of the specified . /// /// /// /// /// /// Creates a object that tracks the Top (Y) position of the specified . /// /// /// /// /// /// Creates a object that tracks the Right (X+Width) coordinate of the specified . /// /// /// /// /// /// Creates a object that tracks the Bottom (Y+Height) coordinate of the specified /// /// /// /// /// /// public class Pos { internal virtual int Anchor (int width) => 0; /// /// Creates a object that computes the position by executing the provided function. The function will be called every time the position is needed. /// /// The function to be executed. /// The returned from the function. public static Pos Function (Func function) => new PosFunc (function); internal class PosFactor : Pos { readonly float _factor; public PosFactor (float n) => _factor = n; internal override int Anchor (int width) => (int)(width * _factor); public override string ToString () => $"Factor({_factor})"; public override int GetHashCode () => _factor.GetHashCode (); public override bool Equals (object other) => other is PosFactor f && f._factor == _factor; } // Helper class to provide dynamic value by the execution of a function that returns an integer. internal class PosFunc : Pos { readonly Func _function; public PosFunc (Func n) => _function = n; internal override int Anchor (int width) => _function (); public override string ToString () => $"PosFunc({_function ()})"; public override int GetHashCode () => _function.GetHashCode (); public override bool Equals (object other) => other is PosFunc f && f._function () == _function (); } /// /// 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 is < 0 or > 100) { throw new ArgumentException ("Percent value must be between 0 and 100"); } return new PosFactor (n / 100); } /// /// 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 PosAnchorEnd : Pos { readonly int _p; public PosAnchorEnd (int n) => _p = n; internal override int Anchor (int width) => width - _p; public override string ToString () => $"AnchorEnd({_p})"; public override int GetHashCode () => _p.GetHashCode (); public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._p == _p; } /// /// Creates 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 { readonly int _n; public PosAbsolute (int n) => _n = n; public override string ToString () => $"Absolute({_n})"; internal override int Anchor (int width) => _n; public override int GetHashCode () => _n.GetHashCode (); public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n; } internal class PosCenter : Pos { internal override int Anchor (int width) => width / 2; public override string ToString () => "Center"; } /// /// Creates a object that is an absolute position based on 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) { _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})"; } /// /// 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); /// /// 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 readonly 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; } /// /// Creates a object that 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)); /// /// Creates a object that 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)); /// /// Creates a object that 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)); /// /// Creates a object that 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)); /// /// Creates a object that 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)); /// /// Creates a object that 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; } /// /// /// A Dim object describes the dimensions of a . Dim is the type of the and /// properties of . Dim objects enable Computed Layout (see ) /// to automatically manage the dimensions of a view. /// /// /// Integer values are implicitly convertible to an absolute . These objects are created using the static methods described below. /// The objects can be combined with the addition and subtraction operators. /// /// /// /// /// /// /// Dim Object /// Description /// /// /// /// /// Creates a object that computes the dimension by executing the provided function. The function will be called every time the dimension is needed. /// /// /// /// /// /// Creates a object that is a percentage of the width or height of the SuperView. /// /// /// /// /// /// Creates a object that fills the dimension, leaving the specified number of columns for a margin. /// /// /// /// /// /// Creates a object that automatically sizes the view to fit all of the view's SubViews. /// /// /// /// /// /// Creates a object that tracks the Width of the specified . /// /// /// /// /// /// Creates a object that tracks the Height of the specified . /// /// /// /// /// /// /// public class Dim { internal virtual int Anchor (int width) => 0; /// /// Creates a function object that computes the dimension by executing the provided function. /// The function will be called every time the dimension is needed. /// /// The function to be executed. /// The returned from the function. public static Dim Function (Func function) => new DimFunc (function); // Helper class to provide dynamic value by the execution of a function that returns an integer. internal class DimFunc : Dim { readonly 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 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 DimFactor : Dim { readonly float _factor; readonly 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; } 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; } /// /// Creates a object that fills the dimension, leaving the specified number of columns for a margin. /// /// The Fill dimension. /// Margin to use. public static Dim Fill (int margin = 0) => new DimFill (margin); /// /// Creates a object that automatically sizes the view to fit all of the view's SubViews. /// /// /// 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); /// /// /// The AutoSize object. /// Specifies how will compute the dimension. The default is . NOT CURRENTLY SUPPORTED. /// Specifies the minimum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED. /// Specifies the maximum dimension that view will be automatically sized to. NOT CURRENTLY SUPPORTED. public static Dim Auto (DimAutoStyle style = DimAutoStyle.Subviews, Dim min = null, Dim max = null) { if (style == DimAutoStyle.Text) { throw new NotImplementedException (@"DimAutoStyle.Text is not implemented."); } //if (min != null) { // throw new NotImplementedException (@"min is not implemented"); //} if (max != null) { throw new NotImplementedException (@"max is not implemented"); } return new DimAuto (style, min, max); } /// /// Specifies how will compute the dimension. /// public enum DimAutoStyle { /// /// The dimension will be computed from the view's . NOT CURRENTLY SUPPORTED. /// Text, /// /// The dimension will be computed from the view's . /// Subviews, } internal class DimAuto : Dim { internal readonly Dim _min; internal readonly Dim _max; internal readonly DimAutoStyle _style; public DimAuto (DimAutoStyle style, Dim min, Dim max) { _min = min; _max = max; _style = style; } public override string ToString () => $"Auto({_style},{_min},{_max})"; public override int GetHashCode () => HashCode.Combine (base.GetHashCode (), _min, _max, _style); public override bool Equals (object other) => other is DimAuto auto && (auto._min == _min && auto._max == _max && auto._style == _style); } /// /// 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 () { if (Target == null) { throw new NullReferenceException (); } 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; } /// /// Creates a object that tracks the Width of the specified . /// /// The width of the other . /// The view that will be tracked. public static Dim Width (View view) => new DimView (view, 1); /// /// Creates a object that tracks the Height of the specified . /// /// The height 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; }