/ src / modules / MouseUtils / MouseJump.Common / Models / Drawing / RectangleInfo.cs
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  }