Compare commits

...

6 commits

Author SHA1 Message Date
Matthew Nagy 0305734abf
Merge 563b858483 into fd97d8179e 2025-10-28 18:36:53 +03:00
Daniel Mayovskiy fd97d8179e
[Renderers/termbox] fixed horizontal text culling bug (#525)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / check_changes (push) Has been cancelled
Odin Bindings Update / build (macos-latest) (push) Has been cancelled
Odin Bindings Update / build (ubuntu-latest) (push) Has been cancelled
Odin Bindings Update / commit (push) Has been cancelled
2025-10-23 12:58:39 +11:00
Daniel Mayovskiy 7216815536
Fixed termbox2 demo build, added scroll functionality (#523) 2025-10-23 12:57:11 +11:00
Thomas Anderson 83129995f7
[Examples/official-website] updated paths in build.sh 2025-10-23 12:56:20 +11:00
Matthew Nagy 563b858483 Added include guard 2025-04-30 23:51:48 +00:00
MattN 9a144d10dd [Renderers/SFML2] Added initial SFML2 support 2025-03-28 19:29:24 +00:00
6 changed files with 299 additions and 8 deletions

View file

@ -15,5 +15,5 @@ mkdir -p build/clay \
-Wl,--initial-memory=6553600 \ -Wl,--initial-memory=6553600 \
-o build/clay/index.wasm \ -o build/clay/index.wasm \
main.c \ main.c \
&& cp index.html build/clay/index.html && cp -r fonts/ build/clay/fonts \ && cp index.html build/index.html && cp -r fonts/ build/clay/fonts \
&& cp index.html build/clay/index.html && cp -r images/ build/clay/images && cp -r images/ build/clay/images

View file

@ -8,7 +8,7 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare( FetchContent_Declare(
termbox2 termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git" GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8" GIT_TAG "ffd159c2a6106dd5eef338a6702ad15d4d4aa809"
GIT_PROGRESS TRUE GIT_PROGRESS TRUE
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
) )
@ -17,7 +17,7 @@ FetchContent_MakeAvailable(termbox2)
FetchContent_Declare( FetchContent_Declare(
stb stb
GIT_REPOSITORY "https://github.com/nothings/stb.git" GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c" GIT_TAG "fede005abaf93d9d7f3a679d1999b2db341b360f"
GIT_PROGRESS TRUE GIT_PROGRESS TRUE
GIT_SHALLOW TRUE GIT_SHALLOW TRUE
) )

View file

@ -90,7 +90,7 @@ void component_text_pair(const char *key, const char *value)
void component_termbox_settings(void) void component_termbox_settings(void)
{ {
CLAY_AUTO_ID({ CLAY(CLAY_ID("Termbox Settings"), {
.floating = { .floating = {
.attachTo = CLAY_ATTACH_TO_PARENT, .attachTo = CLAY_ATTACH_TO_PARENT,
.zIndex = 1, .zIndex = 1,
@ -509,13 +509,18 @@ Clay_RenderCommandArray CreateLayout(clay_tb_image *image1, clay_tb_image *image
{ {
Clay_BeginLayout(); Clay_BeginLayout();
CLAY_AUTO_ID({ CLAY_AUTO_ID({
.clip = {
.vertical = false,
.horizontal = true,
.childOffset = Clay_GetScrollOffset(),
},
.layout = { .layout = {
.sizing = { .sizing = {
.width = CLAY_SIZING_GROW(), .width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW() .height = CLAY_SIZING_GROW()
}, },
.childAlignment = { .childAlignment = {
.x = CLAY_ALIGN_X_CENTER, .x = CLAY_ALIGN_X_LEFT,
.y = CLAY_ALIGN_Y_CENTER .y = CLAY_ALIGN_Y_CENTER
}, },
.childGap = 64 .childGap = 64
@ -714,12 +719,12 @@ void handle_termbox_events(void)
break; break;
} }
case TB_KEY_MOUSE_WHEEL_UP: { case TB_KEY_MOUSE_WHEEL_UP: {
Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() }; Clay_Vector2 scrollDelta = { 0.5 * Clay_Termbox_Cell_Width(), 0 };
Clay_UpdateScrollContainers(false, scrollDelta, 1); Clay_UpdateScrollContainers(false, scrollDelta, 1);
break; break;
} }
case TB_KEY_MOUSE_WHEEL_DOWN: { case TB_KEY_MOUSE_WHEEL_DOWN: {
Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() }; Clay_Vector2 scrollDelta = { -0.5 * Clay_Termbox_Cell_Width(), 0 };
Clay_UpdateScrollContainers(false, scrollDelta, 1); Clay_UpdateScrollContainers(false, scrollDelta, 1);
break; break;
} }

View file

@ -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);
}
}
}
}

View file

@ -0,0 +1,22 @@
#pragma once
#include "../../clay.h"
#include <SFML/Graphics.hpp>
#include <queue>
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<sf::Font> fonts;
sf::RenderTarget* target;
friend void Clay_SFML_Render(SFML_Renderer* renderer, Clay_RenderCommandArray renderCommands);
private:
std::queue<sf::RenderTarget*> pushedTargets;
std::queue<Clay_BoundingBox> clippedRects;
std::vector<sf::RenderTexture*> oldRenderTextures;
};

View file

@ -3,6 +3,8 @@
Copyright (c) 2025 Mivirl Copyright (c) 2025 Mivirl
altered by Godje (Sep 2025)
This software is provided 'as-is', without any express or implied warranty. This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from the In no event will the authors be held liable for any damages arising from the
use of this software. use of this software.
@ -1616,6 +1618,20 @@ void Clay_Termbox_Render(Clay_RenderCommandArray commands)
Clay_StringSlice *text = &render_data.stringContents; Clay_StringSlice *text = &render_data.stringContents;
int32_t i = 0; int32_t i = 0;
// culling text characters that are outside of the layout
int h_clip = 0 - cell_box.x;
while(h_clip > 0 && i < text->length){
uint32_t ch = ' ';
int codepoint_length = tb_utf8_char_to_unicode(&ch, text->chars + i);
if (0 > codepoint_length) {
clay_tb_assert(false, "Invalid utf8");
}
i += codepoint_length;
h_clip -= 1;
}
// printing the rest of the characters
for (int y = box_begin_y; y < box_end_y; ++y) { for (int y = box_begin_y; y < box_end_y; ++y) {
for (int x = box_begin_x; x < box_end_x;) { for (int x = box_begin_x; x < box_end_x;) {
uint32_t ch = ' '; uint32_t ch = ' ';