using System.Text.Json.Serialization; namespace Terminal.Gui; /// /// Describes the thickness of a frame around a rectangle. Four values describe the /// , , , and sides of the rectangle, /// respectively. /// /// /// /// Use the helper API ( to get the rectangle describing the insides of the frame, /// with the thickness widths subtracted. /// /// Use the helper API ( to draw the frame with the specified thickness. /// public class Thickness : IEquatable { /// 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 () { } /// Initializes a new instance of the class with a uniform width to each side. /// public Thickness (int width) : this (width, width, width, width) { } /// /// Initializes a new instance of the class that has specific widths applied to each side /// of the rectangle. /// /// /// /// /// public Thickness (int left, int top, int right, int bottom) { Left = left; Top = top; Right = right; Bottom = bottom; } // TODO: add operator overloads /// Gets an empty thickness. public static Thickness Empty => new (0); /// /// 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; } /// /// 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; } /// /// Gets whether the specified coordinates lie within the thickness (inside the bounding rectangle but outside of /// the rectangle described by . /// /// Describes the location and size of the rectangle that contains the thickness. /// The x coord to check. /// The y coord to check. /// if the specified coordinate is within the thickness; otherwise. public bool Contains (Rect outside, int x, int y) { Rect inside = GetInside (outside); return outside.Contains (x, y) && !inside.Contains (x, y); } /// Draws the rectangle with an optional diagnostics label. /// /// If is set to /// then 'T', 'L', 'R', and 'B' glyphs will be used instead of /// space. If is set to /// then a ruler will be drawn on the outer edge of the /// Thickness. /// /// The location and size of the rectangle that bounds the thickness rectangle, in screen coordinates. /// The diagnostics label to draw on the bottom of the . /// The inner rectangle remaining to be drawn. public Rect Draw (Rect rect, string label = null) { if (rect.Size.Width < 1 || rect.Size.Height < 1) { return Rect.Empty; } var clearChar = (Rune)' '; Rune leftChar = clearChar; Rune rightChar = clearChar; Rune topChar = clearChar; Rune bottomChar = clearChar; if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) { leftChar = (Rune)'L'; rightChar = (Rune)'R'; topChar = (Rune)'T'; bottomChar = (Rune)'B'; if (!string.IsNullOrEmpty (label)) { leftChar = rightChar = bottomChar = topChar = (Rune)label [0]; } } // Draw the Top side if (Top > 0) { Application.Driver.FillRect (new Rect (rect.X, rect.Y, rect.Width, Math.Min (rect.Height, Top)), topChar); } // Draw the Left side if (Left > 0) { Application.Driver.FillRect (new Rect (rect.X, rect.Y, Math.Min (rect.Width, Left), rect.Height), leftChar); } // Draw the Right side if (Right > 0) { Application.Driver.FillRect ( new Rect ( Math.Max (0, rect.X + rect.Width - Right), rect.Y, Math.Min (rect.Width, Right), rect.Height ), rightChar ); } // Draw the Bottom side if (Bottom > 0) { Application.Driver.FillRect ( new Rect ( rect.X, rect.Y + Math.Max (0, rect.Height - Bottom), rect.Width, Bottom ), bottomChar ); } // TODO: This should be moved to LineCanvas as a new LineStyle.Ruler if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FrameRuler) == ConsoleDriver.DiagnosticFlags.FrameRuler) { // Top var hruler = new Ruler { Length = rect.Width, Orientation = Orientation.Horizontal }; if (Top > 0) { hruler.Draw (new Point (rect.X, rect.Y)); } //Left var vruler = new Ruler { Length = rect.Height - 2, Orientation = Orientation.Vertical }; if (Left > 0) { vruler.Draw (new Point (rect.X, rect.Y + 1), 1); } // Bottom if (Bottom > 0) { hruler.Draw (new Point (rect.X, rect.Y + rect.Height - 1)); } // Right if (Right > 0) { vruler.Draw (new Point (rect.X + rect.Width - 1, rect.Y + 1), 1); } } if ((ConsoleDriver.Diagnostics & ConsoleDriver.DiagnosticFlags.FramePadding) == ConsoleDriver.DiagnosticFlags.FramePadding) { // Draw the diagnostics label on the bottom var tf = new TextFormatter { Text = label == null ? string.Empty : $"{label} {this}", Alignment = TextAlignment.Centered, VerticalAlignment = VerticalTextAlignment.Bottom }; tf.Draw (rect, Application.Driver.CurrentAttribute, Application.Driver.CurrentAttribute, rect, false); } 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 == 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. /// /// /// If a thickness width is negative, the inside rectangle will be larger than . e.g. a /// /// Thickness (-1, -1, -1, -1) will result in a rectangle skewed -1 in the X and Y directions and with a Size /// increased by 1. /// /// /// The source rectangle /// public Rect GetInside (Rect rect) { int x = rect.X + Left; int y = rect.Y + Top; int width = Math.Max (0, rect.Size.Width - Horizontal); int height = Math.Max (0, rect.Size.Height - Vertical); return new Rect (new Point (x, y), new Size (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; } }