2021-02-19 21:24:08 +01:00
using System ;
2019-08-09 15:15:22 +02:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2019-08-11 21:38:03 +02:00
using MLEM.Extensions ;
2019-11-02 14:53:59 +01:00
using MLEM.Misc ;
2019-08-09 15:15:22 +02:00
namespace MLEM.Textures {
2020-05-21 17:21:34 +02:00
/// <summary>
/// This class represents a texture with nine areas.
2021-02-19 21:24:08 +01:00
/// 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 <see cref="NinePatchExtensions"/>.
2020-05-21 17:21:34 +02:00
/// </summary>
2020-03-21 00:49:43 +01:00
public class NinePatch : GenericDataHolder {
2019-08-09 15:15:22 +02:00
2020-05-21 17:21:34 +02:00
/// <summary>
/// The texture region of this nine patch
/// </summary>
2019-08-09 15:15:22 +02:00
public readonly TextureRegion Region ;
2020-05-21 17:21:34 +02:00
/// <summary>
2022-11-03 18:16:59 +01:00
/// The padding in each direction that marks where the outline area stops, in pixels
2020-05-21 17:21:34 +02:00
/// </summary>
2019-12-14 14:00:12 +01:00
public readonly Padding Padding ;
2020-05-21 17:21:34 +02:00
/// <summary>
2021-02-19 21:24:08 +01:00
/// The <see cref="NinePatchMode"/> that this nine patch should use for drawing
/// </summary>
public readonly NinePatchMode Mode ;
/// <summary>
2020-05-21 17:21:34 +02:00
/// The nine patches that result from the <see cref="Padding"/>
/// </summary>
2019-08-09 15:15:22 +02:00
public readonly Rectangle [ ] SourceRectangles ;
2020-05-21 17:21:34 +02:00
/// <summary>
/// Creates a new nine patch from a texture and a padding
/// </summary>
/// <param name="texture">The texture to use</param>
2022-11-03 18:16:59 +01:00
/// <param name="padding">The padding that marks where the outline area stops in pixels, or as a percentage if <paramref name="paddingPercent"/> is <see langword="true"/></param>
2021-02-19 21:24:08 +01:00
/// <param name="mode">The mode to use for drawing this nine patch, defaults to <see cref="NinePatchMode.Stretch"/></param>
2022-11-03 18:16:59 +01:00
/// <param name="paddingPercent">Whether the padding should represent a percentage of the underlying <paramref name="texture"/>'s size, rather than an absolute pixel amount</param>
public NinePatch ( TextureRegion texture , Padding padding , NinePatchMode mode = NinePatchMode . Stretch , bool paddingPercent = false ) {
2019-08-09 15:15:22 +02:00
this . Region = texture ;
2022-11-03 18:16:59 +01:00
this . Padding = paddingPercent ? padding * texture . Size . ToVector2 ( ) : padding ;
2021-02-19 21:24:08 +01:00
this . Mode = mode ;
2021-07-03 01:44:39 +02:00
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 ) ;
2019-08-09 15:15:22 +02:00
}
2020-05-21 17:21:34 +02:00
/// <summary>
/// Creates a new nine patch from a texture and a padding
/// </summary>
/// <param name="texture">The texture to use</param>
2022-11-03 18:16:59 +01:00
/// <param name="paddingLeft">The padding on the left edge in pixels, or as a percentage if <paramref name="paddingPercent"/> is <see langword="true"/></param>
/// <param name="paddingRight">The padding on the right edge in pixels, or as a percentage if <paramref name="paddingPercent"/> is <see langword="true"/></param>
/// <param name="paddingTop">The padding on the top edge in pixels, or as a percentage if <paramref name="paddingPercent"/> is <see langword="true"/></param>
/// <param name="paddingBottom">The padding on the bottom edge in pixels, or as a percentage if <paramref name="paddingPercent"/> is <see langword="true"/></param>
2021-02-19 21:24:08 +01:00
/// <param name="mode">The mode to use for drawing this nine patch, defaults to <see cref="NinePatchMode.Stretch"/></param>
2022-11-03 18:16:59 +01:00
/// <param name="paddingPercent">Whether the padding should represent a percentage of the underlying <paramref name="texture"/>'s size, rather than an absolute pixel amount</param>
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 ) { }
2019-12-14 14:00:12 +01:00
2022-11-03 18:16:59 +01:00
/// <inheritdoc cref="NinePatch(TextureRegion, float, float, float, float, NinePatchMode, bool)"/>
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 ) { }
2019-08-09 15:15:22 +02:00
2020-05-21 17:21:34 +02:00
/// <summary>
/// Creates a new nine patch from a texture and a uniform padding
/// </summary>
/// <param name="texture">The texture to use</param>
2022-11-03 18:16:59 +01:00
/// <param name="padding">The padding that each edge should have in pixels, or as a percentage if <paramref name="paddingPercent"/> is <see langword="true"/></param>
2021-02-19 21:24:08 +01:00
/// <param name="mode">The mode to use for drawing this nine patch, defaults to <see cref="NinePatchMode.Stretch"/></param>
2022-11-03 18:16:59 +01:00
/// <param name="paddingPercent">Whether the padding should represent a percentage of the underlying <paramref name="texture"/>'s size, rather than an absolute pixel amount</param>
public NinePatch ( Texture2D texture , float padding , NinePatchMode mode = NinePatchMode . Stretch , bool paddingPercent = false ) : this ( new TextureRegion ( texture ) , padding , mode , paddingPercent ) { }
2019-08-10 21:37:10 +02:00
2022-11-03 18:16:59 +01:00
/// <inheritdoc cref="NinePatch(TextureRegion, float, NinePatchMode, bool)"/>
public NinePatch ( TextureRegion texture , float padding , NinePatchMode mode = NinePatchMode . Stretch , bool paddingPercent = false ) : this ( texture , padding , padding , padding , padding , mode , paddingPercent ) { }
2019-11-02 14:53:59 +01:00
2021-07-03 01:44:39 +02:00
internal RectangleF GetRectangleForIndex ( RectangleF area , int index , float patchScale = 1 ) {
2020-04-26 16:10:32 +02:00
var pl = this . Padding . Left * patchScale ;
var pr = this . Padding . Right * patchScale ;
var pt = this . Padding . Top * patchScale ;
var pb = this . Padding . Bottom * patchScale ;
2019-08-09 15:15:22 +02:00
2019-08-11 21:38:03 +02:00
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 ;
2021-07-03 01:44:39 +02:00
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 ) ) ;
}
2021-02-19 21:24:08 +01:00
}
}
/// <summary>
/// An enumeration that represents the modes that a <see cref="NinePatch"/> uses to be drawn
/// </summary>
public enum NinePatchMode {
/// <summary>
/// The nine resulting patches will each be stretched.
/// This mode is fitting for textures that don't have an intricate design on their edges.
/// </summary>
Stretch ,
/// <summary>
/// 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.
/// </summary>
Tile
2019-08-09 15:15:22 +02:00
}
2020-05-21 17:21:34 +02:00
/// <summary>
/// A set of extensions that allow for <see cref="NinePatch"/> rendering
/// </summary>
2019-08-09 15:15:22 +02:00
public static class NinePatchExtensions {
2020-05-21 17:21:34 +02:00
/// <summary>
/// Draws a nine patch area using the given sprite batch
/// </summary>
/// <param name="batch">The batch to draw with</param>
/// <param name="texture">The nine patch to draw</param>
/// <param name="destinationRectangle">The area that should be covered by this nine patch</param>
/// <param name="color">The color to use</param>
/// <param name="rotation">The rotation</param>
/// <param name="origin">The origin position</param>
/// <param name="effects">The effects that the sprite should have</param>
/// <param name="layerDepth">The depth</param>
/// <param name="patchScale">The scale of each area of the nine patch</param>
2019-11-02 14:53:59 +01:00
public static void Draw ( this SpriteBatch batch , NinePatch texture , RectangleF destinationRectangle , Color color , float rotation , Vector2 origin , SpriteEffects effects , float layerDepth , float patchScale = 1 ) {
2021-07-03 01:44:39 +02:00
for ( var i = 0 ; i < texture . SourceRectangles . Length ; i + + ) {
var rect = texture . GetRectangleForIndex ( destinationRectangle , i , patchScale ) ;
2021-02-19 21:24:08 +01:00
if ( ! rect . IsEmpty ) {
2021-07-03 01:44:39 +02:00
var src = texture . SourceRectangles [ i ] ;
2021-02-19 21:24:08 +01:00
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 ) ) ;
2022-06-24 14:01:26 +02:00
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 ) ;
2021-02-19 21:24:08 +01:00
}
}
break ;
}
}
2019-08-09 15:15:22 +02:00
}
}
2020-05-21 17:21:34 +02:00
/// <inheritdoc cref="Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch,MLEM.Textures.NinePatch,MLEM.Misc.RectangleF,Microsoft.Xna.Framework.Color,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,float,float)"/>
2019-11-02 14:53:59 +01:00
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 ) ;
}
2020-05-21 17:21:34 +02:00
/// <inheritdoc cref="Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch,MLEM.Textures.NinePatch,MLEM.Misc.RectangleF,Microsoft.Xna.Framework.Color,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,float,float)"/>
2019-11-02 14:53:59 +01:00
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 ) ;
}
2020-05-21 17:21:34 +02:00
/// <inheritdoc cref="Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch,MLEM.Textures.NinePatch,MLEM.Misc.RectangleF,Microsoft.Xna.Framework.Color,float,Microsoft.Xna.Framework.Vector2,Microsoft.Xna.Framework.Graphics.SpriteEffects,float,float)"/>
2019-08-11 21:38:03 +02:00
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 ) ;
2019-08-09 15:15:22 +02:00
}
}
2022-06-17 18:23:47 +02:00
}