#pragma once #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" #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)) // 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 arenaOffsetAligned = arena->nextAllocation + (arena->nextAllocation % sizeof(Clay_String)); 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, Clay_Arena *arena) { uint64_t totalSizeBytes = capacity * itemSize; uint64_t arenaOffsetAligned = arena->nextAllocation + (arena->nextAllocation % itemSize); 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), 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), 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), 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), 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), 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), 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), 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), 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), 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), 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*), 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), 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; float momentumTime; uint32_t elementId; bool openThisFrame; bool pointerScrollActive; } Clay__ScrollContainerData; Clay__ScrollContainerData CLAY__SCROLL_CONTAINER_DEFAULT = (Clay__ScrollContainerData) {}; // __GENERATED__ template array_define TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray #pragma region generated typedef struct { uint32_t capacity; uint32_t length; Clay__ScrollContainerData *internalArray; } Clay__ScrollContainerDataArray; Clay__ScrollContainerDataArray Clay__ScrollContainerDataArray_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) { return (Clay__ScrollContainerDataArray){.capacity = capacity, .length = 0, .internalArray = (Clay__ScrollContainerData *)Clay__Array_Allocate_Arena(capacity, sizeof(Clay__ScrollContainerData), arena)}; } #pragma endregion // __GENERATED__ template // __GENERATED__ template array_add TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT #pragma region generated Clay__ScrollContainerData *Clay__ScrollContainerDataArray_Add(Clay__ScrollContainerDataArray *array, Clay__ScrollContainerData 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; } #pragma endregion // __GENERATED__ template // __GENERATED__ template array_get TYPE=Clay__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=&CLAY__SCROLL_CONTAINER_DEFAULT #pragma region generated Clay__ScrollContainerData *Clay__ScrollContainerDataArray_Get(Clay__ScrollContainerDataArray *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__ScrollContainerData NAME=Clay__ScrollContainerDataArray DEFAULT_VALUE=CLAY__SCROLL_CONTAINER_DEFAULT #pragma region generated Clay__ScrollContainerData Clay__ScrollContainerDataArray_RemoveSwapback(Clay__ScrollContainerDataArray *array, int index) { if (Clay__Array_RangeCheck(index, array->length)) { array->length--; Clay__ScrollContainerData 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), 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), 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), 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), 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), 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__ScrollContainerDataArray Clay__scrollContainerOffsets; 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__ScrollContainerData *scrollOffset = CLAY__NULL; for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { Clay__ScrollContainerData *mapping = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); if (id == mapping->elementId) { scrollOffset = mapping; scrollOffset->layoutElement = scrollElement; scrollOffset->openThisFrame = true; } } if (!scrollOffset) { Clay__ScrollContainerDataArray_Add(&Clay__scrollContainerOffsets, (Clay__ScrollContainerData){.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__scrollContainerOffsets = Clay__ScrollContainerDataArray_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); 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) { // 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 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); // 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__ScrollContainerData *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__scrollContainerOffsets.length; i++) { Clay__ScrollContainerData *mapping = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, 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__ScrollContainerData *highestPriorityScrollData = CLAY__NULL; for (int i = 0; i < Clay__scrollContainerOffsets.length; i++) { Clay__ScrollContainerData *scrollData = Clay__ScrollContainerDataArray_Get(&Clay__scrollContainerOffsets, i); if (!scrollData->openThisFrame) { Clay__ScrollContainerDataArray_RemoveSwapback(&Clay__scrollContainerOffsets, 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__ScrollContainerDataArray_RemoveSwapback(&Clay__scrollContainerOffsets, 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)); scrollDeltaX = highestPriorityScrollData->scrollPosition.x - oldYScrollPosition; } if (scrollDeltaX > -0.1f && scrollDeltaX < 0.1f && scrollDeltaY > -0.1f && scrollDeltaY < 0.1f && highestPriorityScrollData->momentumTime > 0.5f) { 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; } #endif //CLAY_IMPLEMENTATION