From 42eb0c36cbe60e0ff0aa5a09fbf2edeb0d786757 Mon Sep 17 00:00:00 2001 From: Tig Kindel Date: Fri, 5 Jan 2024 12:01:59 -0700 Subject: [PATCH] Removes CheckAbsoulte and updates unit tests to match --- .editorconfig | 13 +- Terminal.Gui/View/Layout/PosDim.cs | 922 ++++++++------- Terminal.Gui/View/Layout/ViewLayout.cs | 946 ++++++++-------- UnitTests/View/Layout/DimTests.cs | 1431 ++++++++++++------------ UnitTests/View/Layout/LayoutTests.cs | 7 +- UnitTests/View/Layout/PosTests.cs | 33 - 6 files changed, 1693 insertions(+), 1659 deletions(-) diff --git a/.editorconfig b/.editorconfig index ed21eef37..65ef50ca8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -39,6 +39,9 @@ resharper_csharp_stick_comment = false resharper_csharp_wrap_parameters_style = chop_if_long resharper_force_attribute_style = separate resharper_indent_type_constraints = true +resharper_xmldoc_indent_size = 2 +resharper_xmldoc_indent_style = space +resharper_xmldoc_tab_width = 2 #resharper_int_align_binary_expressions = true resharper_int_align_comments = true resharper_int_align_invocations = true @@ -96,15 +99,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 19c0859e8..19367f500 100644 --- a/Terminal.Gui/View/Layout/PosDim.cs +++ b/Terminal.Gui/View/Layout/PosDim.cs @@ -1,161 +1,165 @@ using System; -using static Terminal.Gui.Dim; namespace Terminal.Gui; /// -/// Describes the position of a which can be an absolute value, a percentage, centered, or +/// 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 +/// 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. public static Pos Function (Func function) => new PosFunc (function); - internal class PosFactor : Pos { - readonly float _factor; - - public PosFactor (float n) => _factor = n; - - internal override int Anchor (int width) => (int)(width * _factor); - - public override string ToString () => $"Factor({_factor})"; - - public override int GetHashCode () => _factor.GetHashCode (); - - public override bool Equals (object other) => other is PosFactor f && f._factor == _factor; - } - - // Helper class to provide dynamic value by the execution of a function that returns an integer. - internal class PosFunc : Pos { - readonly Func _function; - - public PosFunc (Func n) => _function = n; - - internal override int Anchor (int width) => _function (); - - public override string ToString () => $"PosFunc({_function ()})"; - - public override int GetHashCode () => _function.GetHashCode (); - - public override bool Equals (object other) => other is PosFunc f && f._function () == _function (); - } - /// /// Creates a percentage object /// /// The percent object. /// A value between 0 and 100 representing the percentage. /// - /// This creates a that is centered horizontally, is 50% of the way down, + /// 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), - /// }; - /// + /// 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) { @@ -167,7 +171,7 @@ public class Pos { } /// - /// Creates a object that is anchored to the end (right side or bottom) of the dimension, + /// 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). @@ -183,63 +187,30 @@ public class Pos { public static Pos AnchorEnd (int offset = 0) { if (offset < 0) { - throw new ArgumentException (@"Must be positive", nameof(offset)); + throw new ArgumentException (@"Must be positive", nameof (offset)); } return new PosAnchorEnd (offset); } - internal class PosAnchorEnd : Pos { - readonly int _offset; - - public PosAnchorEnd (int offset) => _offset = offset; - - internal override int Anchor (int width) => width - _offset; - - public override string ToString () => $"AnchorEnd({_offset})"; - - public override int GetHashCode () => _offset.GetHashCode (); - - 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, + /// 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), - /// }; - /// + /// var textView = new TextView () { + /// X = Pos.Center (), + /// Y = Pos.Percent (50), + /// Width = Dim.Percent (80), + /// Height = Dim.Percent (30), + /// }; + /// /// public static Pos Center () => new PosCenter (); - internal class PosAbsolute : Pos { - readonly int _n; - public PosAbsolute (int n) => _n = n; - - public override string ToString () => $"Absolute({_n})"; - - internal override int Anchor (int width) => _n; - - public override int GetHashCode () => _n.GetHashCode (); - - public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n; - } - - internal class PosCenter : Pos { - internal override int Anchor (int width) => width / 2; - - public override string ToString () => "Center"; - } - /// /// Creates a object that is an absolute position based on the specified integer value. /// @@ -247,31 +218,6 @@ public class Pos { /// The value to convert to the . public static Pos At (int n) => new PosAbsolute (n); - internal class PosCombine : Pos { - internal Pos _left, _right; - internal bool _add; - - public PosCombine (bool add, Pos left, Pos right) - { - _left = left; - _right = right; - _add = add; - } - - internal override int Anchor (int width) - { - int la = _left.Anchor (width); - int ra = _right.Anchor (width); - if (_add) { - return la + ra; - } else { - return la - ra; - } - } - - public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; - } - /// /// Creates an Absolute from the specified integer value. /// @@ -319,9 +265,148 @@ public class Pos { } } + /// + /// 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; + + public PosFactor (float n) => _factor = n; + + internal override int Anchor (int width) => (int)(width * _factor); + + public override string ToString () => $"Factor({_factor})"; + + public override int GetHashCode () => _factor.GetHashCode (); + + public override bool Equals (object other) => other is PosFactor f && f._factor == _factor; + } + + // Helper class to provide dynamic value by the execution of a function that returns an integer. + internal class PosFunc : Pos { + readonly Func _function; + + public PosFunc (Func n) => _function = n; + + internal override int Anchor (int width) => _function (); + + public override string ToString () => $"PosFunc({_function ()})"; + + public override int GetHashCode () => _function.GetHashCode (); + + public override bool Equals (object other) => other is PosFunc f && f._function () == _function (); + } + + internal class PosAnchorEnd : Pos { + readonly int _offset; + + public PosAnchorEnd (int offset) => _offset = offset; + + internal override int Anchor (int width) => width - _offset; + + public override string ToString () => $"AnchorEnd({_offset})"; + + public override int GetHashCode () => _offset.GetHashCode (); + + public override bool Equals (object other) => other is PosAnchorEnd anchorEnd && anchorEnd._offset == _offset; + } + + internal class PosAbsolute : Pos { + readonly int _n; + public PosAbsolute (int n) => _n = n; + + public override string ToString () => $"Absolute({_n})"; + + internal override int Anchor (int width) => _n; + + public override int GetHashCode () => _n.GetHashCode (); + + public override bool Equals (object other) => other is PosAbsolute abs && abs._n == _n; + } + + internal class PosCenter : Pos { + internal override int Anchor (int width) => width / 2; + + public override string ToString () => "Center"; + } + + internal class PosCombine : Pos { + internal bool _add; + internal Pos _left, _right; + + public PosCombine (bool add, Pos left, Pos right) + { + _left = left; + _right = right; + _add = add; + } + + internal override int Anchor (int width) + { + var la = _left.Anchor (width); + var ra = _right.Anchor (width); + if (_add) { + return la + ra; + } + return la - ra; + } + + public override string ToString () => $"Combine({_left}{(_add ? '+' : '-')}{_right})"; + } + internal class PosView : Pos { public readonly View Target; - int side; + readonly int side; public PosView (View view, int side) { @@ -341,66 +426,217 @@ public class Pos { } } - 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; + 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})"; + } public override int GetHashCode () => Target.GetHashCode (); public override bool Equals (object other) => other is PosView abs && abs.Target == Target; } +} - /// - /// Creates a object that tracks the Left (X) position of the specified . - /// - /// The that depends on the other view. - /// The that will be tracked. - public static Pos Left (View view) => new PosView (view, 0); +/// +/// +/// 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 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 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 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 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 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"); + } - /// - /// 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); + return new DimFactor (n / 100, r); + } - /// - /// 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 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 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 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. @@ -409,73 +645,9 @@ 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 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 { @@ -492,34 +664,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; @@ -568,30 +712,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 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) { @@ -602,55 +725,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) @@ -659,6 +745,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, @@ -670,7 +758,7 @@ public class Dim { if (Target == null) { throw new NullReferenceException (); } - string tside = _side switch { + var tside = _side switch { 0 => "Height", 1 => "Width", _ => "unknown" @@ -682,28 +770,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/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index c57de2604..c65d6cbe4 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -13,39 +13,53 @@ namespace Terminal.Gui; /// public enum LayoutStyle { /// - /// The position and size of the view are based . + /// The position and size of the view are based . /// Absolute, /// - /// The position and size of the view will be computed based on - /// , , , and . will + /// The position and size of the view will be computed based on + /// , , , and . + /// will /// provide the absolute computed values. /// Computed } public partial class View { + bool _autoSize; + // The frame for the object. Relative to the SuperView's Bounds. Rect _frame; + LayoutStyle _layoutStyle; + + Dim _width, _height; + + Pos _x, _y; + /// - /// Gets or sets location and size of the view. The frame is relative to the 's . + /// Gets or sets location and size of the view. The frame is relative to the 's + /// . /// - /// The rectangle describing the location and size of the view, in coordinates relative to the . + /// + /// The rectangle describing the location and size of the view, in coordinates relative to the + /// . + /// /// - /// - /// Change the Frame when using the layout style to move or resize views. - /// - /// - /// Altering the Frame will change to . - /// Additionally, , , , and will be set - /// to the values of the Frame (using and ). - /// - /// - /// Altering the Frame will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// + /// + /// Change the Frame when using the layout style to move or resize views. + /// + /// + /// Altering the Frame will change to . + /// Additionally, , , , and will be set + /// to the values of the Frame (using and ). + /// + /// + /// Altering the Frame will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// /// public virtual Rect Frame { get => _frame; @@ -65,41 +79,46 @@ public partial class View { } /// - /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView. - /// The margin offsets the from the . + /// The frame (specified as a ) that separates a View from other SubViews of the same SuperView. + /// The margin offsets the from the . /// /// - /// - /// The frames (, , and ) are not part of the View's content - /// and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout of the - /// and its . - /// + /// + /// The frames (, , and ) are not part of the View's + /// content + /// and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) + /// will change the size of the and trigger to update the layout + /// of the + /// and its . + /// /// public Frame Margin { get; private set; } /// - /// The frame (specified as a ) inside of the view that offsets the from the . - /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. - /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and - /// title will take up the first row and the second row will be filled with spaces. + /// The frame (specified as a ) inside of the view that offsets the from the + /// . + /// The Border provides the space for a visual border (drawn using line-drawing glyphs) and the Title. + /// The Border expands inward; in other words if `Border.Thickness.Top == 2` the border and + /// title will take up the first row and the second row will be filled with spaces. /// /// - /// - /// provides a simple helper for turning a simple border frame on or off. - /// - /// - /// The frames (, , and ) are not part of the View's content - /// and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout of the - /// and its . - /// + /// + /// provides a simple helper for turning a simple border frame on or off. + /// + /// + /// The frames (, , and ) are not part of the View's + /// content + /// and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) + /// will change the size of the and trigger to update the layout + /// of the + /// and its . + /// /// public Frame Border { get; private set; } @@ -107,18 +126,20 @@ public partial class View { /// Gets or sets whether the view has a one row/col thick border. /// /// - /// - /// This is a helper for manipulating the view's . Setting this property to any value other than - /// is equivalent to setting 's - /// to `1` and to the value. - /// - /// - /// Setting this property to is equivalent to setting 's - /// to `0` and to . - /// - /// - /// For more advanced customization of the view's border, manipulate see directly. - /// + /// + /// This is a helper for manipulating the view's . Setting this property to any value other + /// than + /// is equivalent to setting 's + /// to `1` and to the value. + /// + /// + /// Setting this property to is equivalent to setting 's + /// + /// to `0` and to . + /// + /// + /// For more advanced customization of the view's border, manipulate see directly. + /// /// public LineStyle BorderStyle { get => Border?.BorderStyle ?? LineStyle.None; @@ -138,31 +159,333 @@ public partial class View { } /// - /// The frame (specified as a ) inside of the view that offsets the from the . + /// The frame (specified as a ) inside of the view that offsets the from the + /// . /// /// - /// - /// The frames (, , and ) are not part of the View's content - /// and are not clipped by the View's Clip Area. - /// - /// - /// Changing the size of a frame (, , or ) - /// will change the size of the and trigger to update the layout of the - /// and its . - /// + /// + /// The frames (, , and ) are not part of the View's + /// content + /// and are not clipped by the View's Clip Area. + /// + /// + /// Changing the size of a frame (, , or ) + /// will change the size of the and trigger to update the layout + /// of the + /// and its . + /// /// public Frame Padding { get; private set; } /// - /// Helper to get the total thickness of the , , and . + /// Controls how the View's is computed during . If the style is set to + /// , LayoutSubviews does not change the . + /// If the style is the is updated using + /// the , , , and properties. + /// + /// + /// + /// Setting this property to will cause to determine the + /// size and position of the view. and will be set to + /// using . + /// + /// + /// Setting this property to will cause the view to use the + /// method to + /// size and position of the view. If either of the and properties are `null` they + /// will be set to using + /// the current value of . + /// If either of the and properties are `null` they will be set to + /// using . + /// + /// + /// The layout style. + public LayoutStyle LayoutStyle { + get => _layoutStyle; + //if ((X == null || X is Pos.PosAbsolute) && (Y == null || Y is Pos.PosAbsolute) && + //(Width == null || Width is Dim.DimAbsolute) && (Height == null || Height is Dim.DimAbsolute)) { + // return LayoutStyle.Absolute; + //} else { + // return LayoutStyle.Computed; + //} + set { + _layoutStyle = value; + //switch (_layoutStyle) { + //case LayoutStyle.Absolute: + // X = Frame.X; + // Y = Frame.Y; + // Width = Frame.Width; + // Height = Frame.Height; + // break; + + //case LayoutStyle.Computed: + // X ??= Frame.X; + // Y ??= Frame.Y; + // Width ??= Frame.Width; + // Height ??= Frame.Height; + // break; + //} + SetNeedsLayout (); + } + } + + /// + /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and + /// content are presented. + /// + /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. + /// + /// + /// If is the value of Bounds is indeterminate until + /// the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Updates to the Bounds updates , and has the same side effects as updating the + /// . + /// + /// + /// Altering the Bounds will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// Because coordinates are relative to the upper-left corner of the , + /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). + /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. + /// + /// + public virtual Rect Bounds { + get { +#if DEBUG + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { + Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"); + } +#endif // DEBUG + //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size); + var frameRelativeBounds = FrameGetInsideBounds (); + return new Rect (default, frameRelativeBounds.Size); + } + set { + // BUGBUG: Margin etc.. can be null (if typeof(Frame)) + Frame = new Rect (Frame.Location, + new Size ( + value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, + value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical + ) + ); + } + } + + /// + /// Gets or sets the X position for the view (the column). + /// + /// The object representing the X position. + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and + /// methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// is the same as Pos.Absolute(0). + /// + /// + public Pos X { + get => VerifyIsInitialized (_x, nameof (X)); + set { + // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? + _x = value; + + OnResizeNeeded (); + } + } + + /// + /// Gets or sets the Y position for the view (the row). + /// + /// The object representing the Y position. + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// and + /// methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + /// is the same as Pos.Absolute(0). + /// + /// + public Pos Y { + get => VerifyIsInitialized (_y, nameof (Y)); + set { + // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? + _y = value; + + OnResizeNeeded (); + } + } + + /// + /// Gets or sets the width of the view. + /// + /// The object representing the width of the view (the number of columns). + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + public Dim Width { + get => VerifyIsInitialized (_width, nameof (Width)); + set { + // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? + _width = value; + + if (ValidatePosDim) { + var isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); + + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); + } + } + OnResizeNeeded (); + } + } + + /// + /// Gets or sets the height of the view. + /// + /// The object representing the height of the view (the number of rows). + /// + /// + /// If is the value is indeterminate until the + /// view has been initialized ( is true) and has been + /// called. + /// + /// + /// Changing this property will eventually (when the view is next drawn) cause the + /// + /// and methods to be called. + /// + /// + /// If is changing this property will cause the + /// to be updated. If + /// the new value is not of type the will change to + /// . + /// + /// + public Dim Height { + get => VerifyIsInitialized (_height, nameof (Height)); + set { + // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? + _height = value; + + if (ValidatePosDim) { + var isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); + + if (IsAdded && AutoSize && !isValidNewAutSize) { + throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); + } + } + OnResizeNeeded (); + } + } + + /// + /// Gets or sets whether validation of and occurs. + /// + /// + /// Setting this to will enable validation of , , , + /// and + /// during set operations and in .If invalid settings are discovered exceptions will be thrown + /// indicating the error. + /// This will impose a performance penalty and thus should only be used for debugging. + /// + public bool ValidatePosDim { get; set; } + + internal bool LayoutNeeded { get; private set; } = true; + + /// + /// Gets or sets a flag that determines whether the View will be automatically resized to fit the + /// within + /// + /// The default is . Set to to turn on AutoSize. If + /// then + /// and will be used if can fit; + /// if won't fit the view will be resized as needed. + /// + /// + /// In addition, if is the new values of and + /// must be of the same types of the existing one to avoid breaking the settings. + /// + /// + public virtual bool AutoSize { + get => _autoSize; + set { + var v = ResizeView (value); + TextFormatter.AutoSize = v; + if (_autoSize != v) { + _autoSize = v; + TextFormatter.NeedsFormat = true; + UpdateTextFormatterText (); + OnResizeNeeded (); + } + } + } + + /// + /// Event called only once when the is being initialized for the first time. + /// Allows configurations and assignments to be performed before the being shown. + /// This derived from to allow notify all the views that are being + /// initialized. + /// + public event EventHandler Initialized; + + /// + /// Helper to get the total thickness of the , , and . /// /// A thickness that describes the sum of the Frames' thicknesses. public Thickness GetFramesThickness () { - int left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; - int top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; - int right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; - int bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; + var left = Margin.Thickness.Left + Border.Thickness.Left + Padding.Thickness.Left; + var top = Margin.Thickness.Top + Border.Thickness.Top + Padding.Thickness.Top; + var right = Margin.Thickness.Right + Border.Thickness.Right + Padding.Thickness.Right; + var bottom = Margin.Thickness.Bottom + Border.Thickness.Bottom + Padding.Thickness.Bottom; return new Thickness (left, top, right, bottom); } @@ -191,7 +514,7 @@ public partial class View { Margin.ThicknessChanged -= ThicknessChangedHandler; Margin.Dispose (); } - Margin = new Frame () { Id = "Margin", Thickness = new Thickness (0) }; + Margin = new Frame { Id = "Margin", Thickness = new Thickness (0) }; Margin.ThicknessChanged += ThicknessChangedHandler; Margin.Parent = this; @@ -199,7 +522,7 @@ public partial class View { Border.ThicknessChanged -= ThicknessChangedHandler; Border.Dispose (); } - Border = new Frame () { Id = "Border", Thickness = new Thickness (0) }; + Border = new Frame { Id = "Border", Thickness = new Thickness (0) }; Border.ThicknessChanged += ThicknessChangedHandler; Border.Parent = this; @@ -209,275 +532,21 @@ public partial class View { Padding.ThicknessChanged -= ThicknessChangedHandler; Padding.Dispose (); } - Padding = new Frame () { Id = "Padding", Thickness = new Thickness (0) }; + Padding = new Frame { Id = "Padding", Thickness = new Thickness (0) }; Padding.ThicknessChanged += ThicknessChangedHandler; Padding.Parent = this; } - LayoutStyle _layoutStyle; - - /// - /// Controls how the View's is computed during . If the style is set to - /// , LayoutSubviews does not change the . - /// If the style is the is updated using - /// the , , , and properties. - /// - /// - /// - /// Setting this property to will cause to determine the - /// size and position of the view. and will be set to using . - /// - /// - /// Setting this property to will cause the view to use the method to - /// size and position of the view. If either of the and properties are `null` they will be set to using - /// the current value of . - /// If either of the and properties are `null` they will be set to using . - /// - /// - /// The layout style. - public LayoutStyle LayoutStyle { - get { - return _layoutStyle; - //if ((X == null || X is Pos.PosAbsolute) && (Y == null || Y is Pos.PosAbsolute) && - //(Width == null || Width is Dim.DimAbsolute) && (Height == null || Height is Dim.DimAbsolute)) { - // return LayoutStyle.Absolute; - //} else { - // return LayoutStyle.Computed; - //} - } - set { - _layoutStyle = value; - //switch (_layoutStyle) { - //case LayoutStyle.Absolute: - // X = Frame.X; - // Y = Frame.Y; - // Width = Frame.Width; - // Height = Frame.Height; - // break; - - //case LayoutStyle.Computed: - // X ??= Frame.X; - // Y ??= Frame.Y; - // Width ??= Frame.Width; - // Height ??= Frame.Height; - // break; - //} - SetNeedsLayout (); - } - } - - /// - /// The bounds represent the View-relative rectangle used for this view; the area inside of the view where subviews and content are presented. - /// - /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. - /// - /// - /// If is the value of Bounds is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Updates to the Bounds updates , and has the same side effects as updating the . - /// - /// - /// Altering the Bounds will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// - /// - /// Because coordinates are relative to the upper-left corner of the , - /// the coordinates of the upper-left corner of the rectangle returned by this property are (0,0). - /// Use this property to obtain the size of the area of the view for tasks such as drawing the view's contents. - /// - /// - public virtual Rect Bounds { - get { -#if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) { - Debug.WriteLine ($"WARNING: Bounds is being accessed before the View has been initialized. This is likely a bug in {this}"); - } -#endif // DEBUG - //var frameRelativeBounds = Padding?.Thickness.GetInside (Padding.Frame) ?? new Rect (default, Frame.Size); - var frameRelativeBounds = FrameGetInsideBounds (); - return new Rect (default, frameRelativeBounds.Size); - } - set { - // BUGBUG: Margin etc.. can be null (if typeof(Frame)) - Frame = new Rect (Frame.Location, - new Size ( - value.Size.Width + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal, - value.Size.Height + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical - ) - ); - } - } - Rect FrameGetInsideBounds () { if (Margin == null || Border == null || Padding == null) { return new Rect (default, Frame.Size); } - int width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal); - int height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical); + var width = Math.Max (0, Frame.Size.Width - Margin.Thickness.Horizontal - Border.Thickness.Horizontal - Padding.Thickness.Horizontal); + var height = Math.Max (0, Frame.Size.Height - Margin.Thickness.Vertical - Border.Thickness.Vertical - Padding.Thickness.Vertical); return new Rect (Point.Empty, new Size (width, height)); } - Pos _x, _y; - - /// - /// Gets or sets the X position for the view (the column). - /// - /// The object representing the X position. - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the and - /// methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// - /// - /// is the same as Pos.Absolute(0). - /// - /// - public Pos X { - get => VerifyIsInitialized (_x, nameof(X)); - set { - // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? - - if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (X), _x, value); - } - - _x = value; - - OnResizeNeeded (); - } - } - - /// - /// Gets or sets the Y position for the view (the row). - /// - /// The object representing the Y position. - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the and - /// methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// - /// - /// is the same as Pos.Absolute(0). - /// - /// - public Pos Y { - get => VerifyIsInitialized (_y, nameof(Y)); - set { - // BUGBUG: null is the sames a Pos.Absolute(0). Should we be explicit and set it? - - if (ValidatePosDim && LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (Y), _y, value); - } - - _y = value; - - OnResizeNeeded (); - } - } - - Dim _width, _height; - - /// - /// Gets or sets the width of the view. - /// - /// The object representing the width of the view (the number of columns). - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// - /// - public Dim Width { - get => VerifyIsInitialized (_width, nameof (Width)); - set { - // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? - if (ValidatePosDim) { - if (LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (Width), _width, value); - } - } - - _width = value; - - if (ValidatePosDim) { - bool isValidNewAutSize = AutoSize && IsValidAutoSizeWidth (_width); - - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before set the Width."); - } - } - OnResizeNeeded (); - } - } - - /// - /// Gets or sets the height of the view. - /// - /// The object representing the height of the view (the number of rows). - /// - /// - /// If is the value is indeterminate until the - /// view has been initialized ( is true) and has been called. - /// - /// - /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. - /// - /// - /// If is changing this property will cause the to be updated. If - /// the new value is not of type the will change to . - /// - /// - public Dim Height { - get => VerifyIsInitialized (_height, nameof (Height)); - set { - // BUGBUG: null is the sames a Dim.Fill(0). Should we be explicit and set it? - if (ValidatePosDim) { - if (LayoutStyle == LayoutStyle.Computed) { - CheckAbsolute (nameof (Height), _height, value); - } - } - - _height = value; - - if (ValidatePosDim) { - bool isValidNewAutSize = AutoSize && IsValidAutoSizeHeight (_height); - - if (IsAdded && AutoSize && !isValidNewAutSize) { - throw new InvalidOperationException ("Must set AutoSize to false before setting the Height."); - } - } - OnResizeNeeded (); - } - } - // Diagnostics to highlight when X or Y is read before the view has been initialized Pos VerifyIsInitialized (Pos pos, string member) { @@ -501,17 +570,8 @@ public partial class View { } /// - /// Gets or sets whether validation of and occurs. - /// - /// - /// Setting this to will enable validation of , , , and - /// during set operations and in .If invalid settings are discovered exceptions will be thrown indicating the error. - /// This will impose a performance penalty and thus should only be used for debugging. - /// - public bool ValidatePosDim { get; set; } - - /// - /// Throws an if is or . + /// Throws an if is or + /// . /// Used when is turned on to verify correct behavior. /// /// @@ -540,20 +600,20 @@ public partial class View { /// protected virtual void OnResizeNeeded () { - int actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X; - int actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y; + var actX = _x is Pos.PosAbsolute ? _x.Anchor (0) : _frame.X; + var actY = _y is Pos.PosAbsolute ? _y.Anchor (0) : _frame.Y; if (AutoSize) { //if (TextAlignment == TextAlignment.Justified) { // throw new InvalidOperationException ("TextAlignment.Justified cannot be used with AutoSize"); //} var s = GetAutoSize (); - int w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width; - int h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height; + var w = _width is Dim.DimAbsolute && _width.Anchor (0) > s.Width ? _width.Anchor (0) : s.Width; + var h = _height is Dim.DimAbsolute && _height.Anchor (0) > s.Height ? _height.Anchor (0) : s.Height; _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! } else { - int w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width; - int h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height; + var w = _width is Dim.DimAbsolute ? _width.Anchor (0) : _frame.Width; + var h = _height is Dim.DimAbsolute ? _height.Anchor (0) : _frame.Height; // BUGBUG: v2 - ? - If layoutstyle is absolute, this overwrites the current frame h/w with 0. Hmmm... // This is needed for DimAbsolute values by setting the frame before LayoutSubViews. _frame = new Rect (new Point (actX, actY), new Size (w, h)); // Set frame, not Frame! @@ -568,8 +628,6 @@ public partial class View { } } - internal bool LayoutNeeded { get; private set; } = true; - internal void SetNeedsLayout () { if (LayoutNeeded) { @@ -607,7 +665,7 @@ public partial class View { } /// - /// Converts a screen-relative coordinate to a bounds-relative coordinate. + /// Converts a screen-relative coordinate to a bounds-relative coordinate. /// /// The coordinate relative to this view's . /// Screen-relative column. @@ -620,15 +678,18 @@ public partial class View { } /// - /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped to the screen dimensions. + /// Converts a -relative coordinate to a screen-relative coordinate. The output is optionally clamped + /// to the screen dimensions. /// /// -relative column. /// -relative row. /// Absolute column; screen-relative. /// Absolute row; screen-relative. - /// If , and will be clamped to the + /// + /// If , and will be clamped to the /// screen dimensions (will never be negative and will always be less than and - /// , respectively. + /// , respectively. + /// public virtual void BoundsToScreen (int x, int y, out int rx, out int ry, bool clamped = true) { var boundsOffset = GetBoundsOffset (); @@ -651,16 +712,16 @@ public partial class View { } /// - /// Converts a -relative region to a screen-relative region. + /// Converts a -relative region to a screen-relative region. /// public Rect BoundsToScreen (Rect region) { - BoundsToScreen (region.X, region.Y, out int x, out int y, false); + BoundsToScreen (region.X, region.Y, out var x, out var y, false); return new Rect (x, y, region.Width, region.Height); } /// - /// Gets the with a screen-relative location. + /// Gets the with a screen-relative location. /// /// The location and size of the view in screen-relative coordinates. public virtual Rect FrameToScreen () @@ -678,10 +739,15 @@ public partial class View { // TODO: Come up with a better name for this method. "SetRelativeLayout" lacks clarity and confuses. AdjustSizeAndPosition? /// - /// Applies the view's position (, ) and dimension (, and ) to - /// , given a rectangle describing the SuperView's Bounds (nominally the same as this.SuperView.Bounds). + /// Applies the view's position (, ) and dimension (, and + /// ) to + /// , given a rectangle describing the SuperView's Bounds (nominally the same as + /// this.SuperView.Bounds). /// - /// The rectangle describing the SuperView's Bounds (nominally the same as this.SuperView.Bounds). + /// + /// The rectangle describing the SuperView's Bounds (nominally the same as + /// this.SuperView.Bounds). + /// internal void SetRelativeLayout (Rect superviewBounds) { int newX, newW, newY, newH; @@ -715,8 +781,8 @@ public partial class View { break; case Dim.DimCombine combine: - int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); - int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize); + var leftNewDim = GetNewDimension (combine._left, location, dimension, autosize); + var rightNewDim = GetNewDimension (combine._right, location, dimension, autosize); if (combine._add) { newDimension = leftNewDim + rightNewDim; } else { @@ -741,7 +807,7 @@ public partial class View { } int newDimension, newLocation; - int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; + var superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; // Determine new location switch (pos) { @@ -755,7 +821,7 @@ public partial class View { case Pos.PosCombine combine: int left, right; - (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension); + (left, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._left, dim, autosizeDimension); (right, newDimension) = GetNewLocationAndDimension (width, superviewBounds, combine._right, dim, autosizeDimension); if (combine._add) { newLocation = left + right; @@ -799,35 +865,32 @@ public partial class View { } /// - /// Fired after the View's method has completed. + /// Fired after the View's method has completed. /// /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise + /// changed. /// public event EventHandler LayoutStarted; /// - /// Raises the event. Called from before any subviews have been laid out. + /// Raises the event. Called from before any subviews have been + /// laid out. /// internal virtual void OnLayoutStarted (LayoutEventArgs args) => LayoutStarted?.Invoke (this, args); /// - /// Fired after the View's method has completed. + /// Fired after the View's method has completed. /// /// - /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise changed. + /// Subscribe to this event to perform tasks when the has been resized or the layout has otherwise + /// changed. /// public event EventHandler LayoutComplete; /// - /// Event called only once when the is being initialized for the first time. - /// Allows configurations and assignments to be performed before the being shown. - /// This derived from to allow notify all the views that are being initialized. - /// - public event EventHandler Initialized; - - /// - /// Raises the event. Called from before all sub-views have been laid out. + /// Raises the event. Called from before all sub-views have been + /// laid out. /// internal virtual void OnLayoutComplete (LayoutEventArgs args) => LayoutComplete?.Invoke (this, args); @@ -844,7 +907,7 @@ public partial class View { } return; case Pos.PosCombine pc: - CollectPos (pc._left, from, ref nNodes, ref nEdges); + CollectPos (pc._left, from, ref nNodes, ref nEdges); CollectPos (pc._right, from, ref nNodes, ref nEdges); break; } @@ -863,7 +926,7 @@ public partial class View { } return; case Dim.DimCombine dc: - CollectDim (dc._left, from, ref nNodes, ref nEdges); + CollectDim (dc._left, from, ref nNodes, ref nEdges); CollectDim (dc._right, from, ref nNodes, ref nEdges); break; } @@ -879,7 +942,7 @@ public partial class View { } CollectPos (v.X, v, ref nNodes, ref nEdges); CollectPos (v.Y, v, ref nNodes, ref nEdges); - CollectDim (v.Width, v, ref nNodes, ref nEdges); + CollectDim (v.Width, v, ref nNodes, ref nEdges); CollectDim (v.Height, v, ref nNodes, ref nEdges); } } @@ -942,9 +1005,8 @@ public partial class View { } else if (from != superView?.GetTopSuperView (to, from) && !ReferenceEquals (from, to)) { if (ReferenceEquals (from.SuperView, to)) { throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{to}\" references a SubView (\"{from}\")."); - } else { - throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); } + throw new InvalidOperationException ($"ComputedLayout for \"{superView}\": \"{from}\" linked with \"{to}\" was not found. Did you forget to add it to {superView}?"); } } // return L (a topologically sorted order) @@ -998,13 +1060,13 @@ public partial class View { /// response to the container view or terminal resizing. /// /// - /// - /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, - /// the behavior of this method is indeterminate if is . - /// - /// - /// Raises the event) before it returns. - /// + /// + /// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, + /// the behavior of this method is indeterminate if is . + /// + /// + /// Raises the event) before it returns. + /// /// public virtual void LayoutSubviews () { @@ -1019,7 +1081,7 @@ public partial class View { LayoutFrames (); var oldBounds = Bounds; - OnLayoutStarted (new LayoutEventArgs () { OldBounds = oldBounds }); + OnLayoutStarted (new LayoutEventArgs { OldBounds = oldBounds }); TextFormatter.Size = GetTextFormatterSizeNeededForTextAndHotKey (); @@ -1042,7 +1104,7 @@ public partial class View { LayoutNeeded = false; - OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds }); + OnLayoutComplete (new LayoutEventArgs { OldBounds = oldBounds }); } void LayoutSubview (View v, Rect contentArea) @@ -1055,42 +1117,13 @@ public partial class View { v.LayoutNeeded = false; } - bool _autoSize; - - /// - /// Gets or sets a flag that determines whether the View will be automatically resized to fit the - /// within - /// - /// The default is . Set to to turn on AutoSize. If then - /// and will be used if can fit; - /// if won't fit the view will be resized as needed. - /// - /// - /// In addition, if is the new values of and - /// must be of the same types of the existing one to avoid breaking the settings. - /// - /// - public virtual bool AutoSize { - get => _autoSize; - set { - bool v = ResizeView (value); - TextFormatter.AutoSize = v; - if (_autoSize != v) { - _autoSize = v; - TextFormatter.NeedsFormat = true; - UpdateTextFormatterText (); - OnResizeNeeded (); - } - } - } - bool ResizeView (bool autoSize) { if (!autoSize) { return false; } - bool boundsChanged = true; + var boundsChanged = true; var newFrameSize = GetAutoSize (); if (IsInitialized && newFrameSize != Frame.Size) { if (ValidatePosDim) { @@ -1111,9 +1144,9 @@ public partial class View { /// whether the Bounds was changed or not bool ResizeBoundsToFit (Size size) { - bool boundsChanged = false; - bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW); - bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH); + var boundsChanged = false; + var canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out var rW); + var canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out var rH); if (canSizeW) { boundsChanged = true; _width = rW; @@ -1130,21 +1163,22 @@ public partial class View { } /// - /// Gets the Frame dimensions required to fit within using the text specified by the + /// Gets the Frame dimensions required to fit within using the text + /// specified by the /// property and accounting for any characters. /// /// The of the view required to fit the text. public Size GetAutoSize () { - int x = 0; - int y = 0; + var x = 0; + var y = 0; if (IsInitialized) { x = Bounds.X; y = Bounds.Y; } var rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); - int newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal; - int newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical; + var newWidth = rect.Size.Width - GetHotKeySpecifierLength () + Margin.Thickness.Horizontal + Border.Thickness.Horizontal + Padding.Thickness.Horizontal; + var newHeight = rect.Size.Height - GetHotKeySpecifierLength (false) + Margin.Thickness.Vertical + Border.Thickness.Vertical + Padding.Thickness.Vertical; return new Size (newWidth, newHeight); } @@ -1153,36 +1187,40 @@ public partial class View { var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); autoSize = new Size (rect.Size.Width - GetHotKeySpecifierLength (), rect.Size.Height - GetHotKeySpecifierLength (false)); - return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) - || _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () - || _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); + return !(ValidatePosDim && (!(Width is Dim.DimAbsolute) || !(Height is Dim.DimAbsolute)) || + _frame.Size.Width != rect.Size.Width - GetHotKeySpecifierLength () || + _frame.Size.Height != rect.Size.Height - GetHotKeySpecifierLength (false)); } bool IsValidAutoSizeWidth (Dim width) { var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - int dimValue = width.Anchor (0); - return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - - GetHotKeySpecifierLength ()); + var dimValue = width.Anchor (0); + return !(ValidatePosDim && !(width is Dim.DimAbsolute) || dimValue != rect.Size.Width - GetHotKeySpecifierLength ()); } bool IsValidAutoSizeHeight (Dim height) { var rect = TextFormatter.CalcRect (_frame.X, _frame.Y, TextFormatter.Text, TextDirection); - int dimValue = height.Anchor (0); - return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - - GetHotKeySpecifierLength (false)); + var dimValue = height.Anchor (0); + return !(ValidatePosDim && !(height is Dim.DimAbsolute) || dimValue != rect.Size.Height - GetHotKeySpecifierLength (false)); } /// /// Determines if the View's can be set to a new value. /// /// - /// Contains the width that would result if were set to "/> - /// if the View's can be changed to the specified value. False otherwise. + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// internal bool TrySetWidth (int desiredWidth, out int resultWidth) { - int w = desiredWidth; + var w = desiredWidth; bool canSetWidth; switch (Width) { case Dim.DimCombine _: @@ -1194,7 +1232,7 @@ public partial class View { break; case Dim.DimFactor factor: // Tries to get the SuperView Width otherwise the view Width. - int sw = SuperView != null ? SuperView.Frame.Width : w; + var sw = SuperView != null ? SuperView.Frame.Width : w; if (factor.IsFromRemaining ()) { sw -= Frame.X; } @@ -1214,11 +1252,17 @@ public partial class View { /// Determines if the View's can be set to a new value. /// /// - /// Contains the width that would result if were set to "/> - /// if the View's can be changed to the specified value. False otherwise. + /// + /// Contains the width that would result if were set to + /// "/> + /// + /// + /// if the View's can be changed to the specified value. False + /// otherwise. + /// internal bool TrySetHeight (int desiredHeight, out int resultHeight) { - int h = desiredHeight; + var h = desiredHeight; bool canSetHeight; switch (Height) { case Dim.DimCombine _: @@ -1230,7 +1274,7 @@ public partial class View { break; case Dim.DimFactor factor: // Tries to get the SuperView height otherwise the view height. - int sh = SuperView != null ? SuperView.Frame.Height : h; + var sh = SuperView != null ? SuperView.Frame.Height : h; if (factor.IsFromRemaining ()) { sh -= Frame.Y; } @@ -1255,8 +1299,8 @@ public partial class View { /// The found view screen relative column location. /// The found view screen relative row location. /// - /// The view that was found at the and coordinates. - /// if no view was found. + /// The view that was found at the and coordinates. + /// if no view was found. /// public static View FindDeepestView (View start, int x, int y, out int resx, out int resy) { @@ -1267,12 +1311,12 @@ public partial class View { var startFrame = start.Frame; if (start.InternalSubviews != null) { - int count = start.InternalSubviews.Count; + var count = start.InternalSubviews.Count; if (count > 0) { var boundsOffset = start.GetBoundsOffset (); - int rx = x - (startFrame.X + boundsOffset.X); - int ry = y - (startFrame.Y + boundsOffset.Y); - for (int i = count - 1; i >= 0; i--) { + var rx = x - (startFrame.X + boundsOffset.X); + var ry = y - (startFrame.Y + boundsOffset.Y); + for (var i = count - 1; i >= 0; i--) { var v = start.InternalSubviews [i]; if (v.Visible && v.Frame.Contains (rx, ry)) { var deep = FindDeepestView (v, rx, ry, out resx, out resy); diff --git a/UnitTests/View/Layout/DimTests.cs b/UnitTests/View/Layout/DimTests.cs index f12452e11..e017a2170 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,680 @@ 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] [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 +1013,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