using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; using MLEM.Misc; namespace MLEM.Textures { /// /// This class represents a texture with nine areas. /// A nine patch texture is useful if a big area should be covered by a small texture that has a specific outline, like a gui panel texture. The center of the texture will be stretched or tiled, while the outline of the texture will remain at its original size, keeping aspect ratios alive. /// The nine patch can then be drawn using . /// public class NinePatch : GenericDataHolder { /// /// The texture region of this nine patch /// public readonly TextureRegion Region; /// /// The padding in each direction that marks where the outline area stops, in pixels /// public readonly Padding Padding; /// /// The that this nine patch should use for drawing /// public readonly NinePatchMode Mode; /// /// The nine patches that result from the /// public readonly Rectangle[] SourceRectangles; /// /// Creates a new nine patch from a texture and a padding /// /// The texture to use /// The padding that marks where the outline area stops in pixels, or as a percentage if is /// The mode to use for drawing this nine patch, defaults to /// Whether the padding should represent a percentage of the underlying 's size, rather than an absolute pixel amount public NinePatch(TextureRegion texture, Padding padding, NinePatchMode mode = NinePatchMode.Stretch, bool paddingPercent = false) { this.Region = texture; this.Padding = paddingPercent ? padding * texture.Size.ToVector2() : padding; this.Mode = mode; this.SourceRectangles = new Rectangle[9]; for (var i = 0; i < this.SourceRectangles.Length; i++) this.SourceRectangles[i] = (Rectangle) this.GetRectangleForIndex((RectangleF) this.Region.Area, i); } /// /// Creates a new nine patch from a texture and a padding /// /// The texture to use /// The padding on the left edge in pixels, or as a percentage if is /// The padding on the right edge in pixels, or as a percentage if is /// The padding on the top edge in pixels, or as a percentage if is /// The padding on the bottom edge in pixels, or as a percentage if is /// The mode to use for drawing this nine patch, defaults to /// Whether the padding should represent a percentage of the underlying 's size, rather than an absolute pixel amount public NinePatch(TextureRegion texture, float paddingLeft, float paddingRight, float paddingTop, float paddingBottom, NinePatchMode mode = NinePatchMode.Stretch, bool paddingPercent = false) : this(texture, new Padding(paddingLeft, paddingRight, paddingTop, paddingBottom), mode, paddingPercent) {} /// public NinePatch(Texture2D texture, float paddingLeft, float paddingRight, float paddingTop, float paddingBottom, NinePatchMode mode = NinePatchMode.Stretch, bool paddingPercent = false) : this(new TextureRegion(texture), paddingLeft, paddingRight, paddingTop, paddingBottom, mode, paddingPercent) {} /// /// Creates a new nine patch from a texture and a uniform padding /// /// The texture to use /// The padding that each edge should have in pixels, or as a percentage if is /// The mode to use for drawing this nine patch, defaults to /// Whether the padding should represent a percentage of the underlying 's size, rather than an absolute pixel amount public NinePatch(Texture2D texture, float padding, NinePatchMode mode = NinePatchMode.Stretch, bool paddingPercent = false) : this(new TextureRegion(texture), padding, mode, paddingPercent) {} /// public NinePatch(TextureRegion texture, float padding, NinePatchMode mode = NinePatchMode.Stretch, bool paddingPercent = false) : this(texture, padding, padding, padding, padding, mode, paddingPercent) {} internal RectangleF GetRectangleForIndex(RectangleF area, int index, float patchScale = 1) { var pl = this.Padding.Left * patchScale; var pr = this.Padding.Right * patchScale; var pt = this.Padding.Top * patchScale; var pb = this.Padding.Bottom * patchScale; var centerW = area.Width - pl - pr; var centerH = area.Height - pt - pb; var leftX = area.X + pl; var rightX = area.X + area.Width - pr; var topY = area.Y + pt; var bottomY = area.Y + area.Height - pb; switch (index) { case 0: return new RectangleF(area.X, area.Y, pl, pt); case 1: return new RectangleF(leftX, area.Y, centerW, pt); case 2: return new RectangleF(rightX, area.Y, pr, pt); case 3: return new RectangleF(area.X, topY, pl, centerH); case 4: return new RectangleF(leftX, topY, centerW, centerH); case 5: return new RectangleF(rightX, topY, pr, centerH); case 6: return new RectangleF(area.X, bottomY, pl, pb); case 7: return new RectangleF(leftX, bottomY, centerW, pb); case 8: return new RectangleF(rightX, bottomY, pr, pb); default: throw new ArgumentOutOfRangeException(nameof(index)); } } } /// /// An enumeration that represents the modes that a uses to be drawn /// public enum NinePatchMode { /// /// The nine resulting patches will each be stretched. /// This mode is fitting for textures that don't have an intricate design on their edges. /// Stretch, /// /// The nine resulting paches will be tiled, repeating the texture multiple times. /// This mode is fitting for textures that have a more complex design on their edges. /// Tile } /// /// A set of extensions that allow for rendering /// public static class NinePatchExtensions { /// /// Draws a nine patch area using the given sprite batch /// /// The batch to draw with /// The nine patch to draw /// The area that should be covered by this nine patch /// The color to use /// The rotation /// The origin position /// The effects that the sprite should have /// The depth /// The scale of each area of the nine patch public static void Draw(this SpriteBatch batch, NinePatch texture, RectangleF destinationRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth, float patchScale = 1) { for (var i = 0; i < texture.SourceRectangles.Length; i++) { var rect = texture.GetRectangleForIndex(destinationRectangle, i, patchScale); if (!rect.IsEmpty) { var src = texture.SourceRectangles[i]; switch (texture.Mode) { case NinePatchMode.Stretch: batch.Draw(texture.Region.Texture, rect, src, color, rotation, origin, effects, layerDepth); break; case NinePatchMode.Tile: var width = src.Width * patchScale; var height = src.Height * patchScale; if (width > 0 && height > 0) { for (var x = 0F; x < rect.Width; x += width) { for (var y = 0F; y < rect.Height; y += height) { var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height)); var srcSize = (size / patchScale).CeilCopy().ToPoint(); batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.X, src.Y, srcSize.X, srcSize.Y), color, rotation, origin, effects, layerDepth); } } } break; } } } } /// public static void Draw(this SpriteBatch batch, NinePatch texture, Rectangle destinationRectangle, Color color, float rotation, Vector2 origin, SpriteEffects effects, float layerDepth, float patchScale = 1) { batch.Draw(texture, (RectangleF) destinationRectangle, color, rotation, origin, effects, layerDepth, patchScale); } /// public static void Draw(this SpriteBatch batch, NinePatch texture, RectangleF destinationRectangle, Color color, float patchScale = 1) { batch.Draw(texture, destinationRectangle, color, 0, Vector2.Zero, SpriteEffects.None, 0, patchScale); } /// public static void Draw(this SpriteBatch batch, NinePatch texture, Rectangle destinationRectangle, Color color, float patchScale = 1) { batch.Draw(texture, destinationRectangle, color, 0, Vector2.Zero, SpriteEffects.None, 0, patchScale); } } }