From 88bb3984c06ea1cad42d40e9d2d101a2c865ca28 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 6 Jul 2024 16:19:35 -0600 Subject: [PATCH 01/18] Fixed bug. Added unit tests --- Terminal.Gui/View/Adornment/Margin.cs | 4 +- UnitTests/View/Adornment/MarginTests.cs | 1 + UnitTests/View/Adornment/ShadowStyletests.cs | 126 +++++++++++++++++++ UnitTests/View/MouseTests.cs | 1 - 4 files changed, 129 insertions(+), 3 deletions(-) diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index 11ae2dd2b..a7273a74c 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -43,7 +43,7 @@ public class Margin : Adornment { if (ShadowStyle != Gui.ShadowStyle.None) { - if (_pressed && e.CurrentValue == HighlightStyle.None) + if (_pressed && e.NewValue == HighlightStyle.None) { Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom); @@ -61,7 +61,7 @@ public class Margin : Adornment return; } - if (!_pressed && (e.CurrentValue.HasFlag (HighlightStyle.Pressed) /*|| e.HighlightStyle.HasFlag (HighlightStyle.PressedOutside)*/)) + if (!_pressed && (e.NewValue.HasFlag (HighlightStyle.Pressed) /*|| e.HighlightStyle.HasFlag (HighlightStyle.PressedOutside)*/)) { Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom); _pressed = true; diff --git a/UnitTests/View/Adornment/MarginTests.cs b/UnitTests/View/Adornment/MarginTests.cs index 87f76f45f..58b6d1dce 100644 --- a/UnitTests/View/Adornment/MarginTests.cs +++ b/UnitTests/View/Adornment/MarginTests.cs @@ -40,4 +40,5 @@ MMM", ); TestHelpers.AssertDriverAttributesAre ("0", null, superView.GetNormalColor ()); } + } diff --git a/UnitTests/View/Adornment/ShadowStyletests.cs b/UnitTests/View/Adornment/ShadowStyletests.cs index c8f3f8063..967720e36 100644 --- a/UnitTests/View/Adornment/ShadowStyletests.cs +++ b/UnitTests/View/Adornment/ShadowStyletests.cs @@ -27,6 +27,132 @@ public class ShadowStyleTests (ITestOutputHelper _output) view.Dispose (); } + [Theory] + [InlineData (ShadowStyle.None, 0, 0, 0, 0)] + [InlineData (ShadowStyle.Opaque, 1, 0, 0, 1)] + [InlineData (ShadowStyle.Transparent, 1, 0, 0, 1)] + public void ShadowStyle_Button1Pressed_Causes_Movement (ShadowStyle style, int expectedLeft, int expectedTop, int expectedRight, int expectedBottom) + { + var superView = new View + { + Height = 10, Width = 10 + }; + + View view = new () + { + Width = Dim.Auto (), + Height = Dim.Auto (), + Text = "0123", + HighlightStyle = HighlightStyle.Pressed, + ShadowStyle = style, + CanFocus = true + }; + + superView.Add (view); + superView.BeginInit (); + superView.EndInit (); + + Thickness origThickness = view.Margin.Thickness; + view.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (0, 0) }); + Assert.Equal (new (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin.Thickness); + + view.NewMouseEvent (new () { Flags = MouseFlags.Button1Released, Position = new (0, 0) }); + Assert.Equal (origThickness, view.Margin.Thickness); + } + + [Theory] + [InlineData (ShadowStyle.None, 0, 0, 0, 0)] + [InlineData (ShadowStyle.Opaque, 0, 0, 1, 1)] + [InlineData (ShadowStyle.Transparent, 0, 0, 1, 1)] + public void ShadowStyle_Margin_Thickness (ShadowStyle style, int expectedLeft, int expectedTop, int expectedRight, int expectedBottom) + { + var superView = new View + { + Height = 10, Width = 10 + }; + + View view = new () + { + Width = Dim.Auto (), + Height = Dim.Auto (), + Text = "0123", + HighlightStyle = HighlightStyle.Pressed, + ShadowStyle = style, + CanFocus = true + }; + + superView.Add (view); + superView.BeginInit (); + superView.EndInit (); + + Assert.Equal (new (expectedLeft, expectedTop, expectedRight, expectedBottom), view.Margin.Thickness); + } + + [Theory] + [InlineData ( + ShadowStyle.None, + """ + 011 + 111 + 111 + """)] + [InlineData ( + ShadowStyle.Transparent, + """ + 011 + 131 + 111 + """)] + [InlineData ( + ShadowStyle.Opaque, + """ + 011 + 121 + 111 + """)] + [SetupFakeDriver] + public void ShadowView_Colors (ShadowStyle style, string expectedAttrs) + { + Color fg = Color.Red; + Color bg = Color.Green; + + // 0 - View + // 1 - SuperView + // 2 - Opaque - fg is Black, bg is SuperView.Bg + // 3 - Transparent - fg is darker fg, bg is darker bg + Attribute [] attributes = + { + Attribute.Default, + new (fg, bg), + new (Color.Black, bg), + new (fg.GetDarkerColor (), bg.GetDarkerColor ()) + }; + + var superView = new View + { + Height = 3, + Width = 3, + Text = "012ABC!@#", + ColorScheme = new (new Attribute (fg, bg)) + }; + superView.TextFormatter.WordWrap = true; + + View view = new () + { + Width = Dim.Auto (), + Height = Dim.Auto (), + Text = " ", + ShadowStyle = style, + ColorScheme = new (Attribute.Default) + }; + superView.Add (view); + superView.BeginInit (); + superView.EndInit (); + + superView.Draw (); + TestHelpers.AssertDriverAttributesAre (expectedAttrs, Application.Driver, attributes); + } + [Theory] [InlineData (ShadowStyle.None, 3)] [InlineData (ShadowStyle.Opaque, 4)] diff --git a/UnitTests/View/MouseTests.cs b/UnitTests/View/MouseTests.cs index 956f4b202..5c5453f3a 100644 --- a/UnitTests/View/MouseTests.cs +++ b/UnitTests/View/MouseTests.cs @@ -647,5 +647,4 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews } } } - } From 2497419a2876f8451591248c86f926dc0b2b74ce Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 6 Jul 2024 16:27:37 -0600 Subject: [PATCH 02/18] Added comments --- Terminal.Gui/View/Adornment/Margin.cs | 253 +++++++++++++------------- 1 file changed, 131 insertions(+), 122 deletions(-) diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index a7273a74c..f76f0e8b8 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -1,7 +1,5 @@ #nullable enable -using System.Drawing; - namespace Terminal.Gui; /// The Margin for a . @@ -28,93 +26,43 @@ public class Margin : Adornment CanFocus = false; } - private void Margin_LayoutStarted (object? sender, LayoutEventArgs e) - { - // Adjust the shadow such that it is drawn aligned with the Border - if (ShadowStyle != Gui.ShadowStyle.None && _rightShadow is { } && _bottomShadow is { }) - { - _rightShadow.Y = Parent.Border.Thickness.Top > 0 ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.ShowTitle ? 1 : 0) : 1; - _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1; - } - } - private bool _pressed; - private void Margin_Highlight (object? sender, CancelEventArgs e) + + private ShadowView? _bottomShadow; + private ShadowView? _rightShadow; + + /// + public override void BeginInit () { - if (ShadowStyle != Gui.ShadowStyle.None) + base.BeginInit (); + + if (Parent is null) { - if (_pressed && e.NewValue == HighlightStyle.None) - { - Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom); - - if (_rightShadow is { }) - { - _rightShadow.Visible = true; - } - - if (_bottomShadow is { }) - { - _bottomShadow.Visible = true; - } - - _pressed = false; - return; - } - - if (!_pressed && (e.NewValue.HasFlag (HighlightStyle.Pressed) /*|| e.HighlightStyle.HasFlag (HighlightStyle.PressedOutside)*/)) - { - Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom); - _pressed = true; - if (_rightShadow is { }) - { - _rightShadow.Visible = false; - } - - if (_bottomShadow is { }) - { - _bottomShadow.Visible = false; - } - } + return; } - } + ShadowStyle = base.ShadowStyle; - /// - public override void OnDrawContent (Rectangle viewport) - { - Rectangle screen = ViewportToScreen (viewport); - Attribute normalAttr = GetNormalColor (); - - Driver?.SetAttribute (normalAttr); - - - // This just draws/clears the thickness, not the insides. - if (ShadowStyle != ShadowStyle.None) - { - screen = Rectangle.Inflate (screen, -1, -1); - } - Thickness.Draw (screen, ToString ()); - - if (Subviews.Count > 0) - { - // Draw subviews - // TODO: Implement OnDrawSubviews (cancelable); - if (Subviews is { } && SubViewNeedsDisplay) - { - IEnumerable subviewsNeedingDraw = Subviews.Where ( - view => view.Visible - && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded) - ); - foreach (View view in subviewsNeedingDraw) - { - if (view.LayoutNeeded) - { - view.LayoutSubviews (); - } - view.Draw (); - } - } - } + Add ( + _rightShadow = new() + { + X = Pos.AnchorEnd (1), + Y = 0, + Width = 1, + Height = Dim.Fill (), + ShadowStyle = ShadowStyle, + Orientation = Orientation.Vertical + }, + _bottomShadow = new() + { + X = 0, + Y = Pos.AnchorEnd (1), + Width = Dim.Fill (), + Height = 1, + ShadowStyle = ShadowStyle, + Orientation = Orientation.Horizontal + } + ); } /// @@ -139,18 +87,48 @@ public class Margin : Adornment } } - /// - public override ShadowStyle ShadowStyle + /// + public override void OnDrawContent (Rectangle viewport) { - get => base.ShadowStyle; - set + Rectangle screen = ViewportToScreen (viewport); + Attribute normalAttr = GetNormalColor (); + + Driver?.SetAttribute (normalAttr); + + // This just draws/clears the thickness, not the insides. + if (ShadowStyle != ShadowStyle.None) { - base.ShadowStyle = SetShadow (value); + screen = Rectangle.Inflate (screen, -1, -1); + } + + Thickness.Draw (screen, ToString ()); + + if (Subviews.Count > 0) + { + // Draw subviews + // TODO: Implement OnDrawSubviews (cancelable); + if (Subviews is { } && SubViewNeedsDisplay) + { + IEnumerable subviewsNeedingDraw = Subviews.Where ( + view => view.Visible + && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded) + ); + + foreach (View view in subviewsNeedingDraw) + { + if (view.LayoutNeeded) + { + view.LayoutSubviews (); + } + + view.Draw (); + } + } } } /// - /// Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the + /// Sets whether the Margin includes a shadow effect. The shadow is drawn on the right and bottom sides of the /// Margin. /// public ShadowStyle SetShadow (ShadowStyle style) @@ -181,42 +159,73 @@ public class Margin : Adornment { _bottomShadow.ShadowStyle = style; } + return style; } - private ShadowView? _bottomShadow; - private ShadowView? _rightShadow; - /// - public override void BeginInit () + public override ShadowStyle ShadowStyle { - base.BeginInit (); - - if (Parent is null) - { - return; - } - - ShadowStyle = base.ShadowStyle; - Add ( - _rightShadow = new ShadowView - { - X = Pos.AnchorEnd (1), - Y = 0, - Width = 1, - Height = Dim.Fill (), - ShadowStyle = ShadowStyle, - Orientation = Orientation.Vertical - }, - _bottomShadow = new ShadowView - { - X = 0, - Y = Pos.AnchorEnd (1), - Width = Dim.Fill (), - Height = 1, - ShadowStyle = ShadowStyle, - Orientation = Orientation.Horizontal - } - ); + get => base.ShadowStyle; + set => base.ShadowStyle = SetShadow (value); } -} \ No newline at end of file + + private void Margin_Highlight (object? sender, CancelEventArgs e) + { + if (ShadowStyle != ShadowStyle.None) + { + if (_pressed && e.NewValue == HighlightStyle.None) + { + // If the view is pressed and the highlight is being removed, move the shadow back. + // Note, for visual effects reasons, we only move horizontally. + // TODO: Add a setting or flag that lets the shadow move vertically as well. + Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom); + + if (_rightShadow is { }) + { + _rightShadow.Visible = true; + } + + if (_bottomShadow is { }) + { + _bottomShadow.Visible = true; + } + + _pressed = false; + + return; + } + + if (!_pressed && e.NewValue.HasFlag (HighlightStyle.Pressed)) + { + // If the view is not pressed and we want highlight move the shadow + // Note, for visual effects reasons, we only move horizontally. + // TODO: Add a setting or flag that lets the shadow move vertically as well. + Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom); + _pressed = true; + + if (_rightShadow is { }) + { + _rightShadow.Visible = false; + } + + if (_bottomShadow is { }) + { + _bottomShadow.Visible = false; + } + } + } + } + + private void Margin_LayoutStarted (object? sender, LayoutEventArgs e) + { + // Adjust the shadow such that it is drawn aligned with the Border + if (ShadowStyle != ShadowStyle.None && _rightShadow is { } && _bottomShadow is { }) + { + _rightShadow.Y = Parent.Border.Thickness.Top > 0 + ? Parent.Border.Thickness.Top - (Parent.Border.Thickness.Top > 2 && Parent.Border.ShowTitle ? 1 : 0) + : 1; + _bottomShadow.X = Parent.Border.Thickness.Left > 0 ? Parent.Border.Thickness.Left : 1; + } + } +} From 6f92d6db40c4196a7f7cc978abce1924ddc676fb Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 6 Jul 2024 16:29:19 -0600 Subject: [PATCH 03/18] Added comments --- Terminal.Gui/View/Adornment/Margin.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs index f76f0e8b8..2e1ea5760 100644 --- a/Terminal.Gui/View/Adornment/Margin.cs +++ b/Terminal.Gui/View/Adornment/Margin.cs @@ -178,7 +178,7 @@ public class Margin : Adornment { // If the view is pressed and the highlight is being removed, move the shadow back. // Note, for visual effects reasons, we only move horizontally. - // TODO: Add a setting or flag that lets the shadow move vertically as well. + // TODO: Add a setting or flag that lets the view move vertically as well. Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom); if (_rightShadow is { }) @@ -200,7 +200,7 @@ public class Margin : Adornment { // If the view is not pressed and we want highlight move the shadow // Note, for visual effects reasons, we only move horizontally. - // TODO: Add a setting or flag that lets the shadow move vertically as well. + // TODO: Add a setting or flag that lets the view move vertically as well. Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom); _pressed = true; From 8125357ce4950d1fa94ffa48f9287bbf90af70ae Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 6 Jul 2024 16:31:16 -0600 Subject: [PATCH 04/18] code cleanup --- UnitTests/View/Adornment/MarginTests.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/UnitTests/View/Adornment/MarginTests.cs b/UnitTests/View/Adornment/MarginTests.cs index 58b6d1dce..1cfe6f0d1 100644 --- a/UnitTests/View/Adornment/MarginTests.cs +++ b/UnitTests/View/Adornment/MarginTests.cs @@ -10,13 +10,13 @@ public class MarginTests (ITestOutputHelper output) { ((FakeDriver)Application.Driver).SetBufferSize (5, 5); var view = new View { Height = 3, Width = 3 }; - view.Margin.Thickness = new Thickness (1); + view.Margin.Thickness = new (1); var superView = new View (); - superView.ColorScheme = new ColorScheme + superView.ColorScheme = new() { - Normal = new Attribute (Color.Red, Color.Green), Focus = new Attribute (Color.Green, Color.Red) + Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) }; superView.Add (view); @@ -40,5 +40,4 @@ MMM", ); TestHelpers.AssertDriverAttributesAre ("0", null, superView.GetNormalColor ()); } - } From fb29b5c139dfda3fd03743c143ed88e27d5b9c54 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 6 Jul 2024 20:11:55 -0600 Subject: [PATCH 05/18] Made Thickness a record struct --- Terminal.Gui/Drawing/Thickness.cs | 44 +------------------------ UnitTests/View/Adornment/BorderTests.cs | 10 +++--- 2 files changed, 6 insertions(+), 48 deletions(-) diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index ad684470b..54178a846 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -15,7 +15,7 @@ namespace Terminal.Gui; /// /// Use the helper API ( to draw the frame with the specified thickness. /// -public class Thickness : IEquatable +public record struct Thickness { /// Gets or sets the width of the lower side of the rectangle. [JsonInclude] @@ -240,32 +240,6 @@ public class Thickness : IEquatable return GetInside (rect); } - /// Determines whether the specified object is equal to the current object. - /// The object to compare with the current object. - /// true if the specified object is equal to the current object; otherwise, false. - public override bool Equals (object obj) - { - //Check for null and compare run-time types. - if (obj is null || !GetType ().Equals (obj.GetType ())) - { - return false; - } - - return Equals ((Thickness)obj); - } - - /// - public override int GetHashCode () - { - var hashCode = 1380952125; - hashCode = hashCode * -1521134295 + Left.GetHashCode (); - hashCode = hashCode * -1521134295 + Right.GetHashCode (); - hashCode = hashCode * -1521134295 + Top.GetHashCode (); - hashCode = hashCode * -1521134295 + Bottom.GetHashCode (); - - return hashCode; - } - /// /// Returns a rectangle describing the location and size of the inside area of with the /// thickness widths subtracted. The height and width of the returned rectangle will never be less than 0. @@ -289,23 +263,7 @@ public class Thickness : IEquatable return new (x, y, width, height); } - /// - public static bool operator == (Thickness left, Thickness right) { return EqualityComparer.Default.Equals (left, right); } - - /// - public static bool operator != (Thickness left, Thickness right) { return !(left == right); } - /// Returns the thickness widths of the Thickness formatted as a string. /// The thickness widths as a string. public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } - - private int validate (int width) - { - if (width < 0) - { - throw new ArgumentException ("Thickness widths cannot be negative."); - } - - return width; - } } diff --git a/UnitTests/View/Adornment/BorderTests.cs b/UnitTests/View/Adornment/BorderTests.cs index d2f88916b..387844dbe 100644 --- a/UnitTests/View/Adornment/BorderTests.cs +++ b/UnitTests/View/Adornment/BorderTests.cs @@ -18,7 +18,7 @@ public class BorderTests (ITestOutputHelper output) view.Border.Thickness = new (0, 1, 0, 0); view.Border.LineStyle = LineStyle.Single; - view.ColorScheme = new() + view.ColorScheme = new () { Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) @@ -53,7 +53,7 @@ public class BorderTests (ITestOutputHelper output) view.Border.Thickness = new (0, 1, 0, 0); view.Border.LineStyle = LineStyle.Single; - view.ColorScheme = new() + view.ColorScheme = new () { Normal = new (Color.Red, Color.Green), Focus = new (Color.Green, Color.Red) }; @@ -90,7 +90,7 @@ public class BorderTests (ITestOutputHelper output) { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness.Top = 4; + win.Border.Thickness = win.Border.Thickness with { Top = 4 }; RunState rs = Application.Begin (win); var firstIteration = false; @@ -224,7 +224,7 @@ public class BorderTests (ITestOutputHelper output) { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness.Top = 3; + win.Border.Thickness = win.Border.Thickness with { Top = 3 }; RunState rs = Application.Begin (win); var firstIteration = false; @@ -358,7 +358,7 @@ public class BorderTests (ITestOutputHelper output) { Title = "1234", Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Double }; - win.Border.Thickness.Top = 2; + win.Border.Thickness = win.Border.Thickness with { Top = 2 }; RunState rs = Application.Begin (win); var firstIteration = false; From 4f1688f6db9f5df78fb5cc9d2513131677d61ef6 Mon Sep 17 00:00:00 2001 From: Tig Date: Sat, 6 Jul 2024 20:25:13 -0600 Subject: [PATCH 06/18] Code cleanup --- Terminal.Gui/Drawing/Thickness.cs | 111 ++++++++++++++---------------- 1 file changed, 52 insertions(+), 59 deletions(-) diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 54178a846..653b210ac 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -15,24 +15,8 @@ namespace Terminal.Gui; /// /// Use the helper API ( to draw the frame with the specified thickness. /// -public record struct Thickness +public record struct Thickness { - /// Gets or sets the width of the lower side of the rectangle. - [JsonInclude] - public int Bottom; - - /// Gets or sets the width of the left side of the rectangle. - [JsonInclude] - public int Left; - - /// Gets or sets the width of the right side of the rectangle. - [JsonInclude] - public int Right; - - /// Gets or sets the width of the upper side of the rectangle. - [JsonInclude] - public int Top; - /// Initializes a new instance of the class with all widths set to 0. public Thickness () { } @@ -56,35 +40,29 @@ public record struct Thickness Bottom = bottom; } - // TODO: add operator overloads - /// Gets an empty thickness. - public static Thickness Empty => new (0); + /// Gets or sets the width of the lower side of the rectangle. + [JsonInclude] + public int Bottom { get; set; } + + /// Gets or sets the width of the left side of the rectangle. + [JsonInclude] + public int Left { get; set; } + + /// Gets or sets the width of the right side of the rectangle. + [JsonInclude] + public int Right { get; set; } + + /// Gets or sets the width of the upper side of the rectangle. + [JsonInclude] + public int Top { get; set; } /// - /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides - /// of the rectangle to half the specified value. + /// Adds the thickness widths of another to the current , returning a + /// new . /// - public int Horizontal - { - get => Left + Right; - set => Left = Right = value / 2; - } - - /// - /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom - /// sides of the rectangle to half the specified value. - /// - public int Vertical - { - get => Top + Bottom; - set => Top = Bottom = value / 2; - } - - // IEquitable - /// Indicates whether the current object is equal to another object of the same type. /// - /// true if the current object is equal to the other parameter; otherwise, false. - public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; } + /// + public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); } /// /// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside @@ -100,22 +78,6 @@ public record struct Thickness return outside.Contains (location) && !inside.Contains (location); } - /// - /// Adds the thickness widths of another to the current , returning a - /// new . - /// - /// - /// - public Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); } - - /// - /// Adds the thickness widths of another to another . - /// - /// - /// - /// - public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); } - /// Draws the rectangle with an optional diagnostics label. /// /// If is set to @@ -240,6 +202,9 @@ public record struct Thickness return GetInside (rect); } + /// Gets an empty thickness. + public static Thickness Empty => new (0); + /// /// Returns a rectangle describing the location and size of the inside area of with the /// thickness widths subtracted. The height and width of the returned rectangle will never be less than 0. @@ -263,7 +228,35 @@ public record struct Thickness return new (x, y, width, height); } + /// + /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides + /// of the rectangle to half the specified value. + /// + public int Horizontal + { + get => Left + Right; + set => Left = Right = value / 2; + } + + /// + /// Adds the thickness widths of another to another . + /// + /// + /// + /// + public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); } + /// Returns the thickness widths of the Thickness formatted as a string. /// The thickness widths as a string. - public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } + public readonly override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } + + /// + /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom + /// sides of the rectangle to half the specified value. + /// + public int Vertical + { + get => Top + Bottom; + set => Top = Bottom = value / 2; + } } From efcb635477663b9759cada9df3b4ed84bd6a879a Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 08:41:32 -0600 Subject: [PATCH 07/18] Use Vector4 --- Terminal.Gui/Drawing/Thickness.cs | 48 ++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 653b210ac..733275c88 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using System.Numerics; +using System.Text.Json.Serialization; namespace Terminal.Gui; @@ -17,8 +18,19 @@ namespace Terminal.Gui; /// public record struct Thickness { + private Vector4 _sides; + + /// Initializes a new instance of the class. + public Thickness (float left, float top, float right, float bottom) + { + _sides = new (left, top, right, bottom); + } + /// Initializes a new instance of the class with all widths set to 0. - public Thickness () { } + public Thickness () + { + _sides = new (0, 0, 0, 0); + } /// Initializes a new instance of the class with a uniform width to each side. /// @@ -40,21 +52,37 @@ public record struct Thickness Bottom = bottom; } - /// Gets or sets the width of the lower side of the rectangle. - [JsonInclude] - public int Bottom { get; set; } /// Gets or sets the width of the left side of the rectangle. [JsonInclude] - public int Left { get; set; } + public int Left + { + get => (int)_sides.X; + set => _sides.X = value; + } + /// Gets or sets the width of the upper side of the rectangle. + [JsonInclude] + public int Top + { + get => (int)_sides.Y; + set => _sides.Y = value; + } /// Gets or sets the width of the right side of the rectangle. [JsonInclude] - public int Right { get; set; } + public int Right + { + get => (int)_sides.Z; + set => _sides.Z = value; + } - /// Gets or sets the width of the upper side of the rectangle. + /// Gets or sets the width of the lower side of the rectangle. [JsonInclude] - public int Top { get; set; } + public int Bottom + { + get => (int)_sides.W; + set => _sides.W = value; + } /// /// Adds the thickness widths of another to the current , returning a @@ -248,7 +276,7 @@ public record struct Thickness /// Returns the thickness widths of the Thickness formatted as a string. /// The thickness widths as a string. - public readonly override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } + public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } /// /// Gets the total height of the top and bottom sides of the rectangle. Sets the height of the top and bottom From 8d527709e2141c3c35d36990cfa2a0d3781d9fb2 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 08:45:10 -0600 Subject: [PATCH 08/18] Code cleanup --- Terminal.Gui/Drawing/Thickness.cs | 85 +++++++++++++++---------------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 733275c88..532c0af8a 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -14,23 +14,18 @@ namespace Terminal.Gui; /// frame, /// with the thickness widths subtracted. /// -/// Use the helper API ( to draw the frame with the specified thickness. +/// +/// Use the helper API ( to draw the frame with the specified thickness. +/// +/// +/// Thickness uses intenrally. As a result, there is a potential precision loss for very +/// large numbers. This is typically not an issue for UI dimensions but could be relevant in other contexts. +/// /// public record struct Thickness { - private Vector4 _sides; - - /// Initializes a new instance of the class. - public Thickness (float left, float top, float right, float bottom) - { - _sides = new (left, top, right, bottom); - } - /// Initializes a new instance of the class with all widths set to 0. - public Thickness () - { - _sides = new (0, 0, 0, 0); - } + public Thickness () { _sides = Vector4.Zero; } /// Initializes a new instance of the class with a uniform width to each side. /// @@ -52,37 +47,7 @@ public record struct Thickness Bottom = bottom; } - - /// Gets or sets the width of the left side of the rectangle. - [JsonInclude] - public int Left - { - get => (int)_sides.X; - set => _sides.X = value; - } - /// Gets or sets the width of the upper side of the rectangle. - [JsonInclude] - public int Top - { - get => (int)_sides.Y; - set => _sides.Y = value; - } - - /// Gets or sets the width of the right side of the rectangle. - [JsonInclude] - public int Right - { - get => (int)_sides.Z; - set => _sides.Z = value; - } - - /// Gets or sets the width of the lower side of the rectangle. - [JsonInclude] - public int Bottom - { - get => (int)_sides.W; - set => _sides.W = value; - } + private Vector4 _sides; /// /// Adds the thickness widths of another to the current , returning a @@ -92,6 +57,14 @@ public record struct Thickness /// public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); } + /// Gets or sets the width of the lower side of the rectangle. + [JsonInclude] + public int Bottom + { + get => (int)_sides.W; + set => _sides.W = value; + } + /// /// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside /// the rectangle described by . @@ -266,6 +239,14 @@ public record struct Thickness set => Left = Right = value / 2; } + /// Gets or sets the width of the left side of the rectangle. + [JsonInclude] + public int Left + { + get => (int)_sides.X; + set => _sides.X = value; + } + /// /// Adds the thickness widths of another to another . /// @@ -274,6 +255,22 @@ public record struct Thickness /// public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); } + /// Gets or sets the width of the right side of the rectangle. + [JsonInclude] + public int Right + { + get => (int)_sides.Z; + set => _sides.Z = value; + } + + /// Gets or sets the width of the upper side of the rectangle. + [JsonInclude] + public int Top + { + get => (int)_sides.Y; + set => _sides.Y = value; + } + /// Returns the thickness widths of the Thickness formatted as a string. /// The thickness widths as a string. public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; } From 12a396846d2318459ad93b92c2716c066100dfe7 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 09:25:54 -0600 Subject: [PATCH 09/18] Moved code out of testhelpers to Application.ToString() --- Terminal.Gui/Application/Application.cs | 60 +++- UnitTests/TestHelpers.cs | 444 ++++++++---------------- 2 files changed, 199 insertions(+), 305 deletions(-) diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs index 9e501f22c..947cab271 100644 --- a/Terminal.Gui/Application/Application.cs +++ b/Terminal.Gui/Application/Application.cs @@ -975,7 +975,7 @@ public static partial class Application if (state.Toplevel.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || OverlappedChildNeedsDisplay ()) { - state.Toplevel.SetNeedsDisplay(); + state.Toplevel.SetNeedsDisplay (); state.Toplevel.Draw (); Driver.UpdateScreen (); @@ -1439,4 +1439,62 @@ public static partial class Application } #endregion Toplevel handling + + /// + /// Gets a string representation of the Application as rendered by . + /// + /// A string representation of the Application + public new static string ToString () + { + ConsoleDriver driver = Driver; + + if (driver is null) + { + return string.Empty; + } + + return ToString (driver); + } + + /// + /// Gets a string representation of the Application rendered by the provided . + /// + /// The driver to use to render the contents. + /// A string representation of the Application + public static string ToString (ConsoleDriver driver) + { + var sb = new StringBuilder (); + + Cell [,] contents = driver.Contents; + + for (var r = 0; r < driver.Rows; r++) + { + for (var c = 0; c < driver.Cols; c++) + { + Rune rune = contents [r, c].Rune; + + if (rune.DecodeSurrogatePair (out char [] sp)) + { + sb.Append (sp); + } + else + { + sb.Append ((char)rune.Value); + } + + if (rune.GetColumns () > 1) + { + c++; + } + + // See Issue #2616 + //foreach (var combMark in contents [r, c].CombiningMarks) { + // sb.Append ((char)combMark.Value); + //} + } + + sb.AppendLine (); + } + return sb.ToString (); + } } diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 32b68eb37..22819e701 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -1,10 +1,8 @@ -using System.Collections; -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using UICatalog; using Xunit.Abstractions; using Xunit.Sdk; @@ -19,14 +17,11 @@ namespace Terminal.Gui; [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] public class AutoInitShutdownAttribute : BeforeAfterTestAttribute { - private readonly Type _driverType; - /// /// Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown /// are automatically called Before/After a test runs. /// /// If true, Application.Init will be called Before the test runs. - /// If true, Application.Shutdown will be called After the test runs. /// /// Determines which ConsoleDriver (FakeDriver, WindowsDriver, CursesDriver, NetDriver) /// will be used when Application.Init is called. If null FakeDriver will be used. Only valid if @@ -65,7 +60,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute ConfigurationManager.Locations = configLocation; } - private bool AutoInit { get; } + private readonly Type _driverType; public override void After (MethodInfo methodUnderTest) { @@ -102,6 +97,7 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute ConfigurationManager.Reset (); #if DEBUG_IDISPOSABLE + // Clear out any lingering Responder instances from previous tests if (Responder.Instances.Count == 0) { @@ -115,6 +111,8 @@ public class AutoInitShutdownAttribute : BeforeAfterTestAttribute Application.Init ((ConsoleDriver)Activator.CreateInstance (_driverType)); } } + + private bool AutoInit { get; } } [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] @@ -178,8 +176,8 @@ public class SetupFakeDriverAttribute : BeforeAfterTestAttribute [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] public class TestDateAttribute : BeforeAfterTestAttribute { - private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture; public TestDateAttribute () { CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; } + private readonly CultureInfo _currentCulture = CultureInfo.CurrentCulture; public override void After (MethodInfo methodUnderTest) { @@ -238,12 +236,12 @@ internal partial class TestHelpers switch (match.Count) { case 0: - throw new Exception ( - $"{DriverContentsToString (driver)}\n" - + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n" - + $" Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n" - + $" But Was: " - ); + throw new ( + $"{Application.ToString (driver)}\n" + + $"Expected Attribute {val} (PlatformColor = {val.Value.PlatformColor}) at Contents[{line},{c}] {contents [line, c]} ((PlatformColor = {contents [line, c].Attribute.Value.PlatformColor}) was not found.\n" + + $" Expected: {string.Join (",", expectedAttributes.Select (c => c))}\n" + + $" But Was: " + ); case > 1: throw new ArgumentException ( $"Bad value for expectedColors, {match.Count} Attributes had the same Value" @@ -255,12 +253,12 @@ internal partial class TestHelpers if (colorUsed != userExpected) { - throw new Exception ( - $"{DriverContentsToString (driver)}\n" - + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n" - + $" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n" - + $" But Was: {colorUsed} ({val})\n" - ); + throw new ( + $"{Application.ToString (driver)}\n" + + $"Unexpected Attribute at Contents[{line},{c}] {contents [line, c]}.\n" + + $" Expected: {userExpected} ({expectedAttributes [int.Parse (userExpected.ToString ())]})\n" + + $" But Was: {colorUsed} ({val})\n" + ); } } @@ -282,7 +280,7 @@ internal partial class TestHelpers ) { #pragma warning restore xUnit1013 // Public method should be marked as test - string actualLook = DriverContentsToString (driver); + var actualLook = Application.ToString (driver); if (string.Equals (expectedLook, actualLook)) { @@ -337,7 +335,6 @@ internal partial class TestHelpers Cell [,] contents = driver.Contents; - for (var rowIndex = 0; rowIndex < driver.Rows; rowIndex++) { List runes = []; @@ -353,7 +350,7 @@ internal partial class TestHelpers x = colIndex; y = rowIndex; - for (int i = 0; i < colIndex; i++) + for (var i = 0; i < colIndex; i++) { runes.InsertRange (i, [SpaceRune]); } @@ -433,7 +430,7 @@ internal partial class TestHelpers if (string.Equals (expectedLook, actualLook)) { - return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); + return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); } // standardize line endings for the comparison @@ -453,7 +450,7 @@ internal partial class TestHelpers Assert.Equal (expectedLook, actualLook); - return new Rectangle (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); + return new (x > -1 ? x : 0, y > -1 ? y : 0, w > -1 ? w : 0, h > -1 ? h : 0); } #pragma warning disable xUnit1013 // Public method should be marked as test @@ -483,278 +480,6 @@ internal partial class TestHelpers } #pragma warning restore xUnit1013 // Public method should be marked as test - public static string DriverContentsToString (ConsoleDriver driver = null) - { - var sb = new StringBuilder (); - driver ??= Application.Driver; - - Cell [,] contents = driver.Contents; - - for (var r = 0; r < driver.Rows; r++) - { - for (var c = 0; c < driver.Cols; c++) - { - Rune rune = contents [r, c].Rune; - - if (rune.DecodeSurrogatePair (out char [] sp)) - { - sb.Append (sp); - } - else - { - sb.Append ((char)rune.Value); - } - - if (rune.GetColumns () > 1) - { - c++; - } - - // See Issue #2616 - //foreach (var combMark in contents [r, c].CombiningMarks) { - // sb.Append ((char)combMark.Value); - //} - } - - sb.AppendLine (); - } - - return sb.ToString (); - } - - //// TODO: Update all tests that use GetALlViews to use GetAllViewsTheoryData instead - ///// Gets a list of instances of all classes derived from View. - ///// List of View objects - //public static List GetAllViews () - //{ - // return typeof (View).Assembly.GetTypes () - // .Where ( - // type => type.IsClass - // && !type.IsAbstract - // && type.IsPublic - // && type.IsSubclassOf (typeof (View)) - // ) - // .Select (type => CreateView (type, type.GetConstructor (Array.Empty ()))) - // .ToList (); - //} - - //public class AllViewsData : IEnumerable - //{ - // private Lazy> data; - - // public AllViewsData () - // { - // data = new Lazy> (GetTestData); - // } - - // public IEnumerator GetEnumerator () - // { - // return data.Value.GetEnumerator (); - // } - - // IEnumerator IEnumerable.GetEnumerator () => GetEnumerator (); - - // private List GetTestData () - // { - // var viewTypes = typeof (View).Assembly - // .GetTypes () - // .Where (type => type.IsClass && !type.IsAbstract && type.IsPublic && type.IsSubclassOf (typeof (View))); - - // var testData = new List (); - - // foreach (var type in viewTypes) - // { - // var view = CreateView (type, type.GetConstructor (Array.Empty ())); - // testData.Add (new object [] { view, type.Name }); - // } - - // return testData; - // } - //} - - - /// - /// Verifies the console used all the when rendering. If one or more of the - /// expected colors are not used then the failure will output both the colors that were found to be used and which of - /// your expectations was not met. - /// - /// if null uses - /// - internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors) - { - driver ??= Application.Driver; - Cell [,] contents = driver.Contents; - - List toFind = expectedColors.ToList (); - - // Contents 3rd column is an Attribute - HashSet colorsUsed = new (); - - for (var r = 0; r < driver.Rows; r++) - { - for (var c = 0; c < driver.Cols; c++) - { - Attribute? val = contents [r, c].Attribute; - - if (val.HasValue) - { - colorsUsed.Add (val.Value); - - Attribute match = toFind.FirstOrDefault (e => e == val); - - // need to check twice because Attribute is a struct and therefore cannot be null - if (toFind.Any (e => e == val)) - { - toFind.Remove (match); - } - } - } - } - - if (!toFind.Any ()) - { - return; - } - - var sb = new StringBuilder (); - sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString ()))); - sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString ()))); - - throw new Exception (sb.ToString ()); - } - - private static void AddArguments (Type paramType, List pTypes) - { - if (paramType == typeof (Rectangle)) - { - pTypes.Add (Rectangle.Empty); - } - else if (paramType == typeof (string)) - { - pTypes.Add (string.Empty); - } - else if (paramType == typeof (int)) - { - pTypes.Add (0); - } - else if (paramType == typeof (bool)) - { - pTypes.Add (true); - } - else if (paramType.Name == "IList") - { - pTypes.Add (new List ()); - } - else if (paramType.Name == "View") - { - var top = new Toplevel (); - var view = new View (); - top.Add (view); - pTypes.Add (view); - } - else if (paramType.Name == "View[]") - { - pTypes.Add (new View [] { }); - } - else if (paramType.Name == "Stream") - { - pTypes.Add (new MemoryStream ()); - } - else if (paramType.Name == "String") - { - pTypes.Add (string.Empty); - } - else if (paramType.Name == "TreeView`1[T]") - { - pTypes.Add (string.Empty); - } - else - { - pTypes.Add (null); - } - } - - public static View CreateView (Type type, ConstructorInfo ctor) - { - View view = null; - - if (type.IsGenericType && type.IsTypeDefinition) - { - List gTypes = new (); - - foreach (Type args in type.GetGenericArguments ()) - { - gTypes.Add (typeof (object)); - } - - type = type.MakeGenericType (gTypes.ToArray ()); - - Assert.IsType (type, (View)Activator.CreateInstance (type)); - } - else - { - ParameterInfo [] paramsInfo = ctor.GetParameters (); - Type paramType; - List pTypes = new (); - - if (type.IsGenericType) - { - foreach (Type args in type.GetGenericArguments ()) - { - paramType = args.GetType (); - - if (args.Name == "T") - { - pTypes.Add (typeof (object)); - } - else - { - AddArguments (paramType, pTypes); - } - } - } - - foreach (ParameterInfo p in paramsInfo) - { - paramType = p.ParameterType; - - if (p.HasDefaultValue) - { - pTypes.Add (p.DefaultValue); - } - else - { - AddArguments (paramType, pTypes); - } - } - - if (type.IsGenericType && !type.IsTypeDefinition) - { - view = (View)Activator.CreateInstance (type); - Assert.IsType (type, view); - } - else - { - view = (View)ctor.Invoke (pTypes.ToArray ()); - Assert.IsType (type, view); - } - } - - return view; - } - - public static List GetAllViewClasses () - { - return typeof (View).Assembly.GetTypes () - .Where ( - myType => myType.IsClass - && !myType.IsAbstract - && myType.IsPublic - && myType.IsSubclassOf (typeof (View)) - ) - .ToList (); - } - public static View CreateViewFromType (Type type, ConstructorInfo ctor) { View viewType = null; @@ -824,6 +549,119 @@ internal partial class TestHelpers return viewType; } + public static List GetAllViewClasses () + { + return typeof (View).Assembly.GetTypes () + .Where ( + myType => myType.IsClass + && !myType.IsAbstract + && myType.IsPublic + && myType.IsSubclassOf (typeof (View)) + ) + .ToList (); + } + + /// + /// Verifies the console used all the when rendering. If one or more of the + /// expected colors are not used then the failure will output both the colors that were found to be used and which of + /// your expectations was not met. + /// + /// if null uses + /// + internal static void AssertDriverUsedColors (ConsoleDriver driver = null, params Attribute [] expectedColors) + { + driver ??= Application.Driver; + Cell [,] contents = driver.Contents; + + List toFind = expectedColors.ToList (); + + // Contents 3rd column is an Attribute + HashSet colorsUsed = new (); + + for (var r = 0; r < driver.Rows; r++) + { + for (var c = 0; c < driver.Cols; c++) + { + Attribute? val = contents [r, c].Attribute; + + if (val.HasValue) + { + colorsUsed.Add (val.Value); + + Attribute match = toFind.FirstOrDefault (e => e == val); + + // need to check twice because Attribute is a struct and therefore cannot be null + if (toFind.Any (e => e == val)) + { + toFind.Remove (match); + } + } + } + } + + if (!toFind.Any ()) + { + return; + } + + var sb = new StringBuilder (); + sb.AppendLine ("The following colors were not used:" + string.Join ("; ", toFind.Select (a => a.ToString ()))); + sb.AppendLine ("Colors used were:" + string.Join ("; ", colorsUsed.Select (a => a.ToString ()))); + + throw new (sb.ToString ()); + } + + private static void AddArguments (Type paramType, List pTypes) + { + if (paramType == typeof (Rectangle)) + { + pTypes.Add (Rectangle.Empty); + } + else if (paramType == typeof (string)) + { + pTypes.Add (string.Empty); + } + else if (paramType == typeof (int)) + { + pTypes.Add (0); + } + else if (paramType == typeof (bool)) + { + pTypes.Add (true); + } + else if (paramType.Name == "IList") + { + pTypes.Add (new List ()); + } + else if (paramType.Name == "View") + { + var top = new Toplevel (); + var view = new View (); + top.Add (view); + pTypes.Add (view); + } + else if (paramType.Name == "View[]") + { + pTypes.Add (new View [] { }); + } + else if (paramType.Name == "Stream") + { + pTypes.Add (new MemoryStream ()); + } + else if (paramType.Name == "String") + { + pTypes.Add (string.Empty); + } + else if (paramType.Name == "TreeView`1[T]") + { + pTypes.Add (string.Empty); + } + else + { + pTypes.Add (null); + } + } + [GeneratedRegex ("^\\s+", RegexOptions.Multiline)] private static partial Regex LeadingWhitespaceRegEx (); @@ -832,11 +670,11 @@ internal partial class TestHelpers string replaced = toReplace; replaced = Environment.NewLine.Length switch - { - 2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine), - 1 => replaced.Replace ("\r\n", Environment.NewLine), - var _ => replaced - }; + { + 2 when !replaced.Contains ("\r\n") => replaced.Replace ("\n", Environment.NewLine), + 1 => replaced.Replace ("\r\n", Environment.NewLine), + var _ => replaced + }; return replaced; } @@ -863,6 +701,4 @@ public class TestsAllViews return Activator.CreateInstance (type) as View; } - } - From f9fc33ff543d2f94f1821553d8961007213a43c9 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 09:34:40 -0600 Subject: [PATCH 10/18] Fixed error --- UnitTests/TestHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 22819e701..02af3b000 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -280,7 +280,7 @@ internal partial class TestHelpers ) { #pragma warning restore xUnit1013 // Public method should be marked as test - var actualLook = Application.ToString (driver); + var actualLook = Application.ToString (driver ?? Application.Driver); if (string.Equals (expectedLook, actualLook)) { From f1cab7d55ae2f5b5340deb1dfddce7e9d90ee78d Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 09:38:00 -0600 Subject: [PATCH 11/18] Fixed API docs --- UnitTests/TestHelpers.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/UnitTests/TestHelpers.cs b/UnitTests/TestHelpers.cs index 02af3b000..899a35986 100644 --- a/UnitTests/TestHelpers.cs +++ b/UnitTests/TestHelpers.cs @@ -8,12 +8,13 @@ using Xunit.Sdk; namespace Terminal.Gui; -// This class enables test functions annotated with the [AutoInitShutdown] attribute to -// automatically call Application.Init at start of the test and Application.Shutdown after the -// test exits. -// -// This is necessary because a) Application is a singleton and Init/Shutdown must be called -// as a pair, and b) all unit test functions should be atomic.. +/// +/// This class enables test functions annotated with the [AutoInitShutdown] attribute to +/// automatically call Application.Init at start of the test and Application.Shutdown after the +/// test exits. +/// This is necessary because a) Application is a singleton and Init/Shutdown must be called +/// as a pair, and b) all unit test functions should be atomic.. +/// [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)] public class AutoInitShutdownAttribute : BeforeAfterTestAttribute { @@ -312,8 +313,7 @@ internal partial class TestHelpers } /// - /// Asserts that the driver contents are equal to the expected look, and that the cursor is at the expected - /// position. + /// Asserts that the driver contents are equal to the provided string. /// /// /// From adc4feda1f0e924863dacd7b959459a55d032eb3 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 13:30:21 -0600 Subject: [PATCH 12/18] Tweaked yml --- .github/workflows/dotnet-core.yml | 47 ++++++++++++++++--------------- .github/workflows/publish.yml | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 4ca317ce0..431024549 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -2,19 +2,22 @@ name: Build & Test Terminal.Gui with .NET Core on: push: - branches: [ main, develop, v2_develop ] + branches: [ v2, v2_develop ] + paths-ignore: + - '**.md' pull_request: - branches: [ main, develop, v2_develop ] - + branches: [ v2, v2_develop ] + paths-ignore: + - '**.md' + jobs: build_and_test: - runs-on: windows-latest - + runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 - - name: Setup dotnet + - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: dotnet-version: 8.x @@ -34,19 +37,19 @@ jobs: mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ # Note: this step is currently not writing to the gist for some reason - - name: Create Test Coverage Badge - uses: simon-k/dotnet-code-coverage-badge@v1.0.0 - id: create_coverage_badge - with: - label: Unit Test Coverage - color: brightgreen - path: UnitTests/TestResults/coverage.opencover.xml - gist-filename: code-coverage.json - # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27 - gist-id: 90ef67a684cb71db1817921a970f8d27 - gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} + # - name: Create Test Coverage Badge + # uses: simon-k/dotnet-code-coverage-badge@v1.0.0 + # id: create_coverage_badge + # with: + # label: Unit Test Coverage + # color: brightgreen + # path: UnitTests/TestResults/coverage.opencover.xml + # gist-filename: code-coverage.json + # # https://gist.github.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27 + # gist-id: 90ef67a684cb71db1817921a970f8d27 + # gist-auth-token: ${{ secrets.GIST_AUTH_TOKEN }} - - name: Print Code Coverage - run: | - echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%" - echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}" + # - name: Print Code Coverage + # run: | + # echo "Code coverage percentage: ${{steps.create_coverage_badge.outputs.percentage}}%" + # echo "Badge data: ${{steps.create_coverage_badge.outputs.badge}}" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2c80e6e82..faf867527 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,7 @@ jobs: includePrerelease: true - name: Determine Version - uses: gittools/actions/gitversion/execute@v0 + uses: gittools/actions/gitversion/execute@v1 with: useConfigFile: true #additionalArguments: /b develop From 230fa5b02a3c0c7b944ab053d8cd48a70f745ae4 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 13:35:01 -0600 Subject: [PATCH 13/18] Re added checkout --- .github/workflows/dotnet-core.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 431024549..502cfb89f 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -17,6 +17,8 @@ jobs: timeout-minutes: 10 steps: + - uses: actions/checkout@v4 + - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: From 19c059de206f295bca8c73cc327df08d826dde51 Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 14:20:18 -0600 Subject: [PATCH 14/18] backported from v2_release --- .github/workflows/api-docs.yml | 2 +- .github/workflows/dotnet-core.yml | 4 ++-- GitVersion.yml | 39 +++++++++++++++++++------------ 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.github/workflows/api-docs.yml b/.github/workflows/api-docs.yml index 0386c3fef..b2c998fad 100644 --- a/.github/workflows/api-docs.yml +++ b/.github/workflows/api-docs.yml @@ -2,7 +2,7 @@ name: Build and publish API docs on: push: - branches: [main, develop, v2_develop] + branches: [main, v2_develop] permissions: id-token: write diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 502cfb89f..3c92c7854 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -2,11 +2,11 @@ name: Build & Test Terminal.Gui with .NET Core on: push: - branches: [ v2, v2_develop ] + branches: [ v2_release, v2_develop ] paths-ignore: - '**.md' pull_request: - branches: [ v2, v2_develop ] + branches: [ v2_release, v2_develop ] paths-ignore: - '**.md' diff --git a/GitVersion.yml b/GitVersion.yml index d5f370a83..af28d2f24 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -12,28 +12,37 @@ branches: v2_develop: mode: ContinuousDeployment - tag: pre + tag: dev regex: ^v2_develop?[/-] - is-release-branch: true + #is-release-branch: true tracks-release-branches: true - #is-source-branch-for: ['v2'] + is-source-branch-for: ['v2_release'] source-branches: [] - main: - tag: rc - increment: Patch - source-branches: - - develop - - main - feature: - tag: useBranchName - regex: ^features?[/-] - source-branches: - - develop - - main + v2_release: + mode: ContinuousDeployment + tag: prealpha + regex: v2_release + is-release-branch: true + source-branches: ['v2_develop'] + pull-request: + mode: ContinuousDeployment tag: PullRequest.{BranchName} increment: Inherit + tag-number-pattern: '[/-](?\d+)' + regex: ^(pull|pull\-requests|pr)[/-] + source-branches: + - develop + - main + - release + - v2_develop + - v2_release + - feature + - support + - hotfix + pre-release-weight: 30000 + ignore: sha: [] From 97335e4048ee27366d24758fd0bb2a3272b49beb Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 14:25:08 -0600 Subject: [PATCH 15/18] trying to get release tag right --- GitVersion.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/GitVersion.yml b/GitVersion.yml index af28d2f24..ba888b3d5 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,10 +1,10 @@ mode: ContinuousDeployment tag-prefix: '[vV]' -continuous-delivery-fallback-tag: pre +continuous-delivery-fallback-tag: dev branches: develop: mode: ContinuousDeployment - tag: pre + tag: dev regex: develop source-branches: - main @@ -14,7 +14,6 @@ branches: mode: ContinuousDeployment tag: dev regex: ^v2_develop?[/-] - #is-release-branch: true tracks-release-branches: true is-source-branch-for: ['v2_release'] source-branches: [] From 02999638f089c239f5701a70c819389158ad068e Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 14:40:54 -0600 Subject: [PATCH 16/18] trying to fix 3052 --- .github/workflows/dotnet-core.yml | 3 ++- UnitTests/UnitTests.sln | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 UnitTests/UnitTests.sln diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 3c92c7854..26507c2b0 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -35,7 +35,8 @@ jobs: - name: Test run: | sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json - dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings --blame + dotnet test --verbosity normal --blame-hang --blame-hang-dump-type full --blame-hang-timeout 5s + #dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings --blame mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ # Note: this step is currently not writing to the gist for some reason diff --git a/UnitTests/UnitTests.sln b/UnitTests/UnitTests.sln new file mode 100644 index 000000000..0d950924c --- /dev/null +++ b/UnitTests/UnitTests.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests.csproj", "{A29633F2-B26E-48B2-997A-1733286E3C13}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A29633F2-B26E-48B2-997A-1733286E3C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A29633F2-B26E-48B2-997A-1733286E3C13}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A29633F2-B26E-48B2-997A-1733286E3C13}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A29633F2-B26E-48B2-997A-1733286E3C13}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {86ED8EAD-F1D5-4F95-A0E6-6D73DFC8442F} + EndGlobalSection +EndGlobal From ec04bd55120338bde0fb21afc092efbb45a02a8f Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 14:45:21 -0600 Subject: [PATCH 17/18] trying to fix 3052 2 --- .github/workflows/dotnet-core.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index 26507c2b0..e205e6af5 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -35,8 +35,8 @@ jobs: - name: Test run: | sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json - dotnet test --verbosity normal --blame-hang --blame-hang-dump-type full --blame-hang-timeout 5s - #dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings --blame + dotnet test --verbosity normal --blame + # dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings --blame mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ # Note: this step is currently not writing to the gist for some reason From 8683568956c6ab7a2ef5432948202300dec1c4ac Mon Sep 17 00:00:00 2001 From: Tig Date: Sun, 7 Jul 2024 14:46:53 -0600 Subject: [PATCH 18/18] trying to fix 3052 3 --- .github/workflows/dotnet-core.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml index e205e6af5..908b67eb1 100644 --- a/.github/workflows/dotnet-core.yml +++ b/.github/workflows/dotnet-core.yml @@ -36,7 +36,6 @@ jobs: run: | sed -i 's/"stopOnFail": false/"stopOnFail": true/g' UnitTests/xunit.runner.json dotnet test --verbosity normal --blame - # dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --settings UnitTests/coverlet.runsettings --blame mv -v UnitTests/TestResults/*/*.* UnitTests/TestResults/ # Note: this step is currently not writing to the gist for some reason