You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
551 lines
20 KiB
C#
551 lines
20 KiB
C#
using SFML.Graphics;
|
|
using SFML.System;
|
|
using SFML.Window;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace Otter {
|
|
/// <summary>
|
|
/// Graphic type used to render a panel made up of 9 slices of an image. Handy for rendering panels
|
|
/// with border graphics.
|
|
/// </summary>
|
|
public class NineSlice : Graphic {
|
|
|
|
#region Static Methods
|
|
|
|
/// <summary>
|
|
/// Register a fill rectangle for a specific asset. Useful for not having to set the same fill rect
|
|
/// every time you use a NineSlice for a specific image.
|
|
/// </summary>
|
|
/// <param name="key">The asset path.</param>
|
|
/// <param name="x1">Fill rect x1.</param>
|
|
/// <param name="y1">Fill Rect y1</param>
|
|
/// <param name="x2">Fill rect x2.</param>
|
|
/// <param name="y2">Fill Rect y2</param>
|
|
public static void SetFillRect(string key, int x1, int y1, int x2, int y2) {
|
|
var rect = new Rectangle() {
|
|
X = x1,
|
|
Y = y1,
|
|
Width = x2 - x1,
|
|
Height = y2 - y1
|
|
};
|
|
|
|
if (!fillRects.ContainsKey(key)) {
|
|
fillRects.Add(key, rect);
|
|
}
|
|
fillRects[key] = rect;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the FillRect for a NineSlice globally.
|
|
/// </summary>
|
|
/// <param name="keys">The source Texture of the NineSlice. (File path or name on Atlas both work.)</param>
|
|
/// <param name="x1">The left corner of the fill rectangle.</param>
|
|
/// <param name="y1">The top corner of the fill rectangle.</param>
|
|
/// <param name="x2">The right corner of the fill rectangle.</param>
|
|
/// <param name="y2">The bottom corner of the fill rectangle.</param>
|
|
public static void SetFillRect(string[] keys, int x1, int y1, int x2, int y2) {
|
|
foreach (var k in keys) {
|
|
SetFillRect(k, x1, y1, x2, y2);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice using padding values.
|
|
/// </summary>
|
|
/// <param name="top">How far from the top of the texture to begin the rectangle.</param>
|
|
/// <param name="right">How far from the right of the texture to end the rectangle.</param>
|
|
/// <param name="bottom">How far from the bottom of the texture to end the rectangle.</param>
|
|
/// <param name="left">How far from the left of the texture to begin the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public static void SetBorderPadding(string key, int top, int right, int bottom, int left) {
|
|
var texture = new Texture(key); // Have to load a texture here but I think that's okay?
|
|
|
|
var x1 = left;
|
|
var y1 = top;
|
|
var x2 = texture.Width - right;
|
|
var y2 = texture.Height - bottom;
|
|
SetFillRect(key, x1, y1, x2, y2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice using padding values.
|
|
/// </summary>
|
|
/// <param name="padding">How far from the border of the texture to make the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public static void SetBorderPadding(string key, int padding) {
|
|
SetBorderPadding(key, padding, padding, padding, padding);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice using padding values.
|
|
/// </summary>
|
|
/// <param name="horizontal">How far horizontally from the border of the texture to make the rectangle.</param>
|
|
/// <param name="vertical">How far horizontally from the border of the texture to make the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public static void SetBorderPadding(string key, int horizontal, int vertical) {
|
|
SetBorderPadding(key, horizontal, vertical, horizontal, vertical);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Fields
|
|
|
|
int
|
|
tWidth,
|
|
tHeight;
|
|
|
|
static Dictionary<string, Rectangle> fillRects = new Dictionary<string, Rectangle>();
|
|
|
|
int sliceX1, sliceX2, sliceY1, sliceY2;
|
|
|
|
PanelType paneltype;
|
|
|
|
bool usePanelClip;
|
|
Rectangle panelClip;
|
|
|
|
float panelScaleX, panelScaleY;
|
|
|
|
#endregion
|
|
|
|
#region Public Fields
|
|
|
|
/// <summary>
|
|
/// Draw the panel from the top left corner of the middle slice.
|
|
/// </summary>
|
|
public bool UseInsideOrigin;
|
|
|
|
/// <summary>
|
|
/// When using PanelType.Tiled snap the width to increments of the tile width.
|
|
/// </summary>
|
|
public bool SnapWidth;
|
|
|
|
/// <summary>
|
|
/// When using PanelType.Tiled snap the height to increments of the tile height.
|
|
/// </summary>
|
|
public bool SnapHeight;
|
|
|
|
/// <summary>
|
|
/// Determines how the size of the panel will be adjusted when setting PanelWidth and PanelHeight.
|
|
/// If set to All, the entire panel will be the width and height.
|
|
/// If set to Inside, the inside of the panel will be the width and height.
|
|
/// </summary>
|
|
public PanelSizeMode PanelSizeMode = PanelSizeMode.All;
|
|
|
|
#endregion
|
|
|
|
#region Public Properties
|
|
|
|
/// <summary>
|
|
/// The type of panel to use for the NineSlice.
|
|
/// </summary>
|
|
public PanelType PanelType {
|
|
get {
|
|
return paneltype;
|
|
}
|
|
set {
|
|
paneltype = value;
|
|
NeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Render the NineSlice through a clipping rectangle.
|
|
/// </summary>
|
|
public Rectangle ClippingRegion {
|
|
set {
|
|
panelClip = value;
|
|
usePanelClip = true;
|
|
NeedsUpdate = true;
|
|
}
|
|
get {
|
|
return panelClip;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the ClippingRegion is used or not.
|
|
/// </summary>
|
|
public bool UsePanelClip {
|
|
set { usePanelClip = value; }
|
|
get { return usePanelClip; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the panel width of the NineSlice. This will update and rerender it.
|
|
/// </summary>
|
|
public int PanelWidth {
|
|
set {
|
|
if (PanelSizeMode == PanelSizeMode.Inside) {
|
|
value += sliceX1 + tWidth - sliceX2;
|
|
}
|
|
|
|
if (SnapWidth) {
|
|
Width = GetSnapWidth(value);
|
|
}
|
|
else {
|
|
Width = value;
|
|
}
|
|
if (Width < 1) Width = 1;
|
|
NeedsUpdate = true;
|
|
}
|
|
get {
|
|
if (PanelSizeMode == PanelSizeMode.Inside) {
|
|
return Width - sliceX1 + tWidth - sliceX2;
|
|
}
|
|
|
|
return Width;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the panel height of the NineSlice. This will update and rerender it.
|
|
/// </summary>
|
|
public int PanelHeight {
|
|
set {
|
|
if (PanelSizeMode == PanelSizeMode.Inside) {
|
|
value += sliceY1 + tHeight - sliceY2;
|
|
}
|
|
|
|
if (SnapHeight) {
|
|
Height = GetSnapHeight(value);
|
|
}
|
|
else {
|
|
Height = value;
|
|
}
|
|
if (Height < 1) Height = 1;
|
|
NeedsUpdate = true;
|
|
}
|
|
get {
|
|
if (PanelSizeMode == PanelSizeMode.Inside) {
|
|
return Height - sliceY1 + tHeight - sliceY2;
|
|
}
|
|
|
|
return Height;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
/// <summary>
|
|
/// Create a new NineSlice with a file path to a Texture.
|
|
/// </summary>
|
|
/// <param name="source">The file path to the Texture.</param>
|
|
/// <param name="width">The width of the NineSlice panel.</param>
|
|
/// <param name="height">The height of the NineSlice panel.</param>
|
|
/// <param name="fillRect">The rectangle to determine the stretched areas.</param>
|
|
public NineSlice(string source, int width = 0, int height = 0, Rectangle? fillRect = null)
|
|
: base() {
|
|
SetTexture(new Texture(source));
|
|
Initialize(source, width, height, fillRect);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new NineSlice with a Texture.
|
|
/// </summary>
|
|
/// <param name="texture">The Texture to use.</param>
|
|
/// <param name="width">The width of the NineSlice panel.</param>
|
|
/// <param name="height">The height of the NineSlice panel.</param>
|
|
/// <param name="fillRect">The rectangle to determine the stretched areas.</param>
|
|
public NineSlice(Texture texture, int width, int height, Rectangle? fillRect = null)
|
|
: base() {
|
|
SetTexture(texture);
|
|
Initialize(texture.Source, width, height, fillRect);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new NineSlice with an AtlasTexture.
|
|
/// </summary>
|
|
/// <param name="texture">The AtlasTexture to use.</param>
|
|
/// <param name="width">The width of the NineSlice panel.</param>
|
|
/// <param name="height">The height of the NineSlice panel.</param>
|
|
/// <param name="fillRect">The rectangle to determine the stretched areas.</param>
|
|
public NineSlice(AtlasTexture texture, int width, int height, Rectangle? fillRect = null)
|
|
: base() {
|
|
SetTexture(texture);
|
|
Initialize(texture.Name + ".png", width, height, fillRect);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
void Initialize(string source, int width, int height, Rectangle? fillRect) {
|
|
tWidth = TextureRegion.Width;
|
|
tHeight = TextureRegion.Height;
|
|
|
|
if (width == 0 || height == 0) {
|
|
Width = tWidth;
|
|
Height = tHeight;
|
|
}
|
|
else {
|
|
Width = width;
|
|
Height = height;
|
|
}
|
|
|
|
sliceX1 = tWidth / 3;
|
|
sliceX2 = tWidth / 3 * 2;
|
|
sliceY1 = tHeight / 3;
|
|
sliceY2 = tHeight / 3 * 2;
|
|
|
|
if (fillRect == null) {
|
|
if (fillRects.ContainsKey(source)) {
|
|
var rect = fillRects[source];
|
|
SetFillRect(rect.Left, rect.Top, rect.Right, rect.Bottom);
|
|
}
|
|
}
|
|
else {
|
|
var rect = fillRect.Value;
|
|
SetFillRect(rect.Left, rect.Top, rect.Right, rect.Bottom);
|
|
}
|
|
}
|
|
|
|
void DrawQuad(VertexArray v, float x1, float y1, float x2, float y2, float u1, float v1, float u2, float v2) {
|
|
float cx1 = x1, cx2 = x2, cy1 = y1, cy2 = y2;
|
|
float cu1 = u1, cu2 = u2, cv1 = v1, cv2 = v2;
|
|
|
|
if (usePanelClip) {
|
|
cx1 = Util.Clamp(x1, ClippingRegion.Left, ClippingRegion.Right);
|
|
cu1 = Util.ScaleClamp(cx1, x1, x2, u1, u2);
|
|
|
|
cx2 = Util.Clamp(x2, ClippingRegion.Left, ClippingRegion.Right);
|
|
cu2 = Util.ScaleClamp(cx2, x1, x2, u1, u2);
|
|
|
|
cy1 = Util.Clamp(y1, ClippingRegion.Top, ClippingRegion.Bottom);
|
|
cv1 = Util.ScaleClamp(cy1, y1, y2, v1, v2);
|
|
|
|
cy2 = Util.Clamp(y2, ClippingRegion.Top, ClippingRegion.Bottom);
|
|
cv2 = Util.ScaleClamp(cy2, y1, y2, v1, v2);
|
|
}
|
|
|
|
v.Append(cx1, cy1, Color, cu1, cv1);
|
|
v.Append(cx2, cy1, Color, cu2, cv1);
|
|
v.Append(cx2, cy2, Color, cu2, cv2);
|
|
v.Append(cx1, cy2, Color, cu1, cv2);
|
|
}
|
|
|
|
protected override void UpdateDrawable() {
|
|
var minWidth = sliceX1 + tWidth - sliceX2;
|
|
panelScaleX = (float)Width / (float)minWidth;
|
|
if (panelScaleX > 1) panelScaleX = 1;
|
|
|
|
var minHeight = sliceY1 + tHeight - sliceY2;
|
|
panelScaleY = (float)Height / (float)minHeight;
|
|
if (panelScaleY > 1) panelScaleY = 1;
|
|
|
|
var v = new VertexArray(PrimitiveType.Quads);
|
|
|
|
int x0, x1, x2, x3, y0, y1, y2, y3;
|
|
int u0, u1, u2, u3, v0, v1, v2, v3;
|
|
x0 = 0;
|
|
y0 = 0;
|
|
x1 = sliceX1;
|
|
y1 = sliceY1;
|
|
x2 = Width - (tWidth - sliceX2);
|
|
y2 = Height - (tHeight - sliceY2);
|
|
x3 = Width;
|
|
y3 = Height;
|
|
|
|
u0 = TextureLeft;
|
|
v0 = TextureTop;
|
|
u1 = TextureLeft + sliceX1;
|
|
v1 = TextureTop + sliceY1;
|
|
u2 = TextureLeft + sliceX2;
|
|
v2 = TextureTop + sliceY2;
|
|
u3 = TextureLeft + tWidth;
|
|
v3 = TextureTop + tHeight;
|
|
|
|
if (panelScaleX < 1) {
|
|
x1 = (int)Math.Round(x1 * panelScaleX);
|
|
x2 = x1;
|
|
}
|
|
if (panelScaleY < 1) {
|
|
y1 = (int)Math.Round(y1 * panelScaleY);
|
|
y2 = y1;
|
|
}
|
|
|
|
int tileX = sliceX2 - sliceX1;
|
|
int tileY = sliceY2 - sliceY1;
|
|
|
|
if (PanelType == PanelType.Stretch) {
|
|
//top
|
|
DrawQuad(v, x1, y0, x2, y1, u1, v0, u2, v1);
|
|
|
|
//left
|
|
DrawQuad(v, x0, y1, x1, y2, u0, v1, u1, v2);
|
|
|
|
//right
|
|
DrawQuad(v, x2, y1, x3, y2, u2, v1, u3, v2);
|
|
|
|
//bottom
|
|
DrawQuad(v, x1, y2, x2, y3, u1, v2, u2, v3);
|
|
|
|
//middle
|
|
DrawQuad(v, x1, y1, x2, y2, u1, v1, u2, v2);
|
|
}
|
|
else {
|
|
for (int xx = x1; xx < x2; xx += tileX) {
|
|
for (int yy = y1; yy < y2; yy += tileY) {
|
|
//middle
|
|
DrawQuad(v, xx, yy, xx + tileX, yy + tileY, u1, v1, u2, v2);
|
|
}
|
|
}
|
|
|
|
for (int yy = y1; yy < y2; yy += tileY) {
|
|
//left
|
|
DrawQuad(v, x0, yy, x1, yy + tileY, u0, v1, u1, v2);
|
|
|
|
//right
|
|
DrawQuad(v, x2, yy, x3, yy + tileY, u2, v1, u3, v2);
|
|
}
|
|
|
|
for (int xx = x1; xx < x2; xx += tileX) {
|
|
//top
|
|
DrawQuad(v, xx, y0, xx + tileX, y1, u1, v0, u2, v1);
|
|
|
|
//bottom
|
|
DrawQuad(v, xx, y2, xx + tileX, y3, u1, v2, u2, v3);
|
|
}
|
|
}
|
|
|
|
//top left
|
|
DrawQuad(v, x0, y0, x1, y1, u0, v0, u1, v1);
|
|
|
|
//top right
|
|
DrawQuad(v, x2, y0, x3, y1, u2, v0, u3, v1);
|
|
|
|
//bottom left
|
|
DrawQuad(v, x0, y2, x1, y3, u0, v2, u1, v3);
|
|
|
|
//bottom right
|
|
DrawQuad(v, x2, y2, x3, y3, u2, v2, u3, v3);
|
|
|
|
SFMLVertices = v;
|
|
}
|
|
|
|
void Append(VertexArray v, float x, float y, float tx, float ty) {
|
|
v.Append(new Vertex(new Vector2f(x, y), new Vector2f(tx, ty)));
|
|
}
|
|
|
|
int GetSnapWidth(int width) {
|
|
var sliceWidth = sliceX2 - sliceX1;
|
|
int snapWidth = Width - sliceWidth;
|
|
|
|
while (snapWidth < width) {
|
|
snapWidth += sliceWidth;
|
|
}
|
|
|
|
return snapWidth;
|
|
}
|
|
|
|
int GetSnapHeight(int height) {
|
|
var sliceHeight = sliceY2 - sliceY1;
|
|
int snapHeight = Width - sliceHeight;
|
|
|
|
while (snapHeight < height) {
|
|
snapHeight += sliceHeight;
|
|
}
|
|
|
|
return snapHeight;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice. This determines which areas are stretched or tiled when rendering the tiles.
|
|
/// </summary>
|
|
/// <param name="x1">The left corner of the rectangle.</param>
|
|
/// <param name="y1">The top corner of the rectangle.</param>
|
|
/// <param name="x2">The right corner of the rectangle.</param>
|
|
/// <param name="y2">The bottom corner of the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public NineSlice SetFillRect(int x1, int y1, int x2, int y2) {
|
|
sliceX1 = x1;
|
|
sliceX2 = x2;
|
|
sliceY1 = y1;
|
|
sliceY2 = y2;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the FillRect of the NineSlice. This determines which areas are stretched or tiled when rendering the tiles.
|
|
/// </summary>
|
|
/// <returns>The Rectangle of the FillRect.</returns>
|
|
public Rectangle GetFillRect() {
|
|
return new Rectangle(sliceX1, sliceY1, sliceX2 - sliceX1, sliceY2 - sliceY1);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice using padding values.
|
|
/// </summary>
|
|
/// <param name="top">How far from the top of the texture to begin the rectangle.</param>
|
|
/// <param name="right">How far from the right of the texture to end the rectangle.</param>
|
|
/// <param name="bottom">How far from the bottom of the texture to end the rectangle.</param>
|
|
/// <param name="left">How far from the left of the texture to begin the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public NineSlice SetBorderPadding(int top, int right, int bottom, int left) {
|
|
var x1 = left;
|
|
var y1 = top;
|
|
var x2 = Texture.Width - right;
|
|
var y2 = Texture.Height - bottom;
|
|
return SetFillRect(x1, y1, x2, y2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice using padding values.
|
|
/// </summary>
|
|
/// <param name="padding">How far from the border of the texture to make the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public NineSlice SetBorderPadding(int padding) {
|
|
return SetBorderPadding(padding, padding, padding, padding);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the FillRect of the NineSlice using padding values.
|
|
/// </summary>
|
|
/// <param name="horizontal">How far horizontally from the border of the texture to make the rectangle.</param>
|
|
/// <param name="vertical">How far horizontally from the border of the texture to make the rectangle.</param>
|
|
/// <returns>The NineSlice object.</returns>
|
|
public NineSlice SetBorderPadding(int horizontal, int vertical) {
|
|
return SetBorderPadding(horizontal, vertical, horizontal, vertical);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Draw the NineSlice.
|
|
/// </summary>
|
|
/// <param name="x">The X position offset.</param>
|
|
/// <param name="y">The Y position offset.</param>
|
|
public override void Render(float x = 0, float y = 0) {
|
|
float ox = 0, oy = 0;
|
|
|
|
if (UseInsideOrigin) {
|
|
ox = -sliceX1;
|
|
oy = -sliceY1;
|
|
}
|
|
|
|
base.Render(x + ox, y + oy);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
#region Enum
|
|
|
|
public enum PanelType {
|
|
Stretch,
|
|
Tile
|
|
}
|
|
|
|
public enum PanelSizeMode {
|
|
All,
|
|
Inside
|
|
}
|
|
|
|
#endregion
|
|
}
|