RectangleInfo.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System.Text.Json.Serialization; 6 7 using MouseJump.Common.Models.Styles; 8 using BorderStyle = MouseJump.Common.Models.Styles.BorderStyle; 9 10 namespace MouseJump.Common.Models.Drawing; 11 12 /// <summary> 13 /// Immutable version of a System.Drawing.Rectangle object with some extra utility methods. 14 /// </summary> 15 public sealed class RectangleInfo 16 { 17 public static readonly RectangleInfo Empty = new(0, 0, 0, 0); 18 19 public RectangleInfo(decimal x, decimal y, decimal width, decimal height) 20 { 21 this.X = x; 22 this.Y = y; 23 this.Width = width; 24 this.Height = height; 25 } 26 27 public RectangleInfo(Rectangle rectangle) 28 : this(rectangle.X, rectangle.Y, rectangle.Width, rectangle.Height) 29 { 30 } 31 32 public RectangleInfo(Point location, SizeInfo size) 33 : this(location.X, location.Y, size.Width, size.Height) 34 { 35 } 36 37 public RectangleInfo(SizeInfo size) 38 : this(0, 0, size.Width, size.Height) 39 { 40 } 41 42 public decimal X 43 { 44 get; 45 } 46 47 public decimal Y 48 { 49 get; 50 } 51 52 public decimal Width 53 { 54 get; 55 } 56 57 public decimal Height 58 { 59 get; 60 } 61 62 [JsonIgnore] 63 public decimal Left => 64 this.X; 65 66 [JsonIgnore] 67 public decimal Top => 68 this.Y; 69 70 [JsonIgnore] 71 public decimal Right => 72 this.X + this.Width; 73 74 [JsonIgnore] 75 public decimal Bottom => 76 this.Y + this.Height; 77 78 [JsonIgnore] 79 public decimal Area => 80 this.Width * this.Height; 81 82 [JsonIgnore] 83 public PointInfo Location => 84 new(this.X, this.Y); 85 86 [JsonIgnore] 87 public PointInfo Midpoint => 88 new( 89 x: this.X + (this.Width / 2), 90 y: this.Y + (this.Height / 2)); 91 92 [JsonIgnore] 93 public SizeInfo Size => new(this.Width, this.Height); 94 95 /// <summary> 96 /// Centers the rectangle around a specified point. 97 /// </summary> 98 /// <param name="point">The <see cref="PointInfo"/> around which the rectangle will be centered.</param> 99 /// <returns>A new <see cref="RectangleInfo"/> that is centered around the specified point.</returns> 100 public RectangleInfo Center(PointInfo point) => 101 new( 102 x: point.X - (this.Width / 2), 103 y: point.Y - (this.Height / 2), 104 width: this.Width, 105 height: this.Height); 106 107 /// <summary> 108 /// Returns a new <see cref="RectangleInfo"/> that is moved within the bounds of the specified outer rectangle. 109 /// If the current rectangle is larger than the outer rectangle, an exception is thrown. 110 /// </summary> 111 /// <param name="outer">The outer <see cref="RectangleInfo"/> within which to confine this rectangle.</param> 112 /// <returns>A new <see cref="RectangleInfo"/> that is the result of moving this rectangle within the bounds of the outer rectangle.</returns> 113 /// <exception cref="ArgumentException">Thrown when the current rectangle is larger than the outer rectangle.</exception> 114 public RectangleInfo Clamp(RectangleInfo outer) 115 { 116 if ((this.Width > outer.Width) || (this.Height > outer.Height)) 117 { 118 throw new ArgumentException($"Value cannot be larger than {nameof(outer)}."); 119 } 120 121 return new( 122 x: Math.Clamp(this.X, outer.X, outer.Right - this.Width), 123 y: Math.Clamp(this.Y, outer.Y, outer.Bottom - this.Height), 124 width: this.Width, 125 height: this.Height); 126 } 127 128 /// <remarks> 129 /// Adapted from https://github.com/dotnet/runtime 130 /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs 131 /// </remarks> 132 public bool Contains(decimal x, decimal y) => 133 this.X <= x && x < this.X + this.Width && this.Y <= y && y < this.Y + this.Height; 134 135 /// <remarks> 136 /// Adapted from https://github.com/dotnet/runtime 137 /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs 138 /// </remarks> 139 public bool Contains(PointInfo pt) => 140 this.Contains(pt.X, pt.Y); 141 142 /// <remarks> 143 /// Adapted from https://github.com/dotnet/runtime 144 /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs 145 /// </remarks> 146 public bool Contains(RectangleInfo rect) => 147 (this.X <= rect.X) && (rect.X + rect.Width <= this.X + this.Width) && 148 (this.Y <= rect.Y) && (rect.Y + rect.Height <= this.Y + this.Height); 149 150 /// <summary> 151 /// Returns a new <see cref="RectangleInfo"/> that is larger than the current rectangle. 152 /// The dimensions of the new rectangle are calculated by enlarging the current rectangle's dimensions by the size of the border. 153 /// </summary> 154 /// <param name="border">The <see cref="BorderStyle"/> that specifies the amount to enlarge the rectangle on each side.</param> 155 /// <returns>A new <see cref="RectangleInfo"/> that is larger than the current rectangle by the specified border amounts.</returns> 156 public RectangleInfo Enlarge(BorderStyle border) => 157 new( 158 this.X - border.Left, 159 this.Y - border.Top, 160 this.Width + border.Horizontal, 161 this.Height + border.Vertical); 162 163 /// <summary> 164 /// Returns a new <see cref="RectangleInfo"/> that is larger than the current rectangle. 165 /// The dimensions of the new rectangle are calculated by enlarging the current rectangle's dimensions by the size of the margin. 166 /// </summary> 167 /// <param name="margin">The <see cref="MarginStyle"/> that specifies the amount to enlarge the rectangle on each side.</param> 168 /// <returns>A new <see cref="RectangleInfo"/> that is larger than the current rectangle by the specified margin amounts.</returns> 169 public RectangleInfo Enlarge(MarginStyle margin) => 170 new( 171 this.X - margin.Left, 172 this.Y - margin.Top, 173 this.Width + margin.Horizontal, 174 this.Height + margin.Vertical); 175 176 /// <summary> 177 /// Returns a new <see cref="RectangleInfo"/> that is larger than the current rectangle. 178 /// The dimensions of the new rectangle are calculated by enlarging the current rectangle's dimensions by the size of the padding. 179 /// </summary> 180 /// <param name="padding">The <see cref="PaddingStyle"/> that specifies the amount to enlarge the rectangle on each side.</param> 181 /// <returns>A new <see cref="RectangleInfo"/> that is larger than the current rectangle by the specified padding amounts.</returns> 182 public RectangleInfo Enlarge(PaddingStyle padding) => 183 new( 184 this.X - padding.Left, 185 this.Y - padding.Top, 186 this.Width + padding.Horizontal, 187 this.Height + padding.Vertical); 188 189 /// <summary> 190 /// Returns a new <see cref="RectangleInfo"/> that is offset by the specified amount. 191 /// </summary> 192 /// <param name="amount">The <see cref="SizeInfo"/> representing the amount to offset in both the X and Y directions.</param> 193 /// <returns>A new <see cref="RectangleInfo"/> that is offset by the specified amount.</returns> 194 public RectangleInfo Offset(SizeInfo amount) => 195 this.Offset(amount.Width, amount.Height); 196 197 /// <summary> 198 /// Returns a new <see cref="RectangleInfo"/> that is offset by the specified X and Y distances. 199 /// </summary> 200 /// <param name="dx">The distance to offset the rectangle along the X-axis.</param> 201 /// <param name="dy">The distance to offset the rectangle along the Y-axis.</param> 202 /// <returns>A new <see cref="RectangleInfo"/> that is offset by the specified X and Y distances.</returns> 203 public RectangleInfo Offset(decimal dx, decimal dy) => 204 new(this.X + dx, this.Y + dy, this.Width, this.Height); 205 206 public RectangleInfo Round() => 207 this.Round(0); 208 209 public RectangleInfo Round(int decimals) => new( 210 Math.Round(this.X, decimals), 211 Math.Round(this.Y, decimals), 212 Math.Round(this.Width, decimals), 213 Math.Round(this.Height, decimals)); 214 215 /// <summary> 216 /// Returns a new <see cref="RectangleInfo"/> that is a scaled version of the current rectangle. 217 /// The dimensions of the new rectangle are calculated by multiplying the current rectangle's dimensions by the scaling factor. 218 /// </summary> 219 /// <param name="scalingFactor">The factor by which to scale the rectangle's dimensions.</param> 220 /// <returns>A new <see cref="RectangleInfo"/> that is a scaled version of the current rectangle.</returns> 221 public RectangleInfo Scale(decimal scalingFactor) => 222 new( 223 this.X * scalingFactor, 224 this.Y * scalingFactor, 225 this.Width * scalingFactor, 226 this.Height * scalingFactor); 227 228 /// <summary> 229 /// Returns a new <see cref="RectangleInfo"/> that is smaller than the current rectangle. 230 /// The dimensions of the new rectangle are calculated by shrinking the current rectangle's dimensions by the size of the border. 231 /// </summary> 232 /// <param name="border">The <see cref="BorderStyle"/> that specifies the amount to shrink the rectangle on each side.</param> 233 /// <returns>A new <see cref="RectangleInfo"/> that is smaller than the current rectangle by the specified border amounts.</returns> 234 public RectangleInfo Shrink(BorderStyle border) => 235 new( 236 this.X + border.Left, 237 this.Y + border.Top, 238 this.Width - border.Horizontal, 239 this.Height - border.Vertical); 240 241 /// <summary> 242 /// Returns a new <see cref="RectangleInfo"/> that is smaller than the current rectangle. 243 /// The dimensions of the new rectangle are calculated by shrinking the current rectangle's dimensions by the size of the margin. 244 /// </summary> 245 /// <param name="margin">The <see cref="MarginStyle"/> that specifies the amount to shrink the rectangle on each side.</param> 246 /// <returns>A new <see cref="RectangleInfo"/> that is smaller than the current rectangle by the specified margin amounts.</returns> 247 public RectangleInfo Shrink(MarginStyle margin) => 248 new( 249 this.X + margin.Left, 250 this.Y + margin.Top, 251 this.Width - margin.Horizontal, 252 this.Height - margin.Vertical); 253 254 /// <summary> 255 /// Returns a new <see cref="RectangleInfo"/> that is smaller than the current rectangle. 256 /// The dimensions of the new rectangle are calculated by shrinking the current rectangle's dimensions by the size of the padding. 257 /// </summary> 258 /// <param name="padding">The <see cref="PaddingStyle"/> that specifies the amount to shrink the rectangle on each side.</param> 259 /// <returns>A new <see cref="RectangleInfo"/> that is smaller than the current rectangle by the specified padding amounts.</returns> 260 public RectangleInfo Shrink(PaddingStyle padding) => 261 new( 262 this.X + padding.Left, 263 this.Y + padding.Top, 264 this.Width - padding.Horizontal, 265 this.Height - padding.Vertical); 266 267 /// <summary> 268 /// Returns a new <see cref="RectangleInfo"/> where the X, Y, Width, and Height properties of the current rectangle are truncated to integers. 269 /// </summary> 270 /// <returns>A new <see cref="RectangleInfo"/> with the X, Y, Width, and Height properties of the current rectangle truncated to integers.</returns> 271 public RectangleInfo Truncate() => 272 new( 273 (int)this.X, 274 (int)this.Y, 275 (int)this.Width, 276 (int)this.Height); 277 278 /// <remarks> 279 /// Adapted from https://github.com/dotnet/runtime 280 /// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs 281 /// </remarks> 282 public RectangleInfo Union(RectangleInfo rect) 283 { 284 var x1 = Math.Min(this.X, rect.X); 285 var x2 = Math.Max(this.X + this.Width, rect.X + rect.Width); 286 var y1 = Math.Min(this.Y, rect.Y); 287 var y2 = Math.Max(this.Y + this.Height, rect.Y + rect.Height); 288 289 return new RectangleInfo(x1, y1, x2 - x1, y2 - y1); 290 } 291 292 public Rectangle ToRectangle() => 293 new( 294 (int)this.X, 295 (int)this.Y, 296 (int)this.Width, 297 (int)this.Height); 298 299 public override string ToString() 300 { 301 return "{" + 302 $"{nameof(this.Left)}={this.Left}," + 303 $"{nameof(this.Top)}={this.Top}," + 304 $"{nameof(this.Width)}={this.Width}," + 305 $"{nameof(this.Height)}={this.Height}" + 306 "}"; 307 } 308 }