mirror of
https://github.com/nicbarker/clay.git
synced 2025-09-18 12:36:17 +00:00
[Core] Add cascading property support for text element configs.
This change introduces an "inheritance" mechanism for all text-related properties, enabling developers to define base styles once and have child elements automatically pick up defaults unless explicitly overridden, much like CSS’s cascade model. By providing individual sentinel values (`CLAY_COLOR_INHERIT`, `CLAY_TEXT_INHERIT_U16`, `CLAY_TEXT_WRAP_INHERIT`, and `CLAY_TEXT_ALIGN_INHERIT`), or `CLAY_TEXT_CONFIG_INHERIT_ALL`, we can now defer resolution of properties, Backward compatibility is preserved: if no `_INHERIT` values are used, behavior remains identical to previous versions. Previously, Clay used the presence of `CLAY__ELEMENT_CONFIG_TYPE_TEXT` in `Clay_LayoutElement.elementConfigs` to distinguish between a text and non-text element. This is not possible to use anymore, since parent non-text elements may have a `CLAY__ELEMENT_CONFIG_TYPE_TEXT` with text properties. I introduced a `hasTextElementData`, but I can understand if this design is considered to not be acceptable due to the higher memory usage in this core struct, perhaps in alternative a new `CLAY__ELEMENT_CONFIG_TYPE_TEXT_STRING` element config can be introduced?
This commit is contained in:
parent
b33ba4ff62
commit
d6ed6cb5c2
124
clay.h
124
clay.h
|
@ -350,6 +350,8 @@ typedef CLAY_PACKED_ENUM {
|
|||
CLAY_TEXT_WRAP_NEWLINES,
|
||||
// Disable text wrapping entirely.
|
||||
CLAY_TEXT_WRAP_NONE,
|
||||
// Inherit from nearest ancestor (or default `CLAY_TEXT_WRAP_WORDS` if none)
|
||||
CLAY_TEXT_WRAP_INHERIT
|
||||
} Clay_TextElementConfigWrapMode;
|
||||
|
||||
// Controls how wrapped lines of text are horizontally aligned within the outer text bounding box.
|
||||
|
@ -360,6 +362,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,
|
||||
// Inherit from nearest ancestor (or default `CLAY_TEXT_ALIGN_LEFT` if none)
|
||||
CLAY_TEXT_ALIGN_INHERIT,
|
||||
} Clay_TextAlignment;
|
||||
|
||||
// Controls various functionality related to text elements.
|
||||
|
@ -391,6 +395,29 @@ typedef struct {
|
|||
|
||||
CLAY__WRAPPER_STRUCT(Clay_TextElementConfig);
|
||||
|
||||
// Cascading support --------------------
|
||||
|
||||
/* 0xFFFF is outside the signed 16-bit range Clay already uses
|
||||
and cannot conflict with “real” font sizes, IDs, spacing, etc. */
|
||||
#define CLAY_TEXT_INHERIT_U16 ((uint16_t)0xFFFF)
|
||||
#define CLAY_TEXT_WRAP_INHERIT ((Clay_TextElementConfigWrapMode)-1)
|
||||
#define CLAY_TEXT_ALIGN_INHERIT ((Clay_TextAlignment)-1)
|
||||
|
||||
/* Chosen because fully–transparent black should never be rendered. */
|
||||
static const Clay_Color CLAY_COLOR_INHERIT = {0,0,0,0};
|
||||
|
||||
#define CLAY_TEXT_CONFIG_INHERIT_ALL \
|
||||
((Clay_TextElementConfig){ \
|
||||
.userData = NULL, \
|
||||
.textColor = CLAY_COLOR_INHERIT, \
|
||||
.fontId = CLAY_TEXT_INHERIT_U16, \
|
||||
.fontSize = CLAY_TEXT_INHERIT_U16, \
|
||||
.letterSpacing = CLAY_TEXT_INHERIT_U16, \
|
||||
.lineHeight = CLAY_TEXT_INHERIT_U16, \
|
||||
.wrapMode = CLAY_TEXT_WRAP_INHERIT, \
|
||||
.textAlignment = CLAY_TEXT_ALIGN_INHERIT \
|
||||
})
|
||||
|
||||
// Image --------------------------------
|
||||
|
||||
// Controls various settings related to image elements.
|
||||
|
@ -723,6 +750,8 @@ typedef struct {
|
|||
Clay_Color backgroundColor;
|
||||
// Controls the "radius", or corner rounding of elements, including rectangles, borders and images.
|
||||
Clay_CornerRadius cornerRadius;
|
||||
// Controls settings related to text elements.
|
||||
Clay_TextElementConfig text;
|
||||
// Controls settings related to image elements.
|
||||
Clay_ImageElementConfig image;
|
||||
// Controls whether and how an element "floats", which means it layers over the top of other elements in z order, and doesn't affect the position and size of siblings or parent elements.
|
||||
|
@ -1100,6 +1129,7 @@ typedef struct {
|
|||
Clay_LayoutConfig *layoutConfig;
|
||||
Clay__ElementConfigArraySlice elementConfigs;
|
||||
uint32_t id;
|
||||
bool hasTextElementData;
|
||||
} Clay_LayoutElement;
|
||||
|
||||
CLAY__ARRAY_DEFINE(Clay_LayoutElement, Clay_LayoutElementArray)
|
||||
|
@ -1731,6 +1761,10 @@ bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConf
|
|||
return false;
|
||||
}
|
||||
|
||||
bool Clay__ElementHasTextElementData(Clay_LayoutElement *layoutElement) {
|
||||
return layoutElement->hasTextElementData;
|
||||
}
|
||||
|
||||
void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) {
|
||||
for (int32_t j = 0; j < layoutElement->elementConfigs.length; j++) {
|
||||
Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, j);
|
||||
|
@ -1938,6 +1972,63 @@ void Clay__OpenElement(void) {
|
|||
}
|
||||
}
|
||||
|
||||
#define CLAY__IS_INHERIT_U16(v) ((v) == CLAY_TEXT_INHERIT_U16)
|
||||
#define CLAY__IS_INHERIT_WRAP(v) ((v) == CLAY_TEXT_WRAP_INHERIT)
|
||||
#define CLAY__IS_INHERIT_ALIGN(v) ((v) == CLAY_TEXT_ALIGN_INHERIT)
|
||||
#define CLAY__IS_INHERIT_COLOR(v) \
|
||||
((v).r == CLAY_COLOR_INHERIT.r && \
|
||||
(v).g == CLAY_COLOR_INHERIT.g && \
|
||||
(v).b == CLAY_COLOR_INHERIT.b && \
|
||||
(v).a == CLAY_COLOR_INHERIT.a)
|
||||
|
||||
// Returns true if any of the text properties still needs merging.
|
||||
static inline bool Clay__TextConfigHasInherit(const Clay_TextElementConfig *c)
|
||||
{
|
||||
return CLAY__IS_INHERIT_U16(c->fontId) || CLAY__IS_INHERIT_U16(c->fontSize) || CLAY__IS_INHERIT_U16(c->letterSpacing) ||
|
||||
CLAY__IS_INHERIT_U16(c->lineHeight) || CLAY__IS_INHERIT_WRAP(c->wrapMode) || CLAY__IS_INHERIT_ALIGN(c->textAlignment) ||
|
||||
(c->textColor.a == CLAY_COLOR_INHERIT.a);
|
||||
}
|
||||
|
||||
static Clay_TextElementConfig Clay__ResolveTextConfig(const Clay_TextElementConfig *local)
|
||||
{
|
||||
Clay_Context* ctx = Clay_GetCurrentContext();
|
||||
Clay_TextElementConfig out = *local;
|
||||
|
||||
int32_t stackLen = (int32_t)ctx->openLayoutElementStack.length;
|
||||
for (int32_t si = stackLen - 1; si >= 0 && Clay__TextConfigHasInherit(&out); --si)
|
||||
{
|
||||
int32_t elemIndex = Clay__int32_tArray_GetValue(&ctx->openLayoutElementStack, si);
|
||||
Clay_LayoutElement *ancestor = Clay_LayoutElementArray_Get(&ctx->layoutElements, elemIndex);
|
||||
|
||||
for (int32_t ci = 0; ci < ancestor->elementConfigs.length; ++ci) {
|
||||
Clay_ElementConfig *cfg =
|
||||
Clay__ElementConfigArraySlice_Get(&ancestor->elementConfigs, ci);
|
||||
if (cfg->type != CLAY__ELEMENT_CONFIG_TYPE_TEXT)
|
||||
continue;
|
||||
|
||||
Clay_TextElementConfig *anc = cfg->config.textElementConfig;
|
||||
if (CLAY__IS_INHERIT_U16(out.fontId)) out.fontId = anc->fontId;
|
||||
if (CLAY__IS_INHERIT_U16(out.fontSize)) out.fontSize = anc->fontSize;
|
||||
if (CLAY__IS_INHERIT_U16(out.letterSpacing)) out.letterSpacing = anc->letterSpacing;
|
||||
if (CLAY__IS_INHERIT_U16(out.lineHeight)) out.lineHeight = anc->lineHeight;
|
||||
if (CLAY__IS_INHERIT_WRAP(out.wrapMode)) out.wrapMode = anc->wrapMode;
|
||||
if (CLAY__IS_INHERIT_ALIGN(out.textAlignment)) out.textAlignment = anc->textAlignment;
|
||||
if (CLAY__IS_INHERIT_COLOR(out.textColor)) out.textColor = anc->textColor;
|
||||
}
|
||||
}
|
||||
|
||||
// Final fall-backs if there are still "inherit" properties to resolve.
|
||||
if (CLAY__IS_INHERIT_U16(out.fontId)) out.fontId = Clay_TextElementConfig_DEFAULT.fontId;
|
||||
if (CLAY__IS_INHERIT_U16(out.fontSize)) out.fontSize = Clay_TextElementConfig_DEFAULT.fontSize;
|
||||
if (CLAY__IS_INHERIT_U16(out.letterSpacing)) out.letterSpacing = Clay_TextElementConfig_DEFAULT.letterSpacing;
|
||||
if (CLAY__IS_INHERIT_U16(out.lineHeight)) out.lineHeight = Clay_TextElementConfig_DEFAULT.lineHeight;
|
||||
if (CLAY__IS_INHERIT_WRAP(out.wrapMode)) out.wrapMode = Clay_TextElementConfig_DEFAULT.wrapMode;
|
||||
if (CLAY__IS_INHERIT_ALIGN(out.textAlignment)) out.textAlignment = Clay_TextElementConfig_DEFAULT.textAlignment;
|
||||
if (CLAY__IS_INHERIT_COLOR(out.textColor)) out.textColor = Clay_TextElementConfig_DEFAULT.textColor;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
if (context->layoutElements.length == context->layoutElements.capacity - 1 || context->booleanWarnings.maxElementsExceeded) {
|
||||
|
@ -1955,6 +2046,12 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig)
|
|||
}
|
||||
|
||||
Clay__int32_tArray_Add(&context->layoutElementChildrenBuffer, context->layoutElements.length - 1);
|
||||
|
||||
if (Clay__TextConfigHasInherit(textConfig)) {
|
||||
Clay_TextElementConfig resolvedTextConfig = Clay__ResolveTextConfig(textConfig);
|
||||
textConfig = Clay__StoreTextElementConfig(resolvedTextConfig);
|
||||
}
|
||||
|
||||
Clay__MeasureTextCacheItem *textMeasured = Clay__MeasureTextCached(&text, textConfig);
|
||||
Clay_ElementId elementId = Clay__HashNumber(parentElement->childrenOrTextContent.children.length, parentElement->id);
|
||||
textElement->id = elementId.id;
|
||||
|
@ -1964,6 +2061,7 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig)
|
|||
textElement->dimensions = textDimensions;
|
||||
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->hasTextElementData = true;
|
||||
textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) {
|
||||
.length = 1,
|
||||
.internalArray = Clay__ElementConfigArray_Add(&context->elementConfigs, CLAY__INIT(Clay_ElementConfig) { .type = CLAY__ELEMENT_CONFIG_TYPE_TEXT, .config = { .textElementConfig = textConfig }})
|
||||
|
@ -2097,6 +2195,9 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) {
|
|||
if (!Clay__MemCmp((char *)(&declaration->border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration->border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER);
|
||||
}
|
||||
if (!Clay__MemCmp((char *)(&declaration->text), (char *)(&Clay_TextElementConfig_DEFAULT), sizeof(Clay_TextElementConfig))) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .textElementConfig = Clay__StoreTextElementConfig(declaration->text) }, CLAY__ELEMENT_CONFIG_TYPE_TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
|
@ -2212,13 +2313,13 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
|||
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
|
||||
float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height;
|
||||
|
||||
if (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && childElement->childrenOrTextContent.children.length > 0) {
|
||||
if (!Clay__ElementHasTextElementData(childElement) && childElement->childrenOrTextContent.children.length > 0) {
|
||||
Clay__int32_tArray_Add(&bfsBuffer, childElementIndex);
|
||||
}
|
||||
|
||||
if (childSizing.type != CLAY__SIZING_TYPE_PERCENT
|
||||
&& childSizing.type != CLAY__SIZING_TYPE_FIXED
|
||||
&& (!Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig->wrapMode == CLAY_TEXT_WRAP_WORDS)) // todo too many loops
|
||||
&& (!Clay__ElementHasTextElementData(childElement) || (Clay__FindElementConfigWithType(childElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT).textElementConfig->wrapMode == CLAY_TEXT_WRAP_WORDS)) // todo too many loops
|
||||
&& (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE))
|
||||
) {
|
||||
Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex);
|
||||
|
@ -2514,7 +2615,7 @@ void Clay__CalculateFinalLayout(void) {
|
|||
if (!context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) {
|
||||
context->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 (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0) {
|
||||
if (Clay__ElementHasTextElementData(currentElement) || currentElement->childrenOrTextContent.children.length == 0) {
|
||||
dfsBuffer.length--;
|
||||
continue;
|
||||
}
|
||||
|
@ -2792,6 +2893,9 @@ void Clay__CalculateFinalLayout(void) {
|
|||
break;
|
||||
}
|
||||
shouldRender = false;
|
||||
if (!currentElement->hasTextElementData) {
|
||||
break;
|
||||
}
|
||||
Clay_ElementConfigUnion configUnion = elementConfig->config;
|
||||
Clay_TextElementConfig *textElementConfig = configUnion.textElementConfig;
|
||||
float naturalLineHeight = currentElement->childrenOrTextContent.textElementData->preferredDimensions.height;
|
||||
|
@ -2873,7 +2977,7 @@ void Clay__CalculateFinalLayout(void) {
|
|||
}
|
||||
|
||||
// Setup initial on-axis alignment
|
||||
if (!Clay__ElementHasConfig(currentElementTreeNode->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) {
|
||||
if (!Clay__ElementHasTextElementData(currentElementTreeNode->layoutElement)) {
|
||||
Clay_Dimensions contentSize = {0,0};
|
||||
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
|
||||
for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) {
|
||||
|
@ -3001,7 +3105,7 @@ void Clay__CalculateFinalLayout(void) {
|
|||
}
|
||||
|
||||
// Add children to the DFS buffer
|
||||
if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) {
|
||||
if (!Clay__ElementHasTextElementData(currentElement)) {
|
||||
dfsBuffer.length += currentElement->childrenOrTextContent.children.length;
|
||||
for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) {
|
||||
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]);
|
||||
|
@ -3114,7 +3218,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR
|
|||
int32_t currentElementIndex = Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1);
|
||||
Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, (int)currentElementIndex);
|
||||
if (context->treeNodeVisited.internalArray[dfsBuffer.length - 1]) {
|
||||
if (!Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) && currentElement->childrenOrTextContent.children.length > 0) {
|
||||
if (!Clay__ElementHasTextElementData(currentElement) && currentElement->childrenOrTextContent.children.length > 0) {
|
||||
Clay__CloseElement();
|
||||
Clay__CloseElement();
|
||||
Clay__CloseElement();
|
||||
|
@ -3138,7 +3242,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR
|
|||
}
|
||||
CLAY({ .id = CLAY_IDI("Clay__DebugView_ElementOuter", currentElement->id), .layout = Clay__DebugView_ScrollViewItemLayoutConfig }) {
|
||||
// Collapse icon / button
|
||||
if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || currentElement->childrenOrTextContent.children.length == 0)) {
|
||||
if (!(Clay__ElementHasTextElementData(currentElement) || currentElement->childrenOrTextContent.children.length == 0)) {
|
||||
CLAY({
|
||||
.id = CLAY_IDI("Clay__DebugView_CollapseElement", currentElement->id),
|
||||
.layout = { .sizing = {CLAY_SIZING_FIXED(16), CLAY_SIZING_FIXED(16)}, .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER} },
|
||||
|
@ -3198,7 +3302,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR
|
|||
}
|
||||
|
||||
// Render the text contents below the element as a non-interactive row
|
||||
if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) {
|
||||
if (Clay__ElementHasTextElementData(currentElement)) {
|
||||
layoutData.rowCount++;
|
||||
Clay__TextElementData *textElementData = currentElement->childrenOrTextContent.textElementData;
|
||||
Clay_TextElementConfig *rawTextConfig = offscreen ? CLAY_TEXT_CONFIG({ .textColor = CLAY__DEBUGVIEW_COLOR_3, .fontSize = 16 }) : &Clay__DebugView_TextNameConfig;
|
||||
|
@ -3221,7 +3325,7 @@ Clay__RenderDebugLayoutData Clay__RenderDebugLayoutElementsList(int32_t initialR
|
|||
}
|
||||
|
||||
layoutData.rowCount++;
|
||||
if (!(Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT) || (currentElementData && currentElementData->debugData->collapsed))) {
|
||||
if (!(Clay__ElementHasTextElementData(currentElement) || (currentElementData && currentElementData->debugData->collapsed))) {
|
||||
for (int32_t i = currentElement->childrenOrTextContent.children.length - 1; i >= 0; --i) {
|
||||
Clay__int32_tArray_Add(&dfsBuffer, currentElement->childrenOrTextContent.children.elements[i]);
|
||||
context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = false; // TODO needs to be ranged checked
|
||||
|
@ -3826,7 +3930,7 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) {
|
|||
Clay__ElementIdArray_Add(&context->pointerOverIds, CLAY__INIT(Clay_ElementId) { .id = mapItem->idAlias });
|
||||
}
|
||||
}
|
||||
if (Clay__ElementHasConfig(currentElement, CLAY__ELEMENT_CONFIG_TYPE_TEXT)) {
|
||||
if (Clay__ElementHasTextElementData(currentElement)) {
|
||||
dfsBuffer.length--;
|
||||
continue;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue