Merge branch 'main' of https://github.com/nicbarker/clay into fix_clay_macro_latch_increment

This commit is contained in:
Taylor Robbins (Piggybank Studios) 2025-03-24 18:43:26 -07:00
commit 8d165e271a
22 changed files with 778 additions and 91 deletions

View File

@ -4,7 +4,7 @@
### Major Features
- Microsecond layout performance
- Flex-box like layout model for complex, responsive layouts including text wrapping, scrolling containers and aspect ratio scaling
- Single ~2k LOC **clay.h** file with **zero** dependencies (including no standard library)
- Single ~4k LOC **clay.h** file with **zero** dependencies (including no standard library)
- Wasm support: compile with clang to a 15kb uncompressed **.wasm** file for use in the browser
- Static arena based memory use with no malloc / free, and low total memory overhead (e.g. ~3.5mb for 8192 layout elements).
- React-like nested declarative syntax

View File

@ -17,6 +17,7 @@ when ODIN_OS == .Windows {
}
String :: struct {
isStaticallyAllocated: c.bool,
length: c.int32_t,
chars: [^]c.char,
}
@ -102,6 +103,7 @@ TextAlignment :: enum EnumBackingType {
}
TextElementConfig :: struct {
userData: rawptr,
textColor: Color,
fontId: u16,
fontSize: u16,
@ -172,7 +174,7 @@ FloatingElementConfig :: struct {
offset: Vector2,
expand: Dimensions,
parentId: u32,
zIndex: i32,
zIndex: i16,
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
@ -366,6 +368,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) ---
@ -398,9 +402,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 ---
@ -418,7 +420,13 @@ UI :: proc() -> proc (config: ElementDeclaration) -> bool {
return ConfigureOpenElement
}
Text :: proc(text: string, config: ^TextElementConfig) {
Text :: proc($text: string, config: ^TextElementConfig) {
wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
}
TextDynamic :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config)
}
@ -457,3 +465,7 @@ MakeString :: proc(label: string) -> String {
ID :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index, 0)
}
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index, _GetParentElementId())
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -62,7 +62,7 @@ border2pxRed := clay.BorderElementConfig {
color = COLOR_RED
}
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: string, image: ^raylib.Texture2D) {
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, $text: string, image: ^raylib.Texture2D) {
if clay.UI()({
id = clay.ID("HeroBlob", index),
layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } },
@ -252,7 +252,7 @@ ColorLerp :: proc(a: clay.Color, b: clay.Color, amount: f32) -> clay.Color {
return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount}
}
LOREM_IPSUM_TEXT := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
LOREM_IPSUM_TEXT :: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI()({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
@ -321,7 +321,7 @@ HighPerformancePageMobile :: proc(lerpValue: f32) {
}
}
RendererButtonActive :: proc(index: i32, text: string) {
RendererButtonActive :: proc(index: i32, $text: string) {
if clay.UI()({
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_RED,
@ -331,7 +331,7 @@ RendererButtonActive :: proc(index: i32, text: string) {
}
}
RendererButtonInactive :: proc(index: u32, text: string) {
RendererButtonInactive :: proc(index: u32, $text: string) {
if clay.UI()({ border = border2pxRed }) {
if clay.UI()({
id = clay.ID("RendererButtonInactiveInner", index),
@ -489,7 +489,7 @@ errorHandler :: proc "c" (errorData: clay.ErrorData) {
}
main :: proc() {
minMemorySize: u32 = clay.MinMemorySize()
minMemorySize: c.size_t = cast(c.size_t)clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })

276
clay.h
View File

@ -1,4 +1,4 @@
// VERSION: 0.12
// VERSION: 0.13
/*
NOTE: In order to use this library you must define
@ -96,12 +96,16 @@
#define CLAY__ENSURE_STRING_LITERAL(x) ("" x "")
// Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString)
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING_CONST(string) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
#define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
// GCC marks the above CLAY__ELEMENT_DEFINITION_LATCH as an unused variable for files that include clay.h but don't declare any layout
// This is to suppress that warning
static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (void) CLAY__ELEMENT_DEFINITION_LATCH; }
// Publicly visible layout element macros -----------------------------------------------------
/* This macro looks scary on the surface, but is actually quite simple.
@ -181,6 +185,9 @@ extern "C" {
// 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 {
// Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program.
// This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal.
bool isStaticallyAllocated;
int32_t length;
// The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory.
const char *chars;
@ -266,7 +273,7 @@ typedef CLAY_PACKED_ENUM {
CLAY_ALIGN_Y_TOP,
// Aligns child elements to the bottom of this element, offset by padding.width.bottom
CLAY_ALIGN_Y_BOTTOM,
// Aligns child elements vertiically to the center of this element
// Aligns child elements vertically to the center of this element
CLAY_ALIGN_Y_CENTER,
} Clay_LayoutAlignmentY;
@ -357,6 +364,8 @@ typedef CLAY_PACKED_ENUM {
// Controls various functionality related to text elements.
typedef struct {
// A pointer that will be transparently passed through to the resulting render command.
void *userData;
// The RGBA color of the font to render, conventionally specified as 0-255.
Clay_Color textColor;
// An integer transparently passed to Clay_MeasureText to identify the font to use.
@ -378,10 +387,6 @@ typedef struct {
// CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box.
// CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
Clay_TextAlignment textAlignment;
// When set to true, clay will hash the entire text contents of this string as an identifier for its internal
// text measurement cache, rather than just the pointer and length. This will incur significant performance cost for
// long bodies of text.
bool hashStringContents;
} Clay_TextElementConfig;
CLAY__WRAPPER_STRUCT(Clay_TextElementConfig);
@ -870,14 +875,14 @@ CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
// Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
// This may require reallocating additional memory, and re-calling Clay_Initialize();
CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
// Resets Clay's internal text measurement cache, useful if memory to represent strings is being re-used.
// Similar behaviour can be achieved on an individual text element level by using Clay_TextElementConfig.hashStringContents
// Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded.
CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void);
// Internal API functions required by macros ----------------------
CLAY_DLL_EXPORT void Clay__OpenElement(void);
CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config);
CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config);
CLAY_DLL_EXPORT void Clay__CloseElement(void);
CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed);
CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig);
@ -1149,6 +1154,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;
@ -1340,26 +1346,140 @@ Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const ui
return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id"
}
uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
uint32_t hash = 0;
uintptr_t pointerAsNumber = (uintptr_t)text->chars;
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) {
return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r));
}
if (config->hashStringContents) {
uint32_t maxLengthToHash = CLAY__MIN(text->length, 256);
for (uint32_t i = 0; i < maxLengthToHash; i++) {
hash += text->chars[i];
hash += (hash << 10);
hash ^= (hash >> 6);
static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) {
*a = _mm_add_epi64(*a, *b);
*b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
__m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL);
__m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL);
__m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL);
__m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs
while (length > 0) {
__m128i msg;
if (length >= 16) {
msg = _mm_loadu_si128((const __m128i*)data);
data += 16;
length -= 16;
}
} else {
hash += pointerAsNumber;
else {
for (int i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
msg = _mm_loadu_si128((const __m128i*)overflowBuffer);
length = 0;
}
v0 = _mm_xor_si128(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
uint64_t result[2];
_mm_storeu_si128((__m128i*)result, v0);
return result[0] ^ result[1];
}
#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__)
static inline uint64x2_t Clay__SIMDRotateLeft(uint64x2_t x, int r) {
return vorrq_u64(vshlq_n_u64(x, 17), vshrq_n_u64(x, 64 - 17));
}
static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) {
*a = vaddq_u64(*a, *b);
*b = veorq_u64(Clay__SIMDRotateLeft(*b, 17), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL);
uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL);
uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL);
uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[8] = { 0 };
while (length > 0) {
uint64x2_t msg;
if (length > 16) {
msg = vld1q_u64((const uint64_t*)data);
data += 16;
length -= 16;
}
else if (length > 8) {
msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0));
data += 8;
length -= 8;
}
else {
for (int i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
uint8x8_t lower = vld1_u8(overflowBuffer);
msg = vcombine_u8(lower, vdup_n_u8(0));
length = 0;
}
v0 = veorq_u64(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
uint64_t result[2];
vst1q_u64(result, v0);
return result[0] ^ result[1];
}
#else
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
uint64_t hash = 0;
for (int32_t i = 0; i < length; i++) {
hash += data[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
return hash;
}
#endif
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
uint32_t hash = 0;
if (text->isStaticallyAllocated) {
hash += (uintptr_t)text->chars;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
} else {
hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX;
}
hash += config->fontId;
hash += (hash << 10);
@ -1369,18 +1489,10 @@ uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *con
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->lineHeight;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->letterSpacing;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->wrapMode;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
@ -1415,7 +1527,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
return &Clay__MeasureTextCacheItem_DEFAULT;
}
#endif
uint32_t id = Clay__HashTextWithConfig(text, config);
uint32_t id = Clay__HashStringContentsWithConfig(text, config);
uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32);
int32_t elementIndexPrevious = 0;
int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket];
@ -1497,6 +1609,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;
@ -1522,6 +1635,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);
@ -1651,24 +1765,30 @@ void Clay__CloseElement(void) {
elementHasScrollVertical = config->config.scrollElementConfig->vertical;
context->openClipElementStack.length--;
break;
} else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) {
context->openClipElementStack.length--;
}
}
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);
}
@ -1677,18 +1797,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);
}
@ -1769,7 +1890,7 @@ bool Clay__MemCmp(const char *s1, const char *s2, int32_t length);
uint8x16_t v2 = vld1q_u8((const uint8_t *)s2);
// Compare vectors
if (vminvq_u32(vceqq_u8(v1, v2)) != 0xFFFFFFFF) { // If there's a difference
if (vminvq_u32(vreinterpretq_u32_u8(vceqq_u8(v1, v2))) != 0xFFFFFFFF) { // If there's a difference
return false;
}
@ -1840,7 +1961,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,
@ -1863,58 +1984,58 @@ Clay_ElementId Clay__AttachId(Clay_ElementId elementId) {
return elementId;
}
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) {
Clay_Context* context = Clay_GetCurrentContext();
Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement();
openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration.layout);
if ((declaration.layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration.layout.sizing.width.size.percent > 1) || (declaration.layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration.layout.sizing.height.size.percent > 1)) {
openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration->layout);
if ((declaration->layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.width.size.percent > 1) || (declaration->layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.height.size.percent > 1)) {
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
.errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1,
.errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."),
.userData = context->errorHandler.userData });
}
Clay_ElementId openLayoutElementId = declaration.id;
Clay_ElementId openLayoutElementId = declaration->id;
openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length];
Clay_SharedElementConfig *sharedConfig = NULL;
if (declaration.backgroundColor.a > 0) {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration.backgroundColor });
if (declaration->backgroundColor.a > 0) {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration->backgroundColor });
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
}
if (!Clay__MemCmp((char *)(&declaration.cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) {
if (!Clay__MemCmp((char *)(&declaration->cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) {
if (sharedConfig) {
sharedConfig->cornerRadius = declaration.cornerRadius;
sharedConfig->cornerRadius = declaration->cornerRadius;
} else {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration.cornerRadius });
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration->cornerRadius });
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
}
}
if (declaration.userData != 0) {
if (declaration->userData != 0) {
if (sharedConfig) {
sharedConfig->userData = declaration.userData;
sharedConfig->userData = declaration->userData;
} else {
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration.userData });
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration->userData });
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
}
}
if (declaration.image.imageData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration.image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE);
if (declaration->image.imageData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration->image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE);
Clay__int32_tArray_Add(&context->imageElementPointers, context->layoutElements.length - 1);
}
if (declaration.floating.attachTo != CLAY_ATTACH_TO_NONE) {
Clay_FloatingElementConfig floatingConfig = declaration.floating;
if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) {
Clay_FloatingElementConfig floatingConfig = declaration->floating;
// This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here
Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2));
if (hierarchicalParent) {
uint32_t clipElementId = 0;
if (declaration.floating.attachTo == CLAY_ATTACH_TO_PARENT) {
if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) {
// Attach to the element's direct hierarchical parent
floatingConfig.parentId = hierarchicalParent->id;
if (context->openClipElementStack.length > 0) {
clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1);
}
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) {
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) {
Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig.parentId);
if (!parentItem) {
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
@ -1922,14 +2043,17 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
.errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."),
.userData = context->errorHandler.userData });
} else {
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, parentItem->layoutElement - context->layoutElements.internalArray);
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray));
}
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ROOT) {
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) {
floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0, 0).id;
}
if (!openLayoutElementId.id) {
openLayoutElementId = Clay__HashString(CLAY_STRING("Clay__FloatingContainer"), context->layoutElementTreeRoots.length, 0);
}
int32_t currentElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1);
Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId);
Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId);
Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) {
.layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1),
.parentId = floatingConfig.parentId,
@ -1939,8 +2063,8 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(floatingConfig) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
}
}
if (declaration.custom.customData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration.custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
if (declaration->custom.customData) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration->custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
}
if (openLayoutElementId.id != 0) {
@ -1949,8 +2073,8 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
openLayoutElementId = Clay__GenerateIdForAnonymousElement(openLayoutElement);
}
if (declaration.scroll.horizontal | declaration.scroll.vertical) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration.scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
if (declaration->scroll.horizontal | declaration->scroll.vertical) {
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration->scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id);
// Retrieve or create cached data to track scroll position across frames
Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL;
@ -1969,11 +2093,15 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData);
}
}
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->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);
}
}
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
Clay__ConfigureOpenElementPtr(&declaration);
}
void Clay__InitializeEphemeralMemory(Clay_Context* context) {
int32_t maxElementCount = context->maxElementCount;
// Ephemeral Memory - reset every frame
@ -2221,25 +2349,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,7 +2820,7 @@ void Clay__CalculateFinalLayout(void) {
.letterSpacing = textElementConfig->letterSpacing,
.lineHeight = textElementConfig->lineHeight,
}},
.userData = sharedConfig->userData,
.userData = textElementConfig->userData,
.id = Clay__HashNumber(lineIndex, currentElement->id).id,
.zIndex = root->zIndex,
.commandType = CLAY_RENDER_COMMAND_TYPE_TEXT,
@ -3627,7 +3755,7 @@ uint32_t Clay_MinMemorySize(void) {
Clay__Context_Allocate_Arena(&fakeContext.internalArena);
Clay__InitializePersistentMemory(&fakeContext);
Clay__InitializeEphemeralMemory(&fakeContext);
return fakeContext.internalArena.nextAllocation + 128;
return (uint32_t)fakeContext.internalArena.nextAllocation + 128;
}
CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory")
@ -3680,7 +3808,7 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) {
context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true;
Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1));
Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great
int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, currentElement - context->layoutElements.internalArray);
int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray));
Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId);
Clay_BoundingBox elementBox = mapItem->boundingBox;
elementBox.x -= root->pointerOffset.x;

View File

@ -17,6 +17,41 @@ void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
struct ResizeRenderData_ {
SDL_Window* window;
int windowWidth;
int windowHeight;
ClayVideoDemo_Data demoData;
SDL_Renderer* renderer;
SDL2_Font* fonts;
};
typedef struct ResizeRenderData_ ResizeRenderData;
int resizeRendering(void* userData, SDL_Event* event) {
ResizeRenderData *actualData = userData;
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED) {
SDL_Window* window = actualData->window;
int windowWidth = actualData->windowWidth;
int windowHeight = actualData->windowHeight;
ClayVideoDemo_Data demoData = actualData->demoData;
SDL_Renderer* renderer = actualData->renderer;
SDL2_Font* fonts = actualData->fonts;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight });
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
Clay_SDL2_Render(renderer, renderCommands, fonts);
SDL_RenderPresent(renderer);
}
return 0;
}
int main(int argc, char *argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError());
@ -73,6 +108,18 @@ int main(int argc, char *argv[]) {
double deltaTime = 0;
ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize();
ResizeRenderData userData = {
window, // SDL_Window*
windowWidth, // int
windowHeight, // int
demoData, // CustomShit
renderer, // SDL_Renderer*
fonts // SDL2_Font[1]
};
// add an event watcher that will render the screen while youre dragging the window to different sizes
SDL_AddEventWatch(resizeRendering, &userData);
while (true) {
Clay_Vector2 scrollDelta = {};
SDL_Event event;

View File

@ -119,6 +119,7 @@
{name: 'bottomRight', type: 'float'},
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'userData', type: 'uint32_t' },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },

View File

@ -119,6 +119,7 @@
{name: 'bottomRight', type: 'float'},
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'userData', type: 'uint32_t' },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },

View File

@ -50,4 +50,6 @@ int main(void) {
Clay_Raylib_Render(renderCommands, fonts);
EndDrawing();
}
// This function is new since the video was published
Clay_Raylib_Close();
}

View File

@ -72,4 +72,6 @@ int main(void) {
Clay_Raylib_Render(renderCommandsBottom, fonts);
EndDrawing();
}
Clay_Raylib_Close();
}

View File

@ -250,5 +250,6 @@ int main(void) {
}
UpdateDrawFrame(fonts);
}
Clay_Raylib_Close();
return 0;
}

View File

@ -0,0 +1,4 @@
# to build this, install mingw
gcc main.c -ggdb -omain -lgdi32 -lmingw32 # -mwindows # comment -mwindows out for console output

218
examples/win32_gdi/main.c Normal file
View File

@ -0,0 +1,218 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "../../renderers/win32_gdi/clay_renderer_gdi.c"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../shared-layouts/clay-video-demo.c"
ClayVideoDemo_Data demo_data;
#define APPNAME "Clay GDI Example"
char szAppName[] = APPNAME; // The name of this application
char szTitle[] = APPNAME; // The title bar text
void CenterWindow(HWND hWnd);
long lastMsgTime = 0;
bool ui_debug_mode;
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
// ----------------------- first and last
case WM_CREATE:
CenterWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_MOUSEWHEEL: // scrolling data
{
short zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
// todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed
long now = GetMessageTime();
float dt = (now - lastMsgTime) / 1000.00;
lastMsgTime = now;
// little hacky hack to make scrolling *feel* right
if (abs(zDelta) > 100)
{
zDelta = zDelta * .012;
}
Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_RBUTTONUP:
case WM_LBUTTONUP:
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_MOUSEMOVE: // mouse events
{
short mouseX = GET_X_LPARAM(lParam);
short mouseY = GET_Y_LPARAM(lParam);
short mouseButtons = LOWORD(wParam);
Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_SIZE: // resize events
{
RECT r = {0};
if (GetClientRect(hwnd, &r))
{
Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left};
Clay_SetLayoutDimensions(dim);
}
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
}
case WM_KEYDOWN:
if (VK_ESCAPE == wParam)
{
DestroyWindow(hwnd);
break;
}
if (wParam == VK_F12)
{
Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode);
break;
}
printf("Key Pressed: %d\r\n", wParam);
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
break;
// ----------------------- render
case WM_PAINT:
{
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
Clay_Win32_Render(hwnd, renderCommands);
break;
}
// ----------------------- let windows do all other stuff
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
bool didAllocConsole = false;
void HandleClayErrors(Clay_ErrorData errorData)
{
if (!didAllocConsole)
{
didAllocConsole = AllocConsole();
}
printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars);
}
int APIENTRY WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
MSG msg;
WNDCLASS wc;
HWND hwnd;
demo_data = ClayVideoDemo_Initialize();
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published
Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, NULL);
ZeroMemory(&wc, sizeof wc);
wc.hInstance = hInstance;
wc.lpszClassName = szAppName;
wc.lpfnWndProc = (WNDPROC)WndProc;
wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW;
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if (FALSE == RegisterClass(&wc))
return 0;
hwnd = CreateWindow(
szAppName,
szTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
800, // CW_USEDEFAULT,
600, // CW_USEDEFAULT,
0,
0,
hInstance,
0);
if (hwnd == NULL)
return 0;
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
void CenterWindow(HWND hwnd_self)
{
HWND hwnd_parent;
RECT rw_self, rc_parent, rw_parent;
int xpos, ypos;
hwnd_parent = GetParent(hwnd_self);
if (NULL == hwnd_parent)
hwnd_parent = GetDesktopWindow();
GetWindowRect(hwnd_parent, &rw_parent);
GetClientRect(hwnd_parent, &rc_parent);
GetWindowRect(hwnd_self, &rw_self);
xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2;
ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2;
SetWindowPos(
hwnd_self, NULL,
xpos, ypos, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
//+---------------------------------------------------------------------------

View File

@ -3,6 +3,7 @@
#include <SDL_ttf.h>
#include <SDL_image.h>
#include <stdio.h>
#include <math.h>
#ifndef M_PI
#define M_PI 3.14159
@ -408,7 +409,7 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
SDL_RenderCornerBorder(renderer, &boundingBox, config, 1, config->color);
}
if (config->width.bottom > 0 & config->cornerRadius.bottomLeft > 0) {
if (config->width.bottom > 0 & config->cornerRadius.bottomRight > 0) {
SDL_RenderCornerBorder(renderer, &boundingBox, config, 2, config->color);
}

View File

@ -125,6 +125,21 @@ void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned i
// EnableEventWaiting();
}
// A MALLOC'd buffer, that we keep modifying inorder to save from so many Malloc and Free Calls.
// Call Clay_Raylib_Close() to free
static char *temp_render_buffer = NULL;
static int temp_render_buffer_len = 0;
// Call after closing the window to clean up the render buffer
void Clay_Raylib_Close()
{
if(temp_render_buffer) free(temp_render_buffer);
temp_render_buffer_len = 0;
CloseWindow();
}
void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
{
for (int j = 0; j < renderCommands.length; j++)
@ -134,14 +149,23 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
switch (renderCommand->commandType)
{
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
Clay_TextRenderData *textData = &renderCommand->renderData.text;
char *cloned = (char *)malloc(textData->stringContents.length + 1);
memcpy(cloned, textData->stringContents.chars, textData->stringContents.length);
cloned[textData->stringContents.length] = '\0';
Font fontToUse = fonts[textData->fontId];
DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
free(cloned);
int strlen = textData->stringContents.length + 1;
if(strlen > temp_render_buffer_len) {
// Grow the temp buffer if we need a larger string
if(temp_render_buffer) free(temp_render_buffer);
temp_render_buffer = malloc(strlen);
temp_render_buffer_len = strlen;
}
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
memcpy(temp_render_buffer, textData->stringContents.chars, textData->stringContents.length);
temp_render_buffer[textData->stringContents.length] = '\0';
DrawTextEx(fontToUse, temp_render_buffer, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {

View File

@ -0,0 +1,5 @@
The windows GDI renderer example is missing the following:
- Images
- Rendering Rounded Rectangle borders
- Custom Fonts (font size)

View File

@ -0,0 +1,241 @@
#include <Windows.h>
#include "../../clay.h"
HDC renderer_hdcMem = {0};
HBITMAP renderer_hbmMem = {0};
HANDLE renderer_hOld = {0};
void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
{
bool is_clipping = false;
HRGN clipping_region = {0};
PAINTSTRUCT ps;
HDC hdc;
RECT rc; // Top left of our window
GetWindowRect(hwnd, &rc);
hdc = BeginPaint(hwnd, &ps);
int win_width = rc.right - rc.left,
win_height = rc.bottom - rc.top;
// Create an off-screen DC for double-buffering
renderer_hdcMem = CreateCompatibleDC(hdc);
renderer_hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height);
renderer_hOld = SelectObject(renderer_hdcMem, renderer_hbmMem);
// draw
for (int j = 0; j < renderCommands.length; j++)
{
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
switch (renderCommand->commandType)
{
case CLAY_RENDER_COMMAND_TYPE_TEXT:
{
Clay_Color c = renderCommand->renderData.text.textColor;
SetTextColor(renderer_hdcMem, RGB(c.r, c.g, c.b));
SetBkMode(renderer_hdcMem, TRANSPARENT);
RECT r = rc;
r.left = boundingBox.x;
r.top = boundingBox.y;
r.right = boundingBox.x + boundingBox.width + r.right;
r.bottom = boundingBox.y + boundingBox.height + r.bottom;
DrawTextA(renderer_hdcMem, renderCommand->renderData.text.stringContents.chars,
renderCommand->renderData.text.stringContents.length,
&r, DT_TOP | DT_LEFT);
break;
}
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE:
{
Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle;
RECT r = rc;
r.left = boundingBox.x;
r.top = boundingBox.y;
r.right = boundingBox.x + boundingBox.width;
r.bottom = boundingBox.y + boundingBox.height;
HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b));
if (rrd.cornerRadius.topLeft > 0)
{
HRGN roundedRectRgn = CreateRoundRectRgn(
r.left, r.top, r.right + 1, r.bottom + 1,
rrd.cornerRadius.topLeft * 2, rrd.cornerRadius.topLeft * 2);
FillRgn(renderer_hdcMem, roundedRectRgn, recColor);
DeleteObject(roundedRectRgn);
}
else
{
FillRect(renderer_hdcMem, &r, recColor);
}
DeleteObject(recColor);
break;
}
// The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox.
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START:
{
is_clipping = true;
clipping_region = CreateRectRgn(boundingBox.x,
boundingBox.y,
boundingBox.x + boundingBox.width,
boundingBox.y + boundingBox.height);
SelectClipRgn(renderer_hdcMem, clipping_region);
break;
}
// The renderer should finish any previously active clipping, and begin rendering elements in full again.
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END:
{
SelectClipRgn(renderer_hdcMem, NULL);
if (clipping_region)
{
DeleteObject(clipping_region);
}
is_clipping = false;
clipping_region = NULL;
break;
}
// The renderer should draw a colored border inset into the bounding box.
case CLAY_RENDER_COMMAND_TYPE_BORDER:
{
Clay_BorderRenderData brd = renderCommand->renderData.border;
RECT r = rc;
r.left = boundingBox.x;
r.top = boundingBox.y;
r.right = boundingBox.x + boundingBox.width;
r.bottom = boundingBox.y + boundingBox.height;
HPEN topPen = CreatePen(PS_SOLID, brd.width.top, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN leftPen = CreatePen(PS_SOLID, brd.width.left, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN bottomPen = CreatePen(PS_SOLID, brd.width.bottom, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN rightPen = CreatePen(PS_SOLID, brd.width.right, RGB(brd.color.r, brd.color.g, brd.color.b));
HPEN oldPen = SelectObject(renderer_hdcMem, topPen);
if (brd.cornerRadius.topLeft == 0)
{
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.top);
SelectObject(renderer_hdcMem, leftPen);
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.left, r.bottom);
SelectObject(renderer_hdcMem, bottomPen);
MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
SelectObject(renderer_hdcMem, rightPen);
MoveToEx(renderer_hdcMem, r.right, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
}
else
{
// todo: i should be rounded
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.top);
SelectObject(renderer_hdcMem, leftPen);
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
LineTo(renderer_hdcMem, r.left, r.bottom);
SelectObject(renderer_hdcMem, bottomPen);
MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
SelectObject(renderer_hdcMem, rightPen);
MoveToEx(renderer_hdcMem, r.right, r.top, NULL);
LineTo(renderer_hdcMem, r.right, r.bottom);
}
SelectObject(renderer_hdcMem, oldPen);
DeleteObject(topPen);
DeleteObject(leftPen);
DeleteObject(bottomPen);
DeleteObject(rightPen);
break;
}
// case CLAY_RENDER_COMMAND_TYPE_IMAGE:
// {
// // TODO: i couldnt get the win 32 api to load a bitmap.... So im punting on this one :(
// break;
// }
default:
printf("Unhandled render command %d\r\n", renderCommand->commandType);
break;
}
}
BitBlt(hdc, 0, 0, win_width, win_height, renderer_hdcMem, 0, 0, SRCCOPY);
// Free-up the off-screen DC
SelectObject(renderer_hdcMem, renderer_hOld);
DeleteObject(renderer_hbmMem);
DeleteDC(renderer_hdcMem);
EndPaint(hwnd, &ps);
}
/*
Hacks due to the windows api not making sence to use.... may measure too large, but never too small
*/
#ifndef WIN32_FONT_HEIGHT
#define WIN32_FONT_HEIGHT (16)
#endif
#ifndef WIN32_FONT_WIDTH
#define WIN32_FONT_WIDTH (8)
#endif
static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
{
Clay_Dimensions textSize = {0};
float maxTextWidth = 0.0f;
float lineTextWidth = 0;
float textHeight = WIN32_FONT_HEIGHT;
for (int i = 0; i < text.length; ++i)
{
if (text.chars[i] == '\n')
{
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
lineTextWidth = 0;
continue;
}
lineTextWidth += WIN32_FONT_WIDTH;
}
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
textSize.width = maxTextWidth;
textSize.height = textHeight;
return textSize;
}