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 + } + } }