From 42e7bcb5f1d98def2f234bb2ce6208d38f835cf3 Mon Sep 17 00:00:00 2001 From: Noah Breedy Date: Tue, 2 Jun 2026 18:17:33 -0400 Subject: [PATCH 1/6] [Renderers/Tigr] Added scaffolding for new clay renderer Added the ability to use the Tigr graphics libary to render layouts to accomplish this I added a couple of helpful utility functions for rendering smooth boxes --- renderers/tigr/clay_renderer_tigr.c | 163 +++++++++++++++++++++++++ renderers/tigr/utils.c | 177 ++++++++++++++++++++++++++++ renderers/tigr/utils.h | 23 ++++ 3 files changed, 363 insertions(+) create mode 100644 renderers/tigr/clay_renderer_tigr.c create mode 100644 renderers/tigr/utils.c create mode 100644 renderers/tigr/utils.h diff --git a/renderers/tigr/clay_renderer_tigr.c b/renderers/tigr/clay_renderer_tigr.c new file mode 100644 index 0000000..81ca39d --- /dev/null +++ b/renderers/tigr/clay_renderer_tigr.c @@ -0,0 +1,163 @@ +// Copyright (c) 2024 Justin Andreas Lacoste (@27justin) +// +// 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 +// use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in a +// product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +// SPDX-License-Identifier: Zlib + +#include +#include +#include +#include + +#include "../../clay.h" + +#include "tigr.h" +#include "utils.h" // for our rounded borders + +// Render the command queue to the `Tigr*` instance provided +void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx); + +#define CLAY_TO_TPIXEL(color) (TPixel){color.r, color.g, color.b, color.a} + +/* Return a null-terminated copy of Clay_String `str` + * caller is required to free + * */ +static inline char *Clay_to_Cstr(Clay_String *str) { + char* copy = (char*) malloc(str->length + 1); + if (!copy) { + fprintf(stderr, "Memory allocation failed\n"); + return NULL; + } + memcpy(copy, str->chars, str->length); + copy[str->length] = '\0'; + return copy; +} + +// Measure text using built-in Tigr functions +static inline Clay_Dimensions Clay_Tigr_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, void *userData) { + + Clay_String toTerminate = (Clay_String){ .chars = str.chars, .length = str.length, .isStaticallyAllocated = false }; + + char* cstr = Clay_to_Cstr(&toTerminate); + + int text_width = tigrTextWidth(tfont, cstr); + int text_height = tigrTextHeight(tfont, cstr); + + free(cstr); + + // Return dimensions + return (Clay_Dimensions){ + .width = (float)text_width, + .height = (float)text_height + }; +} + +void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx) { + for(size_t i = 0; i < commands.length; i++) { + Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i); + + switch(command->commandType) { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData* config = &command->renderData.rectangle; + Clay_BoundingBox bb = command->boundingBox; + + int box_radius = config->cornerRadius.topLeft; // only take take top left radius + + tigrFillArcRect(ctx, bb.x, bb.y, bb.width, bb.height, box_radius, CLAY_TO_TPIXEL(config->backgroundColor)); + + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextRenderData* config = &command->renderData.text; + Clay_String toTerminate = (Clay_String){ .chars = config->stringContents.chars, .length = config->stringContents.length, .isStaticallyAllocated = false }; + char* text = Clay_to_Cstr(&toTerminate); + + Clay_BoundingBox bb = command->boundingBox; + Clay_Color color = config->textColor; + + //cairo_set_font_size(cr, config->fontSize); + tigrPrint(ctx, tfont, bb.x, bb.y, CLAY_TO_TPIXEL(color), text); + + free(text); + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderRenderData* config = &command->renderData.border; + Clay_BoundingBox bb = command->boundingBox; + + int box_radius = config->cornerRadius.topLeft; + + tigrArcRect(ctx, bb.x, bb.y, bb.width, bb.height, box_radius, CLAY_TO_TPIXEL(config->color)); + + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + Clay_ImageRenderData *config = &command->renderData.image; + Clay_BoundingBox bb = command->boundingBox; + + char* path = config->imageData; + + Tigr* img = tigrLoadImage(path); + + double image_w = img->w; + double image_h = img->h; + + /* Calculate the scaling factor to fit within the bounding box while preserving aspect ratio */ + double scale_w = bb.width / image_w; + double scale_h = bb.height / image_h; + double scale = (scale_w < scale_h) ? scale_w : scale_h; // Use the smaller scaling factor + + /* Apply the same scale to both dimensions to preserve aspect ratio */ + double scale_x = scale; + double scale_y = scale; + + /* Calculate the scaled image dimensions */ + double scaled_w = image_w * scale_x; + double scaled_h = image_h * scale_y; + + /* Adjust the x and y coordinates to center the scaled image within the bounding box */ + double centered_x = bb.x + (bb.width - scaled_w) / 2.0; + double centered_y = bb.y + (bb.height - scaled_h) / 2.0; + + /* Blit the scaled and centered image */ + tigrBlit(ctx, img, centered_x, centered_y, 0, 0, scaled_w, scaled_h); + + /* Clean up the source surface */ + tigrFree(img); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + Clay_BoundingBox bb = command->boundingBox; + tigrClip(ctx, bb.x, bb.y, bb.width, bb.height); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + tigrClip(ctx, 0, 0, -1, -1); + break; + } + case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { + // Slot your custom elements in here. + } + default: { + fprintf(stderr, "Unknown command type %d\n", (int) command->commandType); + } + } + } +} diff --git a/renderers/tigr/utils.c b/renderers/tigr/utils.c new file mode 100644 index 0000000..6488738 --- /dev/null +++ b/renderers/tigr/utils.c @@ -0,0 +1,177 @@ +#include + +#include "utils.h" + +enum ARC_STATES { + NOT_DRAWN, + STARTS_HERE, + ALL_DRAWN, + ENDS_HERE, + STARTS_ENDS_HERE +}; + +/* Radian to degree conversion value */ +#define R_TO_D 57.29578 + +/* global memory for our arc renderer */ +int arc_sector[8]; + +void PositiveSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel col) { + + if (arc_sector[s] == NOT_DRAWN) return; + + /* Draw all points of this sector */ + if (arc_sector[s] == ALL_DRAWN) { + tigrPlot(ctx, x, y, col); + return; + } + + /* draw all points flowing to right */ + if (arc_sector[s] == STARTS_HERE) { + if (x >=sp) { + tigrPlot(ctx, x, y, col); + return; + } + } + + /* draw all points flowing from left */ + if (arc_sector[s] == ENDS_HERE) { + if (x <= ep) { + tigrPlot(ctx, x, y, col); + return; + } + } + + /* fill only sections of this sector if ((x >= sp) && (x <= ep)) pixel (x, y); */ + if (arc_sector[s] == STARTS_ENDS_HERE) { + } +} + +void NegativeSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel col) { + + if (arc_sector[s] == NOT_DRAWN) return; + + /* Draw all points in this sector */ + if (arc_sector[s] == ALL_DRAWN) { + tigrPlot(ctx, x, y, col); + return; + } + + /* Draw all points flowing to the left */ + if (arc_sector[s] == STARTS_HERE) { + if (x <= sp) { + tigrPlot(ctx, x, y, col); + return; + } + } + + /* Draw all points flowing from the right if (x >= ep) {plot(x, y); return; } */ + if (arc_sector[s] == ENDS_HERE) { + } + + /* fill only sections of this sector */ + if (arc_sector[s] == STARTS_ENDS_HERE) { + if ((x >= ep) && (x <= sp)) { + tigrPlot(ctx, x, y, col); + } + } +} + +void tigrArc(Tigr* ctx, int xc, int yc, int sa, int ea, int r, TPixel col) { + + int start_sector, end_sector; + + int i; + int x, y; + int ep, sp, d; + + /* Clear all the arc sector flags */ + for(i = 0; i < 8; i++) { + arc_sector[i] = NOT_DRAWN; + } + + /* Calculate start and end arc sectors */ + start_sector = sa / 45; + end_sector = ea / 45; + + if(start_sector == end_sector) { + arc_sector[start_sector] = STARTS_ENDS_HERE; + }else { + /* Set all of the possible drawn sector flags */ + for(i = start_sector; i < end_sector; i++) { + arc_sector[i] = ALL_DRAWN; + } + arc_sector[start_sector] = STARTS_HERE; + arc_sector[end_sector] = ENDS_HERE; + } + + /* Calculate the Start and End points */ + x = 0; + y = r; + + sp = ((double)xc + (double)r * cos((double)sa/R_TO_D)); + ep = ((double)xc + (double)r * cos((double)ea/R_TO_D)); + d = 2 * (1 - r); + + while(y > x) { + NegativeSectorPoint(ctx, xc + y, yc + x, 0, sp, ep, col); + NegativeSectorPoint(ctx, xc + x, yc + y, 1, sp, ep, col); + NegativeSectorPoint(ctx, xc - x, yc + y, 2, sp, ep, col); + NegativeSectorPoint(ctx, xc - y, yc + x, 3, sp, ep, col); + PositiveSectorPoint(ctx, xc - y, yc - x, 4, sp, ep, col); + PositiveSectorPoint(ctx, xc - x, yc - y, 5, sp, ep, col); + PositiveSectorPoint(ctx, xc + x, yc - y, 6, sp, ep, col); + PositiveSectorPoint(ctx, xc + y, yc - x, 7, sp, ep, col); + if (d + y > 0) { + y = y - 1; + d = d - 2 * y + 1; + } + else { + if (x > d) { + x = x + 1; + d = d + 2 * x + 1; + } + } + } + +} + +void tigrArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col) { + + tigrArc(ctx, x + r, y, 180, 270, r, col); // top left + tigrArc(ctx, x - r + w, y, 270, 360, r, col); // top right + tigrArc(ctx, x - r + w, y + h, 0, 90, r, col); // bottom right + tigrArc(ctx, x + r, y + h, 90, 180, r, col); // bottom left + + /* These are the connecting in lines */ + tigrLine(ctx, x + r, y - r, x + w - r, y - r, col); // top + tigrLine(ctx, x + r, y + h + r, x + w - r, y + h + r, col); // bottom + tigrLine(ctx, x, y, x, y + h, col); // left + tigrLine(ctx, x + w, y, x + w, y + h, col); // right + +} + +/* The reason I dont call the tigrArcRect function is to save on draw calls */ +void tigrFillArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col) { + + /* Build the base frame */ + tigrFillRect(ctx, x - 1, y, w + 3, h, col); + tigrFillRect(ctx, x + r, y - r - 1, w - (r * 2) , h + (r * 2) + 3, col); + + /* Draw the corners */ + tigrArc(ctx, x + r, y, 180, 270, r, col); // top left + tigrArc(ctx, x - r + w, y, 270, 360, r, col); // top right + tigrArc(ctx, x - r + w, y + h, 0, 90, r, col); // bottom right + tigrArc(ctx, x + r, y + h, 90, 180, r, col); // bottom left + + /* Fill in the gaps */ + tigrFillCircle(ctx, x + r, y, r, col); // top left + tigrFillCircle(ctx, x + w - r, y, r, col); // top right + tigrFillCircle(ctx, x + w - r, y + h, r, col); // bottom right + tigrFillCircle(ctx, x + r, y + h, r, col); // bottom left + +} + + + + diff --git a/renderers/tigr/utils.h b/renderers/tigr/utils.h new file mode 100644 index 0000000..68ac38e --- /dev/null +++ b/renderers/tigr/utils.h @@ -0,0 +1,23 @@ +#pragma once + +#include "tigr.h" + +/* Fast arc drawing algorithm based on Bressenham Circle Routine + * + * taken from --> https://www.scattergood.io/arc-drawing-algorithm/ + */ +void tigrArc(Tigr* ctx, int xc, int yc, int sa, int ea, int r, TPixel col); + +/* Function to draw rectangles with arches in the angles of them + * makes use of the tigrArc function + * + * This is the wire frame one + */ +void tigrArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col); + +/* Function to draw rectangles with arches in the angles of them + * makes use of the tigrArc function + * + * This is the filled one + */ +void tigrFillArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col); From 4b12d41dd2045675c44637d054e99e7776ee4ba8 Mon Sep 17 00:00:00 2001 From: Noah Breedy Date: Mon, 22 Jun 2026 12:22:57 -0400 Subject: [PATCH 2/6] [Renderers/Tigr] Fixed Asan errors when accessing arc sectors --- renderers/tigr/utils.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/renderers/tigr/utils.c b/renderers/tigr/utils.c index 6488738..fc69e9d 100644 --- a/renderers/tigr/utils.c +++ b/renderers/tigr/utils.c @@ -14,18 +14,18 @@ enum ARC_STATES { #define R_TO_D 57.29578 /* global memory for our arc renderer */ -int arc_sector[8]; +int arc_sector[9]; void PositiveSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel col) { - + if (arc_sector[s] == NOT_DRAWN) return; - + /* Draw all points of this sector */ if (arc_sector[s] == ALL_DRAWN) { tigrPlot(ctx, x, y, col); return; } - + /* draw all points flowing to right */ if (arc_sector[s] == STARTS_HERE) { if (x >=sp) { @@ -33,7 +33,7 @@ void PositiveSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel return; } } - + /* draw all points flowing from left */ if (arc_sector[s] == ENDS_HERE) { if (x <= ep) { @@ -41,22 +41,22 @@ void PositiveSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel return; } } - + /* fill only sections of this sector if ((x >= sp) && (x <= ep)) pixel (x, y); */ if (arc_sector[s] == STARTS_ENDS_HERE) { - } + } } void NegativeSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel col) { if (arc_sector[s] == NOT_DRAWN) return; - + /* Draw all points in this sector */ if (arc_sector[s] == ALL_DRAWN) { tigrPlot(ctx, x, y, col); return; } - + /* Draw all points flowing to the left */ if (arc_sector[s] == STARTS_HERE) { if (x <= sp) { @@ -64,11 +64,11 @@ void NegativeSectorPoint(Tigr* ctx, int x, int y, int s, int sp, int ep, TPixel return; } } - + /* Draw all points flowing from the right if (x >= ep) {plot(x, y); return; } */ if (arc_sector[s] == ENDS_HERE) { } - + /* fill only sections of this sector */ if (arc_sector[s] == STARTS_ENDS_HERE) { if ((x >= ep) && (x <= sp)) { @@ -99,7 +99,7 @@ void tigrArc(Tigr* ctx, int xc, int yc, int sa, int ea, int r, TPixel col) { }else { /* Set all of the possible drawn sector flags */ for(i = start_sector; i < end_sector; i++) { - arc_sector[i] = ALL_DRAWN; + arc_sector[i] = ALL_DRAWN; } arc_sector[start_sector] = STARTS_HERE; arc_sector[end_sector] = ENDS_HERE; @@ -112,7 +112,7 @@ void tigrArc(Tigr* ctx, int xc, int yc, int sa, int ea, int r, TPixel col) { sp = ((double)xc + (double)r * cos((double)sa/R_TO_D)); ep = ((double)xc + (double)r * cos((double)ea/R_TO_D)); d = 2 * (1 - r); - + while(y > x) { NegativeSectorPoint(ctx, xc + y, yc + x, 0, sp, ep, col); NegativeSectorPoint(ctx, xc + x, yc + y, 1, sp, ep, col); @@ -142,18 +142,18 @@ void tigrArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col) { tigrArc(ctx, x - r + w, y, 270, 360, r, col); // top right tigrArc(ctx, x - r + w, y + h, 0, 90, r, col); // bottom right tigrArc(ctx, x + r, y + h, 90, 180, r, col); // bottom left - + /* These are the connecting in lines */ tigrLine(ctx, x + r, y - r, x + w - r, y - r, col); // top tigrLine(ctx, x + r, y + h + r, x + w - r, y + h + r, col); // bottom tigrLine(ctx, x, y, x, y + h, col); // left tigrLine(ctx, x + w, y, x + w, y + h, col); // right - + } /* The reason I dont call the tigrArcRect function is to save on draw calls */ void tigrFillArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col) { - + /* Build the base frame */ tigrFillRect(ctx, x - 1, y, w + 3, h, col); tigrFillRect(ctx, x + r, y - r - 1, w - (r * 2) , h + (r * 2) + 3, col); @@ -163,13 +163,13 @@ void tigrFillArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col) { tigrArc(ctx, x - r + w, y, 270, 360, r, col); // top right tigrArc(ctx, x - r + w, y + h, 0, 90, r, col); // bottom right tigrArc(ctx, x + r, y + h, 90, 180, r, col); // bottom left - + /* Fill in the gaps */ tigrFillCircle(ctx, x + r, y, r, col); // top left tigrFillCircle(ctx, x + w - r, y, r, col); // top right tigrFillCircle(ctx, x + w - r, y + h, r, col); // bottom right tigrFillCircle(ctx, x + r, y + h, r, col); // bottom left - + } From 43882c6994878a9f575cf146a18aa4b31fb28d06 Mon Sep 17 00:00:00 2001 From: Noah Breedy Date: Wed, 24 Jun 2026 14:14:16 -0400 Subject: [PATCH 3/6] [Renderers/Tigr] Added text scaling and header file This should make it easier to integrate into projects --- renderers/tigr/clay_renderer_tigr.c | 85 ++++-------------------- renderers/tigr/clay_renderer_tigr.h | 75 +++++++++++++++++++++ renderers/tigr/{utils.c => tigr_utils.c} | 37 ++++++++++- renderers/tigr/{utils.h => tigr_utils.h} | 10 ++- 4 files changed, 132 insertions(+), 75 deletions(-) create mode 100644 renderers/tigr/clay_renderer_tigr.h rename renderers/tigr/{utils.c => tigr_utils.c} (84%) rename renderers/tigr/{utils.h => tigr_utils.h} (72%) diff --git a/renderers/tigr/clay_renderer_tigr.c b/renderers/tigr/clay_renderer_tigr.c index 81ca39d..80928a5 100644 --- a/renderers/tigr/clay_renderer_tigr.c +++ b/renderers/tigr/clay_renderer_tigr.c @@ -1,74 +1,7 @@ -// Copyright (c) 2024 Justin Andreas Lacoste (@27justin) -// -// 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 -// use of this software. -// -// Permission is granted to anyone to use this software for any purpose, -// including commercial applications, and to alter it and redistribute it -// freely, subject to the following restrictions: -// -// 1. The origin of this software must not be misrepresented; you must not -// claim that you wrote the original software. If you use this software in a -// product, an acknowledgment in the product documentation would be -// appreciated but is not required. -// -// 2. Altered source versions must be plainly marked as such, and must not -// be misrepresented as being the original software. -// -// 3. This notice may not be removed or altered from any source -// distribution. -// -// SPDX-License-Identifier: Zlib - -#include -#include -#include -#include - -#include "../../clay.h" - -#include "tigr.h" -#include "utils.h" // for our rounded borders - -// Render the command queue to the `Tigr*` instance provided -void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx); +#include "clay_renderer_tigr.h" #define CLAY_TO_TPIXEL(color) (TPixel){color.r, color.g, color.b, color.a} -/* Return a null-terminated copy of Clay_String `str` - * caller is required to free - * */ -static inline char *Clay_to_Cstr(Clay_String *str) { - char* copy = (char*) malloc(str->length + 1); - if (!copy) { - fprintf(stderr, "Memory allocation failed\n"); - return NULL; - } - memcpy(copy, str->chars, str->length); - copy[str->length] = '\0'; - return copy; -} - -// Measure text using built-in Tigr functions -static inline Clay_Dimensions Clay_Tigr_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, void *userData) { - - Clay_String toTerminate = (Clay_String){ .chars = str.chars, .length = str.length, .isStaticallyAllocated = false }; - - char* cstr = Clay_to_Cstr(&toTerminate); - - int text_width = tigrTextWidth(tfont, cstr); - int text_height = tigrTextHeight(tfont, cstr); - - free(cstr); - - // Return dimensions - return (Clay_Dimensions){ - .width = (float)text_width, - .height = (float)text_height - }; -} - void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx) { for(size_t i = 0; i < commands.length; i++) { Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i); @@ -92,9 +25,19 @@ void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx) { Clay_BoundingBox bb = command->boundingBox; Clay_Color color = config->textColor; - //cairo_set_font_size(cr, config->fontSize); - tigrPrint(ctx, tfont, bb.x, bb.y, CLAY_TO_TPIXEL(color), text); + float scale = config->fontSize / 8.0f; // tfont is roughly 8px high + Tigr* temp = tigrBitmap( + tigrTextWidth(tfont, text), + tfont->glyphs->h + ); + + tigrClear(temp, tigrRGBA(0,0,0,0)); + tigrPrint(temp, tfont, 0, 0, CLAY_TO_TPIXEL(color), text); + + tigrBlitScale(ctx, temp, bb.x, bb.y, 0, 0, temp->w, temp->h, temp->w * scale, temp->h * scale); + + tigrFree(temp); free(text); break; } @@ -105,7 +48,7 @@ void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx) { int box_radius = config->cornerRadius.topLeft; tigrArcRect(ctx, bb.x, bb.y, bb.width, bb.height, box_radius, CLAY_TO_TPIXEL(config->color)); - + break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { diff --git a/renderers/tigr/clay_renderer_tigr.h b/renderers/tigr/clay_renderer_tigr.h new file mode 100644 index 0000000..cbf94f9 --- /dev/null +++ b/renderers/tigr/clay_renderer_tigr.h @@ -0,0 +1,75 @@ +// Copyright (c) 2026 Noah Arthur Breedy +// +// 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 +// use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software in a +// product, an acknowledgment in the product documentation would be +// appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +// SPDX-License-Identifier: Zlib + +#ifndef __CLAY_TIGR_RENDERER_H__ +#define __CLAY_TIGR_RENDERER_H__ + +#include +#include +#include +#include + +#include "../../clay.h" +#include "tigr.h" +#include "tigr_utils.h" // for our rounded borders + + +#define CLAY_TO_TPIXEL(color) (TPixel){color.r, color.g, color.b, color.a} + +/* Render the command queue to the `Tigr*` instance provided */ +void Clay_Tigr_Render(Clay_RenderCommandArray commands, Tigr* ctx); + +/* Return a null-terminated copy of Clay_String `str` + * caller is required to free + * */ +static inline char *Clay_to_Cstr(Clay_String *str) { + char* copy = (char*) malloc(str->length + 1); + if (!copy) { + fprintf(stderr, "Memory allocation failed\n"); + return NULL; + } + memcpy(copy, str->chars, str->length); + copy[str->length] = '\0'; + return copy; +} + +/* Measure text using built-in Tigr functions */ +static inline Clay_Dimensions Clay_Tigr_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, void *userData) { + + Clay_String toTerminate = (Clay_String){ .chars = str.chars, .length = str.length, .isStaticallyAllocated = false }; + + char* cstr = Clay_to_Cstr(&toTerminate); + + int text_width = tigrTextWidth(tfont, cstr); + int text_height = tigrTextHeight(tfont, cstr); + + free(cstr); + + // Return dimensions + return (Clay_Dimensions){ + .width = (float)text_width, + .height = (float)text_height + }; +} + +#endif /* __CLAY_TIGR_RENDERER_H__ */ diff --git a/renderers/tigr/utils.c b/renderers/tigr/tigr_utils.c similarity index 84% rename from renderers/tigr/utils.c rename to renderers/tigr/tigr_utils.c index fc69e9d..31b4a64 100644 --- a/renderers/tigr/utils.c +++ b/renderers/tigr/tigr_utils.c @@ -1,6 +1,4 @@ -#include - -#include "utils.h" +#include "tigr_utils.h" enum ARC_STATES { NOT_DRAWN, @@ -172,6 +170,39 @@ void tigrFillArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col) { } +/* ChatGPT generated blitScale function */ +void tigrBlitScale(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int sw, int sh, int dw, int dh) { + for(int y = 0; y < dh; y++) + { + int srcY = sy + (y * sh) / dh; + if(srcY < 0 || srcY >= src->h) + continue; + int dstY = dy + y; + if(dstY < 0 || dstY >= dst->h) + continue; + + for(int x = 0; x < dw; x++) + { + int srcX = sx + (x * sw) / dw; + + if(srcX < 0 || srcX >= src->w) + continue; + + int dstX = dx + x; + + if(dstX < 0 || dstX >= dst->w) + continue; + + TPixel p = src->pix[srcY * src->w + srcX]; + + /* Skip transparent pixels */ + if(p.a == 0) + continue; + + tigrPlot(dst, dstX, dstY, p); + } + } +} diff --git a/renderers/tigr/utils.h b/renderers/tigr/tigr_utils.h similarity index 72% rename from renderers/tigr/utils.h rename to renderers/tigr/tigr_utils.h index 68ac38e..d1bb8c0 100644 --- a/renderers/tigr/utils.h +++ b/renderers/tigr/tigr_utils.h @@ -1,4 +1,7 @@ -#pragma once +#ifndef __TIGR_UTILS_H__ +#define __TIGR_UTILS_H__ + +#include #include "tigr.h" @@ -21,3 +24,8 @@ void tigrArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col); * This is the filled one */ void tigrFillArcRect(Tigr* ctx, int x, int y, int w, int h, int r, TPixel col); + +/* ChatGPT generated blitScale function */ +void tigrBlitScale(Tigr* dst, Tigr* src, int dx, int dy, int sx, int sy, int sw, int sh, int dw, int dh); + +#endif /* __TIGR_UTILS_H__ */ From 12d09ed512ef7351bdbfe725622266c132d35748 Mon Sep 17 00:00:00 2001 From: Noah Breedy Date: Wed, 24 Jun 2026 15:38:40 -0400 Subject: [PATCH 4/6] [Renderers/Tigr] Modified measure text function Get a better estimate of the texts total measurements --- renderers/tigr/clay_renderer_tigr.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renderers/tigr/clay_renderer_tigr.h b/renderers/tigr/clay_renderer_tigr.h index cbf94f9..cbc56c0 100644 --- a/renderers/tigr/clay_renderer_tigr.h +++ b/renderers/tigr/clay_renderer_tigr.h @@ -60,8 +60,8 @@ static inline Clay_Dimensions Clay_Tigr_MeasureText(Clay_StringSlice str, Clay_T char* cstr = Clay_to_Cstr(&toTerminate); - int text_width = tigrTextWidth(tfont, cstr); - int text_height = tigrTextHeight(tfont, cstr); + int text_width = tigrTextWidth(tfont, cstr) * (config->fontSize/8); + int text_height = tigrTextHeight(tfont, cstr) * (config->fontSize/8); free(cstr); From 068353bb3112690007a39d71e7006d1fcc846980 Mon Sep 17 00:00:00 2001 From: Noah Breedy Date: Wed, 24 Jun 2026 19:55:31 -0400 Subject: [PATCH 5/6] [Examples/tigr-demo] Added demo program using tigr renderer --- examples/tigr-demo/Makefile | 43 +++++++++ examples/tigr-demo/main.c | 121 +++++++++++++++++++++++++ examples/tigr-demo/resources/leroy.png | Bin 0 -> 1988 bytes 3 files changed, 164 insertions(+) create mode 100644 examples/tigr-demo/Makefile create mode 100644 examples/tigr-demo/main.c create mode 100755 examples/tigr-demo/resources/leroy.png diff --git a/examples/tigr-demo/Makefile b/examples/tigr-demo/Makefile new file mode 100644 index 0000000..781295a --- /dev/null +++ b/examples/tigr-demo/Makefile @@ -0,0 +1,43 @@ +CC := gcc + +# ----------------------------- +# EDIT ME WITH RELATIVE PATH TO: +# +# tigr.c & tigr.h +# ----------------------------- +TIGR_DIR := + +OUTFILE := demo + +CFLAGS := +LDFLAGS := -lm + +# ----------------------------- +# platform flags +# ----------------------------- +ifeq ($(OS),Windows_NT) + LDFLAGS += -lopengl32 -lgdi32 + RM := del /q +else + OS := $(shell uname -s) + + ifeq ($(OS),Darwin) + LDFLAGS += -framework OpenGL -framework Cocoa + else ifeq ($(OS),Linux) + LDFLAGS += -lGLU -lGL -lX11 + endif + + RM := rm -f +endif + +SRCS := \ + main.c \ + $(TIGR_DIR)/tigr.c \ + ../../renderers/tigr/tigr_utils.c \ + ../../renderers/tigr/clay_renderer_tigr.c + +all: main.c + $(CC) -I$(TIGR_DIR) $(CFLAGS) $(SRCS) -o $(OUTFILE) $(LDFLAGS) + +clean: + $(RM) $(OUTFILE) diff --git a/examples/tigr-demo/main.c b/examples/tigr-demo/main.c new file mode 100644 index 0000000..cc4cfba --- /dev/null +++ b/examples/tigr-demo/main.c @@ -0,0 +1,121 @@ +/** + * Simple example based off the Clay README + */ +#define CLAY_IMPLEMENTATION +#include "../../clay.h" + +#include "../../renderers/tigr/clay_renderer_tigr.h" + +const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; +const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; +const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; + +void HandleClayErrors(Clay_ErrorData errorData) { + // See the Clay_ErrorData struct for more information + printf("%s\n", errorData.errorText.chars); + switch(errorData.errorType) { + // etc + } +} + +// Layout config is just a struct that can be declared statically, or inline +Clay_ElementDeclaration sidebarItemConfig = (Clay_ElementDeclaration) { + .layout = { + .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) } + }, + .backgroundColor = COLOR_ORANGE +}; + +// Re-useable components are just normal functions +void SidebarItemComponent(char* my_str, int number) { + snprintf(my_str, 8, "Box #%02d", number); + Clay_String box_txt = (Clay_String){.isStaticallyAllocated = false, .length = 8, .chars = my_str}; + CLAY_AUTO_ID(sidebarItemConfig) { + CLAY_TEXT(box_txt, { .fontSize = 8, .textColor = {255, 255, 255, 255} }); + } +} + +int main() { + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); + + int screenWidth = 1000; + int screenHeight = 600; + + int mousePositionX, mousePositionY, isMouseDown; + float mouseWheelX, mouseWheelY; + Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors }); + Clay_SetMeasureTextFunction(Clay_Tigr_MeasureText, NULL); + + Tigr* win = tigrWindow(screenWidth, screenHeight, "Clay & Tigr", TIGR_AUTO); + + /* 20 8 byte strings */ + char* my_str = calloc(20, 8); + if(my_str == NULL) { + printf("Failed to allocate memoery\n"); + return -1; + } + + uint64_t frame_cnt = 0; + while(!tigrClosed(win) && !tigrKeyDown(win, TK_ESCAPE)) { + + int deltaTime = tigrTime(); // tigrTime return the time since it was last called + tigrMouse(win, &mousePositionX, &mousePositionY, &isMouseDown); + tigrScrollWheel(win, &mouseWheelX, &mouseWheelY); + + screenWidth = win->w; + screenHeight = win->h; + Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight }); + Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown); + Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime); + + Clay_BeginLayout(); + + CLAY(CLAY_ID("OuterContainer"), { + .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, + .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, + .backgroundColor = {41, 41, 61,255} }) { + + CLAY(CLAY_ID("SideBarContainer"), { + .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = {CLAY_SIZING_FIXED(300), CLAY_SIZING_GROW(0)}, + .padding = CLAY_PADDING_ALL(16), + .childGap = 16}, + .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, + .backgroundColor = COLOR_LIGHT }) { + + CLAY(CLAY_ID("ProfilePictureOuter"), { + .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, + .padding = CLAY_PADDING_ALL(16), + .childGap = 16, + .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, + .backgroundColor = COLOR_RED }) { + + CLAY(CLAY_ID("ProfilePicture"), { + .layout = { .sizing = { .width = CLAY_SIZING_FIXED(70), .height = CLAY_SIZING_FIXED(86) }}, + .image = { .imageData = "./resources/leroy.png" } }) {} + + + CLAY_TEXT(CLAY_STRING("Clay & Tigr - UI Library"), { .fontSize = 16, .textColor = {255, 255, 255, 255} }); + } + + for (int i = 0; i < 20; i++) { + SidebarItemComponent(my_str + (8 * i), i); + } + + } + + CLAY(CLAY_ID("MainContent"), { + .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, + .backgroundColor = COLOR_LIGHT }) {} + } + + Clay_RenderCommandArray renderCommands = Clay_EndLayout(deltaTime); + Clay_Tigr_Render(renderCommands, win); + + tigrUpdate(win); + } + + free(my_str); + tigrFree(win); +} diff --git a/examples/tigr-demo/resources/leroy.png b/examples/tigr-demo/resources/leroy.png new file mode 100755 index 0000000000000000000000000000000000000000..f4d163a87999dd061112e777ae6dc1a4b5a46153 GIT binary patch literal 1988 zcmeAS@N?(olHy`uVBq!ia0y~yU~pq#UNSs54@6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dc zykO3*KpO@I2DT(`cNd2LAh=-f^2rPg44efXk;M!Q+`=Ht$S`Y;1Oo#Ddx@v7EBi|} z9zk=1$;O{`85r0(JY5_^DsH`<6WxE?MdWz>@!27%+hanySolT0Zo1$f;IMbk!fD@r zE3K++{-&&u7rUy-MO$=L?6*rV5*#l=8o&_|4f^`^@~my z!*-sW_48})u2uF9Z>HW{U2|dQ-?^bR<)yT^x1uCGyzXEq;y$a-0w^|6npIUDks&e{#m2>RSjzHl3x;I%>DGP_HQIPXEu+uReaW7MHx&yeU8u`Z4(ID=>Uie* zCs<%gOmcqX1HQlzhtqQ&vE9#oFz@a8nQsMt@cy`GSI@m8r!4B$)i?W@?%()#BT=fo zSVi2$*~&xg#T29EBH^cQe0$F{pHY0yi~^=TEc0&s_CD~l?`=J!J@fCfNoG0gH_uUC zJmDPYzNuY)nGdGuH$LBO{cFjwI{t)m_j;d{|F;>mS&QIj9P0i8Sz*>{DCy3>-;0OK>XYSRr z?0I~{|Lngz|H_XtEj_JR&%FEDYv*kH7Z>Ukp8K?Ya%`FR$?^kxL5h9CVn+|Fl9pwX z3^@;ac9#YGKHzw?ar;;K6P9lnmp6AsFU{M{Y_q{{~5zxd$)q`Pt}|NOQ{zs<>iQz7Zy zmZu#KS_?W>Oqlq3<+Yv}TALJ&ByV16W}jVFol>R{`}=$0xoOt_qeJ>R)1gUUaTq~|)>{}vo_56$nHD|xgeIdK-Y084yU*BI^>Nm`PW8wE~84K@gv$-#1Z9!t? zuabO@rLY<_-di|B=GOB1`SXe|zxXL2G5xu7b4%56tsnOC2LcbTdbn%ty`bggC!~Jd zFIg@byUyj`@@+az$xdG;1o8&IkouATL_(hZ%1hPu1IgTd{f*3u(&7gL1umZS*_Z#{ z)8g3j>2G|Ozcpeyt8A&f^?!=YWHowNU1r^j$4cTYr8^5BmMy zx1aOm<-Hd+|G)QTM(H;7$1~P_Dhb~DKD0kFy;|E=&2?U0&)wSO# zeTo9)q=K&$GMFhwFS~m+`;QDI`2C3*mu=&p6O>4O)K|o@{r44UUkOE zQq8L0d@`d~T!GhZn|moTm(A0cT&u2nJfovj`LVF9cl#WnQ=Y{#n-gV$_$Y*@?_X^!bEU`*ZUQ#*t4R=-|}78yw5HGLMVa=-uDt?{S%=hm24U+yt}U0UB=USa zP1kx~S!BH_ySAVzrY_j`R;uj+XT4gN?U!0$8*saHe#qO`U*_C;ApP-(;z`HZ*XG_@_j^gs`&X9AuQwD`emPV8 zWs&^REe`YawyfU$Zd2{#dBuDAKGYy zZtZ$)7F<7FcuUe<^=0p@LVr)#^(p1GSDv7{(eLOj&%bfMHq1Scv2NZ9g;OiVwHzsid;BG(trmk%yz3ield%rxWLirS@ek8j89eJ1>DzWio>o77)7Zq)eRe$TAj z$+PF^i6?AY;uF&Jj+^Gx=WJN9>A;F7tMb<+ho@dH&Ht}|diE-d^VQu;85kHCJYD@< J);T3K0RSeR Date: Thu, 25 Jun 2026 18:37:15 -0400 Subject: [PATCH 6/6] [Examples/tigr-demo] Changed build process to CMake --- CMakeLists.txt | 5 +++ examples/tigr-demo/CMakeLists.txt | 52 +++++++++++++++++++++++++++++++ examples/tigr-demo/Makefile | 43 ------------------------- 3 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 examples/tigr-demo/CMakeLists.txt delete mode 100644 examples/tigr-demo/Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ab1b94..7a9543e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF) option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) +option(CLAY_INCLUDE_TIGR_EXAMPLES "Build Tigr examples" OFF) message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}") @@ -45,6 +46,10 @@ if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES) add_subdirectory("examples/sokol-video-demo") add_subdirectory("examples/sokol-corner-radius") endif() +if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_TIGR_EXAMPLES) + add_subdirectory("examples/tigr-demo") +endif() + # Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement if(CLAY_INCLUDE_PLAYDATE_EXAMPLES) diff --git a/examples/tigr-demo/CMakeLists.txt b/examples/tigr-demo/CMakeLists.txt new file mode 100644 index 0000000..f159169 --- /dev/null +++ b/examples/tigr-demo/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.16) + +project(demo C) + +set(CMAKE_C_STANDARD 99) + +# ----------------------------- +# EDIT ME WITH RELATIVE PATH TO: +# tigr.c & tigr.h +set(TIGR_DIR "../../../tigr") + +add_executable(demo + main.c + ${TIGR_DIR}/tigr.c + ../../renderers/tigr/tigr_utils.c + ../../renderers/tigr/clay_renderer_tigr.c +) + +target_include_directories(demo PRIVATE + ${TIGR_DIR} +) + +# Platform-specific libraries +if(WIN32) + target_link_libraries(demo PRIVATE + opengl32 + gdi32 + ) + +elseif(APPLE) + find_library(OPENGL_FRAMEWORK OpenGL) + find_library(COCOA_FRAMEWORK Cocoa) + + target_link_libraries(demo PRIVATE + ${OPENGL_FRAMEWORK} + ${COCOA_FRAMEWORK} + ) + +elseif(UNIX) + target_link_libraries(demo PRIVATE + m + GLU + GL + X11 + ) +endif() + +add_custom_command(TARGET demo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + $/resources +) diff --git a/examples/tigr-demo/Makefile b/examples/tigr-demo/Makefile deleted file mode 100644 index 781295a..0000000 --- a/examples/tigr-demo/Makefile +++ /dev/null @@ -1,43 +0,0 @@ -CC := gcc - -# ----------------------------- -# EDIT ME WITH RELATIVE PATH TO: -# -# tigr.c & tigr.h -# ----------------------------- -TIGR_DIR := - -OUTFILE := demo - -CFLAGS := -LDFLAGS := -lm - -# ----------------------------- -# platform flags -# ----------------------------- -ifeq ($(OS),Windows_NT) - LDFLAGS += -lopengl32 -lgdi32 - RM := del /q -else - OS := $(shell uname -s) - - ifeq ($(OS),Darwin) - LDFLAGS += -framework OpenGL -framework Cocoa - else ifeq ($(OS),Linux) - LDFLAGS += -lGLU -lGL -lX11 - endif - - RM := rm -f -endif - -SRCS := \ - main.c \ - $(TIGR_DIR)/tigr.c \ - ../../renderers/tigr/tigr_utils.c \ - ../../renderers/tigr/clay_renderer_tigr.c - -all: main.c - $(CC) -I$(TIGR_DIR) $(CFLAGS) $(SRCS) -o $(OUTFILE) $(LDFLAGS) - -clean: - $(RM) $(OUTFILE)