diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ddea29..191b2f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF) option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF) option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF) option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF) +option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF) message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}") @@ -24,6 +25,7 @@ endif() if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS) if(NOT MSVC) add_subdirectory("examples/clay-official-website") + add_subdirectory("examples/terminal-example") endif() add_subdirectory("examples/introducing-clay-video-demo") endif () @@ -43,6 +45,11 @@ if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES) add_subdirectory("examples/sokol-corner-radius") endif() +# Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement +if(CLAY_INCLUDE_PLAYDATE_EXAMPLES) + add_subdirectory("examples/playdate-project-example") +endif() + if(WIN32) # Build only for Win or Wine if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES) add_subdirectory("examples/win32_gdi") diff --git a/README.md b/README.md index dadec73..7854979 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} })); } @@ -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 } ``` @@ -489,6 +489,13 @@ There are also supported bindings for other languages, including: - [Odin Bindings](https://github.com/nicbarker/clay/tree/main/bindings/odin) - [Rust Bindings](https://github.com/clay-ui-rs/clay) +### Other implementations +Clay has also been implemented in other languages: + +- [`glay`](https://github.com/soypat/glay) - Go line-by-line rewrite with readability as main goal. +- [`totallygamerjet/clay`](https://github.com/totallygamerjet/clay) - Port using `cxgo`, a C to Go transpiler. +- [`goclay`](https://github.com/igadmg/goclay) - Go line-by-line rewrite closely matching the reference. + ### Debug Tools Clay includes built-in UI debugging tools, similar to the "inspector" in browsers such as Chrome or Firefox. These tools are included in `clay.h`, and work by injecting additional render commands into the output [Clay_RenderCommandArray](#clay_rendercommandarray). @@ -832,7 +839,6 @@ Clay_TextElementConfig { CLAY_TEXT_WRAP_NEWLINES, CLAY_TEXT_WRAP_NONE, }; - bool hashStringContents }; ``` @@ -892,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 @@ -1055,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; @@ -1101,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. @@ -1287,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 } }) {}` @@ -1314,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** @@ -1323,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; ``` @@ -2013,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 501758d..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 { @@ -170,6 +173,11 @@ FloatingAttachToElement :: enum EnumBackingType { Root, } +FloatingClipToElement :: enum EnumBackingType { + None, + AttachedParent, +} + FloatingElementConfig :: struct { offset: Vector2, expand: Dimensions, @@ -178,6 +186,7 @@ FloatingElementConfig :: struct { attachment: FloatingAttachPoints, pointerCaptureMode: PointerCaptureMode, attachTo: FloatingAttachToElement, + clipTo: FloatingClipToElement, } TextRenderData :: struct { @@ -197,7 +206,6 @@ RectangleRenderData :: struct { ImageRenderData :: struct { backgroundColor: Color, cornerRadius: CornerRadius, - sourceDimensions: Dimensions, imageData: rawptr, } @@ -334,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 1c94a68..2568b60 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 63d5b46..337a884 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 1534a84..6739690 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 09186b4..d92193b 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 e49ae9e..68d46ee 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..8ee7f50 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 @@ -184,7 +184,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 +195,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 +205,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 +248,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 +298,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 +320,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 +338,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 +371,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 +399,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 +434,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 +471,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 +508,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 +519,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 +530,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 +542,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 +552,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 +568,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 +577,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 +585,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 +602,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 +620,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 +638,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 +653,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 +680,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 +705,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 +727,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 +738,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 +750,8 @@ typedef struct { Clay_Color backgroundColor; // Controls the "radius", or corner rounding of elements, including rectangles, borders and images. Clay_CornerRadius cornerRadius; + // Controls settings related to 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 +791,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 +1066,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 +1081,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 +1090,7 @@ typedef CLAY_PACKED_ENUM { typedef union { Clay_TextElementConfig *textElementConfig; + Clay_AspectRatioElementConfig *aspectRatioElementConfig; Clay_ImageElementConfig *imageElementConfig; Clay_FloatingElementConfig *floatingElementConfig; Clay_CustomElementConfig *customElementConfig; @@ -1236,13 +1247,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; @@ -1310,6 +1322,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 +1496,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); @@ -1661,7 +1674,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 +1773,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; } @@ -2048,7 +2060,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 +2160,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 +2175,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 +2264,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 +2303,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 +2398,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)) { @@ -2510,26 +2522,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 +2593,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 @@ -2776,6 +2796,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 +2819,6 @@ void Clay__CalculateFinalLayout(void) { .image = { .backgroundColor = sharedConfig->backgroundColor, .cornerRadius = sharedConfig->cornerRadius, - .sourceDimensions = elementConfig->config.imageElementConfig->sourceDimensions, .imageData = elementConfig->config.imageElementConfig->imageData, } }; @@ -3097,6 +3117,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 +3602,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; } 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/cairo-pdf-rendering/main.c b/examples/cairo-pdf-rendering/main.c index b2374ac..b1d1306 100644 --- a/examples/cairo-pdf-rendering/main.c +++ b/examples/cairo-pdf-rendering/main.c @@ -145,7 +145,7 @@ int main(void) { "Quicksand Semibold" }; - Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText, (uintptr_t)fonts); + Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText, fonts); Clay_BeginLayout(); diff --git a/examples/clay-official-website/build/clay/index.html b/examples/clay-official-website/build/clay/index.html index e626e4b..f68b2d1 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' }, ]}; @@ -168,7 +168,7 @@ { name: 'disablePointerEvents', type: 'uint8_t' }, ]}; let renderCommandDefinition = { - name: 'CLay_RenderCommand', + name: 'Clay_RenderCommand', type: 'struct', members: [ { name: 'boundingBox', type: 'struct', members: [ @@ -183,7 +183,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 +380,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(); } @@ -570,7 +570,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..32882f5 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..f68b2d1 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' }, ]}; @@ -168,7 +168,7 @@ { name: 'disablePointerEvents', type: 'uint8_t' }, ]}; let renderCommandDefinition = { - name: 'CLay_RenderCommand', + name: 'Clay_RenderCommand', type: 'struct', members: [ { name: 'boundingBox', type: 'struct', members: [ @@ -183,7 +183,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 +380,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(); } @@ -570,7 +570,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..3064c66 100644 --- a/examples/clay-official-website/main.c +++ b/examples/clay-official-website/main.c @@ -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")) } }) {} } } } diff --git a/examples/playdate-project-example/.gitignore b/examples/playdate-project-example/.gitignore new file mode 100644 index 0000000..736b2f4 --- /dev/null +++ b/examples/playdate-project-example/.gitignore @@ -0,0 +1,3 @@ +clay_playdate_example.pdx +Source/pdex.dylib +Source/pdex.elf diff --git a/examples/playdate-project-example/CmakeLists.txt b/examples/playdate-project-example/CmakeLists.txt new file mode 100644 index 0000000..c4aaf35 --- /dev/null +++ b/examples/playdate-project-example/CmakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.27) +set(CMAKE_C_STANDARD 99) + +set(ENVSDK $ENV{PLAYDATE_SDK_PATH}) + +if (NOT ${ENVSDK} STREQUAL "") + # Convert path from Windows + file(TO_CMAKE_PATH ${ENVSDK} SDK) +else() + execute_process( + COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config" + COMMAND head -n 1 + COMMAND cut -c9- + OUTPUT_VARIABLE SDK + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +endif() + +if (NOT EXISTS ${SDK}) + message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH") + return() +endif() + +set(CMAKE_CONFIGURATION_TYPES "Debug;Release") +set(CMAKE_XCODE_GENERATE_SCHEME TRUE) + +# Game Name Customization +set(PLAYDATE_GAME_NAME clay_playdate_example) +set(PLAYDATE_GAME_DEVICE clay_playdate_example_DEVICE) + +project(${PLAYDATE_GAME_NAME} C ASM) + +if (TOOLCHAIN STREQUAL "armgcc") + add_executable(${PLAYDATE_GAME_DEVICE} main.c) +else() + add_library(${PLAYDATE_GAME_NAME} SHARED main.c) +endif() + +include(${SDK}/C_API/buildsupport/playdate_game.cmake) + diff --git a/examples/playdate-project-example/README.md b/examples/playdate-project-example/README.md new file mode 100644 index 0000000..bdbe854 --- /dev/null +++ b/examples/playdate-project-example/README.md @@ -0,0 +1,37 @@ +# Playdate console example + +This example uses a modified version of the document viewer application from the Clay video demo. The Playdate console has a very small black and white screen, so some of the fixed sizes and styles needed to be modified to make the application usable on the console. The selected document can be changed using up/down on the D-pad, and the selected document can be scrolled with the crank. + +## Building + +You need to have the (Playdate SDK)[https://play.date/dev/] installed to be able to build this example. Once it's installed you can build it by adding -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON when initialising a directory with cmake. + +e.g. + +``` +cmake -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON cmake-build-debug +``` + +And then build it: + +``` +cmake --build cmake-build-debug +``` + +The pdx file will be located at examples/playdate-project-example/clay_playdate_example.pdx. You can then open it with the Playdate simulator. + +## Building for the playdate device + +By default building this example will produce a pdx which can only run on the Playdate simulator application. To build a pdx that can run on the Playdate hardware you need to set the toolchain to use armgcc, toolchain file to the arm.cmake provided in the playdate SDK and make sure to disable the other examples. The Playdate hardware requires threads to be disabled which is not compatible with some of the other examples. + +e.g. To setup the cmake-build-release directory for device builds: + +``` +cmake -DTOOLCHAIN=armgcc -DCMAKE_TOOLCHAIN_FILE=/Users/mattahj/Developer/PlaydateSDK/C_API/buildsupport/arm.cmake -DCLAY_INCLUDE_ALL_EXAMPLES=OFF -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON -B cmake-build-release +``` + +And then build it: + +``` +cmake --build cmake-build-release +``` diff --git a/examples/playdate-project-example/Source/pdxinfo b/examples/playdate-project-example/Source/pdxinfo new file mode 100644 index 0000000..0e101a2 --- /dev/null +++ b/examples/playdate-project-example/Source/pdxinfo @@ -0,0 +1,5 @@ +name=Clay Playdate Example +author=Matthew Jennings +description=A small demo of Clay running on the Playdate +bundleID=dev.mattahj.clay_example +imagePath= diff --git a/examples/playdate-project-example/Source/star.png b/examples/playdate-project-example/Source/star.png new file mode 100644 index 0000000..1b33759 Binary files /dev/null and b/examples/playdate-project-example/Source/star.png differ diff --git a/examples/playdate-project-example/clay-video-demo-playdate.c b/examples/playdate-project-example/clay-video-demo-playdate.c new file mode 100644 index 0000000..2d062ca --- /dev/null +++ b/examples/playdate-project-example/clay-video-demo-playdate.c @@ -0,0 +1,366 @@ +// This is the video demo with some adjustments so it works on the playdate +// console The playdate screen is only 400x240 pixels and it can only display +// black and white, so some fixed sizes and colours needed tweaking! The file +// menu was also removed as it does not really make sense when there is no +// pointer +// +// Note: The playdate console also does not support dynamic font sizes - fonts must be +// created at a specific size with the pdc tool - so any font size set in the clay layout +// will have no effect. +#include "pd_api.h" +#include "../../clay.h" +#include + +const int FONT_ID_BODY = 0; +const int FONT_ID_BUTTON = 1; + +Clay_Color COLOR_WHITE = { 255, 255, 255, 255 }; +Clay_Color COLOR_BLACK = { 0, 0, 0, 255 }; + +void RenderHeaderButton(Clay_String text) { + CLAY({ + .layout = { .padding = { 8, 8, 4, 4 } }, + .backgroundColor = COLOR_BLACK, + .cornerRadius = CLAY_CORNER_RADIUS(4) + }) { + CLAY_TEXT( + text, + CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_WHITE }) + ); + } +} + +typedef struct { + Clay_String title; + Clay_String contents; + LCDBitmap* image; +} Document; + +typedef struct { + Document *documents; + uint32_t length; +} DocumentArray; + +#define MAX_DOCUMENTS 3 +Document documentsRaw[MAX_DOCUMENTS]; + +DocumentArray documents = { .length = MAX_DOCUMENTS, .documents = documentsRaw }; + +void ClayVideoDemoPlaydate_Initialize(PlaydateAPI* pd) { + documents.documents[0] = (Document){ + .title = CLAY_STRING("Squirrels"), + .image = pd->graphics->loadBitmap("star.png", NULL), + .contents = CLAY_STRING( + "The Secret Life of Squirrels: Nature's Clever Acrobats\n" + "Squirrels are often overlooked creatures, dismissed as mere park " + "inhabitants or backyard nuisances. Yet, beneath their fluffy tails " + "and twitching noses lies an intricate world of cunning, agility, " + "and survival tactics that are nothing short of fascinating. As one " + "of the most common mammals in North America, squirrels have adapted " + "to a wide range of environments from bustling urban centers to " + "tranquil forests and have developed a variety of unique behaviors " + "that continue to intrigue scientists and nature enthusiasts alike.\n" + "\n" + "Master Tree Climbers\n" + "At the heart of a squirrel's skill set is its impressive ability to " + "navigate trees with ease. Whether they're darting from branch to " + "branch or leaping across wide gaps, squirrels possess an innate " + "talent for acrobatics. Their powerful hind legs, which are longer " + "than their front legs, give them remarkable jumping power. With a " + "tail that acts as a counterbalance, squirrels can leap distances of " + "up to ten times the length of their body, making them some of the " + "best aerial acrobats in the animal kingdom.\n" + "But it's not just their agility that makes them exceptional " + "climbers. Squirrels' sharp, curved claws allow them to grip tree " + "bark with precision, while the soft pads on their feet provide " + "traction on slippery surfaces. Their ability to run at high speeds " + "and scale vertical trunks with ease is a testament to the " + "evolutionary adaptations that have made them so successful in their " + "arboreal habitats.\n" + "\n" + "Food Hoarders Extraordinaire\n" + "Squirrels are often seen frantically gathering nuts, seeds, and " + "even fungi in preparation for winter. While this behavior may seem " + "like instinctual hoarding, it is actually a survival strategy that " + "has been honed over millions of years. Known as \"scatter " + "hoarding,\" squirrels store their food in a variety of hidden " + "locations, often burying it deep in the soil or stashing it in " + "hollowed-out tree trunks.\n" + "Interestingly, squirrels have an incredible memory for the " + "locations of their caches. Research has shown that they can " + "remember thousands of hiding spots, often returning to them months " + "later when food is scarce. However, they don't always recover every " + "stash some forgotten caches eventually sprout into new trees, " + "contributing to forest regeneration. This unintentional role as " + "forest gardeners highlights the ecological importance of squirrels " + "in their ecosystems.\n" + "\n" + "The Great Squirrel Debate: Urban vs. Wild\n" + "While squirrels are most commonly associated with rural or wooded " + "areas, their adaptability has allowed them to thrive in urban " + "environments as well. In cities, squirrels have become adept at " + "finding food sources in places like parks, streets, and even " + "garbage cans. However, their urban counterparts face unique " + "challenges, including traffic, predators, and the lack of natural " + "shelters. Despite these obstacles, squirrels in urban areas are " + "often observed using human infrastructure such as buildings, " + "bridges, and power lines as highways for their acrobatic " + "escapades.\n" + "There is, however, a growing concern regarding the impact of urban " + "life on squirrel populations. Pollution, deforestation, and the " + "loss of natural habitats are making it more difficult for squirrels " + "to find adequate food and shelter. As a result, conservationists " + "are focusing on creating squirrel-friendly spaces within cities, " + "with the goal of ensuring these resourceful creatures continue to " + "thrive in both rural and urban landscapes.\n" + "\n" + "A Symbol of Resilience\n" + "In many cultures, squirrels are symbols of resourcefulness, " + "adaptability, and preparation. Their ability to thrive in a variety " + "of environments while navigating challenges with agility and grace " + "serves as a reminder of the resilience inherent in nature. Whether " + "you encounter them in a quiet forest, a city park, or your own " + "backyard, squirrels are creatures that never fail to amaze with " + "their endless energy and ingenuity.\n" + "In the end, squirrels may be small, but they are mighty in their " + "ability to survive and thrive in a world that is constantly " + "changing. So next time you spot one hopping across a branch or " + "darting across your lawn, take a moment to appreciate the " + "remarkable acrobat at work a true marvel of the natural world.\n" + ) + }; + documents.documents[1] = (Document){ + .title = CLAY_STRING("Lorem Ipsum"), + .image = pd->graphics->loadBitmap("star.png", NULL), + .contents = CLAY_STRING( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + "eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim " + "ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut " + "aliquip ex ea commodo consequat. Duis aute irure dolor in " + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla " + "pariatur. Excepteur sint occaecat cupidatat non proident, sunt in " + "culpa qui officia deserunt mollit anim id est laborum." + ) + }; + documents.documents[2] = (Document){ + .title = CLAY_STRING("Vacuum Instructions"), + .image = pd->graphics->loadBitmap("star.png", NULL), + .contents = CLAY_STRING( + "Chapter 3: Getting Started - Unpacking and Setup\n" + "\n" + "Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In " + "this section, we will guide you through the simple steps to get " + "your vacuum up and running. Before you begin, please ensure that " + "you have all the components listed in the \"Package Contents\" " + "section on page 2.\n" + "\n" + "1. Unboxing Your Vacuum\n" + "Carefully remove the vacuum cleaner from the box. Avoid using sharp " + "objects that could damage the product. Once removed, place the unit " + "on a flat, stable surface to proceed with the setup. Inside the " + "box, you should find:\n" + "\n" + " The main vacuum unit\n" + " A telescoping extension wand\n" + " A set of specialized cleaning tools (crevice tool, upholstery " + "brush, etc.)\n" + " A reusable dust bag (if applicable)\n" + " A power cord with a 3-prong plug\n" + " A set of quick-start instructions\n" + "\n" + "2. Assembling Your Vacuum\n" + "Begin by attaching the extension wand to the main body of the " + "vacuum cleaner. Line up the connectors and twist the wand into " + "place until you hear a click. Next, select the desired cleaning " + "tool and firmly attach it to the wand's end, ensuring it is " + "securely locked in.\n" + "\n" + "For models that require a dust bag, slide the bag into the " + "compartment at the back of the vacuum, making sure it is properly " + "aligned with the internal mechanism. If your vacuum uses a bagless " + "system, ensure the dust container is correctly seated and locked in " + "place before use.\n" + "\n" + "3. Powering On\n" + "To start the vacuum, plug the power cord into a grounded electrical " + "outlet. Once plugged in, locate the power switch, usually " + "positioned on the side of the handle or body of the unit, depending " + "on your model. Press the switch to the \"On\" position, and you " + "should hear the motor begin to hum. If the vacuum does not power " + "on, check that the power cord is securely plugged in, and ensure " + "there are no blockages in the power switch.\n" + "\n" + "Note: Before first use, ensure that the vacuum filter (if your " + "model has one) is properly installed. If unsure, refer to \"Section " + "5: Maintenance\" for filter installation instructions." + ) + }; +} + +Clay_RenderCommandArray ClayVideoDemoPlaydate_CreateLayout(int selectedDocumentIndex) { + + Clay_BeginLayout(); + + Clay_Sizing layoutExpand = { + .width = CLAY_SIZING_GROW(0), + .height = CLAY_SIZING_GROW(0) + }; + + Clay_BorderElementConfig contentBorders = { + .color = COLOR_BLACK, + .width = { .top = 1, .left = 1, .right = 1, .bottom = 1 } + }; + + // Build UI here + CLAY({ + .id = CLAY_ID("OuterContainer"), + .backgroundColor = COLOR_WHITE, + .layout = { + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .sizing = layoutExpand, + .padding = CLAY_PADDING_ALL(8), + .childGap = 4 + } + }) { + // Child elements go inside braces + CLAY({ + .id = CLAY_ID("HeaderBar"), + .layout = { + .sizing = { + .height = CLAY_SIZING_FIXED(30), + .width = CLAY_SIZING_GROW(0) + }, + .childGap = 8, + .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } + }, + }) { + // Header buttons go here + CLAY({ + .id = CLAY_ID("FileButton"), + .layout = { + .padding = { 8, 8, 4, 4 } + }, + .backgroundColor = COLOR_BLACK, + .cornerRadius = CLAY_CORNER_RADIUS(4) + }) { + CLAY_TEXT( + CLAY_STRING("File"), + CLAY_TEXT_CONFIG({ + .fontId = FONT_ID_BUTTON, + .textColor = COLOR_WHITE + }) + ); + } + RenderHeaderButton(CLAY_STRING("Edit")); + CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {} + RenderHeaderButton(CLAY_STRING("Upload")); + RenderHeaderButton(CLAY_STRING("Media")); + RenderHeaderButton(CLAY_STRING("Support")); + } + + CLAY({ + .id = CLAY_ID("LowerContent"), + .layout = { .sizing = layoutExpand, .childGap = 8 }, + }) { + CLAY({ + .id = CLAY_ID("Sidebar"), + .border = contentBorders, + .cornerRadius = CLAY_CORNER_RADIUS(4), + .layout = { + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .padding = CLAY_PADDING_ALL(8), + .childGap = 4, + .sizing = { + .width = CLAY_SIZING_FIXED(125), + .height = CLAY_SIZING_GROW(0) + } + } + }) { + for (int i = 0; i < documents.length; i++) { + Document document = documents.documents[i]; + Clay_LayoutConfig sidebarButtonLayout = { + .sizing = { .width = CLAY_SIZING_GROW(0) }, + .padding = CLAY_PADDING_ALL(8) + }; + + if (i == selectedDocumentIndex) { + CLAY({ + .layout = sidebarButtonLayout, + .backgroundColor = COLOR_BLACK, + .cornerRadius = CLAY_CORNER_RADIUS(4) + }) { + CLAY_TEXT( + document.title, + CLAY_TEXT_CONFIG({ + .fontId = FONT_ID_BUTTON, + .textColor = COLOR_WHITE + }) + ); + } + } else { + CLAY({ + .layout = sidebarButtonLayout, + .backgroundColor = (Clay_Color){ 0, 0, 0, Clay_Hovered() ? 120 : 0 }, + .cornerRadius = CLAY_CORNER_RADIUS(4), + .border = contentBorders + }) { + CLAY_TEXT( + document.title, + CLAY_TEXT_CONFIG({ + .fontId = FONT_ID_BUTTON, + .textColor = COLOR_BLACK, + }) + ); + } + } + } + } + + CLAY({ + .id = CLAY_ID("MainContent"), + .border = contentBorders, + .cornerRadius = CLAY_CORNER_RADIUS(4), + .clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() }, + .layout = { + .layoutDirection = CLAY_TOP_TO_BOTTOM, + .childGap = 8, + .padding = CLAY_PADDING_ALL(8), + .sizing = layoutExpand + } + }) { + Document selectedDocument = documents.documents[selectedDocumentIndex]; + CLAY({ + .layout = { + .layoutDirection = CLAY_LEFT_TO_RIGHT, + .childGap = 4, + .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_BOTTOM } + } + }) { + CLAY_TEXT( + selectedDocument.title, + CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK }) + ); + CLAY({ + .layout = { + .sizing = { + .width = CLAY_SIZING_FIXED(32), + .height = CLAY_SIZING_FIXED(30) + } + }, + .image = { .imageData = selectedDocument.image, .sourceDimensions = { 32, 30 } } + }) {} + } + CLAY_TEXT( + selectedDocument.contents, + CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK }) + ); + } + } + } + + Clay_RenderCommandArray renderCommands = Clay_EndLayout(); + for (int32_t i = 0; i < renderCommands.length; i++) { + Clay_RenderCommandArray_Get(&renderCommands, i); + } + return renderCommands; +} diff --git a/examples/playdate-project-example/main.c b/examples/playdate-project-example/main.c new file mode 100644 index 0000000..1aaaaa3 --- /dev/null +++ b/examples/playdate-project-example/main.c @@ -0,0 +1,115 @@ +#include "pd_api.h" +#define CLAY_IMPLEMENTATION +#include "../../clay.h" + +#include "../../renderers/playdate/clay_renderer_playdate.c" +#include "clay-video-demo-playdate.c" + +static int update(void *userdata); + +#define NUM_FONTS 2 +const char *fontsToLoad[NUM_FONTS] = { + "/System/Fonts/Asheville-Sans-14-Bold.pft", + "/System/Fonts/Roobert-10-Bold.pft" +}; + +void HandleClayErrors(Clay_ErrorData errorData) {} + +struct TextUserData { + LCDFont *font[NUM_FONTS]; + PlaydateAPI *pd; +}; + +static struct TextUserData textUserData = { .font = { NULL }, .pd = NULL }; + +static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { + struct TextUserData *textUserData = userData; + int width = textUserData->pd->graphics->getTextWidth( + textUserData->font[config->fontId], + text.chars, + Clay_Playdate_CountUtf8Codepoints(text.chars, text.length), + kUTF8Encoding, + 0 + ); + int height = textUserData->pd->graphics->getFontHeight(textUserData->font[config->fontId]); + return (Clay_Dimensions){ + .width = (float)width, + .height = (float)height, + }; +} + +#ifdef _WINDLL +__declspec(dllexport) +#endif +int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t eventArg) { + if (event == kEventInit) { + const char *err; + for (int i = 0; i < NUM_FONTS; ++i) { + + textUserData.font[i] = pd->graphics->loadFont(fontsToLoad[i], &err); + if (textUserData.font[i] == NULL) { + pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontsToLoad[i], err); + } + } + + textUserData.pd = pd; + pd->system->setUpdateCallback(update, pd); + + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory( + totalMemorySize, + pd->system->realloc(NULL, totalMemorySize) + ); + Clay_Initialize( + clayMemory, + (Clay_Dimensions){ + (float)pd->display->getWidth(), + (float)pd->display->getHeight() + }, + (Clay_ErrorHandler){HandleClayErrors} + ); + Clay_SetMeasureTextFunction(PlayDate_MeasureText, &textUserData); + ClayVideoDemoPlaydate_Initialize(pd); + } + + return 0; +} + +int selectedDocumentIndex = 0; +#define WRAP_RANGE(x, N) ((((x) % (N)) + (N)) % (N)) + +static int update(void *userdata) { + PlaydateAPI *pd = userdata; + PDButtons pushedButtons; + pd->system->getButtonState(NULL, &pushedButtons, NULL); + + if (pushedButtons & kButtonDown) { + selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex + 1, MAX_DOCUMENTS); + } else if (pushedButtons & kButtonUp) { + selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex - 1, MAX_DOCUMENTS); + } + + pd->graphics->clear(kColorWhite); + + // A bit hacky, setting the cursor on to the document view so it can be + // scrolled.. + Clay_SetPointerState( + (Clay_Vector2){ + .x = pd->display->getWidth() / 2.0f, + .y = pd->display->getHeight() / 2.0f + }, + false + ); + + float crankDelta = pd->system->getCrankChange(); + Clay_UpdateScrollContainers( + false, + (Clay_Vector2){ 0, -crankDelta * 0.25f }, + pd->system->getElapsedTime() + ); + + Clay_RenderCommandArray renderCommands = ClayVideoDemoPlaydate_CreateLayout(selectedDocumentIndex); + Clay_Playdate_Render(pd, renderCommands, textUserData.font); + + return 1; +} 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/examples/terminal-example/CMakeLists.txt b/examples/terminal-example/CMakeLists.txt new file mode 100644 index 0000000..2976e50 --- /dev/null +++ b/examples/terminal-example/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.27) +project(clay_examples_terminal C) +set(CMAKE_C_STANDARD 99) + +add_executable(clay_examples_terminal main.c) + +target_compile_options(clay_examples_terminal PUBLIC) +target_include_directories(clay_examples_terminal PUBLIC .) +if (CMAKE_SYSTEM_NAME STREQUAL Linux) + target_link_libraries(clay_examples_terminal INTERFACE m) +endif() + +target_link_libraries(clay_examples_terminal PUBLIC ${CURSES_LIBRARY}) +set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG") +set(CMAKE_CXX_FLAGS_RELEASE "-O3") \ No newline at end of file diff --git a/examples/terminal-example/main.c b/examples/terminal-example/main.c new file mode 100644 index 0000000..de666e9 --- /dev/null +++ b/examples/terminal-example/main.c @@ -0,0 +1,39 @@ +// Must be defined in one file, _before_ #include "clay.h" +#define CLAY_IMPLEMENTATION + +#include +#include "../../clay.h" +#include "../../renderers/terminal/clay_renderer_terminal_ansi.c" +#include "../shared-layouts/clay-video-demo.c" + +const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255}; +const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255}; +const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255}; + +void HandleClayErrors(Clay_ErrorData errorData) { + printf("%s", errorData.errorText.chars); +} + +int main() { + const int width = 145; + const int height = 41; + int columnWidth = 16; + + uint64_t totalMemorySize = Clay_MinMemorySize(); + Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize)); + Clay_Initialize(arena, + (Clay_Dimensions) {.width = (float) width * columnWidth, .height = (float) height * columnWidth}, + (Clay_ErrorHandler) {HandleClayErrors}); + // Tell clay how to measure text + Clay_SetMeasureTextFunction(Console_MeasureText, &columnWidth); + ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize(); + + while (true) { + Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData); + + Clay_Terminal_Render(renderCommands, width, height, columnWidth); + + fflush(stdout); + sleep(1); + } +} \ No newline at end of file diff --git a/renderers/cairo/clay_renderer_cairo.c b/renderers/cairo/clay_renderer_cairo.c index 229ca4f..15696ad 100644 --- a/renderers/cairo/clay_renderer_cairo.c +++ b/renderers/cairo/clay_renderer_cairo.c @@ -75,7 +75,7 @@ static inline char *Clay_Cairo__NullTerminate(Clay_String *str) { } // Measure text using cairo's *toy* text API. -static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, uintptr_t userData) { +static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, void *userData) { // Edge case: Clay computes the width of a whitespace character // once. Cairo does not factor in whitespaces when computing text // extents, this edge-case serves as a short-circuit to introduce @@ -95,7 +95,7 @@ static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_StringSlice str, Clay_ } // Ensure string is null-terminated for Cairo - Clay_String toTerminate = (Clay_String){ str.length, str.chars }; + Clay_String toTerminate = (Clay_String){ .chars = str.chars, .length = str.length, .isStaticallyAllocated = false }; char *text = Clay_Cairo__NullTerminate(&toTerminate); char *font_family = fonts[config->fontId]; @@ -220,7 +220,7 @@ void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts) { // Cairo expects null terminated strings, we need to clone // to temporarily introduce one. Clay_TextRenderData *config = &command->renderData.text; - Clay_String toTerminate = (Clay_String){ config->stringContents.length, config->stringContents.chars }; + Clay_String toTerminate = (Clay_String){ .chars = config->stringContents.chars, .length = config->stringContents.length, .isStaticallyAllocated = false }; char *text = Clay_Cairo__NullTerminate(&toTerminate); char *font_family = fonts[config->fontId]; diff --git a/renderers/playdate/clay_renderer_playdate.c b/renderers/playdate/clay_renderer_playdate.c new file mode 100644 index 0000000..e6c5c85 --- /dev/null +++ b/renderers/playdate/clay_renderer_playdate.c @@ -0,0 +1,273 @@ +#include "pd_api.h" +#include "../../clay.h" + +// Playdate drawText function expects the number of codepoints to draw, not byte length +static size_t Clay_Playdate_CountUtf8Codepoints(const char *str, size_t byteLen) { + size_t count = 0; + size_t i = 0; + while (i < byteLen) { + uint8_t c = (uint8_t)str[i]; + if ((c & 0xC0) != 0x80) { + count++; + } + i++; + } + return count; +} + +// As the playdate can only display black and white, we need to resolve Clay_color to either black or white +// for both color and draw mode. +static LCDColor clayColorToLCDColor(Clay_Color color) { + if (color.r > 0 || color.g > 0 || color.b > 0) { + return kColorWhite; + } + return kColorBlack; +} + +static LCDBitmapDrawMode clayColorToDrawMode(Clay_Color color) { + if (color.r > 0 || color.g > 0 || color.b > 0) { + return kDrawModeFillWhite; + } + return kDrawModeCopy; +} + +static float clampCornerRadius(float yAxisSize, float radius) { + if (radius < 1.0f) { + return 0.0f; + } + if (radius > yAxisSize / 2) { + return yAxisSize / 2; + } + // Trying to draw a 2x2 ellipse seems to result in just a dot, so if + // there is a corner radius at minimum it must be 2 + return CLAY__MAX(2, radius); +} + +static void Clay_Playdate_Render(PlaydateAPI *pd, Clay_RenderCommandArray renderCommands, LCDFont **fonts) { + for (uint32_t i = 0; i < renderCommands.length; i++) { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i); + Clay_BoundingBox boundingBox = renderCommand->boundingBox; + + switch (renderCommand->commandType) { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle; + + float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); + float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); + float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); + float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); + + pd->graphics->fillEllipse( + boundingBox.x, boundingBox.y, + radiusTl * 2, radiusTl * 2, + -90.0f, 0.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + pd->graphics->fillEllipse( + boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, + radiusTr * 2, radiusTr * 2, + 0.0f, 90.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + pd->graphics->fillEllipse( + boundingBox.x + boundingBox.width - radiusBr * 2, + boundingBox.y + boundingBox.height - radiusBr * 2, + radiusBr * 2, radiusBr * 2, + 90.0f, 180.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + pd->graphics->fillEllipse( + boundingBox.x, + boundingBox.y + boundingBox.height - radiusBl * 2, + radiusBl * 2, radiusBl * 2, + 180.0f, 270.0f, + clayColorToLCDColor(config->backgroundColor) + ); + + // Top chunk + pd->graphics->fillRect( + boundingBox.x + radiusTl, boundingBox.y, + boundingBox.width - radiusTl - radiusTr, + CLAY__MAX(radiusTl, radiusTr), + clayColorToLCDColor(config->backgroundColor) + ); + + // bottom chunk + int bottomChunkHeight = CLAY__MAX(radiusBl, radiusBr); + pd->graphics->fillRect( + boundingBox.x + radiusBl, boundingBox.y + boundingBox.height - bottomChunkHeight, + boundingBox.width - radiusBl - radiusBr, + bottomChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + + // Middle chunk + int middleChunkHeight = boundingBox.height - CLAY__MIN(radiusBr, radiusBl) - CLAY__MIN(radiusTr, radiusTl); + pd->graphics->fillRect( + boundingBox.x + CLAY__MIN(radiusTl, radiusBl), boundingBox.y + CLAY__MIN(radiusTr, radiusTl), + boundingBox.width - radiusBl - radiusBr, + middleChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + + // Left chunk + int leftChunkHeight = boundingBox.height - radiusTl - radiusBl; + int leftChunkWidth = CLAY__MAX(radiusTl, radiusBl); + pd->graphics->fillRect( + boundingBox.x, boundingBox.y + radiusTl, + leftChunkWidth, + leftChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + + // Right chunk + int rightChunkHeight = boundingBox.height - radiusTr - radiusBr; + int rightChunkWidth = CLAY__MAX(radiusTr, radiusBr); + pd->graphics->fillRect( + boundingBox.x + boundingBox.width - rightChunkWidth, boundingBox.y + radiusTr, + rightChunkWidth, + rightChunkHeight, + clayColorToLCDColor(config->backgroundColor) + ); + break; + } + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextRenderData *config = &renderCommand->renderData.text; + LCDFont *font = fonts[config->fontId]; + pd->graphics->setFont(font); + pd->graphics->setDrawMode(clayColorToDrawMode(config->textColor)); + pd->graphics->drawText( + renderCommand->renderData.text.stringContents.chars, + Clay_Playdate_CountUtf8Codepoints( + renderCommand->renderData.text.stringContents.chars, + renderCommand->renderData.text.stringContents.length + ), + kUTF8Encoding, + boundingBox.x, + boundingBox.y + ); + pd->graphics->setDrawMode(kDrawModeCopy); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + pd->graphics->setClipRect( + boundingBox.x,boundingBox.y, + boundingBox.width, boundingBox.height + ); + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + pd->graphics->clearClipRect(); + break; + } + case CLAY_RENDER_COMMAND_TYPE_IMAGE: { + Clay_ImageRenderData *config = &renderCommand->renderData.image; + LCDBitmap *texture = config->imageData; + int texWidth; + int texHeight; + pd->graphics->getBitmapData(texture, &texWidth, &texHeight, NULL, NULL, NULL); + if (texWidth != boundingBox.width || texHeight != boundingBox.height) { + pd->graphics->drawScaledBitmap( + texture, + boundingBox.x, boundingBox.y, + boundingBox.width / texWidth, + boundingBox.height / texHeight + ); + } else { + pd->graphics->drawBitmap(texture, boundingBox.x, boundingBox.y, kBitmapUnflipped); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderRenderData *config = &renderCommand->renderData.border; + + float radiusTl = clampCornerRadius(boundingBox.height, config->cornerRadius.topLeft); + float radiusTr = clampCornerRadius(boundingBox.height, config->cornerRadius.topRight); + float radiusBl = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomLeft); + float radiusBr = clampCornerRadius(boundingBox.height, config->cornerRadius.bottomRight); + + if (config->width.top > 0) { + pd->graphics->drawEllipse( + boundingBox.x, boundingBox.y, + radiusTl * 2, radiusTl * 2, + config->width.top, + -90.0f, 0.0f, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawLine( + boundingBox.x + radiusTl, boundingBox.y, + boundingBox.x + boundingBox.width - radiusTr - config->width.right, boundingBox.y, + config->width.top, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawEllipse( + boundingBox.x + boundingBox.width - radiusTr * 2, boundingBox.y, + radiusTr * 2, radiusTr * 2, + config->width.top, + 0.0f, 90.0f, + clayColorToLCDColor(config->color) + ); + } + + if (config->width.right > 0 && radiusTr + radiusBr <= boundingBox.height) { + pd->graphics->drawLine( + boundingBox.x + boundingBox.width - config->width.right, + boundingBox.y + radiusTr, + boundingBox.x + boundingBox.width - config->width.right, + boundingBox.y + boundingBox.height - radiusBr - config->width.bottom, + config->width.right, + clayColorToLCDColor(config->color) + ); + } + + if (config->width.bottom > 0) { + pd->graphics->drawEllipse( + boundingBox.x + boundingBox.width - radiusBr * 2, + boundingBox.y + boundingBox.height - radiusBr * 2, + radiusBr * 2, radiusBr * 2, + config->width.bottom, + 90.0f, 180.0f, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawLine( + boundingBox.x + boundingBox.width - radiusBr - config->width.right, + boundingBox.y + boundingBox.height - config->width.bottom, + boundingBox.x + radiusBl, + boundingBox.y + boundingBox.height - config->width.bottom, + config->width.bottom, + clayColorToLCDColor(config->color) + ); + + pd->graphics->drawEllipse( + boundingBox.x, + boundingBox.y + boundingBox.height - radiusBl * 2, + radiusBl * 2, radiusBl * 2, + config->width.bottom, + 180.0f, 270.0f, + clayColorToLCDColor(config->color) + ); + } + + if (config->width.left > 0 && radiusBl + radiusTl < boundingBox.height) { + pd->graphics->drawLine( + boundingBox.x, boundingBox.y + boundingBox.height - radiusBl - config->width.bottom, + boundingBox.x, boundingBox.y + radiusTl, + config->width.left, + clayColorToLCDColor(config->color) + ); + } + break; + } + default: { + pd->system->logToConsole("Error: unhandled render command: %d\n", renderCommand->commandType); + return; + } + } + } +} 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; } diff --git a/renderers/terminal/clay_renderer_terminal_ansi.c b/renderers/terminal/clay_renderer_terminal_ansi.c new file mode 100644 index 0000000..d73f735 --- /dev/null +++ b/renderers/terminal/clay_renderer_terminal_ansi.c @@ -0,0 +1,194 @@ +#include "stdint.h" +#include "string.h" +#include "stdio.h" +#include "stdlib.h" + +#ifdef CLAY_OVERFLOW_TRAP +#include "signal.h" +#endif + +static inline void Console_MoveCursor(int x, int y) { + printf("\033[%d;%dH", y + 1, x + 1); +} + +bool Clay_PointIsInsideRect(Clay_Vector2 point, Clay_BoundingBox rect) { + // TODO this function is a copy of Clay__PointIsInsideRect but that one is internal, I don't know if we want + // TODO to expose Clay__PointIsInsideRect + return point.x >= rect.x && point.x < rect.x + rect.width && point.y >= rect.y && point.y < rect.y + rect.height; +} + +static inline void Console_DrawRectangle(int x0, int y0, int width, int height, Clay_Color color, + Clay_BoundingBox scissorBox) { + float average = (color.r + color.g + color.b + color.a) / 4 / 255; + + for (int y = y0; y < height + y0; y++) { + for (int x = x0; x < width + x0; x++) { + if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = x, .y = y}, scissorBox)) { + continue; + } + + Console_MoveCursor(x, y); + // TODO this should be replaced by a better logarithmic scale if we're doing black and white + if (average > 0.75) { + printf("█"); + } else if (average > 0.5) { + printf("▓"); + } else if (average > 0.25) { + printf("▒"); + } else { + printf("░"); + } + } + } +} + +static inline Clay_Dimensions +Console_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { + Clay_Dimensions textSize = {0}; + int columnWidth = *(int *) userData; + + // TODO this function is very wrong, it measures in characters, I have no idea what is the size in pixels + + float maxTextWidth = 0.0f; + float lineTextWidth = 0; + + float textHeight = 1; + + for (int i = 0; i < text.length; ++i) { + if (text.chars[i] == '\n') { + maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; + lineTextWidth = 0; + textHeight++; + continue; + } + lineTextWidth++; + } + + maxTextWidth = maxTextWidth > lineTextWidth ? maxTextWidth : lineTextWidth; + + textSize.width = maxTextWidth * columnWidth; + textSize.height = textHeight * columnWidth; + + return textSize; +} + +void Clay_Terminal_Render(Clay_RenderCommandArray renderCommands, int width, int height, int columnWidth) { + printf("\033[H\033[J"); // Clear + + const Clay_BoundingBox fullWindow = { + .x = 0, + .y = 0, + .width = (float) width, + .height = (float) height, + }; + + Clay_BoundingBox scissorBox = fullWindow; + + for (int j = 0; j < renderCommands.length; j++) { + Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); + Clay_BoundingBox boundingBox = (Clay_BoundingBox) { + .x = (int)((renderCommand->boundingBox.x / columnWidth) + 0.5), + .y = (int)((renderCommand->boundingBox.y / columnWidth) + 0.5), + .width = (int)((renderCommand->boundingBox.width / columnWidth) + 0.5), + .height = (int)((renderCommand->boundingBox.height / columnWidth) + 0.5), + }; + switch (renderCommand->commandType) { + case CLAY_RENDER_COMMAND_TYPE_TEXT: { + Clay_TextRenderData data = renderCommand->renderData.text; + Clay_StringSlice text = data.stringContents; + int y = 0; + for (int x = 0; x < text.length; x++) { + if (text.chars[x] == '\n') { + y++; + continue; + } + + int cursorX = (int) boundingBox.x + x; + int cursorY = (int) boundingBox.y + y; + if (cursorY > scissorBox.y + scissorBox.height) { + break; + } + if (!Clay_PointIsInsideRect((Clay_Vector2) {.x = cursorX, .y = cursorY}, scissorBox)) { + continue; + } + + Console_MoveCursor(cursorX, cursorY); + printf("%c", text.chars[x]); + } + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { + scissorBox = boundingBox; + break; + } + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { + scissorBox = fullWindow; + break; + } + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { + Clay_RectangleRenderData data = renderCommand->renderData.rectangle; + Console_DrawRectangle( + (int) boundingBox.x, + (int) boundingBox.y, + (int) boundingBox.width, + (int) boundingBox.height, + data.backgroundColor, + scissorBox); + break; + } + case CLAY_RENDER_COMMAND_TYPE_BORDER: { + Clay_BorderRenderData data = renderCommand->renderData.border; + // Left border + if (data.width.left > 0) { + Console_DrawRectangle( + (int) (boundingBox.x), + (int) (boundingBox.y + data.cornerRadius.topLeft), + (int) data.width.left, + (int) (boundingBox.height - data.cornerRadius.topLeft - data.cornerRadius.bottomLeft), + data.color, + scissorBox); + } + // Right border + if (data.width.right > 0) { + Console_DrawRectangle( + (int) (boundingBox.x + boundingBox.width - data.width.right), + (int) (boundingBox.y + data.cornerRadius.topRight), + (int) data.width.right, + (int) (boundingBox.height - data.cornerRadius.topRight - data.cornerRadius.bottomRight), + data.color, + scissorBox); + } + // Top border + if (data.width.top > 0) { + Console_DrawRectangle( + (int) (boundingBox.x + data.cornerRadius.topLeft), + (int) (boundingBox.y), + (int) (boundingBox.width - data.cornerRadius.topLeft - data.cornerRadius.topRight), + (int) data.width.top, + data.color, + scissorBox); + } + // Bottom border + if (data.width.bottom > 0) { + Console_DrawRectangle( + (int) (boundingBox.x + data.cornerRadius.bottomLeft), + (int) (boundingBox.y + boundingBox.height - data.width.bottom), + (int) (boundingBox.width - data.cornerRadius.bottomLeft - data.cornerRadius.bottomRight), + (int) data.width.bottom, + data.color, + scissorBox); + } + break; + } + default: { + printf("Error: unhandled render command."); +#ifdef CLAY_OVERFLOW_TRAP + raise(SIGTRAP); +#endif + exit(1); + } + } + } + + Console_MoveCursor(-1, -1); // TODO make the user not be able to write +}