NinePatch.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. // Copyright (c) Craftwork Games. All rights reserved.
  2. // Licensed under the MIT license.
  3. // See LICENSE file in the project root for full license information.
  4. using System;
  5. using Microsoft.Xna.Framework;
  6. namespace MonoGame.Extended.Graphics;
  7. /// <summary>
  8. /// Represents a nine-patch texture.
  9. /// </summary>
  10. /// <remarks>
  11. /// <para>
  12. /// A nine-patch texture is a specialized texture object used for rendering scalable graphical assets,
  13. /// particularly user interface (UI) elements.It consists of a single texture region subdivided into nine
  14. /// distinct subregions. When rendered, the four corner subregions remain unscaled, preserving their original
  15. /// dimensions. The top and bottom edge subregions are stretched horizontally, while the left and right edge
  16. /// subregions are stretched vertically. The central subregion is scaled along both axes to fill the desired
  17. /// dimensions.
  18. /// </para>
  19. /// <para>
  20. /// This approach is highly beneficial for UI components that require dynamic scaling, such as containers for
  21. /// menus, dialog boxes, or other resizable elements. By leveraging the nine-patch texture, these graphical
  22. /// assets can be seamlessly scaled to different sizes while maintaining their visual integrity and preventing
  23. /// undesirable distortions or stretching artifacts.
  24. /// </para>
  25. /// </remarks>
  26. public class NinePatch
  27. {
  28. /// <summary>The index representing the top-left patch.</summary>
  29. public const int TopLeft = 0;
  30. /// <summary>The index representing the top-middle patch.</summary>
  31. public const int TopMiddle = 1;
  32. /// <summary>The index representing the top-right patch.</summary>
  33. public const int TopRight = 2;
  34. /// <summary>The index representing the middle-left patch.</summary>
  35. public const int MiddleLeft = 3;
  36. /// <summary>The index representing the middle patch.</summary>
  37. public const int Middle = 4;
  38. /// <summary>The index representing the middle-right patch.</summary>
  39. public const int MiddleRight = 5;
  40. /// <summary>The index representing the bottom-left patch.</summary>
  41. public const int BottomLeft = 6;
  42. /// <summary>The index representing the bottom-middle patch.</summary>
  43. public const int BottomMiddle = 7;
  44. /// <summary>The index representing the bottom-right patch.</summary>
  45. public const int BottomRight = 8;
  46. private readonly Texture2DRegion[] _patches;
  47. /// <summary>
  48. /// Gets the name assigned to this nine-patch.
  49. /// </summary>
  50. public string Name { get; }
  51. /// <summary>
  52. /// The size of the border patches around the middle patch.
  53. /// </summary>
  54. public Thickness Padding { get; }
  55. /// <summary>
  56. /// Gets a read-only span of the texture regions that make up the nine-patch.
  57. /// </summary>
  58. /// <remarks>
  59. /// Elements are in order of top-left, top-middle, top-right, middle-left, middle, middle-right, bottom-left,
  60. /// bottom-middle, and bottom-right.
  61. /// </remarks>
  62. public ReadOnlySpan<Texture2DRegion> Patches => _patches;
  63. /// <summary>
  64. /// Initializes a new instance of the <see cref="NinePatch"/> class with the specified patches.
  65. /// </summary>
  66. /// <remarks>
  67. /// The <paramref name="patches"/> array should contain the elements in the order of top-left, top-middle,
  68. /// top-right, middle-left, middle, middle-right, bottom-left, bottom-middle, and bottom-right.
  69. /// </remarks>
  70. /// <param name="patches">An array of nine <see cref="Texture2DRegion"/> objects.</param>
  71. /// <exception cref="ArgumentNullException">Thrown if <paramref name="patches"/> is null.</exception>
  72. /// <exception cref="ArgumentException">
  73. /// Thrown if <paramref name="patches"/> does not contain exactly nine elements.
  74. /// </exception>
  75. public NinePatch(Texture2DRegion[] patches) : this(patches, null) { }
  76. /// <summary>
  77. /// Initializes a new instance of the <see cref="NinePatch"/> class with the specified patches and name.
  78. /// </summary>
  79. /// <param name="patches">
  80. /// An array of nine <see cref="Texture2DRegion"/> objects.
  81. /// The top, left, bottom and right regions must to be of exactly the same size.
  82. /// Mid patches can be as small as 1x1.
  83. /// </param>
  84. /// <param name="name">
  85. /// The name of the nine-patch. If null or empty, a default name will be generated based on the texture name of the
  86. /// top-left patch.
  87. /// </param>
  88. /// <exception cref="ArgumentNullException">Thrown if <paramref name="patches"/> is null.</exception>
  89. /// <exception cref="ArgumentException">
  90. /// Thrown if <paramref name="patches"/> does not contain exactly nine elements.
  91. /// </exception>
  92. public NinePatch(Texture2DRegion[] patches, string name)
  93. {
  94. ArgumentNullException.ThrowIfNull(patches);
  95. if (patches.Length != 9)
  96. {
  97. throw new ArgumentException($"{nameof(patches)} must contain exactly 9 elements.", nameof(patches));
  98. }
  99. if (string.IsNullOrEmpty(name))
  100. {
  101. name = $"{patches[0].Texture.Name}-nine-patch";
  102. }
  103. _patches = patches;
  104. Size topLeft = patches[NinePatch.TopLeft].OriginalSize;
  105. Size bottomRight = patches[NinePatch.BottomRight].OriginalSize;
  106. Padding = new Thickness(topLeft.Width, topLeft.Height, bottomRight.Width, bottomRight.Height);
  107. Name = name;
  108. }
  109. }