This commit is contained in:
Noah Breedy 2026-06-25 22:38:41 +00:00 committed by GitHub
commit c34386e50d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 598 additions and 0 deletions

View file

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

View file

@ -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
$<TARGET_FILE_DIR:demo>/resources
)

121
examples/tigr-demo/main.c Normal file
View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,106 @@
#include "clay_renderer_tigr.h"
#define CLAY_TO_TPIXEL(color) (TPixel){color.r, color.g, color.b, color.a}
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;
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;
}
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);
}
}
}
}

View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#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) * (config->fontSize/8);
int text_height = tigrTextHeight(tfont, cstr) * (config->fontSize/8);
free(cstr);
// Return dimensions
return (Clay_Dimensions){
.width = (float)text_width,
.height = (float)text_height
};
}
#endif /* __CLAY_TIGR_RENDERER_H__ */

208
renderers/tigr/tigr_utils.c Normal file
View file

@ -0,0 +1,208 @@
#include "tigr_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[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) {
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
}
/* 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);
}
}
}

View file

@ -0,0 +1,31 @@
#ifndef __TIGR_UTILS_H__
#define __TIGR_UTILS_H__
#include <math.h>
#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);
/* 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__ */