using System.Diagnostics; using System.Diagnostics.CodeAnalysis; namespace RhythmBase.Components { /// /// Represents a rectangle structure containing left, top, right, and bottom boundary values. /// /// The left boundary of the rectangle. /// The top boundary of the rectangle. /// The right boundary of the rectangle. /// The bottom boundary of the rectangle. [DebuggerDisplay($"{{{nameof(GetDebuggerDisplay)}(),nq}}")] public struct RDRectI(int? left, int? top, int? right, int? bottom) : IEquatable { /// /// Gets or sets the left boundary of the rectangle. /// public int? Left { get; set; } = left; /// /// Gets or sets the right boundary of the rectangle. /// public int? Right { get; set; } = right; /// /// Gets or sets the top boundary of the rectangle. /// public int? Top { get; set; } = top; /// /// Gets or sets the bottom boundary of the rectangle. /// public int? Bottom { get; set; } = bottom; /// /// Gets the bottom-left corner point of the rectangle. /// public readonly RDPointI LeftBottom { get => new(Left, Bottom); } /// /// Gets the bottom-right corner point of the rectangle. /// public readonly RDPointI RightBottom { get => new(Right, Bottom); } /// /// Gets the top-left corner point of the rectangle. /// public readonly RDPointI LeftTop { get => new(Left, Top); } /// /// Gets the top-right corner point of the rectangle. /// public readonly RDPointI RightTop { get => new(Right, Top); } /// /// Gets the width of the rectangle. /// public readonly int? Width => checked(Right - Left); /// /// Gets the height of the rectangle. /// public readonly int? Height => checked(Top - Bottom); /// /// Initializes a new instance of the rectangle with the specified location and size. /// /// The location of the rectangle. /// The size of the rectangle. public RDRectI(RDPointI? location, RDSizeI? size) : this(location?.X, location?.Y + size?.Height, location?.X + size?.Width, location?.Y) { } /// /// Initializes a new instance of the rectangle with the specified size. /// /// The size of the rectangle. public RDRectI(RDSizeI? size) : this(0, size?.Height, size?.Width, 0) { } /// /// Initializes a new instance of the rectangle with the specified width and height. /// /// The width of the rectangle. /// The height of the rectangle. public RDRectI(int? width, int? height) : this(0, height, width, 0) { } /// /// Gets the location of the rectangle. /// public readonly RDPointI? Location => Left is null && Right is null ? null : new(Left, Bottom); /// /// Gets the size of the rectangle. /// public readonly RDSizeI? Size => Width is null && Height is null ? null : new(Width, Height); /// /// Inflates the rectangle by the specified size. /// /// The rectangle to inflate. /// The size to inflate by. /// The inflated rectangle. public static RDRectI Inflate(RDRectI rect, RDSizeI size) { RDRectI result = new(rect.Left, rect.Top, rect.Right, rect.Bottom); result.Inflate(size); return result; } /// /// Inflates the rectangle by the specified width and height. /// /// The rectangle to inflate. /// The width to inflate by. /// The height to inflate by. /// The inflated rectangle. public static RDRectI Inflate(RDRectI rect, int? x, int? y) { RDRectI result = new(rect.Left, rect.Top, rect.Right, rect.Bottom); result.Inflate(x, y); return result; } /// /// Converts the specified RDRect to RDRectI using ceiling. /// /// The RDRect to convert. /// The converted RDRectI. public static RDRectI Ceiling(RDRect rect) => Ceiling(rect, false); /// /// Converts the specified RDRect to RDRectI using ceiling, and specifies whether to expand outwards. /// /// The RDRect to convert. /// Whether to expand outwards. /// The converted RDRectI. public static RDRectI Ceiling(RDRect rect, bool outwards) => new( rect.Left == null ? null : (int)(outwards && rect.Width > 0 ? Math.Floor((double)rect.Left) : Math.Ceiling((double)rect.Left)), rect.Top == null ? null : (int)(outwards && rect.Height > 0 ? Math.Floor((double)rect.Top) : Math.Ceiling((double)rect.Top)), rect.Right == null ? null : (int)(outwards && rect.Width < 0 ? Math.Floor((double)rect.Right) : Math.Ceiling((double)rect.Right)), rect.Bottom == null ? null : (int)(outwards && rect.Height < 0 ? Math.Floor((double)rect.Bottom) : Math.Ceiling((double)rect.Bottom))); /// /// Converts the specified RDRect to RDRectI using floor. /// /// The RDRect to convert. /// The converted RDRectI. public static RDRectI Floor(RDRect rect) => Ceiling(rect, false); /// /// Converts the specified RDRect to RDRectI using floor, and specifies whether to shrink inwards. /// /// The RDRect to convert. /// Whether to shrink inwards. /// The converted RDRectI. public static RDRectI Floor(RDRect rect, bool inwards) => new( rect.Left == null ? null : (int)(inwards && rect.Width > 0 ? Math.Ceiling((double)rect.Left) : Math.Floor((double)rect.Left)), rect.Top == null ? null : (int)(inwards && rect.Height > 0 ? Math.Ceiling((double)rect.Top) : Math.Floor((double)rect.Top)), rect.Right == null ? null : (int)(inwards && rect.Width < 0 ? Math.Ceiling((double)rect.Right) : Math.Floor((double)rect.Right)), rect.Bottom == null ? null : (int)(inwards && rect.Height < 0 ? Math.Ceiling((double)rect.Bottom) : Math.Floor((double)rect.Bottom))); /// /// Converts the specified RDRect to RDRectI using rounding. /// /// The RDRect to convert. /// The converted RDRectI. public static RDRectI Round(RDRect rect) => new( new int?((int)Math.Round((rect.Left == null) ? 0.0 : Math.Round((double)rect.Left.Value))), new int?((int)Math.Round((rect.Top == null) ? 0.0 : Math.Round((double)rect.Top.Value))), new int?((int)Math.Round((rect.Right == null) ? 0.0 : Math.Round((double)rect.Right.Value))), new int?((int)Math.Round((rect.Bottom == null) ? 0.0 : Math.Round((double)rect.Bottom.Value)))); /// /// Returns a new RDRectI that is the union of two rectangles. /// /// The first rectangle. /// The second rectangle. /// The union of the two rectangles. public static RDRectI Union(RDRectI rect1, RDRectI rect2) => new( new int?((rect1.Left == null || rect2.Left == null) ? 0 : Math.Min(rect1.Left.Value, rect2.Left.Value)), new int?((rect1.Top == null || rect2.Top == null) ? 0 : Math.Min(rect1.Top.Value, rect2.Top.Value)), new int?((rect1.Right == null || rect2.Right == null) ? 0 : Math.Min(rect1.Right.Value, rect2.Right.Value)), new int?((rect1.Bottom == null || rect2.Bottom == null) ? 0 : Math.Min(rect1.Bottom.Value, rect2.Bottom.Value))); /// /// Returns a new RDRectI that is the intersection of two rectangles. /// /// The first rectangle. /// The second rectangle. /// The intersection of the two rectangles. public static RDRectI Intersect(RDRectI rect1, RDRectI rect2) => rect1.IntersectsWithInclusive(rect2) ? new RDRectI( new int?((rect1.Left == null || rect2.Left == null) ? 0 : Math.Max(rect1.Left.Value, rect2.Left.Value)), new int?((rect1.Top == null || rect2.Top == null) ? 0 : Math.Max(rect1.Top.Value, rect2.Top.Value)), new int?((rect1.Right == null || rect2.Right == null) ? 0 : Math.Min(rect1.Right.Value, rect2.Right.Value)), new int?((rect1.Bottom == null || rect2.Bottom == null) ? 0 : Math.Min(rect1.Bottom.Value, rect2.Bottom.Value))) : default; /// /// Converts the specified RDRect to RDRectI by truncating the decimal part. /// /// The RDRect to convert. /// The converted RDRectI. public static RDRectI Truncate(RDRect rect) => new( (int?)rect.Left, (int?)rect.Top, (int?)rect.Right, (int?)rect.Bottom); /// /// Offsets the rectangle by the specified amounts. /// /// The horizontal offset. /// The vertical offset. public void Offset(int? x, int? y) { Left += x; Top += y; Right += x; Bottom += y; } /// /// Moves the rectangle by the specified point. /// /// The point containing the offset. public void Offset(RDPointI p) => Offset(p.X, p.Y); /// /// Inflates the rectangle by the specified size. /// /// The size to inflate by. public void Inflate(RDSizeI size) { Left -= size.Width; Top += size.Height; Right += size.Width; Bottom -= size.Height; } /// /// Inflates the rectangle by the specified width and height. /// /// The width to inflate by. /// The height to inflate by. public void Inflate(int? width, int? height) { Left -= width; Top += height; Right += width; Bottom -= height; } /// /// Returns a new RDRectI that is the union of the current rectangle and the specified rectangle. /// /// The rectangle to merge with. /// The union of the two rectangles. public readonly RDRectI Union(RDRectI rect) => Union(this, rect); /// /// Determines whether the current rectangle intersects with the specified rectangle (including edges). /// /// The rectangle to check. /// true if the two rectangles intersect (including edges); otherwise, false. public readonly bool IntersectsWithInclusive(RDRectI rect) => Left <= rect.Right && Right >= rect.Left && Top <= rect.Bottom && Bottom >= rect.Top; /// public static bool operator ==(RDRectI rect1, RDRectI rect2) => rect1.Equals(rect2); /// public static bool operator !=(RDRectI rect1, RDRectI rect2) => !rect1.Equals(rect2); /// public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is RDRectI e && Equals(e); /// public override readonly int GetHashCode() => HashCode.Combine(Left, Top, Right, Bottom); /// public override readonly string ToString() => $"{{Location=[{Left},{Bottom}],Size=[{Width},{Height}]}}"; /// public readonly bool Equals(RDRectI other) => Left == other.Left && Top == other.Top && Right == other.Right && Bottom == other.Bottom; /// /// Implicitly converts an to an . /// /// The instance to convert. /// The converted instance. public static implicit operator RDRect(RDRectI rect) => new(rect.Left, rect.Top, rect.Right, rect.Bottom); /// /// Implicitly converts an to an . /// /// The instance to convert. /// The converted instance. public static implicit operator RDRectE(RDRectI rect) => new(rect.Left, rect.Top, rect.Right, rect.Bottom); private readonly string GetDebuggerDisplay() => ToString(); } }