Merge branch 'main' into feature/terminal-renderer

This commit is contained in:
Phillip Cook 2025-01-05 21:40:03 -05:00
commit 286c0753fd
49 changed files with 2150 additions and 710 deletions

1
.gitignore vendored
View File

@ -2,6 +2,5 @@ cmake-build-debug/
cmake-build-release/
.DS_Store
.idea/
build/
node_modules/
*.dSYM

View File

@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.27)
project(clay)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_subdirectory("examples/cpp-project-example")
# Don't try to compile C99 projects using MSVC
@ -10,4 +12,5 @@ if(NOT MSVC)
# add_subdirectory("examples/cairo-pdf-rendering")
add_subdirectory("examples/clay-official-website")
add_subdirectory("examples/introducing-clay-video-demo")
add_subdirectory("examples/SDL2-video-demo")
endif()

View File

@ -245,7 +245,7 @@ This ID (or, if not provided, an auto generated ID) will be forwarded to the fin
Clay provides several functions for handling mouse and pointer interactions.
All pointer interactions depend on the function `void Clay_SetPointerState(Clay_Vector2 position)` being called after each mouse position update and before any other clay functions.
All pointer interactions depend on the function `void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown)` being called after each mouse position update and before any other clay functions.
**During UI declaration**

View File

@ -18,7 +18,7 @@ when ODIN_OS == .Windows {
}
String :: struct {
length: c.int,
length: c.int32_t,
chars: [^]c.char,
}
@ -30,9 +30,8 @@ Dimensions :: struct {
}
Arena :: struct {
label: String,
nextAllocation: u64,
capacity: u64,
nextAllocation: uintptr,
capacity: uintptr,
memory: [^]c.char,
}
@ -259,8 +258,8 @@ LayoutConfig :: struct {
}
ClayArray :: struct($type: typeid) {
capacity: u32,
length: u32,
capacity: i32,
length: i32,
internalArray: [^]type,
}
@ -270,12 +269,33 @@ TypedConfig :: struct {
id: ElementId,
}
ErrorType :: enum {
TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED,
ARENA_CAPACITY_EXCEEDED,
ELEMENTS_CAPACITY_EXCEEDED,
TEXT_MEASUREMENT_CAPACITY_EXCEEDED,
DUPLICATE_ID,
FLOATING_CONTAINER_PARENT_NOT_FOUND,
INTERNAL_ERROR,
}
ErrorData :: struct {
errorType: ErrorType,
errorText: String,
userData: rawptr
}
ErrorHandler :: struct {
handler: proc "c" (errorData: ErrorData),
userData: rawptr
}
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) ---
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -87,7 +87,7 @@ LandingPageDesktop :: proc() {
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 56, fontId = FONT_ID_TITLE_56, textColor = COLOR_RED}),
)
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
// if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
clay.Text(
"Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}),
@ -125,7 +125,7 @@ LandingPageMobile :: proc() {
"Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance.",
clay.TextConfig({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}),
)
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(32)}})) {}
clay.Text(
"Clay is laying out this webpage right now!",
clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}),
@ -207,7 +207,7 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
}
if clay.UI(clay.ID("SyntaxPageRightImage"), clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) {
if clay.UI(
clay.ID("SyntaxPageRightImage"),
clay.ID("SyntaxPageRightImageInner"),
clay.Layout({sizing = {width = clay.SizingGrow({max = 568})}}),
clay.Image({imageData = &syntaxImage, sourceDimensions = {1136, 1194}}),
) {}
@ -255,7 +255,7 @@ LOREM_IPSUM_TEXT := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, se
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("PerformanceLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
clay.Text("High Performance", clay.TextConfig(titleTextConfig))
if clay.UI(clay.ID("SyntaxSpacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
clay.Text(
"Fast enough to recompute your entire UI every frame.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
@ -347,7 +347,7 @@ RendererButtonInactive :: proc(index: u32, text: string) {
RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("RendererLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
clay.Text("Renderer & Platform Agnostic", clay.TextConfig(titleTextConfig))
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({max = 16})}})) {}
clay.Text(
"Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
@ -366,7 +366,7 @@ RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.
clay.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}, layoutDirection = .TOP_TO_BOTTOM, childGap = 16}),
) {
clay.Text("Try changing renderer!", clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE}))
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({max = 32})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({max = 32})}})) {}
RendererButtonActive(0, "Raylib Renderer")
}
}
@ -426,7 +426,7 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(50)}, childAlignment = {y = .CENTER}, childGap = 24, padding = {x = 32}}),
) {
clay.Text("Clay", &headerTextConfig)
if clay.UI(clay.ID("Spacer"), clay.Layout({sizing = {width = clay.SizingGrow({})}})) {}
if clay.UI(clay.Layout({sizing = {width = clay.SizingGrow({})}})) {}
if (!mobileScreen) {
if clay.UI(clay.ID("LinkExamplesOuter"), clay.Layout({}), clay.Rectangle({color = {0, 0, 0, 0}})) {
@ -483,12 +483,18 @@ loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) {
raylib.SetTextureFilter(raylibFonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR)
}
errorHandler :: proc "c" (errorData: clay.ErrorData) {
if (errorData.errorType == clay.ErrorType.DUPLICATE_ID) {
}
}
main :: proc() {
minMemorySize: u32 = clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.SetMeasureTextFunction(measureText)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()})
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")

1327
clay.h

File diff suppressed because it is too large Load Diff

32
cmake/FindCairo.cmake Normal file
View File

@ -0,0 +1,32 @@
# Defines:
# CAIRO_FOUND - System has Cairo
# CAIRO_INCLUDE_DIRS - Cairo include directories
# CAIRO_LIBRARY - Cairo library
# Cairo::Cairo - Imported target
find_path(CAIRO_INCLUDE_DIRS
NAMES cairo/cairo.h
PATHS ${CAIRO_ROOT_DIR}
PATH_SUFFIXES include
)
find_library(CAIRO_LIBRARY
NAMES cairo
PATHS ${CAIRO_ROOT_DIR}
PATH_SUFFIXES lib lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Cairo
REQUIRED_VARS CAIRO_LIBRARY CAIRO_INCLUDE_DIRS
)
if(Cairo_FOUND AND NOT TARGET Cairo::Cairo)
add_library(Cairo::Cairo UNKNOWN IMPORTED)
set_target_properties(Cairo::Cairo PROPERTIES
IMPORTED_LOCATION "${CAIRO_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${CAIRO_INCLUDE_DIRS}"
)
endif()
mark_as_advanced(CAIRO_INCLUDE_DIRS CAIRO_LIBRARY)

View File

@ -0,0 +1,44 @@
cmake_minimum_required(VERSION 3.27)
project(SDL2_video_demo C)
set(CMAKE_C_STANDARD 99)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
SDL2
GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git"
GIT_TAG "release-2.30.10"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2)
FetchContent_Declare(
SDL2_ttf
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_ttf.git"
GIT_TAG "release-2.22.0"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2_ttf)
add_executable(SDL2_video_demo main.c)
target_compile_options(SDL2_video_demo PUBLIC)
target_include_directories(SDL2_video_demo PUBLIC .)
target_link_libraries(SDL2_video_demo PUBLIC
SDL2::SDL2main
SDL2::SDL2-static
SDL2_ttf::SDL2_ttf-static
)
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3")
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)

View File

@ -0,0 +1,360 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/SDL2/clay_renderer_SDL2.c"
#include <SDL.h>
#include <SDL_ttf.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
const int FONT_ID_BODY_16 = 0;
Clay_Color COLOR_WHITE = { 255, 255, 255, 255};
void RenderHeaderButton(Clay_String text) {
CLAY(
CLAY_LAYOUT({ .padding = { 16, 8 }}),
CLAY_RECTANGLE({
.color = { 140, 140, 140, 255 },
.cornerRadius = 5
})
) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
}
}
void RenderDropdownMenuItem(Clay_String text) {
CLAY(CLAY_LAYOUT({ .padding = { 16, 16 }})) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
}
}
typedef struct {
Clay_String title;
Clay_String contents;
} Document;
typedef struct {
Document *documents;
uint32_t length;
} DocumentArray;
DocumentArray documents = {
.documents = NULL,
.length = 5
};
uint32_t selectedDocumentIndex = 0;
void HandleSidebarInteraction(
Clay_ElementId elementId,
Clay_PointerData pointerData,
intptr_t userData
) {
// If this button was clicked
if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
if (userData >= 0 && userData < documents.length) {
// Select the corresponding document
selectedDocumentIndex = userData;
}
}
}
static Clay_RenderCommandArray CreateLayout() {
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
};
Clay_RectangleElementConfig contentBackgroundConfig = {
.color = { 90, 90, 90, 255 },
.cornerRadius = 8
};
Clay_BeginLayout();
// Build UI here
CLAY(
CLAY_ID("OuterContainer"),
CLAY_RECTANGLE({ .color = { 43, 41, 51, 255 } }),
CLAY_LAYOUT({
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = { 16, 16 },
.childGap = 16
})
) {
// Child elements go inside braces
CLAY(
CLAY_ID("HeaderBar"),
CLAY_RECTANGLE(contentBackgroundConfig),
CLAY_LAYOUT({
.sizing = {
.height = CLAY_SIZING_FIXED(60),
.width = CLAY_SIZING_GROW()
},
.padding = { 16 },
.childGap = 16,
.childAlignment = {
.y = CLAY_ALIGN_Y_CENTER
}
})
) {
// Header buttons go here
CLAY(
CLAY_ID("FileButton"),
CLAY_LAYOUT({ .padding = { 16, 8 }}),
CLAY_RECTANGLE({
.color = { 140, 140, 140, 255 },
.cornerRadius = 5
})
) {
CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = { 255, 255, 255, 255 }
}));
bool fileMenuVisible =
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileButton")))
||
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileMenu")));
if (fileMenuVisible) { // Below has been changed slightly to fix the small bug where the menu would dismiss when mousing over the top gap
CLAY(
CLAY_ID("FileMenu"),
CLAY_FLOATING({
.attachment = {
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
},
}),
CLAY_LAYOUT({
.padding = {0, 8 }
})
) {
CLAY(
CLAY_LAYOUT({
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIXED(200)
},
}),
CLAY_RECTANGLE({
.color = { 40, 40, 40, 255 },
.cornerRadius = 8
})
) {
// Render dropdown items here
RenderDropdownMenuItem(CLAY_STRING("New"));
RenderDropdownMenuItem(CLAY_STRING("Open"));
RenderDropdownMenuItem(CLAY_STRING("Close"));
}
}
}
}
RenderHeaderButton(CLAY_STRING("Edit"));
CLAY(CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW() }})) {}
RenderHeaderButton(CLAY_STRING("Upload"));
RenderHeaderButton(CLAY_STRING("Media"));
RenderHeaderButton(CLAY_STRING("Support"));
}
CLAY(
CLAY_ID("LowerContent"),
CLAY_LAYOUT({ .sizing = layoutExpand, .childGap = 16 })
) {
CLAY(
CLAY_ID("Sidebar"),
CLAY_RECTANGLE(contentBackgroundConfig),
CLAY_LAYOUT({
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.padding = { 16, 16 },
.childGap = 8,
.sizing = {
.width = CLAY_SIZING_FIXED(250),
.height = CLAY_SIZING_GROW()
}
})
) {
for (int i = 0; i < documents.length; i++) {
Document document = documents.documents[i];
Clay_LayoutConfig sidebarButtonLayout = {
.sizing = { .width = CLAY_SIZING_GROW() },
.padding = { 16, 16 }
};
if (i == selectedDocumentIndex) {
CLAY(
CLAY_LAYOUT(sidebarButtonLayout),
CLAY_RECTANGLE({
.color = { 120, 120, 120, 255 },
.cornerRadius = 8,
})
) {
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
} else {
CLAY(
CLAY_LAYOUT(sidebarButtonLayout),
Clay_OnHover(HandleSidebarInteraction, i),
Clay_Hovered()
? CLAY_RECTANGLE({
.color = { 120, 120, 120, 120 },
.cornerRadius = 8
})
: 0
) {
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
}
}
}
CLAY(
CLAY_ID("MainContent"),
CLAY_RECTANGLE(contentBackgroundConfig),
CLAY_SCROLL({ .vertical = true }),
CLAY_LAYOUT({
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 16,
.padding = { 16, 16 },
.sizing = layoutExpand
})
) {
Document selectedDocument = documents.documents[selectedDocumentIndex];
CLAY_TEXT(selectedDocument.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 24,
.textColor = COLOR_WHITE
}));
CLAY_TEXT(selectedDocument.contents, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 24,
.textColor = COLOR_WHITE
}));
}
}
}
return Clay_EndLayout();
}
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(void) {
documents.documents = (Document[]) {
{ .title = CLAY_STRING("Squirrels"), .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") },
{ .title = CLAY_STRING("Lorem Ipsum"), .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.") },
{ .title = CLAY_STRING("Vacuum Instructions"), .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.") },
{ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") },
{ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") },
};
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError());
return 1;
}
if (TTF_Init() < 0) {
fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError());
return 1;
}
TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 16);
if (!font) {
fprintf(stderr, "Error: could not load font: %s\n", TTF_GetError());
return 1;
}
SDL2_fonts[FONT_ID_BODY_16] = (SDL2_Font) {
.fontId = FONT_ID_BODY_16,
.font = font,
};
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer) < 0) {
fprintf(stderr, "Error: could not create window and renderer: %s", SDL_GetError());
}
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_SetMeasureTextFunction(SDL2_MeasureText);
int windowWidth = 0;
int windowHeight = 0;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors });
Uint64 NOW = SDL_GetPerformanceCounter();
Uint64 LAST = 0;
double deltaTime = 0;
while (true) {
Clay_Vector2 scrollDelta = {};
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT: { goto quit; }
case SDL_MOUSEWHEEL: {
scrollDelta.x = event.wheel.x;
scrollDelta.y = event.wheel.y;
break;
}
}
}
LAST = NOW;
NOW = SDL_GetPerformanceCounter();
deltaTime = (double)((NOW - LAST)*1000 / (double)SDL_GetPerformanceFrequency() );
int mouseX = 0;
int mouseY = 0;
Uint32 mouseState = SDL_GetMouseState(&mouseX, &mouseY);
Clay_Vector2 mousePosition = (Clay_Vector2){ (float)mouseX, (float)mouseY };
Clay_SetPointerState(mousePosition, mouseState & SDL_BUTTON(1));
Clay_UpdateScrollContainers(
true,
(Clay_Vector2) { scrollDelta.x, scrollDelta.y },
deltaTime
);
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight });
Clay_RenderCommandArray renderCommands = CreateLayout();
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
Clay_SDL2_Render(renderer, renderCommands);
SDL_RenderPresent(renderer);
}
quit:
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
SDL_Quit();
return 0;
}

Binary file not shown.

View File

@ -2,14 +2,19 @@ cmake_minimum_required(VERSION 3.27)
project(clay_examples_cairo_pdf_rendering C)
set(CMAKE_C_STANDARD 99)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake")
add_executable(clay_examples_cairo_pdf_rendering main.c)
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC .)
find_package(Cairo REQUIRED)
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC cairo)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC . ${CAIRO_INCLUDE_DIRS})
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC Cairo::Cairo)
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_cairo_pdf_rendering POST_BUILD

View File

@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
add_executable(clay_official_website main.c)
target_compile_options(clay_official_website PUBLIC -Wall -Werror -Wno-unknown-pragmas)
target_compile_options(clay_official_website PUBLIC -Wall -Werror -Wno-unknown-pragmas -Wno-error=missing-braces)
target_include_directories(clay_official_website PUBLIC .)
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_RELEASE "-O3")

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

View File

@ -0,0 +1,789 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preload" href="/clay/fonts/Calistoga-Regular.ttf" as="font" type="font/ttf" crossorigin>
<link rel="preload" href="/clay/fonts/Quicksand-Semibold.ttf" as="font" type="font/ttf" crossorigin>
<title>Clay - UI Layout Library</title>
<style>
html, body {
width: 100%;
height: 100%;
overflow: hidden;
padding: 0;
margin: 0;
pointer-events: none;
background: rgb(244, 235, 230);
}
/* Import the font using @font-face */
@font-face {
font-family: 'Calistoga';
font-style: normal;
font-weight: 400;
src: url('/clay/fonts/Calistoga-Regular.ttf') format('truetype');
}
@font-face {
font-family: 'Quicksand';
font-style: normal;
font-weight: 400;
src: url('/clay/fonts/Quicksand-Semibold.ttf') format('truetype');
}
body > canvas {
width: 100%;
height: 100%;
touch-action: none;
}
div, a, img {
position: absolute;
box-sizing: border-box;
-webkit-backface-visibility: hidden;
pointer-events: none;
}
a {
cursor: pointer;
pointer-events: all;
}
.text {
pointer-events: all;
white-space: pre;
}
/* TODO special exception for text selection in debug tools */
[id='2067877626'] > * {
pointer-events: none !important;
}
</style>
</head>
<script type="module">
const CLAY_RENDER_COMMAND_TYPE_NONE = 0;
const CLAY_RENDER_COMMAND_TYPE_RECTANGLE = 1;
const CLAY_RENDER_COMMAND_TYPE_BORDER = 2;
const CLAY_RENDER_COMMAND_TYPE_TEXT = 3;
const CLAY_RENDER_COMMAND_TYPE_IMAGE = 4;
const CLAY_RENDER_COMMAND_TYPE_SCISSOR_START = 5;
const CLAY_RENDER_COMMAND_TYPE_SCISSOR_END = 6;
const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7;
const GLOBAL_FONT_SCALING_FACTOR = 0.8;
let renderCommandSize = 0;
let scratchSpaceAddress = 8;
let heapSpaceAddress = 0;
let memoryDataView;
let textDecoder = new TextDecoder("utf-8");
let previousFrameTime;
let fontsById = [
'Quicksand',
'Calistoga',
'Quicksand',
'Quicksand',
'Quicksand',
];
let elementCache = {};
let imageCache = {};
let colorDefinition = { type: 'struct', members: [
{name: 'r', type: 'float' },
{name: 'g', type: 'float' },
{name: 'b', type: 'float' },
{name: 'a', type: 'float' },
]};
let stringDefinition = { type: 'struct', members: [
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
]};
let borderDefinition = { type: 'struct', members: [
{name: 'width', type: 'uint32_t'},
{name: 'color', ...colorDefinition},
]};
let cornerRadiusDefinition = { type: 'struct', members: [
{name: 'topLeft', type: 'float'},
{name: 'topRight', type: 'float'},
{name: 'bottomLeft', type: 'float'},
{name: 'bottomRight', type: 'float'},
]};
let rectangleConfigDefinition = { name: 'rectangle', type: 'struct', members: [
{ name: 'color', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'link', ...stringDefinition },
{ name: 'cursorPointer', type: 'uint8_t' },
]};
let borderConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'left', ...borderDefinition },
{ name: 'right', ...borderDefinition },
{ name: 'top', ...borderDefinition },
{ name: 'bottom', ...borderDefinition },
{ name: 'betweenChildren', ...borderDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition }
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineSpacing', type: 'uint16_t' },
{ name: 'wrapMode', type: 'uint32_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' }
]};
let scrollConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'horizontal', type: 'bool' },
{ name: 'vertical', type: 'bool' },
]};
let imageConfigDefinition = { name: 'image', type: 'struct', members: [
{ name: 'imageData', type: 'uint32_t' },
{ name: 'sourceDimensions', type: 'struct', members: [
{ name: 'width', type: 'float' },
{ name: 'height', type: 'float' },
]},
{ name: 'sourceURL', ...stringDefinition }
]};
let customConfigDefinition = { name: 'custom', type: 'struct', members: [
{ name: 'customData', type: 'uint32_t' },
]}
let renderCommandDefinition = {
name: 'CLay_RenderCommand',
type: 'struct',
members: [
{ name: 'boundingBox', type: 'struct', members: [
{ name: 'x', type: 'float' },
{ name: 'y', type: 'float' },
{ name: 'width', type: 'float' },
{ name: 'height', type: 'float' },
]},
{ name: 'config', type: 'uint32_t'},
{ name: 'text', ...stringDefinition },
{ name: 'id', type: 'uint32_t' },
{ name: 'commandType', type: 'uint32_t', },
]
};
function getStructTotalSize(definition) {
switch(definition.type) {
case 'union':
case 'struct': {
let totalSize = 0;
for (const member of definition.members) {
let result = getStructTotalSize(member);
if (definition.type === 'struct') {
totalSize += result;
} else {
totalSize = Math.max(totalSize, result);
}
}
return totalSize;
}
case 'float': return 4;
case 'uint32_t': return 4;
case 'int32_t': return 4;
case 'uint16_t': return 2;
case 'uint8_t': return 1;
case 'bool': return 1;
default: {
throw "Unimplemented C data type " + definition.type
}
}
}
function readStructAtAddress(address, definition) {
switch(definition.type) {
case 'union':
case 'struct': {
let struct = { __size: 0 };
for (const member of definition.members) {
let result = readStructAtAddress(address, member);
struct[member.name] = result;
if (definition.type === 'struct') {
struct.__size += result.__size;
address += result.__size;
} else {
struct.__size = Math.max(struct.__size, result.__size);
}
}
return struct;
}
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'int32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
default: {
throw "Unimplemented C data type " + definition.type
}
}
}
function getTextDimensions(text, font) {
// re-use canvas object for better performance
window.canvasContext.font = font;
let metrics = window.canvasContext.measureText(text);
return { width: metrics.width, height: metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent };
}
function createMainArena(arenaStructAddress, arenaMemoryAddress) {
let memorySize = instance.exports.Clay_MinMemorySize();
// Last arg is address to store return value
instance.exports.Clay_CreateArenaWithCapacityAndMemory(arenaStructAddress, memorySize, arenaMemoryAddress);
}
async function init() {
await Promise.all(fontsById.map(f => document.fonts.load(`12px "${f}"`)));
window.htmlRoot = document.body.appendChild(document.createElement('div'));
window.canvasRoot = document.body.appendChild(document.createElement('canvas'));
window.canvasContext = window.canvasRoot.getContext("2d");
window.mousePositionXThisFrame = 0;
window.mousePositionYThisFrame = 0;
window.mouseWheelXThisFrame = 0;
window.mouseWheelYThisFrame = 0;
window.touchDown = false;
window.arrowKeyDownPressedThisFrame = false;
window.arrowKeyUpPressedThisFrame = false;
let zeroTimeout = null;
document.addEventListener("wheel", (event) => {
window.mouseWheelXThisFrame = event.deltaX * -0.1;
window.mouseWheelYThisFrame = event.deltaY * -0.1;
clearTimeout(zeroTimeout);
zeroTimeout = setTimeout(() => {
window.mouseWheelXThisFrame = 0;
window.mouseWheelYThisFrame = 0;
}, 10);
});
function handleTouch (event) {
if (event.touches.length === 1) {
window.touchDown = true;
let target = event.target;
let scrollTop = 0;
let scrollLeft = 0;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
while (activeRendererIndex !== 1 && target) {
scrollLeft += target.scrollLeft;
scrollTop += target.scrollTop;
target = target.parentElement;
}
window.mousePositionXThisFrame = event.changedTouches[0].pageX + scrollLeft;
window.mousePositionYThisFrame = event.changedTouches[0].pageY + scrollTop;
}
}
document.addEventListener("touchstart", handleTouch);
document.addEventListener("touchmove", handleTouch);
document.addEventListener("touchend", () => {
window.touchDown = false;
window.mousePositionXThisFrame = 0;
window.mousePositionYThisFrame = 0;
})
document.addEventListener("mousemove", (event) => {
let target = event.target;
let scrollTop = 0;
let scrollLeft = 0;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
while (activeRendererIndex !== 1 && target) {
scrollLeft += target.scrollLeft;
scrollTop += target.scrollTop;
target = target.parentElement;
}
window.mousePositionXThisFrame = event.x + scrollLeft;
window.mousePositionYThisFrame = event.y + scrollTop;
});
document.addEventListener("mousedown", (event) => {
window.mouseDown = true;
window.mouseDownThisFrame = true;
});
document.addEventListener("mouseup", (event) => {
window.mouseDown = false;
});
document.addEventListener("keydown", (event) => {
if (event.key === "ArrowDown") {
window.arrowKeyDownPressedThisFrame = true;
}
if (event.key === "ArrowUp") {
window.arrowKeyUpPressedThisFrame = true;
}
if (event.key === "d") {
window.dKeyPressedThisFrame = true;
}
});
const importObject = {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
let textDecoder = new TextDecoder("utf-8");
let text = textDecoder.decode(memoryDataView.buffer.slice(pointerToString, pointerToString + stringLength));
let sourceDimensions = getTextDimensions(text, `${Math.round(textConfig.fontSize.value * GLOBAL_FONT_SCALING_FACTOR)}px ${fontsById[textConfig.fontId.value]}`);
memoryDataView.setFloat32(addressOfDimensions, sourceDimensions.width, true);
memoryDataView.setFloat32(addressOfDimensions + 4, sourceDimensions.height, true);
},
queryScrollOffsetFunction: (addressOfOffset, elementId) => {
let container = document.getElementById(elementId.toString());
if (container) {
memoryDataView.setFloat32(addressOfOffset, -container.scrollLeft, true);
memoryDataView.setFloat32(addressOfOffset + 4, -container.scrollTop, true);
}
}
},
};
const { instance } = await WebAssembly.instantiateStreaming(
fetch("/clay/index.wasm"), importObject
);
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
scratchSpaceAddress = instance.exports.__heap_base.value;
heapSpaceAddress = instance.exports.__heap_base.value + 1024;
let arenaAddress = scratchSpaceAddress + 8;
window.instance = instance;
createMainArena(arenaAddress, heapSpaceAddress);
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);
renderCommandSize = getStructTotalSize(renderCommandDefinition);
renderLoop();
}
function MemoryIsDifferent(one, two, length) {
for (let i = 0; i < length; i++) {
if (one[i] !== two[i]) {
return true;
}
}
return false;
}
function renderLoopHTML() {
let capacity = memoryDataView.getInt32(scratchSpaceAddress, true);
let length = memoryDataView.getInt32(scratchSpaceAddress + 4, true);
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
let previousId = 0;
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let entireRenderCommandMemory = new Uint32Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let parentElement = scissorStack[scissorStack.length - 1];
let element = null;
let isMultiConfigElement = previousId === renderCommand.id.value;
if (!elementCache[renderCommand.id.value]) {
let elementType = 'div';
switch (renderCommand.commandType.value) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
elementType = 'a';
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
elementType = 'img'; break;
}
default: break;
}
element = document.createElement(elementType);
element.id = renderCommand.id.value;
if (renderCommand.commandType.value === CLAY_RENDER_COMMAND_TYPE_SCISSOR_START) {
element.style.overflow = 'hidden';
}
elementCache[renderCommand.id.value] = {
exists: true,
element: element,
previousMemoryCommand: new Uint8Array(0),
previousMemoryConfig: new Uint8Array(0),
previousMemoryText: new Uint8Array(0)
};
}
let elementData = elementCache[renderCommand.id.value];
element = elementData.element;
if (!isMultiConfigElement && Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) {
if (parentElement.nextElementIndex === 0) {
parentElement.element.insertAdjacentElement('afterbegin', element);
} else {
parentElement.element.childNodes[Math.min(parentElement.nextElementIndex - 1, parentElement.element.childNodes.length - 1)].insertAdjacentElement('afterend', element);
}
}
elementData.exists = true;
// Don't get me started. Cheaper to compare the render command memory than to update HTML elements
let dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize) && !isMultiConfigElement;
if (!isMultiConfigElement) {
parentElement.nextElementIndex++;
}
previousId = renderCommand.id.value;
elementData.previousMemoryCommand = entireRenderCommandMemory;
let offsetX = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.x : 0;
let offsetY = scissorStack.length > 0 ? scissorStack[scissorStack.length - 1].nextAllocation.y : 0;
if (dirty) {
element.style.transform = `translate(${Math.round(renderCommand.boundingBox.x.value - offsetX)}px, ${Math.round(renderCommand.boundingBox.y.value - offsetY)}px)`
element.style.width = Math.round(renderCommand.boundingBox.width.value) + 'px';
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
}
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
break;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || config.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
elementData.previousMemoryConfig = configMemory;
let color = config.color;
element.style.backgroundColor = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
if (config.cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
}
if (config.cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
}
if (config.cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
}
if (config.cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px';
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
break;
}
elementData.previousMemoryConfig = configMemory;
if (config.left.width.value > 0) {
let color = config.left.color;
element.style.borderLeft = `${config.left.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.right.width.value > 0) {
let color = config.right.color;
element.style.borderRight = `${config.right.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.top.width.value > 0) {
let color = config.top.color;
element.style.borderTop = `${config.top.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.bottom.width.value > 0) {
let color = config.bottom.color;
element.style.borderBottom = `${config.bottom.width.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = config.cornerRadius.topLeft.value + 'px';
}
if (config.cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = config.cornerRadius.topRight.value + 'px';
}
if (config.cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = config.cornerRadius.bottomLeft.value + 'px';
}
if (config.cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = config.cornerRadius.bottomRight.value + 'px';
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
let configMemory = new Uint8Array(memoryDataView.buffer.slice(renderCommand.config.value, renderCommand.config.value + config.__size));
let textContents = renderCommand.text;
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
if (MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
element.className = 'text';
let textColor = config.textColor;
let fontSize = Math.round(config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR);
element.style.color = `rgba(${textColor.r.value}, ${textColor.g.value}, ${textColor.b.value}, ${textColor.a.value})`;
element.style.fontFamily = fontsById[config.fontId.value];
element.style.fontSize = fontSize + 'px';
element.style.pointerEvents = config.disablePointerEvents.value ? 'none' : 'all';
elementData.previousMemoryConfig = configMemory;
}
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
element.innerHTML = textDecoder.decode(stringContents);
}
elementData.previousMemoryText = stringContents;
break;
}
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 = readStructAtAddress(renderCommand.config.value, scrollConfigDefinition);
if (config.horizontal.value) {
element.style.overflowX = 'scroll';
element.style.pointerEvents = 'auto';
}
if (config.vertical.value) {
element.style.overflowY = 'scroll';
element.style.pointerEvents = 'auto';
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
scissorStack.splice(scissorStack.length - 1, 1);
break;
}
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition);
let srcContents = new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value));
if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) {
element.src = textDecoder.decode(srcContents);
}
elementData.previousMemoryText = srcContents;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
}
}
for (const key of Object.keys(elementCache)) {
if (elementCache[key].exists) {
elementCache[key].exists = false;
} else {
elementCache[key].element.remove();
delete elementCache[key];
}
}
}
function renderLoopCanvas() {
// Note: Rendering to canvas needs to be scaled up by window.devicePixelRatio in both width and height.
// e.g. if we're working on a device where devicePixelRatio is 2, we need to render
// everything at width^2 x height^2 resolution, then scale back down with css to get the correct pixel density.
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true);
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true);
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
window.canvasRoot.width = window.innerWidth * window.devicePixelRatio;
window.canvasRoot.height = window.innerHeight * window.devicePixelRatio;
window.canvasRoot.style.width = window.innerWidth + 'px';
window.canvasRoot.style.height = window.innerHeight + 'px';
let ctx = window.canvasContext;
let scale = window.devicePixelRatio;
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox;
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
let color = config.color;
ctx.beginPath();
window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
window.canvasContext.roundRect(
boundingBox.x.value * scale, // x
boundingBox.y.value * scale, // y
boundingBox.width.value * scale, // width
boundingBox.height.value * scale,
[config.cornerRadius.topLeft.value * scale, config.cornerRadius.topRight.value * scale, config.cornerRadius.bottomRight.value * scale, config.cornerRadius.bottomLeft.value * scale]) // height;
ctx.fill();
ctx.closePath();
// Handle link clicks
let linkContents = config.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.link.chars.value, config.link.chars.value + config.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
ctx.beginPath();
ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale);
// Top Left Corner
if (config.cornerRadius.topLeft.value > 0) {
let lineWidth = config.top.width.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale);
let color = config.top.color;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, config.cornerRadius.topLeft.value * scale);
ctx.stroke();
}
// Top border
if (config.top.width.value > 0) {
let lineWidth = config.top.width.value;
let halfLineWidth = lineWidth / 2;
let color = config.top.color;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.topLeft.value + halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
ctx.stroke();
}
// Top Right Corner
if (config.cornerRadius.topRight.value > 0) {
let lineWidth = config.top.width.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale);
let color = config.top.color;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale, config.cornerRadius.topRight.value * scale);
ctx.stroke();
}
// Right border
if (config.right.width.value > 0) {
let color = config.right.color;
let lineWidth = config.right.width.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topRight.value + halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.topRight.value - halfLineWidth) * scale);
ctx.stroke();
}
// Bottom Right Corner
if (config.cornerRadius.bottomRight.value > 0) {
let color = config.top.color;
let lineWidth = config.top.width.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + boundingBox.width.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, config.cornerRadius.bottomRight.value * scale);
ctx.stroke();
}
// Bottom Border
if (config.bottom.width.value > 0) {
let color = config.bottom.color;
let lineWidth = config.bottom.width.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.bottomRight.value - halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.stroke();
}
// Bottom Left Corner
if (config.cornerRadius.bottomLeft.value > 0) {
let color = config.bottom.color;
let lineWidth = config.bottom.width.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + config.cornerRadius.bottomLeft.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale);
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.arcTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - halfLineWidth) * scale, (boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale, config.cornerRadius.bottomLeft.value * scale);
ctx.stroke();
}
// Left Border
if (config.left.width.value > 0) {
let color = config.left.color;
let lineWidth = config.left.width.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + boundingBox.height.value - config.cornerRadius.bottomLeft.value - halfLineWidth) * scale);
ctx.lineTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.bottomRight.value + halfLineWidth) * scale);
ctx.stroke();
}
ctx.closePath();
break;
}
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
let textContents = renderCommand.text;
let stringContents = new Uint8Array(memoryDataView.buffer.slice(textContents.chars.value, textContents.chars.value + textContents.length.value));
let fontSize = config.fontSize.value * GLOBAL_FONT_SCALING_FACTOR * scale;
ctx.font = `${fontSize}px ${fontsById[config.fontId.value]}`;
let color = config.textColor;
ctx.textBaseline = 'middle';
ctx.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`;
ctx.fillText(textDecoder.decode(stringContents), boundingBox.x.value * scale, (boundingBox.y.value + boundingBox.height.value / 2 + 1) * scale);
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
window.canvasContext.save();
window.canvasContext.beginPath();
window.canvasContext.rect(boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale);
window.canvasContext.clip();
window.canvasContext.closePath();
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
window.canvasContext.restore();
break;
}
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = readStructAtAddress(renderCommand.config.value, imageConfigDefinition);
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(config.sourceURL.chars.value, config.sourceURL.chars.value + config.sourceURL.length.value)));
if (!imageCache[src]) {
imageCache[src] = {
image: new Image(),
loaded: false,
}
imageCache[src].image.onload = () => imageCache[src].loaded = true;
imageCache[src].image.src = src;
} else if (imageCache[src].loaded) {
ctx.drawImage(imageCache[src].image, boundingBox.x.value * scale, boundingBox.y.value * scale, boundingBox.width.value * scale, boundingBox.height.value * scale);
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
}
}
}
function renderLoop(currentTime) {
const elapsed = currentTime - previousFrameTime;
previousFrameTime = currentTime;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
if (activeRendererIndex === 0) {
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, 0, 0, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, 0, 0, window.dKeyPressedThisFrame, elapsed / 1000);
} else {
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, window.arrowKeyDownPressedThisFrame, window.arrowKeyUpPressedThisFrame, window.dKeyPressedThisFrame, elapsed / 1000);
}
let rendererChanged = activeRendererIndex !== window.previousActiveRendererIndex;
switch (activeRendererIndex) {
case 0: {
renderLoopHTML();
if (rendererChanged) {
window.htmlRoot.style.display = 'block';
window.canvasRoot.style.display = 'none';
}
break;
}
case 1: {
renderLoopCanvas();
if (rendererChanged) {
window.htmlRoot.style.display = 'none';
window.canvasRoot.style.display = 'block';
}
break;
}
}
window.previousActiveRendererIndex = activeRendererIndex;
requestAnimationFrame(renderLoop);
window.mouseDownThisFrame = false;
window.arrowKeyUpPressedThisFrame = false;
window.arrowKeyDownPressedThisFrame = false;
window.dKeyPressedThisFrame = false;
}
init();
</script>
<body>
</body>
</html>

Binary file not shown.

View File

@ -178,6 +178,7 @@
}
case 'float': return 4;
case 'uint32_t': return 4;
case 'int32_t': return 4;
case 'uint16_t': return 2;
case 'uint8_t': return 1;
case 'bool': return 1;
@ -206,6 +207,7 @@
}
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
case 'uint32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'int32_t': return { value: memoryDataView.getUint32(address, true), __size: 4 };
case 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
@ -312,7 +314,6 @@
const importObject = {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
@ -338,7 +339,7 @@
memoryDataView = new DataView(new Uint8Array(instance.exports.memory.buffer).buffer);
scratchSpaceAddress = instance.exports.__heap_base.value;
heapSpaceAddress = instance.exports.__heap_base.value + 1024;
let arenaAddress = scratchSpaceAddress;
let arenaAddress = scratchSpaceAddress + 8;
window.instance = instance;
createMainArena(arenaAddress, heapSpaceAddress);
memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true);
@ -358,8 +359,8 @@
}
function renderLoopHTML() {
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true);
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true);
let capacity = memoryDataView.getInt32(scratchSpaceAddress, true);
let length = memoryDataView.getInt32(scratchSpaceAddress + 4, true);
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
let previousId = 0;
@ -425,7 +426,8 @@
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
}
switch(renderCommand.commandType.value) {
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}
@ -579,7 +581,9 @@
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox;
switch(renderCommand.commandType.value) {
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}

View File

@ -165,7 +165,7 @@ Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) {
Clay_String LOREM_IPSUM_TEXT = CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.");
void HighPerformancePageDesktop(float lerpValue) {
CLAY(CLAY_ID("PerformanceDesktop"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformancePageOuter"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 82, 32}, .childGap = 64 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 })) {
CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({ .max = 16 }) }})) {}
@ -187,7 +187,7 @@ void HighPerformancePageDesktop(float lerpValue) {
}
void HighPerformancePageMobile(float lerpValue) {
CLAY(CLAY_ID("PerformanceMobile"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformancePageOuter"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT({ .min = windowHeight - 50 }) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32 }), CLAY_RECTANGLE({ .color = COLOR_RED })) {
CLAY(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 })) {
CLAY_TEXT(CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({ .max = 16 }) }})) {}
@ -390,7 +390,11 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
windowWidth = width;
windowHeight = height;
Clay_SetLayoutDimensions((Clay_Dimensions) { width, height });
if (deltaTime == deltaTime) { // NaN propagation can cause pain here
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
Clay_LayoutElementHashMapItem *perfPage = Clay__GetHashMapItem(Clay_GetElementId(CLAY_STRING("PerformancePageOuter")).id);
// NaN propagation can cause pain here
float perfPageYOffset = perfPage->boundingBox.y + scrollContainerData.scrollPosition->y;
if (deltaTime == deltaTime && perfPageYOffset < height && perfPageYOffset + perfPage->boundingBox.height > 0) {
animationLerpValue += deltaTime;
if (animationLerpValue > 1) {
animationLerpValue -= 2;
@ -413,12 +417,10 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
}
if (isMouseDown && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
scrollbarData.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY };
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
scrollbarData.mouseDown = true;
} else if (scrollbarData.mouseDown) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
if (scrollContainerData.contentDimensions.height > 0) {
Clay_Vector2 ratio = (Clay_Vector2) {
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
@ -434,12 +436,10 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
}
if (arrowKeyDownPressedThisFrame) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50;
}
} else if (arrowKeyUpPressedThisFrame) {
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50;
}

View File

@ -9,4 +9,4 @@ add_executable(clay_examples_cpp_project_example main.cpp)
target_include_directories(clay_examples_cpp_project_example PUBLIC .)
set(CMAKE_CXX_FLAGS_DEBUG "-Werror -Wall")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")

View File

@ -24,11 +24,12 @@ target_compile_options(clay_examples_introducing_clay_video_demo PUBLIC)
target_include_directories(clay_examples_introducing_clay_video_demo PUBLIC .)
target_link_libraries(clay_examples_introducing_clay_video_demo PUBLIC raylib)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_introducing_clay_video_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -42,13 +42,7 @@ typedef struct {
} DocumentArray;
DocumentArray documents = {
.documents = (Document[]) {
{ .title = CLAY_STRING("Squirrels"), .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") },
{ .title = CLAY_STRING("Lorem Ipsum"), .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.") },
{ .title = CLAY_STRING("Vacuum Instructions"), .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.") },
{ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") },
{ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") },
},
.documents = NULL, // TODO figure out if it's possible to const init this list
.length = 5
};
@ -75,14 +69,17 @@ void HandleClayErrors(Clay_ErrorData errorData) {
int main(void) {
documents.documents = (Document[]) {
{ .title = CLAY_STRING("Squirrels"), .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") },
{ .title = CLAY_STRING("Lorem Ipsum"), .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.") },
{ .title = CLAY_STRING("Vacuum Instructions"), .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.") },
{ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") },
{ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") },
};
Clay_Raylib_Initialize(1024, 768, "Introducing Clay Demo", FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); // Extra parameters to this function are new since the video was published
uint64_t clayRequiredMemory = Clay_MinMemorySize();
printf("%lld", clayRequiredMemory);
Clay_Arena clayMemory = (Clay_Arena) {
.memory = malloc((size_t)1024 * 1024 * 1024 * 1024),
.capacity = clayRequiredMemory
};
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
@ -302,4 +299,4 @@ int main(void) {
Clay_Raylib_Render(renderCommands);
EndDrawing();
}
}
}

View File

@ -24,11 +24,12 @@ target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC)
target_include_directories(clay_examples_raylib_sidebar_scrolling_container PUBLIC .)
target_link_libraries(clay_examples_raylib_sidebar_scrolling_container PUBLIC raylib)
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror -DCLAY_DEBUG")
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -Wno-error=missing-braces -DCLAY_DEBUG")
set(CMAKE_C_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)
${CMAKE_CURRENT_BINARY_DIR}/resources)

View File

@ -1,3 +1,3 @@
$NAME$ $NAME$_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
$NAME$ $NAME$_Allocate_Arena(int32_t capacity, Clay_Arena *arena) {
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__ALIGNMENT($TYPE$), arena)};
}

View File

@ -0,0 +1,3 @@
$NAME$ $NAME$_Allocate_Arena(int32_t capacity, Clay_Arena *arena) {
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__POINTER_ALIGNMENT, arena)};
}

View File

@ -1,6 +1,6 @@
typedef struct
CLAY__TYPEDEF($NAME$, struct
{
uint32_t capacity;
uint32_t length;
int32_t capacity;
int32_t length;
$TYPE$ *internalArray;
} $NAME$;
});

View File

@ -1,5 +1,5 @@
typedef struct
CLAY__TYPEDEF($NAME$Slice, struct
{
uint32_t length;
int32_t length;
$TYPE$ *internalArray;
} $NAME$Slice;
});

View File

@ -1,3 +1,3 @@
$TYPE$ *$NAME$_Get($NAME$ *array, int index) {
$TYPE$ *$NAME$_Get($NAME$ *array, int32_t index) {
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$;
}

View File

@ -1,3 +1,3 @@
$TYPE$ *$NAME$Slice_Get($NAME$Slice *slice, int index) {
$TYPE$ *$NAME$Slice_Get($NAME$Slice *slice, int32_t index) {
return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : $DEFAULT_VALUE$;
}

View File

@ -1,3 +1,3 @@
$TYPE$ $NAME$_Get($NAME$ *array, int index) {
$TYPE$ $NAME$_Get($NAME$ *array, int32_t index) {
return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : $DEFAULT_VALUE$;
}

View File

@ -1,4 +1,4 @@
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int index) {
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int32_t index) {
if (Clay__Array_RangeCheck(index, array->length)) {
array->length--;
$TYPE$ removed = array->internalArray[index];

View File

@ -1,4 +1,4 @@
void $NAME$_Set($NAME$ *array, int index, $TYPE$ value) {
void $NAME$_Set($NAME$ *array, int32_t index, $TYPE$ value) {
if (Clay__Array_RangeCheck(index, array->capacity)) {
array->internalArray[index] = value;
array->length = index < array->length ? array->length : index + 1;

13
renderers/SDL2/README Normal file
View File

@ -0,0 +1,13 @@
Please note, the SDL2 renderer is not 100% feature complete. It is currently missing:
- Border rendering
- Image rendering
- Rounded rectangle corners
Note: on Mac OSX, SDL2 for some reason decides to automatically disable momentum scrolling on macbook trackpads.
You can re enable it in objective C using:
```C
[[NSUserDefaults standardUserDefaults] setBool: YES
forKey: @"AppleMomentumScrollSupported"];
```

View File

@ -0,0 +1,101 @@
#include "../../clay.h"
#include <SDL.h>
#include <SDL_ttf.h>
typedef struct
{
uint32_t fontId;
TTF_Font *font;
} SDL2_Font;
static SDL2_Font SDL2_fonts[1];
static Clay_Dimensions SDL2_MeasureText(Clay_String *text, Clay_TextElementConfig *config)
{
TTF_Font *font = SDL2_fonts[config->fontId].font;
char *chars = (char *)calloc(text->length + 1, 1);
memcpy(chars, text->chars, text->length);
int width = 0;
int height = 0;
if (TTF_SizeUTF8(font, chars, &width, &height) < 0) {
fprintf(stderr, "Error: could not measure text: %s\n", TTF_GetError());
exit(1);
}
free(chars);
return (Clay_Dimensions) {
.width = (float)width,
.height = (float)height,
};
}
SDL_Rect currentClippingRectangle;
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands)
{
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_RectangleElementConfig *config = renderCommand->config.rectangleElementConfig;
Clay_Color color = config->color;
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
SDL_FRect rect = (SDL_FRect) {
.x = boundingBox.x,
.y = boundingBox.y,
.w = boundingBox.width,
.h = boundingBox.height,
};
SDL_RenderFillRectF(renderer, &rect);
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextElementConfig *config = renderCommand->config.textElementConfig;
Clay_String text = renderCommand->text;
char *cloned = (char *)calloc(text.length + 1, 1);
memcpy(cloned, text.chars, text.length);
TTF_Font* font = SDL2_fonts[config->fontId].font;
SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) {
.r = (Uint8)config->textColor.r,
.g = (Uint8)config->textColor.g,
.b = (Uint8)config->textColor.b,
.a = (Uint8)config->textColor.a,
});
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_Rect destination = (SDL_Rect){
.x = boundingBox.x,
.y = boundingBox.y,
.w = boundingBox.width,
.h = boundingBox.height,
};
SDL_RenderCopy(renderer, texture, NULL, &destination);
SDL_DestroyTexture(texture);
SDL_FreeSurface(surface);
free(cloned);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
currentClippingRectangle = (SDL_Rect) {
.x = boundingBox.x,
.y = boundingBox.y,
.w = boundingBox.width,
.h = boundingBox.height,
};
SDL_RenderSetClipRect(renderer, &currentClippingRectangle);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
SDL_RenderSetClipRect(renderer, NULL);
break;
}
default: {
fprintf(stderr, "Error: unhandled render command: %d\n", renderCommand->commandType);
exit(1);
}
}
}
}

View File

@ -4,9 +4,6 @@
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
#ifdef CLAY_OVERFLOW_TRAP
#include "signal.h"
#endif
#define CLAY_RECTANGLE_TO_RAYLIB_RECTANGLE(rectangle) (Rectangle) { .x = rectangle.x, .y = rectangle.y, .width = rectangle.width, .height = rectangle.height }
#define CLAY_COLOR_TO_RAYLIB_COLOR(color) (Color) { .r = (unsigned char)roundf(color.r), .g = (unsigned char)roundf(color.g), .b = (unsigned char)roundf(color.b), .a = (unsigned char)roundf(color.a) }
@ -229,9 +226,6 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
}
default: {
printf("Error: unhandled render command.");
#ifdef CLAY_OVERFLOW_TRAP
raise(SIGTRAP);
#endif
exit(1);
}
}

View File

@ -317,7 +317,9 @@
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
let boundingBox = renderCommand.boundingBox;
switch(renderCommand.commandType.value) {
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}

View File

@ -336,7 +336,7 @@
let element = null;
if (!elementCache[renderCommand.id.value]) {
let elementType = 'div';
switch (renderCommand.commandType.value) {
switch (renderCommand.commandType.value & 0xff) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
elementType = 'a';
@ -384,7 +384,8 @@
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
}
switch(renderCommand.commandType.value) {
// note: commandType is packed to uint8_t and has 3 garbage bytes of padding
switch(renderCommand.commandType.value & 0xff) {
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
break;
}