Skip to content

Commit

Permalink
Merge pull request #3585 from tig/v2_3303-Thickness-Record-Struct
Browse files Browse the repository at this point in the history
Fixes #3303. Makes `Thickness` a `record struct`
  • Loading branch information
tig committed Jul 7, 2024
2 parents 01dd3ef + 8d52770 commit ff1b898
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 100 deletions.
166 changes: 71 additions & 95 deletions Terminal.Gui/Drawing/Thickness.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json.Serialization;
using System.Numerics;
using System.Text.Json.Serialization;

namespace Terminal.Gui;

Expand All @@ -13,28 +14,18 @@ namespace Terminal.Gui;
/// frame,
/// with the thickness widths subtracted.
/// </para>
/// <para>Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.</para>
/// <para>
/// Use the helper API (<see cref="Draw(Rectangle, string)"/> to draw the frame with the specified thickness.
/// </para>
/// <para>
/// Thickness uses <see langword="float"/> 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.
/// </para>
/// </remarks>
public class Thickness : IEquatable<Thickness>
public record struct Thickness
{
/// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
[JsonInclude]
public int Bottom;

/// <summary>Gets or sets the width of the left side of the rectangle.</summary>
[JsonInclude]
public int Left;

/// <summary>Gets or sets the width of the right side of the rectangle.</summary>
[JsonInclude]
public int Right;

/// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
[JsonInclude]
public int Top;

/// <summary>Initializes a new instance of the <see cref="Thickness"/> class with all widths set to 0.</summary>
public Thickness () { }
public Thickness () { _sides = Vector4.Zero; }

/// <summary>Initializes a new instance of the <see cref="Thickness"/> class with a uniform width to each side.</summary>
/// <param name="width"></param>
Expand All @@ -56,36 +47,24 @@ public Thickness (int left, int top, int right, int bottom)
Bottom = bottom;
}

// TODO: add operator overloads
/// <summary>Gets an empty thickness.</summary>
public static Thickness Empty => new (0);
private Vector4 _sides;

/// <summary>
/// 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 <see cref="Thickness"/> to the current <see cref="Thickness"/>, returning a
/// new <see cref="Thickness"/>.
/// </summary>
public int Horizontal
{
get => Left + Right;
set => Left = Right = value / 2;
}
/// <param name="other"></param>
/// <returns></returns>
public readonly Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

Call to non-readonly member 'Thickness.Left.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

Call to non-readonly member 'Thickness.Top.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

Call to non-readonly member 'Thickness.Right.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

Call to non-readonly member 'Thickness.Bottom.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / build_and_test

Call to non-readonly member 'Thickness.Left.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / build_and_test

Call to non-readonly member 'Thickness.Top.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / build_and_test

Call to non-readonly member 'Thickness.Right.get' from a 'readonly' member results in an implicit copy of 'this'.

Check warning on line 58 in Terminal.Gui/Drawing/Thickness.cs

View workflow job for this annotation

GitHub Actions / build_and_test

Call to non-readonly member 'Thickness.Bottom.get' from a 'readonly' member results in an implicit copy of 'this'.

/// <summary>
/// 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.
/// </summary>
public int Vertical
/// <summary>Gets or sets the width of the lower side of the rectangle.</summary>
[JsonInclude]
public int Bottom
{
get => Top + Bottom;
set => Top = Bottom = value / 2;
get => (int)_sides.W;
set => _sides.W = value;
}

// IEquitable
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other"></param>
/// <returns>true if the current object is equal to the other parameter; otherwise, false.</returns>
public bool Equals (Thickness other) { return other is { } && Left == other.Left && Right == other.Right && Top == other.Top && Bottom == other.Bottom; }

/// <summary>
/// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside
/// the rectangle described by <see cref="GetInside(Rectangle)"/>.
Expand All @@ -100,22 +79,6 @@ public bool Contains (in Rectangle outside, in Point location)
return outside.Contains (location) && !inside.Contains (location);
}

/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to the current <see cref="Thickness"/>, returning a
/// new <see cref="Thickness"/>.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public Thickness Add (Thickness other) { return new (Left + other.Left, Top + other.Top, Right + other.Right, Bottom + other.Bottom); }

/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to another <see cref="Thickness"/>.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }

/// <summary>Draws the <see cref="Thickness"/> rectangle with an optional diagnostics label.</summary>
/// <remarks>
/// If <see cref="ViewDiagnosticFlags"/> is set to
Expand Down Expand Up @@ -240,31 +203,8 @@ rect with
return GetInside (rect);
}

/// <summary>Determines whether the specified object is equal to the current object.</summary>
/// <param name="obj">The object to compare with the current object.</param>
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, <c>false</c>.</returns>
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);
}

/// <inheritdoc/>
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;
}
/// <summary>Gets an empty thickness.</summary>
public static Thickness Empty => new (0);

/// <summary>
/// Returns a rectangle describing the location and size of the inside area of <paramref name="rect"/> with the
Expand All @@ -289,23 +229,59 @@ public Rectangle GetInside (Rectangle rect)
return new (x, y, width, height);
}

/// <inheritdoc/>
public static bool operator == (Thickness left, Thickness right) { return EqualityComparer<Thickness>.Default.Equals (left, right); }
/// <summary>
/// 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.
/// </summary>
public int Horizontal
{
get => Left + Right;
set => Left = Right = value / 2;
}

/// <inheritdoc/>
public static bool operator != (Thickness left, Thickness right) { return !(left == right); }
/// <summary>Gets or sets the width of the left side of the rectangle.</summary>
[JsonInclude]
public int Left
{
get => (int)_sides.X;
set => _sides.X = value;
}

/// <summary>
/// Adds the thickness widths of another <see cref="Thickness"/> to another <see cref="Thickness"/>.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns></returns>
public static Thickness operator + (Thickness a, Thickness b) { return a.Add (b); }

/// <summary>Gets or sets the width of the right side of the rectangle.</summary>
[JsonInclude]
public int Right
{
get => (int)_sides.Z;
set => _sides.Z = value;
}

/// <summary>Gets or sets the width of the upper side of the rectangle.</summary>
[JsonInclude]
public int Top
{
get => (int)_sides.Y;
set => _sides.Y = value;
}

/// <summary>Returns the thickness widths of the Thickness formatted as a string.</summary>
/// <returns>The thickness widths as a string.</returns>
public override string ToString () { return $"(Left={Left},Top={Top},Right={Right},Bottom={Bottom})"; }

private int validate (int width)
/// <summary>
/// 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.
/// </summary>
public int Vertical
{
if (width < 0)
{
throw new ArgumentException ("Thickness widths cannot be negative.");
}

return width;
get => Top + Bottom;
set => Top = Bottom = value / 2;
}
}
10 changes: 5 additions & 5 deletions UnitTests/View/Adornment/BorderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public void Border_Parent_HasFocus_Title_Uses_FocusAttribute ()
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)
Expand Down Expand Up @@ -53,7 +53,7 @@ public void Border_Uses_Parent_ColorScheme ()
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)
};
Expand Down Expand Up @@ -90,7 +90,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Four_Size_Width (int w
{
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;
Expand Down Expand Up @@ -224,7 +224,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Three_Size_Width (int
{
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;
Expand Down Expand Up @@ -358,7 +358,7 @@ public void Border_With_Title_Border_Double_Thickness_Top_Two_Size_Width (int wi
{
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;
Expand Down

0 comments on commit ff1b898

Please sign in to comment.