From 9a144d10dddab034426e13594c9dde11f4c536df Mon Sep 17 00:00:00 2001 From: MattN Date: Fri, 28 Mar 2025 19:26:47 +0000 Subject: [PATCH] [Renderers/SFML2] Added initial SFML2 support --- renderers/SFML2/clay_renderer_SFML2.cpp | 248 ++++++++++++++++++++++++ renderers/SFML2/clay_renderer_SFML2.hpp | 21 ++ 2 files changed, 269 insertions(+) create mode 100644 renderers/SFML2/clay_renderer_SFML2.cpp create mode 100644 renderers/SFML2/clay_renderer_SFML2.hpp diff --git a/renderers/SFML2/clay_renderer_SFML2.cpp b/renderers/SFML2/clay_renderer_SFML2.cpp new file mode 100644 index 0000000..4ea8144 --- /dev/null +++ b/renderers/SFML2/clay_renderer_SFML2.cpp @@ -0,0 +1,248 @@ +#include "clay_renderer_SFML2.hpp" + +// This implimentation is kind of nasty- we create an sf::Text with the desired properties and call +// SFML Text's own getLocalBounds function to measure it. Clay caches this, so it doesn't need to be +// the fastest thing in the world +Clay_Dimensions SFML_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) +{ + SFML_Renderer *renderer = (SFML_Renderer*)userData; + + sf::Font& font = renderer->fonts[config->fontId]; + char *chars = (char *)calloc(text.length + 1, 1); + memcpy(chars, text.chars, text.length); + int width = 0; + int height = 0; + + sf::Text t; + t.setFont(font); + t.setString(chars); + auto floatDimensions = t.getLocalBounds(); + width = floatDimensions.width; + height = floatDimensions.height; + + free(chars); + Clay_Dimensions dimensions; + dimensions.width = width; + dimensions.height = height; + return dimensions; +} + +static void SFML_RenderFillRoundedRect(SFML_Renderer* renderer, const sf::FloatRect rect, const float cornerRadius, const Clay_Color _color) { + const sf::Color colour(_color.r, _color.g, _color.b, _color.a); + + + sf::CircleShape circ; + circ.setFillColor(colour); + circ.setRadius(cornerRadius); + circ.setOrigin(cornerRadius, cornerRadius); + + circ.setPosition(sf::Vector2f(rect.left, rect.top) + sf::Vector2f{cornerRadius, cornerRadius}); + renderer->target->draw(circ); + circ.setPosition(sf::Vector2f(rect.left + rect.width, rect.top) + sf::Vector2f{cornerRadius * -1.0f, cornerRadius}); + renderer->target->draw(circ); + circ.setPosition(sf::Vector2f(rect.left, rect.top + rect.height) + sf::Vector2f{cornerRadius, cornerRadius * -1.0f}); + renderer->target->draw(circ); + circ.setPosition(sf::Vector2f(rect.left + rect.width, rect.top + rect.height) - sf::Vector2f{cornerRadius, cornerRadius}); + renderer->target->draw(circ); + + sf::RectangleShape r; + r.setFillColor(colour); + r.setSize(sf::Vector2f(rect.width, rect.height - cornerRadius * 2.0f)); + + r.setPosition(sf::Vector2f(rect.left, rect.top + cornerRadius)); + renderer->target->draw(r); + r.setSize(sf::Vector2f(rect.width - cornerRadius * 2.0f, rect.height)); + r.setPosition(rect.left + cornerRadius, rect.top); + renderer->target->draw(r); +} + +static void SFML_RenderFillRect(SFML_Renderer* renderer, sf::FloatRect boundingBox, const Clay_Color color) { + sf::RectangleShape shape; + shape.setSize(sf::Vector2f(boundingBox.width, boundingBox.height)); + shape.setPosition(sf::Vector2f(boundingBox.left, boundingBox.top)); + shape.setFillColor(sf::Color(color.r, color.g, color.b, color.a)); + renderer->target->draw(shape); +} + +#define M_PI 3.141592 + +static void SFML_RenderCornerBorder(sf::RenderTarget& target, sf::FloatRect boundingBox, Clay_CornerRadius cornerRadii, + Clay_BorderWidth borderWidths, Clay_Color color, int cornerIndex) { + // Unfinished. I can't quite grok it +} + +void Clay_SFML_Render(SFML_Renderer *renderer, Clay_RenderCommandArray renderCommands) +{ + // If we deleted them last frame it could corrupt the displayed image + for(auto* tex: renderer->oldRenderTextures) + delete tex; + renderer->oldRenderTextures.clear(); + + for (uint32_t i = 0; i < renderCommands.length; i++) + { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); + Clay_BoundingBox boundingBox = renderCommand->boundingBox; + switch (renderCommand->commandType) + { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; + Clay_Color color = config->backgroundColor; + sf::FloatRect rect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); + if (config->cornerRadius.topLeft > 0 && false) { + SFML_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, color); + } + else { + SFML_RenderFillRect(renderer, rect, color); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextRenderData *textData = &renderCommand->renderData.text; + char *cloned = (char *)malloc(textData->stringContents.length + 1); + memcpy(cloned, textData->stringContents.chars, textData->stringContents.length); + cloned[textData->stringContents.length] = '\0'; + sf::Font& font = renderer->fonts[textData->fontId]; + sf::Color textColour(textData->textColor.r, textData->textColor.g, textData->textColor.b, textData->textColor.a); + sf::Text text(cloned, font, textData->fontSize); + text.setPosition(boundingBox.x, boundingBox.y); + text.setLetterSpacing(textData->letterSpacing); + text.setFillColor(textColour); + text.setStyle(sf::Text::Bold); + + renderer->target->draw(text); + free(cloned); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + // SFML 2 doesn't support scissor mode, so we will make a new render target. + // If a fragment is outside of the bounds of this target, it will be discarded, so + // we are simulating the effect + + renderer->clippedRects.emplace(boundingBox); + renderer->pushedTargets.emplace(renderer->target); + sf::RenderTexture* clipTex = new sf::RenderTexture; + clipTex->create(boundingBox.width, boundingBox.height); + clipTex->setView(sf::View(sf::FloatRect(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height))); + clipTex->clear(sf::Color(0, 0, 0, 0)); + renderer->target = clipTex; + + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + // See above- we will have created a new render texture to be our little + // scissor layer. We now need to render it, and then draw that texture to the + // correct position on the target beneath it. + + sf::RenderTexture* renderTex = (sf::RenderTexture*)renderer->target; + renderTex->display(); + + Clay_BoundingBox clip = renderer->clippedRects.front(); + renderer->clippedRects.pop(); + renderer->target = renderer->pushedTargets.front(); + renderer->pushedTargets.pop(); + + sf::RectangleShape shape(sf::Vector2f(clip.width, clip.height)); + shape.setPosition(clip.x, clip.y); + shape.setTexture(&renderTex->getTexture()); + shape.setTextureRect(sf::IntRect(0, 0, clip.width, clip.height)); + + renderer->target->draw(shape); + renderer->oldRenderTextures.emplace_back(renderTex); + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + // TODO, images currently don't scale nicely + Clay_ImageRenderData *config = &renderCommand->renderData.image; + + sf::Texture* texture = (sf::Texture*)config->imageData; + sf::RectangleShape rect(sf::Vector2f(boundingBox.width, boundingBox.height)); + rect.setPosition(boundingBox.x, boundingBox.y); + rect.setTexture(texture); + rect.setTextureRect(sf::IntRect(0, 0, texture->getSize().x, texture->getSize().y)); + + renderer->target->draw(rect); + + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderRenderData *config = &renderCommand->renderData.border; + // SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color)); + + if(boundingBox.width > 0 & boundingBox.height > 0){ + const float maxRadius = std::min(boundingBox.width, boundingBox.height) / 2.0f; + + if (config->width.left > 0) { + const float clampedRadiusTop = std::min((float)config->cornerRadius.topLeft, maxRadius); + const float clampedRadiusBottom = std::min((float)config->cornerRadius.bottomLeft, maxRadius); + sf::FloatRect rect = { + boundingBox.x, + boundingBox.y + clampedRadiusTop, + (float)config->width.left, + boundingBox.height - clampedRadiusTop - clampedRadiusBottom + }; + SFML_RenderFillRect(renderer, rect, config->color); + } + + if (config->width.right > 0) { + const float clampedRadiusTop = std::min((float)config->cornerRadius.topRight, maxRadius); + const float clampedRadiusBottom = std::min((float)config->cornerRadius.bottomRight, maxRadius); + sf::FloatRect rect = { + boundingBox.x + boundingBox.width - config->width.right, + boundingBox.y + clampedRadiusTop, + (float)config->width.right, + boundingBox.height - clampedRadiusTop - clampedRadiusBottom + }; + SFML_RenderFillRect(renderer, rect, config->color); + } + + if (config->width.top > 0) { + const float clampedRadiusLeft = std::min((float)config->cornerRadius.topLeft, maxRadius); + const float clampedRadiusRight = std::min((float)config->cornerRadius.topRight, maxRadius); + sf::FloatRect rect = { + boundingBox.x + clampedRadiusLeft, + boundingBox.y, + boundingBox.width - clampedRadiusLeft - clampedRadiusRight, + (float)config->width.top }; + SFML_RenderFillRect(renderer, rect, config->color); + } + + if (config->width.bottom > 0) { + const float clampedRadiusLeft = std::min((float)config->cornerRadius.bottomLeft, maxRadius); + const float clampedRadiusRight = std::min((float)config->cornerRadius.bottomRight, maxRadius); + sf::FloatRect rect = { + boundingBox.x + clampedRadiusLeft, + boundingBox.y + boundingBox.height - config->width.bottom, + boundingBox.width - clampedRadiusLeft - clampedRadiusRight, + (float)config->width.bottom + }; + SFML_RenderFillRect(renderer, rect, config->color); + } + + //corner index: 0->3 topLeft -> CW -> bottonLeft + sf::FloatRect floatBox; + if (config->width.top > 0 && config->cornerRadius.topLeft > 0) { + SFML_RenderCornerBorder(*renderer->target, floatBox, config->cornerRadius ,config->width , config->color, 1); + } + + if (config->width.top > 0 && config->cornerRadius.topRight> 0) { + SFML_RenderCornerBorder(*renderer->target, floatBox, config->cornerRadius, config->width, config->color, 2); + } + + if (config->width.bottom > 0 && config->cornerRadius.bottomLeft > 0) { + SFML_RenderCornerBorder(*renderer->target, floatBox, config->cornerRadius, config->width, config->color, 3); + } + + if (config->width.bottom > 0 && config->cornerRadius.bottomLeft > 0) { + SFML_RenderCornerBorder(*renderer->target, floatBox, config->cornerRadius, config->width, config->color, 4); + } + } + + break; + } + default: { + fprintf(stderr, "Error: unhandled render command: %d\n", renderCommand->commandType); + exit(1); + } + } + } +} diff --git a/renderers/SFML2/clay_renderer_SFML2.hpp b/renderers/SFML2/clay_renderer_SFML2.hpp new file mode 100644 index 0000000..51b23af --- /dev/null +++ b/renderers/SFML2/clay_renderer_SFML2.hpp @@ -0,0 +1,21 @@ +#include "../../clay.h" +#include +#include + +struct SFML_Renderer; + +Clay_Dimensions SFML_MeasureText(Clay_StringSlice text, Clay_TextElementConfig* config, void* userData); + +void Clay_SFML_Render(SFML_Renderer* renderer, Clay_RenderCommandArray renderCommands); + + +struct SFML_Renderer{ + std::vector fonts; + sf::RenderTarget* target; + + friend void Clay_SFML_Render(SFML_Renderer* renderer, Clay_RenderCommandArray renderCommands); +private: + std::queue pushedTargets; + std::queue clippedRects; + std::vector oldRenderTextures; +};