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 /// 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 /// The mode to use for drawing this nine patch, defaults to public NinePatch(TextureRegion texture, Padding padding, NinePatchMode mode = NinePatchMode.Stretch) { this.Region = texture; this.Padding = 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 /// The padding on the right edge /// The padding on the top edge /// The padding on the bottom edge /// The mode to use for drawing this nine patch, defaults to public NinePatch(TextureRegion texture, int paddingLeft, int paddingRight, int paddingTop, int paddingBottom, NinePatchMode mode = NinePatchMode.Stretch) : this(texture, new Padding(paddingLeft, paddingRight, paddingTop, paddingBottom), mode) {} /// public NinePatch(Texture2D texture, int paddingLeft, int paddingRight, int paddingTop, int paddingBottom, NinePatchMode mode = NinePatchMode.Stretch) : this(new TextureRegion(texture), paddingLeft, paddingRight, paddingTop, paddingBottom, mode) {} /// /// Creates a new nine patch from a texture and a uniform padding /// /// The texture to use /// The padding that each edge should have /// The mode to use for drawing this nine patch, defaults to public NinePatch(Texture2D texture, int padding, NinePatchMode mode = NinePatchMode.Stretch) : this(new TextureRegion(texture), padding, mode) {} /// public NinePatch(TextureRegion texture, int padding, NinePatchMode mode = NinePatchMode.Stretch) : this(texture, padding, padding, padding, padding, mode) {} 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; 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); } } }