Compare commits

...

2 Commits

Author SHA1 Message Date
Nic Barker
02bce89d17
[Core] Improve & streamline grow / shrink handling (#296)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-03-04 10:56:38 +13:00
FintasticMan
b5b086af13
[Macros] Add versions of the CLAY_ID macros that take Clay_String (#285) 2025-03-04 10:30:53 +13:00
9 changed files with 144 additions and 82 deletions

View File

@ -53,7 +53,7 @@ jobs:
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Cache
uses: actions/cache@v4.0.2
uses: actions/cache@v4.2.0
with:
# A list of files, directories, and wildcard patterns to cache and restore
path: "/home/runner/work/clay/clay/build/_deps"

View File

@ -897,18 +897,12 @@ Element is subject to [culling](#visibility-culling). Otherwise, multiple `Clay_
### CLAY_ID
**Usage**
`CLAY(CLAY_ID(char* idString)) {}`
**Lifecycle**
`Clay_BeginLayout()` -> `CLAY(` -> `CLAY_ID()` -> `)` -> `Clay_EndLayout()`
**Notes**
`Clay_ElementId CLAY_ID(STRING_LITERAL idString)`
**CLAY_ID()** is used to generate and attach a [Clay_ElementId](#clay_elementid) to a layout element during declaration.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID](#clay_sid).
To regenerate the same ID outside of layout declaration when using utility functions such as [Clay_PointerOver](#clay_pointerover), use the [Clay_GetElementId](#clay_getelementid) function.
**Examples**
@ -931,11 +925,31 @@ if (buttonIsHovered && leftMouseButtonPressed) {
---
### CLAY_SID()
`Clay_ElementId CLAY_SID(Clay_String idString)`
A version of [CLAY_ID](#clay_id) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
### CLAY_IDI()
`Clay_ElementId CLAY_IDI(char *label, int32_t index)`
`Clay_ElementId CLAY_IDI(STRING_LITERAL idString, int32_t index)`
An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`.
Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI](#clay_sidi).
---
### CLAY_SIDI()
`Clay_ElementId CLAY_SIDI(Clay_String idString, int32_t index)`
A version of [CLAY_IDI](#clay_idi) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
@ -943,7 +957,7 @@ An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_ele
**Usage**
`CLAY(CLAY_ID_LOCAL(char* idString)) {}`
`Clay_ElementId CLAY_ID_LOCAL(STRING_LITERAL idString)`
**Lifecycle**
@ -957,6 +971,8 @@ Unlike [CLAY_ID](#clay_id) which needs to be globally unique, a local ID is base
As a result, local id is suitable for use in reusable components and loops.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID_LOCAL](#clay_sid_local).
**Examples**
```C
@ -976,11 +992,31 @@ for (int i = 0; i < headerButtons.length; i++) {
---
### CLAY_SID_LOCAL()
`Clay_ElementId CLAY_SID_LOCAL(Clay_String idString)`
A version of [CLAY_ID_LOCAL](#clay_id_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---
### CLAY_IDI_LOCAL()
`Clay_ElementId CLAY_IDI_LOCAL(char *label, int32_t index)`
`Clay_ElementId CLAY_IDI_LOCAL(STRING_LITERAL idString, int32_t index)`
An offset version of [CLAY_ID_LOCAL](#clay_local_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
An offset version of [CLAY_ID_LOCAL](#clay_local_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`.
Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI_LOCAL](#clay_sidi_local).
---
### CLAY_SIDI_LOCAL()
`Clay_ElementId CLAY_SIDI_LOCAL(Clay_String idString, int32_t index)`
A version of [CLAY_IDI_LOCAL](#clay_idi_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
---

Binary file not shown.

Binary file not shown.

Binary file not shown.

160
clay.h
View File

@ -71,13 +71,25 @@
#define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT })
// Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead.
#define CLAY_ID(label) CLAY_IDI(label, 0)
#define CLAY_IDI(label, index) Clay__HashString(CLAY_STRING(label), index, 0)
#define CLAY_SID(label) CLAY_SIDI(label, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead.
#define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index)
#define CLAY_SIDI(label, index) Clay__HashString(label, index, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead.
#define CLAY_ID_LOCAL(label) CLAY_IDI_LOCAL(label, 0)
#define CLAY_IDI_LOCAL(label, index) Clay__HashString(CLAY_STRING(label), index, Clay__GetParentElementId())
#define CLAY_SID_LOCAL(label) CLAY_SIDI_LOCAL(label, 0)
// Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead.
#define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index)
#define CLAY_SIDI_LOCAL(label, index) Clay__HashString(label, index, Clay__GetParentElementId())
#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0]))
@ -2018,54 +2030,11 @@ void Clay__InitializePersistentMemory(Clay_Context* context) {
context->arenaResetOffset = arena->nextAllocation;
}
void Clay__CompressChildrenAlongAxis(bool xAxis, float totalSizeToDistribute, Clay__int32_tArray resizableContainerBuffer) {
Clay_Context* context = Clay_GetCurrentContext();
Clay__int32_tArray largestContainers = context->openClipElementStack;
const float CLAY__EPSILON = 0.01;
while (totalSizeToDistribute > 0.1) {
largestContainers.length = 0;
float largestSize = 0;
float targetSize = 0;
for (int32_t i = 0; i < resizableContainerBuffer.length; ++i) {
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height;
if ((childSize - largestSize) < 0.1 && (childSize - largestSize) > -0.1) {
Clay__int32_tArray_Add(&largestContainers, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
} else if (childSize > largestSize) {
targetSize = largestSize;
largestSize = childSize;
largestContainers.length = 0;
Clay__int32_tArray_Add(&largestContainers, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
}
else if (childSize > targetSize) {
targetSize = childSize;
}
}
if (largestContainers.length == 0) {
return;
}
targetSize = CLAY__MAX(targetSize, (largestSize * largestContainers.length) - totalSizeToDistribute) / largestContainers.length;
for (int32_t childOffset = 0; childOffset < largestContainers.length; childOffset++) {
int32_t childIndex = Clay__int32_tArray_GetValue(&largestContainers, childOffset);
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
float childMinSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
float oldChildSize = *childSize;
*childSize = CLAY__MAX(childMinSize, targetSize);
totalSizeToDistribute -= (oldChildSize - *childSize);
if (*childSize == childMinSize) {
for (int32_t i = 0; i < resizableContainerBuffer.length; i++) {
if (Clay__int32_tArray_GetValue(&resizableContainerBuffer, i) == childIndex) {
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, i);
break;
}
}
}
}
}
bool Clay__FloatEqual(float left, float right) {
float subtracted = left - right;
return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON;
}
void Clay__SizeContainersAlongAxis(bool xAxis) {
@ -2103,7 +2072,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
int32_t growContainerCount = 0;
float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height;
float parentPadding = (float)(xAxis ? (parent->layoutConfig->padding.left + parent->layoutConfig->padding.right) : (parent->layoutConfig->padding.top + parent->layoutConfig->padding.bottom));
float innerContentSize = 0, growContainerContentSize = 0, totalPaddingAndChildGaps = parentPadding;
float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding;
bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM);
resizableContainerBuffer.length = 0;
float parentChildGap = parentStyleConfig->childGap;
@ -2129,7 +2098,6 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
if (sizingAlongAxis) {
innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize);
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
growContainerContentSize += childSize;
growContainerCount++;
}
if (childOffset > 0) {
@ -2168,25 +2136,83 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
}
}
// Scrolling containers preferentially compress before others
Clay__CompressChildrenAlongAxis(xAxis, -sizeToDistribute, resizableContainerBuffer);
while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) {
float largest = 0;
float secondLargest = 0;
float widthToAdd = sizeToDistribute;
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float childSize = xAxis ? child->dimensions.width : child->dimensions.height;
if (Clay__FloatEqual(childSize, largest)) { continue; }
if (childSize > largest) {
secondLargest = largest;
largest = childSize;
}
if (childSize < largest) {
secondLargest = CLAY__MAX(secondLargest, childSize);
widthToAdd = secondLargest - largest;
}
}
widthToAdd = CLAY__MAX(widthToAdd, sizeToDistribute / resizableContainerBuffer.length);
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height;
float minSize = xAxis ? child->minDimensions.width : child->minDimensions.height;
float previousWidth = *childSize;
if (Clay__FloatEqual(*childSize, largest)) {
*childSize += widthToAdd;
if (*childSize <= minSize) {
*childSize = minSize;
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
}
sizeToDistribute -= (*childSize - previousWidth);
}
}
}
// The content is too small, allow SIZING_GROW containers to expand
} else if (sizeToDistribute > 0 && growContainerCount > 0) {
float targetSize = (sizeToDistribute + growContainerContentSize) / (float)growContainerCount;
for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) {
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset));
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
float *minSize = xAxis ? &childElement->minDimensions.width : &childElement->minDimensions.height;
if (targetSize < *minSize) {
growContainerContentSize -= *minSize;
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childOffset);
growContainerCount--;
targetSize = (sizeToDistribute + growContainerContentSize) / (float)growContainerCount;
childOffset = -1;
continue;
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
Clay__SizingType childSizing = xAxis ? child->layoutConfig->sizing.width.type : child->layoutConfig->sizing.height.type;
if (childSizing != CLAY__SIZING_TYPE_GROW) {
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
}
}
while (sizeToDistribute > CLAY__EPSILON && resizableContainerBuffer.length > 0) {
float smallest = CLAY__MAXFLOAT;
float secondSmallest = CLAY__MAXFLOAT;
float widthToAdd = sizeToDistribute;
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float childSize = xAxis ? child->dimensions.width : child->dimensions.height;
if (Clay__FloatEqual(childSize, smallest)) { continue; }
if (childSize < smallest) {
secondSmallest = smallest;
smallest = childSize;
}
if (childSize > smallest) {
secondSmallest = CLAY__MIN(secondSmallest, childSize);
widthToAdd = secondSmallest - smallest;
}
}
widthToAdd = CLAY__MIN(widthToAdd, sizeToDistribute / resizableContainerBuffer.length);
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height;
float maxSize = xAxis ? child->layoutConfig->sizing.width.size.minMax.max : child->layoutConfig->sizing.height.size.minMax.max;
float previousWidth = *childSize;
if (Clay__FloatEqual(*childSize, smallest)) {
*childSize += widthToAdd;
if (*childSize >= maxSize) {
*childSize = maxSize;
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
}
sizeToDistribute -= (*childSize - previousWidth);
}
*childSize = targetSize;
}
}
}