diff --git a/README.md b/README.md index 957a8bd..ba6cd10 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ int main() { .backgroundColor = COLOR_LIGHT }) { CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) { - CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} } }) {} + CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture } }) {} CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} })); } @@ -161,8 +161,8 @@ For help starting out or to discuss clay, considering joining [the discord serve - [Clay_MinMemorySize](#clay_minmemorysize) - [Clay_CreateArenaWithCapacityAndMemory](#clay_createarenawithcapacityandmemory) - [Clay_SetMeasureTextFunction](#clay_setmeasuretextfunction) - - [Clay_ResetMeasureTextCache](#clau_resetmeasuretextcache) - - [Clay_SetMaxElementCount](clay_setmaxelementcount) + - [Clay_ResetMeasureTextCache](#clay_resetmeasuretextcache) + - [Clay_SetMaxElementCount](#clay_setmaxelementcount) - [Clay_SetMaxMeasureTextCacheWordCount](#clay_setmaxmeasuretextcachewordcount) - [Clay_Initialize](#clay_initialize) - [Clay_GetCurrentContext](#clay_getcurrentcontext) @@ -360,11 +360,11 @@ Clay_UpdateScrollContainers( ); // ... // Clay internally tracks the scroll containers offset, and Clay_GetScrollOffset returns the x,y offset of the currently open element -CLAY({ .clip = vertical, .childOffset = Clay_GetScrollOffset() }) { +CLAY({ .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() } }) { // Scrolling contents } // .childOffset can be provided directly if you would prefer to manage scrolling outside of clay -CLAY({ .clip = vertical, .childOffset = myData.scrollContainer.offset }) { +CLAY({ .clip = { .vertical = true, .childOffset = myData.scrollContainer.offset } }) { // Scrolling contents } ``` @@ -839,7 +839,6 @@ Clay_TextElementConfig { CLAY_TEXT_WRAP_NEWLINES, CLAY_TEXT_WRAP_NONE, }; - bool hashStringContents }; ``` @@ -899,14 +898,6 @@ Available options are: --- -**`.hashStringContents`** - -`CLAY_TEXT_CONFIG(.hashStringContents = true)` - -By default, clay will cache the dimensions of text measured by [the provided MeasureText function](#clay_setmeasuretextfunction) based on the string's pointer and length. Setting `.hashStringContents = true` will cause Clay to hash the entire string contents. Used to fix incorrect measurements caused by re-use of string memory, disabled by default as it will incur significant performance overhead for very large bodies of text. - ---- - **Examples** ```C @@ -1062,6 +1053,7 @@ typedef struct { Clay_LayoutConfig layout; Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; + Clay_AspectRatioElementConfig aspectRatio; Clay_ImageElementConfig image; Clay_FloatingElementConfig floating; Clay_CustomElementConfig custom; @@ -1108,9 +1100,17 @@ Note that the `CLAY_CORNER_RADIUS(radius)` function-like macro is available to p --- +**`.aspectRatio`** - `Clay_AspectRatioElementConfig` + +`CLAY({ .aspectRatio = 1 })` + +Uses [Clay_AspectRatioElementConfig](#clay_aspectratioelementconfig). Configures the element as an aspect ratio scaling element. Especially useful for rendering images, but can also be used to enforce a fixed width / height ratio of other elements. + +--- + **`.image`** - `Clay_ImageElementConfig` -`CLAY({ .image = { .imageData = &myImage, .sourceDimensions = { 640, 480 } } })` +`CLAY({ .image = { .imageData = &myImage } })` Uses [Clay_ImageElementConfig](#clay_imageelementconfig). Configures the element as an image element. Causes a render command with type `IMAGE` to be emitted. @@ -1294,23 +1294,12 @@ CLAY({ .id = CLAY_ID("Button"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTO ```C Clay_ImageElementConfig { - Clay_Dimensions sourceDimensions { - float width; float height; - }; void * imageData; }; ``` **Fields** -**`.sourceDimensions`** - `Clay_Dimensions` - -`CLAY({ .image = { .sourceDimensions = { 1024, 768 } } }) {}` - -Used to perform **aspect ratio scaling** on the image element. As of this version of clay, aspect ratio scaling only applies to the `height` of an image (i.e. image height will scale with width growth and limitations, but width will not scale with height growth and limitations) - ---- - **`.imageData`** - `void *` `CLAY({ .image = { .imageData = &myImage } }) {}` @@ -1321,7 +1310,7 @@ Used to perform **aspect ratio scaling** on the image element. As of this versio // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Note that when rendering, .imageData will be void* type. -CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = { 60, 60 } } }) {} +CLAY({ .image = { .imageData = &profilePicture } }) {} ``` **Examples** @@ -1330,11 +1319,105 @@ CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = { 60, 60 } } // Load an image somewhere in your code YourImage profilePicture = LoadYourImage("profilePicture.png"); // Declare a reusable image config -Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture, .sourceDimensions = {60, 60} }; +Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture }; // Declare an image element using a reusable config CLAY({ .image = imageConfig }) {} // Declare an image element using an inline config -CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} } }) {} +CLAY({ .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {} +// Rendering example +YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData; +``` + +**Rendering** + +Element is subject to [culling](#visibility-culling). Otherwise, a single `Clay_RenderCommand`s with `commandType = CLAY_RENDER_COMMAND_TYPE_IMAGE` will be created. The user will need to access `renderCommand->renderData.image->imageData` to retrieve image data referenced during layout creation. It's also up to the user to decide how / if they wish to blend `renderCommand->renderData.image->backgroundColor` with the image. + +--- + +### Clay_AspectRatioElementConfig + +**Usage** + +`CLAY({ .aspectRatio = 16.0 / 9.0 }) {}` + +**Clay_AspectRatioElementConfig** configures a clay element to enforce a fixed width / height ratio in its final dimensions. Mostly used for image elements, but can also be used for non image elements. + +**Struct API (Pseudocode)** + +```C +Clay_AspectRatioElementConfig { + float aspectRatio; +}; +``` + +**Fields** + +**`.aspectRatio`** - `float` + +`CLAY({ .aspectRatio = { .aspectRatio = 16.0 / 9.0 } }) {}` + +or alternatively, as C will automatically pass the value to the first nested struct field: + +`CLAY({ .aspectRatio = 16.0 / 9.0 }) {}` + +**Examples** + +```C +// Load an image somewhere in your code +YourImage profilePicture = LoadYourImage("profilePicture.png"); +// Declare an image element that will grow along the X axis while maintaining its original aspect ratio +CLAY({ + .layout = { .width = CLAY_SIZING_GROW() }, + .aspectRatio = profilePicture.width / profilePicture.height, + .image = { .imageData = &profilePicture }, +}) {} +``` + +--- + +### Clay_ImageElementConfig +**Usage** + +`CLAY({ .image = { ...image config } }) {}` + +**Clay_ImageElementConfig** configures a clay element to render an image as its background. + +**Struct API (Pseudocode)** + +```C +Clay_ImageElementConfig { + void * imageData; +}; +``` + +**Fields** + +**`.imageData`** - `void *` + +`CLAY({ .image = { .imageData = &myImage } }) {}` + +`.imageData` is a generic void pointer that can be used to pass through image data to the renderer. + +```C +// Load an image somewhere in your code +YourImage profilePicture = LoadYourImage("profilePicture.png"); +// Note that when rendering, .imageData will be void* type. +CLAY({ .image = { .imageData = &profilePicture } }) {} +``` + +Note: for an image to maintain its original aspect ratio when using dynamic scaling, the [.aspectRatio](#clay_aspectratioelementconfig) config option must be used. + +**Examples** + +```C +// Load an image somewhere in your code +YourImage profilePicture = LoadYourImage("profilePicture.png"); +// Declare a reusable image config +Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture }; +// Declare an image element using a reusable config +CLAY({ .image = imageConfig }) {} +// Declare an image element using an inline config +CLAY({ .image = { .imageData = &profilePicture }, .aspectRatio = 16.0 / 9.0 }) {} // Rendering example YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData; ``` @@ -2020,7 +2103,6 @@ typedef struct { typedef struct { Clay_Color backgroundColor; Clay_CornerRadius cornerRadius; - Clay_Dimensions sourceDimensions; void* imageData; } Clay_ImageRenderData; ``` diff --git a/bindings/odin/README.md b/bindings/odin/README.md index 263ee3b..53c86f3 100644 --- a/bindings/odin/README.md +++ b/bindings/odin/README.md @@ -41,10 +41,10 @@ error_handler :: proc "c" (errorData: clay.ErrorData) { // Do something with the error data. } -min_memory_size: u32 = clay.MinMemorySize() +min_memory_size := clay.MinMemorySize() memory := make([^]u8, min_memory_size) -arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(min_memory_size, memory) -clay.Initialize(arena, { width = 1080, height = 720 }, { handler = error_handler }) +arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(uint(min_memory_size), memory) +clay.Initialize(arena, {1080, 720}, { handler = error_handler }) ``` 3. Provide a `measure_text(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that Clay can measure and wrap text. @@ -74,7 +74,7 @@ clay.SetMeasureTextFunction(measure_text, nil) ```Odin // Update internal pointer position for handling mouseover / click / touch events clay.SetPointerState( - clay.Vector2 { mouse_pos_x, mouse_pos_y }, + { mouse_pos_x, mouse_pos_y }, is_mouse_down, ) ``` @@ -148,6 +148,7 @@ create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) { sizing = { width = clay.SizingFixed(60), height = clay.SizingFixed(60) }, }, image = { + // How you define `profile_picture` depends on your renderer. imageData = &profile_picture, sourceDimensions = { width = 60, @@ -179,8 +180,7 @@ create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) { } // Returns a list of render commands - render_commands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout() - return render_commands + return clay.EndLayout() } ``` diff --git a/bindings/odin/clay-odin/clay.odin b/bindings/odin/clay-odin/clay.odin index eb27cfa..ad1e1ee 100644 --- a/bindings/odin/clay-odin/clay.odin +++ b/bindings/odin/clay-odin/clay.odin @@ -113,9 +113,12 @@ TextElementConfig :: struct { textAlignment: TextAlignment, } +AspectRatioElementConfig :: struct { + aspectRatio: f32, +} + ImageElementConfig :: struct { imageData: rawptr, - sourceDimensions: Dimensions, } CustomElementConfig :: struct { @@ -203,7 +206,6 @@ RectangleRenderData :: struct { ImageRenderData :: struct { backgroundColor: Color, cornerRadius: CornerRadius, - sourceDimensions: Dimensions, imageData: rawptr, } @@ -340,6 +342,7 @@ ElementDeclaration :: struct { layout: LayoutConfig, backgroundColor: Color, cornerRadius: CornerRadius, + aspectRatio: AspectRatioElementConfig, image: ImageElementConfig, floating: FloatingElementConfig, custom: CustomElementConfig, diff --git a/bindings/odin/clay-odin/linux/clay.a b/bindings/odin/clay-odin/linux/clay.a index 15bdaea..ee05b22 100644 Binary files a/bindings/odin/clay-odin/linux/clay.a and b/bindings/odin/clay-odin/linux/clay.a differ diff --git a/bindings/odin/clay-odin/macos-arm64/clay.a b/bindings/odin/clay-odin/macos-arm64/clay.a index 6cfa8b9..5dc2ea5 100644 Binary files a/bindings/odin/clay-odin/macos-arm64/clay.a and b/bindings/odin/clay-odin/macos-arm64/clay.a differ diff --git a/bindings/odin/clay-odin/macos/clay.a b/bindings/odin/clay-odin/macos/clay.a index cf343f7..4d64ce7 100644 Binary files a/bindings/odin/clay-odin/macos/clay.a and b/bindings/odin/clay-odin/macos/clay.a differ diff --git a/bindings/odin/clay-odin/wasm/clay.o b/bindings/odin/clay-odin/wasm/clay.o index f5bdebf..dceb1b8 100644 Binary files a/bindings/odin/clay-odin/wasm/clay.o and b/bindings/odin/clay-odin/wasm/clay.o differ diff --git a/bindings/odin/clay-odin/windows/clay.lib b/bindings/odin/clay-odin/windows/clay.lib index 075cf9e..0297213 100644 Binary files a/bindings/odin/clay-odin/windows/clay.lib and b/bindings/odin/clay-odin/windows/clay.lib differ diff --git a/bindings/odin/examples/clay-official-website/clay-official-website.odin b/bindings/odin/examples/clay-official-website/clay-official-website.odin index 1b7c7f0..5173d74 100644 --- a/bindings/odin/examples/clay-official-website/clay-official-website.odin +++ b/bindings/odin/examples/clay-official-website/clay-official-website.odin @@ -72,7 +72,8 @@ LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Colo if clay.UI()({ id = clay.ID("CheckImage", index), layout = { sizing = { width = clay.SizingFixed(32) } }, - image = { imageData = image, sourceDimensions = { 128, 128 } }, + aspectRatio = { 1.0 }, + image = { imageData = image }, }) {} clay.Text(text, clay.TextConfig({fontSize = fontSize, fontId = fontId, textColor = color})) } @@ -213,7 +214,8 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi if clay.UI()({ id = clay.ID("SyntaxPageRightImageInner"), layout = { sizing = { width = clay.SizingGrow({ max = 568 }) } }, - image = { imageData = &syntaxImage, sourceDimensions = { 1136, 1194 } }, + aspectRatio = { 1136.0 / 1194.0 }, + image = { imageData = &syntaxImage }, }) {} } } diff --git a/clay.h b/clay.h index 1a45b3e..24d5501 100644 --- a/clay.h +++ b/clay.h @@ -1,4 +1,4 @@ -// VERSION: 0.13 +// VERSION: 0.14 /* NOTE: In order to use this library you must define @@ -31,7 +31,8 @@ #if !( \ (defined(__cplusplus) && __cplusplus >= 202002L) || \ (defined(__STDC__) && __STDC__ == 1 && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || \ - defined(_MSC_VER) \ + defined(_MSC_VER) || \ + defined(__OBJC__) \ ) #error "Clay requires C99, C++20, or MSVC" #endif @@ -184,7 +185,7 @@ 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 { +typedef struct Clay_String { // 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; @@ -195,7 +196,7 @@ typedef struct { // Clay_StringSlice is used to represent non owning string slices, and includes // a baseChars field which points to the string this slice is derived from. -typedef struct { +typedef struct Clay_StringSlice { int32_t length; const char *chars; const char *baseChars; // The source string / char* that this slice was derived from @@ -205,33 +206,33 @@ typedef struct Clay_Context Clay_Context; // Clay_Arena is a memory arena structure that is used by clay to manage its internal allocations. // Rather than creating it by hand, it's easier to use Clay_CreateArenaWithCapacityAndMemory() -typedef struct { +typedef struct Clay_Arena { uintptr_t nextAllocation; size_t capacity; char *memory; } Clay_Arena; -typedef struct { +typedef struct Clay_Dimensions { float width, height; } Clay_Dimensions; -typedef struct { +typedef struct Clay_Vector2 { float x, y; } Clay_Vector2; // Internally clay conventionally represents colors as 0-255, but interpretation is up to the renderer. -typedef struct { +typedef struct Clay_Color { float r, g, b, a; } Clay_Color; -typedef struct { +typedef struct Clay_BoundingBox { float x, y, width, height; } Clay_BoundingBox; // Primarily created via the CLAY_ID(), CLAY_IDI(), CLAY_ID_LOCAL() and CLAY_IDI_LOCAL() macros. // Represents a hashed string ID used for identifying and finding specific clay UI elements, required // by functions such as Clay_PointerOver() and Clay_GetElementData(). -typedef struct { +typedef struct Clay_ElementId { uint32_t id; // The resulting hash generated from the other fields. uint32_t offset; // A numerical offset applied after computing the hash from stringId. uint32_t baseId; // A base hash value to start from, for example the parent element ID is used when calculating CLAY_ID_LOCAL(). @@ -248,7 +249,7 @@ typedef struct // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. -typedef struct { +typedef struct Clay_CornerRadius { float topLeft; float topRight; float bottomLeft; @@ -298,20 +299,20 @@ typedef CLAY_PACKED_ENUM { } Clay__SizingType; // Controls how child elements are aligned on each axis. -typedef struct { +typedef struct Clay_ChildAlignment { Clay_LayoutAlignmentX x; // Controls alignment of children along the x axis. Clay_LayoutAlignmentY y; // Controls alignment of children along the y axis. } Clay_ChildAlignment; // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, // overriding sizing types such as FIT or GROW. -typedef struct { +typedef struct Clay_SizingMinMax { float min; // The smallest final size of the element on this axis will be this value in pixels. float max; // The largest final size of the element on this axis will be this value in pixels. } Clay_SizingMinMax; // Controls the sizing of this element along one axis inside its parent container. -typedef struct { +typedef struct Clay_SizingAxis { union { Clay_SizingMinMax minMax; // Controls the minimum and maximum size in pixels that this element is allowed to grow or shrink to, overriding sizing types such as FIT or GROW. float percent; // Expects 0-1 range. Clamps the axis size to a percent of the parent container's axis size minus padding and child gaps. @@ -320,14 +321,14 @@ typedef struct { } Clay_SizingAxis; // Controls the sizing of this element along one axis inside its parent container. -typedef struct { +typedef struct Clay_Sizing { Clay_SizingAxis width; // Controls the width sizing of the element, along the x axis. Clay_SizingAxis height; // Controls the height sizing of the element, along the y axis. } Clay_Sizing; // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children // will be placed. -typedef struct { +typedef struct Clay_Padding { uint16_t left; uint16_t right; uint16_t top; @@ -338,7 +339,7 @@ CLAY__WRAPPER_STRUCT(Clay_Padding); // Controls various settings that affect the size and position of an element, as well as the sizes and positions // of any child elements. -typedef struct { +typedef struct Clay_LayoutConfig { Clay_Sizing sizing; // Controls the sizing of this element inside it's parent container, including FIT, GROW, PERCENT and FIXED sizing. Clay_Padding padding; // Controls "padding" in pixels, which is a gap between the bounding box of this element and where its children will be placed. uint16_t childGap; // Controls the gap in pixels between child elements along the layout axis (horizontal gap for LEFT_TO_RIGHT, vertical gap for TOP_TO_BOTTOM). @@ -371,7 +372,7 @@ typedef CLAY_PACKED_ENUM { } Clay_TextAlignment; // Controls various functionality related to text elements. -typedef struct { +typedef struct Clay_TextElementConfig { // 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. @@ -399,12 +400,20 @@ typedef struct { CLAY__WRAPPER_STRUCT(Clay_TextElementConfig); +// Aspect Ratio -------------------------------- + +// Controls various settings related to aspect ratio scaling element. +typedef struct Clay_AspectRatioElementConfig { + float aspectRatio; // A float representing the target "Aspect ratio" for an element, which is its final width divided by its final height. +} Clay_AspectRatioElementConfig; + +CLAY__WRAPPER_STRUCT(Clay_AspectRatioElementConfig); + // Image -------------------------------- // Controls various settings related to image elements. -typedef struct { +typedef struct Clay_ImageElementConfig { void* imageData; // A transparent pointer used to pass image data through to the renderer. - Clay_Dimensions sourceDimensions; // The original dimensions of the source image, used to control aspect ratio. } Clay_ImageElementConfig; CLAY__WRAPPER_STRUCT(Clay_ImageElementConfig); @@ -426,7 +435,7 @@ typedef CLAY_PACKED_ENUM { } Clay_FloatingAttachPointType; // Controls where a floating element is offset relative to its parent element. -typedef struct { +typedef struct Clay_FloatingAttachPoints { Clay_FloatingAttachPointType element; // Controls the origin point on a floating element that attaches to its parent. Clay_FloatingAttachPointType parent; // Controls the origin point on the parent element that the floating element attaches to. } Clay_FloatingAttachPoints; @@ -463,7 +472,7 @@ typedef CLAY_PACKED_ENUM { // Controls various settings related to "floating" elements, which are elements that "float" above other elements, potentially overlapping their boundaries, // and not affecting the layout of sibling or parent elements. -typedef struct { +typedef struct Clay_FloatingElementConfig { // Offsets this floating element by the provided x,y coordinates from its attachPoints. Clay_Vector2 offset; // Expands the boundaries of the outer floating element without affecting its children. @@ -500,7 +509,7 @@ CLAY__WRAPPER_STRUCT(Clay_FloatingElementConfig); // Custom ----------------------------- // Controls various settings related to custom elements. -typedef struct { +typedef struct Clay_CustomElementConfig { // A transparent pointer through which you can pass custom data to the renderer. // Generates CUSTOM render commands. void* customData; @@ -511,7 +520,7 @@ CLAY__WRAPPER_STRUCT(Clay_CustomElementConfig); // Scroll ----------------------------- // Controls the axis on which an element switches to "scrolling", which clips the contents and allows scrolling in that direction. -typedef struct { +typedef struct Clay_ClipElementConfig { bool horizontal; // Clip overflowing elements on the X axis. bool vertical; // Clip overflowing elements on the Y axis. Clay_Vector2 childOffset; // Offsets the x,y positions of all child elements. Used primarily for scrolling containers. @@ -522,7 +531,7 @@ CLAY__WRAPPER_STRUCT(Clay_ClipElementConfig); // Border ----------------------------- // Controls the widths of individual element borders. -typedef struct { +typedef struct Clay_BorderWidth { uint16_t left; uint16_t right; uint16_t top; @@ -534,7 +543,7 @@ typedef struct { } Clay_BorderWidth; // Controls settings related to element borders. -typedef struct { +typedef struct Clay_BorderElementConfig { Clay_Color color; // Controls the color of all borders with width > 0. Conventionally represented as 0-255, but interpretation is up to the renderer. Clay_BorderWidth width; // Controls the widths of individual borders. At least one of these should be > 0 for a BORDER render command to be generated. } Clay_BorderElementConfig; @@ -544,7 +553,7 @@ CLAY__WRAPPER_STRUCT(Clay_BorderElementConfig); // Render Command Data ----------------------------- // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT -typedef struct { +typedef struct Clay_TextRenderData { // A string slice containing the text to be rendered. // Note: this is not guaranteed to be null terminated. Clay_StringSlice stringContents; @@ -560,7 +569,7 @@ typedef struct { } Clay_TextRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE -typedef struct { +typedef struct Clay_RectangleRenderData { // The solid background color to fill this rectangle with. Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color backgroundColor; // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. @@ -569,7 +578,7 @@ typedef struct { } Clay_RectangleRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_IMAGE -typedef struct { +typedef struct Clay_ImageRenderData { // The tint color for this image. Note that the default value is 0,0,0,0 and should likely be interpreted // as "untinted". // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. @@ -577,14 +586,12 @@ typedef struct { // Controls the "radius", or corner rounding of this image. // The rounding is determined by drawing a circle inset into the element corner by (radius, radius) pixels. Clay_CornerRadius cornerRadius; - // The original dimensions of the source image, used to control aspect ratio. - Clay_Dimensions sourceDimensions; // A pointer transparently passed through from the original element definition, typically used to represent image data. void* imageData; } Clay_ImageRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_CUSTOM -typedef struct { +typedef struct Clay_CustomRenderData { // Passed through from .backgroundColor in the original element declaration. // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color backgroundColor; @@ -596,13 +603,13 @@ typedef struct { } Clay_CustomRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_START || commandType == CLAY_RENDER_COMMAND_TYPE_SCISSOR_END -typedef struct { +typedef struct Clay_ScrollRenderData { bool horizontal; bool vertical; } Clay_ClipRenderData; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_BORDER -typedef struct { +typedef struct Clay_BorderRenderData { // Controls a shared color for all this element's borders. // Conventionally represented as 0-255 for each channel, but interpretation is up to the renderer. Clay_Color color; @@ -614,7 +621,7 @@ typedef struct { } Clay_BorderRenderData; // A struct union containing data specific to this command's .commandType -typedef union { +typedef union Clay_RenderData { // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_RECTANGLE Clay_RectangleRenderData rectangle; // Render command data when commandType == CLAY_RENDER_COMMAND_TYPE_TEXT @@ -632,7 +639,7 @@ typedef union { // Miscellaneous Structs & Enums --------------------------------- // Data representing the current internal state of a scrolling element. -typedef struct { +typedef struct Clay_ScrollContainerData { // Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout. // Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling. Clay_Vector2 *scrollPosition; @@ -647,7 +654,7 @@ typedef struct { } Clay_ScrollContainerData; // Bounding box and other data for a specific UI element. -typedef struct { +typedef struct Clay_ElementData { // The rectangle that encloses this UI element, with the position relative to the root of the layout. Clay_BoundingBox boundingBox; // Indicates whether an actual Element matched the provided ID or if the default struct was returned. @@ -674,7 +681,7 @@ typedef CLAY_PACKED_ENUM { CLAY_RENDER_COMMAND_TYPE_CUSTOM, } Clay_RenderCommandType; -typedef struct { +typedef struct Clay_RenderCommand { // A rectangular box that fully encloses this UI element, with the position relative to the root of the layout. Clay_BoundingBox boundingBox; // A struct union containing data specific to this command's commandType. @@ -699,7 +706,7 @@ typedef struct { } Clay_RenderCommand; // A sized array of render commands. -typedef struct { +typedef struct Clay_RenderCommandArray { // The underlying max capacity of the array, not necessarily all initialized. int32_t capacity; // The number of initialized elements in this array. Used for loops and iteration. @@ -721,7 +728,7 @@ typedef CLAY_PACKED_ENUM { } Clay_PointerDataInteractionState; // Information on the current state of pointer interactions this frame. -typedef struct { +typedef struct Clay_PointerData { // The position of the mouse / touch / pointer relative to the root of the layout. Clay_Vector2 position; // Represents the current state of interaction with clay this frame. @@ -732,7 +739,7 @@ typedef struct { Clay_PointerDataInteractionState state; } Clay_PointerData; -typedef struct { +typedef struct Clay_ElementDeclaration { // Primarily created via the CLAY_ID(), CLAY_IDI(), CLAY_ID_LOCAL() and CLAY_IDI_LOCAL() macros. // Represents a hashed string ID used for identifying and finding specific clay UI elements, required by functions such as Clay_PointerOver() and Clay_GetElementData(). Clay_ElementId id; @@ -744,6 +751,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 aspect ratio scaling. + Clay_AspectRatioElementConfig aspectRatio; // 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. @@ -783,7 +792,7 @@ typedef CLAY_PACKED_ENUM { } Clay_ErrorType; // Data to identify the error that clay has encountered. -typedef struct { +typedef struct Clay_ErrorData { // Represents the type of error clay encountered while computing layout. // CLAY_ERROR_TYPE_TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED - A text measurement function wasn't provided using Clay_SetMeasureTextFunction(), or the provided function was null. // CLAY_ERROR_TYPE_ARENA_CAPACITY_EXCEEDED - Clay attempted to allocate its internal data structures but ran out of space. The arena passed to Clay_Initialize was created with a capacity smaller than that required by Clay_MinMemorySize(). @@ -1058,6 +1067,7 @@ CLAY__ARRAY_DEFINE(char, Clay__charArray) CLAY__ARRAY_DEFINE_FUNCTIONS(Clay_ElementId, Clay_ElementIdArray) CLAY__ARRAY_DEFINE(Clay_LayoutConfig, Clay__LayoutConfigArray) CLAY__ARRAY_DEFINE(Clay_TextElementConfig, Clay__TextElementConfigArray) +CLAY__ARRAY_DEFINE(Clay_AspectRatioElementConfig, Clay__AspectRatioElementConfigArray) CLAY__ARRAY_DEFINE(Clay_ImageElementConfig, Clay__ImageElementConfigArray) CLAY__ARRAY_DEFINE(Clay_FloatingElementConfig, Clay__FloatingElementConfigArray) CLAY__ARRAY_DEFINE(Clay_CustomElementConfig, Clay__CustomElementConfigArray) @@ -1072,6 +1082,7 @@ typedef CLAY_PACKED_ENUM { CLAY__ELEMENT_CONFIG_TYPE_BORDER, CLAY__ELEMENT_CONFIG_TYPE_FLOATING, CLAY__ELEMENT_CONFIG_TYPE_CLIP, + CLAY__ELEMENT_CONFIG_TYPE_ASPECT, CLAY__ELEMENT_CONFIG_TYPE_IMAGE, CLAY__ELEMENT_CONFIG_TYPE_TEXT, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM, @@ -1080,6 +1091,7 @@ typedef CLAY_PACKED_ENUM { typedef union { Clay_TextElementConfig *textElementConfig; + Clay_AspectRatioElementConfig *aspectRatioElementConfig; Clay_ImageElementConfig *imageElementConfig; Clay_FloatingElementConfig *floatingElementConfig; Clay_CustomElementConfig *customElementConfig; @@ -1236,13 +1248,14 @@ struct Clay_Context { Clay__int32_tArray layoutElementChildren; Clay__int32_tArray layoutElementChildrenBuffer; Clay__TextElementDataArray textElementData; - Clay__int32_tArray imageElementPointers; + Clay__int32_tArray aspectRatioElementIndexes; Clay__int32_tArray reusableElementIndexBuffer; Clay__int32_tArray layoutElementClipElementIds; // Configs Clay__LayoutConfigArray layoutConfigs; Clay__ElementConfigArray elementConfigs; Clay__TextElementConfigArray textElementConfigs; + Clay__AspectRatioElementConfigArray aspectRatioElementConfigs; Clay__ImageElementConfigArray imageElementConfigs; Clay__FloatingElementConfigArray floatingElementConfigs; Clay__ClipElementConfigArray clipElementConfigs; @@ -1271,15 +1284,12 @@ struct Clay_Context { Clay_Context* Clay__Context_Allocate_Arena(Clay_Arena *arena) { size_t totalSizeBytes = sizeof(Clay_Context); - uintptr_t memoryAddress = (uintptr_t)arena->memory; - // Make sure the memory address passed in for clay to use is cache line aligned - uintptr_t nextAllocOffset = (memoryAddress % 64); - if (nextAllocOffset + totalSizeBytes > arena->capacity) + if (totalSizeBytes > arena->capacity) { return NULL; } - arena->nextAllocation = nextAllocOffset + totalSizeBytes; - return (Clay_Context*)(memoryAddress + nextAllocOffset); + arena->nextAllocation += totalSizeBytes; + return (Clay_Context*)(arena->memory); } Clay_String Clay__WriteStringToCharBuffer(Clay__charArray *buffer, Clay_String string) { @@ -1310,6 +1320,7 @@ uint32_t Clay__GetParentElementId(void) { Clay_LayoutConfig * Clay__StoreLayoutConfig(Clay_LayoutConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &CLAY_LAYOUT_DEFAULT : Clay__LayoutConfigArray_Add(&Clay_GetCurrentContext()->layoutConfigs, config); } Clay_TextElementConfig * Clay__StoreTextElementConfig(Clay_TextElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_TextElementConfig_DEFAULT : Clay__TextElementConfigArray_Add(&Clay_GetCurrentContext()->textElementConfigs, config); } +Clay_AspectRatioElementConfig * Clay__StoreAspectRatioElementConfig(Clay_AspectRatioElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_AspectRatioElementConfig_DEFAULT : Clay__AspectRatioElementConfigArray_Add(&Clay_GetCurrentContext()->aspectRatioElementConfigs, config); } Clay_ImageElementConfig * Clay__StoreImageElementConfig(Clay_ImageElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_ImageElementConfig_DEFAULT : Clay__ImageElementConfigArray_Add(&Clay_GetCurrentContext()->imageElementConfigs, config); } Clay_FloatingElementConfig * Clay__StoreFloatingElementConfig(Clay_FloatingElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_FloatingElementConfig_DEFAULT : Clay__FloatingElementConfigArray_Add(&Clay_GetCurrentContext()->floatingElementConfigs, config); } Clay_CustomElementConfig * Clay__StoreCustomElementConfig(Clay_CustomElementConfig config) { return Clay_GetCurrentContext()->booleanWarnings.maxElementsExceeded ? &Clay_CustomElementConfig_DEFAULT : Clay__CustomElementConfigArray_Add(&Clay_GetCurrentContext()->customElementConfigs, config); } @@ -1483,7 +1494,7 @@ uint64_t Clay__HashData(const uint8_t* data, size_t length) { uint64_t Clay__HashData(const uint8_t* data, size_t length) { uint64_t hash = 0; - for (int32_t i = 0; i < length; i++) { + for (size_t i = 0; i < length; i++) { hash += data[i]; hash += (hash << 10); hash ^= (hash >> 6); @@ -1632,7 +1643,10 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text char current = text->chars[end]; 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); + Clay_Dimensions dimensions = {}; + if (length > 0) { + 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 == ' ') { @@ -1661,7 +1675,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text measuredHeight = CLAY__MAX(measuredHeight, dimensions.height); measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth); } - measuredWidth = CLAY__MAX(lineWidth, measuredWidth); + measuredWidth = CLAY__MAX(lineWidth, measuredWidth) - config->letterSpacing; measured->measuredWordsStartIndex = tempWord.next; measured->unwrappedDimensions.width = measuredWidth; @@ -1760,16 +1774,15 @@ bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConf 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); - if (config->type == CLAY__ELEMENT_CONFIG_TYPE_IMAGE) { - Clay_ImageElementConfig *imageConfig = config->config.imageElementConfig; - if (imageConfig->sourceDimensions.width == 0 || imageConfig->sourceDimensions.height == 0) { + if (config->type == CLAY__ELEMENT_CONFIG_TYPE_ASPECT) { + Clay_AspectRatioElementConfig *aspectConfig = config->config.aspectRatioElementConfig; + if (aspectConfig->aspectRatio == 0) { break; } - float aspect = imageConfig->sourceDimensions.width / imageConfig->sourceDimensions.height; if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) { - layoutElement->dimensions.width = layoutElement->dimensions.height * aspect; + layoutElement->dimensions.width = layoutElement->dimensions.height * aspectConfig->aspectRatio; } else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) { - layoutElement->dimensions.height = layoutElement->dimensions.height * (1 / aspect); + layoutElement->dimensions.height = layoutElement->dimensions.width * (1 / aspectConfig->aspectRatio); } break; } @@ -1783,13 +1796,13 @@ void Clay__CloseElement(void) { } Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement(); Clay_LayoutConfig *layoutConfig = openLayoutElement->layoutConfig; - bool elementHasScrollHorizontal = false; - bool elementHasScrollVertical = false; + bool elementHasClipHorizontal = false; + bool elementHasClipVertical = false; for (int32_t i = 0; i < openLayoutElement->elementConfigs.length; i++) { Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&openLayoutElement->elementConfigs, i); if (config->type == CLAY__ELEMENT_CONFIG_TYPE_CLIP) { - elementHasScrollHorizontal = config->config.clipElementConfig->horizontal; - elementHasScrollVertical = config->config.clipElementConfig->vertical; + elementHasClipHorizontal = config->config.clipElementConfig->horizontal; + elementHasClipVertical = config->config.clipElementConfig->vertical; context->openClipElementStack.length--; break; } else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) { @@ -1810,18 +1823,20 @@ void Clay__CloseElement(void) { 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 + topBottomPadding); - // Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents - if (!elementHasScrollHorizontal) { + // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents + if (!elementHasClipHorizontal) { openLayoutElement->minDimensions.width += child->minDimensions.width; } - if (!elementHasScrollVertical) { + if (!elementHasClipVertical) { openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding); } Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); } float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); - openLayoutElement->dimensions.width += childGap; // TODO this is technically a bug with childgap and scroll containers - openLayoutElement->minDimensions.width += childGap; + openLayoutElement->dimensions.width += childGap; + if (!elementHasClipHorizontal) { + openLayoutElement->minDimensions.width += childGap; + } } else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) { openLayoutElement->dimensions.height = topBottomPadding; @@ -1831,18 +1846,20 @@ void Clay__CloseElement(void) { 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 + leftRightPadding); - // Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents - if (!elementHasScrollVertical) { + // Minimum size of child elements doesn't matter to clip containers as they can shrink and hide their contents + if (!elementHasClipVertical) { openLayoutElement->minDimensions.height += child->minDimensions.height; } - if (!elementHasScrollHorizontal) { + if (!elementHasClipHorizontal) { openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding); } Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex); } float childGap = (float)(CLAY__MAX(openLayoutElement->childrenOrTextContent.children.length - 1, 0) * layoutConfig->childGap); - openLayoutElement->dimensions.height += childGap; // TODO this is technically a bug with childgap and scroll containers - openLayoutElement->minDimensions.height += childGap; + openLayoutElement->dimensions.height += childGap; + if (!elementHasClipVertical) { + openLayoutElement->minDimensions.height += childGap; + } } context->layoutElementChildrenBuffer.length -= openLayoutElement->childrenOrTextContent.children.length; @@ -2048,7 +2065,10 @@ void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) { } 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->aspectRatio.aspectRatio > 0) { + Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .aspectRatioElementConfig = Clay__StoreAspectRatioElementConfig(declaration->aspectRatio) }, CLAY__ELEMENT_CONFIG_TYPE_ASPECT); + Clay__int32_tArray_Add(&context->aspectRatioElementIndexes, context->layoutElements.length - 1); } if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) { Clay_FloatingElementConfig floatingConfig = declaration->floating; @@ -2145,6 +2165,7 @@ void Clay__InitializeEphemeralMemory(Clay_Context* context) { context->layoutConfigs = Clay__LayoutConfigArray_Allocate_Arena(maxElementCount, arena); context->elementConfigs = Clay__ElementConfigArray_Allocate_Arena(maxElementCount, arena); context->textElementConfigs = Clay__TextElementConfigArray_Allocate_Arena(maxElementCount, arena); + context->aspectRatioElementConfigs = Clay__AspectRatioElementConfigArray_Allocate_Arena(maxElementCount, arena); context->imageElementConfigs = Clay__ImageElementConfigArray_Allocate_Arena(maxElementCount, arena); context->floatingElementConfigs = Clay__FloatingElementConfigArray_Allocate_Arena(maxElementCount, arena); context->clipElementConfigs = Clay__ClipElementConfigArray_Allocate_Arena(maxElementCount, arena); @@ -2159,7 +2180,7 @@ void Clay__InitializeEphemeralMemory(Clay_Context* context) { context->layoutElementChildren = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->openLayoutElementStack = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->textElementData = Clay__TextElementDataArray_Allocate_Arena(maxElementCount, arena); - context->imageElementPointers = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); + context->aspectRatioElementIndexes = Clay__int32_tArray_Allocate_Arena(maxElementCount, arena); context->renderCommands = Clay_RenderCommandArray_Allocate_Arena(maxElementCount, arena); context->treeNodeVisited = Clay__boolArray_Allocate_Arena(maxElementCount, arena); context->treeNodeVisited.length = context->treeNodeVisited.capacity; // This array is accessed directly rather than behaving as a list @@ -2248,7 +2269,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { 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 - && (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) +// && (xAxis || !Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) ) { Clay__int32_tArray_Add(&resizableContainerBuffer, childElementIndex); } @@ -2287,9 +2308,9 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { // The content is too large, compress the children as much as possible if (sizeToDistribute < 0) { // If the parent clips content in this axis direction, don't compress children, just leave them alone - Clay_ClipElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; - if (scrollElementConfig) { - if (((xAxis && scrollElementConfig->horizontal) || (!xAxis && scrollElementConfig->vertical))) { + Clay_ClipElementConfig *clipElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_CLIP).clipElementConfig; + if (clipElementConfig) { + if (((xAxis && clipElementConfig->horizontal) || (!xAxis && clipElementConfig->vertical))) { continue; } } @@ -2382,10 +2403,6 @@ void Clay__SizeContainersAlongAxis(bool xAxis) { 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 - } - 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_CLIP)) { @@ -2500,7 +2517,7 @@ void Clay__CalculateFinalLayout(void) { // measuredWord->length == 0 means a newline character else if (measuredWord->length == 0 || lineWidth + measuredWord->width > containerElement->dimensions.width) { // Wrapped text lines list has overflowed, just render out the line - bool finalCharIsSpace = textElementData->text.chars[lineStartOffset + lineLengthChars - 1] == ' '; + bool finalCharIsSpace = textElementData->text.chars[CLAY__MAX(lineStartOffset + lineLengthChars - 1, 0)] == ' '; Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth + (finalCharIsSpace ? -spaceWidth : 0), lineHeight }, { .length = lineLengthChars + (finalCharIsSpace ? -1 : 0), .chars = &textElementData->text.chars[lineStartOffset] } }); textElementData->wrappedLines.length++; if (lineLengthChars == 0 || measuredWord->length == 0) { @@ -2510,26 +2527,27 @@ void Clay__CalculateFinalLayout(void) { lineLengthChars = 0; lineStartOffset = measuredWord->startOffset; } else { - lineWidth += measuredWord->width; + lineWidth += measuredWord->width + textConfig->letterSpacing; lineLengthChars += measuredWord->length; wordIndex = measuredWord->next; } } if (lineLengthChars > 0) { - Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); + Clay__WrappedTextLineArray_Add(&context->wrappedTextLines, CLAY__INIT(Clay__WrappedTextLine) { { lineWidth - textConfig->letterSpacing, lineHeight }, {.length = lineLengthChars, .chars = &textElementData->text.chars[lineStartOffset] } }); textElementData->wrappedLines.length++; } containerElement->dimensions.height = lineHeight * (float)textElementData->wrappedLines.length; } - // Scale vertical image heights according to aspect ratio - for (int32_t i = 0; i < context->imageElementPointers.length; ++i) { - Clay_LayoutElement* imageElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->imageElementPointers, i)); - Clay_ImageElementConfig *config = Clay__FindElementConfigWithType(imageElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE).imageElementConfig; - imageElement->dimensions.height = (config->sourceDimensions.height / CLAY__MAX(config->sourceDimensions.width, 1)) * imageElement->dimensions.width; + // Scale vertical heights according to aspect ratio + for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { + Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); + Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; + aspectElement->dimensions.height = (1 / config->aspectRatio) * aspectElement->dimensions.width; + aspectElement->layoutConfig->sizing.height.size.minMax.max = aspectElement->dimensions.height; } - // Propagate effect of text wrapping, image aspect scaling etc. on height of parents + // Propagate effect of text wrapping, aspect scaling etc. on height of parents Clay__LayoutElementTreeNodeArray dfsBuffer = context->layoutElementTreeNodeArray1; dfsBuffer.length = 0; for (int32_t i = 0; i < context->layoutElementTreeRoots.length; ++i) { @@ -2580,6 +2598,13 @@ void Clay__CalculateFinalLayout(void) { // Calculate sizing along the Y axis Clay__SizeContainersAlongAxis(false); + // Scale horizontal widths according to aspect ratio + for (int32_t i = 0; i < context->aspectRatioElementIndexes.length; ++i) { + Clay_LayoutElement* aspectElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->aspectRatioElementIndexes, i)); + Clay_AspectRatioElementConfig *config = Clay__FindElementConfigWithType(aspectElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; + aspectElement->dimensions.width = config->aspectRatio * aspectElement->dimensions.height; + } + // Sort tree roots by z-index int32_t sortMax = context->layoutElementTreeRoots.length - 1; while (sortMax > 0) { // todo dumb bubble sort @@ -2670,7 +2695,6 @@ void Clay__CalculateFinalLayout(void) { if (clipConfig->vertical) { rootPosition.y += clipConfig->childOffset.y; } - break; } Clay__AddRenderCommand(CLAY__INIT(Clay_RenderCommand) { .boundingBox = clipHashMapItem->boundingBox, @@ -2776,6 +2800,7 @@ void Clay__CalculateFinalLayout(void) { // Culling - Don't bother to generate render commands for rectangles entirely outside the screen - this won't stop their children from being rendered if they overflow bool shouldRender = !offscreen; switch (elementConfig->type) { + case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: case CLAY__ELEMENT_CONFIG_TYPE_SHARED: case CLAY__ELEMENT_CONFIG_TYPE_BORDER: { @@ -2798,7 +2823,6 @@ void Clay__CalculateFinalLayout(void) { .image = { .backgroundColor = sharedConfig->backgroundColor, .cornerRadius = sharedConfig->cornerRadius, - .sourceDimensions = elementConfig->config.imageElementConfig->sourceDimensions, .imageData = elementConfig->config.imageElementConfig->imageData, } }; @@ -2907,6 +2931,7 @@ void Clay__CalculateFinalLayout(void) { default: break; } currentElementTreeNode->nextChildOffset.x += extraSpace; + extraSpace = CLAY__MAX(0, extraSpace); } else { for (int32_t i = 0; i < currentElement->childrenOrTextContent.children.length; ++i) { Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, currentElement->childrenOrTextContent.children.elements[i]); @@ -2920,6 +2945,7 @@ void Clay__CalculateFinalLayout(void) { case CLAY_ALIGN_Y_CENTER: extraSpace /= 2; break; default: break; } + extraSpace = CLAY__MAX(0, extraSpace); currentElementTreeNode->nextChildOffset.y += extraSpace; } @@ -3097,6 +3123,7 @@ Clay__DebugElementConfigTypeLabelConfig Clay__DebugGetElementConfigTypeLabel(Cla switch (type) { case CLAY__ELEMENT_CONFIG_TYPE_SHARED: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Shared"), {243,134,48,255} }; case CLAY__ELEMENT_CONFIG_TYPE_TEXT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Text"), {105,210,231,255} }; + case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Aspect"), {101,149,194,255} }; case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Image"), {121,189,154,255} }; case CLAY__ELEMENT_CONFIG_TYPE_FLOATING: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) { CLAY_STRING("Floating"), {250,105,0,255} }; case CLAY__ELEMENT_CONFIG_TYPE_CLIP: return CLAY__INIT(Clay__DebugElementConfigTypeLabelConfig) {CLAY_STRING("Scroll"), {242, 196, 90, 255} }; @@ -3581,21 +3608,25 @@ void Clay__RenderDebugView(void) { } break; } + case CLAY__ELEMENT_CONFIG_TYPE_ASPECT: { + Clay_AspectRatioElementConfig *aspectRatioConfig = elementConfig->config.aspectRatioElementConfig; + CLAY({ .id = CLAY_ID("Clay__DebugViewElementInfoAspectRatioBody"), .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { + CLAY_TEXT(CLAY_STRING("Aspect Ratio"), infoTitleConfig); + // Aspect Ratio + CLAY_TEXT(Clay__IntToString(aspectRatioConfig->aspectRatio), infoTextConfig); + } + break; + } case CLAY__ELEMENT_CONFIG_TYPE_IMAGE: { Clay_ImageElementConfig *imageConfig = elementConfig->config.imageElementConfig; + Clay_AspectRatioElementConfig aspectConfig = { 1 }; + if (Clay__ElementHasConfig(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT)) { + aspectConfig = *Clay__FindElementConfigWithType(selectedItem->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_ASPECT).aspectRatioElementConfig; + } CLAY({ .id = CLAY_ID("Clay__DebugViewElementInfoImageBody"), .layout = { .padding = attributeConfigPadding, .childGap = 8, .layoutDirection = CLAY_TOP_TO_BOTTOM } }) { - // .sourceDimensions - CLAY_TEXT(CLAY_STRING("Source Dimensions"), infoTitleConfig); - CLAY({ .id = CLAY_ID("Clay__DebugViewElementInfoImageDimensions") }) { - CLAY_TEXT(CLAY_STRING("{ width: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(imageConfig->sourceDimensions.width), infoTextConfig); - CLAY_TEXT(CLAY_STRING(", height: "), infoTextConfig); - CLAY_TEXT(Clay__IntToString(imageConfig->sourceDimensions.height), infoTextConfig); - CLAY_TEXT(CLAY_STRING(" }"), infoTextConfig); - } // Image Preview CLAY_TEXT(CLAY_STRING("Preview"), infoTitleConfig); - CLAY({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(0, imageConfig->sourceDimensions.width) }}, .image = *imageConfig }) {} + CLAY({ .layout = { .sizing = { .width = CLAY_SIZING_GROW(64, 128), .height = CLAY_SIZING_GROW(64, 128) }}, .aspectRatio = aspectConfig, .image = *imageConfig }) {} } break; } @@ -3793,7 +3824,7 @@ Clay__Warning *Clay__WarningArray_Add(Clay__WarningArray *array, Clay__Warning i void* Clay__Array_Allocate_Arena(int32_t capacity, uint32_t itemSize, Clay_Arena *arena) { size_t totalSizeBytes = capacity * itemSize; - uintptr_t nextAllocOffset = arena->nextAllocation + (64 - (arena->nextAllocation % 64)); + uintptr_t nextAllocOffset = arena->nextAllocation + ((64 - (arena->nextAllocation % 64)) & 63); if (nextAllocOffset + totalSizeBytes <= arena->capacity) { arena->nextAllocation = nextAllocOffset + totalSizeBytes; return (void*)((uintptr_t)arena->memory + (uintptr_t)nextAllocOffset); @@ -3913,7 +3944,7 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { Clay_BoundingBox elementBox = mapItem->boundingBox; elementBox.x -= root->pointerOffset.x; elementBox.y -= root->pointerOffset.y; - if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)))) { + if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)) || context->externalScrollHandlingEnabled)) { if (mapItem->onHoverFunction) { mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData); } @@ -3961,6 +3992,10 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) { CLAY_WASM_EXPORT("Clay_Initialize") Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler) { + // Cacheline align memory passed in + uintptr_t baseOffset = 64 - ((uintptr_t)arena.memory % 64); + baseOffset = baseOffset == 64 ? 0 : baseOffset; + arena.memory += baseOffset; Clay_Context *context = Clay__Context_Allocate_Arena(&arena); if (context == NULL) return NULL; // DEFAULTS diff --git a/examples/SDL2-video-demo/CMakeLists.txt b/examples/SDL2-video-demo/CMakeLists.txt index 4dffd2b..1f31e65 100644 --- a/examples/SDL2-video-demo/CMakeLists.txt +++ b/examples/SDL2-video-demo/CMakeLists.txt @@ -45,14 +45,15 @@ target_link_libraries(SDL2_video_demo PUBLIC ) if(MSVC) - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") else() - set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") - set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") + set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}") endif() add_custom_command( - TARGET SDL2_video_demo POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_CURRENT_SOURCE_DIR}/resources - ${CMAKE_CURRENT_BINARY_DIR}/resources) + TARGET SDL2_video_demo POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_CURRENT_SOURCE_DIR}/resources + ${CMAKE_CURRENT_BINARY_DIR}/resources +) diff --git a/examples/SDL3-simple-demo/CMakeLists.txt b/examples/SDL3-simple-demo/CMakeLists.txt index f5f2184..75cf435 100644 --- a/examples/SDL3-simple-demo/CMakeLists.txt +++ b/examples/SDL3-simple-demo/CMakeLists.txt @@ -26,7 +26,7 @@ set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) FetchContent_Declare( SDL_ttf GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git - GIT_TAG main # Slightly risky to use main branch, but it's the only one available + GIT_TAG release-3.2.2 GIT_SHALLOW TRUE GIT_PROGRESS TRUE ) @@ -38,7 +38,7 @@ set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE) FetchContent_Declare( SDL_image GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git" - GIT_TAG release-3.2.0 # Slightly risky to use main branch, but it's the only one available + GIT_TAG release-3.2.0 GIT_SHALLOW TRUE GIT_PROGRESS TRUE ) diff --git a/examples/SDL3-simple-demo/main.c b/examples/SDL3-simple-demo/main.c index cec629f..ee9f605 100644 --- a/examples/SDL3-simple-demo/main.c +++ b/examples/SDL3-simple-demo/main.c @@ -32,6 +32,7 @@ static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextEl TTF_Font *font = fonts[config->fontId]; int width, height; + TTF_SetFontSize(font, config->fontSize); if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) { SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError()); } @@ -65,12 +66,9 @@ Clay_RenderCommandArray ClayImageSample_CreateLayout() { .layout = { .sizing = layoutExpand }, + .aspectRatio = { 23.0 / 42.0 }, .image = { .imageData = sample_image, - .sourceDimensions = { - .width = 23, - .height = 42 - }, } }); } diff --git a/examples/cairo-pdf-rendering/main.c b/examples/cairo-pdf-rendering/main.c index b1d1306..75dbbd6 100644 --- a/examples/cairo-pdf-rendering/main.c +++ b/examples/cairo-pdf-rendering/main.c @@ -88,7 +88,7 @@ void Layout() { .childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }}, .border = { .color = PRIMARY, .width = 2, 2, 2, 2 }, .cornerRadius = 10 }) { - CLAY({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 32, 32 }, .imageData = "resources/check.png" }}); + CLAY({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .imageData = "resources/check.png" }}); } } } diff --git a/examples/clay-official-website/build/clay/index.html b/examples/clay-official-website/build/clay/index.html index e626e4b..2c58034 100644 --- a/examples/clay-official-website/build/clay/index.html +++ b/examples/clay-official-website/build/clay/index.html @@ -97,6 +97,7 @@ {name: 'a', type: 'float' }, ]}; let stringDefinition = { type: 'struct', members: [ + {name: 'isStaticallyAllocated', type: 'uint32_t'}, {name: 'length', type: 'uint32_t' }, {name: 'chars', type: 'uint32_t' }, ]}; @@ -144,7 +145,6 @@ let imageRenderDataDefinition = { type: 'struct', members: [ { name: 'backgroundColor', ...colorDefinition }, { name: 'cornerRadius', ...cornerRadiusDefinition }, - { name: 'sourceDimensions', ...dimensionsDefinition }, { name: 'imageData', type: 'uint32_t' }, ]}; let customRenderDataDefinition = { type: 'struct', members: [ @@ -158,7 +158,7 @@ { name: 'width', ...borderWidthDefinition }, { name: 'padding', type: 'uint16_t'} ]}; - let scrollRenderDataDefinition = { type: 'struct', members: [ + let clipRenderDataDefinition = { type: 'struct', members: [ { name: 'horizontal', type: 'bool' }, { name: 'vertical', type: 'bool' }, ]}; @@ -166,9 +166,10 @@ { name: 'link', ...stringDefinition }, { name: 'cursorPointer', type: 'uint8_t' }, { name: 'disablePointerEvents', type: 'uint8_t' }, + { name: 'padding', type: 'uint16_t'} ]}; let renderCommandDefinition = { - name: 'CLay_RenderCommand', + name: 'Clay_RenderCommand', type: 'struct', members: [ { name: 'boundingBox', type: 'struct', members: [ @@ -183,7 +184,7 @@ { name: 'image', ...imageRenderDataDefinition }, { name: 'custom', ...customRenderDataDefinition }, { name: 'border', ...borderRenderDataDefinition }, - { name: 'scroll', ...scrollRenderDataDefinition }, + { name: 'clip', ...clipRenderDataDefinition }, ]}, { name: 'userData', type: 'uint32_t'}, { name: 'id', type: 'uint32_t' }, @@ -380,7 +381,7 @@ memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true); memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true); instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value); - instance.exports.SetScratchMemory(arenaAddress, clayScratchSpaceAddress); + instance.exports.SetScratchMemory(clayScratchSpaceAddress); renderCommandSize = getStructTotalSize(renderCommandDefinition); renderLoop(); } @@ -425,10 +426,13 @@ if (!elementCache[renderCommand.id.value]) { let elementType = 'div'; switch (renderCommand.commandType.value & 0xff) { + case CLAY_RENDER_COMMAND_TYPE_TEXT: case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { - // if (readStructAtAddress(renderCommand.renderData.rectangle.value, rectangleRenderDataDefinition).link.length.value > 0) { TODO reimplement links - // elementType = 'a'; - // } + if (renderCommand.userData.value !== 0) { + if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).link.length.value > 0) { + elementType = 'a'; + } + } break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { @@ -549,7 +553,6 @@ } case (CLAY_RENDER_COMMAND_TYPE_TEXT): { let config = renderCommand.renderData.text; - let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition); let configMemory = JSON.stringify(config); let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value)); if (configMemory !== elementData.previousMemoryConfig) { @@ -559,7 +562,23 @@ element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`; element.style.fontFamily = fontsById[config.fontId.value]; element.style.fontSize = fontSize + 'px'; - element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all'; + if (renderCommand.userData.value !== 0) { + let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition); + element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all'; + let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0; + memoryDataView.setUint32(0, renderCommand.id.value, true); + if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) { + window.location.href = linkContents; + } + if (linkContents.length > 0) { + element.href = linkContents; + } + + if (linkContents.length > 0 || customData.cursorPointer.value) { + element.style.pointerEvents = 'all'; + element.style.cursor = 'pointer'; + } + } elementData.previousMemoryConfig = configMemory; } if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) { @@ -570,7 +589,7 @@ } case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 }); - let config = renderCommand.renderData.scroll; + let config = renderCommand.renderData.clip; let configMemory = JSON.stringify(config); if (configMemory === elementData.previousMemoryConfig) { break; diff --git a/examples/clay-official-website/build/clay/index.wasm b/examples/clay-official-website/build/clay/index.wasm index e57e631..8b113a8 100755 Binary files a/examples/clay-official-website/build/clay/index.wasm and b/examples/clay-official-website/build/clay/index.wasm differ diff --git a/examples/clay-official-website/index.html b/examples/clay-official-website/index.html index e626e4b..2c58034 100644 --- a/examples/clay-official-website/index.html +++ b/examples/clay-official-website/index.html @@ -97,6 +97,7 @@ {name: 'a', type: 'float' }, ]}; let stringDefinition = { type: 'struct', members: [ + {name: 'isStaticallyAllocated', type: 'uint32_t'}, {name: 'length', type: 'uint32_t' }, {name: 'chars', type: 'uint32_t' }, ]}; @@ -144,7 +145,6 @@ let imageRenderDataDefinition = { type: 'struct', members: [ { name: 'backgroundColor', ...colorDefinition }, { name: 'cornerRadius', ...cornerRadiusDefinition }, - { name: 'sourceDimensions', ...dimensionsDefinition }, { name: 'imageData', type: 'uint32_t' }, ]}; let customRenderDataDefinition = { type: 'struct', members: [ @@ -158,7 +158,7 @@ { name: 'width', ...borderWidthDefinition }, { name: 'padding', type: 'uint16_t'} ]}; - let scrollRenderDataDefinition = { type: 'struct', members: [ + let clipRenderDataDefinition = { type: 'struct', members: [ { name: 'horizontal', type: 'bool' }, { name: 'vertical', type: 'bool' }, ]}; @@ -166,9 +166,10 @@ { name: 'link', ...stringDefinition }, { name: 'cursorPointer', type: 'uint8_t' }, { name: 'disablePointerEvents', type: 'uint8_t' }, + { name: 'padding', type: 'uint16_t'} ]}; let renderCommandDefinition = { - name: 'CLay_RenderCommand', + name: 'Clay_RenderCommand', type: 'struct', members: [ { name: 'boundingBox', type: 'struct', members: [ @@ -183,7 +184,7 @@ { name: 'image', ...imageRenderDataDefinition }, { name: 'custom', ...customRenderDataDefinition }, { name: 'border', ...borderRenderDataDefinition }, - { name: 'scroll', ...scrollRenderDataDefinition }, + { name: 'clip', ...clipRenderDataDefinition }, ]}, { name: 'userData', type: 'uint32_t'}, { name: 'id', type: 'uint32_t' }, @@ -380,7 +381,7 @@ memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true); memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true); instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value); - instance.exports.SetScratchMemory(arenaAddress, clayScratchSpaceAddress); + instance.exports.SetScratchMemory(clayScratchSpaceAddress); renderCommandSize = getStructTotalSize(renderCommandDefinition); renderLoop(); } @@ -425,10 +426,13 @@ if (!elementCache[renderCommand.id.value]) { let elementType = 'div'; switch (renderCommand.commandType.value & 0xff) { + case CLAY_RENDER_COMMAND_TYPE_TEXT: case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { - // if (readStructAtAddress(renderCommand.renderData.rectangle.value, rectangleRenderDataDefinition).link.length.value > 0) { TODO reimplement links - // elementType = 'a'; - // } + if (renderCommand.userData.value !== 0) { + if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).link.length.value > 0) { + elementType = 'a'; + } + } break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { @@ -549,7 +553,6 @@ } case (CLAY_RENDER_COMMAND_TYPE_TEXT): { let config = renderCommand.renderData.text; - let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition); let configMemory = JSON.stringify(config); let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value)); if (configMemory !== elementData.previousMemoryConfig) { @@ -559,7 +562,23 @@ element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`; element.style.fontFamily = fontsById[config.fontId.value]; element.style.fontSize = fontSize + 'px'; - element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all'; + if (renderCommand.userData.value !== 0) { + let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition); + element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all'; + let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0; + memoryDataView.setUint32(0, renderCommand.id.value, true); + if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) { + window.location.href = linkContents; + } + if (linkContents.length > 0) { + element.href = linkContents; + } + + if (linkContents.length > 0 || customData.cursorPointer.value) { + element.style.pointerEvents = 'all'; + element.style.cursor = 'pointer'; + } + } elementData.previousMemoryConfig = configMemory; } if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) { @@ -570,7 +589,7 @@ } case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): { scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 }); - let config = renderCommand.renderData.scroll; + let config = renderCommand.renderData.clip; let configMemory = JSON.stringify(config); if (configMemory === elementData.previousMemoryConfig) { break; diff --git a/examples/clay-official-website/main.c b/examples/clay-official-website/main.c index cb2b522..efe0a35 100644 --- a/examples/clay-official-website/main.c +++ b/examples/clay-official-website/main.c @@ -44,7 +44,7 @@ typedef struct { Arena frameArena = {}; -typedef struct { +typedef struct d { Clay_String link; bool cursorPointer; bool disablePointerEvents; @@ -66,7 +66,7 @@ Clay_String* FrameAllocateString(Clay_String string) { void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) { CLAY({ .id = CLAY_IDI("HeroBlob", index), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .border = { .color = color, .width = { 2, 2, 2, 2 }}, .cornerRadius = CLAY_CORNER_RADIUS(10) }) { - CLAY({ .id = CLAY_IDI("CheckImage", index), .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 128, 128 }, .imageData = FrameAllocateString(imageURL) } }) {} + CLAY({ .id = CLAY_IDI("CheckImage", index), .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .aspectRatio = { 1 }, .image = { .imageData = FrameAllocateString(imageURL) } }) {} CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color })); } } @@ -156,7 +156,7 @@ void DeclarativeSyntaxPageDesktop() { CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } CLAY({ .id = CLAY_ID("SyntaxPageRightImage"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) { - CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {} + CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {} } } } @@ -172,7 +172,7 @@ void DeclarativeSyntaxPageMobile() { CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED })); } CLAY({ .id = CLAY_ID("SyntaxPageRightImage"), .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) { - CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {} + CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {} } } } @@ -323,7 +323,7 @@ void DebuggerPageDesktop() { CLAY_TEXT(CLAY_STRING("Press the \"d\" key to try it out now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE })); } CLAY({ .id = CLAY_ID("DebuggerRightImageOuter"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) { - CLAY({ .id = CLAY_ID("DebuggerPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .image = { .sourceDimensions = {1620, 1474}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {} + CLAY({ .id = CLAY_ID("DebuggerPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .aspectRatio = { 1620.0 / 1474.0 }, .image = {.imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {} } } } @@ -345,11 +345,18 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { CLAY_TEXT(CLAY_STRING("Clay"), &headerTextConfig); CLAY({ .id = CLAY_ID("Spacer"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {} if (!mobileScreen) { - CLAY({ .id = CLAY_ID("LinkExamplesOuter"), .layout = { .padding = {8, 8} }, .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }) }) { - CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); + CLAY({ .id = CLAY_ID("LinkExamplesOuter"), .layout = { .padding = {8, 8} } }) { + CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({ + .userData = FrameAllocateCustomData((CustomHTMLData) { + .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") + }), + .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); } - CLAY({ .id = CLAY_ID("LinkDocsOuter"), .layout = { .padding = {8, 8} }, .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }) }) { - CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); + CLAY({ .id = CLAY_ID("LinkDocsOuter"), .layout = { .padding = {8, 8} } }) { + CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({ + .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }), + .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }) + ); } } CLAY({ @@ -357,9 +364,11 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { .backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .border = { .width = {2, 2, 2, 2}, .color = COLOR_RED }, .cornerRadius = CLAY_CORNER_RADIUS(10), - .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }), + .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://discord.gg/b4FTWkxdvT") }), }) { - CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); + CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({ + .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }), + .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); } CLAY({ .layout = { .padding = {16, 16, 6, 6} }, @@ -368,7 +377,9 @@ Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) { .cornerRadius = CLAY_CORNER_RADIUS(10), .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay") }), }) { - CLAY_TEXT(CLAY_STRING("Github"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); + CLAY_TEXT(CLAY_STRING("Github"), CLAY_TEXT_CONFIG({ + .userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }), + .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })); } } Clay_LayoutConfig topBorderConfig = (Clay_LayoutConfig) { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(4) }}; diff --git a/examples/raylib-sidebar-scrolling-container/main.c b/examples/raylib-sidebar-scrolling-container/main.c index 52797a8..97c7042 100644 --- a/examples/raylib-sidebar-scrolling-container/main.c +++ b/examples/raylib-sidebar-scrolling-container/main.c @@ -11,7 +11,7 @@ Texture2D profilePicture; #define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y } Clay_String profileText = CLAY_STRING_CONST("Profile Page one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen"); -Clay_TextElementConfig headerTextConfig = { .fontId = 1, .fontSize = 16, .textColor = {0,0,0,255} }; +Clay_TextElementConfig headerTextConfig = { .fontId = 1, .letterSpacing = 5, .fontSize = 16, .textColor = {0,0,0,255} }; void HandleHeaderButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData) { if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { @@ -47,7 +47,7 @@ Clay_RenderCommandArray CreateLayout(void) { CLAY({ .id = CLAY_ID("OuterContainer"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }, .padding = { 16, 16, 16, 16 }, .childGap = 16 }, .backgroundColor = {200, 200, 200, 255} }) { CLAY({ .id = CLAY_ID("SideBar"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = {16, 16, 16, 16 }, .childGap = 16 }, .backgroundColor = {150, 150, 255, 255} }) { CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = { 8, 8, 8, 8 }, .childGap = 8, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = {130, 130, 255, 255} }) { - CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) } }, .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} }}) {} + CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) } }, .image = { .imageData = &profilePicture }}) {} CLAY_TEXT(profileText, CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0, 0, 0, 255}, .textAlignment = CLAY_TEXT_ALIGN_RIGHT })); } CLAY({ .id = CLAY_ID("SidebarBlob1"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }}, .backgroundColor = {110, 110, 255, 255} }) {} @@ -81,9 +81,9 @@ Clay_RenderCommandArray CreateLayout(void) { CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0,0,0,255} })); CLAY({ .id = CLAY_ID("Photos2"), .layout = { .childGap = 16, .padding = { 16, 16, 16, 16 }}, .backgroundColor = {180, 180, 220, Clay_Hovered() ? 120 : 255} }) { - CLAY({ .id = CLAY_ID("Picture4"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {} - CLAY({ .id = CLAY_ID("Picture5"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {} - CLAY({ .id = CLAY_ID("Picture6"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {} + CLAY({ .id = CLAY_ID("Picture4"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture }}) {} + CLAY({ .id = CLAY_ID("Picture5"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture }}) {} + CLAY({ .id = CLAY_ID("Picture6"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture }}) {} } CLAY_TEXT(CLAY_STRING("Faucibus purus in massa tempor nec. Nec ullamcorper sit amet risus nullam eget felis eget nunc. Diam vulputate ut pharetra sit amet aliquam id diam. Lacus suspendisse faucibus interdum posuere lorem. A diam sollicitudin tempor id. Amet massa vitae tortor condimentum lacinia. Aliquet nibh praesent tristique magna."), @@ -93,12 +93,12 @@ Clay_RenderCommandArray CreateLayout(void) { CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); CLAY({ .id = CLAY_ID("Photos"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = {16, 16, 16, 16} }, .backgroundColor = {180, 180, 220, 255} }) { - CLAY({ .id = CLAY_ID("Picture2"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {} + CLAY({ .id = CLAY_ID("Picture2"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 1, .image = { .imageData = &profilePicture }}) {} CLAY({ .id = CLAY_ID("Picture1"), .layout = { .childAlignment = { .x = CLAY_ALIGN_X_CENTER }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {8, 8, 8, 8} }, .backgroundColor = {170, 170, 220, 255} }) { - CLAY({ .id = CLAY_ID("ProfilePicture2"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} }}) {} + CLAY({ .id = CLAY_ID("ProfilePicture2"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture }}) {} CLAY_TEXT(CLAY_STRING("Image caption below"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); } - CLAY({ .id = CLAY_ID("Picture3"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {} + CLAY({ .id = CLAY_ID("Picture3"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120) }}, .aspectRatio = 1, .image = { .imageData = &profilePicture }}) {} } CLAY_TEXT(CLAY_STRING("Amet cursus sit amet dictum sit amet justo donec. Et malesuada fames ac turpis egestas maecenas. A lacus vestibulum sed arcu non odio euismod lacinia. Gravida neque convallis a cras. Dui nunc mattis enim ut tellus elementum sagittis vitae et. Orci sagittis eu volutpat odio facilisis mauris. Neque gravida in fermentum et sollicitudin ac orci. Ultrices dui sapien eget mi proin sed libero. Euismod quis viverra nibh cras pulvinar mattis. Diam volutpat commodo sed egestas egestas. In fermentum posuere urna nec tincidunt praesent semper. Integer eget aliquet nibh praesent tristique magna.\nId cursus metus aliquam eleifend mi in. Sed pulvinar proin gravida hendrerit lectus a. Etiam tempor orci eu lobortis elementum nibh tellus. Nullam vehicula ipsum a arcu cursus vitae. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Condimentum lacinia quis vel eros donec ac odio. Mattis pellentesque id nibh tortor id aliquet lectus. Turpis egestas integer eget aliquet nibh praesent tristique. Porttitor massa id neque aliquam vestibulum morbi. Mauris commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Nunc scelerisque viverra mauris in aliquam sem fringilla. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla.\nLacus laoreet non curabitur gravida arcu ac tortor dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tristique senectus et netus et malesuada fames ac. Nunc aliquet bibendum enim facilisis gravida. Egestas maecenas pharetra convallis posuere morbi leo urna molestie. Sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna. Viverra vitae congue eu consequat. Aliquet enim tortor at auctor urna. Ornare massa eget egestas purus viverra accumsan in nisl nisi. Elit pellentesque habitant morbi tristique senectus et netus et malesuada.\nSuspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Lobortis feugiat vivamus at augue eget arcu. Vitae justo eget magna fermentum iaculis eu. Gravida rutrum quisque non tellus orci. Ipsum faucibus vitae aliquet nec. Nullam non nisi est sit amet. Nunc consequat interdum varius sit amet mattis vulputate enim. Sem fringilla ut morbi tincidunt augue interdum. Vitae purus faucibus ornare suspendisse. Massa tincidunt nunc pulvinar sapien et. Fringilla ut morbi tincidunt augue interdum velit euismod in. Donec massa sapien faucibus et. Est placerat in egestas erat imperdiet. Gravida rutrum quisque non tellus. Morbi non arcu risus quis varius quam quisque id diam. Habitant morbi tristique senectus et netus et malesuada fames ac. Eget lorem dolor sed viverra.\nOrnare massa eget egestas purus viverra. Varius vel pharetra vel turpis nunc eget lorem. Consectetur purus ut faucibus pulvinar elementum. Placerat in egestas erat imperdiet sed euismod nisi. Interdum velit euismod in pellentesque massa placerat duis ultricies lacus. Aliquam nulla facilisi cras fermentum odio eu. Est pellentesque elit ullamcorper dignissim cras tincidunt. Nunc sed id semper risus in hendrerit gravida rutrum. A pellentesque sit amet porttitor eget dolor morbi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Sed id semper risus in hendrerit gravida. Tincidunt praesent semper feugiat nibh. Aliquet lectus proin nibh nisl condimentum id venenatis a. Enim sit amet venenatis urna cursus eget. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Lacinia quis vel eros donec ac odio tempor orci. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Erat pellentesque adipiscing commodo elit at.\nEgestas sed sed risus pretium quam vulputate. Vitae congue mauris rhoncus aenean vel elit scelerisque mauris pellentesque. Aliquam malesuada bibendum arcu vitae elementum. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pellentesque dignissim enim sit amet venenatis urna cursus. Et malesuada fames ac turpis egestas sed tempus urna. Vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Nibh cras pulvinar mattis nunc sed blandit libero. Fringilla est ullamcorper eget nulla facilisi etiam dignissim. Aenean euismod elementum nisi quis eleifend quam adipiscing vitae proin. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Ornare quam viverra orci sagittis eu. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Ornare lectus sit amet est. Ullamcorper sit amet risus nullam eget. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum.\nUrna nec tincidunt praesent semper feugiat nibh. Ut venenatis tellus in metus vulputate eu scelerisque felis. Cursus risus at ultrices mi tempus. In pellentesque massa placerat duis ultricies lacus sed turpis. Platea dictumst quisque sagittis purus. Cras adipiscing enim eu turpis egestas. Egestas sed tempus urna et pharetra pharetra. Netus et malesuada fames ac turpis egestas integer eget aliquet. Ac turpis egestas sed tempus. Sed lectus vestibulum mattis ullamcorper velit sed. Ante metus dictum at tempor commodo ullamcorper a. Augue neque gravida in fermentum et sollicitudin ac. Praesent semper feugiat nibh sed pulvinar proin gravida. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas.\nRidiculus mus mauris vitae ultricies. Morbi quis commodo odio aenean. Duis ultricies lacus sed turpis. Non pulvinar neque laoreet suspendisse interdum consectetur. Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam. Volutpat est velit egestas dui id ornare arcu odio ut. Viverra tellus in hac habitasse platea dictumst vestibulum rhoncus est. Vestibulum lectus mauris ultrices eros. Sed blandit libero volutpat sed cras ornare. Id leo in vitae turpis massa sed elementum tempus. Gravida dictum fusce ut placerat orci nulla pellentesque. Pretium quam vulputate dignissim suspendisse in. Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Risus viverra adipiscing at in tellus. Turpis nunc eget lorem dolor sed viverra ipsum. Senectus et netus et malesuada fames ac. Habitasse platea dictumst vestibulum rhoncus est. Nunc sed id semper risus in hendrerit gravida. Felis eget velit aliquet sagittis id. Eget felis eget nunc lobortis.\nMaecenas pharetra convallis posuere morbi leo. Maecenas volutpat blandit aliquam etiam. A condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Pulvinar mattis nunc sed blandit libero volutpat sed. Feugiat in ante metus dictum at tempor commodo ullamcorper. Vel pharetra vel turpis nunc eget lorem dolor. Est placerat in egestas erat imperdiet sed euismod. Quisque non tellus orci ac auctor augue mauris augue. Placerat vestibulum lectus mauris ultrices eros in cursus turpis. Enim nunc faucibus a pellentesque sit. Adipiscing vitae proin sagittis nisl. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Aliquam sem fringilla ut morbi.\nArcu odio ut sem nulla pharetra diam sit amet nisl. Non diam phasellus vestibulum lorem sed. At erat pellentesque adipiscing commodo elit at. Lacus luctus accumsan tortor posuere ac ut consequat. Et malesuada fames ac turpis egestas integer. Tristique magna sit amet purus. A condimentum vitae sapien pellentesque habitant. Quis varius quam quisque id diam vel quam. Est ullamcorper eget nulla facilisi etiam dignissim diam quis. Augue interdum velit euismod in pellentesque massa. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant. Vulputate eu scelerisque felis imperdiet. Nibh tellus molestie nunc non blandit massa. Velit euismod in pellentesque massa placerat. Sed cras ornare arcu dui. Ut sem viverra aliquet eget sit. Eu lobortis elementum nibh tellus molestie nunc non. Blandit libero volutpat sed cras ornare arcu dui vivamus.\nSit amet aliquam id diam maecenas. Amet risus nullam eget felis eget nunc lobortis mattis aliquam. Magna sit amet purus gravida. Egestas purus viverra accumsan in nisl nisi. Leo duis ut diam quam. Ante metus dictum at tempor commodo ullamcorper. Ac turpis egestas integer eget. Fames ac turpis egestas integer eget aliquet nibh. Sem integer vitae justo eget magna fermentum. Semper auctor neque vitae tempus quam pellentesque nec nam aliquam. Vestibulum mattis ullamcorper velit sed. Consectetur adipiscing elit duis tristique sollicitudin nibh. Massa id neque aliquam vestibulum morbi blandit cursus risus.\nCursus sit amet dictum sit amet justo donec enim diam. Egestas erat imperdiet sed euismod. Nullam vehicula ipsum a arcu cursus vitae congue mauris. Habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Duis ultricies lacus sed turpis tincidunt id aliquet risus feugiat. Faucibus ornare suspendisse sed nisi lacus sed viverra. Pretium fusce id velit ut tortor pretium viverra. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Senectus et netus et malesuada. Tellus pellentesque eu tincidunt tortor aliquam. Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat. Quis vel eros donec ac odio. Id interdum velit laoreet id donec ultrices tincidunt.\nMassa id neque aliquam vestibulum morbi blandit cursus risus at. Enim tortor at auctor urna nunc id cursus metus. Lorem ipsum dolor sit amet consectetur. At quis risus sed vulputate odio. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Et malesuada fames ac turpis egestas maecenas. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Viverra orci sagittis eu volutpat odio facilisis mauris. Adipiscing bibendum est ultricies integer quis auctor elit sed. Neque viverra justo nec ultrices dui sapien. Elementum nibh tellus molestie nunc non blandit massa enim. Euismod elementum nisi quis eleifend quam adipiscing vitae proin sagittis. Faucibus ornare suspendisse sed nisi. Quis viverra nibh cras pulvinar mattis nunc sed blandit. Tristique senectus et netus et. Magnis dis parturient montes nascetur ridiculus mus.\nDolor magna eget est lorem ipsum dolor. Nibh sit amet commodo nulla. Donec pretium vulputate sapien nec sagittis aliquam malesuada. Cras adipiscing enim eu turpis egestas pretium. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Mus mauris vitae ultricies leo integer. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Quis hendrerit dolor magna eget. Nisl tincidunt eget nullam non. Vitae congue eu consequat ac felis donec et odio. Vivamus at augue eget arcu dictum varius duis at. Ornare quam viverra orci sagittis.\nErat nam at lectus urna duis convallis. Massa placerat duis ultricies lacus sed turpis tincidunt id aliquet. Est ullamcorper eget nulla facilisi etiam dignissim diam. Arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Neque viverra justo nec ultrices dui sapien eget mi proin. Viverra accumsan in nisl nisi scelerisque eu ultrices. Consequat interdum varius sit amet mattis. In aliquam sem fringilla ut morbi. Eget arcu dictum varius duis at. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Arcu bibendum at varius vel pharetra vel turpis. Hac habitasse platea dictumst quisque sagittis purus sit amet. Sapien eget mi proin sed libero enim sed. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. Semper viverra nam libero justo. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Et malesuada fames ac turpis egestas maecenas pharetra convallis posuere.\nTurpis egestas sed tempus urna et pharetra pharetra massa. Gravida in fermentum et sollicitudin ac orci phasellus. Ornare suspendisse sed nisi lacus sed viverra tellus in. Fames ac turpis egestas maecenas pharetra convallis posuere. Mi proin sed libero enim sed faucibus turpis. Sit amet mauris commodo quis imperdiet massa tincidunt nunc. Ut etiam sit amet nisl purus in mollis nunc. Habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat. Eget aliquet nibh praesent tristique magna. Sit amet est placerat in egestas erat. Commodo sed egestas egestas fringilla. Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dignissim convallis aenean et tortor at risus viverra. Morbi blandit cursus risus at ultrices mi. Ac turpis egestas integer eget aliquet nibh praesent tristique magna.\nVolutpat sed cras ornare arcu dui. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Viverra justo nec ultrices dui sapien. Amet risus nullam eget felis eget nunc lobortis. Metus aliquam eleifend mi in. Ut eu sem integer vitae. Auctor elit sed vulputate mi sit amet. Nisl nisi scelerisque eu ultrices. Dictum fusce ut placerat orci nulla. Pellentesque habitant morbi tristique senectus et. Auctor elit sed vulputate mi sit. Tincidunt arcu non sodales neque. Mi in nulla posuere sollicitudin aliquam. Morbi non arcu risus quis varius quam quisque id diam. Cras adipiscing enim eu turpis egestas pretium aenean pharetra magna. At auctor urna nunc id cursus metus aliquam. Mauris a diam maecenas sed enim ut sem viverra. Nunc scelerisque viverra mauris in. In iaculis nunc sed augue lacus viverra vitae congue eu. Volutpat blandit aliquam etiam erat velit scelerisque in dictum non."), diff --git a/renderers/SDL2/clay_renderer_SDL2.c b/renderers/SDL2/clay_renderer_SDL2.c index 6eccb01..6372ba7 100644 --- a/renderers/SDL2/clay_renderer_SDL2.c +++ b/renderers/SDL2/clay_renderer_SDL2.c @@ -23,6 +23,7 @@ static Clay_Dimensions SDL2_MeasureText(Clay_StringSlice text, Clay_TextElementC SDL2_Font *fonts = (SDL2_Font*)userData; TTF_Font *font = fonts[config->fontId].font; + TTF_SetFontSize(font, config->fontSize); char *chars = (char *)calloc(text.length + 1, 1); memcpy(chars, text.chars, text.length); int width = 0; @@ -294,6 +295,7 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren char *cloned = (char *)calloc(config->stringContents.length + 1, 1); memcpy(cloned, config->stringContents.chars, config->stringContents.length); TTF_Font* font = fonts[config->fontId].font; + TTF_SetFontSize(font, config->fontSize); SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) { .r = (Uint8)config->textColor.r, .g = (Uint8)config->textColor.g, diff --git a/renderers/SDL3/clay_renderer_SDL3.c b/renderers/SDL3/clay_renderer_SDL3.c index b918c97..fc71290 100644 --- a/renderers/SDL3/clay_renderer_SDL3.c +++ b/renderers/SDL3/clay_renderer_SDL3.c @@ -164,6 +164,7 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_TextRenderData *config = &rcmd->renderData.text; TTF_Font *font = rendererData->fonts[config->fontId]; + TTF_SetFontSize(font, config->fontSize); TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length); TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a); TTF_DrawRendererText(text, rect.x, rect.y); @@ -184,11 +185,11 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla if (config->width.left > 0) { const float starting_y = rect.y + clampedRadii.topLeft; const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft; - SDL_FRect line = { rect.x, starting_y, config->width.left, length }; + SDL_FRect line = { rect.x - 1, starting_y, config->width.left, length }; SDL_RenderFillRect(rendererData->renderer, &line); } if (config->width.right > 0) { - const float starting_x = rect.x + rect.w - (float)config->width.right; + const float starting_x = rect.x + rect.w - (float)config->width.right + 1; const float starting_y = rect.y + clampedRadii.topRight; const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight; SDL_FRect line = { starting_x, starting_y, config->width.right, length }; @@ -197,12 +198,12 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla if (config->width.top > 0) { const float starting_x = rect.x + clampedRadii.topLeft; const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight; - SDL_FRect line = { starting_x, rect.y, length, config->width.top }; + SDL_FRect line = { starting_x, rect.y - 1, length, config->width.top }; SDL_RenderFillRect(rendererData->renderer, &line); } if (config->width.bottom > 0) { const float starting_x = rect.x + clampedRadii.bottomLeft; - const float starting_y = rect.y + rect.h - (float)config->width.bottom; + const float starting_y = rect.y + rect.h - (float)config->width.bottom + 1; const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight; SDL_FRect line = { starting_x, starting_y, length, config->width.bottom }; SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a); @@ -211,25 +212,25 @@ static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Cla //corners if (config->cornerRadius.topLeft > 0) { const float centerX = rect.x + clampedRadii.topLeft -1; - const float centerY = rect.y + clampedRadii.topLeft; + const float centerY = rect.y + clampedRadii.topLeft - 1; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft, 180.0f, 270.0f, config->width.top, config->color); } if (config->cornerRadius.topRight > 0) { - const float centerX = rect.x + rect.w - clampedRadii.topRight -1; - const float centerY = rect.y + clampedRadii.topRight; + const float centerX = rect.x + rect.w - clampedRadii.topRight; + const float centerY = rect.y + clampedRadii.topRight - 1; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight, 270.0f, 360.0f, config->width.top, config->color); } if (config->cornerRadius.bottomLeft > 0) { const float centerX = rect.x + clampedRadii.bottomLeft -1; - const float centerY = rect.y + rect.h - clampedRadii.bottomLeft -1; + const float centerY = rect.y + rect.h - clampedRadii.bottomLeft; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft, 90.0f, 180.0f, config->width.bottom, config->color); } if (config->cornerRadius.bottomRight > 0) { - const float centerX = rect.x + rect.w - clampedRadii.bottomRight -1; //TODO: why need to -1 in all calculations??? - const float centerY = rect.y + rect.h - clampedRadii.bottomRight -1; + const float centerX = rect.x + rect.w - clampedRadii.bottomRight; + const float centerY = rect.y + rect.h - clampedRadii.bottomRight; SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight, 0.0f, 90.0f, config->width.bottom, config->color); } diff --git a/renderers/raylib/clay_renderer_raylib.c b/renderers/raylib/clay_renderer_raylib.c index 6914770..6e56103 100644 --- a/renderers/raylib/clay_renderer_raylib.c +++ b/renderers/raylib/clay_renderer_raylib.c @@ -87,6 +87,8 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_Tex float maxTextWidth = 0.0f; float lineTextWidth = 0; + int maxLineCharCount = 0; + int lineCharCount = 0; float textHeight = config->fontSize; Font* fonts = (Font*)userData; @@ -99,11 +101,13 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_Tex float scaleFactor = config->fontSize/(float)fontToUse.baseSize; - for (int i = 0; i < text.length; ++i) + for (int i = 0; i < text.length; ++i, lineCharCount++) { if (text.chars[i] == '\n') { maxTextWidth = fmax(maxTextWidth, lineTextWidth); + maxLineCharCount = CLAY__MAX(maxLineCharCount, lineCharCount); lineTextWidth = 0; + lineCharCount = 0; continue; } int index = text.chars[i] - 32; @@ -112,8 +116,9 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_Tex } maxTextWidth = fmax(maxTextWidth, lineTextWidth); + maxLineCharCount = CLAY__MAX(maxLineCharCount, lineCharCount); - textSize.width = maxTextWidth * scaleFactor; + textSize.width = maxTextWidth * scaleFactor + (lineCharCount * config->letterSpacing); textSize.height = textHeight; return textSize; @@ -145,7 +150,7 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) for (int j = 0; j < renderCommands.length; j++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); - Clay_BoundingBox boundingBox = renderCommand->boundingBox; + Clay_BoundingBox boundingBox = {roundf(renderCommand->boundingBox.x), roundf(renderCommand->boundingBox.y), roundf(renderCommand->boundingBox.width), roundf(renderCommand->boundingBox.height)}; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { @@ -174,11 +179,12 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts) if (tintColor.r == 0 && tintColor.g == 0 && tintColor.b == 0 && tintColor.a == 0) { tintColor = (Clay_Color) { 255, 255, 255, 255 }; } - DrawTextureEx( + DrawTexturePro( imageTexture, - (Vector2){boundingBox.x, boundingBox.y}, + (Rectangle) { 0, 0, imageTexture.width, imageTexture.height }, + (Rectangle){boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, + (Vector2) {}, 0, - boundingBox.width / (float)imageTexture.width, CLAY_COLOR_TO_RAYLIB_COLOR(tintColor)); break; }