Compare commits

..

No commits in common. "v0.10" and "main" have entirely different histories.
v0.10 ... main

133 changed files with 21330 additions and 3344 deletions

6
.dockerignore Normal file
View file

@ -0,0 +1,6 @@
cmake-build-debug/
cmake-build-release/
.DS_Store
.idea/
build/
node_modules/

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: [nicbarker]

View file

@ -0,0 +1,92 @@
name: CMake on multiple platforms
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
# Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable.
fail-fast: false
# Set up a matrix to run the following 3 configurations:
# 1. <Windows, Release, latest MSVC compiler toolchain on the default runner image, default generator>
# 2. <Linux, Release, latest GCC compiler toolchain on the default runner image, default generator>
# 3. <Linux, Release, latest Clang compiler toolchain on the default runner image, default generator>
#
# To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list.
matrix:
os: [ubuntu-latest, windows-latest]
build_type: [Release]
c_compiler: [gcc, clang, cl]
include:
- os: windows-latest
c_compiler: cl
cpp_compiler: cl
- os: ubuntu-latest
c_compiler: gcc
cpp_compiler: g++
- os: ubuntu-latest
c_compiler: clang
cpp_compiler: clang++
exclude:
- os: windows-latest
c_compiler: gcc
- os: windows-latest
c_compiler: clang
- os: ubuntu-latest
c_compiler: cl
steps:
- uses: actions/checkout@v4
- name: Set reusable strings
# Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
id: strings
shell: bash
run: |
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
- name: Cache
uses: actions/cache@v4.2.0
with:
# A list of files, directories, and wildcard patterns to cache and restore
path: "/home/runner/work/clay/clay/build/_deps"
# An explicit key for restoring and saving the cache
key: "_deps"
- name: Install Dependencies
if: runner.os == 'Linux'
run: |
DEBIAN_FRONTEND=noninteractive sudo apt-get update -y
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y git
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libwayland-dev
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y pkg-config
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y libxkbcommon-dev
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y xorg-dev
- name: Configure CMake
# Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
# See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
run: >
cmake -B ${{ steps.strings.outputs.build-output-dir }}
-DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
-DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
-S ${{ github.workspace }}
- name: Build
# Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
- name: Test
working-directory: ${{ steps.strings.outputs.build-output-dir }}
# Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
# See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
run: ctest --build-config ${{ matrix.build_type }}

View file

@ -0,0 +1,104 @@
name: Odin Bindings Update
on:
push:
branches: [main]
jobs:
check_changes:
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.check_clay.outputs.changed }}
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Check if clay.h changed
id: check_clay
run: |
if git diff --name-only HEAD^ HEAD | grep -Fx "clay.h"; then
echo "changed=true" >> $GITHUB_OUTPUT
else
echo "changed=false" >> $GITHUB_OUTPUT
fi
build:
needs: check_changes
if: needs.check_changes.outputs.changed == 'true'
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Install clang (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y clang
- name: Build libs
run: |
mkdir -p build
mkdir -p artifacts
cp clay.h clay.c
COMMON_FLAGS="-DCLAY_IMPLEMENTATION -fno-ident -frandom-seed=clay"
if [[ "$(uname)" == "Linux" ]]; then
mkdir -p artifacts/linux
mkdir -p artifacts/windows
mkdir -p artifacts/wasm
echo "Building for Linux..."
clang -c $COMMON_FLAGS -fPIC -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -o build/linux.o
ar rD artifacts/linux/clay.a build/linux.o
echo "Building for Windows..."
clang -c $COMMON_FLAGS -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib clay.c -o artifacts/windows/clay.lib
echo "Building for WASM..."
clang -c $COMMON_FLAGS -fPIC -target wasm32 -nostdlib -static clay.c -o artifacts/wasm/clay.o
elif [[ "$(uname)" == "Darwin" ]]; then
mkdir -p artifacts/macos
mkdir -p artifacts/macos-arm64
echo "Building for macOS (x86_64)..."
clang -c $COMMON_FLAGS -fPIC -target x86_64-apple-macos clay.c -o build/macos.o
libtool -static -o artifacts/macos/clay.a build/macos.o
echo "Building for macOS (ARM64)..."
clang -c $COMMON_FLAGS -fPIC -target arm64-apple-macos clay.c -o build/macos-arm64.o
libtool -static -o artifacts/macos-arm64/clay.a build/macos-arm64.o
fi
rm -f clay.c build/*.o
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: artifacts-${{ matrix.os }}
path: artifacts/
commit:
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Move artifacts
run: |
cp -r artifacts-ubuntu-latest/* bindings/odin/clay-odin/
cp -r artifacts-macos-latest/* bindings/odin/clay-odin/
- name: Commit/Push changes
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bindings/odin/clay-odin/
git commit -m "[bindings/odin] Update Odin bindings"
git push

3
.gitignore vendored
View file

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

View file

@ -1,7 +1,62 @@
cmake_minimum_required(VERSION 3.28)
project(clay C)
cmake_minimum_required(VERSION 3.27)
project(clay)
set(CMAKE_C_STANDARD 99)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
add_subdirectory("examples/raylib-sidebar-scrolling-container")
option(CLAY_INCLUDE_ALL_EXAMPLES "Build all examples" ON)
option(CLAY_INCLUDE_DEMOS "Build video demo and website" OFF)
option(CLAY_INCLUDE_CPP_EXAMPLE "Build C++ example" OFF)
option(CLAY_INCLUDE_RAYLIB_EXAMPLES "Build raylib examples" OFF)
option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 examples" OFF)
option(CLAY_INCLUDE_SDL3_EXAMPLES "Build SDL 3 examples" OFF)
option(CLAY_INCLUDE_WIN32_GDI_EXAMPLES "Build Win32 GDI examples" OFF)
option(CLAY_INCLUDE_SOKOL_EXAMPLES "Build Sokol examples" OFF)
option(CLAY_INCLUDE_PLAYDATE_EXAMPLES "Build Playdate examples" OFF)
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
if(APPLE)
enable_language(OBJC)
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_CPP_EXAMPLE)
add_subdirectory("examples/cpp-project-example")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_DEMOS)
if(NOT MSVC)
add_subdirectory("examples/clay-official-website")
add_subdirectory("examples/terminal-example")
endif()
add_subdirectory("examples/introducing-clay-video-demo")
endif ()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_RAYLIB_EXAMPLES)
add_subdirectory("examples/raylib-multi-context")
add_subdirectory("examples/raylib-sidebar-scrolling-container")
endif ()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL2_EXAMPLES)
add_subdirectory("examples/SDL2-video-demo")
endif ()
if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES))
add_subdirectory("examples/SDL3-simple-demo")
endif()
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SOKOL_EXAMPLES)
add_subdirectory("examples/sokol-video-demo")
add_subdirectory("examples/sokol-corner-radius")
endif()
# Playdate example not included in ALL because users need to install the playdate SDK first which requires a license agreement
if(CLAY_INCLUDE_PLAYDATE_EXAMPLES)
add_subdirectory("examples/playdate-project-example")
endif()
if(WIN32) # Build only for Win or Wine
if(CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_WIN32_GDI_EXAMPLES)
add_subdirectory("examples/win32_gdi")
endif()
endif()
# add_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now
#add_library(${PROJECT_NAME} INTERFACE)
#target_include_directories(${PROJECT_NAME} INTERFACE .)

2691
README.md

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
0.10

1
bindings/cpp/README.md Normal file
View file

@ -0,0 +1 @@
https://github.com/TimothyHoytBSME/ClayMan

1
bindings/csharp/README Normal file
View file

@ -0,0 +1 @@
https://github.com/Orcolom/clay-cs

4
bindings/odin/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
odin
odin.dSYM
.vscode
examples/clay-official-website/clay-official-website

203
bindings/odin/README.md Normal file
View file

@ -0,0 +1,203 @@
### Odin Language Bindings
This directory contains bindings for the [Odin](odin-lang.org) programming language, as well as an example implementation of the [Clay website](https://nicbarker.com/clay) in Odin.
Special thanks to
- [laytan](https://github.com/laytan)
- [Dudejoe870](https://github.com/Dudejoe870)
- MrStevns from the Odin Discord server
If you haven't taken a look at the [full documentation for Clay](https://github.com/nicbarker/clay/blob/main/README.md), it's recommended that you take a look there first to familiarise yourself with the general concepts. This README is abbreviated and applies to using Clay in Odin specifically.
The **most notable difference** between the C API and the Odin bindings is the use of `if` statements to create the scope for declaring child elements, when using the equivalent of the [Element Macros](https://github.com/nicbarker/clay/blob/main/README.md#element-macros):
```C
// C form of element macros
// Define an element with 16px of x and y padding
CLAY({ .id = CLAY_ID("Outer"), .layout = { .padding = CLAY_PADDING_ALL(16) } }) {
// Child elements here
}
```
```Odin
// Odin form of element macros
if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) {
// Child elements here
}
```
### Quick Start
1. Download the [clay-odin](https://github.com/nicbarker/clay/tree/main/bindings/odin/clay-odin) directory and copy it into your project.
```Odin
import clay "clay-odin"
```
2. Ask Clay for how much static memory it needs using [clay.MinMemorySize()](https://github.com/nicbarker/clay/blob/main/README.md#clay_minmemorysize), create an Arena for it to use with [clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)](https://github.com/nicbarker/clay/blob/main/README.md#clay_createarenawithcapacityandmemory), and initialize it with [clay.Initialize(clay.Arena, clay.Dimensions, clay.ErrorHandler)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
```Odin
error_handler :: proc "c" (errorData: clay.ErrorData) {
// Do something with the error data.
}
min_memory_size := clay.MinMemorySize()
memory := make([^]u8, min_memory_size)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(uint(min_memory_size), memory)
clay.Initialize(arena, {1080, 720}, { handler = error_handler })
```
3. Provide a `measure_text(text, config)` proc "c" with [clay.SetMeasureTextFunction(function)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setmeasuretextfunction) so that Clay can measure and wrap text.
```Odin
// Example measure text function
measure_text :: proc "c" (
text: clay.StringSlice,
config: ^clay.TextElementConfig,
userData: rawptr,
) -> clay.Dimensions {
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing, etc..
// Note: clay.String->chars is not guaranteed to be null terminated
return {
width = f32(text.length * i32(config.fontSize)),
height = f32(config.fontSize),
}
}
// Tell clay how to measure text
clay.SetMeasureTextFunction(measure_text, nil)
```
4. **Optional** - Call [clay.SetPointerState(pointerPosition, isPointerDown)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerstate) if you want to use mouse interactions.
```Odin
// Update internal pointer position for handling mouseover / click / touch events
clay.SetPointerState(
{ mouse_pos_x, mouse_pos_y },
is_mouse_down,
)
```
5. Call [clay.BeginLayout()](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros.
```Odin
// Define some colors.
COLOR_LIGHT :: clay.Color{224, 215, 210, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 255}
COLOR_BLACK :: clay.Color{0, 0, 0, 255}
// Layout config is just a struct that can be declared statically, or inline
sidebar_item_layout := clay.LayoutConfig {
sizing = {
width = clay.SizingGrow({}),
height = clay.SizingFixed(50)
},
}
// Re-useable components are just normal procs.
sidebar_item_component :: proc(index: u32) {
if clay.UI()({
id = clay.ID("SidebarBlob", index),
layout = sidebar_item_layout,
backgroundColor = COLOR_ORANGE,
}) {}
}
// An example function to create your layout tree
create_layout :: proc() -> clay.ClayArray(clay.RenderCommand) {
// Begin constructing the layout.
clay.BeginLayout()
// An example of laying out a UI with a fixed-width sidebar and flexible-width main content
// NOTE: To create a scope for child components, the Odin API uses `if` with components that have children
if clay.UI()({
id = clay.ID("OuterContainer"),
layout = {
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
},
backgroundColor = { 250, 250, 255, 255 },
}) {
if clay.UI()({
id = clay.ID("SideBar"),
layout = {
layoutDirection = .TopToBottom,
sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
},
backgroundColor = COLOR_LIGHT,
}) {
if clay.UI()({
id = clay.ID("ProfilePictureOuter"),
layout = {
sizing = { width = clay.SizingGrow({}) },
padding = { 16, 16, 16, 16 },
childGap = 16,
childAlignment = { y = .Center },
},
backgroundColor = COLOR_RED,
cornerRadius = { 6, 6, 6, 6 },
}) {
if clay.UI()({
id = clay.ID("ProfilePicture"),
layout = {
sizing = { width = clay.SizingFixed(60), height = clay.SizingFixed(60) },
},
image = {
// How you define `profile_picture` depends on your renderer.
imageData = &profile_picture,
sourceDimensions = {
width = 60,
height = 60,
},
},
}) {}
clay.Text(
"Clay - UI Library",
clay.TextConfig({ textColor = COLOR_BLACK, fontSize = 16 }),
)
}
// Standard Odin code like loops, etc. work inside components.
// Here we render 5 sidebar items.
for i in u32(0)..<5 {
sidebar_item_component(i)
}
}
if clay.UI()({
id = clay.ID("MainContent"),
layout = {
sizing = { width = clay.SizingGrow({}), height = clay.SizingGrow({}) },
},
backgroundColor = COLOR_LIGHT,
}) {}
}
// Returns a list of render commands
return clay.EndLayout()
}
```
6. Call your layout proc and process the resulting [clay.ClayArray(clay.RenderCommand)](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer.
```Odin
render_commands := create_layout()
for i in 0..<i32(render_commands.length) {
render_command := clay.RenderCommandArray_Get(render_commands, i)
switch render_command.commandType {
case .Rectangle:
DrawRectangle(render_command.boundingBox, render_command.config.rectangleElementConfig.color)
// ... Implement handling of other command types
}
}
```
Please see the [full C documentation for Clay](https://github.com/nicbarker/clay/blob/main/README.md) for API details. All public C functions and Macros have Odin binding equivalents, generally of the form `CLAY_ID` (C) -> `clay.ID` (Odin).

13
bindings/odin/build-clay-lib.sh Executable file
View file

@ -0,0 +1,13 @@
cp ../../clay.h clay.c;
# Intel Mac
rm -f clay-odin/macos/clay.a && clang -c -DCLAY_IMPLEMENTATION -o clay.o -ffreestanding -static -target x86_64-apple-darwin clay.c -fPIC -O3 && ar r clay-odin/macos/clay.a clay.o;
# ARM Mac
rm -f clay-odin/macos-arm64/clay.a && clang -c -DCLAY_IMPLEMENTATION -g -o clay.o -static clay.c -fPIC -O3 && ar r clay-odin/macos-arm64/clay.a clay.o;
# x64 Windows
rm -f clay-odin/windows/clay.lib && clang -c -DCLAY_IMPLEMENTATION -o clay-odin/windows/clay.lib -ffreestanding -target x86_64-pc-windows-msvc -fuse-ld=llvm-lib -static -O3 clay.c;
# Linux
rm -f clay-odin/linux/clay.a && clang -c -DCLAY_IMPLEMENTATION -o clay.o -ffreestanding -static -target x86_64-unknown-linux-gnu clay.c -fPIC -O3 && ar r clay-odin/linux/clay.a clay.o;
# WASM
rm -f clay-odin/wasm/clay.o && clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay.c;
rm clay.o;
rm clay.c;

View file

@ -0,0 +1,498 @@
package clay
import "core:c"
when ODIN_OS == .Windows {
foreign import Clay "windows/clay.lib"
} else when ODIN_OS == .Linux {
foreign import Clay "linux/clay.a"
} else when ODIN_OS == .Darwin {
when ODIN_ARCH == .arm64 {
foreign import Clay "macos-arm64/clay.a"
} else {
foreign import Clay "macos/clay.a"
}
} else when ODIN_ARCH == .wasm32 || ODIN_ARCH == .wasm64p32 {
foreign import Clay "wasm/clay.o"
}
String :: struct {
isStaticallyAllocated: c.bool,
length: c.int32_t,
chars: [^]c.char,
}
StringSlice :: struct {
length: c.int32_t,
chars: [^]c.char,
baseChars: [^]c.char,
}
Vector2 :: [2]c.float
Dimensions :: struct {
width: c.float,
height: c.float,
}
Arena :: struct {
nextAllocation: uintptr,
capacity: c.size_t,
memory: [^]c.char,
}
BoundingBox :: struct {
x: c.float,
y: c.float,
width: c.float,
height: c.float,
}
Color :: [4]c.float
CornerRadius :: struct {
topLeft: c.float,
topRight: c.float,
bottomLeft: c.float,
bottomRight: c.float,
}
BorderData :: struct {
width: u32,
color: Color,
}
ElementId :: struct {
id: u32,
offset: u32,
baseId: u32,
stringId: String,
}
when ODIN_OS == .Windows {
EnumBackingType :: u32
} else {
EnumBackingType :: u8
}
RenderCommandType :: enum EnumBackingType {
None,
Rectangle,
Border,
Text,
Image,
ScissorStart,
ScissorEnd,
Custom,
}
RectangleElementConfig :: struct {
color: Color,
}
TextWrapMode :: enum EnumBackingType {
Words,
Newlines,
None,
}
TextAlignment :: enum EnumBackingType {
Left,
Center,
Right,
}
TextElementConfig :: struct {
userData: rawptr,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
wrapMode: TextWrapMode,
textAlignment: TextAlignment,
}
AspectRatioElementConfig :: struct {
aspectRatio: f32,
}
ImageElementConfig :: struct {
imageData: rawptr,
}
CustomElementConfig :: struct {
customData: rawptr,
}
BorderWidth :: struct {
left: u16,
right: u16,
top: u16,
bottom: u16,
betweenChildren: u16,
}
BorderElementConfig :: struct {
color: Color,
width: BorderWidth,
}
ClipElementConfig :: struct {
horizontal: bool, // clip overflowing elements on the "X" axis
vertical: bool, // clip overflowing elements on the "Y" axis
childOffset: Vector2, // offsets the [X,Y] positions of all child elements, primarily for scrolling containers
}
FloatingAttachPointType :: enum EnumBackingType {
LeftTop,
LeftCenter,
LeftBottom,
CenterTop,
CenterCenter,
CenterBottom,
RightTop,
RightCenter,
RightBottom,
}
FloatingAttachPoints :: struct {
element: FloatingAttachPointType,
parent: FloatingAttachPointType,
}
PointerCaptureMode :: enum EnumBackingType {
Capture,
Passthrough,
}
FloatingAttachToElement :: enum EnumBackingType {
None,
Parent,
ElementWithId,
Root,
}
FloatingClipToElement :: enum EnumBackingType {
None,
AttachedParent,
}
FloatingElementConfig :: struct {
offset: Vector2,
expand: Dimensions,
parentId: u32,
zIndex: i16,
attachment: FloatingAttachPoints,
pointerCaptureMode: PointerCaptureMode,
attachTo: FloatingAttachToElement,
clipTo: FloatingClipToElement,
}
TextRenderData :: struct {
stringContents: StringSlice,
textColor: Color,
fontId: u16,
fontSize: u16,
letterSpacing: u16,
lineHeight: u16,
}
RectangleRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
}
ImageRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
imageData: rawptr,
}
CustomRenderData :: struct {
backgroundColor: Color,
cornerRadius: CornerRadius,
customData: rawptr,
}
BorderRenderData :: struct {
color: Color,
cornerRadius: CornerRadius,
width: BorderWidth,
}
RenderCommandData :: struct #raw_union {
rectangle: RectangleRenderData,
text: TextRenderData,
image: ImageRenderData,
custom: CustomRenderData,
border: BorderRenderData,
}
RenderCommand :: struct {
boundingBox: BoundingBox,
renderData: RenderCommandData,
userData: rawptr,
id: u32,
zIndex: i16,
commandType: RenderCommandType,
}
ScrollContainerData :: struct {
// Note: This is a pointer to the real internal scroll position, mutating it may cause a change in final layout.
// Intended for use with external functionality that modifies scroll position, such as scroll bars or auto scrolling.
scrollPosition: ^Vector2,
scrollContainerDimensions: Dimensions,
contentDimensions: Dimensions,
config: ClipElementConfig,
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
found: bool,
}
ElementData :: struct {
boundingBox: BoundingBox,
found: bool,
}
PointerDataInteractionState :: enum EnumBackingType {
PressedThisFrame,
Pressed,
ReleasedThisFrame,
Released,
}
PointerData :: struct {
position: Vector2,
state: PointerDataInteractionState,
}
SizingType :: enum EnumBackingType {
Fit,
Grow,
Percent,
Fixed,
}
SizingConstraintsMinMax :: struct {
min: c.float,
max: c.float,
}
SizingConstraints :: struct #raw_union {
sizeMinMax: SizingConstraintsMinMax,
sizePercent: c.float,
}
SizingAxis :: struct {
// Note: `min` is used for CLAY_SIZING_PERCENT, slightly different to clay.h due to lack of C anonymous unions
constraints: SizingConstraints,
type: SizingType,
}
Sizing :: struct {
width: SizingAxis,
height: SizingAxis,
}
Padding :: struct {
left: u16,
right: u16,
top: u16,
bottom: u16,
}
LayoutDirection :: enum EnumBackingType {
LeftToRight,
TopToBottom,
}
LayoutAlignmentX :: enum EnumBackingType {
Left,
Right,
Center,
}
LayoutAlignmentY :: enum EnumBackingType {
Top,
Bottom,
Center,
}
ChildAlignment :: struct {
x: LayoutAlignmentX,
y: LayoutAlignmentY,
}
LayoutConfig :: struct {
sizing: Sizing,
padding: Padding,
childGap: u16,
childAlignment: ChildAlignment,
layoutDirection: LayoutDirection,
}
ClayArray :: struct($type: typeid) {
capacity: i32,
length: i32,
internalArray: [^]type,
}
ElementDeclaration :: struct {
layout: LayoutConfig,
backgroundColor: Color,
cornerRadius: CornerRadius,
aspectRatio: AspectRatioElementConfig,
image: ImageElementConfig,
floating: FloatingElementConfig,
custom: CustomElementConfig,
clip: ClipElementConfig,
border: BorderElementConfig,
userData: rawptr,
}
ErrorType :: enum EnumBackingType {
TextMeasurementFunctionNotProvided,
ArenaCapacityExceeded,
ElementsCapacityExceeded,
TextMeasurementCapacityExceeded,
DuplicateId,
FloatingContainerParentNotFound,
PercentageOver1,
InternalError,
}
ErrorData :: struct {
errorType: ErrorType,
errorText: String,
userData: rawptr,
}
ErrorHandler :: struct {
handler: proc "c" (errorData: ErrorData),
userData: rawptr,
}
Context :: struct {} // opaque structure, only use as a pointer
@(link_prefix = "Clay_", default_calling_convention = "c")
foreign Clay {
_OpenElement :: proc() ---
_OpenElementWithId :: proc(id: ElementId) ---
_CloseElement :: proc() ---
MinMemorySize :: proc() -> u32 ---
CreateArenaWithCapacityAndMemory :: proc(capacity: c.size_t, offset: [^]u8) -> Arena ---
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) -> ^Context ---
GetCurrentContext :: proc() -> ^Context ---
SetCurrentContext :: proc(ctx: ^Context) ---
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
BeginLayout :: proc() ---
EndLayout :: proc() -> ClayArray(RenderCommand) ---
GetElementId :: proc(id: String) -> ElementId ---
GetElementIdWithIndex :: proc(id: String, index: u32) -> ElementId ---
GetElementData :: proc(id: ElementId) -> ElementData ---
Hovered :: proc() -> bool ---
OnHover :: proc(onHoverFunction: proc "c" (id: ElementId, pointerData: PointerData, userData: rawptr), userData: rawptr) ---
PointerOver :: proc(id: ElementId) -> bool ---
GetScrollOffset :: proc() -> Vector2 ---
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: rawptr) -> Dimensions, userData: rawptr) ---
SetQueryScrollOffsetFunction :: proc(queryScrollOffsetFunction: proc "c" (elementId: u32, userData: rawptr) -> Vector2, userData: rawptr) ---
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
SetDebugModeEnabled :: proc(enabled: bool) ---
IsDebugModeEnabled :: proc() -> bool ---
SetCullingEnabled :: proc(enabled: bool) ---
GetMaxElementCount :: proc() -> i32 ---
SetMaxElementCount :: proc(maxElementCount: i32) ---
GetMaxMeasureTextCacheWordCount :: proc() -> i32 ---
SetMaxMeasureTextCacheWordCount :: proc(maxMeasureTextCacheWordCount: i32) ---
ResetMeasureTextCache :: proc() ---
}
@(link_prefix = "Clay_", default_calling_convention = "c", private)
foreign Clay {
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
_HashString :: proc(key: String, seed: u32) -> ElementId ---
_HashStringWithOffset :: proc(key: String, index: u32, seed: u32) -> ElementId ---
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
_GetParentElementId :: proc() -> u32 ---
}
ConfigureOpenElement :: proc(config: ElementDeclaration) -> bool {
_ConfigureOpenElement(config)
return true
}
@(deferred_none = _CloseElement)
UI_WithId :: proc(id: ElementId) -> proc (config: ElementDeclaration) -> bool {
_OpenElementWithId(id)
return ConfigureOpenElement
}
@(deferred_none = _CloseElement)
UI_AutoId :: proc() -> proc (config: ElementDeclaration) -> bool {
_OpenElement()
return ConfigureOpenElement
}
UI :: proc{UI_WithId, UI_AutoId};
Text :: proc($text: string, config: ^TextElementConfig) {
wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
}
TextDynamic :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config)
}
TextConfig :: proc(config: TextElementConfig) -> ^TextElementConfig {
return _StoreTextElementConfig(config)
}
PaddingAll :: proc(allPadding: u16) -> Padding {
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
}
BorderOutside :: proc(width: u16) -> BorderWidth {
return {width, width, width, width, 0}
}
BorderAll :: proc(width: u16) -> BorderWidth {
return {width, width, width, width, width}
}
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
return CornerRadius{radius, radius, radius, radius}
}
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax = {}) -> SizingAxis {
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
}
SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax = {}) -> SizingAxis {
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
}
SizingFixed :: proc(size: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
}
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis {
return SizingAxis{type = SizingType.Percent, constraints = {sizePercent = sizePercent}}
}
MakeString :: proc(label: string) -> String {
return String{chars = raw_data(label), length = cast(c.int)len(label)}
}
ID :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashString(MakeString(label), index)
}
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
return _HashStringWithOffset(MakeString(label), index, _GetParentElementId())
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,515 @@
package main
import clay "../../clay-odin"
import "core:c"
import "core:fmt"
import "vendor:raylib"
windowWidth: i32 = 1024
windowHeight: i32 = 768
syntaxImage: raylib.Texture2D = {}
checkImage1: raylib.Texture2D = {}
checkImage2: raylib.Texture2D = {}
checkImage3: raylib.Texture2D = {}
checkImage4: raylib.Texture2D = {}
checkImage5: raylib.Texture2D = {}
FONT_ID_BODY_16 :: 0
FONT_ID_TITLE_56 :: 9
FONT_ID_TITLE_52 :: 1
FONT_ID_TITLE_48 :: 2
FONT_ID_TITLE_36 :: 3
FONT_ID_TITLE_32 :: 4
FONT_ID_BODY_36 :: 5
FONT_ID_BODY_30 :: 6
FONT_ID_BODY_28 :: 7
FONT_ID_BODY_24 :: 8
COLOR_LIGHT :: clay.Color{244, 235, 230, 255}
COLOR_LIGHT_HOVER :: clay.Color{224, 215, 210, 255}
COLOR_BUTTON_HOVER :: clay.Color{238, 227, 225, 255}
COLOR_BROWN :: clay.Color{61, 26, 5, 255}
//COLOR_RED :: clay.Color {252, 67, 27, 255}
COLOR_RED :: clay.Color{168, 66, 28, 255}
COLOR_RED_HOVER :: clay.Color{148, 46, 8, 255}
COLOR_ORANGE :: clay.Color{225, 138, 50, 255}
COLOR_BLUE :: clay.Color{111, 173, 162, 255}
COLOR_TEAL :: clay.Color{111, 173, 162, 255}
COLOR_BLUE_DARK :: clay.Color{2, 32, 82, 255}
// Colors for top stripe
COLOR_TOP_BORDER_1 :: clay.Color{168, 66, 28, 255}
COLOR_TOP_BORDER_2 :: clay.Color{223, 110, 44, 255}
COLOR_TOP_BORDER_3 :: clay.Color{225, 138, 50, 255}
COLOR_TOP_BORDER_4 :: clay.Color{236, 189, 80, 255}
COLOR_TOP_BORDER_5 :: clay.Color{240, 213, 137, 255}
COLOR_BLOB_BORDER_1 :: clay.Color{168, 66, 28, 255}
COLOR_BLOB_BORDER_2 :: clay.Color{203, 100, 44, 255}
COLOR_BLOB_BORDER_3 :: clay.Color{225, 138, 50, 255}
COLOR_BLOB_BORDER_4 :: clay.Color{236, 159, 70, 255}
COLOR_BLOB_BORDER_5 :: clay.Color{240, 189, 100, 255}
headerTextConfig := clay.TextElementConfig {
fontId = FONT_ID_BODY_24,
fontSize = 24,
textColor = {61, 26, 5, 255},
}
border2pxRed := clay.BorderElementConfig {
width = { 2, 2, 2, 2, 0 },
color = COLOR_RED
}
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, $text: string, image: ^raylib.Texture2D) {
if clay.UI(clay.ID("HeroBlob", index))({
layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } },
border = border2pxRed,
cornerRadius = clay.CornerRadiusAll(10)
}) {
if clay.UI(clay.ID("CheckImage", index))({
layout = { sizing = { width = clay.SizingFixed(32) } },
aspectRatio = { 1.0 },
image = { imageData = image },
}) {}
clay.Text(text, clay.TextConfig({fontSize = fontSize, fontId = fontId, textColor = color}))
}
}
LandingPageDesktop :: proc() {
if clay.UI(clay.ID("LandingPage1Desktop"))({
layout = { sizing = { width = clay.SizingGrow(), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI(clay.ID("LandingPage1"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = { COLOR_RED, { left = 2, right = 2 } },
}) {
if clay.UI(clay.ID("LeftText"))({ layout = { sizing = { width = clay.SizingPercent(0.55) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(
"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()({ 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}),
)
}
if clay.UI(clay.ID("HeroImageOuter"))({
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingPercent(0.45) }, childAlignment = { x = .Center }, childGap = 16 },
}) {
LandingPageBlob(1, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_5, "High performance", &checkImage5)
LandingPageBlob(2, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4)
LandingPageBlob(3, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_3, "Declarative syntax", &checkImage3)
LandingPageBlob(4, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_2, "Single .h file for C/C++", &checkImage2)
LandingPageBlob(5, 30, FONT_ID_BODY_30, COLOR_BLOB_BORDER_1, "Compile to 15kb .wasm", &checkImage1)
}
}
}
}
LandingPageMobile :: proc() {
if clay.UI(clay.ID("LandingPage1Mobile"))({
layout = {
layoutDirection = .TopToBottom,
sizing = { width = clay.SizingGrow(), height = clay.SizingFit({ min = cast(f32)windowHeight - 70 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 32,
},
}) {
if clay.UI(clay.ID("LeftText"))({ layout = { sizing = { width = clay.SizingGrow() }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text(
"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()({ 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}),
)
}
if clay.UI(clay.ID("HeroImageOuter"))({
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow() }, childAlignment = { x = .Center }, childGap = 16 },
}) {
LandingPageBlob(1, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_5, "High performance", &checkImage5)
LandingPageBlob(2, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_4, "Flexbox-style responsive layout", &checkImage4)
LandingPageBlob(3, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_3, "Declarative syntax", &checkImage3)
LandingPageBlob(4, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_2, "Single .h file for C/C++", &checkImage2)
LandingPageBlob(5, 24, FONT_ID_BODY_24, COLOR_BLOB_BORDER_1, "Compile to 15kb .wasm", &checkImage1)
}
}
}
FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) {
textConfig := clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_RED})
if clay.UI(clay.ID("HFileBoxOuter"))({
layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
}) {
if clay.UI(clay.ID("HFileIncludeOuter"))({ layout = { padding = { 8, 8, 4, 4 } }, backgroundColor = COLOR_RED, cornerRadius = clay.CornerRadiusAll(8) }) {
clay.Text("#include clay.h", clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_LIGHT}))
}
clay.Text("~2000 lines of C99.", textConfig)
clay.Text("Zero dependencies, including no C standard library.", textConfig)
}
if clay.UI(clay.ID("BringYourOwnRendererOuter"))({
layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
}) {
clay.Text("Renderer agnostic.", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = COLOR_ORANGE}))
clay.Text("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML.", textConfig)
clay.Text("Flexible output for easy compositing in your custom engine or environment.", textConfig)
}
}
FeatureBlocksDesktop :: proc() {
if clay.UI(clay.ID("FeatureBlocksOuter"))({ layout = { sizing = { width = clay.SizingGrow({}) } } }) {
if clay.UI(clay.ID("FeatureBlocksInner"))({
layout = { sizing = { width = clay.SizingGrow() }, childAlignment = { y = .Center } },
border = { width = { betweenChildren = 2}, color = COLOR_RED },
}) {
FeatureBlocks(clay.SizingPercent(0.5), 50)
}
}
}
FeatureBlocksMobile :: proc() {
if clay.UI(clay.ID("FeatureBlocksInner"))({
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow() } },
border = { width = { betweenChildren = 2}, color = COLOR_RED },
}) {
FeatureBlocks(clay.SizingGrow({}), 16)
}
}
DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("SyntaxPageLeftText"))({ layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text("Declarative Syntax", clay.TextConfig(titleTextConfig))
if clay.UI(clay.ID("SyntaxSpacer"))({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
clay.Text(
"Flexible and readable declarative syntax with nested UI element hierarchies.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
)
clay.Text(
"Mix elements with standard C code like loops, conditionals and functions.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
)
clay.Text(
"Create your own library of re-usable components from UI primitives like text, images and rectangles.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
)
}
if clay.UI(clay.ID("SyntaxPageRightImage"))({ layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI(clay.ID("SyntaxPageRightImageInner"))({
layout = { sizing = { width = clay.SizingGrow({ max = 568 }) } },
aspectRatio = { 1136.0 / 1194.0 },
image = { imageData = &syntaxImage },
}) {}
}
}
DeclarativeSyntaxPageDesktop :: proc() {
if clay.UI(clay.ID("SyntaxPageDesktop"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI(clay.ID("SyntaxPage"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = border2pxRed,
}) {
DeclarativeSyntaxPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5))
}
}
}
DeclarativeSyntaxPageMobile :: proc() {
if clay.UI(clay.ID("SyntaxPageMobile"))({
layout = {
layoutDirection = .TopToBottom,
sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 16,
},
}) {
DeclarativeSyntaxPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({}))
}
}
ColorLerp :: proc(a: clay.Color, b: clay.Color, amount: f32) -> clay.Color {
return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount}
}
LOREM_IPSUM_TEXT :: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("PerformanceLeftText"))({ layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text("High Performance", clay.TextConfig(titleTextConfig))
if clay.UI()({ 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}),
)
clay.Text(
"Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
)
clay.Text(
"Simplify animations and reactive UI design by avoiding the standard performance hacks.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
)
}
if clay.UI(clay.ID("PerformanceRightImageOuter"))({ layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
if clay.UI(clay.ID("PerformanceRightBorder"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(400) } },
border = { COLOR_LIGHT, {2, 2, 2, 2, 2} },
}) {
if clay.UI(clay.ID("AnimationDemoContainerLeft"))({
layout = { sizing = { clay.SizingPercent(0.35 + 0.3 * lerpValue), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue),
}) {
clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
}
if clay.UI(clay.ID("AnimationDemoContainerRight"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(16) },
backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue),
}) {
clay.Text(LOREM_IPSUM_TEXT, clay.TextConfig({fontSize = 16, fontId = FONT_ID_BODY_16, textColor = COLOR_LIGHT}))
}
}
}
}
HighPerformancePageDesktop :: proc(lerpValue: f32) {
if clay.UI(clay.ID("PerformanceDesktop"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { 82, 82, 32, 32 }, childGap = 64 },
backgroundColor = COLOR_RED,
}) {
HighPerformancePage(lerpValue, {fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_LIGHT}, clay.SizingPercent(0.5))
}
}
HighPerformancePageMobile :: proc(lerpValue: f32) {
if clay.UI(clay.ID("PerformanceMobile"))({
layout = {
layoutDirection = .TopToBottom,
sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 32,
},
backgroundColor = COLOR_RED,
}) {
HighPerformancePage(lerpValue, {fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_LIGHT}, clay.SizingGrow({}))
}
}
RendererButtonActive :: proc(index: i32, $text: string) {
if clay.UI()({
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_RED,
cornerRadius = clay.CornerRadiusAll(10)
}) {
clay.Text(text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_LIGHT}))
}
}
RendererButtonInactive :: proc(index: u32, $text: string) {
if clay.UI()({ border = border2pxRed }) {
if clay.UI(clay.ID("RendererButtonInactiveInner", index))({
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_LIGHT,
cornerRadius = clay.CornerRadiusAll(10)
}) {
clay.Text(text, clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}))
}
}
}
RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI(clay.ID("RendererLeftText"))({ layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
clay.Text("Renderer & Platform Agnostic", clay.TextConfig(titleTextConfig))
if clay.UI()({ 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}),
)
clay.Text(
"Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more.",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
)
clay.Text(
"There's even an HTML renderer - you're looking at it right now!",
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
)
}
if clay.UI(clay.ID("RendererRightText"))({
layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center }, layoutDirection = .TopToBottom, childGap = 16 },
}) {
clay.Text("Try changing renderer!", clay.TextConfig({fontSize = 36, fontId = FONT_ID_BODY_36, textColor = COLOR_ORANGE}))
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 32 }) } } }) {}
RendererButtonActive(0, "Raylib Renderer")
}
}
RendererPageDesktop :: proc() {
if clay.UI(clay.ID("RendererPageDesktop"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) }, childAlignment = { y = .Center }, padding = { left = 50, right = 50 } },
}) {
if clay.UI(clay.ID("RendererPage"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
border = { COLOR_RED, { left = 2, right = 2 } },
}) {
RendererPage({fontSize = 52, fontId = FONT_ID_TITLE_52, textColor = COLOR_RED}, clay.SizingPercent(0.5))
}
}
}
RendererPageMobile :: proc() {
if clay.UI(clay.ID("RendererMobile"))({
layout = {
layoutDirection = .TopToBottom,
sizing = { clay.SizingGrow(), clay.SizingFit({ min = cast(f32)windowHeight - 50 }) },
childAlignment = { x = .Center, y = .Center },
padding = { 16, 16, 32, 32 },
childGap = 32,
},
backgroundColor = COLOR_LIGHT,
}) {
RendererPage({fontSize = 48, fontId = FONT_ID_TITLE_48, textColor = COLOR_RED}, clay.SizingGrow({}))
}
}
ScrollbarData :: struct {
clickOrigin: clay.Vector2,
positionOrigin: clay.Vector2,
mouseDown: bool,
}
scrollbarData := ScrollbarData{}
animationLerpValue: f32 = -1.0
createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
mobileScreen := windowWidth < 750
clay.BeginLayout()
if clay.UI(clay.ID("OuterContainer"))({
layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow(), clay.SizingGrow() } },
backgroundColor = COLOR_LIGHT,
}) {
if clay.UI(clay.ID("Header"))({
layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(50) }, childAlignment = { y = .Center }, childGap = 24, padding = { left = 32, right = 32 } },
}) {
clay.Text("Clay", &headerTextConfig)
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow() } } }) {}
if (!mobileScreen) {
if clay.UI(clay.ID("LinkExamplesOuter"))({ backgroundColor = {0, 0, 0, 0} }) {
clay.Text("Examples", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
if clay.UI(clay.ID("LinkDocsOuter"))({ backgroundColor = {0, 0, 0, 0} }) {
clay.Text("Docs", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
}
if clay.UI(clay.ID("LinkGithubOuter"))({
layout = { padding = { 16, 16, 6, 6 } },
border = border2pxRed,
backgroundColor = clay.Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
cornerRadius = clay.CornerRadiusAll(10)
}) {
clay.Text("Github", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
}
}
if clay.UI(clay.ID("TopBorder1"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
if clay.UI(clay.ID("TopBorder2"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
if clay.UI(clay.ID("TopBorder3"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
if clay.UI(clay.ID("TopBorder4"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
if clay.UI(clay.ID("TopBorder5"))({ layout = { sizing = { clay.SizingGrow(), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
if clay.UI(clay.ID("ScrollContainerBackgroundRectangle"))({
clip = { vertical = true, childOffset = clay.GetScrollOffset() },
layout = { sizing = { clay.SizingGrow(), clay.SizingGrow() }, layoutDirection = clay.LayoutDirection.TopToBottom },
backgroundColor = COLOR_LIGHT,
border = { COLOR_RED, { betweenChildren = 2} },
}) {
if (!mobileScreen) {
LandingPageDesktop()
FeatureBlocksDesktop()
DeclarativeSyntaxPageDesktop()
HighPerformancePageDesktop(lerpValue)
RendererPageDesktop()
} else {
LandingPageMobile()
FeatureBlocksMobile()
DeclarativeSyntaxPageMobile()
HighPerformancePageMobile(lerpValue)
RendererPageMobile()
}
}
}
return clay.EndLayout()
}
loadFont :: proc(fontId: u16, fontSize: u16, path: cstring) {
assign_at(&raylib_fonts,fontId,Raylib_Font{
font = raylib.LoadFontEx(path, cast(i32)fontSize * 2, nil, 0),
fontId = cast(u16)fontId,
})
raylib.SetTextureFilter(raylib_fonts[fontId].font.texture, raylib.TextureFilter.TRILINEAR)
}
errorHandler :: proc "c" (errorData: clay.ErrorData) {
if (errorData.errorType == clay.ErrorType.DuplicateId) {
// etc
}
}
main :: proc() {
minMemorySize: c.size_t = cast(c.size_t)clay.MinMemorySize()
memory := make([^]u8, minMemorySize)
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
clay.Initialize(arena, {cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()}, { handler = errorHandler })
clay.SetMeasureTextFunction(measure_text, nil)
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .MSAA_4X_HINT})
raylib.InitWindow(windowWidth, windowHeight, "Raylib Odin Example")
raylib.SetTargetFPS(raylib.GetMonitorRefreshRate(0))
loadFont(FONT_ID_TITLE_56, 56, "resources/Calistoga-Regular.ttf")
loadFont(FONT_ID_TITLE_52, 52, "resources/Calistoga-Regular.ttf")
loadFont(FONT_ID_TITLE_48, 48, "resources/Calistoga-Regular.ttf")
loadFont(FONT_ID_TITLE_36, 36, "resources/Calistoga-Regular.ttf")
loadFont(FONT_ID_TITLE_32, 32, "resources/Calistoga-Regular.ttf")
loadFont(FONT_ID_BODY_36, 36, "resources/Quicksand-Semibold.ttf")
loadFont(FONT_ID_BODY_30, 30, "resources/Quicksand-Semibold.ttf")
loadFont(FONT_ID_BODY_28, 28, "resources/Quicksand-Semibold.ttf")
loadFont(FONT_ID_BODY_24, 24, "resources/Quicksand-Semibold.ttf")
loadFont(FONT_ID_BODY_16, 16, "resources/Quicksand-Semibold.ttf")
syntaxImage = raylib.LoadTexture("resources/declarative.png")
checkImage1 = raylib.LoadTexture("resources/check_1.png")
checkImage2 = raylib.LoadTexture("resources/check_2.png")
checkImage3 = raylib.LoadTexture("resources/check_3.png")
checkImage4 = raylib.LoadTexture("resources/check_4.png")
checkImage5 = raylib.LoadTexture("resources/check_5.png")
debugModeEnabled: bool = false
for !raylib.WindowShouldClose() {
defer free_all(context.temp_allocator)
animationLerpValue += raylib.GetFrameTime()
if animationLerpValue > 1 {
animationLerpValue = animationLerpValue - 2
}
windowWidth = raylib.GetScreenWidth()
windowHeight = raylib.GetScreenHeight()
if (raylib.IsKeyPressed(.D)) {
debugModeEnabled = !debugModeEnabled
clay.SetDebugModeEnabled(debugModeEnabled)
}
clay.SetPointerState(transmute(clay.Vector2)raylib.GetMousePosition(), raylib.IsMouseButtonDown(raylib.MouseButton.LEFT))
clay.UpdateScrollContainers(false, transmute(clay.Vector2)raylib.GetMouseWheelMoveV(), raylib.GetFrameTime())
clay.SetLayoutDimensions({cast(f32)raylib.GetScreenWidth(), cast(f32)raylib.GetScreenHeight()})
renderCommands: clay.ClayArray(clay.RenderCommand) = createLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue))
raylib.BeginDrawing()
clay_raylib_render(&renderCommands)
raylib.EndDrawing()
}
}

View file

@ -0,0 +1,201 @@
package main
import clay "../../clay-odin"
import "core:math"
import "core:strings"
import rl "vendor:raylib"
Raylib_Font :: struct {
fontId: u16,
font: rl.Font,
}
clay_color_to_rl_color :: proc(color: clay.Color) -> rl.Color {
return {u8(color.r), u8(color.g), u8(color.b), u8(color.a)}
}
raylib_fonts := [dynamic]Raylib_Font{}
measure_text :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr) -> clay.Dimensions {
line_width: f32 = 0
font := raylib_fonts[config.fontId].font
for i in 0 ..< text.length {
glyph_index := text.chars[i] - 32
glyph := font.glyphs[glyph_index]
if glyph.advanceX != 0 {
line_width += f32(glyph.advanceX)
} else {
line_width += font.recs[glyph_index].width + f32(glyph.offsetX)
}
}
return {width = line_width / 2, height = f32(config.fontSize)}
}
clay_raylib_render :: proc(render_commands: ^clay.ClayArray(clay.RenderCommand), allocator := context.temp_allocator) {
for i in 0 ..< render_commands.length {
render_command := clay.RenderCommandArray_Get(render_commands, i)
bounds := render_command.boundingBox
switch render_command.commandType {
case .None: // None
case .Text:
config := render_command.renderData.text
text := string(config.stringContents.chars[:config.stringContents.length])
// Raylib uses C strings instead of Odin strings, so we need to clone
// Assume this will be freed elsewhere since we default to the temp allocator
cstr_text := strings.clone_to_cstring(text, allocator)
font := raylib_fonts[config.fontId].font
rl.DrawTextEx(font, cstr_text, {bounds.x, bounds.y}, f32(config.fontSize), f32(config.letterSpacing), clay_color_to_rl_color(config.textColor))
case .Image:
config := render_command.renderData.image
tint := config.backgroundColor
if tint == 0 {
tint = {255, 255, 255, 255}
}
imageTexture := (^rl.Texture2D)(config.imageData)
rl.DrawTextureEx(imageTexture^, {bounds.x, bounds.y}, 0, bounds.width / f32(imageTexture.width), clay_color_to_rl_color(tint))
case .ScissorStart:
rl.BeginScissorMode(i32(math.round(bounds.x)), i32(math.round(bounds.y)), i32(math.round(bounds.width)), i32(math.round(bounds.height)))
case .ScissorEnd:
rl.EndScissorMode()
case .Rectangle:
config := render_command.renderData.rectangle
if config.cornerRadius.topLeft > 0 {
radius: f32 = (config.cornerRadius.topLeft * 2) / min(bounds.width, bounds.height)
draw_rect_rounded(bounds.x, bounds.y, bounds.width, bounds.height, radius, config.backgroundColor)
} else {
draw_rect(bounds.x, bounds.y, bounds.width, bounds.height, config.backgroundColor)
}
case .Border:
config := render_command.renderData.border
// Left border
if config.width.left > 0 {
draw_rect(
bounds.x,
bounds.y + config.cornerRadius.topLeft,
f32(config.width.left),
bounds.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft,
config.color,
)
}
// Right border
if config.width.right > 0 {
draw_rect(
bounds.x + bounds.width - f32(config.width.right),
bounds.y + config.cornerRadius.topRight,
f32(config.width.right),
bounds.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight,
config.color,
)
}
// Top border
if config.width.top > 0 {
draw_rect(
bounds.x + config.cornerRadius.topLeft,
bounds.y,
bounds.width - config.cornerRadius.topLeft - config.cornerRadius.topRight,
f32(config.width.top),
config.color,
)
}
// Bottom border
if config.width.bottom > 0 {
draw_rect(
bounds.x + config.cornerRadius.bottomLeft,
bounds.y + bounds.height - f32(config.width.bottom),
bounds.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight,
f32(config.width.bottom),
config.color,
)
}
// Rounded Borders
if config.cornerRadius.topLeft > 0 {
draw_arc(
bounds.x + config.cornerRadius.topLeft,
bounds.y + config.cornerRadius.topLeft,
config.cornerRadius.topLeft - f32(config.width.top),
config.cornerRadius.topLeft,
180,
270,
config.color,
)
}
if config.cornerRadius.topRight > 0 {
draw_arc(
bounds.x + bounds.width - config.cornerRadius.topRight,
bounds.y + config.cornerRadius.topRight,
config.cornerRadius.topRight - f32(config.width.top),
config.cornerRadius.topRight,
270,
360,
config.color,
)
}
if config.cornerRadius.bottomLeft > 0 {
draw_arc(
bounds.x + config.cornerRadius.bottomLeft,
bounds.y + bounds.height - config.cornerRadius.bottomLeft,
config.cornerRadius.bottomLeft - f32(config.width.top),
config.cornerRadius.bottomLeft,
90,
180,
config.color,
)
}
if config.cornerRadius.bottomRight > 0 {
draw_arc(
bounds.x + bounds.width - config.cornerRadius.bottomRight,
bounds.y + bounds.height - config.cornerRadius.bottomRight,
config.cornerRadius.bottomRight - f32(config.width.bottom),
config.cornerRadius.bottomRight,
0.1,
90,
config.color,
)
}
case clay.RenderCommandType.Custom:
// Implement custom element rendering here
}
}
}
// Helper procs, mainly for repeated conversions
@(private = "file")
draw_arc :: proc(x, y: f32, inner_rad, outer_rad: f32,start_angle, end_angle: f32, color: clay.Color){
rl.DrawRing(
{math.round(x),math.round(y)},
math.round(inner_rad),
outer_rad,
start_angle,
end_angle,
10,
clay_color_to_rl_color(color),
)
}
@(private = "file")
draw_rect :: proc(x, y, w, h: f32, color: clay.Color) {
rl.DrawRectangle(
i32(math.round(x)),
i32(math.round(y)),
i32(math.round(w)),
i32(math.round(h)),
clay_color_to_rl_color(color)
)
}
@(private = "file")
draw_rect_rounded :: proc(x,y,w,h: f32, radius: f32, color: clay.Color){
rl.DrawRectangleRounded({x,y,w,h},radius,8,clay_color_to_rl_color(color))
}

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: 193 KiB

View file

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json",
"character_width": 180,
"sort_imports": true,
"tabs": false
}

6
bindings/odin/ols.json Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/ols.schema.json",
"enable_document_symbols": true,
"enable_hover": true,
"enable_snippets": true
}

2
bindings/rust/README Normal file
View file

@ -0,0 +1,2 @@
https://github.com/clay-ui-rs/clay
https://crates.io/crates/clay-layout

2
bindings/zig/README Normal file
View file

@ -0,0 +1,2 @@
https://codeberg.org/Zettexe/clay-zig
https://github.com/johan0A/clay-zig-bindings

5277
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,59 @@
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)
FetchContent_Declare(
SDL2_image
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git"
GIT_TAG "release-2.8.4"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(SDL2_image)
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
SDL2_image::SDL2_image-static
)
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
add_custom_command(
TARGET SDL2_video_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources
)

View file

@ -0,0 +1,173 @@
#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>
#include "../shared-layouts/clay-video-demo.c"
SDL_Surface *sample_image;
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
struct ResizeRenderData_ {
SDL_Window* window;
int windowWidth;
int windowHeight;
ClayVideoDemo_Data demoData;
SDL_Renderer* renderer;
SDL2_Font* fonts;
};
typedef struct ResizeRenderData_ ResizeRenderData;
int resizeRendering(void* userData, SDL_Event* event) {
ResizeRenderData *actualData = userData;
if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED) {
SDL_Window* window = actualData->window;
int windowWidth = actualData->windowWidth;
int windowHeight = actualData->windowHeight;
ClayVideoDemo_Data demoData = actualData->demoData;
SDL_Renderer* renderer = actualData->renderer;
SDL2_Font* fonts = actualData->fonts;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_SetLayoutDimensions((Clay_Dimensions) { (float)windowWidth, (float)windowHeight });
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
Clay_SDL2_Render(renderer, renderCommands, fonts);
SDL_RenderPresent(renderer);
}
return 0;
}
int main(int argc, char *argv[]) {
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
fprintf(stderr, "Error: could not initialize SDL: %s\n", SDL_GetError());
return 1;
}
if (TTF_Init() < 0) {
fprintf(stderr, "Error: could not initialize TTF: %s\n", TTF_GetError());
return 1;
}
if (IMG_Init(IMG_INIT_PNG) < 0) {
fprintf(stderr, "Error: could not initialize IMG: %s\n", IMG_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_Font fonts[1] = {};
fonts[FONT_ID_BODY_16] = (SDL2_Font) {
.fontId = FONT_ID_BODY_16,
.font = font,
};
sample_image = IMG_Load("resources/sample.png");
SDL_Window *window = NULL;
SDL_Renderer *renderer = NULL;
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); //for antialiasing
window = SDL_CreateWindow("SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 800, 600, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); //for antialiasing
bool enableVsync = false;
if(enableVsync){ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);} //"SDL_RENDERER_ACCELERATED" is for antialiasing
else{renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);}
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); //for alpha blending
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
int windowWidth = 0;
int windowHeight = 0;
SDL_GetWindowSize(window, &windowWidth, &windowHeight);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)windowWidth, (float)windowHeight }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(SDL2_MeasureText, &fonts);
Uint64 NOW = SDL_GetPerformanceCounter();
Uint64 LAST = 0;
double deltaTime = 0;
ClayVideoDemo_Data demoData = ClayVideoDemo_Initialize();
ResizeRenderData userData = {
window, // SDL_Window*
windowWidth, // int
windowHeight, // int
demoData, // CustomShit
renderer, // SDL_Renderer*
fonts // SDL2_Font[1]
};
// add an event watcher that will render the screen while youre dragging the window to different sizes
SDL_AddEventWatch(resizeRendering, &userData);
while (true) {
Clay_Vector2 scrollDelta = {};
SDL_Event event;
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() );
printf("%f\n", deltaTime);
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 = ClayVideoDemo_CreateLayout(&demoData);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
Clay_SDL2_Render(renderer, renderCommands, fonts);
SDL_RenderPresent(renderer);
}
quit:
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
IMG_Quit();
TTF_Quit();
SDL_Quit();
return 0;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

View file

@ -0,0 +1,62 @@
cmake_minimum_required(VERSION 3.27)
# Project setup
project(clay_examples_sdl3_simple_demo C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# Download SDL3
FetchContent_Declare(
SDL
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
GIT_TAG release-3.2.4
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
message(STATUS "Using SDL via FetchContent")
FetchContent_MakeAvailable(SDL)
set_property(DIRECTORY "${sdl_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
# Download SDL_ttf
FetchContent_Declare(
SDL_ttf
GIT_REPOSITORY https://github.com/libsdl-org/SDL_ttf.git
GIT_TAG release-3.2.2
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
message(STATUS "Using SDL_ttf via FetchContent")
FetchContent_MakeAvailable(SDL_ttf)
set_property(DIRECTORY "${sdl_ttf_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
# Download SDL_image
FetchContent_Declare(
SDL_image
GIT_REPOSITORY "https://github.com/libsdl-org/SDL_image.git"
GIT_TAG release-3.2.0
GIT_SHALLOW TRUE
GIT_PROGRESS TRUE
)
message(STATUS "Using SDL_image via FetchContent")
FetchContent_MakeAvailable(SDL_image)
set_property(DIRECTORY "${SDL_image_SOURCE_DIR}" PROPERTY EXCLUDE_FROM_ALL TRUE)
# Example executable
add_executable(${PROJECT_NAME} main.c)
target_link_libraries(${PROJECT_NAME} PRIVATE
SDL3::SDL3
SDL3_ttf::SDL3_ttf
SDL3_image::SDL3_image
)
add_custom_command(
TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources
)

View file

@ -0,0 +1,232 @@
#define SDL_MAIN_USE_CALLBACKS
#include <SDL3/SDL_main.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include <stdio.h>
#include "../../renderers/SDL3/clay_renderer_SDL3.c"
#include "../shared-layouts/clay-video-demo.c"
static const Uint32 FONT_ID = 0;
static const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
static const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
typedef struct app_state {
SDL_Window *window;
Clay_SDL3RendererData rendererData;
ClayVideoDemo_Data demoData;
} AppState;
SDL_Texture *sample_image;
bool show_demo = true;
static inline Clay_Dimensions SDL_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
{
TTF_Font **fonts = userData;
TTF_Font *font = fonts[config->fontId];
int width, height;
TTF_SetFontSize(font, config->fontSize);
if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to measure text: %s", SDL_GetError());
}
return (Clay_Dimensions) { (float) width, (float) height };
}
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
Clay_RenderCommandArray ClayImageSample_CreateLayout() {
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
CLAY(CLAY_ID("OuterContainer"), {
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(16),
.childGap = 16
}
}) {
CLAY(CLAY_ID("SampleImage"), {
.layout = {
.sizing = layoutExpand
},
.aspectRatio = { 23.0 / 42.0 },
.image = {
.imageData = sample_image,
}
});
}
return Clay_EndLayout();
}
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
{
(void) argc;
(void) argv;
if (!TTF_Init()) {
return SDL_APP_FAILURE;
}
AppState *state = SDL_calloc(1, sizeof(AppState));
if (!state) {
return SDL_APP_FAILURE;
}
*appstate = state;
if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->rendererData.renderer)) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create window and renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
SDL_SetWindowResizable(state->window, true);
state->rendererData.textEngine = TTF_CreateRendererTextEngine(state->rendererData.renderer);
if (!state->rendererData.textEngine) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to create text engine from renderer: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
state->rendererData.fonts = SDL_calloc(1, sizeof(TTF_Font *));
if (!state->rendererData.fonts) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to allocate memory for the font array: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
TTF_Font *font = TTF_OpenFont("resources/Roboto-Regular.ttf", 24);
if (!font) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load font: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
state->rendererData.fonts[FONT_ID] = font;
sample_image = IMG_LoadTexture(state->rendererData.renderer, "resources/sample.png");
if (!sample_image) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Failed to load image: %s", SDL_GetError());
return SDL_APP_FAILURE;
}
/* Initialize Clay */
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = (Clay_Arena) {
.memory = SDL_malloc(totalMemorySize),
.capacity = totalMemorySize
};
int width, height;
SDL_GetWindowSize(state->window, &width, &height);
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float) width, (float) height }, (Clay_ErrorHandler) { HandleClayErrors });
Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts);
state->demoData = ClayVideoDemo_Initialize();
*appstate = state;
return SDL_APP_CONTINUE;
}
SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
{
SDL_AppResult ret_val = SDL_APP_CONTINUE;
switch (event->type) {
case SDL_EVENT_QUIT:
ret_val = SDL_APP_SUCCESS;
break;
case SDL_EVENT_KEY_UP:
if (event->key.scancode == SDL_SCANCODE_SPACE) {
show_demo = !show_demo;
}
break;
case SDL_EVENT_WINDOW_RESIZED:
Clay_SetLayoutDimensions((Clay_Dimensions) { (float) event->window.data1, (float) event->window.data2 });
break;
case SDL_EVENT_MOUSE_MOTION:
Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y },
event->motion.state & SDL_BUTTON_LMASK);
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
Clay_SetPointerState((Clay_Vector2) { event->button.x, event->button.y },
event->button.button == SDL_BUTTON_LEFT);
break;
case SDL_EVENT_MOUSE_WHEEL:
Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f);
break;
default:
break;
};
return ret_val;
}
SDL_AppResult SDL_AppIterate(void *appstate)
{
AppState *state = appstate;
Clay_RenderCommandArray render_commands = (show_demo
? ClayVideoDemo_CreateLayout(&state->demoData)
: ClayImageSample_CreateLayout()
);
SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255);
SDL_RenderClear(state->rendererData.renderer);
SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands);
SDL_RenderPresent(state->rendererData.renderer);
return SDL_APP_CONTINUE;
}
void SDL_AppQuit(void *appstate, SDL_AppResult result)
{
(void) result;
if (result != SDL_APP_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Application failed to run");
}
AppState *state = appstate;
if (sample_image) {
SDL_DestroyTexture(sample_image);
}
if (state) {
if (state->rendererData.renderer)
SDL_DestroyRenderer(state->rendererData.renderer);
if (state->window)
SDL_DestroyWindow(state->window);
if (state->rendererData.fonts) {
for(size_t i = 0; i < sizeof(state->rendererData.fonts) / sizeof(*state->rendererData.fonts); i++) {
TTF_CloseFont(state->rendererData.fonts[i]);
}
SDL_free(state->rendererData.fonts);
}
if (state->rendererData.textEngine)
TTF_DestroyRendererTextEngine(state->rendererData.textEngine);
SDL_free(state);
}
TTF_Quit();
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

View file

@ -0,0 +1,22 @@
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)
find_package(Cairo REQUIRED)
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 "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
add_custom_command(
TARGET clay_examples_cairo_pdf_rendering POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,171 @@
// Copyright (c) 2024 Justin Andreas Lacoste (@27justin)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the
// use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software in a
// product, an acknowledgment in the product documentation would be
// appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
// be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
// distribution.
//
// SPDX-License-Identifier: Zlib
#include <stdlib.h>
// The renderer includes clay.h while also providing the
// CLAY_IMPLEMENTATION
#include "../../renderers/cairo/clay_renderer_cairo.c"
// cairo-pdf, though this is optional and not required if you,
// e.g. render PNGs.
#include <cairo/cairo-pdf.h>
const uint16_t FONT_CALLISTOGA = 0;
const uint16_t FONT_QUICKSAND = 0;
// Layout the first page.
void Layout() {
static Clay_Color PRIMARY = { 0xa8, 0x42, 0x1c, 255 };
static Clay_Color BACKGROUND = { 0xF4, 0xEB, 0xE6, 255 };
static Clay_Color ACCENT = { 0xFA, 0xE0, 0xD4, 255 };
CLAY_AUTO_ID({
.layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) },
.layoutDirection = CLAY_TOP_TO_BOTTOM },
.backgroundColor = BACKGROUND
}) {
CLAY(CLAY_ID("PageMargins"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) },
.padding = { 70, 70, 50, 50 }, // Some nice looking page margins
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 10}
}) {
// Section Title
CLAY_TEXT(CLAY_STRING("Features Overview"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .textColor = PRIMARY, .fontSize = 24 }));
// Feature Box
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }, .childGap = 10 }}) {
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }}, .backgroundColor = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(12) }) {
CLAY_AUTO_ID({ .layout = {.padding = CLAY_PADDING_ALL(20), .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) {
CLAY_TEXT(CLAY_STRING("- High performance"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- Declarative syntax"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- Flexbox-style responsive layout"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- Single .h file for C/C++"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
CLAY_TEXT(CLAY_STRING("- And now with cairo!"),
CLAY_TEXT_CONFIG({ .textColor = PRIMARY, .fontSize = 14, .fontId = FONT_QUICKSAND }));
}
}
CLAY_AUTO_ID({
.layout = {
.sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)},
.padding = CLAY_PADDING_ALL(10),
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER },
.childGap = 4
},
.backgroundColor = ACCENT,
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Profile picture
CLAY_AUTO_ID({ .layout = {
.sizing = {CLAY_SIZING_FIT(0), CLAY_SIZING_GROW(0)},
.padding = { 30, 30, 0, 0 },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = { CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER }},
.border = { .color = PRIMARY, .width = 2, 2, 2, 2 }, .cornerRadius = 10
}) {
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .imageData = "resources/check.png" }});
}
}
}
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(16) } }});
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childGap = 10, .layoutDirection = CLAY_TOP_TO_BOTTOM }}) {
CLAY_TEXT(CLAY_STRING("Cairo"), CLAY_TEXT_CONFIG({ .fontId = FONT_CALLISTOGA, .fontSize = 24, .textColor = PRIMARY }));
CLAY_AUTO_ID({ .layout = { .padding = CLAY_PADDING_ALL(10) }, .backgroundColor = ACCENT, .cornerRadius = 10 }) {
CLAY_TEXT(CLAY_STRING("Officiis quia quia qui inventore ratione voluptas et. Quidem sunt unde similique. Qui est et exercitationem cumque harum illum. Numquam placeat aliquid quo voluptatem. "
"Deleniti saepe nihil exercitationem nemo illo. Consequatur beatae repellat provident similique. Provident qui exercitationem deserunt sapiente. Quam qui dolor corporis odit. "
"Assumenda corrupti sunt culpa pariatur. Vero sit ut minima. In est consequatur minus et cum sint illum aperiam. Qui ipsa quas nisi omnis aut quia nobis. "
"Corporis deserunt eum mollitia modi rerum voluptas. Expedita non ab esse. Sit voluptates eos voluptatem labore aspernatur quia eum. Modi cumque atque non. Sunt officiis corrupti neque ut inventore excepturi rem minima. Possimus sed soluta qui ea aut ipsum laborum fugit. "
"Voluptate eum consectetur non. Quo autem voluptate soluta atque dolorum maxime. Officiis inventore omnis eveniet beatae ipsa optio. Unde voluptatum ut autem quia sit sit et. Ut inventore qui quia totam consequatur. Sit ea consequatur omnis rerum nulla aspernatur deleniti."), CLAY_TEXT_CONFIG({ .fontId = FONT_QUICKSAND, .fontSize = 16, .textColor = PRIMARY, .lineHeight = 16 }));
}
}
}
}
}
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(void) {
// First we set up our cairo surface.
// In this example we will use the PDF backend,
// but you should be able to use any of them.
// Guaranteed to be working are: PDF, PNG
// Create a PDF surface that is the same size as a DIN A4 sheet
// When using the PDF backend, cairo calculates in points (1 point == 1/72.0 inch)
double width = (21.0 / 2.54) * 72, // cm in points
height = (29.7 / 2.54) * 72;
cairo_surface_t *surface = cairo_pdf_surface_create("output.pdf", width, height);
cairo_t *cr = cairo_create(surface);
cairo_surface_destroy(surface); // Drop reference
// Initialize internal global variable with `cr`.
// We require some kind of global reference to a valid
// cairo instance to properly measure text.
// Note that due to this, this interface is not thread-safe!
Clay_Cairo_Initialize(cr);
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
// We initialize Clay with the same size
Clay_Initialize(clayMemory, (Clay_Dimensions) { width, height }, (Clay_ErrorHandler) { HandleClayErrors });
char** fonts = (char*[]) {
"Callistoga",
"Quicksand Semibold"
};
Clay_SetMeasureTextFunction(Clay_Cairo_MeasureText, fonts);
Clay_BeginLayout();
// Here you can now create the declarative clay layout.
// Moved into a separate function for brevity.
Layout();
Clay_RenderCommandArray commands = Clay_EndLayout();
// Pass our layout to the cairo backend
Clay_Cairo_Render(commands, fonts);
// To keep this example short, we will not emit a second page in the PDF.
// But to do so, you have to
// 1. cairo_show_page(cr)
// 2. Clay_BeginLayout();
// 3. Create your layout
// 4. commands = Clay_EndLayout();
// 5. Clay_Cairo_Render(commands);
cairo_destroy(cr);
return 0;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,17 +1,9 @@
cmake_minimum_required(VERSION 3.28)
cmake_minimum_required(VERSION 3.27)
project(clay_official_website C)
set(CMAKE_C_STANDARD 99)
add_executable(clay_official_website main.c)
target_compile_options(clay_official_website PUBLIC -DCLAY_OVERFLOW_TRAP -Wno-initializer-overrides)
target_compile_options(clay_official_website PUBLIC)
target_include_directories(clay_official_website PUBLIC .)
set(CMAKE_CXX_FLAGS_RELEASE "-O3")
add_custom_command(
TARGET clay_official_website POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -1,5 +1,7 @@
mkdir -p build/clay \
&& clang \
-Wall \
-Werror \
-Os \
-DCLAY_WASM \
-mbulk-memory \

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,857 @@
<!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 dimensionsDefinition = { type: 'struct', members: [
{name: 'width', type: 'float'},
{name: 'height', type: 'float'},
]};
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: 'isStaticallyAllocated', type: 'uint32_t'},
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
]};
let stringSliceDefinition = { type: 'struct', members: [
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
{name: 'baseChars', type: 'uint32_t' },
]};
let borderWidthDefinition = { type: 'struct', members: [
{name: 'left', type: 'uint16_t'},
{name: 'right', type: 'uint16_t'},
{name: 'top', type: 'uint16_t'},
{name: 'bottom', type: 'uint16_t'},
{name: 'betweenChildren', type: 'uint16_t'},
]};
let cornerRadiusDefinition = { type: 'struct', members: [
{name: 'topLeft', type: 'float'},
{name: 'topRight', type: 'float'},
{name: 'bottomLeft', type: 'float'},
{name: 'bottomRight', type: 'float'},
]};
let textConfigDefinition = { name: 'text', type: 'struct', members: [
{ name: 'userData', type: 'uint32_t' },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineSpacing', type: 'uint16_t' },
{ name: 'wrapMode', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: '_padding', type: 'uint16_t' },
]};
let textRenderDataDefinition = { type: 'struct', members: [
{ name: 'stringContents', ...stringSliceDefinition },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineHeight', type: 'uint16_t' },
]};
let rectangleRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
]};
let imageRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'imageData', type: 'uint32_t' },
]};
let customRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'customData', type: 'uint32_t' },
]};
let borderRenderDataDefinition = { type: 'struct', members: [
{ name: 'color', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'width', ...borderWidthDefinition },
{ name: 'padding', type: 'uint16_t'}
]};
let clipRenderDataDefinition = { type: 'struct', members: [
{ name: 'horizontal', type: 'bool' },
{ name: 'vertical', type: 'bool' },
]};
let customHTMLDataDefinition = { type: 'struct', members: [
{ name: 'link', ...stringDefinition },
{ name: 'cursorPointer', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: 'padding', type: 'uint16_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: 'renderData', type: 'union', members: [
{ name: 'rectangle', ...rectangleRenderDataDefinition },
{ name: 'text', ...textRenderDataDefinition },
{ name: 'image', ...imageRenderDataDefinition },
{ name: 'custom', ...customRenderDataDefinition },
{ name: 'border', ...borderRenderDataDefinition },
{ name: 'clip', ...clipRenderDataDefinition },
]},
{ name: 'userData', type: 'uint32_t'},
{ name: 'id', type: 'uint32_t' },
{ name: 'zIndex', type: 'int16_t' },
{ name: 'commandType', type: 'uint8_t' },
{ name: '_padding', type: 'uint8_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 'int16_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 'int16_t': return { value: memoryDataView.getInt16(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, userData) => {
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;
let clayScratchSpaceAddress = instance.exports.__heap_base.value + 1024;
heapSpaceAddress = instance.exports.__heap_base.value + 2048;
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);
instance.exports.SetScratchMemory(clayScratchSpaceAddress);
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 SetElementBackgroundColorAndRadius(element, cornerRadius, backgroundColor) {
element.style.backgroundColor = `rgba(${backgroundColor.r.value}, ${backgroundColor.g.value}, ${backgroundColor.b.value}, ${backgroundColor.a.value / 255})`;
if (cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = cornerRadius.topLeft.value + 'px';
}
if (cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = cornerRadius.topRight.value + 'px';
}
if (cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = cornerRadius.bottomLeft.value + 'px';
}
if (cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = cornerRadius.bottomRight.value + 'px';
}
}
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 Uint8Array(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 & 0xff) {
case CLAY_RENDER_COMMAND_TYPE_TEXT:
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (renderCommand.userData.value !== 0) {
if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).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 = renderCommand.renderData.rectangle;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
SetElementBackgroundColorAndRadius(element, config.cornerRadius, config.backgroundColor);
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
}
elementData.previousMemoryConfig = configMemory;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = renderCommand.renderData.border;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
let color = config.color;
elementData.previousMemoryConfig = configMemory;
if (config.width.left.value > 0) {
element.style.borderLeft = `${config.width.left.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.width.right.value > 0) {
element.style.borderRight = `${config.width.right.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.width.top.value > 0) {
element.style.borderTop = `${config.width.top.value}px solid rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value / 255})`
}
if (config.width.bottom.value > 0) {
element.style.borderBottom = `${config.width.bottom.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 = renderCommand.renderData.text;
let configMemory = JSON.stringify(config);
let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
if (configMemory !== elementData.previousMemoryConfig) {
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';
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
}
elementData.previousMemoryConfig = configMemory;
}
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
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 = renderCommand.renderData.clip;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
if (config.horizontal.value) {
element.style.overflowX = 'scroll';
element.style.pointerEvents = 'auto';
}
if (config.vertical.value) {
element.style.overflowY = 'scroll';
element.style.pointerEvents = 'auto';
}
elementData.previousMemoryConfig = configMemory;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
scissorStack.splice(scissorStack.length - 1, 1);
break;
}
case (CLAY_RENDER_COMMAND_TYPE_IMAGE): {
let config = renderCommand.renderData.image;
let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let srcContents = new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.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;
default: {
console.log("Error: unhandled render command");
}
}
}
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 = renderCommand.renderData.rectangle;
let color = config.backgroundColor;
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
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = renderCommand.renderData.border;
let color = config.color;
ctx.beginPath();
ctx.moveTo(boundingBox.x.value * scale, boundingBox.y.value * scale);
// Top Left Corner
if (config.cornerRadius.topLeft.value > 0) {
let lineWidth = config.width.top.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + halfLineWidth) * scale, (boundingBox.y.value + config.cornerRadius.topLeft.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 + 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.width.top.value > 0) {
let lineWidth = config.width.top.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.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.width.top.value;
let halfLineWidth = lineWidth / 2;
ctx.moveTo((boundingBox.x.value + boundingBox.width.value - config.cornerRadius.topRight.value - halfLineWidth) * scale, (boundingBox.y.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 + 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.width.right.value > 0) {
let lineWidth = config.width.right.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 lineWidth = config.width.top.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.width.bottom.value > 0) {
let lineWidth = config.width.bottom.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 lineWidth = config.width.bottom.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.width.left.value > 0) {
let lineWidth = config.width.left.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 = renderCommand.renderData.text;
let textContents = config.stringContents;
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 = renderCommand.renderData.image;
let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View file

@ -4,6 +4,8 @@
<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 {
@ -13,6 +15,7 @@
padding: 0;
margin: 0;
pointer-events: none;
background: rgb(244, 235, 230);
}
/* Import the font using @font-face */
@font-face {
@ -32,12 +35,14 @@
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 {
@ -47,7 +52,12 @@
.text {
pointer-events: all;
white-space: nowrap;
white-space: pre;
}
/* TODO special exception for text selection in debug tools */
[id='2067877626'] > * {
pointer-events: none !important;
}
</style>
</head>
@ -62,20 +72,24 @@
const CLAY_RENDER_COMMAND_TYPE_CUSTOM = 7;
const GLOBAL_FONT_SCALING_FACTOR = 0.8;
let renderCommandSize = 0;
let scratchSpaceAddress = 0;
let scratchSpaceAddress = 8;
let heapSpaceAddress = 0;
let memoryDataView;
let textDecoder = new TextDecoder("utf-8");
let previousFrameTime;
let fontsById = [
'Calistoga',
'Quicksand',
'Calistoga',
'Quicksand',
'Quicksand',
'Quicksand',
];
let elementCache = {};
let imageCache = {};
let dimensionsDefinition = { type: 'struct', members: [
{name: 'width', type: 'float'},
{name: 'height', type: 'float'},
]};
let colorDefinition = { type: 'struct', members: [
{name: 'r', type: 'float' },
{name: 'g', type: 'float' },
@ -83,12 +97,21 @@
{name: 'a', type: 'float' },
]};
let stringDefinition = { type: 'struct', members: [
{name: 'isStaticallyAllocated', type: 'uint32_t'},
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
]};
let borderDefinition = { type: 'struct', members: [
{name: 'width', type: 'uint32_t'},
{name: 'color', ...colorDefinition},
let stringSliceDefinition = { type: 'struct', members: [
{name: 'length', type: 'uint32_t' },
{name: 'chars', type: 'uint32_t' },
{name: 'baseChars', type: 'uint32_t' },
]};
let borderWidthDefinition = { type: 'struct', members: [
{name: 'left', type: 'uint16_t'},
{name: 'right', type: 'uint16_t'},
{name: 'top', type: 'uint16_t'},
{name: 'bottom', type: 'uint16_t'},
{name: 'betweenChildren', type: 'uint16_t'},
]};
let cornerRadiusDefinition = { type: 'struct', members: [
{name: 'topLeft', type: 'float'},
@ -96,41 +119,57 @@
{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: 'userData', type: 'uint32_t' },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineSpacing', type: 'uint16_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' }
{ name: 'wrapMode', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: '_padding', type: 'uint16_t' },
]};
let imageConfigDefinition = { name: 'image', type: 'struct', members: [
let textRenderDataDefinition = { type: 'struct', members: [
{ name: 'stringContents', ...stringSliceDefinition },
{ name: 'textColor', ...colorDefinition },
{ name: 'fontId', type: 'uint16_t' },
{ name: 'fontSize', type: 'uint16_t' },
{ name: 'letterSpacing', type: 'uint16_t' },
{ name: 'lineHeight', type: 'uint16_t' },
]};
let rectangleRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
]};
let imageRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ 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: [
let customRenderDataDefinition = { type: 'struct', members: [
{ name: 'backgroundColor', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'customData', type: 'uint32_t' },
]}
]};
let borderRenderDataDefinition = { type: 'struct', members: [
{ name: 'color', ...colorDefinition },
{ name: 'cornerRadius', ...cornerRadiusDefinition },
{ name: 'width', ...borderWidthDefinition },
{ name: 'padding', type: 'uint16_t'}
]};
let clipRenderDataDefinition = { type: 'struct', members: [
{ name: 'horizontal', type: 'bool' },
{ name: 'vertical', type: 'bool' },
]};
let customHTMLDataDefinition = { type: 'struct', members: [
{ name: 'link', ...stringDefinition },
{ name: 'cursorPointer', type: 'uint8_t' },
{ name: 'disablePointerEvents', type: 'uint8_t' },
{ name: 'padding', type: 'uint16_t'}
]};
let renderCommandDefinition = {
name: 'CLay_RenderCommand',
name: 'Clay_RenderCommand',
type: 'struct',
members: [
{ name: 'boundingBox', type: 'struct', members: [
@ -139,10 +178,19 @@
{ name: 'width', type: 'float' },
{ name: 'height', type: 'float' },
]},
{ name: 'config', type: 'uint32_t'},
{ name: 'text', ...stringDefinition },
{ name: 'renderData', type: 'union', members: [
{ name: 'rectangle', ...rectangleRenderDataDefinition },
{ name: 'text', ...textRenderDataDefinition },
{ name: 'image', ...imageRenderDataDefinition },
{ name: 'custom', ...customRenderDataDefinition },
{ name: 'border', ...borderRenderDataDefinition },
{ name: 'clip', ...clipRenderDataDefinition },
]},
{ name: 'userData', type: 'uint32_t'},
{ name: 'id', type: 'uint32_t' },
{ name: 'commandType', type: 'uint32_t', },
{ name: 'zIndex', type: 'int16_t' },
{ name: 'commandType', type: 'uint8_t' },
{ name: '_padding', type: 'uint8_t' },
]
};
@ -163,8 +211,11 @@
}
case 'float': return 4;
case 'uint32_t': return 4;
case 'int32_t': return 4;
case 'uint16_t': return 2;
case 'int16_t': return 2;
case 'uint8_t': return 1;
case 'bool': return 1;
default: {
throw "Unimplemented C data type " + definition.type
}
@ -190,8 +241,11 @@
}
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.getUint16(address, true), __size: 1 };
case 'int16_t': return { value: memoryDataView.getInt16(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
}
@ -211,6 +265,7 @@
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");
@ -219,8 +274,10 @@
window.mouseWheelXThisFrame = 0;
window.mouseWheelYThisFrame = 0;
window.touchDown = false;
window.arrowKeyDownPressedThisFrame = false;
window.arrowKeyUpPressedThisFrame = false;
let zeroTimeout = null;
addEventListener("wheel", (event) => {
document.addEventListener("wheel", (event) => {
window.mouseWheelXThisFrame = event.deltaX * -0.1;
window.mouseWheelYThisFrame = event.deltaY * -0.1;
clearTimeout(zeroTimeout);
@ -233,8 +290,17 @@
function handleTouch (event) {
if (event.touches.length === 1) {
window.touchDown = true;
window.mousePositionXThisFrame = event.changedTouches[0].pageX;
window.mousePositionYThisFrame = event.changedTouches[0].pageY;
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;
}
}
@ -247,16 +313,43 @@
})
document.addEventListener("mousemove", (event) => {
window.mousePositionXThisFrame = event.x;
window.mousePositionYThisFrame = event.y;
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) => {
clay: {
measureTextFunction: (addressOfDimensions, textToMeasure, addressOfConfig, userData) => {
let stringLength = memoryDataView.getUint32(textToMeasure, true);
let pointerToString = memoryDataView.getUint32(textToMeasure + 4, true);
let textConfig = readStructAtAddress(addressOfConfig, textConfigDefinition);
@ -265,18 +358,30 @@
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;
let clayScratchSpaceAddress = instance.exports.__heap_base.value + 1024;
heapSpaceAddress = instance.exports.__heap_base.value + 2048;
let arenaAddress = scratchSpaceAddress + 8;
window.instance = instance;
createMainArena(arenaAddress, heapSpaceAddress);
instance.exports.Clay_Initialize(arenaAddress);
memoryDataView.setFloat32(instance.exports.__heap_base.value, window.innerWidth, true);
memoryDataView.setFloat32(instance.exports.__heap_base.value + 4, window.innerHeight, true);
instance.exports.Clay_Initialize(arenaAddress, instance.exports.__heap_base.value);
instance.exports.SetScratchMemory(clayScratchSpaceAddress);
renderCommandSize = getStructTotalSize(renderCommandDefinition);
renderLoop();
}
@ -290,30 +395,56 @@
return false;
}
function SetElementBackgroundColorAndRadius(element, cornerRadius, backgroundColor) {
element.style.backgroundColor = `rgba(${backgroundColor.r.value}, ${backgroundColor.g.value}, ${backgroundColor.b.value}, ${backgroundColor.a.value / 255})`;
if (cornerRadius.topLeft.value > 0) {
element.style.borderTopLeftRadius = cornerRadius.topLeft.value + 'px';
}
if (cornerRadius.topRight.value > 0) {
element.style.borderTopRightRadius = cornerRadius.topRight.value + 'px';
}
if (cornerRadius.bottomLeft.value > 0) {
element.style.borderBottomLeftRadius = cornerRadius.bottomLeft.value + 'px';
}
if (cornerRadius.bottomRight.value > 0) {
element.style.borderBottomRightRadius = cornerRadius.bottomRight.value + 'px';
}
}
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;
for (let i = 0; i < length; i++, arrayOffset += renderCommandSize) {
let entireRenderCommandMemory = new Uint32Array(memoryDataView.buffer.slice(arrayOffset, arrayOffset + renderCommandSize));
let entireRenderCommandMemory = new Uint8Array(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) {
switch (renderCommand.commandType.value & 0xff) {
case CLAY_RENDER_COMMAND_TYPE_TEXT:
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
if (renderCommand.userData.value !== 0) {
if (readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition).link.length.value > 0) {
elementType = 'a';
}
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: { elementType = 'img'; 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,
@ -323,26 +454,25 @@
};
}
let dirty = false;
let elementData = elementCache[renderCommand.id.value];
element = elementData.element;
if (Array.prototype.indexOf.call(parentElement.element.children, element) !== parentElement.nextElementIndex) {
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[parentElement.nextElementIndex - 1].insertAdjacentElement('afterend', element);
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
if (renderCommand.commandType.value !== CLAY_RENDER_COMMAND_TYPE_SCISSOR_START && renderCommand.commandType.value !== CLAY_RENDER_COMMAND_TYPE_SCISSOR_END) {
dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize);
let dirty = MemoryIsDifferent(elementData.previousMemoryCommand, entireRenderCommandMemory, renderCommandSize) && !isMultiConfigElement;
if (!isMultiConfigElement) {
parentElement.nextElementIndex++;
} else {
dirty = true;
}
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;
@ -352,67 +482,60 @@
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;
}
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;
if (linkContents.length > 0 && (window.mouseDown || window.touchDown) && instance.exports.Clay_PointerOver(renderCommand.id.value)) {
window.location.href = linkContents;
}
if (!dirty && !MemoryIsDifferent(configMemory, elementData.previousMemoryConfig, config.__size)) {
let config = renderCommand.renderData.rectangle;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
SetElementBackgroundColorAndRadius(element, config.cornerRadius, config.backgroundColor);
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || config.cursorPointer.value) {
if (linkContents.length > 0 || customData.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})`;
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)) {
let config = renderCommand.renderData.border;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
let color = config.color;
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})`
if (config.width.left.value > 0) {
element.style.borderLeft = `${config.width.left.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})`
if (config.width.right.value > 0) {
element.style.borderRight = `${config.width.right.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})`
if (config.width.top.value > 0) {
element.style.borderTop = `${config.width.top.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})`
if (config.width.bottom.value > 0) {
element.style.borderBottom = `${config.width.bottom.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';
@ -429,18 +552,33 @@
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)) {
let config = renderCommand.renderData.text;
let configMemory = JSON.stringify(config);
let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
if (configMemory !== elementData.previousMemoryConfig) {
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';
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
if (linkContents.length > 0) {
element.href = linkContents;
}
if (linkContents.length > 0 || customData.cursorPointer.value) {
element.style.pointerEvents = 'all';
element.style.cursor = 'pointer';
}
}
elementData.previousMemoryConfig = configMemory;
}
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
@ -451,7 +589,20 @@
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 });
element.style.overflow = 'hidden';
let config = renderCommand.renderData.clip;
let configMemory = JSON.stringify(config);
if (configMemory === elementData.previousMemoryConfig) {
break;
}
if (config.horizontal.value) {
element.style.overflowX = 'scroll';
element.style.pointerEvents = 'auto';
}
if (config.vertical.value) {
element.style.overflowY = 'scroll';
element.style.pointerEvents = 'auto';
}
elementData.previousMemoryConfig = configMemory;
break;
}
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_END): {
@ -459,8 +610,9 @@
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));
let config = renderCommand.renderData.image;
let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let srcContents = new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value));
if (srcContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(srcContents, elementData.previousMemoryText, srcContents.length)) {
element.src = textDecoder.decode(srcContents);
}
@ -468,6 +620,9 @@
break;
}
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
default: {
console.log("Error: unhandled render command");
}
}
}
@ -490,22 +645,24 @@
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;
window.canvasRoot.style.height = window.innerHeight;
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;
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;
}
case (CLAY_RENDER_COMMAND_TYPE_RECTANGLE): {
let config = readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition);
let color = config.color;
let config = renderCommand.renderData.rectangle;
let color = config.backgroundColor;
ctx.beginPath();
window.canvasContext.fillStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
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
@ -514,96 +671,98 @@
[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
if (renderCommand.userData.value !== 0) {
let customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
let linkContents = customData.link.length.value > 0 ? textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(customData.link.chars.value, customData.link.chars.value + customData.link.length.value))) : 0;
memoryDataView.setUint32(0, renderCommand.id.value, true);
if (linkContents.length > 0 && (window.mouseDownThisFrame || window.touchDown) && instance.exports.Clay_PointerOver(0)) {
window.location.href = linkContents;
}
}
break;
}
case (CLAY_RENDER_COMMAND_TYPE_BORDER): {
let config = readStructAtAddress(renderCommand.config.value, borderConfigDefinition);
let config = renderCommand.renderData.border;
let color = config.color;
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 lineWidth = config.width.top.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})`;
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;
if (config.width.top.value > 0) {
let lineWidth = config.width.top.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})`;
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 lineWidth = config.width.top.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})`;
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;
if (config.width.right.value > 0) {
let lineWidth = config.width.right.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
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 lineWidth = config.width.top.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})`;
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;
if (config.width.bottom.value > 0) {
let lineWidth = config.width.bottom.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
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 lineWidth = config.width.bottom.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})`;
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;
if (config.width.left.value > 0) {
let lineWidth = config.width.left.value;
let halfLineWidth = lineWidth / 2;
ctx.lineWidth = lineWidth * scale;
ctx.strokeStyle = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
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();
@ -612,21 +771,23 @@
break;
}
case (CLAY_RENDER_COMMAND_TYPE_TEXT): {
let config = readStructAtAddress(renderCommand.config.value, textConfigDefinition);
let textContents = renderCommand.text;
let config = renderCommand.renderData.text;
let textContents = config.stringContents;
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})`;
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): {
@ -634,8 +795,9 @@
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)));
let config = renderCommand.renderData.image;
let imageURL = readStructAtAddress(config.imageData.value, stringDefinition);
let src = textDecoder.decode(new Uint8Array(memoryDataView.buffer.slice(imageURL.chars.value, imageURL.chars.value + imageURL.length.value)));
if (!imageCache[src]) {
imageCache[src] = {
image: new Image(),
@ -657,7 +819,11 @@
const elapsed = currentTime - previousFrameTime;
previousFrameTime = currentTime;
let activeRendererIndex = memoryDataView.getUint32(instance.exports.ACTIVE_RENDERER_INDEX.value, true);
instance.exports.UpdateDrawFrame(scratchSpaceAddress, window.innerWidth, window.innerHeight, window.mouseWheelXThisFrame, window.mouseWheelYThisFrame, window.mousePositionXThisFrame, window.mousePositionYThisFrame, window.touchDown, window.mouseDown, elapsed / 1000);
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: {
@ -679,7 +845,10 @@
}
window.previousActiveRendererIndex = activeRendererIndex;
requestAnimationFrame(renderLoop);
window.mouseDown = false;
window.mouseDownThisFrame = false;
window.arrowKeyUpPressedThisFrame = false;
window.arrowKeyDownPressedThisFrame = false;
window.dKeyPressedThisFrame = false;
}
init();
</script>

View file

@ -1,160 +1,180 @@
#define CLAY_EXTEND_CONFIG_RECTANGLE Clay_String link; bool cursorPointer;
#define CLAY_EXTEND_CONFIG_IMAGE Clay_String sourceURL;
#define CLAY_EXTEND_CONFIG_TEXT bool disablePointerEvents;
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
double windowWidth = 1024, windowHeight = 768;
float modelPageOneZRotation = 0;
int ACTIVE_RENDERER_INDEX = 0;
uint32_t ACTIVE_RENDERER_INDEX = 0;
const uint32_t FONT_ID_TITLE_56 = 0;
const uint32_t FONT_ID_BODY_24 = 1;
const uint32_t FONT_ID_BODY_16 = 2;
const uint32_t FONT_ID_BODY_16 = 0;
const uint32_t FONT_ID_TITLE_56 = 1;
const uint32_t FONT_ID_BODY_24 = 2;
const uint32_t FONT_ID_BODY_36 = 3;
const uint32_t FONT_ID_TITLE_36 = 4;
const uint32_t FONT_ID_MONOSPACE_24 = 5;
const Clay_Color COLOR_LIGHT = (Clay_Color) {244, 235, 230, 255};
Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255};
Clay_Color COLOR_BUTTON_HOVER = (Clay_Color) {238, 227, 225, 255};
Clay_Color COLOR_BROWN = (Clay_Color) {61, 26, 5, 255};
//Clay_Color COLOR_RED = (Clay_Color) {252, 67, 27, 255};
Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255};
Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
Clay_Color COLOR_TEAL = (Clay_Color) {111, 173, 162, 255};
Clay_Color COLOR_BLUE_DARK = (Clay_Color) {2, 32, 82, 255};
const Clay_Color COLOR_LIGHT_HOVER = (Clay_Color) {224, 215, 210, 255};
const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
const Clay_Color COLOR_RED_HOVER = (Clay_Color) {148, 46, 8, 255};
const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
const Clay_Color COLOR_BLUE = (Clay_Color) {111, 173, 162, 255};
// Colors for top stripe
Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255};
Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255};
Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255};
Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255};
const Clay_Color COLOR_TOP_BORDER_1 = (Clay_Color) {168, 66, 28, 255};
const Clay_Color COLOR_TOP_BORDER_2 = (Clay_Color) {223, 110, 44, 255};
const Clay_Color COLOR_TOP_BORDER_3 = (Clay_Color) {225, 138, 50, 255};
const Clay_Color COLOR_TOP_BORDER_4 = (Clay_Color) {236, 189, 80, 255};
const Clay_Color COLOR_TOP_BORDER_5 = (Clay_Color) {240, 213, 137, 255};
Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255};
Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255};
Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255};
Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255};
Clay_Color COLOR_BLOB_BORDER_5 = (Clay_Color) {240, 189, 100, 255};
const Clay_Color COLOR_BLOB_BORDER_1 = (Clay_Color) {168, 66, 28, 255};
const Clay_Color COLOR_BLOB_BORDER_2 = (Clay_Color) {203, 100, 44, 255};
const Clay_Color COLOR_BLOB_BORDER_3 = (Clay_Color) {225, 138, 50, 255};
const Clay_Color COLOR_BLOB_BORDER_4 = (Clay_Color) {236, 159, 70, 255};
const Clay_Color COLOR_BLOB_BORDER_5 = (Clay_Color) {240, 189, 100, 255};
#define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = vector.x, .y = vector.y }
#define RAYLIB_VECTOR2_TO_CLAY_VECTOR2(vector) (Clay_Vector2) { .x = (vector).x, .y = (vector).y }
Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} };
Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = FONT_ID_BODY_24, .fontSize = 30, .textColor = COLOR_LIGHT };
Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = 2, .fontSize = 24, .textColor = {61, 26, 5, 255} };
Clay_TextElementConfig blobTextConfig = (Clay_TextElementConfig) { .fontId = 2, .fontSize = 30, .textColor = {244, 235, 230, 255} };
typedef struct {
void* memory;
uintptr_t offset;
} Arena;
Arena frameArena = {};
typedef struct d {
Clay_String link;
bool cursorPointer;
bool disablePointerEvents;
} CustomHTMLData;
CustomHTMLData* FrameAllocateCustomData(CustomHTMLData data) {
CustomHTMLData *customData = (CustomHTMLData *)(frameArena.memory + frameArena.offset);
*customData = data;
frameArena.offset += sizeof(CustomHTMLData);
return customData;
}
Clay_String* FrameAllocateString(Clay_String string) {
Clay_String *allocated = (Clay_String *)(frameArena.memory + frameArena.offset);
*allocated = string;
frameArena.offset += sizeof(Clay_String);
return allocated;
}
void LandingPageBlob(int index, int fontSize, Clay_Color color, Clay_String text, Clay_String imageURL) {
CLAY_BORDER_CONTAINER(CLAY_IDI("HeroBlob", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = {16, 16}, .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, color, 10), {
CLAY_IMAGE(CLAY_IDI("CheckImage", index), CLAY_LAYOUT(.sizing = { CLAY_SIZING_FIXED(32) }), CLAY_IMAGE_CONFIG(.sourceDimensions = { 128, 128 }, .sourceURL = imageURL), {});
CLAY_TEXT(CLAY_IDI("HeroBlobText", index), text, CLAY_TEXT_CONFIG(.fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color));
});
CLAY(CLAY_IDI("HeroBlob", index), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .border = { .color = color, .width = { 2, 2, 2, 2 }}, .cornerRadius = CLAY_CORNER_RADIUS(10) }) {
CLAY(CLAY_IDI("CheckImage", index), { .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .aspectRatio = { 1 }, .image = { .imageData = FrameAllocateString(imageURL) } }) {}
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = fontSize, .fontId = FONT_ID_BODY_24, .textColor = color }));
}
}
void LandingPageDesktop() {
CLAY_CONTAINER(CLAY_ID("LandingPage1Desktop"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { .x = 50 }), {
CLAY_BORDER_CONTAINER(CLAY_ID("LandingPage1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), {
CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED));
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {});
CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE));
});
CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), {
CLAY(CLAY_ID("LandingPage1Desktop"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) {
CLAY(CLAY_ID("LandingPage1"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED } }) {
CLAY(CLAY_ID("LeftText"), { .layout = { .sizing = { .width = CLAY_SIZING_PERCENT(0.55f) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG({ .fontSize = 56, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY(CLAY_ID("LandingPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {}
CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
}
CLAY(CLAY_ID("HeroImageOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_PERCENT(0.45f) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) {
LandingPageBlob(1, 32, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png"));
LandingPageBlob(2, 32, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png"));
LandingPageBlob(3, 32, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png"));
LandingPageBlob(4, 32, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png"));
LandingPageBlob(5, 32, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png"));
});
});
});
}
}
}
}
void LandingPageMobile() {
CLAY_CONTAINER(CLAY_ID("LandingPage1Mobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 32 }, .childGap = 32), {
CLAY_CONTAINER(CLAY_ID("LeftText"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("LeftTextTitle"), CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED));
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(32) }), {});
CLAY_TEXT(CLAY_ID("LeftTextTagline"), CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG(.fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE));
});
CLAY_CONTAINER(CLAY_ID("HeroImageOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16), {
CLAY(CLAY_ID("LandingPage1Mobile"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(.min = windowHeight - 70) }, .childAlignment = {CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 16, 32, 32 }, .childGap = 32 } }) {
CLAY(CLAY_ID("LeftText"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Clay is a flex-box style UI auto layout library in C, with declarative syntax and microsecond performance."), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY(CLAY_ID("LandingPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {}
CLAY_TEXT(CLAY_STRING("Clay is laying out this webpage right now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
}
CLAY(CLAY_ID("HeroImageOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(0) }, .childAlignment = { CLAY_ALIGN_X_CENTER }, .childGap = 16 } }) {
LandingPageBlob(1, 28, COLOR_BLOB_BORDER_5, CLAY_STRING("High performance"), CLAY_STRING("/clay/images/check_5.png"));
LandingPageBlob(2, 28, COLOR_BLOB_BORDER_4, CLAY_STRING("Flexbox-style responsive layout"), CLAY_STRING("/clay/images/check_4.png"));
LandingPageBlob(3, 28, COLOR_BLOB_BORDER_3, CLAY_STRING("Declarative syntax"), CLAY_STRING("/clay/images/check_3.png"));
LandingPageBlob(4, 28, COLOR_BLOB_BORDER_2, CLAY_STRING("Single .h file for C/C++"), CLAY_STRING("/clay/images/check_2.png"));
LandingPageBlob(5, 28, COLOR_BLOB_BORDER_1, CLAY_STRING("Compile to 15kb .wasm"), CLAY_STRING("/clay/images/check_1.png"));
});
});
}
}
}
void FeatureBlocksDesktop() {
CLAY_CONTAINER(CLAY_ID("FeatureBlocksOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }), {
CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), {
Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED );
CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 32}, .childGap = 8), {
CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), {
CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT));
});
CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig);
CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig);
});
CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50, .y = 32}, .childGap = 8), {
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE));
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig);
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig);
});
});
});
CLAY(CLAY_ID("FeatureBlocksOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {
CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) {
Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED });
CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) {
CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT }));
}
CLAY_TEXT(CLAY_STRING("~2000 lines of C99."), textConfig);
CLAY_TEXT(CLAY_STRING("Zero dependencies, including no C standard library."), textConfig);
}
CLAY(CLAY_ID("BringYourOwnRendererOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_PERCENT(0.5f) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {50, 50, 32, 32}, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE }));
CLAY_TEXT(CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig);
CLAY_TEXT(CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig);
}
}
}
}
void FeatureBlocksMobile() {
CLAY_BORDER_CONTAINER(CLAY_ID("FeatureBlocksInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, ), CLAY_BORDER_CONFIG(.betweenChildren = { .width = 2, .color = COLOR_RED }), {
Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED );
CLAY_CONTAINER(CLAY_ID("HFileBoxOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 32}, .childGap = 8), {
CLAY_RECTANGLE(CLAY_ID("HFileIncludeOuter"), CLAY_LAYOUT(.padding = {8, 4}), CLAY_RECTANGLE_CONFIG(.color = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8)), {
CLAY_TEXT(CLAY_IDI("HFileBoxText", 2), CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT));
});
CLAY_TEXT(CLAY_ID("HFileSecondLine"), CLAY_STRING("~2000 lines of C99."), textConfig);
CLAY_TEXT(CLAY_IDI("HFileBoxText", 5), CLAY_STRING("Zero dependencies, including no C standard library."), textConfig);
});
CLAY_CONTAINER(CLAY_ID("BringYourOwnRendererOuter"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, .y = 32}, .childGap = 8), {
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 1), CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG(.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE));
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 2), CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig);
CLAY_TEXT(CLAY_IDI("ZeroDependenciesText", 3), CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig);
});
});
CLAY(CLAY_ID("FeatureBlocksInner"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) } }, .border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED } }) {
Clay_TextElementConfig *textConfig = CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_RED });
CLAY(CLAY_ID("HFileBoxOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) {
CLAY(CLAY_ID("HFileIncludeOuter"), { .layout = { .padding = {8, 4} }, .backgroundColor = COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
CLAY_TEXT(CLAY_STRING("#include clay.h"), CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_BODY_24, .textColor = COLOR_LIGHT }));
}
CLAY_TEXT(CLAY_STRING("~2000 lines of C99."), textConfig);
CLAY_TEXT(CLAY_STRING("Zero dependencies, including no C standard library."), textConfig);
}
CLAY(CLAY_ID("BringYourOwnRendererOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Renderer agnostic."), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = COLOR_ORANGE }));
CLAY_TEXT(CLAY_STRING("Layout with clay, then render with Raylib, WebGL Canvas or even as HTML."), textConfig);
CLAY_TEXT(CLAY_STRING("Flexible output for easy compositing in your custom engine or environment."), textConfig);
}
}
}
void DeclarativeSyntaxPageDesktop() {
CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), {
CLAY_BORDER_CONTAINER(CLAY_ID("SyntaxPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), {
CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED));
CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {});
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
});
CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), {
CLAY_IMAGE(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {});
});
});
});
CLAY(CLAY_ID("SyntaxPageDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) {
CLAY(CLAY_ID("SyntaxPage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED }}) {
CLAY(CLAY_ID("SyntaxPageLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY(CLAY_ID("SyntaxSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) } } }) {}
CLAY_TEXT(CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}
CLAY(CLAY_ID("SyntaxPageRightImage"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
CLAY(CLAY_ID("SyntaxPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
}
}
}
}
void DeclarativeSyntaxPageMobile() {
CLAY_CONTAINER(CLAY_ID("SyntaxPageDesktop"), 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 = {16, 32}, .childGap = 16), {
CLAY_CONTAINER(CLAY_ID("SyntaxPageLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("SyntaxPageTextTitle"), CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED));
CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {});
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle1"), CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle2"), CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_ID("SyntaxPageTextSubTitle3"), CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
});
CLAY_CONTAINER(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER}), {
CLAY_IMAGE(CLAY_ID("SyntaxPageRightImage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 568) }), CLAY_IMAGE_CONFIG(.sourceDimensions = {1136, 1194}, .sourceURL = CLAY_STRING("/clay/images/declarative.png")), {});
});
});
CLAY(CLAY_ID("SyntaxPageDesktop"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 16 } }) {
CLAY(CLAY_ID("SyntaxPageLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Declarative Syntax"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY(CLAY_ID("SyntaxSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) } } }) {}
CLAY_TEXT(CLAY_STRING("Flexible and readable declarative syntax with nested UI element hierarchies."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("Mix elements with standard C code like loops, conditionals and functions."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("Create your own library of re-usable components from UI primitives like text, images and rectangles."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}
CLAY(CLAY_ID("SyntaxPageRightImage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
CLAY(CLAY_ID("SyntaxPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .aspectRatio = { 1136.0 / 1194.0 }, .image = { .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
}
}
}
Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) {
@ -169,138 +189,210 @@ 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_RECTANGLE(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_CONFIG(.color = COLOR_RED), {
CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT));
CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {});
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
});
CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER}), {
CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), {
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), {
CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT));
});
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {32, 32}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), {
CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT));
});
});
});
});
CLAY(CLAY_ID("PerformanceOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {82, 82, 32, 32}, .childGap = 64 }, .backgroundColor = COLOR_RED }) {
CLAY(CLAY_ID("PerformanceLeftText"), { .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"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
}
CLAY(CLAY_ID("PerformanceRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = {2, 2, 2, 2}, .color = COLOR_LIGHT } }) {
CLAY(CLAY_ID("AnimationDemoContainerLeft"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.3f + 0.4f * lerpValue), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue) }) {
CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}
CLAY(CLAY_ID("AnimationDemoContainerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(32) }, .backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue) }) {
CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}
}
}
}
}
void HighPerformancePageMobile(float lerpValue) {
CLAY_RECTANGLE(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_CONFIG(.color = COLOR_RED), {
CLAY_CONTAINER(CLAY_ID("PerformanceLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("PerformanceTextTitle"), CLAY_STRING("High Performance"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT));
CLAY_CONTAINER(CLAY_ID("SyntaxSpacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {});
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 1), CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 2), CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
CLAY_TEXT(CLAY_IDI("PerformanceTextSubTitle", 3), CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
});
CLAY_CONTAINER(CLAY_ID("PerformanceRightImageOuter"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .childAlignment = {CLAY_ALIGN_X_CENTER}), {
CLAY_BORDER_CONTAINER(CLAY_ID(""), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(400) }), CLAY_BORDER_CONFIG_ALL(.width = 2, .color = COLOR_LIGHT), {
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerLeft"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue)), {
CLAY_TEXT(CLAY_ID("AnimationDemoTextLeft"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT));
});
CLAY_RECTANGLE(CLAY_ID("AnimationDemoContainerRight"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue)), {
CLAY_TEXT(CLAY_ID("AnimationDemoTextRight"), LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG(.fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT));
});
});
});
});
CLAY(CLAY_ID("PerformanceOuter"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {CLAY_ALIGN_X_CENTER, CLAY_ALIGN_Y_CENTER}, .padding = {16, 16, 32, 32}, .childGap = 32 }, .backgroundColor = COLOR_RED }) {
CLAY(CLAY_ID("PerformanceLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .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"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_STRING("Fast enough to recompute your entire UI every frame."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_STRING("Small memory footprint (3.5mb default) with static allocation & reuse. No malloc / free."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_STRING("Simplify animations and reactive UI design by avoiding the standard performance hacks."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
}
CLAY(CLAY_ID("PerformanceRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = { 2, 2, 2, 2 }, .color = COLOR_LIGHT }}) {
CLAY(CLAY_ID("AnimationDemoContainerLeft"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.35f + 0.3f * lerpValue), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = ColorLerp(COLOR_RED, COLOR_ORANGE, lerpValue) }) {
CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}
CLAY(CLAY_ID("AnimationDemoContainerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER}, .padding = CLAY_PADDING_ALL(16) }, .backgroundColor = ColorLerp(COLOR_ORANGE, COLOR_RED, lerpValue) }) {
CLAY_TEXT(LOREM_IPSUM_TEXT, CLAY_TEXT_CONFIG({ .fontSize = 24, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
}
}
}
}
}
void RendererButtonActive(uint32_t id, int index, Clay_String text) {
CLAY_RECTANGLE(id, CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_RED_HOVER : COLOR_RED, .cornerRadius = CLAY_CORNER_RADIUS(10)), {
CLAY_TEXT(CLAY_ID("RendererButtonActiveText"), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT));
});
void HandleRendererButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerInfo, intptr_t userData) {
if (pointerInfo.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
ACTIVE_RENDERER_INDEX = (uint32_t)userData;
Clay_SetCullingEnabled(ACTIVE_RENDERER_INDEX == 1);
Clay_SetExternalScrollHandlingEnabled(ACTIVE_RENDERER_INDEX == 0);
}
}
void RendererButtonInactive(uint32_t id, int index, Clay_String text) {
CLAY_BORDER_CONTAINER(id, CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), {
CLAY_RECTANGLE(CLAY_IDI("RendererButtonInactiveInner", index), CLAY_LAYOUT(.sizing = {CLAY_SIZING_FIXED(300) }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = Clay_PointerOver(id) ? COLOR_LIGHT_HOVER : COLOR_LIGHT, .cornerRadius = CLAY_CORNER_RADIUS(10), .cursorPointer = true), {
CLAY_TEXT(CLAY_IDI("RendererButtonInactiveText", index), text, CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
});
});
void RendererButtonActive(Clay_String text) {
CLAY_AUTO_ID({
.layout = { .sizing = {CLAY_SIZING_FIXED(300) }, .padding = CLAY_PADDING_ALL(16) },
.backgroundColor = Clay_Hovered() ? COLOR_RED_HOVER : COLOR_RED,
.cornerRadius = CLAY_CORNER_RADIUS(10),
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })
}) {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
}
}
void RendererButtonInactive(Clay_String text, size_t rendererIndex) {
CLAY_AUTO_ID({
.layout = { .sizing = {CLAY_SIZING_FIXED(300)}, .padding = CLAY_PADDING_ALL(16) },
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
.cornerRadius = CLAY_CORNER_RADIUS(10),
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true, .cursorPointer = true })
}) {
Clay_OnHover(HandleRendererButtonInteraction, rendererIndex);
CLAY_TEXT(text, CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}
}
void RendererPageDesktop() {
CLAY_CONTAINER(CLAY_ID("RendererPageDesktop"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = {.x = 50}), {
CLAY_BORDER_CONTAINER(CLAY_ID("RendererPage"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = { 32, 32 }, .childGap = 32), CLAY_BORDER_CONFIG(.left = { 2, COLOR_RED }, .right = { 2, COLOR_RED }), {
CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED));
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {});
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
});
CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), {
CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE));
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {});
if (ACTIVE_RENDERER_INDEX == 0) {
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer"));
RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer"));
} else {
RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer"));
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer"));
CLAY(CLAY_ID("RendererPageDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 50, 50 } } }) {
CLAY(CLAY_ID("RendererPage"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .padding = CLAY_PADDING_ALL(32), .childGap = 32 }, .border = { .width = { .left = 2, .right = 2 }, .color = COLOR_RED } }) {
CLAY(CLAY_ID("RendererLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY(CLAY_ID("RendererSpacerLeft"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}
CLAY(CLAY_ID("RendererRightText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .childAlignment = {CLAY_ALIGN_X_CENTER}, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
CLAY_TEXT(CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE }));
CLAY(CLAY_ID("RendererSpacerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 32) } } }) {}
if (ACTIVE_RENDERER_INDEX == 0) {
RendererButtonActive(CLAY_STRING("HTML Renderer"));
RendererButtonInactive(CLAY_STRING("Canvas Renderer"), 1);
} else {
RendererButtonInactive(CLAY_STRING("HTML Renderer"), 0);
RendererButtonActive(CLAY_STRING("Canvas Renderer"));
}
}
}
}
});
});
});
}
void RendererPageMobile() {
CLAY_RECTANGLE(CLAY_ID("RendererMobile"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = {.x = 16, 32}, .childGap = 32), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {
CLAY_CONTAINER(CLAY_ID("RendererLeftText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8), {
CLAY_TEXT(CLAY_ID("RendererTextTitle"), CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG(.fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED));
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 16) }), {});
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 1), CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 2), CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
CLAY_TEXT(CLAY_IDI("RendererTextSubTitle", 3), CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG(.fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED));
});
CLAY_CONTAINER(CLAY_ID("RendererRightText"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW() }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16), {
CLAY_TEXT(CLAY_ID("RendererTextRightTitle"), CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG(.fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE));
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(.max = 32) }), {});
CLAY(CLAY_ID("RendererMobile"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER}, .padding = { 16, 16, 32, 32}, .childGap = 32 }, .backgroundColor = COLOR_LIGHT }) {
CLAY(CLAY_ID("RendererLeftText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Renderer & Platform Agnostic"), CLAY_TEXT_CONFIG({ .fontSize = 48, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_RED }));
CLAY(CLAY_ID("RendererSpacerLeft"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_STRING("Clay outputs a sorted array of primitive render commands, such as RECTANGLE, TEXT or IMAGE."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("Write your own renderer in a few hundred lines of code, or use the provided examples for Raylib, WebGL canvas and more."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
CLAY_TEXT(CLAY_STRING("There's even an HTML renderer - you're looking at it right now!"), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_RED }));
}
CLAY(CLAY_ID("RendererRightText"), { .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 16 } }) {
CLAY_TEXT(CLAY_STRING("Try changing renderer!"), CLAY_TEXT_CONFIG({ .fontSize = 36, .fontId = FONT_ID_BODY_36, .textColor = COLOR_ORANGE }));
CLAY(CLAY_ID("RendererSpacerRight"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 32) }} }) {}
if (ACTIVE_RENDERER_INDEX == 0) {
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 0, CLAY_STRING("HTML Renderer"));
RendererButtonInactive(CLAY_ID("RendererSelectButtonCanvas"), 1, CLAY_STRING("Canvas Renderer"));
RendererButtonActive(CLAY_STRING("HTML Renderer"));
RendererButtonInactive(CLAY_STRING("Canvas Renderer"), 1);
} else {
RendererButtonInactive(CLAY_ID("RendererSelectButtonHTML"), 0, CLAY_STRING("HTML Renderer"));
RendererButtonActive(CLAY_IDI("RendererSelectButtonActive", 0), 1, CLAY_STRING("Canvas Renderer"));
RendererButtonInactive(CLAY_STRING("HTML Renderer"), 0);
RendererButtonActive(CLAY_STRING("Canvas Renderer"));
}
}
}
});
});
}
Clay_RenderCommandArray CreateLayout(float lerpValue) {
bool mobileScreen = windowWidth < 750;
Clay_BeginLayout((int)windowWidth, (int)windowHeight);
CLAY_RECTANGLE(CLAY_ID("OuterContainer"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {
CLAY_CONTAINER(CLAY_ID("Header"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 24, .padding = { 32 }), {
CLAY_TEXT(CLAY_ID("Logo"), CLAY_STRING("Clay"), &headerTextConfig);
CLAY_CONTAINER(CLAY_ID("Spacer"), CLAY_LAYOUT(.sizing = { .width = CLAY_SIZING_GROW() }), {});
void DebuggerPageDesktop() {
CLAY(CLAY_ID("DebuggerDesktop"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(.min = windowHeight - 50) }, .childAlignment = {0, CLAY_ALIGN_Y_CENTER}, .padding = { 82, 82, 32, 32 }, .childGap = 64 }, .backgroundColor = COLOR_RED }) {
CLAY(CLAY_ID("DebuggerLeftText"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.5) }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .childGap = 8 } }) {
CLAY_TEXT(CLAY_STRING("Integrated Debug Tools"), CLAY_TEXT_CONFIG({ .fontSize = 52, .fontId = FONT_ID_TITLE_56, .textColor = COLOR_LIGHT }));
CLAY(CLAY_ID("DebuggerSpacer"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 16) }} }) {}
CLAY_TEXT(CLAY_STRING("Clay includes built in \"Chrome Inspector\"-style debug tooling."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY_TEXT(CLAY_STRING("View your layout hierarchy and config in real time."), CLAY_TEXT_CONFIG({ .fontSize = 28, .fontId = FONT_ID_BODY_36, .textColor = COLOR_LIGHT }));
CLAY(CLAY_ID("DebuggerPageSpacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(32) } } }) {}
CLAY_TEXT(CLAY_STRING("Press the \"d\" key to try it out now!"), CLAY_TEXT_CONFIG({ .fontSize = 32, .fontId = FONT_ID_TITLE_36, .textColor = COLOR_ORANGE }));
}
CLAY(CLAY_ID("DebuggerRightImageOuter"), { .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
CLAY(CLAY_ID("DebuggerPageRightImageInner"), { .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .aspectRatio = { 1620.0 / 1474.0 }, .image = {.imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {}
}
}
}
typedef struct
{
Clay_Vector2 clickOrigin;
Clay_Vector2 positionOrigin;
bool mouseDown;
} ScrollbarData;
ScrollbarData scrollbarData = (ScrollbarData) {};
float animationLerpValue = -1.0f;
Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
Clay_BeginLayout();
CLAY(CLAY_ID("OuterContainer"), { .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {
CLAY(CLAY_ID("Header"), { .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(50) }, .childAlignment = { 0, CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = { 32, 32 } } }) {
CLAY_TEXT(CLAY_STRING("Clay"), &headerTextConfig);
CLAY(CLAY_ID("Spacer"), { .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {}
if (!mobileScreen) {
CLAY_TEXT(CLAY_ID("LinkFeatures"), CLAY_STRING("Features"), &headerTextConfig);
CLAY_TEXT(CLAY_ID("LinkDocs"), CLAY_STRING("Docs"), &headerTextConfig);
CLAY(CLAY_ID("LinkExamplesOuter"), { .layout = { .padding = {8, 8} } }) {
CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({
.userData = FrameAllocateCustomData((CustomHTMLData) {
.link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples")
}),
.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
}
uint32_t githubButtonId = CLAY_ID("HeaderButtonGithub");
CLAY_BORDER_CONTAINER(CLAY_ID("LinkGithubOuter"), CLAY_LAYOUT(), CLAY_BORDER_CONFIG_OUTSIDE_RADIUS(2, COLOR_RED, 10), {
CLAY_RECTANGLE(githubButtonId, CLAY_LAYOUT(.padding = {16, 6}), CLAY_RECTANGLE_CONFIG(.cornerRadius = CLAY_CORNER_RADIUS(10), .link = CLAY_STRING("https://github.com/nicbarker/clay"), .color = Clay_PointerOver(githubButtonId) ? COLOR_LIGHT_HOVER : COLOR_LIGHT), {
CLAY_TEXT(CLAY_ID("LinkGithubText"), CLAY_STRING("Github"), CLAY_TEXT_CONFIG(.disablePointerEvents = true, .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255}));
});
});
});
CLAY_RECTANGLE(CLAY_ID("TopBorder1"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_5), {});
CLAY_RECTANGLE(CLAY_ID("TopBorder2"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_4), {});
CLAY_RECTANGLE(CLAY_ID("TopBorder3"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_3), {});
CLAY_RECTANGLE(CLAY_ID("TopBorder4"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_2), {});
CLAY_RECTANGLE(CLAY_ID("TopBorder5"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_FIXED(4) }), CLAY_RECTANGLE_CONFIG(.color = COLOR_TOP_BORDER_1), {});
CLAY_RECTANGLE(CLAY_ID("ScrollContainerBackgroundRectangle"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {
CLAY_SCROLL_CONTAINER(CLAY_ID("OuterScrollContainer"), CLAY_LAYOUT(.sizing = { CLAY_SIZING_GROW(), CLAY_SIZING_GROW() }), CLAY_SCROLL_CONFIG(.vertical = true), {
CLAY_BORDER_CONTAINER(CLAY_ID("ScrollContainerInner"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW() }), CLAY_BORDER_CONFIG(.betweenChildren = {2, COLOR_RED}), {
CLAY(CLAY_ID("LinkDocsOuter"), { .layout = { .padding = {8, 8} } }) {
CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({
.userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }),
.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} })
);
}
}
CLAY_AUTO_ID({
.layout = { .padding = {16, 16, 6, 6} },
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
.cornerRadius = CLAY_CORNER_RADIUS(10),
.userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://discord.gg/b4FTWkxdvT") }),
}) {
CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }),
.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
}
CLAY_AUTO_ID({
.layout = { .padding = {16, 16, 6, 6} },
.backgroundColor = Clay_Hovered() ? COLOR_LIGHT_HOVER : COLOR_LIGHT,
.border = { .width = {2, 2, 2, 2}, .color = COLOR_RED },
.cornerRadius = CLAY_CORNER_RADIUS(10),
.userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay") }),
}) {
CLAY_TEXT(CLAY_STRING("Github"), CLAY_TEXT_CONFIG({
.userData = FrameAllocateCustomData((CustomHTMLData) { .disablePointerEvents = true }),
.fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
}
}
Clay_LayoutConfig topBorderConfig = (Clay_LayoutConfig) { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(4) }};
CLAY(CLAY_ID("TopBorder1"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_5 }) {}
CLAY(CLAY_ID("TopBorder2"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_4 }) {}
CLAY(CLAY_ID("TopBorder3"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_3 }) {}
CLAY(CLAY_ID("TopBorder4"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_2 }) {}
CLAY(CLAY_ID("TopBorder5"), { .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_1 }) {}
CLAY(CLAY_ID("OuterScrollContainer"), {
.layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM },
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
.border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED }
}) {
if (mobileScreen) {
LandingPageMobile();
FeatureBlocksMobile();
@ -313,37 +405,106 @@ Clay_RenderCommandArray CreateLayout(float lerpValue) {
DeclarativeSyntaxPageDesktop();
HighPerformancePageDesktop(lerpValue);
RendererPageDesktop();
DebuggerPageDesktop();
}
}
});
});
});
});
return Clay_EndLayout((int)windowWidth, (int)windowHeight);
}
float animationLerpValue = -1.0f;
if (!mobileScreen && ACTIVE_RENDERER_INDEX == 1) {
Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
Clay_Color scrollbarColor = (Clay_Color){225, 138, 50, 120};
if (scrollbarData.mouseDown) {
scrollbarColor = (Clay_Color){225, 138, 50, 200};
} else if (Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
scrollbarColor = (Clay_Color){225, 138, 50, 160};
}
float scrollHeight = scrollData.scrollContainerDimensions.height - 12;
CLAY(CLAY_ID("ScrollBar"), {
.floating = { .offset = { .x = -6, .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollHeight + 6}, .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("OuterScrollContainer")).id, .attachPoints = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP }, .attachTo = CLAY_ATTACH_TO_PARENT },
.layout = { .sizing = {CLAY_SIZING_FIXED(10), CLAY_SIZING_FIXED((scrollHeight / scrollData.contentDimensions.height) * scrollHeight)} },
.backgroundColor = scrollbarColor,
.cornerRadius = CLAY_CORNER_RADIUS(5)
}) {}
}
return Clay_EndLayout();
}
CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, float deltaTime) {
bool debugModeEnabled = false;
CLAY_WASM_EXPORT("SetScratchMemory") void SetScratchMemory(void * memory) {
frameArena.memory = memory;
}
CLAY_WASM_EXPORT("UpdateDrawFrame") Clay_RenderCommandArray UpdateDrawFrame(float width, float height, float mouseWheelX, float mouseWheelY, float mousePositionX, float mousePositionY, bool isTouchDown, bool isMouseDown, bool arrowKeyDownPressedThisFrame, bool arrowKeyUpPressedThisFrame, bool dKeyPressedThisFrame, float deltaTime) {
frameArena.offset = 0;
windowWidth = width;
windowHeight = height;
if (deltaTime == deltaTime) { // NaN propagation can cause pain here
Clay_SetLayoutDimensions((Clay_Dimensions) { width, height });
Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("OuterScrollContainer")));
Clay_LayoutElementHashMapItem *perfPage = Clay__GetHashMapItem(Clay_GetElementId(CLAY_STRING("PerformanceOuter")).id);
// NaN propagation can cause pain here
float perfPageYOffset = perfPage->boundingBox.y + scrollContainerData.scrollPosition->y;
if (deltaTime == deltaTime && (ACTIVE_RENDERER_INDEX == 1 || (perfPageYOffset < height && perfPageYOffset + perfPage->boundingBox.height > 0))) {
animationLerpValue += deltaTime;
if (animationLerpValue > 1) {
animationLerpValue -= 2;
}
}
if (isTouchDown || isMouseDown) {
if (Clay_PointerOver(CLAY_ID("RendererSelectButtonHTML"))) {
ACTIVE_RENDERER_INDEX = 0;
} else if (Clay_PointerOver(CLAY_ID("RendererSelectButtonCanvas"))) {
ACTIVE_RENDERER_INDEX = 1;
if (dKeyPressedThisFrame) {
debugModeEnabled = !debugModeEnabled;
Clay_SetDebugModeEnabled(debugModeEnabled);
}
Clay_SetCullingEnabled(ACTIVE_RENDERER_INDEX == 1);
Clay_SetExternalScrollHandlingEnabled(ACTIVE_RENDERER_INDEX == 0);
Clay__debugViewHighlightColor = (Clay_Color) {105,210,231, 120};
Clay_SetPointerState((Clay_Vector2) {mousePositionX, mousePositionY}, isMouseDown || isTouchDown);
if (!isMouseDown) {
scrollbarData.mouseDown = false;
}
if (isMouseDown && !scrollbarData.mouseDown && Clay_PointerOver(Clay_GetElementId(CLAY_STRING("ScrollBar")))) {
scrollbarData.clickOrigin = (Clay_Vector2) { mousePositionX, mousePositionY };
scrollbarData.positionOrigin = *scrollContainerData.scrollPosition;
scrollbarData.mouseDown = true;
} else if (scrollbarData.mouseDown) {
if (scrollContainerData.contentDimensions.height > 0) {
Clay_Vector2 ratio = (Clay_Vector2) {
scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width,
scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height,
};
if (scrollContainerData.config.vertical) {
scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePositionY) * ratio.y;
}
if (scrollContainerData.config.horizontal) {
scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePositionX) * ratio.x;
}
}
//----------------------------------------------------------------------------------
// Handle scroll containers
Clay_SetPointerPosition((Clay_Vector2) {mousePositionX, mousePositionY});
}
if (arrowKeyDownPressedThisFrame) {
if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y - 50;
}
} else if (arrowKeyUpPressedThisFrame) {
if (scrollContainerData.contentDimensions.height > 0) {
scrollContainerData.scrollPosition->y = scrollContainerData.scrollPosition->y + 50;
}
}
Clay_UpdateScrollContainers(isTouchDown, (Clay_Vector2) {mouseWheelX, mouseWheelY}, deltaTime);
return CreateLayout(animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue));
bool isMobileScreen = windowWidth < 750;
if (debugModeEnabled) {
isMobileScreen = windowWidth < 950;
}
return CreateLayout(isMobileScreen, animationLerpValue < 0 ? (animationLerpValue + 1) : (1 - animationLerpValue));
//----------------------------------------------------------------------------------
}
// Dummy main() to please cmake - TODO get wasm working with cmake on this example
int main() {
return 0;
}

View file

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_cpp_project_example CXX)
set(CMAKE_CXX_STANDARD 20)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g")
endif()
add_executable(clay_examples_cpp_project_example main.cpp)
target_include_directories(clay_examples_cpp_project_example PUBLIC .)
if(NOT MSVC)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()

View file

@ -0,0 +1,21 @@
#include <iostream>
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
Clay_LayoutConfig layoutElement = Clay_LayoutConfig { .padding = {5} };
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(void) {
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, (char *)malloc(totalMemorySize));
Clay_Initialize(clayMemory, Clay_Dimensions {1024,768}, Clay_ErrorHandler { HandleClayErrors });
Clay_BeginLayout();
CLAY_AUTO_ID({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) {
CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 }));
}
Clay_EndLayout();
return 0;
}

View file

@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_introducing_clay_video_demo C)
set(CMAKE_C_STANDARD 99)
# Adding Raylib
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples
set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games
FetchContent_Declare(
raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "5.5"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(raylib)
add_executable(clay_examples_introducing_clay_video_demo main.c)
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)
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "/D CLAY_DEBUG")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
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)

View file

@ -0,0 +1,55 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/raylib/clay_renderer_raylib.c"
#include "../shared-layouts/clay-video-demo.c"
// This function is new since the video was published
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
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
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Initialize(clayMemory, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
Font fonts[1];
fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR);
Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts);
ClayVideoDemo_Data data = ClayVideoDemo_Initialize();
while (!WindowShouldClose()) {
// Run once per frame
Clay_SetLayoutDimensions((Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight()
});
Vector2 mousePosition = GetMousePosition();
Vector2 scrollDelta = GetMouseWheelMoveV();
Clay_SetPointerState(
(Clay_Vector2) { mousePosition.x, mousePosition.y },
IsMouseButtonDown(0)
);
Clay_UpdateScrollContainers(
true,
(Clay_Vector2) { scrollDelta.x, scrollDelta.y },
GetFrameTime()
);
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&data);
BeginDrawing();
ClearBackground(BLACK);
Clay_Raylib_Render(renderCommands, fonts);
EndDrawing();
}
// This function is new since the video was published
Clay_Raylib_Close();
}

View file

@ -0,0 +1,3 @@
clay_playdate_example.pdx
Source/pdex.dylib
Source/pdex.elf

View file

@ -0,0 +1,40 @@
cmake_minimum_required(VERSION 3.27)
set(CMAKE_C_STANDARD 99)
set(ENVSDK $ENV{PLAYDATE_SDK_PATH})
if (NOT ${ENVSDK} STREQUAL "")
# Convert path from Windows
file(TO_CMAKE_PATH ${ENVSDK} SDK)
else()
execute_process(
COMMAND bash -c "egrep '^\\s*SDKRoot' $HOME/.Playdate/config"
COMMAND head -n 1
COMMAND cut -c9-
OUTPUT_VARIABLE SDK
OUTPUT_STRIP_TRAILING_WHITESPACE
)
endif()
if (NOT EXISTS ${SDK})
message(FATAL_ERROR "SDK Path not found; set ENV value PLAYDATE_SDK_PATH")
return()
endif()
set(CMAKE_CONFIGURATION_TYPES "Debug;Release")
set(CMAKE_XCODE_GENERATE_SCHEME TRUE)
# Game Name Customization
set(PLAYDATE_GAME_NAME clay_playdate_example)
set(PLAYDATE_GAME_DEVICE clay_playdate_example_DEVICE)
project(${PLAYDATE_GAME_NAME} C ASM)
if (TOOLCHAIN STREQUAL "armgcc")
add_executable(${PLAYDATE_GAME_DEVICE} main.c)
else()
add_library(${PLAYDATE_GAME_NAME} SHARED main.c)
endif()
include(${SDK}/C_API/buildsupport/playdate_game.cmake)

View file

@ -0,0 +1,37 @@
# Playdate console example
This example uses a modified version of the document viewer application from the Clay video demo. The Playdate console has a very small black and white screen, so some of the fixed sizes and styles needed to be modified to make the application usable on the console. The selected document can be changed using up/down on the D-pad, and the selected document can be scrolled with the crank.
## Building
You need to have the (Playdate SDK)[https://play.date/dev/] installed to be able to build this example. Once it's installed you can build it by adding -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON when initialising a directory with cmake.
e.g.
```
cmake -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON cmake-build-debug
```
And then build it:
```
cmake --build cmake-build-debug
```
The pdx file will be located at examples/playdate-project-example/clay_playdate_example.pdx. You can then open it with the Playdate simulator.
## Building for the playdate device
By default building this example will produce a pdx which can only run on the Playdate simulator application. To build a pdx that can run on the Playdate hardware you need to set the toolchain to use armgcc, toolchain file to the arm.cmake provided in the playdate SDK and make sure to disable the other examples. The Playdate hardware requires threads to be disabled which is not compatible with some of the other examples.
e.g. To setup the cmake-build-release directory for device builds:
```
cmake -DTOOLCHAIN=armgcc -DCMAKE_TOOLCHAIN_FILE=/Users/mattahj/Developer/PlaydateSDK/C_API/buildsupport/arm.cmake -DCLAY_INCLUDE_ALL_EXAMPLES=OFF -DCLAY_INCLUDE_PLAYDATE_EXAMPLES=ON -B cmake-build-release
```
And then build it:
```
cmake --build cmake-build-release
```

View file

@ -0,0 +1,5 @@
name=Clay Playdate Example
author=Matthew Jennings
description=A small demo of Clay running on the Playdate
bundleID=dev.mattahj.clay_example
imagePath=

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,360 @@
// This is the video demo with some adjustments so it works on the playdate
// console The playdate screen is only 400x240 pixels and it can only display
// black and white, so some fixed sizes and colours needed tweaking! The file
// menu was also removed as it does not really make sense when there is no
// pointer
//
// Note: The playdate console also does not support dynamic font sizes - fonts must be
// created at a specific size with the pdc tool - so any font size set in the clay layout
// will have no effect.
#include "pd_api.h"
#include "../../clay.h"
#include <stdlib.h>
const int FONT_ID_BODY = 0;
const int FONT_ID_BUTTON = 1;
Clay_Color COLOR_WHITE = { 255, 255, 255, 255 };
Clay_Color COLOR_BLACK = { 0, 0, 0, 255 };
void RenderHeaderButton(Clay_String text) {
CLAY_AUTO_ID({
.layout = { .padding = { 8, 8, 4, 4 } },
.backgroundColor = COLOR_BLACK,
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
CLAY_TEXT(
text,
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BUTTON, .textColor = COLOR_WHITE })
);
}
}
typedef struct {
Clay_String title;
Clay_String contents;
LCDBitmap* image;
} Document;
typedef struct {
Document *documents;
uint32_t length;
} DocumentArray;
#define MAX_DOCUMENTS 3
Document documentsRaw[MAX_DOCUMENTS];
DocumentArray documents = { .length = MAX_DOCUMENTS, .documents = documentsRaw };
void ClayVideoDemoPlaydate_Initialize(PlaydateAPI* pd) {
documents.documents[0] = (Document){
.title = CLAY_STRING("Squirrels"),
.image = pd->graphics->loadBitmap("star.png", NULL),
.contents = CLAY_STRING(
"The Secret Life of Squirrels: Nature's Clever Acrobats\n"
"Squirrels are often overlooked creatures, dismissed as mere park "
"inhabitants or backyard nuisances. Yet, beneath their fluffy tails "
"and twitching noses lies an intricate world of cunning, agility, "
"and survival tactics that are nothing short of fascinating. As one "
"of the most common mammals in North America, squirrels have adapted "
"to a wide range of environments from bustling urban centers to "
"tranquil forests and have developed a variety of unique behaviors "
"that continue to intrigue scientists and nature enthusiasts alike.\n"
"\n"
"Master Tree Climbers\n"
"At the heart of a squirrel's skill set is its impressive ability to "
"navigate trees with ease. Whether they're darting from branch to "
"branch or leaping across wide gaps, squirrels possess an innate "
"talent for acrobatics. Their powerful hind legs, which are longer "
"than their front legs, give them remarkable jumping power. With a "
"tail that acts as a counterbalance, squirrels can leap distances of "
"up to ten times the length of their body, making them some of the "
"best aerial acrobats in the animal kingdom.\n"
"But it's not just their agility that makes them exceptional "
"climbers. Squirrels' sharp, curved claws allow them to grip tree "
"bark with precision, while the soft pads on their feet provide "
"traction on slippery surfaces. Their ability to run at high speeds "
"and scale vertical trunks with ease is a testament to the "
"evolutionary adaptations that have made them so successful in their "
"arboreal habitats.\n"
"\n"
"Food Hoarders Extraordinaire\n"
"Squirrels are often seen frantically gathering nuts, seeds, and "
"even fungi in preparation for winter. While this behavior may seem "
"like instinctual hoarding, it is actually a survival strategy that "
"has been honed over millions of years. Known as \"scatter "
"hoarding,\" squirrels store their food in a variety of hidden "
"locations, often burying it deep in the soil or stashing it in "
"hollowed-out tree trunks.\n"
"Interestingly, squirrels have an incredible memory for the "
"locations of their caches. Research has shown that they can "
"remember thousands of hiding spots, often returning to them months "
"later when food is scarce. However, they don't always recover every "
"stash some forgotten caches eventually sprout into new trees, "
"contributing to forest regeneration. This unintentional role as "
"forest gardeners highlights the ecological importance of squirrels "
"in their ecosystems.\n"
"\n"
"The Great Squirrel Debate: Urban vs. Wild\n"
"While squirrels are most commonly associated with rural or wooded "
"areas, their adaptability has allowed them to thrive in urban "
"environments as well. In cities, squirrels have become adept at "
"finding food sources in places like parks, streets, and even "
"garbage cans. However, their urban counterparts face unique "
"challenges, including traffic, predators, and the lack of natural "
"shelters. Despite these obstacles, squirrels in urban areas are "
"often observed using human infrastructure such as buildings, "
"bridges, and power lines as highways for their acrobatic "
"escapades.\n"
"There is, however, a growing concern regarding the impact of urban "
"life on squirrel populations. Pollution, deforestation, and the "
"loss of natural habitats are making it more difficult for squirrels "
"to find adequate food and shelter. As a result, conservationists "
"are focusing on creating squirrel-friendly spaces within cities, "
"with the goal of ensuring these resourceful creatures continue to "
"thrive in both rural and urban landscapes.\n"
"\n"
"A Symbol of Resilience\n"
"In many cultures, squirrels are symbols of resourcefulness, "
"adaptability, and preparation. Their ability to thrive in a variety "
"of environments while navigating challenges with agility and grace "
"serves as a reminder of the resilience inherent in nature. Whether "
"you encounter them in a quiet forest, a city park, or your own "
"backyard, squirrels are creatures that never fail to amaze with "
"their endless energy and ingenuity.\n"
"In the end, squirrels may be small, but they are mighty in their "
"ability to survive and thrive in a world that is constantly "
"changing. So next time you spot one hopping across a branch or "
"darting across your lawn, take a moment to appreciate the "
"remarkable acrobat at work a true marvel of the natural world.\n"
)
};
documents.documents[1] = (Document){
.title = CLAY_STRING("Lorem Ipsum"),
.image = pd->graphics->loadBitmap("star.png", NULL),
.contents = CLAY_STRING(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim "
"ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut "
"aliquip ex ea commodo consequat. Duis aute irure dolor in "
"reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla "
"pariatur. Excepteur sint occaecat cupidatat non proident, sunt in "
"culpa qui officia deserunt mollit anim id est laborum."
)
};
documents.documents[2] = (Document){
.title = CLAY_STRING("Vacuum Instructions"),
.image = pd->graphics->loadBitmap("star.png", NULL),
.contents = CLAY_STRING(
"Chapter 3: Getting Started - Unpacking and Setup\n"
"\n"
"Congratulations on your new SuperClean Pro 5000 vacuum cleaner! In "
"this section, we will guide you through the simple steps to get "
"your vacuum up and running. Before you begin, please ensure that "
"you have all the components listed in the \"Package Contents\" "
"section on page 2.\n"
"\n"
"1. Unboxing Your Vacuum\n"
"Carefully remove the vacuum cleaner from the box. Avoid using sharp "
"objects that could damage the product. Once removed, place the unit "
"on a flat, stable surface to proceed with the setup. Inside the "
"box, you should find:\n"
"\n"
" The main vacuum unit\n"
" A telescoping extension wand\n"
" A set of specialized cleaning tools (crevice tool, upholstery "
"brush, etc.)\n"
" A reusable dust bag (if applicable)\n"
" A power cord with a 3-prong plug\n"
" A set of quick-start instructions\n"
"\n"
"2. Assembling Your Vacuum\n"
"Begin by attaching the extension wand to the main body of the "
"vacuum cleaner. Line up the connectors and twist the wand into "
"place until you hear a click. Next, select the desired cleaning "
"tool and firmly attach it to the wand's end, ensuring it is "
"securely locked in.\n"
"\n"
"For models that require a dust bag, slide the bag into the "
"compartment at the back of the vacuum, making sure it is properly "
"aligned with the internal mechanism. If your vacuum uses a bagless "
"system, ensure the dust container is correctly seated and locked in "
"place before use.\n"
"\n"
"3. Powering On\n"
"To start the vacuum, plug the power cord into a grounded electrical "
"outlet. Once plugged in, locate the power switch, usually "
"positioned on the side of the handle or body of the unit, depending "
"on your model. Press the switch to the \"On\" position, and you "
"should hear the motor begin to hum. If the vacuum does not power "
"on, check that the power cord is securely plugged in, and ensure "
"there are no blockages in the power switch.\n"
"\n"
"Note: Before first use, ensure that the vacuum filter (if your "
"model has one) is properly installed. If unsure, refer to \"Section "
"5: Maintenance\" for filter installation instructions."
)
};
}
Clay_RenderCommandArray ClayVideoDemoPlaydate_CreateLayout(int selectedDocumentIndex) {
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
Clay_BorderElementConfig contentBorders = {
.color = COLOR_BLACK,
.width = { .top = 1, .left = 1, .right = 1, .bottom = 1 }
};
// Build UI here
CLAY(CLAY_ID("OuterContainer"), {
.backgroundColor = COLOR_WHITE,
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(8),
.childGap = 4
}
}) {
// Child elements go inside braces
CLAY(CLAY_ID("HeaderBar"), {
.layout = {
.sizing = {
.height = CLAY_SIZING_FIXED(30),
.width = CLAY_SIZING_GROW(0)
},
.childGap = 8,
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER }
},
}) {
// Header buttons go here
CLAY(CLAY_ID("FileButton"), {
.layout = {
.padding = { 8, 8, 4, 4 }
},
.backgroundColor = COLOR_BLACK,
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
CLAY_TEXT(
CLAY_STRING("File"),
CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BUTTON,
.textColor = COLOR_WHITE
})
);
}
RenderHeaderButton(CLAY_STRING("Edit"));
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {}
RenderHeaderButton(CLAY_STRING("Upload"));
RenderHeaderButton(CLAY_STRING("Media"));
RenderHeaderButton(CLAY_STRING("Support"));
}
CLAY(CLAY_ID("LowerContent"), {
.layout = { .sizing = layoutExpand, .childGap = 8 },
}) {
CLAY(CLAY_ID("Sidebar"), {
.border = contentBorders,
.cornerRadius = CLAY_CORNER_RADIUS(4),
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.padding = CLAY_PADDING_ALL(8),
.childGap = 4,
.sizing = {
.width = CLAY_SIZING_FIXED(125),
.height = CLAY_SIZING_GROW(0)
}
}
}) {
for (int i = 0; i < documents.length; i++) {
Document document = documents.documents[i];
Clay_LayoutConfig sidebarButtonLayout = {
.sizing = { .width = CLAY_SIZING_GROW(0) },
.padding = CLAY_PADDING_ALL(8)
};
if (i == selectedDocumentIndex) {
CLAY_AUTO_ID({
.layout = sidebarButtonLayout,
.backgroundColor = COLOR_BLACK,
.cornerRadius = CLAY_CORNER_RADIUS(4)
}) {
CLAY_TEXT(
document.title,
CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BUTTON,
.textColor = COLOR_WHITE
})
);
}
} else {
CLAY_AUTO_ID({
.layout = sidebarButtonLayout,
.backgroundColor = (Clay_Color){ 0, 0, 0, Clay_Hovered() ? 120 : 0 },
.cornerRadius = CLAY_CORNER_RADIUS(4),
.border = contentBorders
}) {
CLAY_TEXT(
document.title,
CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BUTTON,
.textColor = COLOR_BLACK,
})
);
}
}
}
}
CLAY(CLAY_ID("MainContent"), {
.border = contentBorders,
.cornerRadius = CLAY_CORNER_RADIUS(4),
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 8,
.padding = CLAY_PADDING_ALL(8),
.sizing = layoutExpand
}
}) {
Document selectedDocument = documents.documents[selectedDocumentIndex];
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_LEFT_TO_RIGHT,
.childGap = 4,
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_BOTTOM }
}
}) {
CLAY_TEXT(
selectedDocument.title,
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK })
);
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_FIXED(32),
.height = CLAY_SIZING_FIXED(30)
}
},
.image = { .imageData = selectedDocument.image, .sourceDimensions = { 32, 30 } }
}) {}
}
CLAY_TEXT(
selectedDocument.contents,
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY, .textColor = COLOR_BLACK })
);
}
}
}
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommandArray_Get(&renderCommands, i);
}
return renderCommands;
}

View file

@ -0,0 +1,115 @@
#include "pd_api.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/playdate/clay_renderer_playdate.c"
#include "clay-video-demo-playdate.c"
static int update(void *userdata);
#define NUM_FONTS 2
const char *fontsToLoad[NUM_FONTS] = {
"/System/Fonts/Asheville-Sans-14-Bold.pft",
"/System/Fonts/Roobert-10-Bold.pft"
};
void HandleClayErrors(Clay_ErrorData errorData) {}
struct TextUserData {
LCDFont *font[NUM_FONTS];
PlaydateAPI *pd;
};
static struct TextUserData textUserData = { .font = { NULL }, .pd = NULL };
static Clay_Dimensions PlayDate_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) {
struct TextUserData *textUserData = userData;
int width = textUserData->pd->graphics->getTextWidth(
textUserData->font[config->fontId],
text.chars,
Clay_Playdate_CountUtf8Codepoints(text.chars, text.length),
kUTF8Encoding,
0
);
int height = textUserData->pd->graphics->getFontHeight(textUserData->font[config->fontId]);
return (Clay_Dimensions){
.width = (float)width,
.height = (float)height,
};
}
#ifdef _WINDLL
__declspec(dllexport)
#endif
int eventHandler(PlaydateAPI* pd, PDSystemEvent event, uint32_t eventArg) {
if (event == kEventInit) {
const char *err;
for (int i = 0; i < NUM_FONTS; ++i) {
textUserData.font[i] = pd->graphics->loadFont(fontsToLoad[i], &err);
if (textUserData.font[i] == NULL) {
pd->system->error("%s:%i Couldn't load font %s: %s", __FILE__, __LINE__, fontsToLoad[i], err);
}
}
textUserData.pd = pd;
pd->system->setUpdateCallback(update, pd);
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(
totalMemorySize,
pd->system->realloc(NULL, totalMemorySize)
);
Clay_Initialize(
clayMemory,
(Clay_Dimensions){
(float)pd->display->getWidth(),
(float)pd->display->getHeight()
},
(Clay_ErrorHandler){HandleClayErrors}
);
Clay_SetMeasureTextFunction(PlayDate_MeasureText, &textUserData);
ClayVideoDemoPlaydate_Initialize(pd);
}
return 0;
}
int selectedDocumentIndex = 0;
#define WRAP_RANGE(x, N) ((((x) % (N)) + (N)) % (N))
static int update(void *userdata) {
PlaydateAPI *pd = userdata;
PDButtons pushedButtons;
pd->system->getButtonState(NULL, &pushedButtons, NULL);
if (pushedButtons & kButtonDown) {
selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex + 1, MAX_DOCUMENTS);
} else if (pushedButtons & kButtonUp) {
selectedDocumentIndex = WRAP_RANGE(selectedDocumentIndex - 1, MAX_DOCUMENTS);
}
pd->graphics->clear(kColorWhite);
// A bit hacky, setting the cursor on to the document view so it can be
// scrolled..
Clay_SetPointerState(
(Clay_Vector2){
.x = pd->display->getWidth() / 2.0f,
.y = pd->display->getHeight() / 2.0f
},
false
);
float crankDelta = pd->system->getCrankChange();
Clay_UpdateScrollContainers(
false,
(Clay_Vector2){ 0, -crankDelta * 0.25f },
pd->system->getElapsedTime()
);
Clay_RenderCommandArray renderCommands = ClayVideoDemoPlaydate_CreateLayout(selectedDocumentIndex);
Clay_Playdate_Render(pd, renderCommands, textUserData.font);
return 1;
}

View file

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.27)
project(clay_examples_raylib_multi_context C)
set(CMAKE_C_STANDARD 99)
# Adding Raylib
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) # don't build the supplied examples
set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example games
FetchContent_Declare(
raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "5.5"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(raylib)
add_executable(clay_examples_raylib_multi_context main.c)
target_compile_options(clay_examples_raylib_multi_context PUBLIC)
target_include_directories(clay_examples_raylib_multi_context PUBLIC .)
target_link_libraries(clay_examples_raylib_multi_context PUBLIC raylib)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
add_custom_command(
TARGET clay_examples_raylib_multi_context POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,77 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/raylib/clay_renderer_raylib.c"
#include "../shared-layouts/clay-video-demo.c"
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
Clay_RenderCommandArray CreateLayout(Clay_Context* context, ClayVideoDemo_Data *data) {
Clay_SetCurrentContext(context);
Clay_SetDebugModeEnabled(true);
// Run once per frame
Clay_SetLayoutDimensions((Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight() / 2
});
Vector2 mousePosition = GetMousePosition();
mousePosition.y -= data->yOffset;
Vector2 scrollDelta = GetMouseWheelMoveV();
Clay_SetPointerState(
(Clay_Vector2) { mousePosition.x, mousePosition.y },
IsMouseButtonDown(0)
);
Clay_UpdateScrollContainers(
true,
(Clay_Vector2) { scrollDelta.x, scrollDelta.y },
GetFrameTime()
);
return ClayVideoDemo_CreateLayout(data);
}
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
Font fonts[1];
fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
SetTextureFilter(fonts[FONT_ID_BODY_16].texture, TEXTURE_FILTER_BILINEAR);
uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemoryTop = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Context *clayContextTop = Clay_Initialize(clayMemoryTop, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight() / 2
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
ClayVideoDemo_Data dataTop = ClayVideoDemo_Initialize();
Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts);
Clay_Arena clayMemoryBottom = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
Clay_Context *clayContextBottom = Clay_Initialize(clayMemoryBottom, (Clay_Dimensions) {
.width = GetScreenWidth(),
.height = GetScreenHeight() / 2
}, (Clay_ErrorHandler) { HandleClayErrors }); // This final argument is new since the video was published
ClayVideoDemo_Data dataBottom = ClayVideoDemo_Initialize();
Clay_SetMeasureTextFunction(Raylib_MeasureText, fonts);
while (!WindowShouldClose()) {
dataBottom.yOffset = GetScreenHeight() / 2;
Clay_RenderCommandArray renderCommandsTop = CreateLayout(clayContextTop, &dataTop);
Clay_RenderCommandArray renderCommandsBottom = CreateLayout(clayContextBottom, &dataBottom);
BeginDrawing();
ClearBackground(BLACK);
Clay_Raylib_Render(renderCommandsTop, fonts);
Clay_Raylib_Render(renderCommandsBottom, fonts);
EndDrawing();
}
Clay_Raylib_Close();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

View file

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.28)
cmake_minimum_required(VERSION 3.27)
project(clay_examples_raylib_sidebar_scrolling_container C)
set(CMAKE_C_STANDARD 99)
# Adding Raylib
include(FetchContent)
@ -10,21 +11,25 @@ set(BUILD_GAMES OFF CACHE BOOL "" FORCE) # don't build the supplied example g
FetchContent_Declare(
raylib
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
GIT_TAG "master"
GIT_TAG "5.5"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(raylib)
set(CMAKE_C_STANDARD 99)
add_executable(clay_examples_raylib_sidebar_scrolling_container main.c multi-compilation-unit.c)
add_executable(clay_examples_raylib_sidebar_scrolling_container main.c)
target_compile_options(clay_examples_raylib_sidebar_scrolling_container PUBLIC -DCLAY_OVERFLOW_TRAP -Wno-initializer-overrides)
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_RELEASE "-O3")
if(MSVC)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
else()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}")
endif()
add_custom_command(
TARGET clay_examples_raylib_sidebar_scrolling_container POST_BUILD

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,9 @@
#include "../../clay.h"
// NOTE: This file only exists to make sure that clay works when included in multiple translation units.
void SatisfyCompiler(void) {
CLAY(CLAY_ID("SatisfyCompiler"), { }) {
CLAY_TEXT(CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24 }));
}
}

View file

@ -0,0 +1,264 @@
#include "../../clay.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_AUTO_ID({
.layout = { .padding = { 16, 16, 8, 8 }},
.backgroundColor = { 140, 140, 140, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(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_AUTO_ID({.layout = { .padding = CLAY_PADDING_ALL(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;
Document documentsRaw[5];
DocumentArray documents = {
.length = 5,
.documents = documentsRaw
};
typedef struct {
intptr_t offset;
intptr_t memory;
} ClayVideoDemo_Arena;
typedef struct {
int32_t selectedDocumentIndex;
float yOffset;
ClayVideoDemo_Arena frameArena;
} ClayVideoDemo_Data;
typedef struct {
int32_t requestedDocumentIndex;
int32_t* selectedDocumentIndex;
} SidebarClickData;
void HandleSidebarInteraction(
Clay_ElementId elementId,
Clay_PointerData pointerData,
intptr_t userData
) {
SidebarClickData *clickData = (SidebarClickData*)userData;
// If this button was clicked
if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
if (clickData->requestedDocumentIndex >= 0 && clickData->requestedDocumentIndex < documents.length) {
// Select the corresponding document
*clickData->selectedDocumentIndex = clickData->requestedDocumentIndex;
}
}
}
ClayVideoDemo_Data ClayVideoDemo_Initialize() {
documents.documents[0] = (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") };
documents.documents[1] = (Document){ .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.") };
documents.documents[2] = (Document){ .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.") };
documents.documents[3] = (Document){ .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") };
documents.documents[4] = (Document){ .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") };
ClayVideoDemo_Data data = {
.frameArena = { .memory = (intptr_t)malloc(1024) }
};
return data;
}
Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) {
data->frameArena.offset = 0;
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
Clay_Color contentBackgroundColor = { 90, 90, 90, 255 };
// Build UI here
CLAY(CLAY_ID("OuterContainer"), {
.backgroundColor = {43, 41, 51, 255 },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = CLAY_PADDING_ALL(16),
.childGap = 16
}
}) {
// Child elements go inside braces
CLAY(CLAY_ID("HeaderBar"), {
.layout = {
.sizing = {
.height = CLAY_SIZING_FIXED(60),
.width = CLAY_SIZING_GROW(0)
},
.padding = { 16, 16, 0, 0 },
.childGap = 16,
.childAlignment = {
.y = CLAY_ALIGN_Y_CENTER
}
},
.backgroundColor = contentBackgroundColor,
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Header buttons go here
CLAY(CLAY_ID("FileButton"), {
.layout = { .padding = { 16, 16, 8, 8 }},
.backgroundColor = {140, 140, 140, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(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"), {
.floating = {
.attachTo = CLAY_ATTACH_TO_PARENT,
.attachPoints = {
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
},
},
.layout = {
.padding = {0, 0, 8, 8 }
}
}) {
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIXED(200)
},
},
.backgroundColor = {40, 40, 40, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
// Render dropdown items here
RenderDropdownMenuItem(CLAY_STRING("New"));
RenderDropdownMenuItem(CLAY_STRING("Open"));
RenderDropdownMenuItem(CLAY_STRING("Close"));
}
}
}
}
RenderHeaderButton(CLAY_STRING("Edit"));
CLAY_AUTO_ID({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {}
RenderHeaderButton(CLAY_STRING("Upload"));
RenderHeaderButton(CLAY_STRING("Media"));
RenderHeaderButton(CLAY_STRING("Support"));
}
CLAY(CLAY_ID("LowerContent"), {
.layout = { .sizing = layoutExpand, .childGap = 16 }
}) {
CLAY(CLAY_ID("Sidebar"), {
.backgroundColor = contentBackgroundColor,
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.padding = CLAY_PADDING_ALL(16),
.childGap = 8,
.sizing = {
.width = CLAY_SIZING_FIXED(250),
.height = CLAY_SIZING_GROW(0)
}
}
}) {
for (int i = 0; i < documents.length; i++) {
Document document = documents.documents[i];
Clay_LayoutConfig sidebarButtonLayout = {
.sizing = { .width = CLAY_SIZING_GROW(0) },
.padding = CLAY_PADDING_ALL(16)
};
if (i == data->selectedDocumentIndex) {
CLAY_AUTO_ID({
.layout = sidebarButtonLayout,
.backgroundColor = {120, 120, 120, 255 },
.cornerRadius = CLAY_CORNER_RADIUS(8)
}) {
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
} else {
SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset);
*clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex };
data->frameArena.offset += sizeof(SidebarClickData);
CLAY_AUTO_ID({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
Clay_OnHover(HandleSidebarInteraction, (intptr_t)clickData);
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 20,
.textColor = { 255, 255, 255, 255 }
}));
}
}
}
}
CLAY(CLAY_ID("MainContent"), {
.backgroundColor = contentBackgroundColor,
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 16,
.padding = CLAY_PADDING_ALL(16),
.sizing = layoutExpand
}
}) {
Document selectedDocument = documents.documents[data->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
}));
}
}
}
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
for (int32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommandArray_Get(&renderCommands, i)->boundingBox.y += data->yOffset;
}
return renderCommands;
}

View file

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.27)
project(sokol_corner_radius C)
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
add_executable(sokol_corner_radius WIN32 main.c)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_corner_radius)
else()
add_executable(sokol_corner_radius main.c)
endif()
target_link_libraries(sokol_corner_radius PUBLIC sokol)

View file

@ -0,0 +1,110 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "util/sokol_gl.h"
#include "fontstash.h"
#include "util/sokol_fontstash.h"
#define SOKOL_CLAY_IMPL
#include "../../renderers/sokol/sokol_clay.h"
static void init() {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
sgl_setup(&(sgl_desc_t){
.logger.func = slog_func,
});
sclay_setup();
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0});
Clay_SetMeasureTextFunction(sclay_measure_text, NULL);
}
Clay_RenderCommandArray CornerRadiusTest(){
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
CLAY(CLAY_ID("OuterContainer"), {
.backgroundColor = {43, 41, 51, 255},
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = layoutExpand,
.padding = {0, 0, 20, 20},
.childGap = 20
}
}) {
for(int i = 0; i < 6; ++i){
CLAY(CLAY_IDI("Row", i), {
.layout = {
.layoutDirection = CLAY_LEFT_TO_RIGHT,
.sizing = layoutExpand,
.padding = {20, 20, 0, 0},
.childGap = 20
}
}) {
for(int j = 0; j < 6; ++j){
CLAY(CLAY_IDI("Tile", i*6+j), {
.backgroundColor = {120, 140, 255, 128},
.cornerRadius = {(i%3)*15, (j%3)*15, (i/2)*15, (j/2)*15},
.border = {
.color = {120, 140, 255, 255},
.width = {3, 9, 6, 12, 0},
},
.layout = { .sizing = layoutExpand }
});
}
}
}
}
return Clay_EndLayout();
}
static void frame() {
sclay_new_frame();
Clay_RenderCommandArray renderCommands = CornerRadiusTest();
sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() });
sgl_matrix_mode_modelview();
sgl_load_identity();
sclay_render(renderCommands, NULL);
sgl_draw();
sg_end_pass();
sg_commit();
}
static void event(const sapp_event *ev) {
if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){
Clay_SetDebugModeEnabled(true);
} else {
sclay_handle_event(ev);
}
}
static void cleanup() {
sclay_shutdown();
sgl_shutdown();
sg_shutdown();
}
sapp_desc sokol_main(int argc, char **argv) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.event_cb = event,
.cleanup_cb = cleanup,
.window_title = "Clay - Corner Radius Test",
.width = 800,
.height = 600,
.icon.sokol_default = true,
.logger.func = slog_func,
};
}

View file

@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.27)
project(sokol_video_demo C)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# Linux -pthread shenanigans
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
endif()
FetchContent_Declare(
fontstash
GIT_REPOSITORY "https://github.com/memononen/fontstash.git"
GIT_TAG "b5ddc9741061343740d85d636d782ed3e07cf7be"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fontstash)
FetchContent_Declare(
sokol
GIT_REPOSITORY "https://github.com/floooh/sokol.git"
GIT_TAG "master"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(sokol)
set(sokol_HEADERS
${sokol_SOURCE_DIR}/sokol_app.h
${sokol_SOURCE_DIR}/sokol_gfx.h
${sokol_SOURCE_DIR}/sokol_glue.h
${sokol_SOURCE_DIR}/sokol_log.h
${sokol_SOURCE_DIR}/util/sokol_gl.h
${fontstash_SOURCE_DIR}/src/fontstash.h
${sokol_SOURCE_DIR}/util/sokol_fontstash.h)
if(CMAKE_SYSTEM_NAME STREQUAL Darwin)
add_library(sokol STATIC sokol.c ${sokol_HEADERS})
target_compile_options(sokol PRIVATE -x objective-c)
target_link_libraries(sokol PUBLIC
"-framework QuartzCore"
"-framework Cocoa"
"-framework MetalKit"
"-framework Metal")
else()
add_library(sokol STATIC sokol.c ${sokol_HEADERS})
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
target_compile_definitions(sokol PRIVATE SOKOL_GLCORE=1)
target_link_libraries(sokol INTERFACE X11 Xi Xcursor GL dl m)
target_link_libraries(sokol PUBLIC Threads::Threads)
endif()
endif()
target_include_directories(sokol INTERFACE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src
PRIVATE ${sokol_SOURCE_DIR} ${fontstash_SOURCE_DIR}/src)
if(CMAKE_SYSTEM_NAME STREQUAL Windows)
add_executable(sokol_video_demo WIN32 main.c)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT sokol_video_demo)
else()
add_executable(sokol_video_demo main.c)
endif()
target_link_libraries(sokol_video_demo PUBLIC sokol)
add_custom_command(
TARGET sokol_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,77 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "util/sokol_gl.h"
#include "fontstash.h"
#include "util/sokol_fontstash.h"
#define SOKOL_CLAY_IMPL
#include "../../renderers/sokol/sokol_clay.h"
#include "../shared-layouts/clay-video-demo.c"
static ClayVideoDemo_Data demoData;
static sclay_font_t fonts[1];
static void init() {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
sgl_setup(&(sgl_desc_t){
.logger.func = slog_func,
});
sclay_setup();
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0});
fonts[FONT_ID_BODY_16] = sclay_add_font("resources/Roboto-Regular.ttf");
Clay_SetMeasureTextFunction(sclay_measure_text, &fonts);
demoData = ClayVideoDemo_Initialize();
}
static void frame() {
sclay_new_frame();
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() });
sgl_matrix_mode_modelview();
sgl_load_identity();
sclay_render(renderCommands, fonts);
sgl_draw();
sg_end_pass();
sg_commit();
}
static void event(const sapp_event *ev) {
if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){
Clay_SetDebugModeEnabled(true);
} else {
sclay_handle_event(ev);
}
}
static void cleanup() {
sclay_shutdown();
sgl_shutdown();
sg_shutdown();
}
sapp_desc sokol_main(int argc, char **argv) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.event_cb = event,
.cleanup_cb = cleanup,
.window_title = "Clay - Sokol Renderer Example",
.width = 800,
.height = 600,
.high_dpi = true,
.icon.sokol_default = true,
.logger.func = slog_func,
};
}

Binary file not shown.

View file

@ -0,0 +1,24 @@
#define SOKOL_IMPL
#if defined(_WIN32)
#define SOKOL_D3D11
#elif defined(__EMSCRIPTEN__)
#define SOKOL_GLES2
#elif defined(__APPLE__)
#define SOKOL_METAL
#else
#define SOKOL_GLCORE33
#endif
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_time.h"
#include "sokol_fetch.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#include "util/sokol_gl.h"
#include <stdio.h> // fontstash requires this
#include <stdlib.h> // fontstash requires this
#define FONTSTASH_IMPLEMENTATION
#include "fontstash.h"
#define SOKOL_FONTSTASH_IMPL
#include "util/sokol_fontstash.h"

View file

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.25)
project(clay_examples_termbox2_demo C)
set(CMAKE_C_STANDARD 11)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(termbox2)
FetchContent_Declare(
stb
GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(stb)
add_executable(clay_examples_termbox2_demo main.c)
target_compile_options(clay_examples_termbox2_demo PUBLIC)
target_include_directories(clay_examples_termbox2_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR})
target_link_libraries(clay_examples_termbox2_demo PRIVATE m) # Used by stb_image.h
add_custom_command(
TARGET clay_examples_termbox2_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,789 @@
/*
Unlicense
Copyright (c) 2025 Mivirl
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
*/
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/termbox2/clay_renderer_termbox2.c"
#define TB_IMPL
#include "termbox2.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
// -------------------------------------------------------------------------------------------------
// -- Internal state
// If the program should exit the main render/interaction loop
bool end_loop = false;
// If the debug tools should be displayed
bool debugMode = false;
// -------------------------------------------------------------------------------------------------
// -- Clay components
void component_text_pair(const char *key, const char *value)
{
size_t keylen = strlen(key);
size_t vallen = strlen(value);
Clay_String keytext = (Clay_String) {
.length = keylen,
.chars = key,
};
Clay_String valtext = (Clay_String) {
.length = vallen,
.chars = value,
};
Clay_TextElementConfig *textconfig =
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } });
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = {
.size.minMax = {
.min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(),
}
},
}
},
}) {
CLAY_TEXT(keytext, textconfig);
CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { }
CLAY_TEXT(valtext, textconfig);
}
}
void component_termbox_settings(void)
{
CLAY_AUTO_ID({
.floating = {
.attachTo = CLAY_ATTACH_TO_PARENT,
.zIndex = 1,
.attachPoints = { CLAY_ATTACH_POINT_CENTER_CENTER, CLAY_ATTACH_POINT_CENTER_TOP },
.offset = { 0, 0 }
},
}) {
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
6 * Clay_Termbox_Cell_Width(),
6 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.border = {
.width = CLAY_BORDER_ALL(1),
.color = { 0x00, 0x00, 0x00, 0xff }
},
.backgroundColor = { 0x7f, 0x00, 0x00, 0x7f }
}) {
const char *color_mode = NULL;
switch (clay_tb_color_mode) {
case TB_OUTPUT_NORMAL: {
color_mode = "TB_OUTPUT_NORMAL";
break;
}
case TB_OUTPUT_256: {
color_mode = "TB_OUTPUT_256";
break;
}
case TB_OUTPUT_216: {
color_mode = "TB_OUTPUT_216";
break;
}
case TB_OUTPUT_GRAYSCALE: {
color_mode = "TB_OUTPUT_GRAYSCALE";
break;
}
case TB_OUTPUT_TRUECOLOR: {
color_mode = "TB_OUTPUT_TRUECOLOR";
break;
}
case CLAY_TB_OUTPUT_NOCOLOR: {
color_mode = "CLAY_TB_OUTPUT_NOCOLOR";
break;
}
default: {
color_mode = "INVALID";
break;
}
}
const char *border_mode = NULL;
switch (clay_tb_border_mode) {
case CLAY_TB_BORDER_MODE_ROUND: {
border_mode = "CLAY_TB_BORDER_MODE_ROUND";
break;
}
case CLAY_TB_BORDER_MODE_MINIMUM: {
border_mode = "CLAY_TB_BORDER_MODE_MINIMUM";
break;
}
default: {
border_mode = "INVALID";
break;
}
}
const char *border_chars = NULL;
switch (clay_tb_border_chars) {
case CLAY_TB_BORDER_CHARS_ASCII: {
border_chars = "CLAY_TB_BORDER_CHARS_ASCII";
break;
}
case CLAY_TB_BORDER_CHARS_UNICODE: {
border_chars = "CLAY_TB_BORDER_CHARS_UNICODE";
break;
}
case CLAY_TB_BORDER_CHARS_BLANK: {
border_chars = "CLAY_TB_BORDER_CHARS_BLANK";
break;
}
case CLAY_TB_BORDER_CHARS_NONE: {
border_chars = "CLAY_TB_BORDER_CHARS_NONE";
break;
}
default: {
border_chars = "INVALID";
break;
}
}
const char *image_mode = NULL;
switch (clay_tb_image_mode) {
case CLAY_TB_IMAGE_MODE_PLACEHOLDER: {
image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER";
break;
}
case CLAY_TB_IMAGE_MODE_BG: {
image_mode = "CLAY_TB_IMAGE_MODE_BG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST";
break;
}
default: {
image_mode = "INVALID";
break;
}
}
const char *transparency = NULL;
if (clay_tb_transparency) {
transparency = "true";
} else {
transparency = "false";
}
CLAY_AUTO_ID({
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM },
}) {
component_text_pair("Color mode", color_mode);
component_text_pair("Border mode", border_mode);
component_text_pair("Border chars", border_chars);
component_text_pair("Image mode", image_mode);
component_text_pair("Transparency", transparency);
}
}
}
}
void component_color_palette(void)
{
CLAY_AUTO_ID({
.layout = {
.childGap = 16,
.padding = {
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.border = {
.width = CLAY_BORDER_OUTSIDE(2),
.color = { 0x00, 0x00, 0x00, 0xff }
},
.backgroundColor = { 0x7f, 0x7f, 0x7f, 0xff }
}) {
for (int type = 0; type < 2; ++type) {
CLAY_AUTO_ID({
.layout ={
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = CLAY_SIZING_FIT(),
.childGap = Clay_Termbox_Cell_Height()
},
}) {
for (float ri = 0; ri < 4; ri += 1) {
CLAY_AUTO_ID({
.layout ={
.sizing = CLAY_SIZING_FIT(),
.childGap = Clay_Termbox_Cell_Width()
},
}) {
for (float r = ri * 0x44; r < (ri + 1) * 0x44; r += 0x22) {
CLAY_AUTO_ID({
.layout ={
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = CLAY_SIZING_FIT(),
},
}) {
for (float g = 0; g < 0xff; g += 0x22) {
CLAY_AUTO_ID({
.layout ={
.sizing = CLAY_SIZING_FIT(),
},
}) {
for (float b = 0; b < 0xff; b += 0x22) {
Clay_Color color = { r, g, b, 0x7f };
Clay_LayoutConfig layout = (Clay_LayoutConfig) {
.sizing = {
.width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()),
.height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height())
}
};
if (0 == type) {
CLAY_AUTO_ID({
.layout = layout,
.backgroundColor = color
}) {}
} else if (1 == type) {
CLAY_AUTO_ID({
.layout = layout,
}) {
CLAY_TEXT(CLAY_STRING("#"), CLAY_TEXT_CONFIG({ .textColor = color }));
}
}
}
}
}
}
}
}
}
}
}
}
}
void component_unicode_text(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.backgroundColor = { 0xcc, 0xbb, 0xaa, 0xff },
.border = {
// This border should still be displayed in CLAY_TB_BORDER_MODE_ROUND mode
.width = {
0.75 * Clay_Termbox_Cell_Width(),
0.75 * Clay_Termbox_Cell_Width(),
0.75 * Clay_Termbox_Cell_Height(),
0.75 * Clay_Termbox_Cell_Height(),
},
.color = { 0x33, 0x33, 0x33, 0xff },
},
}) {
CLAY_TEXT(
CLAY_STRING("Non-ascii character tests:\n"
"\n"
"(from https://www.w3.org/2001/06/utf-8-test/UTF-8-demo.html)\n"
" Mathematics and Sciences:\n"
" ∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈: ⌈x⌉ = x⌋, α ∧ ¬β = ¬(¬α β),\n"
" ⊆ ℕ₀ ⊂ , ⊥ < a ≠ b ≡ c ≤ d ≪ ⇒ (A ⇔ B),\n"
" 2H₂ + O₂ ⇌ 2H₂O, R = 4.7 kΩ, ⌀ 200 mm\n"
"\n"
" Compact font selection example text:\n"
" ABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789\n"
" abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ\n"
" –—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвгд\n"
" ∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi<>⑀₂ἠḂӥẄɐː⍎אԱა\n"
"\n"
"(from https://blog.denisbider.com/2015/09/when-monospace-fonts-arent-unicode.html):\n"
" aeioucsz\n"
" áéíóúčšž\n"
" 台北1234\n"
" 12\n"
" アイウ1234\n"
"\n"
"(from https://stackoverflow.com/a/1644280)\n"
" ٩(-̮̮̃-̃)۶ ٩(●̮̮̃•̃)۶ ٩(͡๏̯͡๏)۶ ٩(-̮̮̃•̃)."
),
CLAY_TEXT_CONFIG({ .textColor = { 0x11, 0x11, 0x11, 0xff } })
);
}
}
void component_keybinds(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
4 * Clay_Termbox_Cell_Width(),
4 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.backgroundColor = { 0x00, 0x7f, 0x7f, 0xff }
}) {
CLAY_TEXT(
CLAY_STRING(
"Termbox2 renderer test\n"
"\n"
"Keybinds:\n"
" c/C - Cycle through color modes\n"
" b/B - Cycle through border modes\n"
" h/H - Cycle through border characters\n"
" i/I - Cycle through image modes\n"
" t/T - Toggle transparency\n"
" d/D - Toggle debug mode\n"
" q/Q - Quit\n"
),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }})
);
}
}
void component_image(clay_tb_image *image, int width)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = (0 == width) ? CLAY_SIZING_GROW() : CLAY_SIZING_FIXED(width),
},
},
.image = {
.imageData = image,
},
.aspectRatio = { 512.0 / 406.0 }
}) { }
}
void component_mouse_data(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
}) {
Clay_Context* context = Clay_GetCurrentContext();
Clay_Vector2 mouse_position = context->pointerInfo.position;
Clay_LayoutConfig layout = (Clay_LayoutConfig) {
.sizing = {
.width = CLAY_SIZING_FIXED(2 * Clay_Termbox_Cell_Width()),
.height = CLAY_SIZING_FIXED(1 * Clay_Termbox_Cell_Height())
}
};
float v = 255 * mouse_position.x / Clay_Termbox_Width();
v = (0 > v) ? 0 : v;
v = (255 < v) ? 255 : v;
Clay_Color color = (Clay_Color) { v, v, v, 0xff };
CLAY_AUTO_ID({
.layout = layout,
.backgroundColor = color
}) {}
v = 255 * mouse_position.y / Clay_Termbox_Height();
v = (0 > v) ? 0 : v;
v = (255 < v) ? 255 : v;
color = (Clay_Color) { v, v, v, 0xff };
CLAY_AUTO_ID({
.layout = layout,
.backgroundColor = color
}) {}
}
}
void component_bordered_text(void)
{
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIT(450),
.height = CLAY_SIZING_FIT(),
},
.padding = CLAY_PADDING_ALL(32)
},
.backgroundColor = { 0x24, 0x55, 0x34, 0xff },
}) {
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0xaa, 0x00, 0x00, 0xff } },
}) {
CLAY_TEXT(
CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0xaa, 0x00, 0xff } },
}) {
CLAY_TEXT(CLAY_STRING("of the border width"),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } },
}) {
CLAY_TEXT(CLAY_STRING("and overlap for multiple lines\nof text"),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
CLAY_AUTO_ID({
.border = { .width = { 1, 1, 1, 1, 1 }, .color = { 0x00, 0x00, 0xaa, 0xff } },
}) {
CLAY_TEXT(CLAY_STRING("this text\nis long enough\nto display all\n borders\naround it"),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } }));
}
}
}
Clay_RenderCommandArray CreateLayout(clay_tb_image *image1, clay_tb_image *image2)
{
Clay_BeginLayout();
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 64
},
.backgroundColor = { 0x24, 0x24, 0x24, 0xff }
}) {
CLAY_AUTO_ID({
.layout = {
.childAlignment = {
.x = CLAY_ALIGN_X_RIGHT,
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = CLAY_SIZING_FIT(),
},
}) {
component_keybinds();
component_unicode_text();
}
CLAY_AUTO_ID({
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 32,
.sizing = CLAY_SIZING_FIT(),
},
}) {
component_termbox_settings();
component_image(image1, 150);
component_image(image2, 0);
component_mouse_data();
component_bordered_text();
}
component_color_palette();
}
return Clay_EndLayout();
}
// -------------------------------------------------------------------------------------------------
// -- Interactive functions
void handle_clay_errors(Clay_ErrorData errorData)
{
Clay_Termbox_Close();
fprintf(stderr, "%s", errorData.errorText.chars);
exit(1);
}
/**
Process events received from termbox2 and handle interaction
*/
void handle_termbox_events(void)
{
// Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing
// If an event is already available, this doesn't wait. Will not wait due to the previous call
// to termbox_waitfor_event. Increasing the wait time reduces load without reducing
// responsiveness (but will of course prevent other code from running on this thread while it's
// waiting)
struct tb_event evt;
int ms_to_wait = 0;
int err = tb_peek_event(&evt, ms_to_wait);
switch (err) {
default:
case TB_ERR_NO_EVENT: {
break;
}
case TB_ERR_POLL: {
if (EINTR != tb_last_errno()) {
Clay_Termbox_Close();
fprintf(stderr, "Failed to read event from TTY\n");
exit(1);
}
break;
}
case TB_OK: {
switch (evt.type) {
case TB_EVENT_RESIZE: {
Clay_SetLayoutDimensions((Clay_Dimensions) {
Clay_Termbox_Width(),
Clay_Termbox_Height()
});
break;
}
case TB_EVENT_KEY: {
if (TB_KEY_CTRL_C == evt.key) {
end_loop = true;
break;
}
switch (evt.ch) {
case 'q':
case 'Q': {
end_loop = true;
break;
}
case 'd':
case 'D': {
debugMode = !debugMode;
Clay_SetDebugModeEnabled(debugMode);
break;
}
case 'c': {
int new_mode = clay_tb_color_mode - 1;
new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR;
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'C': {
int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1);
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'b': {
enum border_mode new_mode = clay_tb_border_mode - 1;
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_MINIMUM;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'B': {
enum border_mode new_mode = (clay_tb_border_mode + 1)
% (CLAY_TB_BORDER_MODE_MINIMUM + 1);
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_ROUND;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'h': {
enum border_chars new_chars = clay_tb_border_chars - 1;
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_NONE;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'H': {
enum border_chars new_chars
= (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1);
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_ASCII;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'i': {
enum image_mode new_mode = clay_tb_image_mode - 1;
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_UNICODE_FAST;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 'I': {
enum image_mode new_mode = (clay_tb_image_mode + 1)
% (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1);
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_PLACEHOLDER;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 't':
case 'T': {
Clay_Termbox_Set_Transparency(!clay_tb_transparency);
}
}
break;
}
case TB_EVENT_MOUSE: {
Clay_Vector2 mousePosition = {
(float)evt.x * Clay_Termbox_Cell_Width(),
(float)evt.y * Clay_Termbox_Cell_Height()
};
// Mouse release events may not be produced by all terminals, and will
// be sent during hover, so can't be used to detect when the mouse has
// been released
switch (evt.key) {
case TB_KEY_MOUSE_LEFT: {
Clay_SetPointerState(mousePosition, true);
break;
}
case TB_KEY_MOUSE_RIGHT: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_MIDDLE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_RELEASE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_WHEEL_UP: {
Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
case TB_KEY_MOUSE_WHEEL_DOWN: {
Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
break;
}
}
}
int main(void)
{
clay_tb_image shark_image1 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg");
clay_tb_image shark_image2 = Clay_Termbox_Image_Load_File("resources/512px-Shark_antwerp_zoo.jpeg");
if (NULL == shark_image1.pixel_data) { exit(1); }
if (NULL == shark_image2.pixel_data) { exit(1); }
int num_elements = 3 * 8192;
Clay_SetMaxElementCount(num_elements);
uint64_t size = Clay_MinMemorySize();
void *memory = malloc(size);
if (NULL == memory) { exit(1); }
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory);
Clay_Termbox_Initialize(
TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false);
Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() },
(Clay_ErrorHandler) { handle_clay_errors, NULL });
Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL);
// Initial render before waiting for events
Clay_RenderCommandArray commands = CreateLayout(&shark_image1, &shark_image2);
Clay_Termbox_Render(commands);
tb_present();
while (!end_loop) {
// Block until event is available. Optional, but reduces load since this demo is purely
// synchronous to user input.
Clay_Termbox_Waitfor_Event();
handle_termbox_events();
commands = CreateLayout(&shark_image1, &shark_image2);
tb_clear();
Clay_Termbox_Render(commands);
tb_present();
}
Clay_Termbox_Close();
Clay_Termbox_Image_Free(&shark_image1);
Clay_Termbox_Image_Free(&shark_image2);
free(memory);
return 0;
}

View file

@ -0,0 +1,72 @@
# Termbox2 renderer demo
Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2)
This demo shows a color palette and a few different components. It allows
changing configuration settings for colors, border size rounding mode,
characters used for borders, and transparency.
```
Keybinds:
c/C - Cycle through color modes
b/B - Cycle through border modes
h/H - Cycle through border characters
i/I - Cycle through image modes
t/T - Toggle transparency
d/D - Toggle debug mode
q/Q - Quit
```
Configuration can be also be overriden by environment variables:
- `CLAY_TB_COLOR_MODE`
- `NORMAL`
- `256`
- `216`
- `GRAYSCALE`
- `TRUECOLOR`
- `NOCOLOR`
- `CLAY_TB_BORDER_CHARS`
- `DEFAULT`
- `ASCII`
- `UNICODE`
- `NONE`
- `CLAY_TB_IMAGE_MODE`
- `DEFAULT`
- `PLACEHOLDER`
- `BG`
- `ASCII_FG`
- `ASCII`
- `UNICODE`
- `ASCII_FG_FAST`
- `ASCII_FAST`
- `UNICODE_FAST`
- `CLAY_TB_TRANSPARENCY`
- `1`
- `0`
- `CLAY_TB_CELL_PIXELS`
- `widthxheight`
## Building
Build the binary with cmake
```sh
mkdir build
cd build
cmake ..
make
```
Then run the executable:
```sh
./clay_examples_termbox2_demo
```
## Attributions
Resources used:
- `512px-Shark_antwerp_zoo.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Shark_antwerp_zoo.jpg>
- License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/deed.en)
- No changes made

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View file

@ -0,0 +1,38 @@
cmake_minimum_required(VERSION 3.25)
project(clay_examples_termbox2_image_demo C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_BUILD_TYPE Release)
set(CMAKE_C_FLAGS_RELEASE "-O3")
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(
termbox2
GIT_REPOSITORY "https://github.com/termbox/termbox2.git"
GIT_TAG "9c9281a9a4c971a2be57f8645e828ec99fd555e8"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(termbox2)
FetchContent_Declare(
stb
GIT_REPOSITORY "https://github.com/nothings/stb.git"
GIT_TAG "f58f558c120e9b32c217290b80bad1a0729fbb2c"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(stb)
add_executable(clay_examples_termbox2_image_demo main.c)
target_compile_options(clay_examples_termbox2_image_demo PUBLIC)
target_include_directories(clay_examples_termbox2_image_demo PUBLIC . PRIVATE ${termbox2_SOURCE_DIR} PRIVATE ${stb_SOURCE_DIR})
target_link_libraries(clay_examples_termbox2_image_demo PRIVATE m) # Used by stb_image.h
add_custom_command(
TARGET clay_examples_termbox2_image_demo POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/resources
${CMAKE_CURRENT_BINARY_DIR}/resources)

View file

@ -0,0 +1,707 @@
/*
Unlicense
Copyright (c) 2025 Mivirl
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <https://unlicense.org/>
*/
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "../../renderers/termbox2/clay_renderer_termbox2.c"
#define TB_IMPL
#include "termbox2.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_RESIZE_IMPLEMENTATION
#include "stb_image_resize2.h"
// -------------------------------------------------------------------------------------------------
// -- Data structures
struct img_group {
clay_tb_image thumbnail;
clay_tb_image image;
clay_tb_image image_1;
clay_tb_image image_2;
int width;
int height;
};
typedef struct img_group img_group;
// -------------------------------------------------------------------------------------------------
// -- Internal state
// If the program should exit the main render/interaction loop
bool end_loop = false;
// If the debug tools should be displayed
bool debugMode = false;
// -------------------------------------------------------------------------------------------------
// -- Internal utility functions
img_group img_group_load(const char *filename)
{
img_group rv;
rv.thumbnail = Clay_Termbox_Image_Load_File(filename);
rv.image = Clay_Termbox_Image_Load_File(filename);
rv.image_1 = Clay_Termbox_Image_Load_File(filename);
rv.image_2 = Clay_Termbox_Image_Load_File(filename);
if (NULL == rv.thumbnail.pixel_data
|| NULL == rv.image.pixel_data
|| NULL == rv.image_1.pixel_data
|| NULL == rv.image_2.pixel_data) {
exit(1);
}
rv.width = rv.image.pixel_width;
rv.height = rv.image.pixel_height;
return rv;
}
void img_group_free(img_group *img)
{
Clay_Termbox_Image_Free(&img->thumbnail);
Clay_Termbox_Image_Free(&img->image);
Clay_Termbox_Image_Free(&img->image_1);
Clay_Termbox_Image_Free(&img->image_2);
}
// -------------------------------------------------------------------------------------------------
// -- Clay components
void component_text_pair(const char *key, const char *value)
{
size_t keylen = strlen(key);
size_t vallen = strlen(value);
Clay_String keytext = (Clay_String) {
.length = keylen,
.chars = key,
};
Clay_String valtext = (Clay_String) {
.length = vallen,
.chars = value,
};
Clay_TextElementConfig *textconfig =
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff } });
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = {
.size.minMax = {
.min = strlen("Border chars CLAY_TB_IMAGE_MODE_UNICODE_FAST") * Clay_Termbox_Cell_Width(),
}
},
}
},
}) {
CLAY_TEXT(keytext, textconfig);
CLAY_AUTO_ID({ .layout = { .sizing = CLAY_SIZING_GROW(1) } }) { }
CLAY_TEXT(valtext, textconfig);
}
}
void component_termbox_settings(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
6 * Clay_Termbox_Cell_Width(),
6 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.border = {
.width = CLAY_BORDER_ALL(1),
.color = { 0x00, 0x00, 0x00, 0xff }
},
.backgroundColor = { 0x7f, 0x00, 0x00, 0x7f }
}) {
const char *color_mode = NULL;
switch (clay_tb_color_mode) {
case TB_OUTPUT_NORMAL: {
color_mode = "TB_OUTPUT_NORMAL";
break;
}
case TB_OUTPUT_256: {
color_mode = "TB_OUTPUT_256";
break;
}
case TB_OUTPUT_216: {
color_mode = "TB_OUTPUT_216";
break;
}
case TB_OUTPUT_GRAYSCALE: {
color_mode = "TB_OUTPUT_GRAYSCALE";
break;
}
case TB_OUTPUT_TRUECOLOR: {
color_mode = "TB_OUTPUT_TRUECOLOR";
break;
}
case CLAY_TB_OUTPUT_NOCOLOR: {
color_mode = "CLAY_TB_OUTPUT_NOCOLOR";
break;
}
default: {
color_mode = "INVALID";
break;
}
}
const char *border_mode = NULL;
switch (clay_tb_border_mode) {
case CLAY_TB_BORDER_MODE_ROUND: {
border_mode = "CLAY_TB_BORDER_MODE_ROUND";
break;
}
case CLAY_TB_BORDER_MODE_MINIMUM: {
border_mode = "CLAY_TB_BORDER_MODE_MINIMUM";
break;
}
default: {
border_mode = "INVALID";
break;
}
}
const char *border_chars = NULL;
switch (clay_tb_border_chars) {
case CLAY_TB_BORDER_CHARS_ASCII: {
border_chars = "CLAY_TB_BORDER_CHARS_ASCII";
break;
}
case CLAY_TB_BORDER_CHARS_UNICODE: {
border_chars = "CLAY_TB_BORDER_CHARS_UNICODE";
break;
}
case CLAY_TB_BORDER_CHARS_BLANK: {
border_chars = "CLAY_TB_BORDER_CHARS_BLANK";
break;
}
case CLAY_TB_BORDER_CHARS_NONE: {
border_chars = "CLAY_TB_BORDER_CHARS_NONE";
break;
}
default: {
border_chars = "INVALID";
break;
}
}
const char *image_mode = NULL;
switch (clay_tb_image_mode) {
case CLAY_TB_IMAGE_MODE_PLACEHOLDER: {
image_mode = "CLAY_TB_IMAGE_MODE_PLACEHOLDER";
break;
}
case CLAY_TB_IMAGE_MODE_BG: {
image_mode = "CLAY_TB_IMAGE_MODE_BG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FG_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FG_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII";
break;
}
case CLAY_TB_IMAGE_MODE_ASCII_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_ASCII_FAST";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE";
break;
}
case CLAY_TB_IMAGE_MODE_UNICODE_FAST: {
image_mode = "CLAY_TB_IMAGE_MODE_UNICODE_FAST";
break;
}
default: {
image_mode = "INVALID";
break;
}
}
const char *transparency = NULL;
if (clay_tb_transparency) {
transparency = "true";
} else {
transparency = "false";
}
CLAY_AUTO_ID({
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM },
}) {
component_text_pair("Color mode", color_mode);
component_text_pair("Border mode", border_mode);
component_text_pair("Border chars", border_chars);
component_text_pair("Image mode", image_mode);
component_text_pair("Transparency", transparency);
}
}
}
void component_keybinds(void)
{
CLAY_AUTO_ID({
.layout = {
.sizing = CLAY_SIZING_FIT(),
.padding = {
4 * Clay_Termbox_Cell_Width(),
4 * Clay_Termbox_Cell_Width(),
2 * Clay_Termbox_Cell_Height(),
2 * Clay_Termbox_Cell_Height(),
}
},
.backgroundColor = { 0x00, 0x7f, 0x7f, 0xff }
}) {
CLAY_TEXT(
CLAY_STRING(
"Termbox2 renderer test\n"
"\n"
"Keybinds:\n"
" up/down arrows - Change selected image\n"
" c/C - Cycle through color modes\n"
" b/B - Cycle through border modes\n"
" h/H - Cycle through border characters\n"
" i/I - Cycle through image modes\n"
" t/T - Toggle transparency\n"
" d/D - Toggle debug mode\n"
" q/Q - Quit\n"
),
CLAY_TEXT_CONFIG({ .textColor = { 0xff, 0xff, 0xff, 0xff }})
);
}
}
void component_image(img_group *img_pair)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 1 * Clay_Termbox_Cell_Height()
},
.backgroundColor = { 0x24, 0x24, 0x24, 0xff }
}) {
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
.image = {
.imageData = &img_pair->image,
},
.aspectRatio = { (float)img_pair->width / img_pair->height }
}) { }
component_keybinds();
}
}
void component_image_small(img_group **img_pairs, int count, int selected_index)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_PERCENT(0.25),
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 20,
.childAlignment = {
.x = CLAY_ALIGN_X_CENTER,
.y = CLAY_ALIGN_Y_CENTER
},
},
}) {
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_PERCENT(0.7),
},
},
.image = {
.imageData = &img_pairs[selected_index]->image_1,
},
.aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height }
}) { }
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
.image = {
.imageData = &img_pairs[selected_index]->image_2,
},
.aspectRatio = { (float)img_pairs[selected_index]->width / img_pairs[selected_index]->height }
}) { }
component_termbox_settings();
}
}
void component_thumbnails(img_group **img_pairs, int count, int selected_index)
{
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_PERCENT(0.1),
.height = CLAY_SIZING_GROW()
},
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.childGap = 20
},
.backgroundColor = { 0x42, 0x42, 0x42, 0xff }
}) {
for (int i = 0; i < count; ++i) {
Clay_BorderElementConfig border;
if (i == selected_index) {
border = (Clay_BorderElementConfig) {
.width =CLAY_BORDER_OUTSIDE(10),
.color = { 0x00, 0x30, 0xc0, 0x8f }
};
} else {
border = (Clay_BorderElementConfig) {
.width = CLAY_BORDER_OUTSIDE(0),
};
}
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
},
},
.border = border,
.image = {
.imageData = &img_pairs[i]->thumbnail,
},
.aspectRatio = { (float)img_pairs[i]->width / img_pairs[i]->height }
}) { }
}
}
}
int selected_thumbnail = 0;
const int thumbnail_count = 5;
Clay_RenderCommandArray CreateLayout(struct img_group **imgs)
{
Clay_BeginLayout();
CLAY_AUTO_ID({
.layout = {
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_GROW()
},
.childAlignment = {
.x = CLAY_ALIGN_X_LEFT,
.y = CLAY_ALIGN_Y_CENTER
},
.childGap = 64
},
.backgroundColor = { 0x24, 0x24, 0x24, 0xff }
}) {
component_thumbnails(imgs, thumbnail_count, selected_thumbnail);
component_image_small(imgs, thumbnail_count, selected_thumbnail);
component_image(imgs[selected_thumbnail]);
}
return Clay_EndLayout();
}
// -------------------------------------------------------------------------------------------------
// -- Interactive functions
void handle_clay_errors(Clay_ErrorData errorData)
{
Clay_Termbox_Close();
fprintf(stderr, "%s", errorData.errorText.chars);
exit(1);
}
/**
Process events received from termbox2 and handle interaction
*/
void handle_termbox_events(void)
{
// Wait up to 100ms for an event (key/mouse press, terminal resize) before continuing
// If an event is already available, this doesn't wait. Will not wait due to the previous call
// to termbox_waitfor_event. Increasing the wait time reduces load without reducing
// responsiveness (but will of course prevent other code from running on this thread while it's
// waiting)
struct tb_event evt;
int ms_to_wait = 0;
int err = tb_peek_event(&evt, ms_to_wait);
switch (err) {
default:
case TB_ERR_NO_EVENT: {
break;
}
case TB_ERR_POLL: {
if (EINTR != tb_last_errno()) {
Clay_Termbox_Close();
fprintf(stderr, "Failed to read event from TTY\n");
exit(1);
}
break;
}
case TB_OK: {
switch (evt.type) {
case TB_EVENT_RESIZE: {
Clay_SetLayoutDimensions((Clay_Dimensions) {
Clay_Termbox_Width(),
Clay_Termbox_Height()
});
break;
}
case TB_EVENT_KEY: {
if (TB_KEY_CTRL_C == evt.key) {
end_loop = true;
break;
}
if (0 != evt.ch) {
switch (evt.ch) {
case 'q':
case 'Q': {
end_loop = true;
break;
}
case 'd':
case 'D': {
debugMode = !debugMode;
Clay_SetDebugModeEnabled(debugMode);
break;
}
case 'c': {
int new_mode = clay_tb_color_mode - 1;
new_mode = (0 <= new_mode) ? new_mode : TB_OUTPUT_TRUECOLOR;
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'C': {
int new_mode = (clay_tb_color_mode + 1) % (TB_OUTPUT_TRUECOLOR + 1);
Clay_Termbox_Set_Color_Mode(new_mode);
break;
}
case 'b': {
enum border_mode new_mode = clay_tb_border_mode - 1;
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_MINIMUM;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'B': {
enum border_mode new_mode = (clay_tb_border_mode + 1)
% (CLAY_TB_BORDER_MODE_MINIMUM + 1);
new_mode = (CLAY_TB_BORDER_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_BORDER_MODE_ROUND;
Clay_Termbox_Set_Border_Mode(new_mode);
break;
}
case 'h': {
enum border_chars new_chars = clay_tb_border_chars - 1;
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_NONE;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'H': {
enum border_chars new_chars
= (clay_tb_border_chars + 1) % (CLAY_TB_BORDER_CHARS_NONE + 1);
new_chars = (CLAY_TB_BORDER_CHARS_DEFAULT < new_chars)
? new_chars
: CLAY_TB_BORDER_CHARS_ASCII;
Clay_Termbox_Set_Border_Chars(new_chars);
break;
}
case 'i': {
enum image_mode new_mode = clay_tb_image_mode - 1;
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_UNICODE_FAST;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 'I': {
enum image_mode new_mode = (clay_tb_image_mode + 1)
% (CLAY_TB_IMAGE_MODE_UNICODE_FAST + 1);
new_mode = (CLAY_TB_IMAGE_MODE_DEFAULT < new_mode)
? new_mode
: CLAY_TB_IMAGE_MODE_PLACEHOLDER;
Clay_Termbox_Set_Image_Mode(new_mode);
break;
}
case 't':
case 'T': {
Clay_Termbox_Set_Transparency(!clay_tb_transparency);
}
}
} else if (0 != evt.key) {
switch (evt.key) {
case TB_KEY_ARROW_UP: {
selected_thumbnail = (selected_thumbnail > 0) ? selected_thumbnail - 1 : 0;
break;
}
case TB_KEY_ARROW_DOWN: {
selected_thumbnail = (selected_thumbnail < thumbnail_count - 1) ? selected_thumbnail + 1 : thumbnail_count - 1;
break;
}
}
}
break;
}
case TB_EVENT_MOUSE: {
Clay_Vector2 mousePosition = {
(float)evt.x * Clay_Termbox_Cell_Width(),
(float)evt.y * Clay_Termbox_Cell_Height()
};
// Mouse release events may not be produced by all terminals, and will
// be sent during hover, so can't be used to detect when the mouse has
// been released
switch (evt.key) {
case TB_KEY_MOUSE_LEFT: {
Clay_SetPointerState(mousePosition, true);
break;
}
case TB_KEY_MOUSE_RIGHT: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_MIDDLE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_RELEASE: {
Clay_SetPointerState(mousePosition, false);
break;
}
case TB_KEY_MOUSE_WHEEL_UP: {
Clay_Vector2 scrollDelta = { 0, 1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
case TB_KEY_MOUSE_WHEEL_DOWN: {
Clay_Vector2 scrollDelta = { 0, -1 * Clay_Termbox_Cell_Height() };
Clay_UpdateScrollContainers(false, scrollDelta, 1);
break;
}
default: {
break;
}
}
break;
}
default: {
break;
}
}
break;
}
}
}
int main(void)
{
img_group *imgs[thumbnail_count];
img_group img_shark = img_group_load("resources/512px-Shark_antwerp_zoo.jpeg");
img_group img_castle = img_group_load("resources/512px-Balmoral_Castle_30_July_2011.jpeg");
img_group img_dog = img_group_load("resources/512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg");
img_group img_rosa = img_group_load("resources/512px-Rosa_Cubana_2018-09-21_1610.jpeg");
img_group img_vorderer = img_group_load("resources/512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg");
imgs[0] = &img_shark;
imgs[1] = &img_castle;
imgs[2] = &img_dog;
imgs[3] = &img_rosa;
imgs[4] = &img_vorderer;
int num_elements = 3 * 8192;
Clay_SetMaxElementCount(num_elements);
uint64_t size = Clay_MinMemorySize();
void *memory = malloc(size);
if (NULL == memory) { exit(1); }
Clay_Arena clay_arena = Clay_CreateArenaWithCapacityAndMemory(size, memory);
Clay_Termbox_Initialize(
TB_OUTPUT_256, CLAY_TB_BORDER_MODE_DEFAULT, CLAY_TB_BORDER_CHARS_DEFAULT, CLAY_TB_IMAGE_MODE_DEFAULT, false);
Clay_Initialize(clay_arena, (Clay_Dimensions) { Clay_Termbox_Width(), Clay_Termbox_Height() },
(Clay_ErrorHandler) { handle_clay_errors, NULL });
Clay_SetMeasureTextFunction(Clay_Termbox_MeasureText, NULL);
// Initial render before waiting for events
Clay_RenderCommandArray commands = CreateLayout(imgs);
Clay_Termbox_Render(commands);
tb_present();
while (!end_loop) {
// Block until event is available. Optional, but reduces load since this demo is purely
// synchronous to user input.
Clay_Termbox_Waitfor_Event();
handle_termbox_events();
commands = CreateLayout(imgs);
Clay_Termbox_Render(commands);
tb_present();
}
Clay_Termbox_Close();
img_group_free(&img_shark);
img_group_free(&img_castle);
img_group_free(&img_dog);
img_group_free(&img_rosa);
img_group_free(&img_vorderer);
free(memory);
return 0;
}

View file

@ -0,0 +1,88 @@
# Termbox2 renderer demo
Terminal-based renderer using [termbox2](https://github.com/termbox/termbox2)
This demo shows a color palette and a few different components. It allows
changing configuration settings for colors, border size rounding mode,
characters used for borders, and transparency.
```
Keybinds:
c/C - Cycle through color modes
b/B - Cycle through border modes
h/H - Cycle through border characters
i/I - Cycle through image modes
t/T - Toggle transparency
d/D - Toggle debug mode
q/Q - Quit
```
Configuration can be also be overriden by environment variables:
- `CLAY_TB_COLOR_MODE`
- `NORMAL`
- `256`
- `216`
- `GRAYSCALE`
- `TRUECOLOR`
- `NOCOLOR`
- `CLAY_TB_BORDER_CHARS`
- `DEFAULT`
- `ASCII`
- `UNICODE`
- `NONE`
- `CLAY_TB_IMAGE_MODE`
- `DEFAULT`
- `PLACEHOLDER`
- `BG`
- `ASCII_FG`
- `ASCII`
- `UNICODE`
- `ASCII_FG_FAST`
- `ASCII_FAST`
- `UNICODE_FAST`
- `CLAY_TB_TRANSPARENCY`
- `1`
- `0`
- `CLAY_TB_CELL_PIXELS`
- `widthxheight`
## Building
Build the binary with cmake
```sh
mkdir build
cd build
cmake ..
make
```
Then run the executable:
```sh
./clay_examples_termbox2_demo
```
## Attributions
Resources used:
- `512px-Shark_antwerp_zoo.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Shark_antwerp_zoo.jpg>
- License: [Creative Commons Attribution 3.0 Unported](https://creativecommons.org/licenses/by/3.0/)
- No changes made
- `512px-Balmoral_Castle_30_July_2011.jpg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Balmoral_Castle_30_July_2011.jpg>
- License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/)
- No changes made
- `512px-German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Schäferhund_(Folder_(IV)_22.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:German_Shepherd_(aka_Alsatian_and_Alsatian_Wolf_Dog),_Deutscher_Sch%C3%A4ferhund_(Folder_(IV)_22.JPG>
- License: [Creative Commons Attribution-ShareAlike 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/)
- No changes made
- `512px-Rosa_Cubana_2018-09-21_1610.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Rosa_Cubana_2018-09-21_1610.jpg>
- License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
- No changes made
- `512px-Vorderer_Graben_10_Bamberg_20190830_001.jpeg`
- Retrieved from <https://commons.wikimedia.org/wiki/File:Vorderer_Graben_10_Bamberg_20190830_001.jpg>
- License: [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/)
- No changes made

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Some files were not shown because too many files have changed in this diff Show more