[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
This commit is contained in:
Noah Breedy 2026-06-02 18:17:33 -04:00
parent e6cc36941a
commit 42e7bcb5f1
3 changed files with 363 additions and 0 deletions

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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);
}
}
}
}

177
renderers/tigr/utils.c Normal file
View file

@ -0,0 +1,177 @@
#include <math.h>
#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
}

23
renderers/tigr/utils.h Normal file
View file

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