diff --git a/.editorconfig b/.editorconfig index ed21eef37..d30be450e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -96,15 +96,7 @@ csharp_style_var_for_built_in_types = true:none resharper_wrap_before_linq_expression = true resharper_wrap_chained_binary_expressions = chop_if_long resharper_wrap_chained_binary_patterns = chop_if_long -resharper_xmldoc_indent_size = 2 -resharper_xmldoc_indent_style = space -resharper_xmldoc_indent_text = DoNotTouch -resharper_xmldoc_linebreaks_inside_tags_for_elements_longer_than = 120 -resharper_xmldoc_max_blank_lines_between_tags = 1 -resharper_xmldoc_max_line_length = 100 -resharper_xmldoc_space_before_self_closing = false -resharper_xmldoc_tab_width = 2 -resharper_xmldoc_use_indent_from_vs = true + [*.{cs,vb}] dotnet_style_operator_placement_when_wrapping = beginning_of_line diff --git a/Terminal.Gui/View/Layout/PosDim.cs b/Terminal.Gui/View/Layout/PosDim.cs index fa60c6683..98ec74c1c 100644 --- a/Terminal.Gui/View/Layout/PosDim.cs +++ b/Terminal.Gui/View/Layout/PosDim.cs @@ -3,113 +3,327 @@ 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. +/// 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 -/// -/// -/// -/// -/// +/// +/// 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. + /// 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. + /// The returned from the function. public static Pos Function (Func function) => new PosFunc (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). + /// The view will be shifted left or up by the amount specified. + /// + /// 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 offset = 0) + { + if (offset < 0) { + throw new ArgumentException (@"Must be positive", nameof (offset)); + } + + return new PosAnchorEnd (offset); + } + + /// + /// 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 (); + + /// + /// 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); + + /// + /// 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 (); + } + } + + /// + /// 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 PosView (view, 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 PosView (view, 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 PosView (view, 1); + + /// + /// 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 PosView (view, 1); + + /// + /// 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 PosView (view, 2); + + /// + /// 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 PosView (view, 3); + + /// 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; + internal class PosFactor : Pos { readonly float _factor; @@ -139,55 +353,6 @@ public class Pos { 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). - /// The view will be shifted left or up by the amount specified. - /// - /// 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 offset = 0) - { - if (offset < 0) { - throw new ArgumentException (@"Must be positive", nameof(offset)); - } - - return new PosAnchorEnd (offset); - } - internal class PosAnchorEnd : Pos { readonly int _offset; @@ -202,24 +367,6 @@ public class Pos { public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; } - /// - /// 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; @@ -239,16 +386,9 @@ public class Pos { 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; + internal Pos _left, _right; public PosCombine (bool add, Pos left, Pos right) { @@ -259,68 +399,20 @@ public class Pos { internal override int Anchor (int width) { - int la = _left.Anchor (width); - int ra = _right.Anchor (width); + var la = _left.Anchor (width); + var ra = _right.Anchor (width); if (_add) { return la + ra; - } else { - return la - ra; } + 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; + readonly int side; public PosView (View view, int side) { @@ -330,76 +422,276 @@ public class Pos { 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; - } + return side switch { + 0 => Target.Frame.X, + 1 => Target.Frame.Y, + 2 => Target.Frame.Right, + 3 => Target.Frame.Bottom, + _ => 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(side={tside},target={Target.ToString ()})"; - } + public override string ToString () + { + string tside = side switch { + 0 => "x", + 1 => "y", + 2 => "right", + 3 => "bottom", + _ => "unknown" + }; + // Note: We do not checkt `Target` for null here to intentionally throw if so + return $"View(side={tside},target={Target})"; + } public override int GetHashCode () => Target.GetHashCode (); public override bool Equals (object other) => other is PosView abs && abs.Target == Target; } +} +/// +/// +/// 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 { + /// + /// Specifies how will compute the dimension. + /// + public enum DimAutoStyle { /// - /// 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 PosView (view, 0); - - /// - /// Creates a object that tracks the Left (X) position of the specified . + /// The dimension will be computed from the view's . NOT CURRENTLY SUPPORTED. /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos X (View view) => new PosView (view, 0); + Text, /// - /// Creates a object that tracks the Top (Y) position of the specified . + /// The dimension will be computed from the view's . /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Top (View view) => new PosView (view, 1); + Subviews + } - /// - /// 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 PosView(view, 1); + internal virtual int Anchor (int width) => 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 PosView (view, 2); + /// + /// 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); - /// - /// 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 PosView (view, 3); + /// + /// 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); + } + + /// + /// 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); + } + + /// + /// 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); + + /// + /// 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 (); + + /// + /// 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. @@ -408,79 +700,10 @@ public class Pos { /// 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); + /// if the specified object is equal to the current object; otherwise, + /// . + /// + public override bool Equals (object other) => other is Dim abs && abs == this; // Helper class to provide dynamic value by the execution of a function that returns an integer. internal class DimFunc : Dim { @@ -497,34 +720,6 @@ public class Dim { 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; @@ -573,61 +768,9 @@ public class Dim { 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 Dim _min; internal readonly DimAutoStyle _style; public DimAuto (DimAutoStyle style, Dim min, Dim max) @@ -641,26 +784,12 @@ public class Dim { 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); + 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; + internal Dim _left, _right; public DimCombine (bool add, Dim left, Dim right) { @@ -671,55 +800,18 @@ public class Dim { internal override int Anchor (int width) { - int la = _left.Anchor (width); - int ra = _right.Anchor (width); + var la = _left.Anchor (width); + var ra = _right.Anchor (width); if (_add) { return la + ra; - } else { - return la - ra; } + 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) @@ -728,6 +820,8 @@ public class Dim { _side = side; } + public View Target { get; init; } + internal override int Anchor (int width) => _side switch { 0 => Target.Frame.Height, 1 => Target.Frame.Width, @@ -739,7 +833,7 @@ public class Dim { if (Target == null) { throw new NullReferenceException (); } - string tside = _side switch { + var tside = _side switch { 0 => "Height", 1 => "Width", _ => "unknown" @@ -751,28 +845,4 @@ public class Dim { 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; } \ No newline at end of file diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs index f12452e11..8c0df1bcc 100644 --- a/UnitTests/View/Layout/DimTests.cs +++ b/UnitTests/View/Layout/DimTests.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.Text; using System.Threading; using Xunit; using Xunit.Abstractions; - // Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; @@ -13,718 +13,7 @@ namespace Terminal.Gui.ViewTests; public class DimTests { readonly ITestOutputHelper _output; - public DimTests (ITestOutputHelper output) - { - _output = output; - Console.OutputEncoding = System.Text.Encoding.Default; - // Change current culture - var culture = CultureInfo.CreateSpecificCulture ("en-US"); - Thread.CurrentThread.CurrentCulture = culture; - Thread.CurrentThread.CurrentUICulture = culture; - } - - [Fact] - public void New_Works () - { - var dim = new Dim (); - Assert.Equal ("Terminal.Gui.Dim", dim.ToString ()); - } - - [Fact] - public void Sized_SetsValue () - { - var dim = Dim.Sized (0); - Assert.Equal ("Absolute(0)", dim.ToString ()); - - int testVal = 5; - dim = Dim.Sized (testVal); - Assert.Equal ($"Absolute({testVal})", dim.ToString ()); - - testVal = -1; - dim = Dim.Sized (testVal); - Assert.Equal ($"Absolute({testVal})", dim.ToString ()); - } - - [Fact] - public void Sized_Equals () - { - int n1 = 0; - int n2 = 0; - var dim1 = Dim.Sized (n1); - var dim2 = Dim.Sized (n2); - Assert.Equal (dim1, dim2); - - n1 = n2 = 1; - dim1 = Dim.Sized (n1); - dim2 = Dim.Sized (n2); - Assert.Equal (dim1, dim2); - - n1 = n2 = -1; - dim1 = Dim.Sized (n1); - dim2 = Dim.Sized (n2); - Assert.Equal (dim1, dim2); - - n1 = 0; - n2 = 1; - dim1 = Dim.Sized (n1); - dim2 = Dim.Sized (n2); - Assert.NotEqual (dim1, dim2); - } - - [Fact] - public void Width_Set_To_Null_Throws () - { - var dim = Dim.Width (null); - Assert.Throws (() => dim.ToString ()); - } - - [Fact, TestRespondersDisposed] - public void SetsValue () - { - var testVal = Rect.Empty; - var testValView = new View (testVal); - var dim = Dim.Width (testValView); - Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); - testValView.Dispose (); - - testVal = new Rect (1, 2, 3, 4); - testValView = new View (testVal); - dim = Dim.Width (testValView); - Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); - testValView.Dispose (); - } - - [Fact] [TestRespondersDisposed] - public void Width_Equals () - { - var testRect1 = Rect.Empty; - var view1 = new View (testRect1); - var testRect2 = Rect.Empty; - var view2 = new View (testRect2); - - var dim1 = Dim.Width (view1); - var dim2 = Dim.Width (view1); - // FIXED: Dim.Width should support Equals() and this should change to Equal. - Assert.Equal (dim1, dim2); - - dim2 = Dim.Width (view2); - Assert.NotEqual (dim1, dim2); - - testRect1 = new Rect (0, 1, 2, 3); - view1 = new View (testRect1); - testRect2 = new Rect (0, 1, 2, 3); - dim1 = Dim.Width (view1); - dim2 = Dim.Width (view1); - // FIXED: Dim.Width should support Equals() and this should change to Equal. - Assert.Equal (dim1, dim2); - - testRect1 = new Rect (0, -1, 2, 3); - view1 = new View (testRect1); - testRect2 = new Rect (0, -1, 2, 3); - dim1 = Dim.Width (view1); - dim2 = Dim.Width (view1); - // FIXED: Dim.Width should support Equals() and this should change to Equal. - Assert.Equal (dim1, dim2); - - testRect1 = new Rect (0, -1, 2, 3); - view1 = new View (testRect1); - testRect2 = Rect.Empty; - view2 = new View (testRect2); - dim1 = Dim.Width (view1); - dim2 = Dim.Width (view2); - Assert.NotEqual (dim1, dim2); -#if DEBUG_IDISPOSABLE - // HACK: Force clean up of Responders to avoid having to Dispose all the Views created above. - Responder.Instances.Clear (); - Assert.Empty (Responder.Instances); -#endif - } - - [Fact] - public void Height_Set_To_Null_Throws () - { - var dim = Dim.Height (null); - Assert.Throws (() => dim.ToString ()); - } - - [Fact, TestRespondersDisposed] - public void Height_SetsValue () - { - var testVal = Rect.Empty; - var testValview = new View (testVal); - var dim = Dim.Height (testValview); - Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); - testValview.Dispose (); - - testVal = new Rect (1, 2, 3, 4); - testValview = new View (testVal); - dim = Dim.Height (testValview); - Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); - testValview.Dispose (); - } - - // TODO: Other Dim.Height tests (e.g. Equal?) - - [Fact] - public void Fill_SetsValue () - { - int testMargin = 0; - var dim = Dim.Fill (); - Assert.Equal ($"Fill({testMargin})", dim.ToString ()); - - testMargin = 0; - dim = Dim.Fill (testMargin); - Assert.Equal ($"Fill({testMargin})", dim.ToString ()); - - testMargin = 5; - dim = Dim.Fill (testMargin); - Assert.Equal ($"Fill({testMargin})", dim.ToString ()); - } - - [Fact] - public void Fill_Equal () - { - int margin1 = 0; - int margin2 = 0; - var dim1 = Dim.Fill (margin1); - var dim2 = Dim.Fill (margin2); - Assert.Equal (dim1, dim2); - } - - [Fact] - public void Percent_SetsValue () - { - float f = 0; - var dim = Dim.Percent (f); - Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ()); - f = 0.5F; - dim = Dim.Percent (f); - Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ()); - f = 100; - dim = Dim.Percent (f); - Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ()); - } - - [Fact] - public void Percent_Equals () - { - float n1 = 0; - float n2 = 0; - var dim1 = Dim.Percent (n1); - var dim2 = Dim.Percent (n2); - Assert.Equal (dim1, dim2); - - n1 = n2 = 1; - dim1 = Dim.Percent (n1); - dim2 = Dim.Percent (n2); - Assert.Equal (dim1, dim2); - - n1 = n2 = 0.5f; - dim1 = Dim.Percent (n1); - dim2 = Dim.Percent (n2); - Assert.Equal (dim1, dim2); - - n1 = n2 = 100f; - dim1 = Dim.Percent (n1); - dim2 = Dim.Percent (n2); - Assert.Equal (dim1, dim2); - - n1 = n2 = 0.3f; - dim1 = Dim.Percent (n1, true); - dim2 = Dim.Percent (n2, true); - Assert.Equal (dim1, dim2); - - n1 = n2 = 0.3f; - dim1 = Dim.Percent (n1); - dim2 = Dim.Percent (n2, true); - Assert.NotEqual (dim1, dim2); - - n1 = 0; - n2 = 1; - dim1 = Dim.Percent (n1); - dim2 = Dim.Percent (n2); - Assert.NotEqual (dim1, dim2); - - n1 = 0.5f; - n2 = 1.5f; - dim1 = Dim.Percent (n1); - dim2 = Dim.Percent (n2); - Assert.NotEqual (dim1, dim2); - } - - [Fact] - public void Percent_Invalid_Throws () - { - var dim = Dim.Percent (0); - Assert.Throws (() => dim = Dim.Percent (-1)); - Assert.Throws (() => dim = Dim.Percent (101)); - Assert.Throws (() => dim = Dim.Percent (100.0001F)); - Assert.Throws (() => dim = Dim.Percent (1000001)); - } - - [Fact] [AutoInitShutdown] - public void ForceValidatePosDim_True_Dim_Validation_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_Throws () - { - var t = Application.Top; - - var w = new Window () { - Width = Dim.Fill (0), - Height = Dim.Sized (10) - }; - var v = new View ("v") { - Width = Dim.Width (w) - 2, - Height = Dim.Percent (10), - ValidatePosDim = true - }; - - w.Add (v); - t.Add (w); - - t.Ready += (s, e) => { - Assert.Equal (2, w.Width = 2); - Assert.Equal (2, w.Height = 2); - Assert.Throws (() => v.Width = 2); - Assert.Throws (() => v.Height = 2); - v.ValidatePosDim = false; - var exception = Record.Exception (() => v.Width = 2); - Assert.Null (exception); - Assert.Equal (2, v.Width); - exception = Record.Exception (() => v.Height = 2); - Assert.Null (exception); - Assert.Equal (2, v.Height); - }; - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (); - } - - [Fact] [TestRespondersDisposed] - public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null () - { - var t = new View ("top") { Width = 80, Height = 25 }; - - var w = new Window (new Rect (1, 2, 4, 5)) { Title = "w" }; - t.Add (w); - t.LayoutSubviews (); - - Assert.Equal (3, w.Width = 3); - Assert.Equal (4, w.Height = 4); - t.Dispose (); - } - - [Fact] [TestRespondersDisposed] - public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () - { - var t = new View ("top") { Width = 80, Height = 25 }; - - var w = new Window () { - Width = Dim.Fill (0), - Height = Dim.Sized (10) - }; - var v = new View ("v") { - Width = Dim.Width (w) - 2, - Height = Dim.Percent (10) - }; - - w.Add (v); - t.Add (w); - - t.LayoutSubviews (); - Assert.Equal (2, v.Width = 2); - Assert.Equal (2, v.Height = 2); - - v.LayoutStyle = LayoutStyle.Absolute; - t.LayoutSubviews (); - - Assert.Equal (2, v.Width = 2); - Assert.Equal (2, v.Height = 2); - t.Dispose (); - } - - [Fact] [AutoInitShutdown] - public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height () - { - // Testing with the Button because it properly handles the Dim class. - var t = Application.Top; - - var w = new Window () { - Width = 100, - Height = 100 - }; - - var f1 = new FrameView ("f1") { - X = 0, - Y = 0, - Width = Dim.Percent (50), - Height = 5 - }; - - var f2 = new FrameView ("f2") { - X = Pos.Right (f1), - Y = 0, - Width = Dim.Fill (), - Height = 5 - }; - - var v1 = new Button ("v1") { - AutoSize = false, - X = Pos.X (f1) + 2, - Y = Pos.Bottom (f1) + 2, - Width = Dim.Width (f1) - 2, - Height = Dim.Fill () - 2, - ValidatePosDim = true - }; - - var v2 = new Button ("v2") { - AutoSize = false, - X = Pos.X (f2) + 2, - Y = Pos.Bottom (f2) + 2, - Width = Dim.Width (f2) - 2, - Height = Dim.Fill () - 2, - ValidatePosDim = true - }; - - var v3 = new Button ("v3") { - AutoSize = false, - Width = Dim.Percent (10), - Height = Dim.Percent (10), - ValidatePosDim = true - }; - - var v4 = new Button ("v4") { - AutoSize = false, - Width = Dim.Sized (50), - Height = Dim.Sized (50), - ValidatePosDim = true - }; - - var v5 = new Button ("v5") { - AutoSize = false, - Width = Dim.Width (v1) - Dim.Width (v3), - Height = Dim.Height (v1) - Dim.Height (v3), - ValidatePosDim = true - }; - - var v6 = new Button ("v6") { - AutoSize = false, - X = Pos.X (f2), - Y = Pos.Bottom (f2) + 2, - Width = Dim.Percent (20, true), - Height = Dim.Percent (20, true), - ValidatePosDim = true - }; - - w.Add (f1, f2, v1, v2, v3, v4, v5, v6); - t.Add (w); - - t.Ready += (s, e) => { - Assert.Equal ("Absolute(100)", w.Width.ToString ()); - Assert.Equal ("Absolute(100)", w.Height.ToString ()); - Assert.Equal (100, w.Frame.Width); - Assert.Equal (100, w.Frame.Height); - - Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ()); - Assert.Equal ("Absolute(5)", f1.Height.ToString ()); - Assert.Equal (49, f1.Frame.Width); // 50-1=49 - Assert.Equal (5, f1.Frame.Height); - - Assert.Equal ("Fill(0)", f2.Width.ToString ()); - Assert.Equal ("Absolute(5)", f2.Height.ToString ()); - Assert.Equal (49, f2.Frame.Width); // 50-1=49 - Assert.Equal (5, f2.Frame.Height); - - Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,49,5))-Absolute(2))", v1.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); - Assert.Equal (47, v1.Frame.Width); // 49-2=47 - Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89 - - Assert.Equal ("Combine(View(Width,FrameView(f2)(49,0,49,5))-Absolute(2))", v2.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); - Assert.Equal (47, v2.Frame.Width); // 49-2=47 - Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89 - - Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ()); - Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ()); - Assert.Equal (9, v3.Frame.Width); // 98*10%=9 - Assert.Equal (9, v3.Frame.Height); // 98*10%=9 - - Assert.Equal ("Absolute(50)", v4.Width.ToString ()); - Assert.Equal ("Absolute(50)", v4.Height.ToString ()); - Assert.Equal (50, v4.Frame.Width); - Assert.Equal (50, v4.Frame.Height); - - Assert.Equal ("Combine(View(Width,Button(v1)(2,7,47,89))-View(Width,Button(v3)(0,0,9,9)))", v5.Width.ToString ()); - Assert.Equal ("Combine(View(Height,Button(v1)(2,7,47,89))-View(Height,Button(v3)(0,0,9,9)))", v5.Height.ToString ()); - Assert.Equal (38, v5.Frame.Width); // 47-9=38 - Assert.Equal (80, v5.Frame.Height); // 89-9=80 - - Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ()); - Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ()); - Assert.Equal (9, v6.Frame.Width); // 47*20%=9 - Assert.Equal (18, v6.Frame.Height); // 89*20%=18 - - w.Width = 200; - Assert.True (t.LayoutNeeded); - w.Height = 200; - t.LayoutSubviews (); - - Assert.Equal ("Absolute(200)", w.Width.ToString ()); - Assert.Equal ("Absolute(200)", w.Height.ToString ()); - Assert.Equal (200, w.Frame.Width); - Assert.Equal (200, w.Frame.Height); - - f1.Text = "Frame1"; - Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ()); - Assert.Equal ("Absolute(5)", f1.Height.ToString ()); - Assert.Equal (99, f1.Frame.Width); // 100-1=99 - Assert.Equal (5, f1.Frame.Height); - - f2.Text = "Frame2"; - Assert.Equal ("Fill(0)", f2.Width.ToString ()); - Assert.Equal ("Absolute(5)", f2.Height.ToString ()); - Assert.Equal (99, f2.Frame.Width); // 100-1=99 - Assert.Equal (5, f2.Frame.Height); - - v1.Text = "Button1"; - Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,99,5))-Absolute(2))", v1.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); - Assert.Equal (97, v1.Frame.Width); // 99-2=97 - Assert.Equal (189, v1.Frame.Height); // 198-2-7=189 - - v2.Text = "Button2"; - Assert.Equal ("Combine(View(Width,FrameView(f2)(99,0,99,5))-Absolute(2))", v2.Width.ToString ()); - Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); - Assert.Equal (97, v2.Frame.Width); // 99-2=97 - Assert.Equal (189, v2.Frame.Height); // 198-2-7=189 - - v3.Text = "Button3"; - Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ()); - Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ()); - Assert.Equal (19, v3.Frame.Width); // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width - Assert.Equal (19, v3.Frame.Height); // 199*10%=19 - - v4.Text = "Button4"; - v4.AutoSize = false; - Assert.Equal ("Absolute(50)", v4.Width.ToString ()); - Assert.Equal ("Absolute(50)", v4.Height.ToString ()); - Assert.Equal (50, v4.Frame.Width); - Assert.Equal (50, v4.Frame.Height); - v4.AutoSize = true; - Assert.Equal ("Absolute(11)", v4.Width.ToString ()); - Assert.Equal ("Absolute(1)", v4.Height.ToString ()); - Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute - Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute - - v5.Text = "Button5"; - Assert.Equal ("Combine(View(Width,Button(v1)(2,7,97,189))-View(Width,Button(v3)(0,0,19,19)))", v5.Width.ToString ()); - Assert.Equal ("Combine(View(Height,Button(v1)(2,7,97,189))-View(Height,Button(v3)(0,0,19,19)))", v5.Height.ToString ()); - Assert.Equal (78, v5.Frame.Width); // 97-9=78 - Assert.Equal (170, v5.Frame.Height); // 189-19=170 - - v6.Text = "Button6"; - Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ()); - Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ()); - Assert.Equal (19, v6.Frame.Width); // 99*20%=19 - Assert.Equal (38, v6.Frame.Height); // 198-7*20=18 - }; - - Application.Iteration += (s, a) => Application.RequestStop (); - - Application.Run (); - } - - // See #2461 - //[Fact] - //public void Dim_Referencing_SuperView_Throws () - //{ - // var super = new View ("super") { - // Width = 10, - // Height = 10 - // }; - // var view = new View ("view") { - // Width = Dim.Width (super), // this is not allowed - // Height = Dim.Height (super), // this is not allowed - // }; - - // super.Add (view); - // super.BeginInit (); - // super.EndInit (); - // Assert.Throws (() => super.LayoutSubviews ()); - //} - - /// - /// This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461 - /// - [Fact] [TestRespondersDisposed] - public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView () - { - var t = new View () { Width = 80, Height = 25 }; - - var w = new Window () { - Width = Dim.Width (t) - 2, // 78 - Height = Dim.Height (t) - 2 // 23 - }; - var f = new FrameView (); - var v1 = new View () { - Width = Dim.Width (w) - 2, // 76 - Height = Dim.Height (w) - 2 // 21 - }; - var v2 = new View () { - Width = Dim.Width (v1) - 2, // 74 - Height = Dim.Height (v1) - 2 // 19 - }; - - f.Add (v1, v2); - w.Add (f); - t.Add (w); - t.BeginInit (); - t.EndInit (); - - // BUGBUG: v2 - f references t here; t is f's super-superview. This is supported! - // BUGBUG: v2 - f references v2 here; v2 is f's subview. This is not supported! - f.Width = Dim.Width (t) - Dim.Width (v2); // 80 - 74 = 6 - f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6 - - Assert.Throws (t.LayoutSubviews); - Assert.Equal (80, t.Frame.Width); - Assert.Equal (25, t.Frame.Height); - Assert.Equal (78, w.Frame.Width); - Assert.Equal (23, w.Frame.Height); - // BUGBUG: v2 - this no longer works - see above - //Assert.Equal (6, f.Frame.Width); - //Assert.Equal (6, f.Frame.Height); - //Assert.Equal (76, v1.Frame.Width); - //Assert.Equal (21, v1.Frame.Height); - //Assert.Equal (74, v2.Frame.Width); - //Assert.Equal (19, v2.Frame.Height); - t.Dispose (); - } - - [Fact] [TestRespondersDisposed] - public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView () - { - var t = new View ("top") { Width = 80, Height = 25 }; - - var w = new Window () { - Width = Dim.Width (t) - 2, // 78 - Height = Dim.Height (t) - 2 // 23 - }; - var f = new FrameView (); - var v1 = new View () { - Width = Dim.Width (w) - 2, // 76 - Height = Dim.Height (w) - 2 // 21 - }; - var v2 = new View () { - Width = Dim.Width (v1) - 2, // 74 - Height = Dim.Height (v1) - 2 // 19 - }; - - f.Add (v1, v2); - w.Add (f); - t.Add (w); - t.BeginInit (); - t.EndInit (); - - f.Width = Dim.Width (t) - Dim.Width (w) + 4; // 80 - 74 = 6 - f.Height = Dim.Height (t) - Dim.Height (w) + 4; // 25 - 19 = 6 - - // BUGBUG: v2 - f references t and w here; t is f's super-superview and w is f's superview. This is supported! - var exception = Record.Exception (t.LayoutSubviews); - Assert.Null (exception); - Assert.Equal (80, t.Frame.Width); - Assert.Equal (25, t.Frame.Height); - Assert.Equal (78, w.Frame.Width); - Assert.Equal (23, w.Frame.Height); - Assert.Equal (6, f.Frame.Width); - Assert.Equal (6, f.Frame.Height); - Assert.Equal (76, v1.Frame.Width); - Assert.Equal (21, v1.Frame.Height); - Assert.Equal (74, v2.Frame.Width); - Assert.Equal (19, v2.Frame.Height); - t.Dispose (); - } - - [Fact] [TestRespondersDisposed] - public void PosCombine_View_Not_Added_Throws () - { - var t = new View () { Width = 80, Height = 50 }; - - // BUGBUG: v2 - super should not reference it's superview (t) - var super = new View () { - Width = Dim.Width (t) - 2, - Height = Dim.Height (t) - 2 - }; - t.Add (super); - - var sub = new View (); - super.Add (sub); - - var v1 = new View () { - Width = Dim.Width (super) - 2, - Height = Dim.Height (super) - 2 - }; - var v2 = new View () { - Width = Dim.Width (v1) - 2, - Height = Dim.Height (v1) - 2 - }; - sub.Add (v1); - // v2 not added to sub; should cause exception on Layout since it's referenced by sub. - sub.Width = Dim.Fill () - Dim.Width (v2); - sub.Height = Dim.Fill () - Dim.Height (v2); - - t.BeginInit (); - t.EndInit (); - - Assert.Throws (() => t.LayoutSubviews ()); - t.Dispose (); - v2.Dispose (); - } - - [Fact] [AutoInitShutdown] - public void Dim_Add_Operator () - { - var top = Application.Top; - - var view = new View () { X = 0, Y = 0, Width = 20, Height = 0 }; - var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 }; - int count = 0; - - field.KeyDown += (s, k) => { - if (k.KeyCode == KeyCode.Enter) { - field.Text = $"Label {count}"; - var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 }; - view.Add (label); - Assert.Equal ($"Label {count}", label.Text); - Assert.Equal ($"Absolute({count})", label.Y.ToString ()); - - Assert.Equal ($"Absolute({count})", view.Height.ToString ()); - view.Height += 1; - count++; - Assert.Equal ($"Absolute({count})", view.Height.ToString ()); - } - }; - - Application.Iteration += (s, a) => { - while (count < 20) { - field.NewKeyDownEvent (new Key (KeyCode.Enter)); - } - - Application.RequestStop (); - }; - - var win = new Window (); - win.Add (view); - win.Add (field); - - top.Add (win); - - Application.Run (top); - - Assert.Equal (20, count); - } - - string [] expecteds = new string [21] { + readonly string [] expecteds = new string [21] { @" ┌────────────────────┐ │View with long text │ @@ -1042,6 +331,717 @@ public class DimTests { └────────────────────┘" }; + public DimTests (ITestOutputHelper output) + { + _output = output; + Console.OutputEncoding = Encoding.Default; + // Change current culture + var culture = CultureInfo.CreateSpecificCulture ("en-US"); + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + } + + [Fact] + public void New_Works () + { + var dim = new Dim (); + Assert.Equal ("Terminal.Gui.Dim", dim.ToString ()); + } + + [Fact] + public void Sized_SetsValue () + { + var dim = Dim.Sized (0); + Assert.Equal ("Absolute(0)", dim.ToString ()); + + var testVal = 5; + dim = Dim.Sized (testVal); + Assert.Equal ($"Absolute({testVal})", dim.ToString ()); + + testVal = -1; + dim = Dim.Sized (testVal); + Assert.Equal ($"Absolute({testVal})", dim.ToString ()); + } + + [Fact] + public void Sized_Equals () + { + var n1 = 0; + var n2 = 0; + var dim1 = Dim.Sized (n1); + var dim2 = Dim.Sized (n2); + Assert.Equal (dim1, dim2); + + n1 = n2 = 1; + dim1 = Dim.Sized (n1); + dim2 = Dim.Sized (n2); + Assert.Equal (dim1, dim2); + + n1 = n2 = -1; + dim1 = Dim.Sized (n1); + dim2 = Dim.Sized (n2); + Assert.Equal (dim1, dim2); + + n1 = 0; + n2 = 1; + dim1 = Dim.Sized (n1); + dim2 = Dim.Sized (n2); + Assert.NotEqual (dim1, dim2); + } + + [Fact] + public void Width_Set_To_Null_Throws () + { + var dim = Dim.Width (null); + Assert.Throws (() => dim.ToString ()); + } + + [Fact] [TestRespondersDisposed] + public void SetsValue () + { + var testVal = Rect.Empty; + var testValView = new View (testVal); + var dim = Dim.Width (testValView); + Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); + testValView.Dispose (); + + testVal = new Rect (1, 2, 3, 4); + testValView = new View (testVal); + dim = Dim.Width (testValView); + Assert.Equal ($"View(Width,View(){testVal})", dim.ToString ()); + testValView.Dispose (); + } + + [Fact] [TestRespondersDisposed] + public void Width_Equals () + { + var testRect1 = Rect.Empty; + var view1 = new View (testRect1); + var testRect2 = Rect.Empty; + var view2 = new View (testRect2); + + var dim1 = Dim.Width (view1); + var dim2 = Dim.Width (view1); + // FIXED: Dim.Width should support Equals() and this should change to Equal. + Assert.Equal (dim1, dim2); + + dim2 = Dim.Width (view2); + Assert.NotEqual (dim1, dim2); + + testRect1 = new Rect (0, 1, 2, 3); + view1 = new View (testRect1); + testRect2 = new Rect (0, 1, 2, 3); + dim1 = Dim.Width (view1); + dim2 = Dim.Width (view1); + // FIXED: Dim.Width should support Equals() and this should change to Equal. + Assert.Equal (dim1, dim2); + + testRect1 = new Rect (0, -1, 2, 3); + view1 = new View (testRect1); + testRect2 = new Rect (0, -1, 2, 3); + dim1 = Dim.Width (view1); + dim2 = Dim.Width (view1); + // FIXED: Dim.Width should support Equals() and this should change to Equal. + Assert.Equal (dim1, dim2); + + testRect1 = new Rect (0, -1, 2, 3); + view1 = new View (testRect1); + testRect2 = Rect.Empty; + view2 = new View (testRect2); + dim1 = Dim.Width (view1); + dim2 = Dim.Width (view2); + Assert.NotEqual (dim1, dim2); +#if DEBUG_IDISPOSABLE + // HACK: Force clean up of Responders to avoid having to Dispose all the Views created above. + Responder.Instances.Clear (); + Assert.Empty (Responder.Instances); +#endif + } + + [Fact] + public void Height_Set_To_Null_Throws () + { + var dim = Dim.Height (null); + Assert.Throws (() => dim.ToString ()); + } + + [Fact] [TestRespondersDisposed] + public void Height_SetsValue () + { + var testVal = Rect.Empty; + var testValview = new View (testVal); + var dim = Dim.Height (testValview); + Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); + testValview.Dispose (); + + testVal = new Rect (1, 2, 3, 4); + testValview = new View (testVal); + dim = Dim.Height (testValview); + Assert.Equal ($"View(Height,View(){testVal})", dim.ToString ()); + testValview.Dispose (); + } + + // TODO: Other Dim.Height tests (e.g. Equal?) + + [Fact] + public void Fill_SetsValue () + { + var testMargin = 0; + var dim = Dim.Fill (); + Assert.Equal ($"Fill({testMargin})", dim.ToString ()); + + testMargin = 0; + dim = Dim.Fill (testMargin); + Assert.Equal ($"Fill({testMargin})", dim.ToString ()); + + testMargin = 5; + dim = Dim.Fill (testMargin); + Assert.Equal ($"Fill({testMargin})", dim.ToString ()); + } + + [Fact] + public void Fill_Equal () + { + var margin1 = 0; + var margin2 = 0; + var dim1 = Dim.Fill (margin1); + var dim2 = Dim.Fill (margin2); + Assert.Equal (dim1, dim2); + } + + [Fact] + public void Percent_SetsValue () + { + float f = 0; + var dim = Dim.Percent (f); + Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ()); + f = 0.5F; + dim = Dim.Percent (f); + Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ()); + f = 100; + dim = Dim.Percent (f); + Assert.Equal ($"Factor({f / 100:0.###},{false})", dim.ToString ()); + } + + [Fact] + public void Percent_Equals () + { + float n1 = 0; + float n2 = 0; + var dim1 = Dim.Percent (n1); + var dim2 = Dim.Percent (n2); + Assert.Equal (dim1, dim2); + + n1 = n2 = 1; + dim1 = Dim.Percent (n1); + dim2 = Dim.Percent (n2); + Assert.Equal (dim1, dim2); + + n1 = n2 = 0.5f; + dim1 = Dim.Percent (n1); + dim2 = Dim.Percent (n2); + Assert.Equal (dim1, dim2); + + n1 = n2 = 100f; + dim1 = Dim.Percent (n1); + dim2 = Dim.Percent (n2); + Assert.Equal (dim1, dim2); + + n1 = n2 = 0.3f; + dim1 = Dim.Percent (n1, true); + dim2 = Dim.Percent (n2, true); + Assert.Equal (dim1, dim2); + + n1 = n2 = 0.3f; + dim1 = Dim.Percent (n1); + dim2 = Dim.Percent (n2, true); + Assert.NotEqual (dim1, dim2); + + n1 = 0; + n2 = 1; + dim1 = Dim.Percent (n1); + dim2 = Dim.Percent (n2); + Assert.NotEqual (dim1, dim2); + + n1 = 0.5f; + n2 = 1.5f; + dim1 = Dim.Percent (n1); + dim2 = Dim.Percent (n2); + Assert.NotEqual (dim1, dim2); + } + + [Fact] + public void Percent_Invalid_Throws () + { + var dim = Dim.Percent (0); + Assert.Throws (() => dim = Dim.Percent (-1)); + Assert.Throws (() => dim = Dim.Percent (101)); + Assert.Throws (() => dim = Dim.Percent (100.0001F)); + Assert.Throws (() => dim = Dim.Percent (1000001)); + } + + [Fact] [AutoInitShutdown] + public void ForceValidatePosDim_True_Dim_Validation_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_Throws () + { + var t = Application.Top; + + var w = new Window { + Width = Dim.Fill (), + Height = Dim.Sized (10) + }; + var v = new View ("v") { + Width = Dim.Width (w) - 2, + Height = Dim.Percent (10), + ValidatePosDim = true + }; + + w.Add (v); + t.Add (w); + + t.Ready += (s, e) => { + Assert.Equal (2, w.Width = 2); + Assert.Equal (2, w.Height = 2); + Assert.Throws (() => v.Width = 2); + Assert.Throws (() => v.Height = 2); + v.ValidatePosDim = false; + var exception = Record.Exception (() => v.Width = 2); + Assert.Null (exception); + Assert.Equal (2, v.Width); + exception = Record.Exception (() => v.Height = 2); + Assert.Null (exception); + Assert.Equal (2, v.Height); + }; + + Application.Iteration += (s, a) => Application.RequestStop (); + + Application.Run (); + } + + [Fact] [TestRespondersDisposed] + public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Null () + { + var t = new View ("top") { Width = 80, Height = 25 }; + + var w = new Window (new Rect (1, 2, 4, 5)) { Title = "w" }; + t.Add (w); + t.LayoutSubviews (); + + Assert.Equal (3, w.Width = 3); + Assert.Equal (4, w.Height = 4); + t.Dispose (); + } + + [Fact] [TestRespondersDisposed] + public void Dim_Validation_Do_Not_Throws_If_NewValue_Is_DimAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute () + { + var t = new View ("top") { Width = 80, Height = 25 }; + + var w = new Window { + Width = Dim.Fill (), + Height = Dim.Sized (10) + }; + var v = new View ("v") { + Width = Dim.Width (w) - 2, + Height = Dim.Percent (10) + }; + + w.Add (v); + t.Add (w); + + t.LayoutSubviews (); + Assert.Equal (2, v.Width = 2); + Assert.Equal (2, v.Height = 2); + + v.LayoutStyle = LayoutStyle.Absolute; + t.LayoutSubviews (); + + Assert.Equal (2, v.Width = 2); + Assert.Equal (2, v.Height = 2); + t.Dispose (); + } + + [Fact] [AutoInitShutdown] + public void Only_DimAbsolute_And_DimFactor_As_A_Different_Procedure_For_Assigning_Value_To_Width_Or_Height () + { + // Testing with the Button because it properly handles the Dim class. + var t = Application.Top; + + var w = new Window { + Width = 100, + Height = 100 + }; + + var f1 = new FrameView ("f1") { + X = 0, + Y = 0, + Width = Dim.Percent (50), + Height = 5 + }; + + var f2 = new FrameView ("f2") { + X = Pos.Right (f1), + Y = 0, + Width = Dim.Fill (), + Height = 5 + }; + + var v1 = new Button ("v1") { + AutoSize = false, + X = Pos.X (f1) + 2, + Y = Pos.Bottom (f1) + 2, + Width = Dim.Width (f1) - 2, + Height = Dim.Fill () - 2, + ValidatePosDim = true + }; + + var v2 = new Button ("v2") { + AutoSize = false, + X = Pos.X (f2) + 2, + Y = Pos.Bottom (f2) + 2, + Width = Dim.Width (f2) - 2, + Height = Dim.Fill () - 2, + ValidatePosDim = true + }; + + var v3 = new Button ("v3") { + AutoSize = false, + Width = Dim.Percent (10), + Height = Dim.Percent (10), + ValidatePosDim = true + }; + + var v4 = new Button ("v4") { + AutoSize = false, + Width = Dim.Sized (50), + Height = Dim.Sized (50), + ValidatePosDim = true + }; + + var v5 = new Button ("v5") { + AutoSize = false, + Width = Dim.Width (v1) - Dim.Width (v3), + Height = Dim.Height (v1) - Dim.Height (v3), + ValidatePosDim = true + }; + + var v6 = new Button ("v6") { + AutoSize = false, + X = Pos.X (f2), + Y = Pos.Bottom (f2) + 2, + Width = Dim.Percent (20, true), + Height = Dim.Percent (20, true), + ValidatePosDim = true + }; + + w.Add (f1, f2, v1, v2, v3, v4, v5, v6); + t.Add (w); + + t.Ready += (s, e) => { + Assert.Equal ("Absolute(100)", w.Width.ToString ()); + Assert.Equal ("Absolute(100)", w.Height.ToString ()); + Assert.Equal (100, w.Frame.Width); + Assert.Equal (100, w.Frame.Height); + + Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ()); + Assert.Equal ("Absolute(5)", f1.Height.ToString ()); + Assert.Equal (49, f1.Frame.Width); // 50-1=49 + Assert.Equal (5, f1.Frame.Height); + + Assert.Equal ("Fill(0)", f2.Width.ToString ()); + Assert.Equal ("Absolute(5)", f2.Height.ToString ()); + Assert.Equal (49, f2.Frame.Width); // 50-1=49 + Assert.Equal (5, f2.Frame.Height); + + Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,49,5))-Absolute(2))", v1.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); + Assert.Equal (47, v1.Frame.Width); // 49-2=47 + Assert.Equal (89, v1.Frame.Height); // 98-5-2-2=89 + + Assert.Equal ("Combine(View(Width,FrameView(f2)(49,0,49,5))-Absolute(2))", v2.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); + Assert.Equal (47, v2.Frame.Width); // 49-2=47 + Assert.Equal (89, v2.Frame.Height); // 98-5-2-2=89 + + Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ()); + Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ()); + Assert.Equal (9, v3.Frame.Width); // 98*10%=9 + Assert.Equal (9, v3.Frame.Height); // 98*10%=9 + + Assert.Equal ("Absolute(50)", v4.Width.ToString ()); + Assert.Equal ("Absolute(50)", v4.Height.ToString ()); + Assert.Equal (50, v4.Frame.Width); + Assert.Equal (50, v4.Frame.Height); + + Assert.Equal ("Combine(View(Width,Button(v1)(2,7,47,89))-View(Width,Button(v3)(0,0,9,9)))", v5.Width.ToString ()); + Assert.Equal ("Combine(View(Height,Button(v1)(2,7,47,89))-View(Height,Button(v3)(0,0,9,9)))", v5.Height.ToString ()); + Assert.Equal (38, v5.Frame.Width); // 47-9=38 + Assert.Equal (80, v5.Frame.Height); // 89-9=80 + + Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ()); + Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ()); + Assert.Equal (9, v6.Frame.Width); // 47*20%=9 + Assert.Equal (18, v6.Frame.Height); // 89*20%=18 + + w.Width = 200; + Assert.True (t.LayoutNeeded); + w.Height = 200; + t.LayoutSubviews (); + + Assert.Equal ("Absolute(200)", w.Width.ToString ()); + Assert.Equal ("Absolute(200)", w.Height.ToString ()); + Assert.Equal (200, w.Frame.Width); + Assert.Equal (200, w.Frame.Height); + + f1.Text = "Frame1"; + Assert.Equal ("Factor(0.5,False)", f1.Width.ToString ()); + Assert.Equal ("Absolute(5)", f1.Height.ToString ()); + Assert.Equal (99, f1.Frame.Width); // 100-1=99 + Assert.Equal (5, f1.Frame.Height); + + f2.Text = "Frame2"; + Assert.Equal ("Fill(0)", f2.Width.ToString ()); + Assert.Equal ("Absolute(5)", f2.Height.ToString ()); + Assert.Equal (99, f2.Frame.Width); // 100-1=99 + Assert.Equal (5, f2.Frame.Height); + + v1.Text = "Button1"; + Assert.Equal ("Combine(View(Width,FrameView(f1)(0,0,99,5))-Absolute(2))", v1.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v1.Height.ToString ()); + Assert.Equal (97, v1.Frame.Width); // 99-2=97 + Assert.Equal (189, v1.Frame.Height); // 198-2-7=189 + + v2.Text = "Button2"; + Assert.Equal ("Combine(View(Width,FrameView(f2)(99,0,99,5))-Absolute(2))", v2.Width.ToString ()); + Assert.Equal ("Combine(Fill(0)-Absolute(2))", v2.Height.ToString ()); + Assert.Equal (97, v2.Frame.Width); // 99-2=97 + Assert.Equal (189, v2.Frame.Height); // 198-2-7=189 + + v3.Text = "Button3"; + Assert.Equal ("Factor(0.1,False)", v3.Width.ToString ()); + Assert.Equal ("Factor(0.1,False)", v3.Height.ToString ()); + Assert.Equal (19, v3.Frame.Width); // 198*10%=19 * Percent is related to the super-view if it isn't null otherwise the view width + Assert.Equal (19, v3.Frame.Height); // 199*10%=19 + + v4.Text = "Button4"; + v4.AutoSize = false; + Assert.Equal ("Absolute(50)", v4.Width.ToString ()); + Assert.Equal ("Absolute(50)", v4.Height.ToString ()); + Assert.Equal (50, v4.Frame.Width); + Assert.Equal (50, v4.Frame.Height); + v4.AutoSize = true; + Assert.Equal ("Absolute(11)", v4.Width.ToString ()); + Assert.Equal ("Absolute(1)", v4.Height.ToString ()); + Assert.Equal (11, v4.Frame.Width); // 11 is the text length and because is Dim.DimAbsolute + Assert.Equal (1, v4.Frame.Height); // 1 because is Dim.DimAbsolute + + v5.Text = "Button5"; + Assert.Equal ("Combine(View(Width,Button(v1)(2,7,97,189))-View(Width,Button(v3)(0,0,19,19)))", v5.Width.ToString ()); + Assert.Equal ("Combine(View(Height,Button(v1)(2,7,97,189))-View(Height,Button(v3)(0,0,19,19)))", v5.Height.ToString ()); + Assert.Equal (78, v5.Frame.Width); // 97-9=78 + Assert.Equal (170, v5.Frame.Height); // 189-19=170 + + v6.Text = "Button6"; + Assert.Equal ("Factor(0.2,True)", v6.Width.ToString ()); + Assert.Equal ("Factor(0.2,True)", v6.Height.ToString ()); + Assert.Equal (19, v6.Frame.Width); // 99*20%=19 + Assert.Equal (38, v6.Frame.Height); // 198-7*20=18 + }; + + Application.Iteration += (s, a) => Application.RequestStop (); + + Application.Run (); + } + + // See #2461 + //[Fact] + //public void Dim_Referencing_SuperView_Throws () + //{ + // var super = new View ("super") { + // Width = 10, + // Height = 10 + // }; + // var view = new View ("view") { + // Width = Dim.Width (super), // this is not allowed + // Height = Dim.Height (super), // this is not allowed + // }; + + // super.Add (view); + // super.BeginInit (); + // super.EndInit (); + // Assert.Throws (() => super.LayoutSubviews ()); + //} + + /// + /// This is an intentionally obtuse test. See https://github.com/gui-cs/Terminal.Gui/issues/2461 + /// + [Fact] [TestRespondersDisposed] + public void DimCombine_ObtuseScenario_Throw_If_SuperView_Refs_SubView () + { + var t = new View { Width = 80, Height = 25 }; + + var w = new Window { + Width = Dim.Width (t) - 2, // 78 + Height = Dim.Height (t) - 2 // 23 + }; + var f = new FrameView (); + var v1 = new View { + Width = Dim.Width (w) - 2, // 76 + Height = Dim.Height (w) - 2 // 21 + }; + var v2 = new View { + Width = Dim.Width (v1) - 2, // 74 + Height = Dim.Height (v1) - 2 // 19 + }; + + f.Add (v1, v2); + w.Add (f); + t.Add (w); + t.BeginInit (); + t.EndInit (); + + // BUGBUG: v2 - f references t here; t is f's super-superview. This is supported! + // BUGBUG: v2 - f references v2 here; v2 is f's subview. This is not supported! + f.Width = Dim.Width (t) - Dim.Width (v2); // 80 - 74 = 6 + f.Height = Dim.Height (t) - Dim.Height (v2); // 25 - 19 = 6 + + Assert.Throws (t.LayoutSubviews); + Assert.Equal (80, t.Frame.Width); + Assert.Equal (25, t.Frame.Height); + Assert.Equal (78, w.Frame.Width); + Assert.Equal (23, w.Frame.Height); + // BUGBUG: v2 - this no longer works - see above + //Assert.Equal (6, f.Frame.Width); + //Assert.Equal (6, f.Frame.Height); + //Assert.Equal (76, v1.Frame.Width); + //Assert.Equal (21, v1.Frame.Height); + //Assert.Equal (74, v2.Frame.Width); + //Assert.Equal (19, v2.Frame.Height); + t.Dispose (); + } + + [Fact] [TestRespondersDisposed] + public void DimCombine_ObtuseScenario_Does_Not_Throw_If_Two_SubViews_Refs_The_Same_SuperView () + { + var t = new View ("top") { Width = 80, Height = 25 }; + + var w = new Window { + Width = Dim.Width (t) - 2, // 78 + Height = Dim.Height (t) - 2 // 23 + }; + var f = new FrameView (); + var v1 = new View { + Width = Dim.Width (w) - 2, // 76 + Height = Dim.Height (w) - 2 // 21 + }; + var v2 = new View { + Width = Dim.Width (v1) - 2, // 74 + Height = Dim.Height (v1) - 2 // 19 + }; + + f.Add (v1, v2); + w.Add (f); + t.Add (w); + t.BeginInit (); + t.EndInit (); + + f.Width = Dim.Width (t) - Dim.Width (w) + 4; // 80 - 74 = 6 + f.Height = Dim.Height (t) - Dim.Height (w) + 4; // 25 - 19 = 6 + + // BUGBUG: v2 - f references t and w here; t is f's super-superview and w is f's superview. This is supported! + var exception = Record.Exception (t.LayoutSubviews); + Assert.Null (exception); + Assert.Equal (80, t.Frame.Width); + Assert.Equal (25, t.Frame.Height); + Assert.Equal (78, w.Frame.Width); + Assert.Equal (23, w.Frame.Height); + Assert.Equal (6, f.Frame.Width); + Assert.Equal (6, f.Frame.Height); + Assert.Equal (76, v1.Frame.Width); + Assert.Equal (21, v1.Frame.Height); + Assert.Equal (74, v2.Frame.Width); + Assert.Equal (19, v2.Frame.Height); + t.Dispose (); + } + + [Fact] [TestRespondersDisposed] + public void PosCombine_View_Not_Added_Throws () + { + var t = new View { Width = 80, Height = 50 }; + + // BUGBUG: v2 - super should not reference it's superview (t) + var super = new View { + Width = Dim.Width (t) - 2, + Height = Dim.Height (t) - 2 + }; + t.Add (super); + + var sub = new View (); + super.Add (sub); + + var v1 = new View { + Width = Dim.Width (super) - 2, + Height = Dim.Height (super) - 2 + }; + var v2 = new View { + Width = Dim.Width (v1) - 2, + Height = Dim.Height (v1) - 2 + }; + sub.Add (v1); + // v2 not added to sub; should cause exception on Layout since it's referenced by sub. + sub.Width = Dim.Fill () - Dim.Width (v2); + sub.Height = Dim.Fill () - Dim.Height (v2); + + t.BeginInit (); + t.EndInit (); + + Assert.Throws (() => t.LayoutSubviews ()); + t.Dispose (); + v2.Dispose (); + } + + [Fact] [AutoInitShutdown] + public void Dim_Add_Operator () + { + var top = Application.Top; + + var view = new View { X = 0, Y = 0, Width = 20, Height = 0 }; + var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 }; + var count = 0; + + field.KeyDown += (s, k) => { + if (k.KeyCode == KeyCode.Enter) { + field.Text = $"Label {count}"; + var label = new Label (field.Text) { X = 0, Y = view.Bounds.Height, Width = 20 }; + view.Add (label); + Assert.Equal ($"Label {count}", label.Text); + Assert.Equal ($"Absolute({count})", label.Y.ToString ()); + + Assert.Equal ($"Absolute({count})", view.Height.ToString ()); + view.Height += 1; + count++; + Assert.Equal ($"Absolute({count})", view.Height.ToString ()); + } + }; + + Application.Iteration += (s, a) => { + while (count < 20) { + field.NewKeyDownEvent (new Key (KeyCode.Enter)); + } + + Application.RequestStop (); + }; + + var win = new Window (); + win.Add (view); + win.Add (field); + + top.Add (win); + + Application.Run (top); + + Assert.Equal (20, count); + } + [Fact] [AutoInitShutdown] public void Dim_Add_Operator_With_Text () { @@ -1050,8 +1050,8 @@ public class DimTests { // BUGBUG: v2 - If a View's height is zero, it should not be drawn. //// Although view height is zero the text it's draw due the SetMinWidthHeight method var view = new View ("View with long text") { X = 0, Y = 0, Width = 20, Height = 1 }; - var field = new TextField () { X = 0, Y = Pos.Bottom (view), Width = 20 }; - int count = 0; + var field = new TextField { X = 0, Y = Pos.Bottom (view), Width = 20 }; + var count = 0; var listLabels = new List