From 42e7bcb5f1d98def2f234bb2ce6208d38f835cf3 Mon Sep 17 00:00:00 2001 From: Noah Breedy Date: Tue, 2 Jun 2026 18:17:33 -0400 Subject: [PATCH] [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);