clay/clay.h

2342 lines
120 KiB
C

#pragma once
// VERSION: 0.10
#ifndef CLAY_IMPLEMENTATION
#define CLAY_IMPLEMENTATION
#ifdef CLAY_WASM
#define CLAY_WASM_EXPORT(name) __attribute__((export_name(name)))
#else
#define CLAY_WASM_EXPORT(null)
#endif
#include "stdint.h"
#include "stdbool.h"
#include "stddef.h"
#ifdef CLAY_OVERFLOW_TRAP
#include "signal.h"
#endif
#ifndef CLAY_MAX_ELEMENT_COUNT
#define CLAY_MAX_ELEMENT_COUNT 8192
#endif
#ifndef CLAY__NULL
#define CLAY__NULL 0
#endif
#ifndef CLAY__MAXFLOAT
#define CLAY__MAXFLOAT 3.40282346638528859812e+38F
#endif
#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y))
#define CLAY__MIN(x, y) (((x) < (y)) ? (x) : (y))
#define CLAY__ALIGNMENT(type) (offsetof(struct { char c; type x; }, x))
// Publicly visible config macro -----------------------------------------------------
#define CLAY_LAYOUT(...) Clay_LayoutConfigArray_Add(&Clay__layoutConfigs, (Clay_LayoutConfig) {__VA_ARGS__ })
#define CLAY_RECTANGLE_CONFIG(...) Clay_RectangleElementConfigArray_Add(&Clay__rectangleElementConfigs, (Clay_RectangleElementConfig) {__VA_ARGS__ })
#define CLAY_TEXT_CONFIG(...) Clay_TextElementConfigArray_Add(&Clay__textElementConfigs, (Clay_TextElementConfig) {__VA_ARGS__ })
#define CLAY_IMAGE_CONFIG(...) Clay_ImageElementConfigArray_Add(&Clay__imageElementConfigs, (Clay_ImageElementConfig) {__VA_ARGS__ })
#define CLAY_FLOATING_CONFIG(...) Clay_FloatingElementConfigArray_Add(&Clay__floatingElementConfigs, (Clay_FloatingElementConfig) {__VA_ARGS__ })
#define CLAY_CUSTOM_ELEMENT_CONFIG(...) Clay_CustomElementConfigArray_Add(&Clay__customElementConfigs, (Clay_CustomElementConfig) {__VA_ARGS__ })
#define CLAY_SCROLL_CONFIG(...) Clay_ScrollContainerElementConfigArray_Add(&Clay__scrollElementConfigs, (Clay_ScrollContainerElementConfig) {__VA_ARGS__ })
#define CLAY_BORDER_CONFIG(...) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { __VA_ARGS__ })
#define CLAY_BORDER_CONFIG_OUTSIDE(...) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { __VA_ARGS__ }, .right = { __VA_ARGS__ }, .top = { __VA_ARGS__ }, .bottom = { __VA_ARGS__ } })
#define CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(width, color, radius) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { width, color }, .right = { width, color }, .top = { width, color }, .bottom = { width, color }, .cornerRadius = { radius, radius, radius, radius } })
#define CLAY_BORDER_CONFIG_ALL(...) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { __VA_ARGS__ }, .right = { __VA_ARGS__ }, .top = { __VA_ARGS__ }, .bottom = { __VA_ARGS__ }, .betweenChildren = { __VA_ARGS__ } })
#define CLAY_BORDER_CONFIG_ALL_RADIUS(width, color, radius) Clay_BorderContainerElementConfigArray_Add(&Clay__borderElementConfigs, (Clay_BorderContainerElementConfig ) { .left = { __VA_ARGS__ }, .right = { __VA_ARGS__ }, .top = { __VA_ARGS__ }, .bottom = { __VA_ARGS__ }, .betweenChildren = { __VA_ARGS__ }, .cornerRadius = { radius, radius, radius, radius }})
#define CLAY_CORNER_RADIUS(radius) (Clay_CornerRadius) { radius, radius, radius, radius }
#define CLAY_SIZING_FIT(...) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_FIT, .sizeMinMax = (Clay_SizingMinMax) {__VA_ARGS__} }
#define CLAY_SIZING_GROW(...) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_GROW, .sizeMinMax = (Clay_SizingMinMax) {__VA_ARGS__} }
#define CLAY_SIZING_FIXED(fixedSize) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_GROW, .sizeMinMax = { fixedSize, fixedSize } }
#define CLAY_SIZING_PERCENT(percentOfParent) (Clay_SizingAxis) { .type = CLAY__SIZING_TYPE_PERCENT, .sizePercent = percentOfParent }
#define CLAY_ID(label) Clay__HashString(CLAY_STRING(label), 0)
#define CLAY_IDI(label, index) Clay__HashString(CLAY_STRING(label), index)
#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof(s[0])) - sizeof(s[0]))
#define CLAY_STRING(string) (Clay_String) { .length = CLAY__STRING_LENGTH(string), .chars = string }
// Publicly visible layout element macros -----------------------------------------------------
#define CLAY_CONTAINER(id, layoutConfig, children) \
Clay__OpenContainerElement(id, layoutConfig); \
children \
Clay__CloseContainerElement()
#define CLAY_RECTANGLE(id, layoutConfig, rectangleConfig, children) \
Clay__OpenRectangleElement(id, layoutConfig, rectangleConfig); \
children \
Clay__CloseContainerElement()
#define CLAY_TEXT(id, text, textConfig) Clay__OpenTextElement(id, text, textConfig)
#define CLAY_IMAGE(id, layoutConfig, imageConfig, children) \
Clay__OpenImageContainerElement(id, layoutConfig, imageConfig); \
children \
Clay__CloseContainerElement()
#define CLAY_SCROLL_CONTAINER(id, layoutConfig, scrollConfig, children) \
Clay__OpenScrollElement(id, layoutConfig, scrollConfig); \
children \
Clay__CloseScrollContainerElement()
#define CLAY_FLOATING_CONTAINER(id, layoutConfig, floatingConfig, children) \
Clay__OpenFloatingContainerElement(id, layoutConfig, floatingConfig); \
children \
Clay__CloseFloatingContainer()
#define CLAY_BORDER_CONTAINER(id, layoutConfig, borderConfig, children) \
Clay__OpenBorderContainerElement(id, layoutConfig, borderConfig); \
children \
Clay__CloseContainerElement()
#define CLAY_CUSTOM_ELEMENT(id, layoutConfig, customElementConfig, children) \
Clay__OpenCustomElement(id, layoutConfig, customElementConfig); \
children \
Clay__CloseContainerElement()
// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string,
// but it is also used to represent slices.
typedef struct {
int length;
const char *chars;
} Clay_String;
Clay_String CLAY__SPACECHAR = (Clay_String) { .length = 1, .chars = " " };
Clay_String CLAY__STRING_DEFAULT = (Clay_String) { .length = 0, .chars = "" };
typedef struct {
Clay_String label;
uint64_t nextAllocation;
uint64_t capacity;
char *memory;
} Clay_Arena;
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_String *internalArray;
} Clay_StringArray;
Clay_StringArray Clay_warnings = (Clay_StringArray) {};
Clay_String *Clay_StringArray_Add(Clay_StringArray *array, Clay_String item)
{
if (array->length < array->capacity) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
else {
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
}
return &CLAY__STRING_DEFAULT;
}
Clay_StringArray Clay_StringArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena)
{
uint64_t totalSizeBytes = capacity * sizeof(Clay_String);
Clay_StringArray array = (Clay_StringArray){.capacity = capacity, .length = 0};
uint64_t nextAllocAddress = (uint64_t)(arena->nextAllocation + arena->memory);
uint64_t arenaOffsetAligned = nextAllocAddress + (CLAY__ALIGNMENT(Clay_String) - (nextAllocAddress % CLAY__ALIGNMENT(Clay_String)));
arenaOffsetAligned -= (uint64_t)arena->memory;
if (arenaOffsetAligned + totalSizeBytes <= arena->capacity) {
array.internalArray = (Clay_String*)(arena->memory + arenaOffsetAligned);
arena->nextAllocation = arenaOffsetAligned + totalSizeBytes;
}
else {
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
}
return array;
}
void* Clay__Array_Allocate_Arena(uint32_t capacity, uint32_t itemSize, uint32_t alignment, Clay_Arena *arena)
{
uint64_t totalSizeBytes = capacity * itemSize;
uint64_t nextAllocAddress = (uint64_t)(arena->nextAllocation + arena->memory);
uint64_t arenaOffsetAligned = nextAllocAddress + (alignment - (nextAllocAddress % alignment));
arenaOffsetAligned -= (uint64_t)arena->memory;
if (arenaOffsetAligned + totalSizeBytes <= arena->capacity) {
arena->nextAllocation = arenaOffsetAligned + totalSizeBytes;
return (void*)(arena->memory + arenaOffsetAligned);
}
else {
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
}
return CLAY__NULL;
}
bool Clay__Array_RangeCheck(int index, uint32_t length)
{
if (index < length && index >= 0) {
return true;
}
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Array access out of bounds."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
return false;
}
bool Clay__Array_IncrementCapacityCheck(uint32_t length, uint32_t capacity)
{
if (length < capacity) {
return true;
}
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to add to array that is already at capacity."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
return false;
}
bool CLAY__BOOL_DEFAULT = false;
// __GENERATED__ template array_define TYPE=bool NAME=Clay__BoolArray
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
bool *internalArray;
} Clay__BoolArray;
Clay__BoolArray Clay__BoolArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__BoolArray){.capacity = capacity, .length = 0, .internalArray = (bool *)Clay__Array_Allocate_Arena(capacity, sizeof(bool), CLAY__ALIGNMENT(bool), arena)};
}
#pragma endregion
// __GENERATED__ template
typedef struct {
float r, g, b, a;
} Clay_Color;
typedef struct {
float x, y, width, height;
} Clay_Rectangle;
typedef struct {
float width, height;
} Clay_Dimensions;
typedef struct {
float x, y;
} Clay_Vector2;
typedef enum __attribute__((__packed__)) {
CLAY_LEFT_TO_RIGHT,
CLAY_TOP_TO_BOTTOM,
} Clay_LayoutDirection;
typedef enum __attribute__((__packed__)) {
CLAY_ALIGN_X_LEFT,
CLAY_ALIGN_X_RIGHT,
CLAY_ALIGN_X_CENTER,
} Clay_LayoutAlignmentX;
typedef enum __attribute__((__packed__)) {
CLAY_ALIGN_Y_TOP,
CLAY_ALIGN_Y_BOTTOM,
CLAY_ALIGN_Y_CENTER,
} Clay_LayoutAlignmentY;
typedef enum __attribute__((__packed__)) {
CLAY__SIZING_TYPE_FIT,
CLAY__SIZING_TYPE_GROW,
CLAY__SIZING_TYPE_PERCENT,
} Clay__SizingType;
typedef enum {
CLAY_RENDER_COMMAND_TYPE_NONE,
CLAY_RENDER_COMMAND_TYPE_RECTANGLE,
CLAY_RENDER_COMMAND_TYPE_BORDER,
CLAY_RENDER_COMMAND_TYPE_TEXT,
CLAY_RENDER_COMMAND_TYPE_IMAGE,
CLAY_RENDER_COMMAND_TYPE_SCISSOR_START,
CLAY_RENDER_COMMAND_TYPE_SCISSOR_END,
CLAY_RENDER_COMMAND_TYPE_CUSTOM,
} Clay_RenderCommandType;
typedef enum __attribute__((__packed__)) {
CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER,
CLAY__LAYOUT_ELEMENT_TYPE_RECTANGLE,
CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER,
CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER,
CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER,
CLAY__LAYOUT_ELEMENT_TYPE_IMAGE,
CLAY__LAYOUT_ELEMENT_TYPE_TEXT,
CLAY__LAYOUT_ELEMENT_TYPE_CUSTOM,
} Clay__LayoutElementType;
Clay_RenderCommandType Clay__LayoutElementTypeToRenderCommandType[] = {
[CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_NONE,
[CLAY__LAYOUT_ELEMENT_TYPE_RECTANGLE] = CLAY_RENDER_COMMAND_TYPE_RECTANGLE,
[CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_NONE,
[CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_NONE,
[CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER] = CLAY_RENDER_COMMAND_TYPE_BORDER,
[CLAY__LAYOUT_ELEMENT_TYPE_IMAGE] = CLAY_RENDER_COMMAND_TYPE_IMAGE,
[CLAY__LAYOUT_ELEMENT_TYPE_TEXT] = CLAY_RENDER_COMMAND_TYPE_TEXT,
[CLAY__LAYOUT_ELEMENT_TYPE_CUSTOM] = CLAY_RENDER_COMMAND_TYPE_CUSTOM,
};
typedef enum __attribute__((__packed__)) {
CLAY_ATTACH_POINT_LEFT_TOP,
CLAY_ATTACH_POINT_LEFT_CENTER,
CLAY_ATTACH_POINT_LEFT_BOTTOM,
CLAY_ATTACH_POINT_CENTER_TOP,
CLAY_ATTACH_POINT_CENTER_CENTER,
CLAY_ATTACH_POINT_CENTER_BOTTOM,
CLAY_ATTACH_POINT_RIGHT_TOP,
CLAY_ATTACH_POINT_RIGHT_CENTER,
CLAY_ATTACH_POINT_RIGHT_BOTTOM,
} Clay_FloatingAttachPointType;
typedef struct
{
Clay_FloatingAttachPointType element;
Clay_FloatingAttachPointType parent;
} Clay_FloatingAttachPoints;
typedef struct {
Clay_LayoutAlignmentX x;
Clay_LayoutAlignmentY y;
} Clay_ChildAlignment;
typedef struct {
float min;
float max;
} Clay_SizingMinMax;
typedef struct {
union {
Clay_SizingMinMax sizeMinMax;
float sizePercent;
};
Clay__SizingType type;
} Clay_SizingAxis;
typedef struct {
Clay_SizingAxis width;
Clay_SizingAxis height;
} Clay_Sizing;
typedef struct {
uint16_t x;
uint16_t y;
} Clay_Padding;
typedef struct {
float topLeft;
float topRight;
float bottomLeft;
float bottomRight;
} Clay_CornerRadius;
typedef struct {
Clay_Sizing sizing;
Clay_Padding padding;
uint16_t childGap;
Clay_LayoutDirection layoutDirection;
Clay_ChildAlignment childAlignment;
} Clay_LayoutConfig;
Clay_LayoutConfig CLAY_LAYOUT_DEFAULT = (Clay_LayoutConfig){};
// __GENERATED__ template array_define,array_add TYPE=Clay_LayoutConfig NAME=Clay_LayoutConfigArray DEFAULT_VALUE=&CLAY_LAYOUT_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_LayoutConfig *internalArray;
} Clay_LayoutConfigArray;
Clay_LayoutConfigArray Clay_LayoutConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_LayoutConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutConfig), CLAY__ALIGNMENT(Clay_LayoutConfig), arena)};
}
Clay_LayoutConfig *Clay_LayoutConfigArray_Add(Clay_LayoutConfigArray *array, Clay_LayoutConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY_LAYOUT_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct {
Clay_Color color;
Clay_CornerRadius cornerRadius;
#ifdef CLAY_EXTEND_CONFIG_RECTANGLE
CLAY_EXTEND_CONFIG_RECTANGLE
#endif
} Clay_RectangleElementConfig;
Clay_RectangleElementConfig CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT = (Clay_RectangleElementConfig){0};
// __GENERATED__ template array_define,array_add TYPE=Clay_RectangleElementConfig NAME=Clay_RectangleElementConfigArray DEFAULT_VALUE=&CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_RectangleElementConfig *internalArray;
} Clay_RectangleElementConfigArray;
Clay_RectangleElementConfigArray Clay_RectangleElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_RectangleElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_RectangleElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_RectangleElementConfig), CLAY__ALIGNMENT(Clay_RectangleElementConfig), arena)};
}
Clay_RectangleElementConfig *Clay_RectangleElementConfigArray_Add(Clay_RectangleElementConfigArray *array, Clay_RectangleElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__RECTANGLE_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_Color textColor;
uint16_t fontId;
uint16_t fontSize;
uint16_t letterSpacing;
uint16_t lineSpacing;
#ifdef CLAY_EXTEND_CONFIG_TEXT
CLAY_EXTEND_CONFIG_TEXT
#endif
} Clay_TextElementConfig;
Clay_TextElementConfig CLAY__TEXT_ELEMENT_CONFIG_DEFAULT = (Clay_TextElementConfig) {};
// __GENERATED__ template array_define,array_add TYPE=Clay_TextElementConfig NAME=Clay_TextElementConfigArray DEFAULT_VALUE=&CLAY__TEXT_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_TextElementConfig *internalArray;
} Clay_TextElementConfigArray;
Clay_TextElementConfigArray Clay_TextElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_TextElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_TextElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_TextElementConfig), CLAY__ALIGNMENT(Clay_TextElementConfig), arena)};
}
Clay_TextElementConfig *Clay_TextElementConfigArray_Add(Clay_TextElementConfigArray *array, Clay_TextElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__TEXT_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
void * imageData;
Clay_Dimensions sourceDimensions;
#ifdef CLAY_EXTEND_CONFIG_IMAGE
CLAY_EXTEND_CONFIG_IMAGE
#endif
} Clay_ImageElementConfig;
Clay_ImageElementConfig CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT = (Clay_ImageElementConfig) {};
// __GENERATED__ template array_define,array_add TYPE=Clay_ImageElementConfig NAME=Clay_ImageElementConfigArray DEFAULT_VALUE=&CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_ImageElementConfig *internalArray;
} Clay_ImageElementConfigArray;
Clay_ImageElementConfigArray Clay_ImageElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_ImageElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ImageElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ImageElementConfig), CLAY__ALIGNMENT(Clay_ImageElementConfig), arena)};
}
Clay_ImageElementConfig *Clay_ImageElementConfigArray_Add(Clay_ImageElementConfigArray *array, Clay_ImageElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__IMAGE_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_Vector2 offset;
Clay_Dimensions expand;
uint16_t zIndex;
uint32_t parentId;
Clay_FloatingAttachPoints attachment;
} Clay_FloatingElementConfig;
Clay_FloatingElementConfig CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT = (Clay_FloatingElementConfig) {};
// __GENERATED__ template array_define,array_add TYPE=Clay_FloatingElementConfig NAME=Clay_FloatingElementConfigArray DEFAULT_VALUE=&CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_FloatingElementConfig *internalArray;
} Clay_FloatingElementConfigArray;
Clay_FloatingElementConfigArray Clay_FloatingElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_FloatingElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_FloatingElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_FloatingElementConfig), CLAY__ALIGNMENT(Clay_FloatingElementConfig), arena)};
}
Clay_FloatingElementConfig *Clay_FloatingElementConfigArray_Add(Clay_FloatingElementConfigArray *array, Clay_FloatingElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__FLOATING_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
#ifndef CLAY_EXTEND_CONFIG_CUSTOM
void* customData;
#else
CLAY_EXTEND_CONFIG_CUSTOM
#endif
} Clay_CustomElementConfig;
Clay_CustomElementConfig CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT = (Clay_CustomElementConfig) {};
// __GENERATED__ template array_define,array_add TYPE=Clay_CustomElementConfig NAME=Clay_CustomElementConfigArray DEFAULT_VALUE=&CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_CustomElementConfig *internalArray;
} Clay_CustomElementConfigArray;
Clay_CustomElementConfigArray Clay_CustomElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_CustomElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_CustomElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_CustomElementConfig), CLAY__ALIGNMENT(Clay_CustomElementConfig), arena)};
}
Clay_CustomElementConfig *Clay_CustomElementConfigArray_Add(Clay_CustomElementConfigArray *array, Clay_CustomElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__CUSTOM_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
bool horizontal;
bool vertical;
} Clay_ScrollContainerElementConfig;
Clay_ScrollContainerElementConfig CLAY__SCROLL_CONTAINER_ELEMENT_CONFIG_DEFAULT = (Clay_ScrollContainerElementConfig ) {};
// __GENERATED__ template array_define,array_add TYPE=Clay_ScrollContainerElementConfig NAME=Clay_ScrollContainerElementConfigArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_ScrollContainerElementConfig *internalArray;
} Clay_ScrollContainerElementConfigArray;
Clay_ScrollContainerElementConfigArray Clay_ScrollContainerElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_ScrollContainerElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_ScrollContainerElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_ScrollContainerElementConfig), CLAY__ALIGNMENT(Clay_ScrollContainerElementConfig), arena)};
}
Clay_ScrollContainerElementConfig *Clay_ScrollContainerElementConfigArray_Add(Clay_ScrollContainerElementConfigArray *array, Clay_ScrollContainerElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__SCROLL_CONTAINER_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
uint32_t width;
Clay_Color color;
} Clay_Border;
typedef struct
{
Clay_Border left;
Clay_Border right;
Clay_Border top;
Clay_Border bottom;
Clay_Border betweenChildren;
Clay_CornerRadius cornerRadius;
} Clay_BorderContainerElementConfig;
Clay_BorderContainerElementConfig CLAY__BORDER_CONTAINER_ELEMENT_CONFIG_DEFAULT = (Clay_BorderContainerElementConfig ) {};
// __GENERATED__ template array_define,array_add TYPE=Clay_BorderContainerElementConfig NAME=Clay_BorderContainerElementConfigArray DEFAULT_VALUE=&CLAY__BORDER_CONTAINER_ELEMENT_CONFIG_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_BorderContainerElementConfig *internalArray;
} Clay_BorderContainerElementConfigArray;
Clay_BorderContainerElementConfigArray Clay_BorderContainerElementConfigArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_BorderContainerElementConfigArray){.capacity = capacity, .length = 0, .internalArray = (Clay_BorderContainerElementConfig *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_BorderContainerElementConfig), CLAY__ALIGNMENT(Clay_BorderContainerElementConfig), arena)};
}
Clay_BorderContainerElementConfig *Clay_BorderContainerElementConfigArray_Add(Clay_BorderContainerElementConfigArray *array, Clay_BorderContainerElementConfig item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__BORDER_CONTAINER_ELEMENT_CONFIG_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
struct t_Clay_LayoutElement **elements;
uint16_t length;
} Clay__LayoutElementChildren;
typedef union
{
Clay_RectangleElementConfig *rectangleElementConfig;
Clay_TextElementConfig *textElementConfig;
Clay_ImageElementConfig *imageElementConfig;
Clay_FloatingElementConfig *floatingElementConfig;
Clay_CustomElementConfig *customElementConfig;
Clay_ScrollContainerElementConfig *scrollElementConfig;
Clay_BorderContainerElementConfig *borderElementConfig;
} Clay_ElementConfigUnion;
typedef struct t_Clay_LayoutElement
{
#ifdef CLAY_DEBUG
Clay_String name;
#endif
union {
Clay__LayoutElementChildren children;
Clay_String text;
};
Clay_Dimensions dimensions;
Clay_Dimensions minDimensions;
Clay_LayoutConfig *layoutConfig;
Clay_ElementConfigUnion elementConfig;
uint32_t id;
Clay__LayoutElementType elementType;
} Clay_LayoutElement;
Clay_LayoutElement CLAY__LAYOUT_ELEMENT_DEFAULT = (Clay_LayoutElement) {};
// __GENERATED__ template array_define,array_add,array_get TYPE=Clay_LayoutElement NAME=Clay_LayoutElementArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_LayoutElement *internalArray;
} Clay_LayoutElementArray;
Clay_LayoutElementArray Clay_LayoutElementArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_LayoutElementArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElement *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElement), CLAY__ALIGNMENT(Clay_LayoutElement), arena)};
}
Clay_LayoutElement *Clay_LayoutElementArray_Add(Clay_LayoutElementArray *array, Clay_LayoutElement item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__LAYOUT_ELEMENT_DEFAULT;
}
Clay_LayoutElement *Clay_LayoutElementArray_Get(Clay_LayoutElementArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
// __GENERATED__ template array_define,array_add,array_get,array_remove_swapback TYPE=Clay_LayoutElement* NAME=Clay__LayoutElementPointerArray DEFAULT_VALUE=CLAY__NULL
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_LayoutElement* *internalArray;
} Clay__LayoutElementPointerArray;
Clay__LayoutElementPointerArray Clay__LayoutElementPointerArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__LayoutElementPointerArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElement* *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElement*), CLAY__ALIGNMENT(Clay_LayoutElement*), arena)};
}
Clay_LayoutElement* *Clay__LayoutElementPointerArray_Add(Clay__LayoutElementPointerArray *array, Clay_LayoutElement* item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return CLAY__NULL;
}
Clay_LayoutElement* *Clay__LayoutElementPointerArray_Get(Clay__LayoutElementPointerArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : CLAY__NULL;
}
Clay_LayoutElement* Clay__LayoutElementPointerArray_RemoveSwapback(Clay__LayoutElementPointerArray *array, int index) {
if (Clay__Array_RangeCheck(index, array->length)) {
array->length--;
Clay_LayoutElement* removed = array->internalArray[index];
array->internalArray[index] = array->internalArray[array->length];
return removed;
}
return CLAY__NULL;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_Rectangle boundingBox;
Clay_ElementConfigUnion config;
Clay_String text; // TODO I wish there was a way to avoid having to have this on every render command
uint32_t id;
Clay_RenderCommandType commandType;
} Clay_RenderCommand;
Clay_RenderCommand CLAY__RENDER_COMMAND_DEFAULT = (Clay_RenderCommand) {};
// __GENERATED__ template array_define TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_RenderCommand *internalArray;
} Clay_RenderCommandArray;
Clay_RenderCommandArray Clay_RenderCommandArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay_RenderCommandArray){.capacity = capacity, .length = 0, .internalArray = (Clay_RenderCommand *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_RenderCommand), CLAY__ALIGNMENT(Clay_RenderCommand), arena)};
}
#pragma endregion
// __GENERATED__ template
// __GENERATED__ template array_add TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray DEFAULT_VALUE=&CLAY__RENDER_COMMAND_DEFAULT
#pragma region generated
Clay_RenderCommand *Clay_RenderCommandArray_Add(Clay_RenderCommandArray *array, Clay_RenderCommand item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__RENDER_COMMAND_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
// __GENERATED__ template array_get TYPE=Clay_RenderCommand NAME=Clay_RenderCommandArray DEFAULT_VALUE=&CLAY__RENDER_COMMAND_DEFAULT
#pragma region generated
Clay_RenderCommand *Clay_RenderCommandArray_Get(Clay_RenderCommandArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__RENDER_COMMAND_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_LayoutElement *layoutElement;
Clay_Rectangle boundingBox;
Clay_Dimensions contentSize;
Clay_Vector2 scrollOrigin;
Clay_Vector2 pointerOrigin;
Clay_Vector2 scrollMomentum;
Clay_Vector2 scrollPosition;
Clay_Vector2 previousDelta;
float momentumTime;
uint32_t elementId;
bool openThisFrame;
bool pointerScrollActive;
} Clay__ScrollContainerDataInternal;
Clay__ScrollContainerDataInternal CLAY__SCROLL_CONTAINER_DEFAULT = (Clay__ScrollContainerDataInternal) {};
// __GENERATED__ template define,array_add,array_get TYPE=Clay__ScrollContainerDataInternal NAME=Clay__ScrollContainerDataInternalArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay__ScrollContainerDataInternal *internalArray;
} Clay__ScrollContainerDataInternalArray;
Clay__ScrollContainerDataInternalArray Clay__ScrollContainerDataInternalArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__ScrollContainerDataInternalArray){.capacity = capacity, .length = 0, .internalArray = (Clay__ScrollContainerDataInternal *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__ScrollContainerDataInternal), CLAY__ALIGNMENT(Clay__ScrollContainerDataInternal), arena)};
}
Clay__ScrollContainerDataInternal *Clay__ScrollContainerDataInternalArray_Add(Clay__ScrollContainerDataInternalArray *array, Clay__ScrollContainerDataInternal item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__SCROLL_CONTAINER_DEFAULT;
}
Clay__ScrollContainerDataInternal *Clay__ScrollContainerDataInternalArray_Get(Clay__ScrollContainerDataInternalArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__SCROLL_CONTAINER_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
// __GENERATED__ template array_remove_swapback TYPE=Clay__ScrollContainerDataInternal NAME=Clay__ScrollContainerDataInternalArray DEFAULT_VALUE=CLAY__SCROLL_CONTAINER_DEFAULT
#pragma region generated
Clay__ScrollContainerDataInternal Clay__ScrollContainerDataInternalArray_RemoveSwapback(Clay__ScrollContainerDataInternalArray *array, int index) {
if (Clay__Array_RangeCheck(index, array->length)) {
array->length--;
Clay__ScrollContainerDataInternal removed = array->internalArray[index];
array->internalArray[index] = array->internalArray[array->length];
return removed;
}
return CLAY__SCROLL_CONTAINER_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_Rectangle boundingBox;
uint32_t id;
Clay_LayoutElement* layoutElement;
int32_t nextIndex;
} Clay_LayoutElementHashMapItem;
Clay_LayoutElementHashMapItem CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT = (Clay_LayoutElementHashMapItem) { .layoutElement = &CLAY__LAYOUT_ELEMENT_DEFAULT };
// __GENERATED__ template array_define,array_get,array_add,array_set TYPE=Clay_LayoutElementHashMapItem NAME=Clay__LayoutElementHashMapItemArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay_LayoutElementHashMapItem *internalArray;
} Clay__LayoutElementHashMapItemArray;
Clay__LayoutElementHashMapItemArray Clay__LayoutElementHashMapItemArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__LayoutElementHashMapItemArray){.capacity = capacity, .length = 0, .internalArray = (Clay_LayoutElementHashMapItem *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay_LayoutElementHashMapItem), CLAY__ALIGNMENT(Clay_LayoutElementHashMapItem), arena)};
}
Clay_LayoutElementHashMapItem *Clay__LayoutElementHashMapItemArray_Get(Clay__LayoutElementHashMapItemArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT;
}
Clay_LayoutElementHashMapItem *Clay__LayoutElementHashMapItemArray_Add(Clay__LayoutElementHashMapItemArray *array, Clay_LayoutElementHashMapItem item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__LAYOUT_ELEMENT_HASH_MAP_ITEM_DEFAULT;
}
void Clay__LayoutElementHashMapItemArray_Set(Clay__LayoutElementHashMapItemArray *array, int index, Clay_LayoutElementHashMapItem value) {
if (index < array->capacity && index >= 0) {
array->internalArray[index] = value;
array->length = index < array->length ? array->length : index + 1;
} else {
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
}
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_Dimensions dimensions;
uint32_t id;
int32_t nextIndex;
} Clay__MeasureTextCacheItem;
Clay__MeasureTextCacheItem CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT = (Clay__MeasureTextCacheItem) { };
// __GENERATED__ template array_define,array_get,array_add,array_set TYPE=Clay__MeasureTextCacheItem NAME=Clay__MeasureTextCacheItemArray DEFAULT_VALUE=&CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay__MeasureTextCacheItem *internalArray;
} Clay__MeasureTextCacheItemArray;
Clay__MeasureTextCacheItemArray Clay__MeasureTextCacheItemArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__MeasureTextCacheItemArray){.capacity = capacity, .length = 0, .internalArray = (Clay__MeasureTextCacheItem *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__MeasureTextCacheItem), CLAY__ALIGNMENT(Clay__MeasureTextCacheItem), arena)};
}
Clay__MeasureTextCacheItem *Clay__MeasureTextCacheItemArray_Get(Clay__MeasureTextCacheItemArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT;
}
Clay__MeasureTextCacheItem *Clay__MeasureTextCacheItemArray_Add(Clay__MeasureTextCacheItemArray *array, Clay__MeasureTextCacheItem item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__MEASURE_TEXT_CACHE_ITEM_DEFAULT;
}
void Clay__MeasureTextCacheItemArray_Set(Clay__MeasureTextCacheItemArray *array, int index, Clay__MeasureTextCacheItem value) {
if (index < array->capacity && index >= 0) {
array->internalArray[index] = value;
array->length = index < array->length ? array->length : index + 1;
} else {
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
}
}
#pragma endregion
// __GENERATED__ template
int32_t CLAY__INDEX_ARRAY_DEFAULT_VALUE = -1;
// __GENERATED__ template array_define,array_get,array_add,array_set TYPE=int32_t NAME=Clay__int32_tArray DEFAULT_VALUE=&CLAY__INDEX_ARRAY_DEFAULT_VALUE
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
int32_t *internalArray;
} Clay__int32_tArray;
Clay__int32_tArray Clay__int32_tArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__int32_tArray){.capacity = capacity, .length = 0, .internalArray = (int32_t *)Clay__Array_Allocate_Arena(capacity, sizeof(int32_t), CLAY__ALIGNMENT(int32_t), arena)};
}
int32_t *Clay__int32_tArray_Get(Clay__int32_tArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__INDEX_ARRAY_DEFAULT_VALUE;
}
int32_t *Clay__int32_tArray_Add(Clay__int32_tArray *array, int32_t item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__INDEX_ARRAY_DEFAULT_VALUE;
}
void Clay__int32_tArray_Set(Clay__int32_tArray *array, int index, int32_t value) {
if (index < array->capacity && index >= 0) {
array->internalArray[index] = value;
array->length = index < array->length ? array->length : index + 1;
} else {
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow."));
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
}
}
#pragma endregion
// __GENERATED__ template
Clay_LayoutElement *Clay__openLayoutElement = CLAY__NULL;
typedef struct
{
Clay_LayoutElement *layoutElement;
Clay_Vector2 position;
Clay_Vector2 nextChildOffset;
} Clay__LayoutElementTreeNode;
Clay__LayoutElementTreeNode CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT = (Clay__LayoutElementTreeNode) {};
// __GENERATED__ template array_define,array_add,array_get TYPE=Clay__LayoutElementTreeNode NAME=Clay__LayoutElementTreeNodeArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay__LayoutElementTreeNode *internalArray;
} Clay__LayoutElementTreeNodeArray;
Clay__LayoutElementTreeNodeArray Clay__LayoutElementTreeNodeArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__LayoutElementTreeNodeArray){.capacity = capacity, .length = 0, .internalArray = (Clay__LayoutElementTreeNode *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__LayoutElementTreeNode), CLAY__ALIGNMENT(Clay__LayoutElementTreeNode), arena)};
}
Clay__LayoutElementTreeNode *Clay__LayoutElementTreeNodeArray_Add(Clay__LayoutElementTreeNodeArray *array, Clay__LayoutElementTreeNode item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT;
}
Clay__LayoutElementTreeNode *Clay__LayoutElementTreeNodeArray_Get(Clay__LayoutElementTreeNodeArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_TREE_NODE_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
typedef struct
{
Clay_LayoutElement *layoutElement;
uint32_t parentId; // This can be zero in the case of the root layout tree
uint32_t clipElementId; // This can be zero if there is no clip element
uint32_t zIndex;
} Clay__LayoutElementTreeRoot;
Clay__LayoutElementTreeRoot CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT = (Clay__LayoutElementTreeRoot) {};
// __GENERATED__ template array_define,array_add,array_get TYPE=Clay__LayoutElementTreeRoot NAME=Clay__LayoutElementTreeRootArray DEFAULT_VALUE=&CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT
#pragma region generated
typedef struct
{
uint32_t capacity;
uint32_t length;
Clay__LayoutElementTreeRoot *internalArray;
} Clay__LayoutElementTreeRootArray;
Clay__LayoutElementTreeRootArray Clay__LayoutElementTreeRootArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
return (Clay__LayoutElementTreeRootArray){.capacity = capacity, .length = 0, .internalArray = (Clay__LayoutElementTreeRoot *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__LayoutElementTreeRoot), CLAY__ALIGNMENT(Clay__LayoutElementTreeRoot), arena)};
}
Clay__LayoutElementTreeRoot *Clay__LayoutElementTreeRootArray_Add(Clay__LayoutElementTreeRootArray *array, Clay__LayoutElementTreeRoot item) {
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
array->internalArray[array->length++] = item;
return &array->internalArray[array->length - 1];
}
return &CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT;
}
Clay__LayoutElementTreeRoot *Clay__LayoutElementTreeRootArray_Get(Clay__LayoutElementTreeRootArray *array, int index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : &CLAY__LAYOUT_ELEMENT_TREE_ROOT_DEFAULT;
}
#pragma endregion
// __GENERATED__ template
Clay_Vector2 Clay__pointerPosition = (Clay_Vector2) { -1, -1 };
uint64_t Clay__arenaResetOffset = 0;
Clay_Arena Clay__internalArena;
// Layout Elements / Render Commands
Clay_LayoutElementArray Clay__layoutElements;
Clay_RenderCommandArray Clay__renderCommands;
Clay__LayoutElementPointerArray Clay__openLayoutElementStack;
Clay__LayoutElementPointerArray Clay__layoutElementChildren;
Clay__LayoutElementPointerArray Clay__layoutElementChildrenBuffer;
Clay__LayoutElementPointerArray Clay__textElementPointers;
Clay__LayoutElementPointerArray Clay__imageElementPointers;
Clay__LayoutElementPointerArray Clay__layoutElementReusableBuffer;
// Configs
Clay_LayoutConfigArray Clay__layoutConfigs;
Clay_RectangleElementConfigArray Clay__rectangleElementConfigs;
Clay_TextElementConfigArray Clay__textElementConfigs;
Clay_ImageElementConfigArray Clay__imageElementConfigs;
Clay_FloatingElementConfigArray Clay__floatingElementConfigs;
Clay_ScrollContainerElementConfigArray Clay__scrollElementConfigs;
Clay_CustomElementConfigArray Clay__customElementConfigs;
Clay_BorderContainerElementConfigArray Clay__borderElementConfigs;
// Misc Data Structures
Clay__LayoutElementTreeNodeArray Clay__layoutElementTreeNodeArray1;
Clay__LayoutElementTreeRootArray Clay__layoutElementTreeRoots;
Clay__LayoutElementHashMapItemArray Clay__layoutElementsHashMapInternal;
Clay__int32_tArray Clay__layoutElementsHashMap;
Clay__MeasureTextCacheItemArray Clay__measureTextHashMapInternal;
Clay__int32_tArray Clay__measureTextHashMap;
Clay__int32_tArray Clay__openClipElementStack;
Clay__int32_tArray Clay__pointerOverIds;
Clay__ScrollContainerDataInternalArray Clay__scrollContainerDatas;
Clay__BoolArray Clay__treeNodeVisited;
#if CLAY_WASM
__attribute__((import_module("clay"), import_name("measureTextFunction"))) Clay_Dimensions Clay__MeasureText(Clay_String *text, Clay_TextElementConfig *config);
#else
Clay_Dimensions (*Clay__MeasureText)(Clay_String *text, Clay_TextElementConfig *config);
#endif
Clay_String LAST_HASH;
uint32_t Clay__HashString(Clay_String key, const uint32_t offset) {
uint32_t hash = 0;
for (int i = 0; i < key.length; i++) {
hash += key.chars[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += offset;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
#ifdef CLAY_DEBUG
LAST_HASH = key;
LAST_HASH.length = (int)offset;
#endif
return hash + 1; // Reserve the hash result of zero as "null id"
}
uint32_t Clay__RehashWithNumber(uint32_t id, uint32_t number) {
id += number;
id += (id << 10);
id ^= (id >> 6);
id += (id << 3);
id ^= (id >> 11);
id += (id << 15);
return id;
}
uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
union {
float fontSize;
uint32_t bits;
} fontSizeBits = { .fontSize = config->fontSize };
uint32_t hash = 0;
uint64_t pointerAsNumber = (uint64_t)text->chars;
hash += pointerAsNumber;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->fontId;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += fontSizeBits.bits;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash + 1; // Reserve the hash result of zero as "null id"
}
Clay_Dimensions Clay__MeasureTextCached(Clay_String *text, Clay_TextElementConfig *config) {
if (text->length < 50) {
return Clay__MeasureText(text, config);
}
uint32_t id = Clay__HashTextWithConfig(text, config);
uint32_t hashBucket = id % Clay__measureTextHashMap.capacity;
int32_t elementIndexPrevious = 0;
int32_t elementIndex = Clay__measureTextHashMap.internalArray[hashBucket];
while (elementIndex != 0) {
Clay__MeasureTextCacheItem *hashEntry = Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndex);
if (hashEntry->id == id) {
return hashEntry->dimensions;
}
elementIndexPrevious = elementIndex;
elementIndex = hashEntry->nextIndex;
}
Clay_Dimensions measured = Clay__MeasureText(text, config);
if (elementIndexPrevious != 0) {
Clay__MeasureTextCacheItemArray_Get(&Clay__measureTextHashMapInternal, elementIndexPrevious)->nextIndex = (int32_t)Clay__measureTextHashMapInternal.length - 1;
} else {
Clay__measureTextHashMap.internalArray[hashBucket] = (int32_t)Clay__measureTextHashMapInternal.length - 1;
}
return measured;
}
bool Clay__PointIsInsideRect(Clay_Vector2 point, Clay_Rectangle rect) {
return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
}
Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(uint32_t id, Clay_LayoutElement* layoutElement) {
Clay_LayoutElementHashMapItem item = (Clay_LayoutElementHashMapItem) { .id = id, .layoutElement = layoutElement, .nextIndex = -1 };
uint32_t hashBucket = id % Clay__layoutElementsHashMap.capacity;
int32_t hashItemPrevious = -1;
int32_t hashItemIndex = Clay__layoutElementsHashMap.internalArray[hashBucket];
while (hashItemIndex != -1) { // Just replace collision, not a big deal - leave it up to the end user
Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemIndex);
if (hashItem->id == id) { // Collision - swap out linked list item
item.nextIndex = hashItem->nextIndex;
Clay__LayoutElementHashMapItemArray_Set(&Clay__layoutElementsHashMapInternal, hashItemIndex, item);
return Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemIndex);
}
hashItemPrevious = hashItemIndex;
hashItemIndex = hashItem->nextIndex;
}
if (hashItemPrevious != -1) {
Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, hashItemPrevious)->nextIndex = (int32_t)Clay__layoutElementsHashMapInternal.length;
}
Clay_LayoutElementHashMapItem *hashItem = Clay__LayoutElementHashMapItemArray_Add(&Clay__layoutElementsHashMapInternal, item);
Clay__layoutElementsHashMap.internalArray[hashBucket] = (int32_t)Clay__layoutElementsHashMapInternal.length - 1;
return hashItem;
}
Clay_LayoutElementHashMapItem *Clay__GetHashMapItem(uint32_t id) {
uint32_t hashBucket = id % Clay__layoutElementsHashMap.capacity;
int32_t elementIndex = Clay__layoutElementsHashMap.internalArray[hashBucket];
while (elementIndex != -1) {
Clay_LayoutElementHashMapItem *hashEntry = Clay__LayoutElementHashMapItemArray_Get(&Clay__layoutElementsHashMapInternal, elementIndex);
if (hashEntry->id == id) {
return hashEntry;
}
elementIndex = hashEntry->nextIndex;
}
return CLAY__NULL;
}
Clay_LayoutElement *Clay__OpenElementWithParent(uint32_t id, Clay__LayoutElementType commandType, Clay_LayoutConfig* layoutConfig, Clay_ElementConfigUnion elementConfig) {
Clay_LayoutElement layoutElement = (Clay_LayoutElement) {
#ifdef CLAY_DEBUG
.name = LAST_HASH,
#endif
.id = id,
.elementType = commandType,
.minDimensions = (Clay_Dimensions) { (float)layoutConfig->padding.x * 2, (float)layoutConfig->padding.y * 2 },
.children = (Clay__LayoutElementChildren) { .length = 0 },
.layoutConfig = layoutConfig,
.elementConfig = elementConfig,
};
if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) {
layoutElement.dimensions.width = (float)layoutConfig->padding.x * 2;
layoutElement.minDimensions.width = CLAY__MAX(layoutElement.minDimensions.width, layoutConfig->sizing.width.sizeMinMax.min);
if (layoutConfig->sizing.width.sizeMinMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier
layoutConfig->sizing.width.sizeMinMax.max = CLAY__MAXFLOAT;
}
}
if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) {
layoutElement.dimensions.height = (float)layoutConfig->padding.y * 2;
layoutElement.minDimensions.height = CLAY__MAX(layoutElement.minDimensions.height, layoutConfig->sizing.height.sizeMinMax.min);
if (layoutConfig->sizing.height.sizeMinMax.max <= 0) { // Set the max size if the user didn't specify, makes calculations easier
layoutConfig->sizing.height.sizeMinMax.max = CLAY__MAXFLOAT;
}
}
Clay__openLayoutElement = Clay_LayoutElementArray_Add(&Clay__layoutElements, layoutElement);
Clay__LayoutElementPointerArray_Add(&Clay__openLayoutElementStack, Clay__openLayoutElement);
Clay__AddHashMapItem(id, Clay__openLayoutElement);
return Clay__openLayoutElement;
}
Clay_LayoutElement *Clay__OpenElement(uint32_t id, Clay__LayoutElementType commandType, Clay_LayoutConfig *layoutConfig, Clay_ElementConfigUnion elementConfig) {
Clay__openLayoutElement->children.length++;
Clay_LayoutElement *element = Clay__OpenElementWithParent(id, commandType, layoutConfig, elementConfig);
Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildrenBuffer, element);
return element;
}
void Clay__OpenContainerElement(int id, Clay_LayoutConfig *layoutConfig) {
Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ CLAY__NULL });
}
void Clay__OpenRectangleElement(int id, Clay_LayoutConfig *layoutConfig, Clay_RectangleElementConfig *rectangleConfig) {
Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_RECTANGLE, layoutConfig, (Clay_ElementConfigUnion) { .rectangleElementConfig = rectangleConfig });
}
void Clay__OpenImageContainerElement(int id, Clay_LayoutConfig *layoutConfig, Clay_ImageElementConfig *imageConfig) {
Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_IMAGE, layoutConfig, (Clay_ElementConfigUnion) { .imageElementConfig = imageConfig });
Clay__LayoutElementPointerArray_Add(&Clay__imageElementPointers, Clay__openLayoutElement);
}
void Clay__OpenBorderContainerElement(int id, Clay_LayoutConfig *layoutConfig, Clay_BorderContainerElementConfig *borderConfig) {
Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ .borderElementConfig = borderConfig });
}
void Clay__OpenCustomElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_CustomElementConfig *customElementConfig) {
Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_CUSTOM, layoutConfig, (Clay_ElementConfigUnion) { .customElementConfig = customElementConfig });
}
Clay_LayoutElement *Clay__OpenScrollElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_ScrollContainerElementConfig *scrollConfig) {
Clay_LayoutElement *scrollElement = Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER, layoutConfig, (Clay_ElementConfigUnion){ .scrollElementConfig = scrollConfig });
Clay__int32_tArray_Add(&Clay__openClipElementStack, (int)scrollElement->id);
Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL;
for (int i = 0; i < Clay__scrollContainerDatas.length; i++) {
Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i);
if (id == mapping->elementId) {
scrollOffset = mapping;
scrollOffset->layoutElement = scrollElement;
scrollOffset->openThisFrame = true;
}
}
if (!scrollOffset) {
Clay__ScrollContainerDataInternalArray_Add(&Clay__scrollContainerDatas, (Clay__ScrollContainerDataInternal){.elementId = id, .layoutElement = scrollElement, .scrollOrigin = {-1,-1}, .openThisFrame = true});
}
return scrollElement;
}
Clay_LayoutElement *Clay__OpenFloatingContainerElement(uint32_t id, Clay_LayoutConfig *layoutConfig, Clay_FloatingElementConfig *floatingElementConfig) {
Clay_LayoutElement *parent = Clay__openLayoutElement;
if (floatingElementConfig->parentId > 0) {
Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingElementConfig->parentId);
if (!parentItem) {
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Clay Warning: Couldn't find parent container to attach floating container to."));
} else {
parent = parentItem->layoutElement;
}
}
Clay__OpenElementWithParent(id, CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER, layoutConfig, (Clay_ElementConfigUnion) { .floatingElementConfig = floatingElementConfig });
Clay__LayoutElementTreeRootArray_Add(&Clay__layoutElementTreeRoots, (Clay__LayoutElementTreeRoot) {
.layoutElement = Clay__openLayoutElement,
.parentId = parent->id,
.zIndex = floatingElementConfig->zIndex,
.clipElementId = Clay__openClipElementStack.length > 0 ? (uint32_t)*Clay__int32_tArray_Get(&Clay__openClipElementStack, (int)Clay__openClipElementStack.length - 1) : 0,
});
return Clay__openLayoutElement;
}
void Clay__AttachContainerChildren() {
Clay_LayoutConfig *layoutConfig = Clay__openLayoutElement->layoutConfig;
Clay__openLayoutElement->children.elements = &Clay__layoutElementChildren.internalArray[Clay__layoutElementChildren.length];
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
for (int i = 0; i < Clay__openLayoutElement->children.length; i++) {
Clay_LayoutElement *child = *Clay__LayoutElementPointerArray_Get(&Clay__layoutElementChildrenBuffer, (int)Clay__layoutElementChildrenBuffer.length - Clay__openLayoutElement->children.length + i);
Clay__openLayoutElement->dimensions.width += child->dimensions.width;
Clay__openLayoutElement->dimensions.height = CLAY__MAX(Clay__openLayoutElement->dimensions.height, child->dimensions.height + layoutConfig->padding.y * 2);
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->horizontal) {
Clay__openLayoutElement->minDimensions.width += child->minDimensions.width;
}
if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->vertical) {
Clay__openLayoutElement->minDimensions.height = CLAY__MAX(Clay__openLayoutElement->minDimensions.height, child->minDimensions.height + layoutConfig->padding.y * 2);
}
Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildren, child);
}
float childGap = (float)(CLAY__MAX(Clay__openLayoutElement->children.length - 1, 0) * layoutConfig->childGap);
Clay__openLayoutElement->dimensions.width += childGap;
Clay__openLayoutElement->minDimensions.width += childGap;
}
else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) {
for (int i = 0; i < Clay__openLayoutElement->children.length; i++) {
Clay_LayoutElement *child = *Clay__LayoutElementPointerArray_Get(&Clay__layoutElementChildrenBuffer, (int)Clay__layoutElementChildrenBuffer.length - Clay__openLayoutElement->children.length + i);
Clay__openLayoutElement->dimensions.height += child->dimensions.height;
Clay__openLayoutElement->dimensions.width = CLAY__MAX(Clay__openLayoutElement->dimensions.width, child->dimensions.width + layoutConfig->padding.x * 2);
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->vertical) {
Clay__openLayoutElement->minDimensions.height += child->minDimensions.height;
}
if (Clay__openLayoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || !Clay__openLayoutElement->elementConfig.scrollElementConfig->horizontal) {
Clay__openLayoutElement->minDimensions.width = CLAY__MAX(Clay__openLayoutElement->minDimensions.width, child->minDimensions.width + layoutConfig->padding.x * 2);
}
Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildren, child);
}
float childGap = (float)(CLAY__MAX(Clay__openLayoutElement->children.length - 1, 0) * layoutConfig->childGap);
Clay__openLayoutElement->dimensions.height += childGap;
Clay__openLayoutElement->minDimensions.height += childGap;
}
Clay__layoutElementChildrenBuffer.length -= Clay__openLayoutElement->children.length;
}
void Clay__CloseElement() {
Clay_LayoutConfig *layoutConfig = Clay__openLayoutElement->layoutConfig;
if (layoutConfig->sizing.width.type != CLAY__SIZING_TYPE_PERCENT) {
// TODO I think minsize has already been applied by this point so no need to do it again
Clay__openLayoutElement->dimensions.width = CLAY__MIN(CLAY__MAX(Clay__openLayoutElement->dimensions.width, layoutConfig->sizing.width.sizeMinMax.min), layoutConfig->sizing.width.sizeMinMax.max);
} else {
Clay__openLayoutElement->dimensions.width = 0;
}
if (layoutConfig->sizing.height.type != CLAY__SIZING_TYPE_PERCENT) {
Clay__openLayoutElement->dimensions.height = CLAY__MIN(CLAY__MAX(Clay__openLayoutElement->dimensions.height, layoutConfig->sizing.height.sizeMinMax.min), layoutConfig->sizing.height.sizeMinMax.max);
} else {
Clay__openLayoutElement->dimensions.height = 0;
}
Clay__LayoutElementPointerArray_RemoveSwapback(&Clay__openLayoutElementStack, (int)Clay__openLayoutElementStack.length - 1);
Clay__openLayoutElement = *Clay__LayoutElementPointerArray_Get(&Clay__openLayoutElementStack, (int)Clay__openLayoutElementStack.length - 1);
}
void Clay__OpenTextElement(int id, Clay_String text, Clay_TextElementConfig *textConfig) {
Clay_LayoutElement *internalElement = Clay__OpenElement(id, CLAY__LAYOUT_ELEMENT_TYPE_TEXT, &CLAY_LAYOUT_DEFAULT, (Clay_ElementConfigUnion) { .textElementConfig = textConfig });
Clay_Dimensions textMeasured = Clay__MeasureTextCached(&text, textConfig);
internalElement->dimensions.width = textMeasured.width;
internalElement->dimensions.height = textMeasured.height;
internalElement->text = text;
internalElement->minDimensions = (Clay_Dimensions) { .width = textMeasured.height, .height = textMeasured.height }; // TODO not sure this is the best way to decide min width for text
Clay__LayoutElementPointerArray_Add(&Clay__textElementPointers, internalElement);
Clay__CloseElement();
}
void Clay__CloseContainerElement() {
Clay__AttachContainerChildren();
Clay__CloseElement();
}
void Clay__CloseScrollContainerElement() {
Clay__openClipElementStack.length--;
Clay__CloseContainerElement();
}
void Clay__CloseFloatingContainer() {
Clay__AttachContainerChildren();
Clay__CloseElement();
}
void Clay__InitializeEphemeralMemory(Clay_Arena *arena) {
// Ephemeral Memory - reset every frame
Clay__internalArena.nextAllocation = Clay__arenaResetOffset;
Clay__layoutElementChildrenBuffer = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__layoutElements = Clay_LayoutElementArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay_warnings = Clay_StringArray_Allocate_Arena(100, arena);
Clay__layoutConfigs = Clay_LayoutConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__rectangleElementConfigs = Clay_RectangleElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__textElementConfigs = Clay_TextElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__imageElementConfigs = Clay_ImageElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__floatingElementConfigs = Clay_FloatingElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__scrollElementConfigs = Clay_ScrollContainerElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__customElementConfigs = Clay_CustomElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__borderElementConfigs = Clay_BorderContainerElementConfigArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__layoutElementTreeNodeArray1 = Clay__LayoutElementTreeNodeArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__layoutElementTreeRoots = Clay__LayoutElementTreeRootArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__layoutElementChildren = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__openLayoutElementStack = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__textElementPointers = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__imageElementPointers = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__layoutElementReusableBuffer = Clay__LayoutElementPointerArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__renderCommands = Clay_RenderCommandArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__treeNodeVisited = Clay__BoolArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__treeNodeVisited.length = Clay__treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list
Clay__openClipElementStack = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
}
void Clay__InitializePersistentMemory(Clay_Arena *arena) {
Clay__scrollContainerDatas = Clay__ScrollContainerDataInternalArray_Allocate_Arena(10, arena);
Clay__layoutElementsHashMapInternal = Clay__LayoutElementHashMapItemArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__layoutElementsHashMap = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__measureTextHashMapInternal = Clay__MeasureTextCacheItemArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__measureTextHashMap = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__pointerOverIds = Clay__int32_tArray_Allocate_Arena(CLAY_MAX_ELEMENT_COUNT, arena);
Clay__arenaResetOffset = arena->nextAllocation;
}
typedef enum
{
CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER,
CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER,
CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER,
} Clay__SizeDistributionType;
// Because of the max and min sizing properties, we can't predict ahead of time how (or if) all the excess width
// will actually be distributed. So we keep looping until either all the excess width is distributed or
// we have exhausted all our containers that can change size along this axis
float Clay__DistributeSizeAmongChildren(bool xAxis, float sizeToDistribute, Clay__LayoutElementPointerArray resizableContainerBuffer, Clay__SizeDistributionType distributionType) {
Clay__LayoutElementPointerArray backBuffer = Clay__layoutElementReusableBuffer;
backBuffer.length = 0;
Clay__LayoutElementPointerArray remainingElements = resizableContainerBuffer;
float totalDistributedSize;
while (sizeToDistribute != 0 && remainingElements.length > 0) {
totalDistributedSize = 0;
for (int childOffset = 0; childOffset < remainingElements.length; childOffset++) {
Clay_LayoutElement *childElement = *Clay__LayoutElementPointerArray_Get(&remainingElements, childOffset);
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
float childMinSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
if ((sizeToDistribute < 0 && *childSize == childSizing.sizeMinMax.min) || (sizeToDistribute > 0 && *childSize == childSizing.sizeMinMax.max)) {
continue;
}
if (!xAxis && childElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_IMAGE) {
continue; // Currently, we don't support squishing aspect ratio images on their Y axis as it would break ratio
}
switch (distributionType) {
case CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER: break;
case CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER: if (childSizing.type != CLAY__SIZING_TYPE_GROW) { continue; } break;
case CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER: if ((childElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER || (xAxis && !childElement->elementConfig.scrollElementConfig->horizontal) || (!xAxis && !childElement->elementConfig.scrollElementConfig->vertical))) { continue; } break;
}
float dividedSize = sizeToDistribute / (float)(remainingElements.length - childOffset);
float oldChildSize = *childSize;
*childSize = CLAY__MAX(CLAY__MAX(CLAY__MIN(childSizing.sizeMinMax.max, *childSize + dividedSize), childSizing.sizeMinMax.min), childMinSize);
float diff = *childSize - oldChildSize;
if (diff != 0) {
Clay__LayoutElementPointerArray_Add(&backBuffer, childElement);
}
sizeToDistribute -= diff;
totalDistributedSize += diff;
}
if (totalDistributedSize == 0) {
break;
}
// Flip the buffers
Clay__LayoutElementPointerArray temp = remainingElements;
remainingElements = backBuffer;
backBuffer = temp;
}
return sizeToDistribute;
}
void Clay__SizeContainersAlongAxis(bool xAxis) {
Clay__LayoutElementPointerArray bfsBuffer = Clay__layoutElementChildrenBuffer;
Clay__LayoutElementPointerArray resizableContainerBuffer = Clay__openLayoutElementStack;
for (int rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) {
bfsBuffer.length = 0;
Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex);
Clay_LayoutElement *rootElement = root->layoutElement;
Clay__LayoutElementPointerArray_Add(&bfsBuffer, root->layoutElement);
// Size floating containers to their parents
if (rootElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER) {
Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(rootElement->elementConfig.floatingElementConfig->parentId);
if (parentItem) {
Clay_LayoutElement *parentLayoutElement = parentItem->layoutElement;
if (rootElement->layoutConfig->sizing.width.type == CLAY__SIZING_TYPE_GROW) {
rootElement->dimensions.width = parentLayoutElement->dimensions.width - (float)parentLayoutElement->layoutConfig->padding.x * 2;
}
if (rootElement->layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_GROW) {
rootElement->dimensions.height = parentLayoutElement->dimensions.height - (float)parentLayoutElement->layoutConfig->padding.x * 2;
}
}
}
rootElement->dimensions.width = CLAY__MIN(CLAY__MAX(rootElement->dimensions.width, rootElement->layoutConfig->sizing.width.sizeMinMax.min), rootElement->layoutConfig->sizing.width.sizeMinMax.max);
rootElement->dimensions.height = CLAY__MIN(CLAY__MAX(rootElement->dimensions.height, rootElement->layoutConfig->sizing.height.sizeMinMax.min), rootElement->layoutConfig->sizing.height.sizeMinMax.max);
for (int i = 0; i < bfsBuffer.length; ++i) {
Clay_LayoutElement *parent = *Clay__LayoutElementPointerArray_Get(&bfsBuffer, i);
Clay_LayoutConfig *parentStyleConfig = parent->layoutConfig;
float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height;
float parentPadding = (float)(xAxis ? parent->layoutConfig->padding.x : parent->layoutConfig->padding.y);
float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding * 2;
bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM);
resizableContainerBuffer.length = 0;
float parentChildGap = parentStyleConfig->childGap;
for (int childOffset = 0; childOffset < parent->children.length; childOffset++) {
Clay_LayoutElement *childElement = parent->children.elements[childOffset];
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height;
if (childElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_TEXT && childElement->children.length > 0) {
Clay__LayoutElementPointerArray_Add(&bfsBuffer, childElement);
}
if (childSizing.type != CLAY__SIZING_TYPE_PERCENT) {
Clay__LayoutElementPointerArray_Add(&resizableContainerBuffer, childElement);
}
if (sizingAlongAxis) {
innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize);
if (childOffset > 0) {
innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child
totalPaddingAndChildGaps += parentChildGap;
}
} else {
innerContentSize = CLAY__MAX(childSize, innerContentSize);
}
}
// Expand percentage containers to size
for (int childOffset = 0; childOffset < parent->children.length; childOffset++) {
Clay_LayoutElement *childElement = parent->children.elements[childOffset];
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
if (childSizing.type == CLAY__SIZING_TYPE_PERCENT) {
*childSize = (parentSize - totalPaddingAndChildGaps) * childSizing.sizePercent;
if (sizingAlongAxis) {
innerContentSize += *childSize;
if (childOffset > 0) {
innerContentSize += parentChildGap; // For children after index 0, the childAxisOffset is the gap from the previous child
totalPaddingAndChildGaps += parentChildGap;
}
} else {
innerContentSize = CLAY__MAX(*childSize, innerContentSize);
}
}
}
if (sizingAlongAxis) {
float sizeToDistribute = parentSize - parentPadding * 2 - innerContentSize;
// If the content is too large, compress the children as much as possible
if (sizeToDistribute < 0) {
// If the parent can scroll in the axis direction in this direction, just leave the children alone
if (parent->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) {
if (((xAxis && parent->elementConfig.scrollElementConfig->horizontal) || (!xAxis && parent->elementConfig.scrollElementConfig->vertical))) {
continue;
}
}
// Scrolling containers preferentially compress before others
sizeToDistribute = Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_SCROLL_CONTAINER);
// If there is still height to make up, remove it from all containers that haven't hit their minimum size
if (sizeToDistribute < 0) {
Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_RESIZEABLE_CONTAINER);
}
// The content is too small, allow SIZING_GROW containers to expand
} else {
Clay__DistributeSizeAmongChildren(xAxis, sizeToDistribute, resizableContainerBuffer, CLAY__SIZE_DISTRIBUTION_TYPE_GROW_CONTAINER);
}
// Sizing along the non layout axis ("off axis")
} else {
for (int childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) {
Clay_LayoutElement *childElement = *Clay__LayoutElementPointerArray_Get(&resizableContainerBuffer, childOffset);
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
if (!xAxis && childElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_IMAGE) {
continue; // Currently we don't support resizing aspect ratio images on the Y axis because it would break the ratio
}
// If we're laying out the children of a scroll panel, grow containers expand to the height of the inner content, not the outer container
float maxSize = parentSize - parentPadding * 2;
if (parent->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER && ((xAxis && parent->elementConfig.scrollElementConfig->horizontal) || (!xAxis && parent->elementConfig.scrollElementConfig->vertical))) {
maxSize = CLAY__MAX(maxSize, innerContentSize);
}
if (childSizing.type == CLAY__SIZING_TYPE_FIT) {
*childSize = CLAY__MAX(childSizing.sizeMinMax.min, CLAY__MIN(*childSize, maxSize));
} else if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
*childSize = CLAY__MIN(maxSize, childSizing.sizeMinMax.max);
}
}
}
}
}
}
void Clay__CalculateFinalLayout(int screenWidth, int screenHeight) {
// Calculate sizing along the X axis
Clay__SizeContainersAlongAxis(true);
// Wrap text
uint32_t originalTextLayoutElementDataLength = Clay__textElementPointers.length;
for (int i = 0; i < originalTextLayoutElementDataLength; ++i) {
Clay_LayoutElement *containerElement = *Clay__LayoutElementPointerArray_Get(&Clay__textElementPointers, i);
Clay_String text = containerElement->text;
Clay_TextElementConfig *textConfig = containerElement->elementConfig.textElementConfig;
containerElement->elementType = CLAY__LAYOUT_ELEMENT_TYPE_CONTAINER;
// Clone the style config to prevent pollution of other elements that share this config
containerElement->layoutConfig = Clay_LayoutConfigArray_Add(&Clay__layoutConfigs, *containerElement->layoutConfig);
containerElement->layoutConfig->layoutDirection = CLAY_TOP_TO_BOTTOM;
containerElement->layoutConfig->childGap = textConfig->lineSpacing;
containerElement->dimensions.height = 0;
float fontSize = containerElement->elementConfig.textElementConfig->fontSize;
int lineStartIndex = 0;
int wordStartIndex = 0;
int wordEndIndex = 0;
containerElement->children = (Clay__LayoutElementChildren) { // Note: this overwrites the text property
.length = 0,
.elements = &Clay__layoutElementChildren.internalArray[Clay__layoutElementChildren.length]
};
Clay_Dimensions lineDimensions = (Clay_Dimensions){};
float spaceWidth = Clay__MeasureText(&CLAY__SPACECHAR, textConfig).width; // todo may as well cache it somewhere
while (wordStartIndex < text.length) {
if (text.chars[wordEndIndex] == ' ' || text.chars[wordEndIndex] == '\n' || wordEndIndex == text.length) {
Clay_String stringToRender = (Clay_String) { .length = wordEndIndex - lineStartIndex, .chars = text.chars + lineStartIndex };
Clay_String wordToMeasure = (Clay_String) { .length = wordEndIndex - wordStartIndex, .chars = text.chars + wordStartIndex };
// Clip off trailing spaces and newline characters
Clay_Dimensions wordDimensions = Clay__MeasureTextCached(&wordToMeasure, textConfig);
lineDimensions.width = lineDimensions.width + wordDimensions.width + spaceWidth;
lineDimensions.height = wordDimensions.height;
bool isOverlappingBoundaries = (lineDimensions.width - spaceWidth) > containerElement->dimensions.width + 0.01f; // Epsilon for floating point inaccuracy of adding components
// Need to wrap
if (isOverlappingBoundaries) {
lineDimensions.width -= spaceWidth;
// We can wrap at the most recent word start
if (wordStartIndex != lineStartIndex) {
stringToRender = (Clay_String) { .length = wordStartIndex - lineStartIndex - 1, .chars = text.chars + lineStartIndex };
lineDimensions.width -= (wordDimensions.width + spaceWidth);
lineStartIndex = wordStartIndex;
wordStartIndex = lineStartIndex;
wordEndIndex = lineStartIndex;
containerElement->dimensions.width = CLAY__MAX(containerElement->dimensions.width, lineDimensions.width);
// The single word is larger than the entire container - just render it in place
} else {
lineStartIndex = wordEndIndex + 1;
wordStartIndex = lineStartIndex;
wordEndIndex = lineStartIndex;
containerElement->dimensions.width = CLAY__MAX(containerElement->dimensions.width, lineDimensions.width);
}
// If we're at a space character and the current phrase fits, just keep going
} else if (text.chars[wordEndIndex] == ' ') {
wordStartIndex = wordEndIndex + 1;
wordEndIndex = wordStartIndex;
continue;
// Newline or end of string
} else {
lineStartIndex = wordEndIndex + 1;
wordStartIndex = lineStartIndex;
wordEndIndex = lineStartIndex;
}
Clay_LayoutElement *newTextLayoutElement = Clay_LayoutElementArray_Add(&Clay__layoutElements, (Clay_LayoutElement) {
.id = Clay__RehashWithNumber(containerElement->id, containerElement->children.length),
.elementType = CLAY__LAYOUT_ELEMENT_TYPE_TEXT,
.text = stringToRender,
.layoutConfig = &CLAY_LAYOUT_DEFAULT,
.elementConfig.textElementConfig = containerElement->elementConfig.textElementConfig,
.dimensions = { lineDimensions.width, lineDimensions.height },
});
containerElement->dimensions.height += lineDimensions.height + (float)(containerElement->children.length > 0 ? textConfig->lineSpacing : 0);
containerElement->children.length++;
lineDimensions = (Clay_Dimensions) {};
Clay__LayoutElementPointerArray_Add(&Clay__layoutElementChildren, newTextLayoutElement);
} else {
// In the middle of a word
wordEndIndex++;
}
}
}
// Scale vertical image heights according to aspect ratio
for (int i = 0; i < Clay__imageElementPointers.length; ++i) {
Clay_LayoutElement* imageElement = *Clay__LayoutElementPointerArray_Get(&Clay__imageElementPointers, i);
Clay_ImageElementConfig *config = imageElement->elementConfig.imageElementConfig;
imageElement->dimensions.height = (config->sourceDimensions.height / CLAY__MAX(config->sourceDimensions.width, 1)) * imageElement->dimensions.width;
}
// Propagate effect of text wrapping, image aspect scaling etc. on height of parents
Clay__LayoutElementTreeNodeArray dfsBuffer = Clay__layoutElementTreeNodeArray1;
dfsBuffer.length = 0;
for (int i = 0; i < Clay__layoutElementTreeRoots.length; ++i) {
Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, i);
Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, (Clay__LayoutElementTreeNode) { .layoutElement = root->layoutElement });
}
Clay__treeNodeVisited.internalArray[0] = false;
while (dfsBuffer.length > 0) {
Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1);
Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement;
if (!Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) {
Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true;
// If the element has no children or is the container for a text element, don't bother inspecting it
if (currentElement->children.length == 0 || (currentElement->children.length > 0 && currentElement->children.elements[0]->elementType == CLAY__LAYOUT_ELEMENT_TYPE_TEXT)) {
dfsBuffer.length--;
continue;
}
// Add the children to the DFS buffer (needs to be pushed in reverse so that stack traversal is in correct layout order)
for (int i = 0; i < currentElement->children.length; i++) {
Clay__treeNodeVisited.internalArray[dfsBuffer.length] = false;
Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, (Clay__LayoutElementTreeNode) { .layoutElement = currentElement->children.elements[i] });
}
continue;
}
dfsBuffer.length--;
// DFS node has been visited, this is on the way back up to the root
Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig;
if (layoutConfig->sizing.height.type == CLAY__SIZING_TYPE_PERCENT) {
continue;
}
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
// Resize any parent containers that have grown in height along their non layout axis
for (int j = 0; j < currentElement->children.length; ++j) {
Clay_LayoutElement *childElement = currentElement->children.elements[j];
float childHeightWithPadding = CLAY__MAX(childElement->dimensions.height + layoutConfig->padding.y * 2, currentElement->dimensions.height);
currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(childHeightWithPadding, layoutConfig->sizing.height.sizeMinMax.min), layoutConfig->sizing.height.sizeMinMax.max);
}
} else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) {
// Resizing along the layout axis
float contentHeight = (float)layoutConfig->padding.y * 2;
for (int j = 0; j < currentElement->children.length; ++j) {
Clay_LayoutElement *childElement = currentElement->children.elements[j];
contentHeight += childElement->dimensions.height;
}
contentHeight += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap);
currentElement->dimensions.height = CLAY__MIN(CLAY__MAX(contentHeight, layoutConfig->sizing.height.sizeMinMax.min), layoutConfig->sizing.height.sizeMinMax.max);
}
}
// Calculate sizing along the Y axis
Clay__SizeContainersAlongAxis(false);
// layoutElementsHashMap has non-linear access pattern so just resetting .length won't zero out the data.
// Need to zero it all out here
for (int i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) {
Clay__layoutElementsHashMap.internalArray[i] = -1;
}
Clay__layoutElementsHashMapInternal.length = 0;
// Calculate final positions and generate render commands
Clay__renderCommands.length = 0;
dfsBuffer.length = 0;
for (int rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) {
dfsBuffer.length = 0;
Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex);
Clay_Vector2 rootPosition = (Clay_Vector2) {};
Clay_LayoutElementHashMapItem *parentHashMapItem = Clay__GetHashMapItem(root->parentId);
// Position root floating containers
if (parentHashMapItem) {
Clay_FloatingElementConfig *config = root->layoutElement->elementConfig.floatingElementConfig;
Clay_Dimensions rootDimensions = root->layoutElement->dimensions;
Clay_Rectangle parentBoundingBox = parentHashMapItem->boundingBox;
// Set X position
Clay_Vector2 targetAttachPosition = (Clay_Vector2){};
switch (config->attachment.parent) {
case CLAY_ATTACH_POINT_LEFT_TOP:
case CLAY_ATTACH_POINT_LEFT_CENTER:
case CLAY_ATTACH_POINT_LEFT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x; break;
case CLAY_ATTACH_POINT_CENTER_TOP:
case CLAY_ATTACH_POINT_CENTER_CENTER:
case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + (parentBoundingBox.width / 2); break;
case CLAY_ATTACH_POINT_RIGHT_TOP:
case CLAY_ATTACH_POINT_RIGHT_CENTER:
case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x = parentBoundingBox.x + parentBoundingBox.width; break;
}
switch (config->attachment.element) {
case CLAY_ATTACH_POINT_LEFT_TOP:
case CLAY_ATTACH_POINT_LEFT_CENTER:
case CLAY_ATTACH_POINT_LEFT_BOTTOM: break;
case CLAY_ATTACH_POINT_CENTER_TOP:
case CLAY_ATTACH_POINT_CENTER_CENTER:
case CLAY_ATTACH_POINT_CENTER_BOTTOM: targetAttachPosition.x -= (rootDimensions.width / 2); break;
case CLAY_ATTACH_POINT_RIGHT_TOP:
case CLAY_ATTACH_POINT_RIGHT_CENTER:
case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.x -= rootDimensions.width; break;
}
switch (config->attachment.parent) { // I know I could merge the x and y switch statements, but this is easier to read
case CLAY_ATTACH_POINT_LEFT_TOP:
case CLAY_ATTACH_POINT_RIGHT_TOP:
case CLAY_ATTACH_POINT_CENTER_TOP: targetAttachPosition.y = parentBoundingBox.y; break;
case CLAY_ATTACH_POINT_LEFT_CENTER:
case CLAY_ATTACH_POINT_CENTER_CENTER:
case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y = parentBoundingBox.y + (parentBoundingBox.height / 2); break;
case CLAY_ATTACH_POINT_LEFT_BOTTOM:
case CLAY_ATTACH_POINT_CENTER_BOTTOM:
case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y = parentBoundingBox.y + parentBoundingBox.height; break;
}
switch (config->attachment.element) {
case CLAY_ATTACH_POINT_LEFT_TOP:
case CLAY_ATTACH_POINT_RIGHT_TOP:
case CLAY_ATTACH_POINT_CENTER_TOP: break;
case CLAY_ATTACH_POINT_LEFT_CENTER:
case CLAY_ATTACH_POINT_CENTER_CENTER:
case CLAY_ATTACH_POINT_RIGHT_CENTER: targetAttachPosition.y -= (rootDimensions.height / 2); break;
case CLAY_ATTACH_POINT_LEFT_BOTTOM:
case CLAY_ATTACH_POINT_CENTER_BOTTOM:
case CLAY_ATTACH_POINT_RIGHT_BOTTOM: targetAttachPosition.y -= rootDimensions.height; break;
}
targetAttachPosition.x += config->offset.x;
targetAttachPosition.y += config->offset.y;
rootPosition = targetAttachPosition;
}
if (root->clipElementId) {
Clay_LayoutElementHashMapItem *clipHashMapItem = Clay__GetHashMapItem(root->clipElementId);
if (clipHashMapItem) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) {
.id = Clay__RehashWithNumber(root->layoutElement->id, 10), // TODO need a better strategy for managing derived ids
.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START,
.boundingBox = clipHashMapItem->boundingBox,
});
}
}
Clay__LayoutElementTreeNodeArray_Add(&dfsBuffer, (Clay__LayoutElementTreeNode) { .layoutElement = root->layoutElement, .position = rootPosition, .nextChildOffset = (Clay_Vector2) { .x = (float)root->layoutElement->layoutConfig->padding.x, .y = (float)root->layoutElement->layoutConfig->padding.y } });
Clay__treeNodeVisited.internalArray[0] = false;
while (dfsBuffer.length > 0) {
Clay__LayoutElementTreeNode *currentElementTreeNode = Clay__LayoutElementTreeNodeArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1);
Clay_LayoutElement *currentElement = currentElementTreeNode->layoutElement;
Clay_LayoutConfig *layoutConfig = currentElement->layoutConfig;
Clay_Vector2 scrollOffset = {0};
// This will only be run a single time for each element in downwards DFS order
if (!Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) {
Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true;
Clay_Rectangle currentElementBoundingBox = (Clay_Rectangle) { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height };
if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_FLOATING_CONTAINER) {
Clay_FloatingElementConfig *floatingElementConfig = currentElement->elementConfig.floatingElementConfig;
Clay_Dimensions expand = floatingElementConfig->expand;
currentElementBoundingBox.x -= expand.width;
currentElementBoundingBox.width += expand.width * 2;
currentElementBoundingBox.y -= expand.height;
currentElementBoundingBox.height += expand.height * 2;
}
Clay__ScrollContainerDataInternal *scrollContainerData = CLAY__NULL;
// Apply scroll offsets to container
if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) {
.id = Clay__RehashWithNumber(currentElement->id, 10),
.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_START,
.boundingBox = currentElementBoundingBox,
});
// This linear scan could theoretically be slow under very strange conditions, but I can't imagine a real UI with more than a few 10's of scroll containers
for (int i = 0; i < Clay__scrollContainerDatas.length; i++) {
Clay__ScrollContainerDataInternal *mapping = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i);
if (mapping->layoutElement == currentElement) {
scrollContainerData = mapping;
mapping->boundingBox = currentElementBoundingBox;
Clay_ScrollContainerElementConfig *config = mapping->layoutElement->elementConfig.scrollElementConfig;
if (config->horizontal) {
scrollOffset.x = mapping->scrollPosition.x;
}
if (config->vertical) {
scrollOffset.y = mapping->scrollPosition.y;
}
break;
}
}
}
// Create the render command for this element
Clay_RenderCommand renderCommand = (Clay_RenderCommand) {
.id = currentElement->id,
.commandType = Clay__LayoutElementTypeToRenderCommandType[currentElement->elementType],
.boundingBox = currentElementBoundingBox,
.config = currentElement->elementConfig
};
Clay_LayoutElementHashMapItem *hashMapItem = Clay__AddHashMapItem(currentElement->id, currentElement);
if (hashMapItem) {
hashMapItem->boundingBox = renderCommand.boundingBox;
}
// Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow
bool offscreen = currentElementBoundingBox.x > (float)screenWidth || currentElementBoundingBox.y > (float)screenHeight || currentElementBoundingBox.x + currentElementBoundingBox.width < 0 || currentElementBoundingBox.y + currentElementBoundingBox.height < 0;
bool shouldRender = !offscreen;
switch (renderCommand.commandType) {
case CLAY_RENDER_COMMAND_TYPE_NONE: {
shouldRender = false;
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
renderCommand.text = currentElement->text;
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: { // We render borders on close because they need to render above children
shouldRender = false;
break;
}
default: break;
}
if (shouldRender) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, renderCommand);
}
if (offscreen) {
// NOTE: You may be tempted to try an early return / continue if an element is off screen. Why bother calculating layout for its children, right?
// Unfortunately, a FLOATING_CONTAINER may be defined that attaches to a child or grandchild of this element, which is large enough to still
// be on screen, even if this element isn't. That depends on this element and it's children being laid out correctly (even if they are entirely off screen)
}
// Handle child alignment along the layout axis
if (currentElementTreeNode->layoutElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_TEXT) {
dfsBuffer.length += currentElement->children.length;
Clay_Dimensions contentSize = (Clay_Dimensions) {0,0};
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
for (int i = 0; i < currentElement->children.length; ++i) {
Clay_LayoutElement *childElement = currentElement->children.elements[i];
contentSize.width += childElement->dimensions.width;
contentSize.height = CLAY__MAX(contentSize.height, childElement->dimensions.height);
}
contentSize.width += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap);
float extraSpace = currentElement->dimensions.width - (float)layoutConfig->padding.x * 2 - contentSize.width;
switch (layoutConfig->childAlignment.x) {
case CLAY_ALIGN_X_LEFT: extraSpace = 0; break;
case CLAY_ALIGN_X_CENTER: extraSpace /= 2; break;
default: break;
}
currentElementTreeNode->nextChildOffset.x += extraSpace;
} else {
for (int i = 0; i < currentElement->children.length; ++i) {
Clay_LayoutElement *childElement = currentElement->children.elements[i];
contentSize.width = CLAY__MAX(contentSize.width, childElement->dimensions.width);
contentSize.height += childElement->dimensions.height;
}
contentSize.height += (float)(CLAY__MAX(currentElement->children.length - 1, 0) * layoutConfig->childGap);
float extraSpace = currentElement->dimensions.height - (float)layoutConfig->padding.y * 2 - contentSize.height;
switch (layoutConfig->childAlignment.y) {
case CLAY_ALIGN_Y_TOP: extraSpace = 0; break;
case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break;
default: break;
}
currentElementTreeNode->nextChildOffset.y += extraSpace;
}
if (scrollContainerData) {
scrollContainerData->contentSize = contentSize;
}
}
} else {
// DFS is returning upwards backwards
if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_SCROLL_CONTAINER) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) {
.id = Clay__RehashWithNumber(currentElement->id, 11),
.commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END,
});
// Borders between elements are expressed as additional rectangle render commands
} else if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_BORDER_CONTAINER) {
Clay_Rectangle currentElementBoundingBox = (Clay_Rectangle) { currentElementTreeNode->position.x, currentElementTreeNode->position.y, currentElement->dimensions.width, currentElement->dimensions.height };
Clay_BorderContainerElementConfig *borderConfig = currentElement->elementConfig.borderElementConfig;
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) {
.id = currentElement->id,
.commandType = CLAY_RENDER_COMMAND_TYPE_BORDER,
.boundingBox = currentElementBoundingBox,
.config = currentElement->elementConfig
});
// Render border elements between children
if (borderConfig->betweenChildren.width > 0 && borderConfig->betweenChildren.color.a > 0) {
Clay_Vector2 borderOffset = { (float)layoutConfig->padding.x, (float)layoutConfig->padding.y };
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
for (int i = 0; i < currentElement->children.length; ++i) {
Clay_LayoutElement *childElement = currentElement->children.elements[i];
if (i > 0) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) {
.id = Clay__RehashWithNumber(currentElement->id, 5 + i),
.commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE,
.boundingBox = { currentElementBoundingBox.x + borderOffset.x, currentElementBoundingBox.y, (float)borderConfig->betweenChildren.width, currentElement->dimensions.height },
.config = CLAY_RECTANGLE_CONFIG(.color = borderConfig->betweenChildren.color)
});
}
borderOffset.x += (childElement->dimensions.width + (float)layoutConfig->childGap / 2);
}
} else {
for (int i = 0; i < currentElement->children.length; ++i) {
Clay_LayoutElement *childElement = currentElement->children.elements[i];
if (i > 0) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) {
.id = Clay__RehashWithNumber(currentElement->id, 5 + i),
.commandType = CLAY_RENDER_COMMAND_TYPE_RECTANGLE,
.boundingBox = { currentElementBoundingBox.x, currentElementBoundingBox.y + borderOffset.y, currentElement->dimensions.width, (float)borderConfig->betweenChildren.width },
.config = CLAY_RECTANGLE_CONFIG(.color = borderConfig->betweenChildren.color)
});
}
borderOffset.y += (childElement->dimensions.height + (float)layoutConfig->childGap / 2);
}
}
}
}
dfsBuffer.length--;
continue;
}
// Add children to the DFS buffer
if (currentElement->elementType != CLAY__LAYOUT_ELEMENT_TYPE_TEXT) {
for (int i = 0; i < currentElement->children.length; ++i) {
Clay_LayoutElement *childElement = currentElement->children.elements[i];
// Alignment along non layout axis
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
currentElementTreeNode->nextChildOffset.y = currentElement->layoutConfig->padding.y;
float whiteSpaceAroundChild = currentElement->dimensions.height - (float)currentElement->layoutConfig->padding.y * 2 - childElement->dimensions.height;
switch (layoutConfig->childAlignment.y) {
case CLAY_ALIGN_Y_TOP: break;
case CLAY_ALIGN_Y_CENTER: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild / 2; break;
case CLAY_ALIGN_Y_BOTTOM: currentElementTreeNode->nextChildOffset.y += whiteSpaceAroundChild; break;
}
} else {
currentElementTreeNode->nextChildOffset.x = currentElement->layoutConfig->padding.x;
float whiteSpaceAroundChild = currentElement->dimensions.width - (float)currentElement->layoutConfig->padding.x * 2 - childElement->dimensions.width;
switch (layoutConfig->childAlignment.x) {
case CLAY_ALIGN_X_LEFT: break;
case CLAY_ALIGN_X_CENTER: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild / 2; break;
case CLAY_ALIGN_X_RIGHT: currentElementTreeNode->nextChildOffset.x += whiteSpaceAroundChild; break;
}
}
Clay_Vector2 childPosition = (Clay_Vector2) {
currentElementTreeNode->position.x + currentElementTreeNode->nextChildOffset.x + scrollOffset.x,
currentElementTreeNode->position.y + currentElementTreeNode->nextChildOffset.y + scrollOffset.y,
};
// DFS buffer elements need to be added in reverse because stack traversal happens backwards
uint32_t newNodeIndex = dfsBuffer.length - 1 - i;
dfsBuffer.internalArray[newNodeIndex] = (Clay__LayoutElementTreeNode) {
.layoutElement = childElement,
.position = (Clay_Vector2) { childPosition.x, childPosition.y },
.nextChildOffset = (Clay_Vector2) { .x = (float)childElement->layoutConfig->padding.x, .y = (float)childElement->layoutConfig->padding.y },
};
Clay__treeNodeVisited.internalArray[newNodeIndex] = false;
// Update parent offsets
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
currentElementTreeNode->nextChildOffset.x += childElement->dimensions.width + (float)layoutConfig->childGap;
} else {
currentElementTreeNode->nextChildOffset.y += childElement->dimensions.height + (float)layoutConfig->childGap;
}
}
}
}
if (root->clipElementId) {
Clay_RenderCommandArray_Add(&Clay__renderCommands, (Clay_RenderCommand) { .id = Clay__RehashWithNumber(root->layoutElement->id, 11), .commandType = CLAY_RENDER_COMMAND_TYPE_SCISSOR_END });
}
}
}
// PUBLIC API FROM HERE ---------------------------------------
CLAY_WASM_EXPORT("Clay_MinMemorySize")
uint32_t Clay_MinMemorySize() {
Clay_Arena fakeArena = (Clay_Arena) { .capacity = INT64_MAX };
Clay__InitializePersistentMemory(&fakeArena);
Clay__InitializeEphemeralMemory(&fakeArena);
return fakeArena.nextAllocation;
}
CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory")
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *offset) {
Clay_Arena arena = (Clay_Arena) {
.capacity = capacity,
.memory = (char *)offset
};
return arena;
}
#ifndef CLAY_WASM
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_String *text, Clay_TextElementConfig *config)) {
Clay__MeasureText = measureTextFunction;
}
#endif
CLAY_WASM_EXPORT("Clay_SetPointerPosition")
void Clay_SetPointerPosition(Clay_Vector2 position) {
Clay__pointerPosition = position;
Clay__pointerOverIds.length = 0;
Clay__LayoutElementPointerArray dfsBuffer = Clay__layoutElementChildrenBuffer;
for (int rootIndex = 0; rootIndex < Clay__layoutElementTreeRoots.length; ++rootIndex) {
dfsBuffer.length = 0;
Clay__LayoutElementTreeRoot *root = Clay__LayoutElementTreeRootArray_Get(&Clay__layoutElementTreeRoots, rootIndex);
Clay__LayoutElementPointerArray_Add(&dfsBuffer, root->layoutElement);
Clay__treeNodeVisited.internalArray[0] = false;
while (dfsBuffer.length > 0) {
if (Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1]) {
dfsBuffer.length--;
continue;
}
Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = true;
Clay_LayoutElement *currentElement = *Clay__LayoutElementPointerArray_Get(&dfsBuffer, (int)dfsBuffer.length - 1);
Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO I wish there was a way around this, maybe the fact that it's essentially a binary tree limits the cost, have to measure
if ((mapItem && Clay__PointIsInsideRect(position, mapItem->boundingBox)) || (!mapItem && Clay__PointIsInsideRect(position, (Clay_Rectangle) {0,0, currentElement->dimensions.width, currentElement->dimensions.height}))) {
Clay__int32_tArray_Add(&Clay__pointerOverIds, (int)currentElement->id);
if (currentElement->elementType == CLAY__LAYOUT_ELEMENT_TYPE_TEXT) {
dfsBuffer.length--;
continue;
}
for (int i = currentElement->children.length - 1; i >= 0; --i) {
Clay__LayoutElementPointerArray_Add(&dfsBuffer, currentElement->children.elements[i]);
Clay__treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked
}
} else {
dfsBuffer.length--;
}
}
}
}
CLAY_WASM_EXPORT("Clay_Initialize")
void Clay_Initialize(Clay_Arena arena) {
Clay__internalArena = arena;
Clay__InitializePersistentMemory(&Clay__internalArena);
Clay__InitializeEphemeralMemory(&Clay__internalArena);
for (int i = 0; i < Clay__layoutElementsHashMap.capacity; ++i) {
Clay__layoutElementsHashMap.internalArray[i] = -1;
}
Clay__measureTextHashMapInternal.length = 1; // Reserve the 0 value to mean "no next element"
}
CLAY_WASM_EXPORT("Clay_UpdateScrollContainers")
void Clay_UpdateScrollContainers(bool isPointerActive, Clay_Vector2 scrollDelta, float deltaTime) {
// Don't apply scroll events to ancestors of the inner element
int32_t highestPriorityElementIndex = -1;
Clay__ScrollContainerDataInternal *highestPriorityScrollData = CLAY__NULL;
for (int i = 0; i < Clay__scrollContainerDatas.length; i++) {
Clay__ScrollContainerDataInternal *scrollData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i);
if (!scrollData->openThisFrame) {
Clay__ScrollContainerDataInternalArray_RemoveSwapback(&Clay__scrollContainerDatas, i);
continue;
}
scrollData->openThisFrame = false;
Clay_LayoutElementHashMapItem *hashMapItem = Clay__GetHashMapItem(scrollData->elementId);
// Element isn't rendered this frame but scroll offset has been retained
if (!hashMapItem) {
Clay__ScrollContainerDataInternalArray_RemoveSwapback(&Clay__scrollContainerDatas, i);
continue;
}
// Touch / click is released
if (!isPointerActive && scrollData->pointerScrollActive) {
float xDiff = scrollData->scrollPosition.x - scrollData->scrollOrigin.x;
if (xDiff < -10 || xDiff > 10) {
scrollData->scrollMomentum.x = (scrollData->scrollPosition.x - scrollData->scrollOrigin.x) / (scrollData->momentumTime * 25);
}
float yDiff = scrollData->scrollPosition.y - scrollData->scrollOrigin.y;
if (yDiff < -10 || yDiff > 10) {
scrollData->scrollMomentum.y = (scrollData->scrollPosition.y - scrollData->scrollOrigin.y) / (scrollData->momentumTime * 25);
}
scrollData->pointerScrollActive = false;
scrollData->pointerOrigin = (Clay_Vector2){0,0};
scrollData->scrollOrigin = (Clay_Vector2){0,0};
scrollData->momentumTime = 0;
}
// Apply existing momentum
scrollData->scrollPosition.x += scrollData->scrollMomentum.x;
scrollData->scrollMomentum.x *= 0.95f;
bool scrollOccurred = scrollDelta.x != 0 || scrollDelta.y != 0;
if ((scrollData->scrollMomentum.x > -0.1f && scrollData->scrollMomentum.x < 0.1f) || scrollOccurred) {
scrollData->scrollMomentum.x = 0;
}
scrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(scrollData->scrollPosition.x, 0), -(scrollData->contentSize.width - scrollData->layoutElement->dimensions.width));
scrollData->scrollPosition.y += scrollData->scrollMomentum.y;
scrollData->scrollMomentum.y *= 0.95f;
if ((scrollData->scrollMomentum.y > -0.1f && scrollData->scrollMomentum.y < 0.1f) || scrollOccurred) {
scrollData->scrollMomentum.y = 0;
}
scrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(scrollData->scrollPosition.y, 0), -(scrollData->contentSize.height - scrollData->layoutElement->dimensions.height));
for (int j = 0; j < Clay__pointerOverIds.length; ++j) { // TODO n & m are small here but this being n*m gives me the creeps
if (scrollData->layoutElement->id == *Clay__int32_tArray_Get(&Clay__pointerOverIds, j)) {
highestPriorityElementIndex = j;
highestPriorityScrollData = scrollData;
}
}
}
if (highestPriorityElementIndex > -1 && highestPriorityScrollData) {
Clay_LayoutElement *scrollElement = highestPriorityScrollData->layoutElement;
bool canScrollVertically = scrollElement->elementConfig.scrollElementConfig->vertical && highestPriorityScrollData->contentSize.height > scrollElement->dimensions.height;
bool canScrollHorizontally = scrollElement->elementConfig.scrollElementConfig->horizontal && highestPriorityScrollData->contentSize.width > scrollElement->dimensions.width;
// Handle wheel scroll
if (canScrollVertically) {
highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollPosition.y + scrollDelta.y * 10;
}
if (canScrollHorizontally) {
highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollPosition.x + scrollDelta.x * 10;
}
// Handle click / touch scroll
if (isPointerActive) {
highestPriorityScrollData->scrollMomentum = (Clay_Vector2){0};
if (!highestPriorityScrollData->pointerScrollActive) {
highestPriorityScrollData->pointerOrigin = Clay__pointerPosition;
highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition;
highestPriorityScrollData->pointerScrollActive = true;
} else {
float scrollDeltaX = 0, scrollDeltaY = 0;
if (canScrollHorizontally) {
float oldXScrollPosition = highestPriorityScrollData->scrollPosition.x;
highestPriorityScrollData->scrollPosition.x = highestPriorityScrollData->scrollOrigin.x + (Clay__pointerPosition.x - highestPriorityScrollData->pointerOrigin.x);
highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - highestPriorityScrollData->boundingBox.width));
scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldXScrollPosition;
}
if (canScrollVertically) {
float oldYScrollPosition = highestPriorityScrollData->scrollPosition.y;
highestPriorityScrollData->scrollPosition.y = highestPriorityScrollData->scrollOrigin.y + (Clay__pointerPosition.y - highestPriorityScrollData->pointerOrigin.y);
highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - highestPriorityScrollData->boundingBox.height));
scrollDeltaY = highestPriorityScrollData->scrollPosition.y - oldYScrollPosition;
}
if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.15f) {
highestPriorityScrollData->momentumTime = 0;
highestPriorityScrollData->pointerOrigin = Clay__pointerPosition;
highestPriorityScrollData->scrollOrigin = highestPriorityScrollData->scrollPosition;
} else {
highestPriorityScrollData->momentumTime += deltaTime;
}
}
}
// Clamp any changes to scroll position to the maximum size of the contents
if (canScrollVertically) {
highestPriorityScrollData->scrollPosition.y = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.y, 0), -(highestPriorityScrollData->contentSize.height - scrollElement->dimensions.height));
}
if (canScrollHorizontally) {
highestPriorityScrollData->scrollPosition.x = CLAY__MAX(CLAY__MIN(highestPriorityScrollData->scrollPosition.x, 0), -(highestPriorityScrollData->contentSize.width - scrollElement->dimensions.width));
}
}
}
CLAY_WASM_EXPORT("Clay_BeginLayout")
void Clay_BeginLayout(int screenWidth, int screenHeight) {
Clay__InitializeEphemeralMemory(&Clay__internalArena);
// Set up the root container that covers the entire window
Clay_LayoutElement rootLayoutElement = (Clay_LayoutElement){.layoutConfig = CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED((float)screenWidth), CLAY_SIZING_FIXED((float)screenHeight)})};
Clay__openLayoutElement = Clay_LayoutElementArray_Add(&Clay__layoutElements, rootLayoutElement);
Clay__LayoutElementPointerArray_Add(&Clay__openLayoutElementStack, Clay__openLayoutElement);
Clay__LayoutElementTreeRootArray_Add(&Clay__layoutElementTreeRoots, (Clay__LayoutElementTreeRoot) { .layoutElement = Clay__openLayoutElement });
}
CLAY_WASM_EXPORT("Clay_EndLayout")
Clay_RenderCommandArray Clay_EndLayout(int screenWidth, int screenHeight)
{
Clay__AttachContainerChildren();
Clay__CalculateFinalLayout(screenWidth, screenHeight);
return Clay__renderCommands;
}
CLAY_WASM_EXPORT("Clay_PointerOver")
bool Clay_PointerOver(uint32_t id) { // TODO return priority for separating multiple results
for (int i = 0; i < Clay__pointerOverIds.length; ++i) {
if (*Clay__int32_tArray_Get(&Clay__pointerOverIds, i) == id) {
return true;
}
}
return false;
}
typedef struct
{
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout.
// Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
Clay_Vector2 *scrollPosition;
Clay_Dimensions scrollContainerDimensions;
Clay_Dimensions contentDimensions;
Clay_ScrollContainerElementConfig config;
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
bool found;
} Clay_ScrollContainerData;
CLAY_WASM_EXPORT("Clay_GetScrollContainerData")
Clay_ScrollContainerData Clay_GetScrollContainerData(uint32_t id) {
for (int i = 0; i < Clay__scrollContainerDatas.length; ++i) {
Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&Clay__scrollContainerDatas, i);
if (scrollContainerData->elementId == id) {
return (Clay_ScrollContainerData) {
.scrollPosition = &scrollContainerData->scrollPosition,
.scrollContainerDimensions = (Clay_Dimensions) { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height },
.contentDimensions = scrollContainerData->contentSize,
.config = *scrollContainerData->layoutElement->elementConfig.scrollElementConfig,
.found = true
};
}
}
return (Clay_ScrollContainerData){};
}
#endif //CLAY_IMPLEMENTATION
/*
LICENSE
zlib/libpng license
Copyright (c) 2024 Nic Barker
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.
*/