Merge branch 'nicbarker:main' into main
1
.gitignore
vendored
@ -1,6 +1,5 @@
|
|||||||
cmake-build-*/
|
cmake-build-*/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.idea/
|
.idea/
|
||||||
build/
|
|
||||||
node_modules/
|
node_modules/
|
||||||
*.dSYM
|
*.dSYM
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
cmake_minimum_required(VERSION 3.27)
|
cmake_minimum_required(VERSION 3.27)
|
||||||
project(clay)
|
project(clay)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
add_subdirectory("examples/cpp-project-example")
|
add_subdirectory("examples/cpp-project-example")
|
||||||
|
|
||||||
# Don't try to compile C99 projects using MSVC
|
# Don't try to compile C99 projects using MSVC
|
||||||
|
@ -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.
|
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**
|
**During UI declaration**
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ when ODIN_OS == .Windows {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String :: struct {
|
String :: struct {
|
||||||
length: c.int,
|
length: c.int32_t,
|
||||||
chars: [^]c.char,
|
chars: [^]c.char,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,9 +30,8 @@ Dimensions :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Arena :: struct {
|
Arena :: struct {
|
||||||
label: String,
|
nextAllocation: uintptr,
|
||||||
nextAllocation: u64,
|
capacity: uintptr,
|
||||||
capacity: u64,
|
|
||||||
memory: [^]c.char,
|
memory: [^]c.char,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,8 +258,8 @@ LayoutConfig :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClayArray :: struct($type: typeid) {
|
ClayArray :: struct($type: typeid) {
|
||||||
capacity: u32,
|
capacity: i32,
|
||||||
length: u32,
|
length: i32,
|
||||||
internalArray: [^]type,
|
internalArray: [^]type,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,12 +269,33 @@ TypedConfig :: struct {
|
|||||||
id: ElementId,
|
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")
|
@(link_prefix = "Clay_", default_calling_convention = "c")
|
||||||
foreign Clay {
|
foreign Clay {
|
||||||
MinMemorySize :: proc() -> u32 ---
|
MinMemorySize :: proc() -> u32 ---
|
||||||
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
|
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
|
||||||
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
|
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) ---
|
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
|
||||||
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
|
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
|
||||||
BeginLayout :: proc() ---
|
BeginLayout :: proc() ---
|
||||||
|
@ -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 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}),
|
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.Text(
|
||||||
"Clay is laying out this webpage right now!",
|
"Clay is laying out this webpage right now!",
|
||||||
clay.TextConfig({fontSize = 36, fontId = FONT_ID_TITLE_36, textColor = COLOR_ORANGE}),
|
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 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}),
|
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.Text(
|
||||||
"Clay is laying out this webpage right now!",
|
"Clay is laying out this webpage right now!",
|
||||||
clay.TextConfig({fontSize = 32, fontId = FONT_ID_TITLE_32, textColor = COLOR_ORANGE}),
|
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.Layout({sizing = {width = widthSizing}, childAlignment = {x = .CENTER}})) {
|
||||||
if clay.UI(
|
if clay.UI(
|
||||||
clay.ID("SyntaxPageRightImage"),
|
clay.ID("SyntaxPageRightImageInner"),
|
||||||
clay.Layout({sizing = {width = clay.SizingGrow({max = 568})}}),
|
clay.Layout({sizing = {width = clay.SizingGrow({max = 568})}}),
|
||||||
clay.Image({imageData = &syntaxImage, sourceDimensions = {1136, 1194}}),
|
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) {
|
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})) {
|
if clay.UI(clay.ID("PerformanceLeftText"), clay.Layout({sizing = {width = widthSizing}, layoutDirection = .TOP_TO_BOTTOM, childGap = 8})) {
|
||||||
clay.Text("High Performance", clay.TextConfig(titleTextConfig))
|
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(
|
clay.Text(
|
||||||
"Fast enough to recompute your entire UI every frame.",
|
"Fast enough to recompute your entire UI every frame.",
|
||||||
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
|
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) {
|
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})) {
|
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))
|
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.Text(
|
||||||
"Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE.",
|
"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}),
|
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.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}))
|
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")
|
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.Layout({sizing = {clay.SizingGrow({}), clay.SizingFixed(50)}, childAlignment = {y = .CENTER}, childGap = 24, padding = {x = 32}}),
|
||||||
) {
|
) {
|
||||||
clay.Text("Clay", &headerTextConfig)
|
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 (!mobileScreen) {
|
||||||
if clay.UI(clay.ID("LinkExamplesOuter"), clay.Layout({}), clay.Rectangle({color = {0, 0, 0, 0}})) {
|
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)
|
raylib.SetTextureFilter(raylibFonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errorHandler :: proc "c" (errorData: clay.ErrorData) {
|
||||||
|
if (errorData.errorType == clay.ErrorType.DUPLICATE_ID) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
minMemorySize: u32 = clay.MinMemorySize()
|
minMemorySize: u32 = clay.MinMemorySize()
|
||||||
memory := make([^]u8, minMemorySize)
|
memory := make([^]u8, minMemorySize)
|
||||||
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
|
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
|
||||||
clay.SetMeasureTextFunction(measureText)
|
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.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
|
||||||
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
|
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
|
||||||
|
32
cmake/FindCairo.cmake
Normal 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)
|
@ -33,8 +33,9 @@ target_link_libraries(SDL2_video_demo PUBLIC
|
|||||||
SDL2::SDL2-static
|
SDL2::SDL2-static
|
||||||
SDL2_ttf::SDL2_ttf-static
|
SDL2_ttf::SDL2_ttf-static
|
||||||
)
|
)
|
||||||
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(
|
add_custom_command(
|
||||||
TARGET SDL2_video_demo POST_BUILD
|
TARGET SDL2_video_demo POST_BUILD
|
||||||
|
@ -2,14 +2,19 @@ cmake_minimum_required(VERSION 3.27)
|
|||||||
project(clay_examples_cairo_pdf_rendering C)
|
project(clay_examples_cairo_pdf_rendering C)
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake")
|
||||||
|
|
||||||
|
|
||||||
add_executable(clay_examples_cairo_pdf_rendering main.c)
|
add_executable(clay_examples_cairo_pdf_rendering main.c)
|
||||||
|
|
||||||
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
|
find_package(Cairo REQUIRED)
|
||||||
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC .)
|
|
||||||
|
|
||||||
target_link_libraries(clay_examples_cairo_pdf_rendering PUBLIC cairo)
|
target_compile_options(clay_examples_cairo_pdf_rendering PUBLIC)
|
||||||
set(CMAKE_CXX_FLAGS_DEBUG "-Wall -Werror")
|
target_include_directories(clay_examples_cairo_pdf_rendering PUBLIC . ${CAIRO_INCLUDE_DIRS})
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
|
||||||
|
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(
|
add_custom_command(
|
||||||
TARGET clay_examples_cairo_pdf_rendering POST_BUILD
|
TARGET clay_examples_cairo_pdf_rendering POST_BUILD
|
||||||
|
@ -5,7 +5,7 @@ set(CMAKE_C_STANDARD 99)
|
|||||||
|
|
||||||
add_executable(clay_official_website main.c)
|
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 .)
|
target_include_directories(clay_official_website PUBLIC .)
|
||||||
|
|
||||||
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
|
set(CMAKE_C_FLAGS_RELEASE "-O3")
|
BIN
examples/clay-official-website/build/clay/images/check_1.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_2.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_3.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_4.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_5.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/debugger.png
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
examples/clay-official-website/build/clay/images/declarative.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
examples/clay-official-website/build/clay/images/renderer.png
Normal file
After Width: | Height: | Size: 310 KiB |
789
examples/clay-official-website/build/clay/index.html
Normal 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>
|
BIN
examples/clay-official-website/build/clay/index.wasm
Executable file
@ -178,6 +178,7 @@
|
|||||||
}
|
}
|
||||||
case 'float': return 4;
|
case 'float': return 4;
|
||||||
case 'uint32_t': return 4;
|
case 'uint32_t': return 4;
|
||||||
|
case 'int32_t': return 4;
|
||||||
case 'uint16_t': return 2;
|
case 'uint16_t': return 2;
|
||||||
case 'uint8_t': return 1;
|
case 'uint8_t': return 1;
|
||||||
case 'bool': return 1;
|
case 'bool': return 1;
|
||||||
@ -206,6 +207,7 @@
|
|||||||
}
|
}
|
||||||
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
|
case 'float': return { value: memoryDataView.getFloat32(address, true), __size: 4 };
|
||||||
case 'uint32_t': return { value: memoryDataView.getUint32(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 'uint16_t': return { value: memoryDataView.getUint16(address, true), __size: 2 };
|
||||||
case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
|
case 'uint8_t': return { value: memoryDataView.getUint8(address, true), __size: 1 };
|
||||||
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
|
case 'bool': return { value: memoryDataView.getUint8(address, true), __size: 1 };
|
||||||
@ -312,7 +314,6 @@
|
|||||||
|
|
||||||
const importObject = {
|
const importObject = {
|
||||||
clay: {
|
clay: {
|
||||||
|
|
||||||
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
|
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig) => {
|
||||||
let stringLength = memoryDataView.getUint32(textToMeasure, true);
|
let stringLength = memoryDataView.getUint32(textToMeasure, true);
|
||||||
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
|
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
|
||||||
@ -358,8 +359,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderLoopHTML() {
|
function renderLoopHTML() {
|
||||||
let capacity = memoryDataView.getUint32(scratchSpaceAddress, true);
|
let capacity = memoryDataView.getInt32(scratchSpaceAddress, true);
|
||||||
let length = memoryDataView.getUint32(scratchSpaceAddress + 4, true);
|
let length = memoryDataView.getInt32(scratchSpaceAddress + 4, true);
|
||||||
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
|
let arrayOffset = memoryDataView.getUint32(scratchSpaceAddress + 8, true);
|
||||||
let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
|
let scissorStack = [{ nextAllocation: { x: 0, y: 0 }, element: htmlRoot, nextElementIndex: 0 }];
|
||||||
let previousId = 0;
|
let previousId = 0;
|
||||||
@ -425,7 +426,8 @@
|
|||||||
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
|
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): {
|
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -579,7 +581,9 @@
|
|||||||
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
|
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
|
||||||
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
||||||
let boundingBox = renderCommand.boundingBox;
|
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): {
|
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -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.");
|
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) {
|
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(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_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 }) }})) {}
|
CLAY(CLAY_ID("PerformanceSpacer"), CLAY_LAYOUT({ .sizing = { CLAY_SIZING_GROW({ .max = 16 }) }})) {}
|
||||||
@ -187,7 +187,7 @@ void HighPerformancePageDesktop(float lerpValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HighPerformancePageMobile(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(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_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 }) }})) {}
|
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;
|
windowWidth = width;
|
||||||
windowHeight = height;
|
windowHeight = height;
|
||||||
Clay_SetLayoutDimensions((Clay_Dimensions) { width, 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;
|
animationLerpValue += deltaTime;
|
||||||
if (animationLerpValue > 1) {
|
if (animationLerpValue > 1) {
|
||||||
animationLerpValue -= 2;
|
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")))) {
|
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.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY };
|
||||||
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
|
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
|
||||||
scrollbarData.mouseDown = true;
|
scrollbarData.mouseDown = true;
|
||||||
} else if (scrollbarData.mouseDown) {
|
} else if (scrollbarData.mouseDown) {
|
||||||
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
|
|
||||||
if (scrollContainerData.contentDimensions.height > 0) {
|
if (scrollContainerData.contentDimensions.height > 0) {
|
||||||
Clay_Vector2 ratio = (Clay_Vector2) {
|
Clay_Vector2 ratio = (Clay_Vector2) {
|
||||||
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
|
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
|
||||||
@ -434,12 +436,10 @@ CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(floa
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (arrowKeyDownPressedThisFrame) {
|
if (arrowKeyDownPressedThisFrame) {
|
||||||
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
|
|
||||||
if (scrollContainerData.contentDimensions.height > 0) {
|
if (scrollContainerData.contentDimensions.height > 0) {
|
||||||
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50;
|
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50;
|
||||||
}
|
}
|
||||||
} else if (arrowKeyUpPressedThisFrame) {
|
} else if (arrowKeyUpPressedThisFrame) {
|
||||||
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
|
|
||||||
if (scrollContainerData.contentDimensions.height > 0) {
|
if (scrollContainerData.contentDimensions.height > 0) {
|
||||||
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50;
|
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50;
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,9 @@ target_compile_options(clay_examples_introducing_clay_video_demo PUBLIC)
|
|||||||
target_include_directories(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)
|
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(
|
add_custom_command(
|
||||||
TARGET clay_examples_introducing_clay_video_demo POST_BUILD
|
TARGET clay_examples_introducing_clay_video_demo POST_BUILD
|
||||||
|
@ -79,10 +79,7 @@ int main(void) {
|
|||||||
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
|
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();
|
uint64_t clayRequiredMemory = Clay_MinMemorySize();
|
||||||
Clay_Arena clayMemory = (Clay_Arena) {
|
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
|
||||||
.memory = malloc((size_t)1024 * 1024 * 1024 * 1024),
|
|
||||||
.capacity = clayRequiredMemory
|
|
||||||
};
|
|
||||||
Clay_Initialize(clayMemory, (Clay_Dimensions) {
|
Clay_Initialize(clayMemory, (Clay_Dimensions) {
|
||||||
.width = GetScreenWidth(),
|
.width = GetScreenWidth(),
|
||||||
.height = GetScreenHeight()
|
.height = GetScreenHeight()
|
||||||
|
@ -24,8 +24,9 @@ target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC)
|
|||||||
target_include_directories(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)
|
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(
|
add_custom_command(
|
||||||
TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD
|
TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD
|
||||||
|
@ -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)};
|
return CLAY__INIT($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), CLAY__ALIGNMENT($TYPE$), arena)};
|
||||||
}
|
}
|
3
generator/array_allocate_pointer.template.c
Normal 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)};
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
typedef struct
|
CLAY__TYPEDEF($NAME$, struct
|
||||||
{
|
{
|
||||||
uint32_t capacity;
|
int32_t capacity;
|
||||||
uint32_t length;
|
int32_t length;
|
||||||
$TYPE$ *internalArray;
|
$TYPE$ *internalArray;
|
||||||
} $NAME$;
|
});
|
@ -1,5 +1,5 @@
|
|||||||
typedef struct
|
CLAY__TYPEDEF($NAME$Slice, struct
|
||||||
{
|
{
|
||||||
uint32_t length;
|
int32_t length;
|
||||||
$TYPE$ *internalArray;
|
$TYPE$ *internalArray;
|
||||||
} $NAME$Slice;
|
});
|
@ -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$;
|
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$;
|
||||||
}
|
}
|
@ -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$;
|
return Clay__Array_RangeCheck(index, slice->length) ? &slice->internalArray[index] : $DEFAULT_VALUE$;
|
||||||
}
|
}
|
@ -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$;
|
return Clay__Array_RangeCheck(index, array->length) ? array->internalArray[index] : $DEFAULT_VALUE$;
|
||||||
}
|
}
|
@ -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)) {
|
if (Clay__Array_RangeCheck(index, array->length)) {
|
||||||
array->length--;
|
array->length--;
|
||||||
$TYPE$ removed = array->internalArray[index];
|
$TYPE$ removed = array->internalArray[index];
|
||||||
|
@ -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)) {
|
if (Clay__Array_RangeCheck(index, array->capacity)) {
|
||||||
array->internalArray[index] = value;
|
array->internalArray[index] = value;
|
||||||
array->length = index < array->length ? array->length : index + 1;
|
array->length = index < array->length ? array->length : index + 1;
|
||||||
|
@ -317,7 +317,9 @@
|
|||||||
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
|
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
|
||||||
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
let renderCommand = readStructAtAddress(arrayOffset, renderCommandDefinition);
|
||||||
let boundingBox = renderCommand.boundingBox;
|
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): {
|
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -336,7 +336,7 @@
|
|||||||
let element = null;
|
let element = null;
|
||||||
if (!elementCache[renderCommand.id.value]) {
|
if (!elementCache[renderCommand.id.value]) {
|
||||||
let elementType = 'div';
|
let elementType = 'div';
|
||||||
switch (renderCommand.commandType.value) {
|
switch (renderCommand.commandType.value & 0xff) {
|
||||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||||
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
|
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
|
||||||
elementType = 'a';
|
elementType = 'a';
|
||||||
@ -384,7 +384,8 @@
|
|||||||
element.style.height = Math.round(renderCommand.boundingBox.height.value) + 'px';
|
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): {
|
case (CLAY_RENDER_COMMAND_TYPE_NONE): {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|