// // PosDim.cs: Pos and Dim objects for view dimensions. // // Authors: // Miguel de Icaza (miguel@gnome.org) // 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; public PosFunc (Func n) { this.function = n; } internal override int Anchor (int width) { return function (); } public override string ToString () { return $"Pos.PosFunc({function ()})"; } public override int GetHashCode () => function.GetHashCode (); public override bool Equals (object other) => other is PosFunc f && f.function () == 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); } 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 $"Pos.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); } static PosAnchorEnd endNoMargin; 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 $"Pos.AnchorEnd(margin={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"); if (margin == 0) { if (endNoMargin == null) endNoMargin = new PosAnchorEnd (0); return endNoMargin; } return new PosAnchorEnd (margin); } internal class PosCenter : Pos { internal override int Anchor (int width) { return width / 2; } public override string ToString () { return "Pos.Center"; } } static PosCenter pCenter; /// /// 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 () { if (pCenter == null) pCenter = new PosCenter (); return pCenter; } internal class PosAbsolute : Pos { int n; public PosAbsolute (int n) { this.n = n; } public override string ToString () { return $"Pos.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; 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 $"Pos.Combine({left.ToString ()}{(add ? '+' : '-')}{right.ToString ()})"; } } static PosCombine posCombine; /// /// 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) { posCombine = null; return new PosAbsolute (left.Anchor (0) + right.Anchor (0)); } PosCombine newPos = new PosCombine (true, left, right); SetPosCombine (left, newPos); return posCombine = 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) { posCombine = null; return new PosAbsolute (left.Anchor (0) - right.Anchor (0)); } PosCombine newPos = new PosCombine (false, left, right); SetPosCombine (left, newPos); return posCombine = newPos; } static void SetPosCombine (Pos left, PosCombine newPos) { if (posCombine?.ToString () != newPos.ToString ()) { 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; } return $"Pos.View(side={tside}, target={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; } /// /// Dim properties of a to control the position. /// /// /// /// 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; } // 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) { this.function = n; } internal override int Anchor (int width) { return function (); } public override string ToString () { return $"Dim.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) { 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 $"Dim.Factor(factor={factor}, remaining={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 $"Dim.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 $"Dim.Fill(margin={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; } static DimFill zeroMargin; /// /// 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) { if (margin == 0) { if (zeroMargin == null) zeroMargin = new DimFill (0); return zeroMargin; } 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 $"Dim.Combine({left.ToString ()}{(add ? '+' : '-')}{right.ToString ()})"; } } static DimCombine dimCombine; /// /// 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) { dimCombine = null; return new DimAbsolute (left.Anchor (0) + right.Anchor (0)); } DimCombine newDim = new DimCombine (true, left, right); SetDimCombine (left, newDim); return dimCombine = 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) { dimCombine = null; return new DimAbsolute (left.Anchor (0) - right.Anchor (0)); } DimCombine newDim = new DimCombine (false, left, right); SetDimCombine (left, newDim); return dimCombine = newDim; } static void SetDimCombine (Dim left, DimCombine newPos) { if (dimCombine?.ToString () != newPos.ToString ()) { 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 $"DimView(side={tside}, target={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; } }