using System.Diagnostics; namespace Terminal.Gui; /// /// Specifies how will compute the dimension. /// [Flags] public enum DimAutoStyle { /// /// The dimension will be computed using both the view's and /// (whichever is larger). /// Auto = Content | Text, /// /// The dimensions will be computed based on the View's non-Text content. /// /// If is explicitly set (is not ) then /// /// will be used to determine the dimension. /// /// /// Otherwise, the Subview in with the largest corresponding position plus dimension /// will determine the dimension. /// /// /// The corresponding dimension of the view's will be ignored. /// /// Content = 1, /// /// /// The corresponding dimension of the view's , formatted using the /// settings, /// will be used to determine the dimension. /// /// /// The corresponding dimensions of the will be ignored. /// /// Text = 2 } /// /// Indicates the dimension for operations. /// public enum Dimension { /// /// No dimension specified. /// None = 0, /// /// The height dimension. /// Height = 1, /// /// The width dimension. /// Width = 2 } /// /// /// 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 automatically sizes the view to fit /// the view's Text, SubViews, or ContentArea. /// /// /// /// /// /// /// /// 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 from the View's X position /// to the end of the super view's width, leaving the specified number of columns for a margin. /// /// /// /// /// /// /// /// Creates a object that tracks the Width of the specified /// . /// /// /// /// /// /// /// /// Creates a object that tracks the Height of the specified /// . /// /// /// /// /// /// public class Dim { /// /// Creates a object that automatically sizes the view to fit all the view's SubViews and/or Text. /// /// /// /// See . /// /// /// /// 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.Auto (), Height = Dim.Auto () }; /// view.Add (button, textField); /// /// /// The object. /// /// Specifies how will compute the dimension. The default is . /// /// The minimum dimension the View's ContentSize will be constrained to. /// The maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. public static Dim Auto (DimAutoStyle style = DimAutoStyle.Auto, Dim minimumContentDim = null, Dim maximumContentDim = null) { if (maximumContentDim != null) { throw new NotImplementedException (@"maximumContentDim is not implemented"); } return new DimAuto (style, minimumContentDim, maximumContentDim); } /// /// 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) { return new DimFill (margin); } /// /// 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) { return new DimFunc (function); } /// 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) { return new DimView (view, Dimension.Height); } /// 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 the dimension is computed using the View's position ( or /// ). /// If the dimension is computed using the View's . /// /// /// This initializes a that will be centered horizontally, is 50% of the way down, is 30% the /// height, /// and is 80% the width of the SuperView. /// /// var textView = new TextField { /// X = Pos.Center (), /// Y = Pos.Percent (50), /// Width = Dim.Percent (80), /// Height = Dim.Percent (30), /// }; /// /// public static Dim Percent (float percent, bool usePosition = false) { if (percent is < 0 or > 100) { throw new ArgumentException ("Percent value must be between 0 and 100"); } return new DimPercent (percent / 100, usePosition); } /// Creates an Absolute from the specified integer value. /// The Absolute . /// The value to convert to the . public static Dim Absolute (int size) { return new DimAbsolute (size); } /// 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) { return new DimView (view, Dimension.Width); } /// 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); (left as DimView)?.Target.SetNeedsLayout (); return newDim; } /// 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); } /// /// 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); (left as DimView)?.Target.SetNeedsLayout (); return newDim; } /// /// Gets a dimension that is anchored to a certain point in the layout. /// This method is typically used internally by the layout system to determine the size of a View. /// /// The width of the area where the View is being sized (Superview.ContentSize). /// /// An integer representing the calculated dimension. The way this dimension is calculated depends on the specific /// subclass of Dim that is used. For example, DimAbsolute returns a fixed dimension, DimFactor returns a /// dimension that is a certain percentage of the super view's size, and so on. /// internal virtual int Anchor (int size) { return 0; } /// /// Calculates and returns the dimension of a object. It takes into account the location of the /// , it's SuperView's ContentSize, and whether it should automatically adjust its size based on its /// content. /// /// /// The starting point from where the size calculation begins. It could be the left edge for width calculation or the /// top edge for height calculation. /// /// The size of the SuperView's content. It could be width or height. /// The View that holds this Pos object. /// Width or Height /// /// The calculated size of the View. The way this size is calculated depends on the specific subclass of Dim that /// is used. /// internal virtual int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { return Math.Max (Anchor (superviewContentSize - location), 0); } /// /// Diagnostics API to determine if this Dim object references other views. /// /// internal virtual bool ReferencesOtherViews () { return false; } /// public override bool Equals (object other) { return other is Dim abs && abs == this; } /// public override int GetHashCode () { return Anchor (0).GetHashCode (); } } /// /// Represents a dimension that is a fixed size. /// /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// /// /// public class DimAbsolute (int size) : Dim { /// /// Gets the size of the dimension. /// public int Size { get; } = size; /// public override bool Equals (object other) { return other is DimAbsolute abs && abs.Size == Size; } /// public override int GetHashCode () { return Size.GetHashCode (); } /// public override string ToString () { return $"Absolute({Size})"; } internal override int Anchor (int size) { return Size; } internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { // DimAbsolute.Anchor (int size) ignores width and returns n return Math.Max (Anchor (0), 0); } } /// /// Represents a dimension that automatically sizes the view to fit all the view's Content, SubViews, and/or Text. /// /// /// /// See . /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// /// /// /// Specifies how will compute the dimension. The default is . /// /// The minimum dimension the View's ContentSize will be constrained to. /// The maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. public class DimAuto (DimAutoStyle style, Dim minimumContentDim, Dim maximumContentDim) : Dim { /// /// Gets the minimum dimension the View's ContentSize will be constrained to. /// public Dim MinimumContentDim { get; } = minimumContentDim; /// /// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED. /// public Dim MaximumContentDim { get; } = maximumContentDim; /// /// Gets the style of the DimAuto. /// public DimAutoStyle Style { get; } = style; internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { if (us == null) { return MaximumContentDim?.Anchor (0) ?? 0; } var textSize = 0; var subviewsSize = 0; int autoMin = MinimumContentDim?.Anchor (superviewContentSize) ?? 0; if (superviewContentSize < autoMin) { Debug.WriteLine ($"WARNING: DimAuto specifies a min size ({autoMin}), but the SuperView's bounds are smaller ({superviewContentSize})."); //return superviewContentSize; } if (Style.HasFlag (DimAutoStyle.Text)) { textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height); } if (Style.HasFlag (DimAutoStyle.Content)) { if (us._contentSize is { }) { subviewsSize = dimension == Dimension.Width ? us.ContentSize.Width : us.ContentSize.Height; } else { // TODO: AnchorEnd needs work // TODO: If _min > 0 we can SetRelativeLayout for the subviews? subviewsSize = 0; if (us.Subviews.Count > 0) { for (var i = 0; i < us.Subviews.Count; i++) { View v = us.Subviews [i]; bool isNotPosAnchorEnd = dimension == Dimension.Width ? v.X is not PosAnchorEnd : v.Y is not PosAnchorEnd; //if (!isNotPosAnchorEnd) //{ // v.SetRelativeLayout(dimension == Dimension.Width ? (new Size (autoMin, 0)) : new Size (0, autoMin)); //} if (isNotPosAnchorEnd) { int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height; if (size > subviewsSize) { subviewsSize = size; } } } } } } // All sizes here are content-relative; ignoring adornments. // We take the larger of text and content. int max = int.Max (textSize, subviewsSize); // And, if min: is set, it wins if larger max = int.Max (max, autoMin); // Factor in adornments Thickness thickness = us.GetAdornmentsThickness (); if (dimension == Dimension.Width) { max += thickness.Horizontal; } else { max += thickness.Vertical; } // If max: is set, clamp the return - BUGBUG: Not tested return int.Min (max, MaximumContentDim?.Anchor (superviewContentSize) ?? max); } internal override bool ReferencesOtherViews () { // BUGBUG: This is not correct. _contentSize may be null. return false; //_style.HasFlag (DimAutoStyle.Content); } /// public override bool Equals (object other) { return other is DimAuto auto && auto.MinimumContentDim == MinimumContentDim && auto.MaximumContentDim == MaximumContentDim && auto.Style == Style; } /// public override int GetHashCode () { return HashCode.Combine (base.GetHashCode (), MinimumContentDim, MaximumContentDim, Style); } /// public override string ToString () { return $"Auto({Style},{MinimumContentDim},{MaximumContentDim})"; } } /// /// Represents a dimension that is a combination of two other dimensions. /// /// /// Indicates whether the two dimensions are added or subtracted. If , the dimensions are added, /// otherwise they are subtracted. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// /// The left dimension. /// The right dimension. public class DimCombine (bool add, Dim left, Dim right) : Dim { /// /// Gets whether the two dimensions are added or subtracted. /// public bool Add { get; } = add; /// /// Gets the left dimension. /// public Dim Left { get; } = left; /// /// Gets the right dimension. /// public Dim Right { get; } = right; /// public override string ToString () { return $"Combine({Left}{(Add ? '+' : '-')}{Right})"; } internal override int Anchor (int size) { int la = Left.Anchor (size); int ra = Right.Anchor (size); if (Add) { return la + ra; } return la - ra; } internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { int leftNewDim = Left.Calculate (location, superviewContentSize, us, dimension); int rightNewDim = Right.Calculate (location, superviewContentSize, us, dimension); int newDimension; if (Add) { newDimension = leftNewDim + rightNewDim; } else { newDimension = Math.Max (0, leftNewDim - rightNewDim); } return newDimension; } /// /// Diagnostics API to determine if this Dim object references other views. /// /// internal override bool ReferencesOtherViews () { if (Left.ReferencesOtherViews ()) { return true; } if (Right.ReferencesOtherViews ()) { return true; } return false; } } /// /// Represents a dimension that is a percentage of the width or height of the SuperView. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// /// The percentage. /// /// If the dimension is computed using the View's position ( or /// ). /// If the dimension is computed using the View's . /// public class DimPercent (float percent, bool usePosition = false) : Dim { /// /// Gets the percentage. /// public new float Percent { get; } = percent; /// /// Gets whether the dimension is computed using the View's position or ContentSize. /// public bool UsePosition { get; } = usePosition; /// public override bool Equals (object other) { return other is DimPercent f && f.Percent == Percent && f.UsePosition == UsePosition; } /// public override int GetHashCode () { return Percent.GetHashCode (); } /// /// /// public override string ToString () { return $"Percent({Percent},{UsePosition})"; } internal override int Anchor (int size) { return (int)(size * Percent); } internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension) { return UsePosition ? Math.Max (Anchor (superviewContentSize - location), 0) : Anchor (superviewContentSize); } } /// /// Represents a dimension that fills the dimension, leaving the specified margin. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// /// The margin to not fill. public class DimFill (int margin) : Dim { /// /// Gets the margin to not fill. /// public int Margin { get; } = 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})"; } internal override int Anchor (int size) { return size - Margin; } } /// /// Represents a function object that computes the dimension by executing the provided function. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// /// public class DimFunc (Func dim) : Dim { /// /// Gets the function that computes the dimension. /// public Func Func { get; } = dim; /// public override bool Equals (object other) { return other is DimFunc f && f.Func () == Func (); } /// public override int GetHashCode () { return Func.GetHashCode (); } /// public override string ToString () { return $"DimFunc({Func ()})"; } internal override int Anchor (int size) { return Func (); } } /// /// Represents a dimension that tracks the Height or Width of the specified View. /// /// /// This is a low-level API that is typically used internally by the layout system. Use the various static /// methods on the class to create objects instead. /// public class DimView : Dim { /// /// Gets the indicated dimension of the View. /// public Dimension Dimension { get; } /// /// Initializes a new instance of the class. /// /// The view the dimension is anchored to. /// Indicates which dimension is tracked. public DimView (View view, Dimension dimension) { Target = view; Dimension = dimension; } /// /// Gets the View the dimension is anchored to. /// public View Target { get; init; } /// public override bool Equals (object other) { return other is DimView abs && abs.Target == Target; } /// public override int GetHashCode () { return Target.GetHashCode (); } /// public override string ToString () { if (Target == null) { throw new NullReferenceException (); } string dimString = Dimension switch { Dimension.Height => "Height", Dimension.Width => "Width", _ => "unknown" }; return $"View({dimString},{Target})"; } internal override int Anchor (int size) { return Dimension switch { Dimension.Height => Target.Frame.Height, Dimension.Width => Target.Frame.Width, _ => 0 }; } internal override bool ReferencesOtherViews () { return true; } }