diff --git a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs
index f0c194eef..8174a40a6 100644
--- a/Source/Editor/CustomEditors/Editors/IBrushEditor.cs
+++ b/Source/Editor/CustomEditors/Editors/IBrushEditor.cs
@@ -22,6 +22,7 @@ namespace FlaxEditor.CustomEditors.Editors
new OptionType("Material", typeof(MaterialBrush)),
new OptionType("Solid Color", typeof(SolidColorBrush)),
new OptionType("Linear Gradient", typeof(LinearGradientBrush)),
+ new OptionType("Texture 9-Slicing", typeof(Texture9SlicingBrush)),
};
}
}
diff --git a/Source/Engine/Core/Math/Vector4.cs b/Source/Engine/Core/Math/Vector4.cs
index 2a62df067..6d1521e85 100644
--- a/Source/Engine/Core/Math/Vector4.cs
+++ b/Source/Engine/Core/Math/Vector4.cs
@@ -153,6 +153,19 @@ namespace FlaxEngine
W = w;
}
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// A vector containing the values with which to initialize the X and Y components.
+ /// A vector containing the values with which to initialize the Z and W components.
+ public Vector4(Vector2 xy, Vector2 zw)
+ {
+ X = xy.X;
+ Y = xy.Y;
+ Z = zw.X;
+ W = zw.Y;
+ }
+
///
/// Initializes a new instance of the struct.
///
diff --git a/Source/Engine/Render2D/Render2D.cpp b/Source/Engine/Render2D/Render2D.cpp
index a7602dce1..281b05ebc 100644
--- a/Source/Engine/Render2D/Render2D.cpp
+++ b/Source/Engine/Render2D/Render2D.cpp
@@ -25,19 +25,14 @@
#include "Engine/Engine/EngineService.h"
#if USE_EDITOR
-
-// TODO: maybe we could also provide some stack trace for that?
#define RENDER2D_CHECK_RENDERING_STATE \
if (!Render2D::IsRendering()) \
{ \
LOG(Error, "Calling Render2D is only valid during rendering."); \
return; \
}
-
#else
-
#define RENDER2D_CHECK_RENDERING_STATE
-
#endif
#if USE_EDITOR
@@ -353,6 +348,57 @@ FORCE_INLINE void WriteRect(const Rectangle& rect, const Color& color)
WriteRect(rect, color, Vector2::Zero, Vector2::One);
}
+void Write9SlicingRect(const Rectangle& rect, const Color& color, const Vector4& border, const Vector4& borderUVs)
+{
+ const Rectangle upperLeft(rect.Location.X, rect.Location.Y, border.X, border.Z);
+ const Rectangle upperRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y, border.Y, border.Z);
+ const Rectangle bottomLeft(rect.Location.X, rect.Location.Y + rect.Size.Y - border.W, border.X, border.W);
+ const Rectangle bottomRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y + rect.Size.Y - border.W, border.Y, border.W);
+
+ const Vector2 upperLeftUV(borderUVs.X, borderUVs.Z);
+ const Vector2 upperRightUV(1.0f - borderUVs.Y, borderUVs.Z);
+ const Vector2 bottomLeftUV(borderUVs.X, 1.0f - borderUVs.W);
+ const Vector2 bottomRightUV(1.0f - borderUVs.Y, 1.0f - borderUVs.W);
+
+ WriteRect(upperLeft, color, Vector2::Zero, upperLeftUV);
+ WriteRect(upperRight, color, Vector2(upperRightUV.X, 0), Vector2(1, upperLeftUV.Y));
+ WriteRect(bottomLeft, color, Vector2(0, bottomLeftUV.Y), Vector2(bottomLeftUV.X, 1));
+ WriteRect(bottomRight, color, bottomRightUV, Vector2::One);
+
+ WriteRect(Rectangle(upperLeft.GetUpperRight(), upperRight.GetBottomLeft() - upperLeft.GetUpperRight()), color, Vector2(upperLeftUV.X, 0), upperRightUV);
+ WriteRect(Rectangle(upperLeft.GetBottomLeft(), bottomLeft.GetUpperRight() - upperLeft.GetBottomLeft()), color, Vector2(0, upperLeftUV.Y), bottomLeftUV);
+ WriteRect(Rectangle(bottomLeft.GetUpperRight(), bottomRight.GetBottomLeft() - bottomLeft.GetUpperRight()), color, bottomLeftUV, Vector2(bottomRightUV.X, 1));
+ WriteRect(Rectangle(upperRight.GetBottomLeft(), bottomRight.GetUpperRight() - upperRight.GetBottomLeft()), color, upperRightUV, Vector2(1, bottomRightUV.Y));
+
+ WriteRect(Rectangle(upperLeft.GetBottomRight(), bottomRight.GetUpperLeft() - upperLeft.GetBottomRight()), color, upperRightUV, bottomRightUV);
+}
+
+void Write9SlicingRect(const Rectangle& rect, const Color& color, const Vector4& border, const Vector4& borderUVs, const Vector2& uvLocation, const Vector2& uvSize)
+{
+ const Rectangle upperLeft(rect.Location.X, rect.Location.Y, border.X, border.Z);
+ const Rectangle upperRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y, border.Y, border.Z);
+ const Rectangle bottomLeft(rect.Location.X, rect.Location.Y + rect.Size.Y - border.W, border.X, border.W);
+ const Rectangle bottomRight(rect.Location.X + rect.Size.X - border.Y, rect.Location.Y + rect.Size.Y - border.W, border.Y, border.W);
+
+ const Vector2 upperLeftUV = Vector2(borderUVs.X, borderUVs.Z) * uvSize + uvLocation;
+ const Vector2 upperRightUV = Vector2(1.0f - borderUVs.Y, borderUVs.Z) * uvSize + uvLocation;
+ const Vector2 bottomLeftUV = Vector2(borderUVs.X, 1.0f - borderUVs.W) * uvSize + uvLocation;
+ const Vector2 bottomRightUV = Vector2(1.0f - borderUVs.Y, 1.0f - borderUVs.W) * uvSize + uvLocation;
+ const Vector2 uvEnd = uvLocation + uvSize;
+
+ WriteRect(upperLeft, color, uvLocation, upperLeftUV);
+ WriteRect(upperRight, color, Vector2(upperRightUV.X, uvLocation.Y), Vector2(uvEnd.X, upperLeftUV.Y));
+ WriteRect(bottomLeft, color, Vector2(uvLocation.X, bottomLeftUV.Y), Vector2(bottomLeftUV.X, uvEnd.Y));
+ WriteRect(bottomRight, color, bottomRightUV, uvEnd);
+
+ WriteRect(Rectangle(upperLeft.GetUpperRight(), upperRight.GetBottomLeft() - upperLeft.GetUpperRight()), color, Vector2(upperLeftUV.X, uvLocation.Y), upperRightUV);
+ WriteRect(Rectangle(upperLeft.GetBottomLeft(), bottomLeft.GetUpperRight() - upperLeft.GetBottomLeft()), color, Vector2(uvLocation.X, upperLeftUV.Y), bottomLeftUV);
+ WriteRect(Rectangle(bottomLeft.GetUpperRight(), bottomRight.GetBottomLeft() - bottomLeft.GetUpperRight()), color, bottomLeftUV, Vector2(bottomRightUV.X, uvEnd.Y));
+ WriteRect(Rectangle(upperRight.GetBottomLeft(), bottomRight.GetUpperRight() - upperRight.GetBottomLeft()), color, upperRightUV, Vector2(uvEnd.X, bottomRightUV.Y));
+
+ WriteRect(Rectangle(upperLeft.GetBottomRight(), bottomRight.GetUpperLeft() - upperLeft.GetBottomRight()), color, upperRightUV, bottomRightUV);
+}
+
typedef bool (*CanDrawCallCallback)(const Render2DDrawCall&, const Render2DDrawCall&);
bool CanDrawCallCallbackTrue(const Render2DDrawCall& d1, const Render2DDrawCall& d2)
@@ -1548,7 +1594,7 @@ void Render2D::DrawSprite(const SpriteHandle& spriteHandle, const Rectangle& rec
drawCall.Type = DrawCallType::FillTexture;
drawCall.StartIB = IBIndex;
drawCall.CountIB = 6;
- drawCall.AsTexture.Ptr = spriteHandle.GetAtlasTexture();
+ drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight());
}
@@ -1575,10 +1621,66 @@ void Render2D::DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle
drawCall.Type = DrawCallType::FillTexturePoint;
drawCall.StartIB = IBIndex;
drawCall.CountIB = 6;
- drawCall.AsTexture.Ptr = spriteHandle.GetAtlasTexture();
+ drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
WriteRect(rect, color, sprite->Area.GetUpperLeft(), sprite->Area.GetBottomRight());
}
+void Render2D::Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ Render2DDrawCall drawCall;
+ drawCall.Type = DrawCallType::FillTexture;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = 6 * 9;
+ drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr;
+ DrawCalls.Add(drawCall);
+ Write9SlicingRect(rect, color, border, borderUVs);
+}
+
+void Render2D::Draw9SlicingTexturePoint(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+
+ Render2DDrawCall drawCall;
+ drawCall.Type = DrawCallType::FillTexturePoint;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = 6 * 9;
+ drawCall.AsTexture.Ptr = t ? t->GetTexture() : nullptr;
+ DrawCalls.Add(drawCall);
+ Write9SlicingRect(rect, color, border, borderUVs);
+}
+
+void Render2D::Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+ if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip())
+ return;
+
+ Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index);
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = DrawCallType::FillTexture;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = 6 * 9;
+ drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
+ Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size);
+}
+
+void Render2D::Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color)
+{
+ RENDER2D_CHECK_RENDERING_STATE;
+ if (spriteHandle.Index == INVALID_INDEX || !spriteHandle.Atlas || !spriteHandle.Atlas->GetTexture()->HasResidentMip())
+ return;
+
+ Sprite* sprite = &spriteHandle.Atlas->Sprites.At(spriteHandle.Index);
+ Render2DDrawCall& drawCall = DrawCalls.AddOne();
+ drawCall.Type = DrawCallType::FillTexturePoint;
+ drawCall.StartIB = IBIndex;
+ drawCall.CountIB = 6 * 9;
+ drawCall.AsTexture.Ptr = spriteHandle.Atlas->GetTexture();
+ Write9SlicingRect(rect, color, border, borderUVs, sprite->Area.Location, sprite->Area.Size);
+}
+
void Render2D::DrawCustom(GPUTexture* t, const Rectangle& rect, GPUPipelineState* ps, const Color& color)
{
RENDER2D_CHECK_RENDERING_STATE;
diff --git a/Source/Engine/Render2D/Render2D.h b/Source/Engine/Render2D/Render2D.h
index 36a5bcfa2..f1c9c816b 100644
--- a/Source/Engine/Render2D/Render2D.h
+++ b/Source/Engine/Render2D/Render2D.h
@@ -27,7 +27,6 @@ class TextureBase;
API_CLASS(Static) class FLAXENGINE_API Render2D
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(Render2D);
-public:
///
/// The rendering features and options flags.
@@ -50,13 +49,11 @@ public:
///
/// Checks if interface is during rendering phrase (Draw calls may be performed without failing).
///
- /// True if is during rendering, otherwise false/
static bool IsRendering();
///
/// Gets the current rendering viewport.
///
- /// The viewport
static const Viewport& GetViewport();
///
@@ -64,8 +61,6 @@ public:
///
API_FIELD() static RenderingFeatures Features;
-public:
-
///
/// Called when frame rendering begins by the graphics device.
///
@@ -302,6 +297,46 @@ public:
/// The color to multiply all texture pixels.
API_FUNCTION() static void DrawSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Color& color = Color::White);
+ ///
+ /// Draws the texture using 9-slicing.
+ ///
+ /// The texture to draw.
+ /// The rectangle to draw.
+ /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom).
+ /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom).
+ /// The color to multiply all texture pixels.
+ API_FUNCTION() static void Draw9SlicingTexture(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White);
+
+ ///
+ /// Draws the texture using 9-slicing (uses point sampler).
+ ///
+ /// The texture to draw.
+ /// The rectangle to draw.
+ /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom).
+ /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom).
+ /// The color to multiply all texture pixels.
+ API_FUNCTION() static void Draw9SlicingTexturePoint(TextureBase* t, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White);
+
+ ///
+ /// Draws a sprite using 9-slicing.
+ ///
+ /// The sprite to draw.
+ /// The rectangle to draw.
+ /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom).
+ /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom).
+ /// The color to multiply all texture pixels.
+ API_FUNCTION() static void Draw9SlicingSprite(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White);
+
+ ///
+ /// Draws a sprite using 9-slicing (uses point sampler).
+ ///
+ /// The sprite to draw.
+ /// The rectangle to draw.
+ /// The borders for 9-slicing (inside rectangle, ordered: left, right, top, bottom).
+ /// The borders UVs for 9-slicing (inside rectangle UVs, ordered: left, right, top, bottom).
+ /// The color to multiply all texture pixels.
+ API_FUNCTION() static void Draw9SlicingSpritePoint(const SpriteHandle& spriteHandle, const Rectangle& rect, const Vector4& border, const Vector4& borderUVs, const Color& color = Color::White);
+
///
/// Performs custom rendering.
///
diff --git a/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs b/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs
index 05c0bb2e2..eb426eac8 100644
--- a/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs
+++ b/Source/Engine/UI/GUI/Brushes/SpriteBrush.cs
@@ -48,4 +48,76 @@ namespace FlaxEngine.GUI
Render2D.DrawSprite(Sprite, rect, color);
}
}
+
+ ///
+ /// Implementation of for using 9-slicing.
+ ///
+ ///
+ public sealed class Sprite9SlicingBrush : IBrush
+ {
+ ///
+ /// The sprite.
+ ///
+ [ExpandGroups, EditorOrder(0), Tooltip("The sprite.")]
+ public SpriteHandle Sprite;
+
+ ///
+ /// The texture sampling filter mode.
+ ///
+ [ExpandGroups, EditorOrder(1), Tooltip("The texture sampling filter mode.")]
+ public BrushFilter Filter = BrushFilter.Linear;
+
+ ///
+ /// The border size.
+ ///
+ [ExpandGroups, EditorOrder(2), Limit(0), Tooltip("The border size.")]
+ public float BorderSize = 10.0f;
+
+ ///
+ /// The sprite borders (in texture space, range 0-1).
+ ///
+ [ExpandGroups, EditorOrder(3), Limit(0, 1), Tooltip("The sprite borders (in texture space, range 0-1).")]
+ public Margin Border = new Margin(0.1f);
+
+#if FLAX_EDITOR
+ ///
+ /// Displays borders (editor only).
+ ///
+ [NoSerialize, EditorOrder(4), Tooltip("Displays borders (editor only).")]
+ public bool ShowBorders;
+#endif
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Sprite9SlicingBrush()
+ {
+ }
+
+ ///
+ public Vector2 Size => Sprite.IsValid ? Sprite.Size : Vector2.Zero;
+
+ ///
+ public unsafe void Draw(Rectangle rect, Color color)
+ {
+ var border = Border;
+ var borderUV = *(Vector4*)&border;
+ var borderSize = borderUV * new Vector4(BorderSize, BorderSize, BorderSize, BorderSize);
+ if (Filter == BrushFilter.Point)
+ Render2D.Draw9SlicingSpritePoint(Sprite, rect, borderSize, borderUV, color);
+ else
+ Render2D.Draw9SlicingSprite(Sprite, rect, borderSize, borderUV, color);
+#if FLAX_EDITOR
+ if (ShowBorders)
+ {
+ var bordersRect = rect;
+ bordersRect.Location.X += borderSize.X;
+ bordersRect.Location.Y += borderSize.Z;
+ bordersRect.Size.X -= borderSize.X + borderSize.Y;
+ bordersRect.Size.Y -= borderSize.Z + borderSize.W;
+ Render2D.DrawRectangle(bordersRect, Color.YellowGreen, 2.0f);
+ }
+#endif
+ }
+ }
}
diff --git a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs
index 1f772e0a6..7e63e75f2 100644
--- a/Source/Engine/UI/GUI/Brushes/TextureBrush.cs
+++ b/Source/Engine/UI/GUI/Brushes/TextureBrush.cs
@@ -1,7 +1,5 @@
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
-using System;
-
namespace FlaxEngine.GUI
{
///
@@ -50,4 +48,87 @@ namespace FlaxEngine.GUI
Render2D.DrawTexture(Texture, rect, color);
}
}
+
+ ///
+ /// Implementation of for using 9-slicing.
+ ///
+ ///
+ public sealed class Texture9SlicingBrush : IBrush
+ {
+ ///
+ /// The texture.
+ ///
+ [ExpandGroups, EditorOrder(0), Tooltip("The texture asset.")]
+ public Texture Texture;
+
+ ///
+ /// The texture sampling filter mode.
+ ///
+ [ExpandGroups, EditorOrder(1), Tooltip("The texture sampling filter mode.")]
+ public BrushFilter Filter = BrushFilter.Linear;
+
+ ///
+ /// The border size.
+ ///
+ [ExpandGroups, EditorOrder(2), Limit(0), Tooltip("The border size.")]
+ public float BorderSize = 10.0f;
+
+ ///
+ /// The texture borders (in texture space, range 0-1).
+ ///
+ [ExpandGroups, EditorOrder(3), Limit(0, 1), Tooltip("The texture borders (in texture space, range 0-1).")]
+ public Margin Border = new Margin(0.1f);
+
+#if FLAX_EDITOR
+ ///
+ /// Displays borders (editor only).
+ ///
+ [NoSerialize, EditorOrder(4), Tooltip("Displays borders (editor only).")]
+ public bool ShowBorders;
+#endif
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public Texture9SlicingBrush()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The texture.
+ public Texture9SlicingBrush(Texture texture)
+ {
+ Texture = texture;
+ }
+
+ ///
+ public Vector2 Size => Texture?.Size ?? Vector2.Zero;
+
+ ///
+ public unsafe void Draw(Rectangle rect, Color color)
+ {
+ if (Texture == null)
+ return;
+ var border = Border;
+ var borderUV = *(Vector4*)&border;
+ var borderSize = borderUV * new Vector4(BorderSize, BorderSize, BorderSize, BorderSize);
+ if (Filter == BrushFilter.Point)
+ Render2D.Draw9SlicingTexturePoint(Texture, rect, borderSize, borderUV, color);
+ else
+ Render2D.Draw9SlicingTexture(Texture, rect, borderSize, borderUV, color);
+#if FLAX_EDITOR
+ if (ShowBorders)
+ {
+ var bordersRect = rect;
+ bordersRect.Location.X += borderSize.X;
+ bordersRect.Location.Y += borderSize.Z;
+ bordersRect.Size.X -= borderSize.X + borderSize.Y;
+ bordersRect.Size.Y -= borderSize.Z + borderSize.W;
+ Render2D.DrawRectangle(bordersRect, Color.YellowGreen, 2.0f);
+ }
+#endif
+ }
+ }
}