Compare commits

...

4 Commits

Author SHA1 Message Date
Piggybank Studios
daf26f3b32
Merge fbf8251996 into ad49977f1b 2025-03-21 11:46:02 +01:00
Nic Barker
ad49977f1b [Core] Apply minimum width for single words and fix some minimum sizing bugs
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-03-21 11:22:48 +13:00
Leo Zurbriggen
61490e4557
[Bindings/Odin] expose _OpenElement and _CloseElement (#301)
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-21 09:25:50 +13:00
Taylor Robbins (Piggybank Studios)
fbf8251996 Adds CLAY_TEXT_ALIGN_SHRINK option to Clay_TextAlignment which tells the TEXT command producing code it is allowed to pass a boundingBox that is horizontally smaller than the result of the MeasureText call for that string. The Renderer is then able to choose how it wants to fit a piece of text in a smaller space (often this involves removing some number of characters and replacing them with "..." characters) 2025-03-05 13:04:57 -08:00
7 changed files with 34 additions and 14 deletions

View File

@ -367,6 +367,8 @@ Context :: struct {} // opaque structure, only use as a pointer
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
_OpenElement :: proc() ---
_CloseElement :: proc() ---
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
@ -399,9 +401,7 @@ foreign Clay {
@(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay {
_OpenElement :: proc() ---
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
_CloseElement :: proc() ---
_HashString :: proc(key: String, offset: u32, seed: u32) -> ElementId ---
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---

Binary file not shown.

Binary file not shown.

Binary file not shown.

44
clay.h
View File

@ -357,6 +357,8 @@ typedef CLAY_PACKED_ENUM {
CLAY_TEXT_ALIGN_CENTER,
// Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
CLAY_TEXT_ALIGN_RIGHT,
// The boundingBox passed to the TEXT render command may be smaller than the measured text size. The renderer must then decide how to shorten the text to make it fit
CLAY_TEXT_ALIGN_SHRINK,
} Clay_TextAlignment;
// Controls various functionality related to text elements.
@ -1156,6 +1158,7 @@ CLAY__ARRAY_DEFINE(Clay__MeasuredWord, Clay__MeasuredWordArray)
typedef struct {
Clay_Dimensions unwrappedDimensions;
int32_t measuredWordsStartIndex;
float minWidth;
bool containsNewlines;
// Hash map data
uint32_t id;
@ -1504,6 +1507,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
if (current == ' ' || current == '\n') {
int32_t length = end - start;
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = length, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData);
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
if (current == ' ') {
dimensions.width += spaceWidth;
@ -1529,6 +1533,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord);
lineWidth += dimensions.width;
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
}
measuredWidth = CLAY__MAX(lineWidth, measuredWidth);
@ -1663,21 +1668,25 @@ void Clay__CloseElement(void) {
}
}
float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right);
float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom);
// Attach children to the current open element
openLayoutElement->childrenOrTextContent.children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length];
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
openLayoutElement->dimensions.width = (float)(layoutConfig->padding.left + layoutConfig->padding.right);
openLayoutElement->dimensions.width = leftRightPadding;
openLayoutElement->minDimensions.width = leftRightPadding;
for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) {
int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i);
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
openLayoutElement->dimensions.width += child->dimensions.width;
openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom);
openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding);
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
if (!elementHasScrollHorizontal) {
openLayoutElement->minDimensions.width += child->minDimensions.width;
}
if (!elementHasScrollVertical) {
openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom);
openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding);
}
Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex);
}
@ -1686,18 +1695,19 @@ void Clay__CloseElement(void) {
openLayoutElement->minDimensions.width += childGap;
}
else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) {
openLayoutElement->dimensions.height = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom);
openLayoutElement->dimensions.height = topBottomPadding;
openLayoutElement->minDimensions.height = topBottomPadding;
for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) {
int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i);
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
openLayoutElement->dimensions.height += child->dimensions.height;
openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + layoutConfig->padding.left + layoutConfig->padding.right);
openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding);
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
if (!elementHasScrollVertical) {
openLayoutElement->minDimensions.height += child->minDimensions.height;
}
if (!elementHasScrollHorizontal) {
openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + layoutConfig->padding.left + layoutConfig->padding.right);
openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding);
}
Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex);
}
@ -1849,7 +1859,7 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig)
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textMeasured->unwrappedDimensions.height };
textElement->dimensions = textDimensions;
textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->unwrappedDimensions.height, .height = textDimensions.height }; // TODO not sure this is the best way to decide min width for text
textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height };
textElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&context->textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = context->layoutElements.length - 1 });
textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) {
.length = 1,
@ -2237,25 +2247,25 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
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;
float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
if (!xAxis && Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_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;
// If we're laying out the children of a scroll panel, grow containers expand to the size of the inner content, not the outer container
if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL)) {
Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig;
if (((xAxis && scrollElementConfig->horizontal) || (!xAxis && scrollElementConfig->vertical))) {
maxSize = CLAY__MAX(maxSize, innerContentSize);
}
}
if (childSizing.type == CLAY__SIZING_TYPE_FIT) {
*childSize = CLAY__MAX(childSizing.size.minMax.min, CLAY__MIN(*childSize, maxSize));
} else if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
*childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max);
}
*childSize = CLAY__MAX(minSize, CLAY__MIN(*childSize, maxSize));
}
}
}
@ -2692,14 +2702,24 @@ void Clay__CalculateFinalLayout(void) {
continue;
}
float offset = (currentElementBoundingBox.width - wrappedLine->dimensions.width);
if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT) {
if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_LEFT || textElementConfig->textAlignment == CLAY_TEXT_ALIGN_SHRINK) {
offset = 0;
}
if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_CENTER) {
offset /= 2;
}
Clay_BoundingBox textBoundingBox = {
currentElementBoundingBox.x + offset,
currentElementBoundingBox.y + yPosition,
wrappedLine->dimensions.width,
wrappedLine->dimensions.height
};
if (textElementConfig->textAlignment == CLAY_TEXT_ALIGN_SHRINK && boundingBox.width > currentElementBoundingBox.width) {
boundingBox.width = currentElementBoundingBox.width;
}
Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) {
.boundingBox = { currentElementBoundingBox.x + offset, currentElementBoundingBox.y + yPosition, wrappedLine->dimensions.width, wrappedLine->dimensions.height },
.boundingBox = textBoundingBox,
.renderData = { .text = {
.stringContents = CLAY__INIT(Clay_StringSlice) { .length = wrappedLine->line.length, .chars = wrappedLine->line.chars, .baseChars = currentElement->childrenOrTextContent.textElementData->text.chars },
.textColor = textElementConfig->textColor,