mirror of
https://github.com/nicbarker/clay.git
synced 2026-08-04 06:09:12 +00:00
[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:
parent
e6cc36941a
commit
42e7bcb5f1
3 changed files with 363 additions and 0 deletions
163
renderers/tigr/clay_renderer_tigr.c
Normal file
163
renderers/tigr/clay_renderer_tigr.c
Normal 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
177
renderers/tigr/utils.c
Normal 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
23
renderers/tigr/utils.h
Normal 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);
|
||||
Loading…
Add table
Add a link
Reference in a new issue