mirror of
https://github.com/nicbarker/clay.git
synced 2025-05-03 17:08:06 +00:00
Compare commits
5 Commits
1bdd7d5c4f
...
dd7eb0c9cd
Author | SHA1 | Date | |
---|---|---|---|
|
dd7eb0c9cd | ||
|
afba9f0de6 | ||
|
3a4455aa83 | ||
|
b0fe41186c | ||
|
7ce74ba46c |
@ -9,6 +9,7 @@ add_subdirectory("examples/raylib-sidebar-scrolling-container")
|
||||
# add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now
|
||||
if(NOT MSVC)
|
||||
add_subdirectory("examples/clay-official-website")
|
||||
add_subdirectory("examples/terminal-example")
|
||||
endif()
|
||||
add_subdirectory("examples/introducing-clay-video-demo")
|
||||
add_subdirectory("examples/SDL2-video-demo")
|
||||
|
15
README.md
15
README.md
@ -173,9 +173,12 @@ For help starting out or to discuss clay, considering joining [the discord serve
|
||||
- [Clay_MinMemorySize](#clay_minmemorysize)
|
||||
- [Clay_CreateArenaWithCapacityAndMemory](#clay_createarenawithcapacityandmemory)
|
||||
- [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction)
|
||||
- [Clay_ResetMeasureTextCache](#clau_resetmeasuretextcache)
|
||||
- [Clay_SetMaxElementCount](clay_setmaxelementcount)
|
||||
- [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmaxmeasuretextcachewordcount)
|
||||
- [Clay_Initialize](#clay_initialize)
|
||||
- [Clay_GetCurrentContext](#clay_getcurrentcontext)
|
||||
- [Clay_SetCurrentContext](#clay_setcurrentcontext)
|
||||
- [Clay_SetLayoutDimensions](#clay_setlayoutdimensions)
|
||||
- [Clay_SetPointerState](#clay_setpointerstate)
|
||||
- [Clay_UpdateScrollContainers](#clay_updatescrollcontainers)
|
||||
@ -575,6 +578,14 @@ Takes a pointer to a function that can be used to measure the `width, height` di
|
||||
|
||||
---
|
||||
|
||||
### Clay_ResetMeasureTextCache
|
||||
|
||||
`void Clay_ResetMeasureTextCache(void)`
|
||||
|
||||
Clay caches measurements from the provided MeasureTextFunction, and this will be sufficient for the majority of use-cases. However, if the measurements can depend on external factors that clay does not know about, like DPI changes, then the cached values may be incorrect. When one of these external factors changes, Clay_ResetMeasureTextCache can be called to force clay to recalculate all string measurements in the next frame.
|
||||
|
||||
---
|
||||
|
||||
### Clay_SetMaxElementCount
|
||||
|
||||
`void Clay_SetMaxElementCount(uint32_t maxElementCount)`
|
||||
@ -603,12 +614,16 @@ Initializes the internal memory mapping, sets the internal dimensions for layout
|
||||
|
||||
Reference: [Clay_Arena](#clay_createarenawithcapacityandmemory), [Clay_ErrorHandler](#clay_errorhandler), [Clay_SetCurrentContext](#clay_setcurrentcontext)
|
||||
|
||||
---
|
||||
|
||||
### Clay_SetCurrentContext
|
||||
|
||||
`void Clay_SetCurrentContext(Clay_Context* context)`
|
||||
|
||||
Sets the context that subsequent clay commands will operate on. You can get this reference from [Clay_Initialize](#clay_initialize) or [Clay_GetCurrentContext](#clay_getcurrentcontext). See [Running more than one Clay instance](#running-more-than-one-clay-instance).
|
||||
|
||||
---
|
||||
|
||||
### Clay_GetCurrentContext
|
||||
|
||||
`Clay_Context* Clay_GetCurrentContext()`
|
||||
|
36
clay.h
36
clay.h
@ -525,6 +525,7 @@ int32_t Clay_GetMaxElementCount(void);
|
||||
void Clay_SetMaxElementCount(int32_t maxElementCount);
|
||||
int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
|
||||
void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
|
||||
void Clay_ResetMeasureTextCache(void);
|
||||
|
||||
// Internal API functions required by macros
|
||||
void Clay__OpenElement(void);
|
||||
@ -1213,6 +1214,7 @@ Clay__MeasuredWord *Clay__MeasuredWordArray_Add(Clay__MeasuredWordArray *array,
|
||||
CLAY__TYPEDEF(Clay__MeasureTextCacheItem, struct {
|
||||
Clay_Dimensions unwrappedDimensions;
|
||||
int32_t measuredWordsStartIndex;
|
||||
bool containsNewlines;
|
||||
// Hash map data
|
||||
uint32_t id;
|
||||
int32_t nextIndex;
|
||||
@ -1678,6 +1680,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
|
||||
int32_t start = 0;
|
||||
int32_t end = 0;
|
||||
float lineWidth = 0;
|
||||
float measuredWidth = 0;
|
||||
float measuredHeight = 0;
|
||||
float spaceWidth = Clay__MeasureText(&CLAY__SPACECHAR, config).width;
|
||||
@ -1699,18 +1702,22 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
int32_t length = end - start;
|
||||
Clay_String word = { .length = length, .chars = &text->chars[start] };
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(&word, config);
|
||||
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
|
||||
if (current == ' ') {
|
||||
dimensions.width += spaceWidth;
|
||||
previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length + 1, .width = dimensions.width, .next = -1 }, previousWord);
|
||||
lineWidth += dimensions.width;
|
||||
}
|
||||
if (current == '\n') {
|
||||
if (length > 1) {
|
||||
if (length > 0) {
|
||||
previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = length, .width = dimensions.width, .next = -1 }, previousWord);
|
||||
}
|
||||
previousWord = Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = end + 1, .length = 0, .width = 0, .next = -1 }, previousWord);
|
||||
lineWidth += dimensions.width;
|
||||
measuredWidth = CLAY__MAX(lineWidth, measuredWidth);
|
||||
measured->containsNewlines = true;
|
||||
lineWidth = 0;
|
||||
}
|
||||
measuredWidth += dimensions.width;
|
||||
measuredHeight = dimensions.height;
|
||||
start = end + 1;
|
||||
}
|
||||
end++;
|
||||
@ -1719,9 +1726,11 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
Clay_String lastWord = { .length = end - start, .chars = &text->chars[start] };
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(&lastWord, config);
|
||||
Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord);
|
||||
measuredWidth += dimensions.width;
|
||||
measuredHeight = dimensions.height;
|
||||
lineWidth += dimensions.width;
|
||||
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
|
||||
}
|
||||
measuredWidth = CLAY__MAX(lineWidth, measuredWidth);
|
||||
|
||||
measured->measuredWordsStartIndex = tempWord.next;
|
||||
measured->unwrappedDimensions.width = measuredWidth;
|
||||
measured->unwrappedDimensions.height = measuredHeight;
|
||||
@ -2367,7 +2376,7 @@ void Clay__CalculateFinalLayout() {
|
||||
float lineHeight = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textElementData->preferredDimensions.height;
|
||||
int32_t lineLengthChars = 0;
|
||||
int32_t lineStartOffset = 0;
|
||||
if (textElementData->preferredDimensions.width <= containerElement->dimensions.width) {
|
||||
if (!measureTextCacheItem->containsNewlines && textElementData->preferredDimensions.width <= containerElement->dimensions.width) {
|
||||
Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { containerElement->dimensions, textElementData->text });
|
||||
textElementData->wrappedLines.length++;
|
||||
continue;
|
||||
@ -4043,6 +4052,21 @@ void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount)
|
||||
}
|
||||
}
|
||||
|
||||
CLAY_WASM_EXPORT("Clay_ResetMeasureTextCache")
|
||||
void Clay_ResetMeasureTextCache(void) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
context->measureTextHashMapInternal.length = 0;
|
||||
context->measureTextHashMapInternalFreeList.length = 0;
|
||||
context->measureTextHashMap.length = 0;
|
||||
context->measuredWords.length = 0;
|
||||
context->measuredWordsFreeList.length = 0;
|
||||
|
||||
for (int32_t i = 0; i < context->measureTextHashMap.capacity; ++i) {
|
||||
context->measureTextHashMap.internalArray[i] = 0;
|
||||
}
|
||||
context->measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element"
|
||||
}
|
||||
|
||||
#endif // CLAY_IMPLEMENTATION
|
||||
|
||||
/*
|
||||
|
12
examples/terminal-example/CMakeLists.txt
Normal file
12
examples/terminal-example/CMakeLists.txt
Normal file
@ -0,0 +1,12 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
project(clay_examples_terminal C)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
add_executable(clay_examples_terminal main.c)
|
||||
|
||||
target_compile_options(clay_examples_terminal PUBLIC)
|
||||
target_include_directories(clay_examples_terminal PUBLIC .)
|
||||
|
||||
target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY})
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
57
examples/terminal-example/main.c
Normal file
57
examples/terminal-example/main.c
Normal file
@ -0,0 +1,57 @@
|
||||
// Must be defined in one file, _before_ #include "clay.h"
|
||||
#define CLAY_IMPLEMENTATION
|
||||
|
||||
#include <unistd.h>
|
||||
#include "../../clay.h"
|
||||
#include "../../renderers/terminal/clay_renderer_terminal.c"
|
||||
|
||||
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};
|
||||
|
||||
// An example function to begin the "root" of your layout tree
|
||||
Clay_RenderCommandArray CreateLayout() {
|
||||
Clay_BeginLayout();
|
||||
|
||||
CLAY(CLAY_ID("OuterContainer"),
|
||||
CLAY_LAYOUT({.layoutDirection = CLAY_LEFT_TO_RIGHT, .sizing = {CLAY_SIZING_GROW(), CLAY_SIZING_GROW()}, }),
|
||||
CLAY_RECTANGLE({ .color = {0,0,0,255} })) {
|
||||
CLAY(CLAY_ID("SideBar"),
|
||||
CLAY_LAYOUT({.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = {.width = CLAY_SIZING_PERCENT(
|
||||
0.5), .height = CLAY_SIZING_PERCENT(1)}}),
|
||||
CLAY_RECTANGLE({.color = (Clay_Color) {255, 255, 255, 255}})
|
||||
) {
|
||||
}
|
||||
CLAY(CLAY_ID("OtherSideBar"),
|
||||
CLAY_LAYOUT({.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = {.width = CLAY_SIZING_PERCENT(
|
||||
0.5), .height = CLAY_SIZING_PERCENT(1)}}),
|
||||
CLAY_RECTANGLE({ .color = {0,0, 0, 255 }})
|
||||
) {
|
||||
// TODO font size is wrong, only one is allowed, but I don't know which it is
|
||||
CLAY_TEXT(CLAY_STRING("0123456789 0123456 78901 234567 89012 34567 8901234567890 123456789"),
|
||||
CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24, .textColor = {255,255,255,255} }));
|
||||
}
|
||||
}
|
||||
|
||||
return Clay_EndLayout();
|
||||
}
|
||||
|
||||
int main() {
|
||||
const int width = 80;
|
||||
const int height = 24;
|
||||
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
|
||||
Clay_Initialize(arena, (Clay_Dimensions) { .width = (float) width, .height = (float) height }); // TODO this is wrong, but I have no idea what the actual size of the terminal is in pixels
|
||||
// Tell clay how to measure text
|
||||
Clay_SetMeasureTextFunction(Console_MeasureText);
|
||||
|
||||
while(true) {
|
||||
Clay_RenderCommandArray layout = CreateLayout();
|
||||
|
||||
Clay_Console_Render(layout, width, height);
|
||||
|
||||
fflush(stdout);
|
||||
sleep(1);
|
||||
}
|
||||
}
|
190
renderers/terminal/clay_renderer_terminal.c
Normal file
190
renderers/terminal/clay_renderer_terminal.c
Normal file
@ -0,0 +1,190 @@
|
||||
#include "stdint.h"
|
||||
#include "string.h"
|
||||
#include "stdio.h"
|
||||
#include "stdlib.h"
|
||||
#include "math.h"
|
||||
#ifdef CLAY_OVERFLOW_TRAP
|
||||
#include "signal.h"
|
||||
#endif
|
||||
|
||||
static inline void Console_MoveCursor(int x, int y) {
|
||||
printf("\033[%d;%dH", y+1, x+1);
|
||||
}
|
||||
|
||||
bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) {
|
||||
// TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want
|
||||
// TODO to expose Clay__PointIsInsideRect
|
||||
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
|
||||
}
|
||||
|
||||
static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color,
|
||||
Clay_BoundingBox scissorBox) {
|
||||
if (color.r < 127 || color.g < 127 || color.b < 127 || color.a < 127) {
|
||||
// For now there are only two colors,
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = y0; y < height; y++) {
|
||||
for (int x = x0; x < width; x++) {
|
||||
if(!Clay_PointIsInsideRect((Clay_Vector2) { .x = x, .y = y }, scissorBox)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console_MoveCursor(x, y);
|
||||
// TODO there are only two colors actually drawn, the background and white
|
||||
if (color.r < 127 || color.g < 127 || color.b < 127 || color.a < 127) {
|
||||
printf(" ");
|
||||
} else {
|
||||
printf("▪");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline Clay_Dimensions Console_MeasureText(Clay_String *text, Clay_TextElementConfig *config) {
|
||||
Clay_Dimensions textSize = { 0 };
|
||||
|
||||
// TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels
|
||||
|
||||
float maxTextWidth = 0.0f;
|
||||
float lineTextWidth = 0;
|
||||
|
||||
float textHeight = 1;
|
||||
|
||||
for (int i = 0; i < text->length; ++i)
|
||||
{
|
||||
if (text->chars[i] == '\n') {
|
||||
maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth;
|
||||
lineTextWidth = 0;
|
||||
textHeight++;
|
||||
continue;
|
||||
}
|
||||
lineTextWidth++;
|
||||
}
|
||||
|
||||
maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth;
|
||||
|
||||
textSize.width = maxTextWidth;
|
||||
textSize.height = textHeight;
|
||||
|
||||
return textSize;
|
||||
}
|
||||
|
||||
void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) {
|
||||
//TODO
|
||||
}
|
||||
|
||||
void Clay_Console_Render(Clay_RenderCommandArray renderCommands, int width, int height)
|
||||
{
|
||||
printf("\033[H\033[J"); // Clear
|
||||
|
||||
const Clay_BoundingBox fullWindow = {
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = (float) width,
|
||||
.height = (float) height,
|
||||
};
|
||||
|
||||
Clay_BoundingBox scissorBox = fullWindow;
|
||||
|
||||
for (int j = 0; j < renderCommands.length; j++)
|
||||
{
|
||||
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
|
||||
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
|
||||
switch (renderCommand->commandType)
|
||||
{
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
Clay_String text = renderCommand->text;
|
||||
int y = 0;
|
||||
for (int x = 0; x < text.length; x++) {
|
||||
if(text.chars[x] == '\n') {
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
|
||||
int cursorX = (int) boundingBox.x + x;
|
||||
int cursorY = (int) boundingBox.y + y;
|
||||
if(!Clay_PointIsInsideRect((Clay_Vector2) { .x = cursorX, .y = cursorY }, scissorBox)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Console_MoveCursor(cursorX, cursorY);
|
||||
printf("%c", text.chars[x]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||
scissorBox = boundingBox;
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
|
||||
scissorBox = fullWindow;
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
Clay_RectangleElementConfig *config = renderCommand->config.rectangleElementConfig;
|
||||
Console_DrawRectangle(
|
||||
(int)boundingBox.x,
|
||||
(int)boundingBox.y,
|
||||
(int)boundingBox.width,
|
||||
(int)boundingBox.height,
|
||||
config->color,
|
||||
scissorBox);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderElementConfig *config = renderCommand->config.borderElementConfig;
|
||||
// Left border
|
||||
if (config->left.width > 0) {
|
||||
Console_DrawRectangle(
|
||||
(int)(boundingBox.x),
|
||||
(int)(boundingBox.y + config->cornerRadius.topLeft),
|
||||
(int)config->left.width,
|
||||
(int)(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft),
|
||||
config->left.color,
|
||||
scissorBox);
|
||||
}
|
||||
// Right border
|
||||
if (config->right.width > 0) {
|
||||
Console_DrawRectangle(
|
||||
(int)(boundingBox.x + boundingBox.width - config->right.width),
|
||||
(int)(boundingBox.y + config->cornerRadius.topRight),
|
||||
(int)config->right.width,
|
||||
(int)(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight),
|
||||
config->right.color,
|
||||
scissorBox);
|
||||
}
|
||||
// Top border
|
||||
if (config->top.width > 0) {
|
||||
Console_DrawRectangle(
|
||||
(int)(boundingBox.x + config->cornerRadius.topLeft),
|
||||
(int)(boundingBox.y),
|
||||
(int)(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight),
|
||||
(int)config->top.width,
|
||||
config->top.color,
|
||||
scissorBox);
|
||||
}
|
||||
// Bottom border
|
||||
if (config->bottom.width > 0) {
|
||||
Console_DrawRectangle(
|
||||
(int)(boundingBox.x + config->cornerRadius.bottomLeft),
|
||||
(int)(boundingBox.y + boundingBox.height - config->bottom.width),
|
||||
(int)(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight),
|
||||
(int)config->bottom.width,
|
||||
config->bottom.color,
|
||||
scissorBox);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
printf("Error: unhandled render command.");
|
||||
#ifdef CLAY_OVERFLOW_TRAP
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console_MoveCursor(-1, -1); // TODO make the user not be able to write
|
||||
}
|
Loading…
Reference in New Issue
Block a user