Compare commits
No commits in common. "v0.10" and "main" have entirely different histories.
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
.DS_Store
|
||||
.idea/
|
||||
build/
|
||||
node_modules/
|
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: [nicbarker]
|
92
.github/workflows/cmake-multi-platform.yml
vendored
Normal 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 }}
|
5
.gitignore
vendored
@ -2,5 +2,6 @@ cmake-build-debug/
|
||||
cmake-build-release/
|
||||
.DS_Store
|
||||
.idea/
|
||||
build/
|
||||
node_modules/
|
||||
node_modules/
|
||||
*.dSYM
|
||||
.vs/
|
@ -1,7 +1,55 @@
|
||||
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")
|
||||
add_subdirectory("examples/clay-official-website")
|
||||
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)
|
||||
|
||||
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")
|
||||
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()
|
||||
|
||||
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 .)
|
||||
|
1
bindings/cpp/README.md
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/TimothyHoytBSME/ClayMan
|
1
bindings/csharp/README
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/Orcolom/clay-cs
|
4
bindings/odin/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
odin
|
||||
odin.dSYM
|
||||
.vscode
|
||||
examples/clay-official-website/clay-official-website
|
203
bindings/odin/README.md
Normal 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: u32 = clay.MinMemorySize()
|
||||
memory := make([^]u8, min_memory_size)
|
||||
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(min_memory_size, memory)
|
||||
clay.Initialize(arena, { width = 1080, height = 720 }, { handler = error_handler })
|
||||
```
|
||||
|
||||
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(
|
||||
clay.Vector2 { 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 = {
|
||||
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
|
||||
render_commands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout()
|
||||
return render_commands
|
||||
}
|
||||
```
|
||||
|
||||
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
@ -0,0 +1,13 @@
|
||||
cp ../../clay.h clay.c;
|
||||
# Intel Mac
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay.c;
|
||||
rm clay.o;
|
||||
rm clay.c;
|
470
bindings/odin/clay-odin/clay.odin
Normal file
@ -0,0 +1,470 @@
|
||||
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,
|
||||
}
|
||||
|
||||
ImageElementConfig :: struct {
|
||||
imageData: rawptr,
|
||||
sourceDimensions: Dimensions,
|
||||
}
|
||||
|
||||
CustomElementConfig :: struct {
|
||||
customData: rawptr,
|
||||
}
|
||||
|
||||
BorderWidth :: struct {
|
||||
left: u16,
|
||||
right: u16,
|
||||
top: u16,
|
||||
bottom: u16,
|
||||
betweenChildren: u16,
|
||||
}
|
||||
|
||||
BorderElementConfig :: struct {
|
||||
color: Color,
|
||||
width: BorderWidth,
|
||||
}
|
||||
|
||||
ScrollElementConfig :: struct {
|
||||
horizontal: bool,
|
||||
vertical: bool,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
FloatingElementConfig :: struct {
|
||||
offset: Vector2,
|
||||
expand: Dimensions,
|
||||
parentId: u32,
|
||||
zIndex: i16,
|
||||
attachment: FloatingAttachPoints,
|
||||
pointerCaptureMode: PointerCaptureMode,
|
||||
attachTo: FloatingAttachToElement,
|
||||
}
|
||||
|
||||
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,
|
||||
sourceDimensions: Dimensions,
|
||||
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: ScrollElementConfig,
|
||||
// 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 {
|
||||
id: ElementId,
|
||||
layout: LayoutConfig,
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
image: ImageElementConfig,
|
||||
floating: FloatingElementConfig,
|
||||
custom: CustomElementConfig,
|
||||
scroll: ScrollElementConfig,
|
||||
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() ---
|
||||
_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 ---
|
||||
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, offset: 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 :: proc() -> proc (config: ElementDeclaration) -> bool {
|
||||
_OpenElement()
|
||||
return ConfigureOpenElement
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
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, 0)
|
||||
}
|
||||
|
||||
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
|
||||
return _HashString(MakeString(label), index, _GetParentElementId())
|
||||
}
|
BIN
bindings/odin/clay-odin/linux/clay.a
Normal file
BIN
bindings/odin/clay-odin/macos-arm64/clay.a
Normal file
BIN
bindings/odin/clay-odin/macos/clay.a
Normal file
BIN
bindings/odin/clay-odin/wasm/clay.o
Normal file
BIN
bindings/odin/clay-odin/windows/clay.lib
Normal file
@ -0,0 +1,542 @@
|
||||
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()({
|
||||
id = 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()({
|
||||
id = clay.ID("CheckImage", index),
|
||||
layout = { sizing = { width = clay.SizingFixed(32) } },
|
||||
image = { imageData = image, sourceDimensions = { 128, 128 } },
|
||||
}) {}
|
||||
clay.Text(text, clay.TextConfig({fontSize = fontSize, fontId = fontId, textColor = color}))
|
||||
}
|
||||
}
|
||||
|
||||
LandingPageDesktop :: proc() {
|
||||
if clay.UI()({
|
||||
id = 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()({
|
||||
id = 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()({ id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({ id = 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()({
|
||||
id = 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()({
|
||||
id = clay.ID("HFileBoxOuter"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
|
||||
}) {
|
||||
if clay.UI()({ id = 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()({
|
||||
id = 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()({ id = clay.ID("FeatureBlocksOuter"), layout = { sizing = { width = clay.SizingGrow({}) } } }) {
|
||||
if clay.UI()({
|
||||
id = 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()({
|
||||
id = 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()({ id = clay.ID("SyntaxPageLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
|
||||
clay.Text("Declarative Syntax", clay.TextConfig(titleTextConfig))
|
||||
if clay.UI()({ id = 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()({ id = clay.ID("SyntaxPageRightImage"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
|
||||
if clay.UI()({
|
||||
id = clay.ID("SyntaxPageRightImageInner"),
|
||||
layout = { sizing = { width = clay.SizingGrow({ max = 568 }) } },
|
||||
image = { imageData = &syntaxImage, sourceDimensions = { 1136, 1194 } },
|
||||
}) {}
|
||||
}
|
||||
}
|
||||
|
||||
DeclarativeSyntaxPageDesktop :: proc() {
|
||||
if clay.UI()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({ id = 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()({ id = clay.ID("PerformanceRightImageOuter"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
|
||||
if clay.UI()({
|
||||
id = clay.ID("PerformanceRightBorder"),
|
||||
layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(400) } },
|
||||
border = { COLOR_LIGHT, {2, 2, 2, 2, 2} },
|
||||
}) {
|
||||
if clay.UI()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({ id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = 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()({
|
||||
id = clay.ID("OuterContainer"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) } },
|
||||
backgroundColor = COLOR_LIGHT,
|
||||
}) {
|
||||
if clay.UI()({
|
||||
id = 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()({ id = 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()({ id = 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()({
|
||||
id = 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()({ id = clay.ID("TopBorder1"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
|
||||
if clay.UI()({ id = clay.ID("TopBorder2"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
|
||||
if clay.UI()({ id = clay.ID("TopBorder3"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
|
||||
if clay.UI()({ id = clay.ID("TopBorder4"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
|
||||
if clay.UI()({ id = clay.ID("TopBorder5"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
|
||||
if clay.UI()({
|
||||
id = clay.ID("ScrollContainerBackgroundRectangle"),
|
||||
scroll = { vertical = true },
|
||||
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) {
|
||||
raylibFonts[fontId] = RaylibFont {
|
||||
font = raylib.LoadFontEx(path, cast(i32)fontSize * 2, nil, 0),
|
||||
fontId = cast(u16)fontId,
|
||||
}
|
||||
raylib.SetTextureFilter(raylibFonts[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(measureText, 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()
|
||||
clayRaylibRender(&renderCommands)
|
||||
raylib.EndDrawing()
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
package main
|
||||
|
||||
import clay "../../clay-odin"
|
||||
import "core:math"
|
||||
import "core:strings"
|
||||
import "vendor:raylib"
|
||||
|
||||
RaylibFont :: struct {
|
||||
fontId: u16,
|
||||
font: raylib.Font,
|
||||
}
|
||||
|
||||
clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color {
|
||||
return raylib.Color{cast(u8)color.r, cast(u8)color.g, cast(u8)color.b, cast(u8)color.a}
|
||||
}
|
||||
|
||||
raylibFonts := [10]RaylibFont{}
|
||||
|
||||
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr) -> clay.Dimensions {
|
||||
// Measure string size for Font
|
||||
textSize: clay.Dimensions = {0, 0}
|
||||
|
||||
maxTextWidth: f32 = 0
|
||||
lineTextWidth: f32 = 0
|
||||
|
||||
textHeight := cast(f32)config.fontSize
|
||||
fontToUse := raylibFonts[config.fontId].font
|
||||
|
||||
for i in 0 ..< int(text.length) {
|
||||
if (text.chars[i] == '\n') {
|
||||
maxTextWidth = max(maxTextWidth, lineTextWidth)
|
||||
lineTextWidth = 0
|
||||
continue
|
||||
}
|
||||
index := cast(i32)text.chars[i] - 32
|
||||
if (fontToUse.glyphs[index].advanceX != 0) {
|
||||
lineTextWidth += cast(f32)fontToUse.glyphs[index].advanceX
|
||||
} else {
|
||||
lineTextWidth += (fontToUse.recs[index].width + cast(f32)fontToUse.glyphs[index].offsetX)
|
||||
}
|
||||
}
|
||||
|
||||
maxTextWidth = max(maxTextWidth, lineTextWidth)
|
||||
|
||||
textSize.width = maxTextWidth / 2
|
||||
textSize.height = textHeight
|
||||
|
||||
return textSize
|
||||
}
|
||||
|
||||
clayRaylibRender :: proc(renderCommands: ^clay.ClayArray(clay.RenderCommand), allocator := context.temp_allocator) {
|
||||
for i in 0 ..< int(renderCommands.length) {
|
||||
renderCommand := clay.RenderCommandArray_Get(renderCommands, cast(i32)i)
|
||||
boundingBox := renderCommand.boundingBox
|
||||
switch (renderCommand.commandType) {
|
||||
case clay.RenderCommandType.None:
|
||||
{}
|
||||
case clay.RenderCommandType.Text:
|
||||
config := renderCommand.renderData.text
|
||||
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
|
||||
text := string(config.stringContents.chars[:config.stringContents.length])
|
||||
cloned := strings.clone_to_cstring(text, allocator)
|
||||
fontToUse: raylib.Font = raylibFonts[config.fontId].font
|
||||
raylib.DrawTextEx(
|
||||
fontToUse,
|
||||
cloned,
|
||||
raylib.Vector2{boundingBox.x, boundingBox.y},
|
||||
cast(f32)config.fontSize,
|
||||
cast(f32)config.letterSpacing,
|
||||
clayColorToRaylibColor(config.textColor),
|
||||
)
|
||||
case clay.RenderCommandType.Image:
|
||||
config := renderCommand.renderData.image
|
||||
tintColor := config.backgroundColor
|
||||
if (tintColor.rgba == 0) {
|
||||
tintColor = { 255, 255, 255, 255 }
|
||||
}
|
||||
// TODO image handling
|
||||
imageTexture := cast(^raylib.Texture2D)config.imageData
|
||||
raylib.DrawTextureEx(imageTexture^, raylib.Vector2{boundingBox.x, boundingBox.y}, 0, boundingBox.width / cast(f32)imageTexture.width, clayColorToRaylibColor(tintColor))
|
||||
case clay.RenderCommandType.ScissorStart:
|
||||
raylib.BeginScissorMode(
|
||||
cast(i32)math.round(boundingBox.x),
|
||||
cast(i32)math.round(boundingBox.y),
|
||||
cast(i32)math.round(boundingBox.width),
|
||||
cast(i32)math.round(boundingBox.height),
|
||||
)
|
||||
case clay.RenderCommandType.ScissorEnd:
|
||||
raylib.EndScissorMode()
|
||||
case clay.RenderCommandType.Rectangle:
|
||||
config := renderCommand.renderData.rectangle
|
||||
if (config.cornerRadius.topLeft > 0) {
|
||||
radius: f32 = (config.cornerRadius.topLeft * 2) / min(boundingBox.width, boundingBox.height)
|
||||
raylib.DrawRectangleRounded(raylib.Rectangle{boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height}, radius, 8, clayColorToRaylibColor(config.backgroundColor))
|
||||
} else {
|
||||
raylib.DrawRectangle(cast(i32)boundingBox.x, cast(i32)boundingBox.y, cast(i32)boundingBox.width, cast(i32)boundingBox.height, clayColorToRaylibColor(config.backgroundColor))
|
||||
}
|
||||
case clay.RenderCommandType.Border:
|
||||
config := renderCommand.renderData.border
|
||||
// Left border
|
||||
if (config.width.left > 0) {
|
||||
raylib.DrawRectangle(
|
||||
cast(i32)math.round(boundingBox.x),
|
||||
cast(i32)math.round(boundingBox.y + config.cornerRadius.topLeft),
|
||||
cast(i32)config.width.left,
|
||||
cast(i32)math.round(boundingBox.height - config.cornerRadius.topLeft - config.cornerRadius.bottomLeft),
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
// Right border
|
||||
if (config.width.right > 0) {
|
||||
raylib.DrawRectangle(
|
||||
cast(i32)math.round(boundingBox.x + boundingBox.width - cast(f32)config.width.right),
|
||||
cast(i32)math.round(boundingBox.y + config.cornerRadius.topRight),
|
||||
cast(i32)config.width.right,
|
||||
cast(i32)math.round(boundingBox.height - config.cornerRadius.topRight - config.cornerRadius.bottomRight),
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
// Top border
|
||||
if (config.width.top > 0) {
|
||||
raylib.DrawRectangle(
|
||||
cast(i32)math.round(boundingBox.x + config.cornerRadius.topLeft),
|
||||
cast(i32)math.round(boundingBox.y),
|
||||
cast(i32)math.round(boundingBox.width - config.cornerRadius.topLeft - config.cornerRadius.topRight),
|
||||
cast(i32)config.width.top,
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
// Bottom border
|
||||
if (config.width.bottom > 0) {
|
||||
raylib.DrawRectangle(
|
||||
cast(i32)math.round(boundingBox.x + config.cornerRadius.bottomLeft),
|
||||
cast(i32)math.round(boundingBox.y + boundingBox.height - cast(f32)config.width.bottom),
|
||||
cast(i32)math.round(boundingBox.width - config.cornerRadius.bottomLeft - config.cornerRadius.bottomRight),
|
||||
cast(i32)config.width.bottom,
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
if (config.cornerRadius.topLeft > 0) {
|
||||
raylib.DrawRing(
|
||||
raylib.Vector2{math.round(boundingBox.x + config.cornerRadius.topLeft), math.round(boundingBox.y + config.cornerRadius.topLeft)},
|
||||
math.round(config.cornerRadius.topLeft - cast(f32)config.width.top),
|
||||
config.cornerRadius.topLeft,
|
||||
180,
|
||||
270,
|
||||
10,
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
if (config.cornerRadius.topRight > 0) {
|
||||
raylib.DrawRing(
|
||||
raylib.Vector2{math.round(boundingBox.x + boundingBox.width - config.cornerRadius.topRight), math.round(boundingBox.y + config.cornerRadius.topRight)},
|
||||
math.round(config.cornerRadius.topRight - cast(f32)config.width.top),
|
||||
config.cornerRadius.topRight,
|
||||
270,
|
||||
360,
|
||||
10,
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
if (config.cornerRadius.bottomLeft > 0) {
|
||||
raylib.DrawRing(
|
||||
raylib.Vector2{math.round(boundingBox.x + config.cornerRadius.bottomLeft), math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomLeft)},
|
||||
math.round(config.cornerRadius.bottomLeft - cast(f32)config.width.top),
|
||||
config.cornerRadius.bottomLeft,
|
||||
90,
|
||||
180,
|
||||
10,
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
if (config.cornerRadius.bottomRight > 0) {
|
||||
raylib.DrawRing(
|
||||
raylib.Vector2 {
|
||||
math.round(boundingBox.x + boundingBox.width - config.cornerRadius.bottomRight),
|
||||
math.round(boundingBox.y + boundingBox.height - config.cornerRadius.bottomRight),
|
||||
},
|
||||
math.round(config.cornerRadius.bottomRight - cast(f32)config.width.bottom),
|
||||
config.cornerRadius.bottomRight,
|
||||
0.1,
|
||||
90,
|
||||
10,
|
||||
clayColorToRaylibColor(config.color),
|
||||
)
|
||||
}
|
||||
case clay.RenderCommandType.Custom:
|
||||
// Implement custom element rendering here
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 193 KiB |
6
bindings/odin/odinfmt.json
Normal 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
@ -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
@ -0,0 +1,2 @@
|
||||
https://github.com/clay-ui-rs/clay
|
||||
https://crates.io/crates/clay-layout
|
2
bindings/zig/README
Normal file
@ -0,0 +1,2 @@
|
||||
https://codeberg.org/Zettexe/clay-zig
|
||||
https://github.com/johan0A/clay-zig-bindings
|
32
cmake/FindCairo.cmake
Normal file
@ -0,0 +1,32 @@
|
||||
# Defines:
|
||||
# CAIRO_FOUND - System has Cairo
|
||||
# CAIRO_INCLUDE_DIRS - Cairo include directories
|
||||
# CAIRO_LIBRARY - Cairo library
|
||||
# Cairo::Cairo - Imported target
|
||||
|
||||
find_path(CAIRO_INCLUDE_DIRS
|
||||
NAMES cairo/cairo.h
|
||||
PATHS ${CAIRO_ROOT_DIR}
|
||||
PATH_SUFFIXES include
|
||||
)
|
||||
|
||||
find_library(CAIRO_LIBRARY
|
||||
NAMES cairo
|
||||
PATHS ${CAIRO_ROOT_DIR}
|
||||
PATH_SUFFIXES lib lib64
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Cairo
|
||||
REQUIRED_VARS CAIRO_LIBRARY CAIRO_INCLUDE_DIRS
|
||||
)
|
||||
|
||||
if(Cairo_FOUND AND NOT TARGET Cairo::Cairo)
|
||||
add_library(Cairo::Cairo UNKNOWN IMPORTED)
|
||||
set_target_properties(Cairo::Cairo PROPERTIES
|
||||
IMPORTED_LOCATION "${CAIRO_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${CAIRO_INCLUDE_DIRS}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(CAIRO_INCLUDE_DIRS CAIRO_LIBRARY)
|
58
examples/SDL2-video-demo/CMakeLists.txt
Normal file
@ -0,0 +1,58 @@
|
||||
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)
|
173
examples/SDL2-video-demo/main.c
Normal 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;
|
||||
}
|
||||
|
BIN
examples/SDL2-video-demo/resources/Roboto-Regular.ttf
Normal file
BIN
examples/SDL2-video-demo/resources/sample.png
Normal file
After Width: | Height: | Size: 850 B |
62
examples/SDL3-simple-demo/CMakeLists.txt
Normal 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 main # Slightly risky to use main branch, but it's the only one available
|
||||
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 # Slightly risky to use main branch, but it's the only one available
|
||||
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
|
||||
)
|
182
examples/SDL3-simple-demo/main.c
Normal file
@ -0,0 +1,182 @@
|
||||
#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_Surface *sample_image;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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_Load("resources/sample.png");
|
||||
|
||||
/* 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_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 = ClayVideoDemo_CreateLayout(&state->demoData);
|
||||
|
||||
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 (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();
|
||||
}
|
BIN
examples/SDL3-simple-demo/resources/Roboto-Regular.ttf
Normal file
BIN
examples/SDL3-simple-demo/resources/sample.png
Normal file
After Width: | Height: | Size: 850 B |
22
examples/cairo-pdf-rendering/CMakeLists.txt
Normal 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)
|
171
examples/cairo-pdf-rendering/main.c
Normal 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({
|
||||
.layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) },
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM },
|
||||
.backgroundColor = BACKGROUND
|
||||
}) {
|
||||
CLAY({ .id = 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({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }, .childGap = 10 }}) {
|
||||
CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIT(0) }}, .backgroundColor = ACCENT, .cornerRadius = CLAY_CORNER_RADIUS(12) }) {
|
||||
CLAY({ .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({
|
||||
.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({ .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({ .layout = { .sizing = { CLAY_SIZING_FIXED(32), CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 32, 32 }, .imageData = "resources/check.png" }});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(16) } }});
|
||||
|
||||
CLAY({ .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({ .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, (uintptr_t)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;
|
||||
}
|
||||
|
BIN
examples/cairo-pdf-rendering/resources/check.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
@ -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)
|
@ -1,5 +1,7 @@
|
||||
mkdir -p build/clay \
|
||||
&& clang \
|
||||
-Wall \
|
||||
-Werror \
|
||||
-Os \
|
||||
-DCLAY_WASM \
|
||||
-mbulk-memory \
|
||||
|
BIN
examples/clay-official-website/build/clay/images/check_1.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_2.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_3.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_4.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/check_5.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/clay-official-website/build/clay/images/debugger.png
Normal file
After Width: | Height: | Size: 296 KiB |
BIN
examples/clay-official-website/build/clay/images/declarative.png
Normal file
After Width: | Height: | Size: 193 KiB |
BIN
examples/clay-official-website/build/clay/images/renderer.png
Normal file
After Width: | Height: | Size: 310 KiB |
838
examples/clay-official-website/build/clay/index.html
Normal file
@ -0,0 +1,838 @@
|
||||
<!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: '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: 'sourceDimensions', ...dimensionsDefinition },
|
||||
{ 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 scrollRenderDataDefinition = { 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' },
|
||||
]};
|
||||
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: 'scroll', ...scrollRenderDataDefinition },
|
||||
]},
|
||||
{ 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(arenaAddress, 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_RECTANGLE: {
|
||||
// if (readStructAtAddress(renderCommand.renderData.rectangle.value, rectangleRenderDataDefinition).link.length.value > 0) { TODO reimplement links
|
||||
// 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 customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
|
||||
let configMemory = JSON.stringify(config);
|
||||
let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
|
||||
if (configMemory !== elementData.previousMemoryConfig) {
|
||||
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 = customData.disablePointerEvents.value ? 'none' : 'all';
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
}
|
||||
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
|
||||
element.innerHTML = textDecoder.decode(stringContents);
|
||||
}
|
||||
elementData.previousMemoryText = stringContents;
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_SCISSOR_START): {
|
||||
scissorStack.push({ nextAllocation: { x: renderCommand.boundingBox.x.value, y: renderCommand.boundingBox.y.value }, element, nextElementIndex: 0 });
|
||||
let config = renderCommand.renderData.scroll;
|
||||
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>
|
BIN
examples/clay-official-website/build/clay/index.wasm
Executable file
BIN
examples/clay-official-website/images/debugger.png
Normal file
After Width: | Height: | Size: 296 KiB |
@ -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' },
|
||||
@ -86,9 +100,17 @@
|
||||
{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,39 +118,55 @@
|
||||
{name: 'bottomLeft', type: 'float'},
|
||||
{name: 'bottomRight', type: 'float'},
|
||||
]};
|
||||
let rectangleConfigDefinition = { name: 'rectangle', type: 'struct', members: [
|
||||
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: 'sourceDimensions', ...dimensionsDefinition },
|
||||
{ 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 scrollRenderDataDefinition = { 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' },
|
||||
]};
|
||||
let borderConfigDefinition = { name: 'text', type: 'struct', members: [
|
||||
{ name: 'left', ...borderDefinition },
|
||||
{ name: 'right', ...borderDefinition },
|
||||
{ name: 'top', ...borderDefinition },
|
||||
{ name: 'bottom', ...borderDefinition },
|
||||
{ name: 'betweenChildren', ...borderDefinition },
|
||||
{ name: 'cornerRadius', ...cornerRadiusDefinition }
|
||||
]};
|
||||
let textConfigDefinition = { name: 'text', type: 'struct', members: [
|
||||
{ name: 'textColor', ...colorDefinition },
|
||||
{ name: 'fontId', type: 'uint16_t' },
|
||||
{ name: 'fontSize', type: 'uint16_t' },
|
||||
{ name: 'letterSpacing', type: 'uint16_t' },
|
||||
{ name: 'lineSpacing', type: 'uint16_t' },
|
||||
{ name: 'disablePointerEvents', type: 'uint8_t' }
|
||||
]};
|
||||
let imageConfigDefinition = { name: 'image', type: 'struct', members: [
|
||||
{ name: 'imageData', type: 'uint32_t' },
|
||||
{ name: 'sourceDimensions', type: 'struct', members: [
|
||||
{ name: 'width', type: 'float' },
|
||||
{ name: 'height', type: 'float' },
|
||||
]},
|
||||
{ name: 'sourceURL', ...stringDefinition }
|
||||
]};
|
||||
let customConfigDefinition = { name: 'custom', type: 'struct', members: [
|
||||
{ name: 'customData', type: 'uint32_t' },
|
||||
]}
|
||||
let renderCommandDefinition = {
|
||||
name: 'CLay_RenderCommand',
|
||||
type: 'struct',
|
||||
@ -139,10 +177,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: 'scroll', ...scrollRenderDataDefinition },
|
||||
]},
|
||||
{ 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 +210,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 +240,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 +264,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 +273,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 +289,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,36 +312,75 @@
|
||||
})
|
||||
|
||||
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) => {
|
||||
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);
|
||||
}},
|
||||
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;
|
||||
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(arenaAddress, clayScratchSpaceAddress);
|
||||
renderCommandSize = getStructTotalSize(renderCommandDefinition);
|
||||
renderLoop();
|
||||
}
|
||||
@ -290,30 +394,53 @@
|
||||
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_RECTANGLE: {
|
||||
if (readStructAtAddress(renderCommand.config.value, rectangleConfigDefinition).link.length.value > 0) {
|
||||
elementType = 'a';
|
||||
}
|
||||
// if (readStructAtAddress(renderCommand.renderData.rectangle.value, rectangleRenderDataDefinition).link.length.value > 0) { TODO reimplement links
|
||||
// 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 +450,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 +478,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;
|
||||
}
|
||||
if (linkContents.length > 0) {
|
||||
element.href = linkContents;
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
if (linkContents.length > 0 || config.cursorPointer.value) {
|
||||
element.style.pointerEvents = 'all';
|
||||
element.style.cursor = 'pointer';
|
||||
}
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
let color = config.color;
|
||||
element.style.backgroundColor = `rgba(${color.r.value}, ${color.g.value}, ${color.b.value}, ${color.a.value})`;
|
||||
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 +548,18 @@
|
||||
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 customData = readStructAtAddress(renderCommand.userData.value, customHTMLDataDefinition);
|
||||
let configMemory = JSON.stringify(config);
|
||||
let stringContents = new Uint8Array(memoryDataView.buffer.slice(config.stringContents.chars.value, config.stringContents.chars.value + config.stringContents.length.value));
|
||||
if (configMemory !== elementData.previousMemoryConfig) {
|
||||
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';
|
||||
element.style.pointerEvents = customData.disablePointerEvents.value ? 'none' : 'all';
|
||||
elementData.previousMemoryConfig = configMemory;
|
||||
}
|
||||
if (stringContents.length !== elementData.previousMemoryText.length || MemoryIsDifferent(stringContents, elementData.previousMemoryText, stringContents.length)) {
|
||||
@ -451,7 +570,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.scroll;
|
||||
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 +591,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 +601,9 @@
|
||||
break;
|
||||
}
|
||||
case (CLAY_RENDER_COMMAND_TYPE_CUSTOM): break;
|
||||
default: {
|
||||
console.log("Error: unhandled render command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -490,22 +626,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 +652,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 +752,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 +776,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 +800,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 +826,10 @@
|
||||
}
|
||||
window.previousActiveRendererIndex = activeRendererIndex;
|
||||
requestAnimationFrame(renderLoop);
|
||||
window.mouseDown = false;
|
||||
window.mouseDownThisFrame = false;
|
||||
window.arrowKeyUpPressedThisFrame = false;
|
||||
window.arrowKeyDownPressedThisFrame = false;
|
||||
window.dKeyPressedThisFrame = false;
|
||||
}
|
||||
init();
|
||||
</script>
|
||||
|
@ -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 {
|
||||
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({ .id = CLAY_IDI("HeroBlob", index), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 480) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = {.y = CLAY_ALIGN_Y_CENTER} }, .border = { .color = color, .width = { 2, 2, 2, 2 }}, .cornerRadius = CLAY_CORNER_RADIUS(10) }) {
|
||||
CLAY({ .id = CLAY_IDI("CheckImage", index), .layout = { .sizing = { CLAY_SIZING_FIXED(32) } }, .image = { .sourceDimensions = { 128, 128 }, .imageData = FrameAllocateString(imageURL) } }) {}
|
||||
CLAY_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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("FeatureBlocksOuter"), .layout = { .sizing = { CLAY_SIZING_GROW(0) } } }) {
|
||||
CLAY({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("SyntaxPageRightImage"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("SyntaxPageRightImage"), .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {.x = CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID("SyntaxPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 568) } }, .image = { .sourceDimensions = {1136, 1194}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/declarative.png")) } }) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Clay_Color ColorLerp(Clay_Color a, Clay_Color b, float amount) {
|
||||
@ -169,181 +189,312 @@ 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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("PerformanceRightImageOuter"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = {2, 2, 2, 2}, .color = COLOR_LIGHT } }) {
|
||||
CLAY({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("PerformanceRightImageOuter"), .layout = { .sizing = { CLAY_SIZING_GROW(0) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID(""), .layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_FIXED(400) } }, .border = { .width = { 2, 2, 2, 2 }, .color = COLOR_LIGHT }}) {
|
||||
CLAY({ .id = 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({ .id = 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({
|
||||
.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({
|
||||
.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) }), {});
|
||||
CLAY({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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"));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = 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() }), {});
|
||||
|
||||
if (!mobileScreen) {
|
||||
CLAY_TEXT(CLAY_ID("LinkFeatures"), CLAY_STRING("Features"), &headerTextConfig);
|
||||
CLAY_TEXT(CLAY_ID("LinkDocs"), CLAY_STRING("Docs"), &headerTextConfig);
|
||||
}
|
||||
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}), {
|
||||
if (mobileScreen) {
|
||||
LandingPageMobile();
|
||||
FeatureBlocksMobile();
|
||||
DeclarativeSyntaxPageMobile();
|
||||
HighPerformancePageMobile(lerpValue);
|
||||
RendererPageMobile();
|
||||
} else {
|
||||
LandingPageDesktop();
|
||||
FeatureBlocksDesktop();
|
||||
DeclarativeSyntaxPageDesktop();
|
||||
HighPerformancePageDesktop(lerpValue);
|
||||
RendererPageDesktop();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
return Clay_EndLayout((int)windowWidth, (int)windowHeight);
|
||||
void DebuggerPageDesktop() {
|
||||
CLAY({ .id = 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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("DebuggerRightImageOuter"), .layout = { .sizing = { CLAY_SIZING_PERCENT(0.50) }, .childAlignment = {CLAY_ALIGN_X_CENTER} } }) {
|
||||
CLAY({ .id = CLAY_ID("DebuggerPageRightImageInner"), .layout = { .sizing = { CLAY_SIZING_GROW(.max = 558) } }, .image = { .sourceDimensions = {1620, 1474}, .imageData = FrameAllocateString(CLAY_STRING("/clay/images/debugger.png")) } }) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Clay_Vector2 clickOrigin;
|
||||
Clay_Vector2 positionOrigin;
|
||||
bool mouseDown;
|
||||
} ScrollbarData;
|
||||
|
||||
ScrollbarData scrollbarData = (ScrollbarData) {};
|
||||
float animationLerpValue = -1.0f;
|
||||
|
||||
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) {
|
||||
Clay_RenderCommandArray CreateLayout(bool mobileScreen, float lerpValue) {
|
||||
Clay_BeginLayout();
|
||||
CLAY({ .id = CLAY_ID("OuterContainer"), .layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {
|
||||
CLAY({ .id = 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({ .id = CLAY_ID("Spacer"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) } } }) {}
|
||||
if (!mobileScreen) {
|
||||
CLAY({ .id = CLAY_ID("LinkExamplesOuter"), .layout = { .padding = {8, 8} }, .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/tree/main/examples") }) }) {
|
||||
CLAY_TEXT(CLAY_STRING("Examples"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
CLAY({ .id = CLAY_ID("LinkDocsOuter"), .layout = { .padding = {8, 8} }, .userData = FrameAllocateCustomData((CustomHTMLData) { .link = CLAY_STRING("https://github.com/nicbarker/clay/blob/main/README.md") }) }) {
|
||||
CLAY_TEXT(CLAY_STRING("Docs"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
}
|
||||
CLAY({
|
||||
.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/tree/main/examples") }),
|
||||
}) {
|
||||
CLAY_TEXT(CLAY_STRING("Discord"), CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {61, 26, 5, 255} }));
|
||||
}
|
||||
CLAY({
|
||||
.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({ .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({ .id = CLAY_ID("TopBorder1"), .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_5 }) {}
|
||||
CLAY({ .id = CLAY_ID("TopBorder2"), .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_4 }) {}
|
||||
CLAY({ .id = CLAY_ID("TopBorder3"), .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_3 }) {}
|
||||
CLAY({ .id = CLAY_ID("TopBorder4"), .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_2 }) {}
|
||||
CLAY({ .id = CLAY_ID("TopBorder5"), .layout = topBorderConfig, .backgroundColor = COLOR_TOP_BORDER_1 }) {}
|
||||
CLAY({ .id = CLAY_ID("OuterScrollContainer"),
|
||||
.layout = { .sizing = { CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0) }, .layoutDirection = CLAY_TOP_TO_BOTTOM },
|
||||
.scroll = { .vertical = true },
|
||||
.border = { .width = { .betweenChildren = 2 }, .color = COLOR_RED }
|
||||
}) {
|
||||
if (mobileScreen) {
|
||||
LandingPageMobile();
|
||||
FeatureBlocksMobile();
|
||||
DeclarativeSyntaxPageMobile();
|
||||
HighPerformancePageMobile(lerpValue);
|
||||
RendererPageMobile();
|
||||
} else {
|
||||
LandingPageDesktop();
|
||||
FeatureBlocksDesktop();
|
||||
DeclarativeSyntaxPageDesktop();
|
||||
HighPerformancePageDesktop(lerpValue);
|
||||
RendererPageDesktop();
|
||||
DebuggerPageDesktop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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({
|
||||
.id = 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
16
examples/cpp-project-example/CMakeLists.txt
Normal 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()
|
21
examples/cpp-project-example/main.cpp
Normal 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({ .layout = layoutElement, .backgroundColor = {255,255,255,0} }) {
|
||||
CLAY_TEXT(CLAY_STRING(""), CLAY_TEXT_CONFIG({ .fontId = 0 }));
|
||||
}
|
||||
Clay_EndLayout();
|
||||
return 0;
|
||||
}
|
39
examples/introducing-clay-video-demo/CMakeLists.txt
Normal 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 "master"
|
||||
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)
|
55
examples/introducing-clay-video-demo/main.c
Normal 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();
|
||||
}
|
35
examples/raylib-multi-context/CMakeLists.txt
Normal 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 "master"
|
||||
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)
|
77
examples/raylib-multi-context/main.c
Normal 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();
|
||||
}
|
BIN
examples/raylib-multi-context/resources/Roboto-Regular.ttf
Normal file
BIN
examples/raylib-multi-context/resources/RobotoMono-Medium.ttf
Normal file
BIN
examples/raylib-multi-context/resources/profile-picture.png
Normal file
After Width: | Height: | Size: 101 KiB |
@ -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)
|
||||
@ -12,22 +13,26 @@ FetchContent_Declare(
|
||||
GIT_REPOSITORY "https://github.com/raysan5/raylib.git"
|
||||
GIT_TAG "master"
|
||||
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
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources)
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources)
|
||||
|
@ -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({ .id = CLAY_ID("SatisfyCompiler") }) {
|
||||
CLAY_TEXT(CLAY_STRING("Test"), CLAY_TEXT_CONFIG({ .fontId = 0, .fontSize = 24 }));
|
||||
}
|
||||
}
|
266
examples/shared-layouts/clay-video-demo.c
Normal file
@ -0,0 +1,266 @@
|
||||
#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({
|
||||
.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({.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({ .id = 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({ .id = 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({ .id = 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({ .id = CLAY_ID("FileMenu"),
|
||||
.floating = {
|
||||
.attachTo = CLAY_ATTACH_TO_PARENT,
|
||||
.attachPoints = {
|
||||
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
|
||||
},
|
||||
},
|
||||
.layout = {
|
||||
.padding = {0, 0, 8, 8 }
|
||||
}
|
||||
}) {
|
||||
CLAY({
|
||||
.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({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {}
|
||||
RenderHeaderButton(CLAY_STRING("Upload"));
|
||||
RenderHeaderButton(CLAY_STRING("Media"));
|
||||
RenderHeaderButton(CLAY_STRING("Support"));
|
||||
}
|
||||
|
||||
CLAY({
|
||||
.id = CLAY_ID("LowerContent"),
|
||||
.layout = { .sizing = layoutExpand, .childGap = 16 }
|
||||
}) {
|
||||
CLAY({
|
||||
.id = 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({
|
||||
.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({ .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({ .id = CLAY_ID("MainContent"),
|
||||
.backgroundColor = contentBackgroundColor,
|
||||
.scroll = { .vertical = true },
|
||||
.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;
|
||||
}
|
10
examples/sokol-corner-radius/CMakeLists.txt
Normal 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)
|
110
examples/sokol-corner-radius/main.c
Normal 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({ .id = 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({ .id = 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({ .id = 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,
|
||||
};
|
||||
}
|
71
examples/sokol-video-demo/CMakeLists.txt
Normal 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 "da9de496f938b7575eff7f01ab774d77469bd390"
|
||||
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)
|
77
examples/sokol-video-demo/main.c
Normal 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,
|
||||
};
|
||||
}
|
BIN
examples/sokol-video-demo/resources/Roboto-Regular.ttf
Normal file
24
examples/sokol-video-demo/sokol.c
Normal 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"
|
15
examples/win32_gdi/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.27)
|
||||
project(win32_gdi C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
|
||||
add_executable(win32_gdi WIN32 main.c)
|
||||
|
||||
target_compile_options(win32_gdi PUBLIC)
|
||||
target_include_directories(win32_gdi PUBLIC .)
|
||||
|
||||
add_custom_command(
|
||||
TARGET win32_gdi POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||
${CMAKE_CURRENT_BINARY_DIR}/resources)
|
4
examples/win32_gdi/build.ps1
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
# to build this, install mingw
|
||||
|
||||
gcc main.c -ggdb -omain -lgdi32 -lmingw32 # -mwindows # comment -mwindows out for console output
|
235
examples/win32_gdi/main.c
Normal file
@ -0,0 +1,235 @@
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "../../renderers/win32_gdi/clay_renderer_gdi.c"
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "../../clay.h"
|
||||
|
||||
#include "../shared-layouts/clay-video-demo.c"
|
||||
|
||||
ClayVideoDemo_Data demo_data;
|
||||
|
||||
#define APPNAME "Clay GDI Example"
|
||||
char szAppName[] = APPNAME; // The name of this application
|
||||
char szTitle[] = APPNAME; // The title bar text
|
||||
|
||||
void CenterWindow(HWND hWnd);
|
||||
|
||||
long lastMsgTime = 0;
|
||||
bool ui_debug_mode;
|
||||
HFONT fonts[1];
|
||||
|
||||
#ifndef RECTWIDTH
|
||||
#define RECTWIDTH(rc) ((rc).right - (rc).left)
|
||||
#endif
|
||||
#ifndef RECTHEIGHT
|
||||
#define RECTHEIGHT(rc) ((rc).bottom - (rc).top)
|
||||
#endif
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
|
||||
switch (message)
|
||||
{
|
||||
|
||||
// ----------------------- first and last
|
||||
case WM_CREATE:
|
||||
CenterWindow(hwnd);
|
||||
break;
|
||||
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
|
||||
case WM_MOUSEWHEEL: // scrolling data
|
||||
{
|
||||
short zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
|
||||
// todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed
|
||||
long now = GetMessageTime();
|
||||
float dt = (now - lastMsgTime) / 1000.00;
|
||||
|
||||
lastMsgTime = now;
|
||||
|
||||
// little hacky hack to make scrolling *feel* right
|
||||
if (abs(zDelta) > 100)
|
||||
{
|
||||
zDelta = zDelta * .012;
|
||||
}
|
||||
|
||||
Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt);
|
||||
|
||||
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
|
||||
break;
|
||||
}
|
||||
case WM_RBUTTONUP:
|
||||
case WM_LBUTTONUP:
|
||||
case WM_LBUTTONDOWN:
|
||||
case WM_RBUTTONDOWN:
|
||||
case WM_MOUSEMOVE: // mouse events
|
||||
{
|
||||
short mouseX = GET_X_LPARAM(lParam);
|
||||
short mouseY = GET_Y_LPARAM(lParam);
|
||||
short mouseButtons = LOWORD(wParam);
|
||||
|
||||
Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01);
|
||||
|
||||
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_SIZE: // resize events
|
||||
{
|
||||
|
||||
RECT r = {0};
|
||||
if (GetClientRect(hwnd, &r))
|
||||
{
|
||||
Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left};
|
||||
Clay_SetLayoutDimensions(dim);
|
||||
}
|
||||
|
||||
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case WM_KEYDOWN:
|
||||
if (VK_ESCAPE == wParam)
|
||||
{
|
||||
DestroyWindow(hwnd);
|
||||
break;
|
||||
}
|
||||
|
||||
if (wParam == VK_F12)
|
||||
{
|
||||
Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode);
|
||||
break;
|
||||
}
|
||||
|
||||
printf("Key Pressed: %d\r\n", wParam);
|
||||
InvalidateRect(hwnd, NULL, false); // force a wm_paint event
|
||||
break;
|
||||
|
||||
// ----------------------- render
|
||||
case WM_PAINT:
|
||||
{
|
||||
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
|
||||
Clay_Win32_Render(hwnd, renderCommands, fonts);
|
||||
break;
|
||||
}
|
||||
|
||||
// ----------------------- let windows do all other stuff
|
||||
default:
|
||||
return DefWindowProc(hwnd, message, wParam, lParam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool didAllocConsole = false;
|
||||
|
||||
void HandleClayErrors(Clay_ErrorData errorData)
|
||||
{
|
||||
if (!didAllocConsole)
|
||||
{
|
||||
didAllocConsole = AllocConsole();
|
||||
}
|
||||
|
||||
printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars);
|
||||
}
|
||||
|
||||
int APIENTRY WinMain(
|
||||
HINSTANCE hInstance,
|
||||
HINSTANCE hPrevInstance,
|
||||
LPSTR lpCmdLine,
|
||||
int nCmdShow)
|
||||
{
|
||||
MSG msg;
|
||||
WNDCLASS wc;
|
||||
HWND hwnd;
|
||||
|
||||
demo_data = ClayVideoDemo_Initialize();
|
||||
|
||||
uint64_t clayRequiredMemory = Clay_MinMemorySize();
|
||||
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
|
||||
Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published
|
||||
|
||||
Clay_Win32_SetRendererFlags(CLAYGDI_RF_ALPHABLEND | CLAYGDI_RF_SMOOTHCORNERS);
|
||||
|
||||
// Initialize clay fonts and text drawing
|
||||
fonts[FONT_ID_BODY_16] = Clay_Win32_SimpleCreateFont("resources/Roboto-Regular.ttf", "Roboto", -11, FW_NORMAL);
|
||||
Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, fonts);
|
||||
|
||||
ZeroMemory(&wc, sizeof wc);
|
||||
wc.hInstance = hInstance;
|
||||
wc.lpszClassName = szAppName;
|
||||
wc.lpfnWndProc = (WNDPROC)WndProc;
|
||||
wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW;
|
||||
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
||||
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
|
||||
if (FALSE == RegisterClass(&wc))
|
||||
return 0;
|
||||
|
||||
// Calculate window rectangle by given client size
|
||||
// TODO: AdjustWindowRectExForDpi for DPI support
|
||||
RECT rcWindow = { .right = 800, .bottom = 600 };
|
||||
AdjustWindowRect(&rcWindow, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
|
||||
hwnd = CreateWindow(
|
||||
szAppName,
|
||||
szTitle,
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
RECTWIDTH(rcWindow), // CW_USEDEFAULT,
|
||||
RECTHEIGHT(rcWindow), // CW_USEDEFAULT,
|
||||
0,
|
||||
0,
|
||||
hInstance,
|
||||
0);
|
||||
|
||||
if (hwnd == NULL)
|
||||
return 0;
|
||||
|
||||
// Main message loop:
|
||||
while (GetMessage(&msg, NULL, 0, 0) > 0)
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
return msg.wParam;
|
||||
}
|
||||
|
||||
void CenterWindow(HWND hwnd_self)
|
||||
{
|
||||
HWND hwnd_parent;
|
||||
RECT rw_self, rc_parent, rw_parent;
|
||||
int xpos, ypos;
|
||||
|
||||
hwnd_parent = GetParent(hwnd_self);
|
||||
if (NULL == hwnd_parent)
|
||||
hwnd_parent = GetDesktopWindow();
|
||||
|
||||
GetWindowRect(hwnd_parent, &rw_parent);
|
||||
GetClientRect(hwnd_parent, &rc_parent);
|
||||
GetWindowRect(hwnd_self, &rw_self);
|
||||
|
||||
xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2;
|
||||
ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2;
|
||||
|
||||
SetWindowPos(
|
||||
hwnd_self, NULL,
|
||||
xpos, ypos, 0, 0,
|
||||
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
//+---------------------------------------------------------------------------
|
BIN
examples/win32_gdi/resources/Roboto-Regular.ttf
Normal file
@ -1,7 +0,0 @@
|
||||
$TYPE$ *$NAME$_Add($NAME$ *array, $TYPE$ item) {
|
||||
if (Clay__Array_IncrementCapacityCheck(array->length, array->capacity)) {
|
||||
array->internalArray[array->length++] = item;
|
||||
return &array->internalArray[array->length - 1];
|
||||
}
|
||||
return $DEFAULT_VALUE$;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
typedef struct
|
||||
{
|
||||
uint32_t capacity;
|
||||
uint32_t length;
|
||||
$TYPE$ *internalArray;
|
||||
} $NAME$;
|
||||
|
||||
$NAME$ $NAME$_Allocate_Arena(uint32_t capacity, Clay_Arena *arena) {
|
||||
return ($NAME$){.capacity = capacity, .length = 0, .internalArray = ($TYPE$ *)Clay__Array_Allocate_Arena(capacity, sizeof($TYPE$), arena)};
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
$TYPE$ *$NAME$_Get($NAME$ *array, int index) {
|
||||
return Clay__Array_RangeCheck(index, array->length) ? &array->internalArray[index] : $DEFAULT_VALUE$;
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
$TYPE$ $NAME$_RemoveSwapback($NAME$ *array, int index) {
|
||||
if (Clay__Array_RangeCheck(index, array->length)) {
|
||||
array->length--;
|
||||
$TYPE$ removed = array->internalArray[index];
|
||||
array->internalArray[index] = array->internalArray[array->length];
|
||||
return removed;
|
||||
}
|
||||
return $DEFAULT_VALUE$;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
void $NAME$_Set($NAME$ *array, int index, $TYPE$ value) {
|
||||
if (index < array->capacity && index >= 0) {
|
||||
array->internalArray[index] = value;
|
||||
array->length = index < array->length ? array->length : index + 1;
|
||||
} else {
|
||||
Clay_StringArray_Add(&Clay_warnings, CLAY_STRING("Attempting to allocate array in arena, but arena is already at capacity and would overflow."));
|
||||
#ifdef CLAY_OVERFLOW_TRAP
|
||||
raise(SIGTRAP);
|
||||
#endif
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
let files = ['../clay.h'];
|
||||
|
||||
let templates = ['./'];
|
||||
function readCTemplatesRecursive(directory) {
|
||||
fs.readdirSync(directory).forEach(template => {
|
||||
const absolute = path.join(directory, template);
|
||||
if (fs.statSync(absolute).isDirectory()) return readCTemplatesRecursive(absolute);
|
||||
else if (template.endsWith('template.c')) {
|
||||
return templates.push(absolute);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
readCTemplatesRecursive(__dirname);
|
||||
|
||||
for (const file of files) {
|
||||
const contents = fs.readFileSync(file, 'utf8');
|
||||
const lines = contents.split('\n');
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
if (line.startsWith('// __GENERATED__ template')) {
|
||||
const [comment, generated, templateOpen, templateNames, ...args] = line.split(" ");
|
||||
let matchingEndingLine = -1;
|
||||
for (let j = i + 1; j < lines.length; j++) {
|
||||
if (lines[j].startsWith('// __GENERATED__ template')) {
|
||||
matchingEndingLine = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (matchingEndingLine !== -1) {
|
||||
i++;
|
||||
lines.splice(i, matchingEndingLine - (i));
|
||||
lines.splice(i, 0, ['#pragma region generated']);
|
||||
i++;
|
||||
for (const templateName of templateNames.split(',')) {
|
||||
var matchingTemplate = templates.find(t => t.endsWith(`${templateName}.template.c`));
|
||||
if (matchingTemplate) {
|
||||
let templateContents = fs.readFileSync(matchingTemplate, 'utf8');
|
||||
for (const arg of args) {
|
||||
[argName, argValue] = arg.split('=');
|
||||
templateContents = templateContents.replaceAll(`\$${argName}\$`, argValue);
|
||||
}
|
||||
let remainingTokens = templateContents.split('$');
|
||||
if (remainingTokens.length > 1) {
|
||||
console.log(`Error at ${file}:${i}: Template is missing parameter ${remainingTokens[1]}`)
|
||||
process.exit();
|
||||
} else {
|
||||
templateContents = templateContents.split('\n');
|
||||
lines.splice(i, 0, ...templateContents);
|
||||
i += templateContents.length;
|
||||
}
|
||||
} else {
|
||||
console.log(`Error at ${file}:${i + 1}: no template with name ${templateName}.template.c was found.`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
lines.splice(i, 0, ['#pragma endregion']);
|
||||
i++;
|
||||
} else {
|
||||
console.log(`Error at ${file}:${i + 1}: template was opened and not closed again.`);
|
||||
process.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(file, lines.join('\n'));
|
||||
}
|
7
renderers/SDL2/README
Normal file
@ -0,0 +1,7 @@
|
||||
Note: on Mac OSX, SDL2 for some reason decides to automatically disable momentum scrolling on macbook trackpads.
|
||||
You can re enable it in objective C using:
|
||||
|
||||
```C
|
||||
[[NSUserDefaults standardUserDefaults] setBool: YES
|
||||
forKey: @"AppleMomentumScrollSupported"];
|
||||
```
|
429
renderers/SDL2/clay_renderer_SDL2.c
Normal file
@ -0,0 +1,429 @@
|
||||
#include "../../clay.h"
|
||||
#include <SDL.h>
|
||||
#include <SDL_ttf.h>
|
||||
#include <SDL_image.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159
|
||||
#endif
|
||||
|
||||
#define CLAY_COLOR_TO_SDL_COLOR_ARGS(color) color.r, color.g, color.b, color.a
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t fontId;
|
||||
TTF_Font *font;
|
||||
} SDL2_Font;
|
||||
|
||||
|
||||
static Clay_Dimensions SDL2_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
|
||||
{
|
||||
SDL2_Font *fonts = (SDL2_Font*)userData;
|
||||
|
||||
TTF_Font *font = fonts[config->fontId].font;
|
||||
char *chars = (char *)calloc(text.length + 1, 1);
|
||||
memcpy(chars, text.chars, text.length);
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
if (TTF_SizeUTF8(font, chars, &width, &height) < 0) {
|
||||
fprintf(stderr, "Error: could not measure text: %s\n", TTF_GetError());
|
||||
exit(1);
|
||||
}
|
||||
free(chars);
|
||||
return (Clay_Dimensions) {
|
||||
.width = (float)width,
|
||||
.height = (float)height,
|
||||
};
|
||||
}
|
||||
|
||||
/* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with
|
||||
* no AA or low resolution might make it appear as jagged curves) */
|
||||
static int NUM_CIRCLE_SEGMENTS = 16;
|
||||
|
||||
//all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles.
|
||||
static void SDL_RenderFillRoundedRect(SDL_Renderer* renderer, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) {
|
||||
const SDL_Color color = (SDL_Color) {
|
||||
.r = (Uint8)_color.r,
|
||||
.g = (Uint8)_color.g,
|
||||
.b = (Uint8)_color.b,
|
||||
.a = (Uint8)_color.a,
|
||||
};
|
||||
|
||||
int indexCount = 0, vertexCount = 0;
|
||||
|
||||
const float maxRadius = SDL_min(rect.w, rect.h) / 2.0f;
|
||||
const float clampedRadius = SDL_min(cornerRadius, maxRadius);
|
||||
|
||||
const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)clampedRadius * 0.5f);
|
||||
|
||||
SDL_Vertex vertices[512];
|
||||
int indices[512];
|
||||
|
||||
//define center rectangle
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL
|
||||
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = 3;
|
||||
|
||||
//define rounded corners as triangle fans
|
||||
const float step = (M_PI / 2) / numCircleSegments;
|
||||
for (int i = 0; i < numCircleSegments; i++) {
|
||||
const float angle1 = (float)i * step;
|
||||
const float angle2 = ((float)i + 1.0f) * step;
|
||||
|
||||
for (int j = 0; j < 4; j++) { // Iterate over four corners
|
||||
float cx, cy, signX, signY;
|
||||
|
||||
switch (j) {
|
||||
case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left
|
||||
case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right
|
||||
case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right
|
||||
case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left
|
||||
default: return;
|
||||
}
|
||||
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} };
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} };
|
||||
|
||||
indices[indexCount++] = j; // Connect to corresponding central rectangle vertex
|
||||
indices[indexCount++] = vertexCount - 2;
|
||||
indices[indexCount++] = vertexCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
//Define edge rectangles
|
||||
// Top edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR
|
||||
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = vertexCount - 2; //TL
|
||||
indices[indexCount++] = vertexCount - 1; //TR
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = vertexCount - 1; //TR
|
||||
// Right edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB
|
||||
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = vertexCount - 2; //RT
|
||||
indices[indexCount++] = vertexCount - 1; //RB
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = vertexCount - 1; //RB
|
||||
// Bottom edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL
|
||||
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = vertexCount - 2; //BR
|
||||
indices[indexCount++] = vertexCount - 1; //BL
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = vertexCount - 1; //BL
|
||||
// Left edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT
|
||||
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = vertexCount - 2; //LB
|
||||
indices[indexCount++] = vertexCount - 1; //LT
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = vertexCount - 1; //LT
|
||||
|
||||
// Render everything
|
||||
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
|
||||
}
|
||||
|
||||
//all rendering is performed by a single SDL call, using twi sets of arcing triangles, inner and outer, that fit together; along with two tringles to fill the end gaps.
|
||||
static void SDL_RenderCornerBorder(SDL_Renderer *renderer, Clay_BoundingBox* boundingBox, Clay_BorderRenderData* config, int cornerIndex, Clay_Color _color){
|
||||
/////////////////////////////////
|
||||
//The arc is constructed of outer triangles and inner triangles (if needed).
|
||||
//First three vertices are first outer triangle's vertices
|
||||
//Each two vertices after that are the inner-middle and second-outer vertex of
|
||||
//each outer triangle after the first, because there first-outer vertex is equal to the
|
||||
//second-outer vertex of the previous triangle. Indices set accordingly.
|
||||
//The final two vertices are the missing vertices for the first and last inner triangles (if needed)
|
||||
//Everything is in clockwise order (CW).
|
||||
/////////////////////////////////
|
||||
const SDL_Color color = (SDL_Color) {
|
||||
.r = (Uint8)_color.r,
|
||||
.g = (Uint8)_color.g,
|
||||
.b = (Uint8)_color.b,
|
||||
.a = (Uint8)_color.a,
|
||||
};
|
||||
|
||||
float centerX, centerY, outerRadius, clampedRadius, startAngle, borderWidth;
|
||||
const float maxRadius = SDL_min(boundingBox->width, boundingBox->height) / 2.0f;
|
||||
|
||||
SDL_Vertex vertices[512];
|
||||
int indices[512];
|
||||
int indexCount = 0, vertexCount = 0;
|
||||
|
||||
switch (cornerIndex) {
|
||||
case(0):
|
||||
startAngle = M_PI;
|
||||
outerRadius = SDL_min(config->cornerRadius.topLeft, maxRadius);
|
||||
centerX = boundingBox->x + outerRadius;
|
||||
centerY = boundingBox->y + outerRadius;
|
||||
borderWidth = config->width.top;
|
||||
break;
|
||||
case(1):
|
||||
startAngle = 3*M_PI/2;
|
||||
outerRadius = SDL_min(config->cornerRadius.topRight, maxRadius);
|
||||
centerX = boundingBox->x + boundingBox->width - outerRadius;
|
||||
centerY = boundingBox->y + outerRadius;
|
||||
borderWidth = config->width.top;
|
||||
break;
|
||||
case(2):
|
||||
startAngle = 0;
|
||||
outerRadius = SDL_min(config->cornerRadius.bottomRight, maxRadius);
|
||||
centerX = boundingBox->x + boundingBox->width - outerRadius;
|
||||
centerY = boundingBox->y + boundingBox->height - outerRadius;
|
||||
borderWidth = config->width.bottom;
|
||||
break;
|
||||
case(3):
|
||||
startAngle = M_PI/2;
|
||||
outerRadius = SDL_min(config->cornerRadius.bottomLeft, maxRadius);
|
||||
centerX = boundingBox->x + outerRadius;
|
||||
centerY = boundingBox->y + boundingBox->height - outerRadius;
|
||||
borderWidth = config->width.bottom;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
const float innerRadius = outerRadius - borderWidth;
|
||||
const int minNumOuterTriangles = NUM_CIRCLE_SEGMENTS;
|
||||
const int numOuterTriangles = SDL_max(minNumOuterTriangles, ceilf(outerRadius * 0.5f));
|
||||
const float angleStep = M_PI / (2.0*(float)numOuterTriangles);
|
||||
|
||||
//outer triangles, in CW order
|
||||
for (int i = 0; i < numOuterTriangles; i++) {
|
||||
float angle1 = startAngle + i*angleStep; //first-outer vertex angle
|
||||
float angle2 = startAngle + ((float)i + 0.5) * angleStep; //inner-middle vertex angle
|
||||
float angle3 = startAngle + (i+1)*angleStep; // second-outer vertex angle
|
||||
|
||||
if( i == 0){ //first outer triangle
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(angle1) * outerRadius, centerY + SDL_sinf(angle1) * outerRadius}, color, {0, 0} }; //vertex index = 0
|
||||
}
|
||||
indices[indexCount++] = vertexCount - 1; //will be second-outer vertex of last outer triangle if not first outer triangle.
|
||||
|
||||
vertices[vertexCount++] = (innerRadius > 0)?
|
||||
(SDL_Vertex){ {centerX + SDL_cosf(angle2) * (innerRadius), centerY + SDL_sinf(angle2) * (innerRadius)}, color, {0, 0}}:
|
||||
(SDL_Vertex){ {centerX, centerY }, color, {0, 0}};
|
||||
indices[indexCount++] = vertexCount - 1;
|
||||
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(angle3) * outerRadius, centerY + SDL_sinf(angle3) * outerRadius}, color, {0, 0} };
|
||||
indices[indexCount++] = vertexCount - 1;
|
||||
}
|
||||
|
||||
if(innerRadius > 0){
|
||||
// inner triangles in CW order (except the first and last)
|
||||
for (int i = 0; i < numOuterTriangles - 1; i++){ //skip the last outer triangle
|
||||
if(i==0){ //first outer triangle -> second inner triangle
|
||||
indices[indexCount++] = 1; //inner-middle vertex of first outer triangle
|
||||
indices[indexCount++] = 2; //second-outer vertex of first outer triangle
|
||||
indices[indexCount++] = 3; //innder-middle vertex of second-outer triangle
|
||||
}else{
|
||||
int baseIndex = 3; //skip first outer triangle
|
||||
indices[indexCount++] = baseIndex + (i-1)*2; // inner-middle vertex of current outer triangle
|
||||
indices[indexCount++] = baseIndex + (i-1)*2 + 1; // second-outer vertex of current outer triangle
|
||||
indices[indexCount++] = baseIndex + (i-1)*2 + 2; // inner-middle vertex of next outer triangle
|
||||
}
|
||||
}
|
||||
|
||||
float endAngle = startAngle + M_PI/2.0;
|
||||
|
||||
//last inner triangle
|
||||
indices[indexCount++] = vertexCount - 2; //inner-middle vertex of last outer triangle
|
||||
indices[indexCount++] = vertexCount - 1; //second-outer vertex of last outer triangle
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(endAngle) * innerRadius, centerY + SDL_sinf(endAngle) * innerRadius}, color, {0, 0} }; //missing vertex
|
||||
indices[indexCount++] = vertexCount - 1;
|
||||
|
||||
// //first inner triangle
|
||||
indices[indexCount++] = 0; //first-outer vertex of first outer triangle
|
||||
indices[indexCount++] = 1; //inner-middle vertex of first outer triangle
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {centerX + SDL_cosf(startAngle) * innerRadius, centerY + SDL_sinf(startAngle) * innerRadius}, color, {0, 0} }; //missing vertex
|
||||
indices[indexCount++] = vertexCount - 1;
|
||||
}
|
||||
|
||||
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
|
||||
}
|
||||
|
||||
SDL_Rect currentClippingRectangle;
|
||||
|
||||
static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray renderCommands, SDL2_Font *fonts)
|
||||
{
|
||||
for (uint32_t i = 0; i < renderCommands.length; i++)
|
||||
{
|
||||
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i);
|
||||
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
|
||||
switch (renderCommand->commandType)
|
||||
{
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle;
|
||||
Clay_Color color = config->backgroundColor;
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||
SDL_FRect rect = (SDL_FRect) {
|
||||
.x = boundingBox.x,
|
||||
.y = boundingBox.y,
|
||||
.w = boundingBox.width,
|
||||
.h = boundingBox.height,
|
||||
};
|
||||
if (config->cornerRadius.topLeft > 0) {
|
||||
SDL_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, color);
|
||||
}
|
||||
else {
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
Clay_TextRenderData *config = &renderCommand->renderData.text;
|
||||
char *cloned = (char *)calloc(config->stringContents.length + 1, 1);
|
||||
memcpy(cloned, config->stringContents.chars, config->stringContents.length);
|
||||
TTF_Font* font = fonts[config->fontId].font;
|
||||
SDL_Surface *surface = TTF_RenderUTF8_Blended(font, cloned, (SDL_Color) {
|
||||
.r = (Uint8)config->textColor.r,
|
||||
.g = (Uint8)config->textColor.g,
|
||||
.b = (Uint8)config->textColor.b,
|
||||
.a = (Uint8)config->textColor.a,
|
||||
});
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
|
||||
SDL_Rect destination = (SDL_Rect){
|
||||
.x = boundingBox.x,
|
||||
.y = boundingBox.y,
|
||||
.w = boundingBox.width,
|
||||
.h = boundingBox.height,
|
||||
};
|
||||
SDL_RenderCopy(renderer, texture, NULL, &destination);
|
||||
|
||||
SDL_DestroyTexture(texture);
|
||||
SDL_FreeSurface(surface);
|
||||
free(cloned);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||
currentClippingRectangle = (SDL_Rect) {
|
||||
.x = boundingBox.x,
|
||||
.y = boundingBox.y,
|
||||
.w = boundingBox.width,
|
||||
.h = boundingBox.height,
|
||||
};
|
||||
SDL_RenderSetClipRect(renderer, ¤tClippingRectangle);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
|
||||
SDL_RenderSetClipRect(renderer, NULL);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
Clay_ImageRenderData *config = &renderCommand->renderData.image;
|
||||
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, config->imageData);
|
||||
|
||||
SDL_Rect destination = (SDL_Rect){
|
||||
.x = boundingBox.x,
|
||||
.y = boundingBox.y,
|
||||
.w = boundingBox.width,
|
||||
.h = boundingBox.height,
|
||||
};
|
||||
|
||||
SDL_RenderCopy(renderer, texture, NULL, &destination);
|
||||
|
||||
SDL_DestroyTexture(texture);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderRenderData *config = &renderCommand->renderData.border;
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
|
||||
if(boundingBox.width > 0 & boundingBox.height > 0){
|
||||
const float maxRadius = SDL_min(boundingBox.width, boundingBox.height) / 2.0f;
|
||||
|
||||
if (config->width.left > 0) {
|
||||
const float clampedRadiusTop = SDL_min((float)config->cornerRadius.topLeft, maxRadius);
|
||||
const float clampedRadiusBottom = SDL_min((float)config->cornerRadius.bottomLeft, maxRadius);
|
||||
SDL_FRect rect = {
|
||||
boundingBox.x,
|
||||
boundingBox.y + clampedRadiusTop,
|
||||
(float)config->width.left,
|
||||
(float)boundingBox.height - clampedRadiusTop - clampedRadiusBottom
|
||||
};
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->width.right > 0) {
|
||||
const float clampedRadiusTop = SDL_min((float)config->cornerRadius.topRight, maxRadius);
|
||||
const float clampedRadiusBottom = SDL_min((float)config->cornerRadius.bottomRight, maxRadius);
|
||||
SDL_FRect rect = {
|
||||
boundingBox.x + boundingBox.width - config->width.right,
|
||||
boundingBox.y + clampedRadiusTop,
|
||||
(float)config->width.right,
|
||||
(float)boundingBox.height - clampedRadiusTop - clampedRadiusBottom
|
||||
};
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->width.top > 0) {
|
||||
const float clampedRadiusLeft = SDL_min((float)config->cornerRadius.topLeft, maxRadius);
|
||||
const float clampedRadiusRight = SDL_min((float)config->cornerRadius.topRight, maxRadius);
|
||||
SDL_FRect rect = {
|
||||
boundingBox.x + clampedRadiusLeft,
|
||||
boundingBox.y,
|
||||
boundingBox.width - clampedRadiusLeft - clampedRadiusRight,
|
||||
(float)config->width.top };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
if (config->width.bottom > 0) {
|
||||
const float clampedRadiusLeft = SDL_min((float)config->cornerRadius.bottomLeft, maxRadius);
|
||||
const float clampedRadiusRight = SDL_min((float)config->cornerRadius.bottomRight, maxRadius);
|
||||
SDL_FRect rect = {
|
||||
boundingBox.x + clampedRadiusLeft,
|
||||
boundingBox.y + boundingBox.height - config->width.bottom,
|
||||
boundingBox.width - clampedRadiusLeft - clampedRadiusRight,
|
||||
(float)config->width.bottom
|
||||
};
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
|
||||
//corner index: 0->3 topLeft -> CW -> bottonLeft
|
||||
if (config->width.top > 0 & config->cornerRadius.topLeft > 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 0, config->color);
|
||||
}
|
||||
|
||||
if (config->width.top > 0 & config->cornerRadius.topRight> 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 1, config->color);
|
||||
}
|
||||
|
||||
if (config->width.bottom > 0 & config->cornerRadius.bottomRight > 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 2, config->color);
|
||||
}
|
||||
|
||||
if (config->width.bottom > 0 & config->cornerRadius.bottomLeft > 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 3, config->color);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
fprintf(stderr, "Error: unhandled render command: %d\n", renderCommand->commandType);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
266
renderers/SDL3/clay_renderer_SDL3.c
Normal file
@ -0,0 +1,266 @@
|
||||
#include "../../clay.h"
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_ttf/SDL_ttf.h>
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
|
||||
typedef struct {
|
||||
SDL_Renderer *renderer;
|
||||
TTF_TextEngine *textEngine;
|
||||
TTF_Font **fonts;
|
||||
} Clay_SDL3RendererData;
|
||||
|
||||
/* Global for convenience. Even in 4K this is enough for smooth curves (low radius or rect size coupled with
|
||||
* no AA or low resolution might make it appear as jagged curves) */
|
||||
static int NUM_CIRCLE_SEGMENTS = 16;
|
||||
|
||||
//all rendering is performed by a single SDL call, avoiding multiple RenderRect + plumbing choice for circles.
|
||||
static void SDL_Clay_RenderFillRoundedRect(Clay_SDL3RendererData *rendererData, const SDL_FRect rect, const float cornerRadius, const Clay_Color _color) {
|
||||
const SDL_FColor color = { _color.r/255, _color.g/255, _color.b/255, _color.a/255 };
|
||||
|
||||
int indexCount = 0, vertexCount = 0;
|
||||
|
||||
const float minRadius = SDL_min(rect.w, rect.h) / 2.0f;
|
||||
const float clampedRadius = SDL_min(cornerRadius, minRadius);
|
||||
|
||||
const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int) clampedRadius * 0.5f);
|
||||
|
||||
int totalVertices = 4 + (4 * (numCircleSegments * 2)) + 2*4;
|
||||
int totalIndices = 6 + (4 * (numCircleSegments * 3)) + 6*4;
|
||||
|
||||
SDL_Vertex vertices[totalVertices];
|
||||
int indices[totalIndices];
|
||||
|
||||
//define center rectangle
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + clampedRadius}, color, {0, 0} }; //0 center TL
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + clampedRadius}, color, {1, 0} }; //1 center TR
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //2 center BR
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //3 center BL
|
||||
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = 3;
|
||||
|
||||
//define rounded corners as triangle fans
|
||||
const float step = (SDL_PI_F/2) / numCircleSegments;
|
||||
for (int i = 0; i < numCircleSegments; i++) {
|
||||
const float angle1 = (float)i * step;
|
||||
const float angle2 = ((float)i + 1.0f) * step;
|
||||
|
||||
for (int j = 0; j < 4; j++) { // Iterate over four corners
|
||||
float cx, cy, signX, signY;
|
||||
|
||||
switch (j) {
|
||||
case 0: cx = rect.x + clampedRadius; cy = rect.y + clampedRadius; signX = -1; signY = -1; break; // Top-left
|
||||
case 1: cx = rect.x + rect.w - clampedRadius; cy = rect.y + clampedRadius; signX = 1; signY = -1; break; // Top-right
|
||||
case 2: cx = rect.x + rect.w - clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = 1; signY = 1; break; // Bottom-right
|
||||
case 3: cx = rect.x + clampedRadius; cy = rect.y + rect.h - clampedRadius; signX = -1; signY = 1; break; // Bottom-left
|
||||
default: return;
|
||||
}
|
||||
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle1) * clampedRadius * signX, cy + SDL_sinf(angle1) * clampedRadius * signY}, color, {0, 0} };
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {cx + SDL_cosf(angle2) * clampedRadius * signX, cy + SDL_sinf(angle2) * clampedRadius * signY}, color, {0, 0} };
|
||||
|
||||
indices[indexCount++] = j; // Connect to corresponding central rectangle vertex
|
||||
indices[indexCount++] = vertexCount - 2;
|
||||
indices[indexCount++] = vertexCount - 1;
|
||||
}
|
||||
}
|
||||
|
||||
//Define edge rectangles
|
||||
// Top edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y}, color, {0, 0} }; //TL
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y}, color, {1, 0} }; //TR
|
||||
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = vertexCount - 2; //TL
|
||||
indices[indexCount++] = vertexCount - 1; //TR
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = vertexCount - 1; //TR
|
||||
// Right edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + clampedRadius}, color, {1, 0} }; //RT
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w, rect.y + rect.h - clampedRadius}, color, {1, 1} }; //RB
|
||||
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = vertexCount - 2; //RT
|
||||
indices[indexCount++] = vertexCount - 1; //RB
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = 1;
|
||||
indices[indexCount++] = vertexCount - 1; //RB
|
||||
// Bottom edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + rect.w - clampedRadius, rect.y + rect.h}, color, {1, 1} }; //BR
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x + clampedRadius, rect.y + rect.h}, color, {0, 1} }; //BL
|
||||
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = vertexCount - 2; //BR
|
||||
indices[indexCount++] = vertexCount - 1; //BL
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = 2;
|
||||
indices[indexCount++] = vertexCount - 1; //BL
|
||||
// Left edge
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + rect.h - clampedRadius}, color, {0, 1} }; //LB
|
||||
vertices[vertexCount++] = (SDL_Vertex){ {rect.x, rect.y + clampedRadius}, color, {0, 0} }; //LT
|
||||
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = vertexCount - 2; //LB
|
||||
indices[indexCount++] = vertexCount - 1; //LT
|
||||
indices[indexCount++] = 0;
|
||||
indices[indexCount++] = 3;
|
||||
indices[indexCount++] = vertexCount - 1; //LT
|
||||
|
||||
// Render everything
|
||||
SDL_RenderGeometry(rendererData->renderer, NULL, vertices, vertexCount, indices, indexCount);
|
||||
}
|
||||
|
||||
static void SDL_Clay_RenderArc(Clay_SDL3RendererData *rendererData, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) {
|
||||
SDL_SetRenderDrawColor(rendererData->renderer, color.r, color.g, color.b, color.a);
|
||||
|
||||
const float radStart = startAngle * (SDL_PI_F / 180.0f);
|
||||
const float radEnd = endAngle * (SDL_PI_F / 180.0f);
|
||||
|
||||
const int numCircleSegments = SDL_max(NUM_CIRCLE_SEGMENTS, (int)(radius * 1.5f)); //increase circle segments for larger circles, 1.5 is arbitrary.
|
||||
|
||||
const float angleStep = (radEnd - radStart) / (float)numCircleSegments;
|
||||
const float thicknessStep = 0.4f; //arbitrary value to avoid overlapping lines. Changing THICKNESS_STEP or numCircleSegments might cause artifacts.
|
||||
|
||||
for (float t = thicknessStep; t < thickness - thicknessStep; t += thicknessStep) {
|
||||
SDL_FPoint points[numCircleSegments + 1];
|
||||
const float clampedRadius = SDL_max(radius - t, 1.0f);
|
||||
|
||||
for (int i = 0; i <= numCircleSegments; i++) {
|
||||
const float angle = radStart + i * angleStep;
|
||||
points[i] = (SDL_FPoint){
|
||||
SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius),
|
||||
SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) };
|
||||
}
|
||||
SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1);
|
||||
}
|
||||
}
|
||||
|
||||
SDL_Rect currentClippingRectangle;
|
||||
|
||||
static void SDL_Clay_RenderClayCommands(Clay_SDL3RendererData *rendererData, Clay_RenderCommandArray *rcommands)
|
||||
{
|
||||
for (size_t i = 0; i < rcommands->length; i++) {
|
||||
Clay_RenderCommand *rcmd = Clay_RenderCommandArray_Get(rcommands, i);
|
||||
const Clay_BoundingBox bounding_box = rcmd->boundingBox;
|
||||
const SDL_FRect rect = { (int)bounding_box.x, (int)bounding_box.y, (int)bounding_box.width, (int)bounding_box.height };
|
||||
|
||||
switch (rcmd->commandType) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
Clay_RectangleRenderData *config = &rcmd->renderData.rectangle;
|
||||
SDL_SetRenderDrawBlendMode(rendererData->renderer, SDL_BLENDMODE_BLEND);
|
||||
SDL_SetRenderDrawColor(rendererData->renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a);
|
||||
if (config->cornerRadius.topLeft > 0) {
|
||||
SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor);
|
||||
} else {
|
||||
SDL_RenderFillRect(rendererData->renderer, &rect);
|
||||
}
|
||||
} break;
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
Clay_TextRenderData *config = &rcmd->renderData.text;
|
||||
TTF_Font *font = rendererData->fonts[config->fontId];
|
||||
TTF_Text *text = TTF_CreateText(rendererData->textEngine, font, config->stringContents.chars, config->stringContents.length);
|
||||
TTF_SetTextColor(text, config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a);
|
||||
TTF_DrawRendererText(text, rect.x, rect.y);
|
||||
TTF_DestroyText(text);
|
||||
} break;
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderRenderData *config = &rcmd->renderData.border;
|
||||
|
||||
const float minRadius = SDL_min(rect.w, rect.h) / 2.0f;
|
||||
const Clay_CornerRadius clampedRadii = {
|
||||
.topLeft = SDL_min(config->cornerRadius.topLeft, minRadius),
|
||||
.topRight = SDL_min(config->cornerRadius.topRight, minRadius),
|
||||
.bottomLeft = SDL_min(config->cornerRadius.bottomLeft, minRadius),
|
||||
.bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius)
|
||||
};
|
||||
//edges
|
||||
SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
|
||||
if (config->width.left > 0) {
|
||||
const float starting_y = rect.y + clampedRadii.topLeft;
|
||||
const float length = rect.h - clampedRadii.topLeft - clampedRadii.bottomLeft;
|
||||
SDL_FRect line = { rect.x, starting_y, config->width.left, length };
|
||||
SDL_RenderFillRect(rendererData->renderer, &line);
|
||||
}
|
||||
if (config->width.right > 0) {
|
||||
const float starting_x = rect.x + rect.w - (float)config->width.right;
|
||||
const float starting_y = rect.y + clampedRadii.topRight;
|
||||
const float length = rect.h - clampedRadii.topRight - clampedRadii.bottomRight;
|
||||
SDL_FRect line = { starting_x, starting_y, config->width.right, length };
|
||||
SDL_RenderFillRect(rendererData->renderer, &line);
|
||||
}
|
||||
if (config->width.top > 0) {
|
||||
const float starting_x = rect.x + clampedRadii.topLeft;
|
||||
const float length = rect.w - clampedRadii.topLeft - clampedRadii.topRight;
|
||||
SDL_FRect line = { starting_x, rect.y, length, config->width.top };
|
||||
SDL_RenderFillRect(rendererData->renderer, &line);
|
||||
}
|
||||
if (config->width.bottom > 0) {
|
||||
const float starting_x = rect.x + clampedRadii.bottomLeft;
|
||||
const float starting_y = rect.y + rect.h - (float)config->width.bottom;
|
||||
const float length = rect.w - clampedRadii.bottomLeft - clampedRadii.bottomRight;
|
||||
SDL_FRect line = { starting_x, starting_y, length, config->width.bottom };
|
||||
SDL_SetRenderDrawColor(rendererData->renderer, config->color.r, config->color.g, config->color.b, config->color.a);
|
||||
SDL_RenderFillRect(rendererData->renderer, &line);
|
||||
}
|
||||
//corners
|
||||
if (config->cornerRadius.topLeft > 0) {
|
||||
const float centerX = rect.x + clampedRadii.topLeft -1;
|
||||
const float centerY = rect.y + clampedRadii.topLeft;
|
||||
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topLeft,
|
||||
180.0f, 270.0f, config->width.top, config->color);
|
||||
}
|
||||
if (config->cornerRadius.topRight > 0) {
|
||||
const float centerX = rect.x + rect.w - clampedRadii.topRight -1;
|
||||
const float centerY = rect.y + clampedRadii.topRight;
|
||||
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.topRight,
|
||||
270.0f, 360.0f, config->width.top, config->color);
|
||||
}
|
||||
if (config->cornerRadius.bottomLeft > 0) {
|
||||
const float centerX = rect.x + clampedRadii.bottomLeft -1;
|
||||
const float centerY = rect.y + rect.h - clampedRadii.bottomLeft -1;
|
||||
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
|
||||
90.0f, 180.0f, config->width.bottom, config->color);
|
||||
}
|
||||
if (config->cornerRadius.bottomRight > 0) {
|
||||
const float centerX = rect.x + rect.w - clampedRadii.bottomRight -1; //TODO: why need to -1 in all calculations???
|
||||
const float centerY = rect.y + rect.h - clampedRadii.bottomRight -1;
|
||||
SDL_Clay_RenderArc(rendererData, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
|
||||
0.0f, 90.0f, config->width.bottom, config->color);
|
||||
}
|
||||
|
||||
} break;
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||
Clay_BoundingBox boundingBox = rcmd->boundingBox;
|
||||
currentClippingRectangle = (SDL_Rect) {
|
||||
.x = boundingBox.x,
|
||||
.y = boundingBox.y,
|
||||
.w = boundingBox.width,
|
||||
.h = boundingBox.height,
|
||||
};
|
||||
SDL_SetRenderClipRect(rendererData->renderer, ¤tClippingRectangle);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
|
||||
SDL_SetRenderClipRect(rendererData->renderer, NULL);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
SDL_Surface *image = (SDL_Surface *)rcmd->renderData.image.imageData;
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(rendererData->renderer, image);
|
||||
const SDL_FRect dest = { rect.x, rect.y, rect.w, rect.h };
|
||||
|
||||
SDL_RenderTexture(rendererData->renderer, texture, NULL, &dest);
|
||||
SDL_DestroyTexture(texture);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
SDL_Log("Unknown render command type: %d", rcmd->commandType);
|
||||
}
|
||||
}
|
||||
}
|
373
renderers/cairo/clay_renderer_cairo.c
Normal file
@ -0,0 +1,373 @@
|
||||
// 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "../../clay.h"
|
||||
|
||||
#include <cairo/cairo.h>
|
||||
|
||||
////////////////////////////////
|
||||
//
|
||||
// Public API
|
||||
//
|
||||
|
||||
// Initialize the internal cairo pointer with the user provided instance.
|
||||
// This is REQUIRED before calling Clay_Cairo_Render.
|
||||
void Clay_Cairo_Initialize(cairo_t *cairo);
|
||||
|
||||
// Render the command queue to the `cairo_t*` instance you called
|
||||
// `Clay_Cairo_Initialize` on.
|
||||
void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts);
|
||||
////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////
|
||||
// Convencience macros
|
||||
//
|
||||
#define CLAY_TO_CAIRO(color) color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0
|
||||
#define DEG2RAD(degrees) (degrees * ( M_PI / 180.0 ) )
|
||||
////////////////////////////////
|
||||
|
||||
|
||||
////////////////////////////////
|
||||
// Implementation
|
||||
//
|
||||
|
||||
// Cairo instance
|
||||
static cairo_t *Clay__Cairo = NULL;
|
||||
|
||||
// Return a null-terminated copy of Clay_String `str`.
|
||||
// Callee is required to free.
|
||||
static inline char *Clay_Cairo__NullTerminate(Clay_String *str) {
|
||||
char *copy = (char*) malloc(str->length + 1);
|
||||
if (!copy) {
|
||||
fprintf(stderr, "Memory allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
memcpy(copy, str->chars, str->length);
|
||||
copy[str->length] = '\0';
|
||||
return copy;
|
||||
}
|
||||
|
||||
// Measure text using cairo's *toy* text API.
|
||||
static inline Clay_Dimensions Clay_Cairo_MeasureText(Clay_StringSlice str, Clay_TextElementConfig *config, uintptr_t userData) {
|
||||
// Edge case: Clay computes the width of a whitespace character
|
||||
// once. Cairo does not factor in whitespaces when computing text
|
||||
// extents, this edge-case serves as a short-circuit to introduce
|
||||
// (somewhat) sensible values into Clay.
|
||||
char** fonts = (char**)userData;
|
||||
if(str.length == 1 && str.chars[0] == ' ') {
|
||||
cairo_text_extents_t te;
|
||||
cairo_text_extents(Clay__Cairo, " ", &te);
|
||||
return (Clay_Dimensions) {
|
||||
// The multiplication here follows no real logic, just
|
||||
// brute-forcing it until the text boundaries look
|
||||
// okay-ish. You should probably rather use a proper text
|
||||
// shaping engine like HarfBuzz or Pango.
|
||||
.width = ((float) te.x_advance) * 1.9f,
|
||||
.height = (float) config->fontSize
|
||||
};
|
||||
}
|
||||
|
||||
// Ensure string is null-terminated for Cairo
|
||||
Clay_String toTerminate = (Clay_String){ str.length, str.chars };
|
||||
char *text = Clay_Cairo__NullTerminate(&toTerminate);
|
||||
char *font_family = fonts[config->fontId];
|
||||
|
||||
// Save and reset the Cairo context to avoid unwanted transformations
|
||||
cairo_save(Clay__Cairo);
|
||||
cairo_identity_matrix(Clay__Cairo);
|
||||
|
||||
// Set font properties
|
||||
cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(Clay__Cairo, config->fontSize);
|
||||
|
||||
// Use glyph extents for better precision
|
||||
cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(Clay__Cairo);
|
||||
if (!scaled_font) {
|
||||
fprintf(stderr, "Failed to get scaled font\n");
|
||||
cairo_restore(Clay__Cairo);
|
||||
free(text);
|
||||
return (Clay_Dimensions){0, 0};
|
||||
}
|
||||
|
||||
cairo_glyph_t *glyphs = NULL;
|
||||
int num_glyphs = 0;
|
||||
cairo_status_t status = cairo_scaled_font_text_to_glyphs(
|
||||
scaled_font, 0, 0, text, -1, &glyphs, &num_glyphs, NULL, NULL, NULL
|
||||
);
|
||||
|
||||
if (status != CAIRO_STATUS_SUCCESS || !glyphs || num_glyphs == 0) {
|
||||
fprintf(stderr, "Failed to generate glyphs: %s\n", cairo_status_to_string(status));
|
||||
cairo_restore(Clay__Cairo);
|
||||
free(text);
|
||||
return (Clay_Dimensions){0, 0};
|
||||
}
|
||||
|
||||
// Measure the glyph extents
|
||||
cairo_text_extents_t glyph_extents;
|
||||
cairo_glyph_extents(Clay__Cairo, glyphs, num_glyphs, &glyph_extents);
|
||||
|
||||
// Clean up glyphs
|
||||
cairo_glyph_free(glyphs);
|
||||
|
||||
// Restore the Cairo context
|
||||
cairo_restore(Clay__Cairo);
|
||||
|
||||
// Free temporary strings
|
||||
free(text);
|
||||
|
||||
// Return dimensions
|
||||
return (Clay_Dimensions){
|
||||
.width = (float) glyph_extents.width,
|
||||
.height = (float) glyph_extents.height
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void Clay_Cairo_Initialize(cairo_t *cairo) {
|
||||
Clay__Cairo = cairo;
|
||||
}
|
||||
|
||||
// Internally used to copy images onto our document/active workspace.
|
||||
void Clay_Cairo__Blit_Surface(cairo_surface_t *src_surface, cairo_surface_t *dest_surface,
|
||||
double x, double y, double scale_x, double scale_y) {
|
||||
// Create a cairo context for the destination surface
|
||||
cairo_t *cr = cairo_create(dest_surface);
|
||||
|
||||
// Save the context's state
|
||||
cairo_save(cr);
|
||||
|
||||
// Apply translation to position the source at (x, y)
|
||||
cairo_translate(cr, x, y);
|
||||
|
||||
// Apply scaling to the context
|
||||
cairo_scale(cr, scale_x, scale_y);
|
||||
|
||||
// Set the source surface at (0, 0) after applying transformations
|
||||
cairo_set_source_surface(cr, src_surface, 0, 0);
|
||||
|
||||
// Paint the scaled source surface onto the destination surface
|
||||
cairo_paint(cr);
|
||||
|
||||
// Restore the context's state to remove transformations
|
||||
cairo_restore(cr);
|
||||
|
||||
// Clean up
|
||||
cairo_destroy(cr);
|
||||
}
|
||||
|
||||
void Clay_Cairo_Render(Clay_RenderCommandArray commands, char** fonts) {
|
||||
cairo_t *cr = Clay__Cairo;
|
||||
for(size_t i = 0; i < commands.length; i++) {
|
||||
Clay_RenderCommand *command = Clay_RenderCommandArray_Get(&commands, i);
|
||||
|
||||
switch(command->commandType) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
Clay_RectangleRenderData *config = &command->renderData.rectangle;
|
||||
Clay_BoundingBox bb = command->boundingBox;
|
||||
|
||||
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->backgroundColor));
|
||||
|
||||
cairo_new_sub_path(cr);
|
||||
cairo_arc(cr, bb.x + config->cornerRadius.topLeft,
|
||||
bb.y + config->cornerRadius.topLeft,
|
||||
config->cornerRadius.topLeft,
|
||||
M_PI, 3 * M_PI / 2); // 180° to 270°
|
||||
cairo_arc(cr, bb.x + bb.width - config->cornerRadius.topRight,
|
||||
bb.y + config->cornerRadius.topRight,
|
||||
config->cornerRadius.topRight,
|
||||
3 * M_PI / 2, 2 * M_PI); // 270° to 360°
|
||||
cairo_arc(cr, bb.x + bb.width - config->cornerRadius.bottomRight,
|
||||
bb.y + bb.height - config->cornerRadius.bottomRight,
|
||||
config->cornerRadius.bottomRight,
|
||||
0, M_PI / 2); // 0° to 90°
|
||||
cairo_arc(cr, bb.x + config->cornerRadius.bottomLeft,
|
||||
bb.y + bb.height - config->cornerRadius.bottomLeft,
|
||||
config->cornerRadius.bottomLeft,
|
||||
M_PI / 2, M_PI); // 90° to 180°
|
||||
cairo_close_path(cr);
|
||||
|
||||
cairo_fill(cr);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
// Cairo expects null terminated strings, we need to clone
|
||||
// to temporarily introduce one.
|
||||
Clay_TextRenderData *config = &command->renderData.text;
|
||||
Clay_String toTerminate = (Clay_String){ config->stringContents.length, config->stringContents.chars };
|
||||
char *text = Clay_Cairo__NullTerminate(&toTerminate);
|
||||
char *font_family = fonts[config->fontId];
|
||||
|
||||
Clay_BoundingBox bb = command->boundingBox;
|
||||
Clay_Color color = config->textColor;
|
||||
|
||||
cairo_select_font_face(Clay__Cairo, font_family, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
|
||||
cairo_set_font_size(cr, config->fontSize);
|
||||
|
||||
cairo_move_to(cr, bb.x, bb.y + bb.height);
|
||||
|
||||
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(color));
|
||||
cairo_show_text(cr, text);
|
||||
cairo_close_path(cr);
|
||||
|
||||
free(text);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderRenderData *config = &command->renderData.border;
|
||||
Clay_BoundingBox bb = command->boundingBox;
|
||||
|
||||
double top_left_radius = config->cornerRadius.topLeft / 2.0;
|
||||
double top_right_radius = config->cornerRadius.topRight / 2.0;
|
||||
double bottom_right_radius = config->cornerRadius.bottomRight / 2.0;
|
||||
double bottom_left_radius = config->cornerRadius.bottomLeft / 2.0;
|
||||
|
||||
// Draw the top border
|
||||
if (config->width.top > 0) {
|
||||
cairo_set_line_width(cr, config->width.top);
|
||||
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color));
|
||||
|
||||
cairo_new_sub_path(cr);
|
||||
|
||||
// Left half-arc for top-left corner
|
||||
cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(225), DEG2RAD(270));
|
||||
|
||||
// Line to right half-arc
|
||||
cairo_line_to(cr, bb.x + bb.width - top_right_radius, bb.y);
|
||||
|
||||
// Right half-arc for top-right corner
|
||||
cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(270), DEG2RAD(305));
|
||||
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
// Draw the right border
|
||||
if (config->width.right > 0) {
|
||||
cairo_set_line_width(cr, config->width.right);
|
||||
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color));
|
||||
|
||||
cairo_new_sub_path(cr);
|
||||
|
||||
// Top half-arc for top-right corner
|
||||
cairo_arc(cr, bb.x + bb.width - top_right_radius, bb.y + top_right_radius, top_right_radius, DEG2RAD(305), DEG2RAD(350));
|
||||
|
||||
// Line to bottom half-arc
|
||||
cairo_line_to(cr, bb.x + bb.width, bb.y + bb.height - bottom_right_radius);
|
||||
|
||||
// Bottom half-arc for bottom-right corner
|
||||
cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(0), DEG2RAD(45));
|
||||
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
// Draw the bottom border
|
||||
if (config->width.bottom > 0) {
|
||||
cairo_set_line_width(cr, config->width.bottom);
|
||||
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color));
|
||||
|
||||
cairo_new_sub_path(cr);
|
||||
|
||||
// Right half-arc for bottom-right corner
|
||||
cairo_arc(cr, bb.x + bb.width - bottom_right_radius, bb.y + bb.height - bottom_right_radius, bottom_right_radius, DEG2RAD(45), DEG2RAD(90));
|
||||
|
||||
// Line to left half-arc
|
||||
cairo_line_to(cr, bb.x + bottom_left_radius, bb.y + bb.height);
|
||||
|
||||
// Left half-arc for bottom-left corner
|
||||
cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(90), DEG2RAD(135));
|
||||
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
|
||||
// Draw the left border
|
||||
if (config->width.left > 0) {
|
||||
cairo_set_line_width(cr, config->width.left);
|
||||
cairo_set_source_rgba(cr, CLAY_TO_CAIRO(config->color));
|
||||
|
||||
cairo_new_sub_path(cr);
|
||||
|
||||
// Bottom half-arc for bottom-left corner
|
||||
cairo_arc(cr, bb.x + bottom_left_radius, bb.y + bb.height - bottom_left_radius, bottom_left_radius, DEG2RAD(135), DEG2RAD(180));
|
||||
|
||||
// Line to top half-arc
|
||||
cairo_line_to(cr, bb.x, bb.y + top_left_radius);
|
||||
|
||||
// Top half-arc for top-left corner
|
||||
cairo_arc(cr, bb.x + top_left_radius, bb.y + top_left_radius, top_left_radius, DEG2RAD(180), DEG2RAD(225));
|
||||
|
||||
cairo_stroke(cr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
Clay_ImageRenderData *config = &command->renderData.image;
|
||||
Clay_BoundingBox bb = command->boundingBox;
|
||||
|
||||
char *path = config->imageData;
|
||||
|
||||
cairo_surface_t *surf = cairo_image_surface_create_from_png(path),
|
||||
*origin = cairo_get_target(cr);
|
||||
|
||||
// Calculate the original image dimensions
|
||||
double image_w = cairo_image_surface_get_width(surf),
|
||||
image_h = cairo_image_surface_get_height(surf);
|
||||
|
||||
// Calculate the scaling factor to fit within the bounding box while preserving aspect ratio
|
||||
double scale_w = bb.width / image_w;
|
||||
double scale_h = bb.height / image_h;
|
||||
double scale = (scale_w < scale_h) ? scale_w : scale_h; // Use the smaller scaling factor
|
||||
|
||||
// Apply the same scale to both dimensions to preserve aspect ratio
|
||||
double scale_x = scale;
|
||||
double scale_y = scale;
|
||||
|
||||
// Calculate the scaled image dimensions
|
||||
double scaled_w = image_w * scale_x;
|
||||
double scaled_h = image_h * scale_y;
|
||||
|
||||
// Adjust the x and y coordinates to center the scaled image within the bounding box
|
||||
double centered_x = bb.x + (bb.width - scaled_w) / 2.0;
|
||||
double centered_y = bb.y + (bb.height - scaled_h) / 2.0;
|
||||
|
||||
// Blit the scaled and centered image
|
||||
Clay_Cairo__Blit_Surface(surf, origin, centered_x, centered_y, scale_x, scale_y);
|
||||
|
||||
// Clean up the source surface
|
||||
cairo_surface_destroy(surf);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
|
||||
// Slot your custom elements in here.
|
||||
}
|
||||
default: {
|
||||
fprintf(stderr, "Unknown command type %d\n", (int) command->commandType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,18 +4,10 @@
|
||||
#include "string.h"
|
||||
#include "stdio.h"
|
||||
#include "stdlib.h"
|
||||
#include "signal.h"
|
||||
|
||||
#define CLAY_RECTANGLE_TO_RAYLIB_RECTANGLE(rectangle) (Rectangle) { .x = rectangle.x, .y = rectangle.y, .width = rectangle.width, .height = rectangle.height }
|
||||
#define CLAY_COLOR_TO_RAYLIB_COLOR(color) (Color) { .r = (unsigned char)roundf(color.r), .g = (unsigned char)roundf(color.g), .b = (unsigned char)roundf(color.b), .a = (unsigned char)roundf(color.a) }
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t fontId;
|
||||
Font font;
|
||||
} Raylib_Font;
|
||||
|
||||
Raylib_Font Raylib_fonts[10];
|
||||
Camera Raylib_camera;
|
||||
|
||||
typedef enum
|
||||
@ -36,7 +28,7 @@ typedef struct
|
||||
CustomLayoutElementType type;
|
||||
union {
|
||||
CustomLayoutElement_3DModel model;
|
||||
};
|
||||
} customData;
|
||||
} CustomLayoutElement;
|
||||
|
||||
// Get a ray trace from the screen position (i.e mouse) within a specific section of the screen
|
||||
@ -88,10 +80,8 @@ Ray GetScreenToWorldPointWithZDistance(Vector2 position, Camera camera, int scre
|
||||
return ray;
|
||||
}
|
||||
|
||||
uint32_t measureCalls = 0;
|
||||
|
||||
static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextElementConfig *config) {
|
||||
measureCalls++;
|
||||
static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) {
|
||||
// Measure string size for Font
|
||||
Clay_Dimensions textSize = { 0 };
|
||||
|
||||
@ -99,62 +89,97 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_String *text, Clay_TextEle
|
||||
float lineTextWidth = 0;
|
||||
|
||||
float textHeight = config->fontSize;
|
||||
Font fontToUse = Raylib_fonts[config->fontId].font;
|
||||
Font* fonts = (Font*)userData;
|
||||
Font fontToUse = fonts[config->fontId];
|
||||
// Font failed to load, likely the fonts are in the wrong place relative to the execution dir.
|
||||
// RayLib ships with a default font, so we can continue with that built in one.
|
||||
if (!fontToUse.glyphs) {
|
||||
fontToUse = GetFontDefault();
|
||||
}
|
||||
|
||||
for (int i = 0; i < text->length; ++i)
|
||||
float scaleFactor = config->fontSize/(float)fontToUse.baseSize;
|
||||
|
||||
for (int i = 0; i < text.length; ++i)
|
||||
{
|
||||
if (text->chars[i] == '\n') {
|
||||
if (text.chars[i] == '\n') {
|
||||
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
|
||||
lineTextWidth = 0;
|
||||
continue;
|
||||
}
|
||||
int index = text->chars[i] - 32;
|
||||
int index = text.chars[i] - 32;
|
||||
if (fontToUse.glyphs[index].advanceX != 0) lineTextWidth += fontToUse.glyphs[index].advanceX;
|
||||
else lineTextWidth += (fontToUse.recs[index].width + fontToUse.glyphs[index].offsetX);
|
||||
}
|
||||
|
||||
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
|
||||
|
||||
textSize.width = maxTextWidth / 2;
|
||||
textSize.width = maxTextWidth * scaleFactor;
|
||||
textSize.height = textHeight;
|
||||
|
||||
return textSize;
|
||||
}
|
||||
|
||||
void Clay_Raylib_Initialize(unsigned int flags) {
|
||||
void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned int flags) {
|
||||
SetConfigFlags(flags);
|
||||
InitWindow(1024, 768, "Clay - Raylib Renderer Example");
|
||||
InitWindow(width, height, title);
|
||||
// EnableEventWaiting();
|
||||
}
|
||||
|
||||
void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
|
||||
// A MALLOC'd buffer, that we keep modifying inorder to save from so many Malloc and Free Calls.
|
||||
// Call Clay_Raylib_Close() to free
|
||||
static char *temp_render_buffer = NULL;
|
||||
static int temp_render_buffer_len = 0;
|
||||
|
||||
// Call after closing the window to clean up the render buffer
|
||||
void Clay_Raylib_Close()
|
||||
{
|
||||
if(temp_render_buffer) free(temp_render_buffer);
|
||||
temp_render_buffer_len = 0;
|
||||
|
||||
CloseWindow();
|
||||
}
|
||||
|
||||
|
||||
void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
|
||||
{
|
||||
measureCalls = 0;
|
||||
for (int j = 0; j < renderCommands.length; j++)
|
||||
{
|
||||
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
|
||||
Clay_Rectangle boundingBox = renderCommand->boundingBox;
|
||||
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
|
||||
switch (renderCommand->commandType)
|
||||
{
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
Clay_TextRenderData *textData = &renderCommand->renderData.text;
|
||||
Font fontToUse = fonts[textData->fontId];
|
||||
|
||||
int strlen = textData->stringContents.length + 1;
|
||||
|
||||
if(strlen > temp_render_buffer_len) {
|
||||
// Grow the temp buffer if we need a larger string
|
||||
if(temp_render_buffer) free(temp_render_buffer);
|
||||
temp_render_buffer = (char *) malloc(strlen);
|
||||
temp_render_buffer_len = strlen;
|
||||
}
|
||||
|
||||
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
|
||||
Clay_String text = renderCommand->text;
|
||||
char *cloned = (char *)malloc(text.length + 1);
|
||||
memcpy(cloned, text.chars, text.length);
|
||||
cloned[text.length] = '\0';
|
||||
Font fontToUse = Raylib_fonts[renderCommand->config.textElementConfig->fontId].font;
|
||||
DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)renderCommand->config.textElementConfig->fontSize, (float)renderCommand->config.textElementConfig->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.textElementConfig->textColor));
|
||||
free(cloned);
|
||||
memcpy(temp_render_buffer, textData->stringContents.chars, textData->stringContents.length);
|
||||
temp_render_buffer[textData->stringContents.length] = '\0';
|
||||
DrawTextEx(fontToUse, temp_render_buffer, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
|
||||
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
Texture2D imageTexture = *(Texture2D *)renderCommand->config.imageElementConfig->imageData;
|
||||
Texture2D imageTexture = *(Texture2D *)renderCommand->renderData.image.imageData;
|
||||
Clay_Color tintColor = renderCommand->renderData.image.backgroundColor;
|
||||
if (tintColor.r == 0 && tintColor.g == 0 && tintColor.b == 0 && tintColor.a == 0) {
|
||||
tintColor = (Clay_Color) { 255, 255, 255, 255 };
|
||||
}
|
||||
DrawTextureEx(
|
||||
imageTexture,
|
||||
(Vector2){boundingBox.x, boundingBox.y},
|
||||
0,
|
||||
boundingBox.width / (float)imageTexture.width,
|
||||
WHITE);
|
||||
imageTexture,
|
||||
(Vector2){boundingBox.x, boundingBox.y},
|
||||
0,
|
||||
boundingBox.width / (float)imageTexture.width,
|
||||
CLAY_COLOR_TO_RAYLIB_COLOR(tintColor));
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||
@ -166,51 +191,58 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height), CLAY_COLOR_TO_RAYLIB_COLOR(renderCommand->config.rectangleElementConfig->color));
|
||||
Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle;
|
||||
if (config->cornerRadius.topLeft > 0) {
|
||||
float radius = (config->cornerRadius.topLeft * 2) / (float)((boundingBox.width > boundingBox.height) ? boundingBox.height : boundingBox.width);
|
||||
DrawRectangleRounded((Rectangle) { boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height }, radius, 8, CLAY_COLOR_TO_RAYLIB_COLOR(config->backgroundColor));
|
||||
} else {
|
||||
DrawRectangle(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height, CLAY_COLOR_TO_RAYLIB_COLOR(config->backgroundColor));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderContainerElementConfig *config = renderCommand->config.borderElementConfig;
|
||||
Clay_BorderRenderData *config = &renderCommand->renderData.border;
|
||||
// Left border
|
||||
if (config->left.width > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->left.width, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->left.color));
|
||||
if (config->width.left > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->width.left, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
// Right border
|
||||
if (config->right.width > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->right.width), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->right.width, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->right.color));
|
||||
if (config->width.right > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->width.right), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->width.right, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
// Top border
|
||||
if (config->top.width > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->top.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color));
|
||||
if (config->width.top > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->width.top, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
// Bottom border
|
||||
if (config->bottom.width > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->bottom.width), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->bottom.width, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color));
|
||||
if (config->width.bottom > 0) {
|
||||
DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->width.bottom), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->width.bottom, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
if (config->cornerRadius.topLeft > 0) {
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->top.width), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color));
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->width.top), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
if (config->cornerRadius.topRight > 0) {
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->top.width), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color));
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->width.top), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
if (config->cornerRadius.bottomLeft > 0) {
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->top.width), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color));
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->width.bottom), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
if (config->cornerRadius.bottomRight > 0) {
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->bottom.width), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color));
|
||||
DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->width.bottom), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
|
||||
CustomLayoutElement *customElement = (CustomLayoutElement *)renderCommand->config.customElementConfig->customData;
|
||||
Clay_CustomRenderData *config = &renderCommand->renderData.custom;
|
||||
CustomLayoutElement *customElement = (CustomLayoutElement *)config->customData;
|
||||
if (!customElement) continue;
|
||||
switch (customElement->type) {
|
||||
case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: {
|
||||
Clay_Rectangle rootBox = renderCommands.internalArray[0].boundingBox;
|
||||
Clay_BoundingBox rootBox = renderCommands.internalArray[0].boundingBox;
|
||||
float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f);
|
||||
Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140);
|
||||
BeginMode3D(Raylib_camera);
|
||||
DrawModel(customElement->model.model, positionRay.position, customElement->model.scale * scaleValue, WHITE); // Draw 3d model with texture
|
||||
DrawModel(customElement->customData.model.model, positionRay.position, customElement->customData.model.scale * scaleValue, WHITE); // Draw 3d model with texture
|
||||
EndMode3D();
|
||||
break;
|
||||
}
|
||||
@ -220,9 +252,8 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands)
|
||||
}
|
||||
default: {
|
||||
printf("Error: unhandled render command.");
|
||||
raise(SIGTRAP);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* raylib v5.5-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com)
|
||||
* raylib v5.5 - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com)
|
||||
*
|
||||
* FEATURES:
|
||||
* - NO external dependencies, all required libraries included with raylib
|
||||
* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly,
|
||||
* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5.
|
||||
* - Written in plain C code (C99) in PascalCase/camelCase notation
|
||||
* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3 or ES2 - choose at compile)
|
||||
* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile)
|
||||
* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl]
|
||||
* - Multiple Fonts formats supported (TTF, XNA fonts, AngelCode fonts)
|
||||
* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts)
|
||||
* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC)
|
||||
* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more!
|
||||
* - Flexible Materials system, supporting classic maps and PBR maps
|
||||
* - Animated 3D models supported (skeletal bones animation) (IQM)
|
||||
* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF)
|
||||
* - Shaders support, including Model shaders and Postprocessing shaders
|
||||
* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath]
|
||||
* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, XM, MOD)
|
||||
* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD)
|
||||
* - VR stereo rendering with configurable HMD device parameters
|
||||
* - Bindings to multiple programming languages available!
|
||||
*
|
||||
@ -27,29 +27,35 @@
|
||||
* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2)
|
||||
*
|
||||
* DEPENDENCIES (included):
|
||||
* [rcore] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input (PLATFORM_DESKTOP)
|
||||
* [rlgl] glad (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading (PLATFORM_DESKTOP)
|
||||
* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input
|
||||
* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input
|
||||
* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading
|
||||
* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management
|
||||
*
|
||||
* OPTIONAL DEPENDENCIES (included):
|
||||
* [rcore] msf_gif (Miles Fogle) for GIF recording
|
||||
* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm
|
||||
* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm
|
||||
* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation
|
||||
* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage
|
||||
* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...)
|
||||
* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG)
|
||||
* [rtextures] stb_image_resize (Sean Barret) for image resizing algorithms
|
||||
* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms
|
||||
* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation
|
||||
* [rtext] stb_truetype (Sean Barret) for ttf fonts loading
|
||||
* [rtext] stb_rect_pack (Sean Barret) for rectangles packing
|
||||
* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation
|
||||
* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL)
|
||||
* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF)
|
||||
* [rmodels] Model3D (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d)
|
||||
* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d)
|
||||
* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX)
|
||||
* [raudio] dr_wav (David Reid) for WAV audio file loading
|
||||
* [raudio] dr_flac (David Reid) for FLAC audio file loading
|
||||
* [raudio] dr_mp3 (David Reid) for MP3 audio file loading
|
||||
* [raudio] stb_vorbis (Sean Barret) for OGG audio loading
|
||||
* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading
|
||||
* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading
|
||||
* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage
|
||||
*
|
||||
*
|
||||
* LICENSE: zlib/libpng
|
||||
@ -84,7 +90,7 @@
|
||||
#define RAYLIB_VERSION_MAJOR 5
|
||||
#define RAYLIB_VERSION_MINOR 5
|
||||
#define RAYLIB_VERSION_PATCH 0
|
||||
#define RAYLIB_VERSION "5.5-dev"
|
||||
#define RAYLIB_VERSION "5.5"
|
||||
|
||||
// Function specifiers in case library is build/used as a shared library
|
||||
// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll
|
||||
@ -352,8 +358,10 @@ typedef struct Mesh {
|
||||
// Animation vertex data
|
||||
float *animVertices; // Animated vertex positions (after bones transformations)
|
||||
float *animNormals; // Animated normals (after bones transformations)
|
||||
unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning)
|
||||
float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning)
|
||||
unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6)
|
||||
float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7)
|
||||
Matrix *boneMatrices; // Bones animated transformation matrices
|
||||
int boneCount; // Number of bones
|
||||
|
||||
// OpenGL identifiers
|
||||
unsigned int vaoId; // OpenGL Vertex Array Object id
|
||||
@ -790,7 +798,10 @@ typedef enum {
|
||||
SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap
|
||||
SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance
|
||||
SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter
|
||||
SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf
|
||||
SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf
|
||||
SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds
|
||||
SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights
|
||||
SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices
|
||||
} ShaderLocationIndex;
|
||||
|
||||
#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO
|
||||
@ -872,8 +883,7 @@ typedef enum {
|
||||
CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces
|
||||
CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces
|
||||
CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces
|
||||
CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE, // Layout is defined by a 4x3 cross with cubemap faces
|
||||
CUBEMAP_LAYOUT_PANORAMA // Layout is defined by a panorama image (equirrectangular map)
|
||||
CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces
|
||||
} CubemapLayout;
|
||||
|
||||
// Font type, defines generation method
|
||||
@ -960,36 +970,36 @@ RLAPI void CloseWindow(void); // Close windo
|
||||
RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked)
|
||||
RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully
|
||||
RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen
|
||||
RLAPI bool IsWindowHidden(void); // Check if window is currently hidden (only PLATFORM_DESKTOP)
|
||||
RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized (only PLATFORM_DESKTOP)
|
||||
RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized (only PLATFORM_DESKTOP)
|
||||
RLAPI bool IsWindowFocused(void); // Check if window is currently focused (only PLATFORM_DESKTOP)
|
||||
RLAPI bool IsWindowHidden(void); // Check if window is currently hidden
|
||||
RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized
|
||||
RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized
|
||||
RLAPI bool IsWindowFocused(void); // Check if window is currently focused
|
||||
RLAPI bool IsWindowResized(void); // Check if window has been resized last frame
|
||||
RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled
|
||||
RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags (only PLATFORM_DESKTOP)
|
||||
RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags
|
||||
RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags
|
||||
RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed (only PLATFORM_DESKTOP)
|
||||
RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed (only PLATFORM_DESKTOP)
|
||||
RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable (only PLATFORM_DESKTOP)
|
||||
RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable (only PLATFORM_DESKTOP)
|
||||
RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized (only PLATFORM_DESKTOP)
|
||||
RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit, only PLATFORM_DESKTOP)
|
||||
RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit, only PLATFORM_DESKTOP)
|
||||
RLAPI void SetWindowTitle(const char *title); // Set title for window (only PLATFORM_DESKTOP and PLATFORM_WEB)
|
||||
RLAPI void SetWindowPosition(int x, int y); // Set window position on screen (only PLATFORM_DESKTOP)
|
||||
RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution
|
||||
RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution
|
||||
RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable
|
||||
RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable
|
||||
RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized
|
||||
RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit)
|
||||
RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit)
|
||||
RLAPI void SetWindowTitle(const char *title); // Set title for window
|
||||
RLAPI void SetWindowPosition(int x, int y); // Set window position on screen
|
||||
RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window
|
||||
RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE)
|
||||
RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE)
|
||||
RLAPI void SetWindowSize(int width, int height); // Set window dimensions
|
||||
RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] (only PLATFORM_DESKTOP)
|
||||
RLAPI void SetWindowFocused(void); // Set window focused (only PLATFORM_DESKTOP)
|
||||
RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f]
|
||||
RLAPI void SetWindowFocused(void); // Set window focused
|
||||
RLAPI void *GetWindowHandle(void); // Get native window handle
|
||||
RLAPI int GetScreenWidth(void); // Get current screen width
|
||||
RLAPI int GetScreenHeight(void); // Get current screen height
|
||||
RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI)
|
||||
RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI)
|
||||
RLAPI int GetMonitorCount(void); // Get number of connected monitors
|
||||
RLAPI int GetCurrentMonitor(void); // Get current connected monitor
|
||||
RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed
|
||||
RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position
|
||||
RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor)
|
||||
RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor)
|
||||
@ -1001,6 +1011,7 @@ RLAPI Vector2 GetWindowScaleDPI(void); // Get window
|
||||
RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor
|
||||
RLAPI void SetClipboardText(const char *text); // Set clipboard text content
|
||||
RLAPI const char *GetClipboardText(void); // Get clipboard text content
|
||||
RLAPI Image GetClipboardImage(void); // Get clipboard image content
|
||||
RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling
|
||||
RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling
|
||||
|
||||
@ -1039,7 +1050,7 @@ RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR s
|
||||
// NOTE: Shader functionality is not available on OpenGL 1.1
|
||||
RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations
|
||||
RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations
|
||||
RLAPI bool IsShaderReady(Shader shader); // Check if a shader is ready
|
||||
RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU)
|
||||
RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location
|
||||
RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location
|
||||
RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value
|
||||
@ -1122,11 +1133,12 @@ RLAPI const char *GetDirectoryPath(const char *filePath); // Get full pa
|
||||
RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string)
|
||||
RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string)
|
||||
RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string)
|
||||
RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success
|
||||
RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success
|
||||
RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory
|
||||
RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS
|
||||
RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths
|
||||
RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan
|
||||
RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result
|
||||
RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths
|
||||
RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window
|
||||
RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths
|
||||
@ -1138,6 +1150,10 @@ RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *
|
||||
RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree()
|
||||
RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree()
|
||||
RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree()
|
||||
RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code
|
||||
RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes)
|
||||
RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes)
|
||||
|
||||
|
||||
// Automation events functionality
|
||||
RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS
|
||||
@ -1155,7 +1171,7 @@ RLAPI void PlayAutomationEvent(AutomationEvent event);
|
||||
|
||||
// Input-related functions: keyboard
|
||||
RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once
|
||||
RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again (Only PLATFORM_DESKTOP)
|
||||
RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again
|
||||
RLAPI bool IsKeyDown(int key); // Check if a key is being pressed
|
||||
RLAPI bool IsKeyReleased(int key); // Check if a key has been released once
|
||||
RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed
|
||||
@ -1174,7 +1190,7 @@ RLAPI int GetGamepadButtonPressed(void);
|
||||
RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad
|
||||
RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis
|
||||
RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB)
|
||||
RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor); // Set gamepad vibration for both motors
|
||||
RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds)
|
||||
|
||||
// Input-related functions: mouse
|
||||
RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once
|
||||
@ -1205,7 +1221,7 @@ RLAPI int GetTouchPointCount(void); // Get number of t
|
||||
RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags
|
||||
RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected
|
||||
RLAPI int GetGestureDetected(void); // Get latest detected gesture
|
||||
RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in milliseconds
|
||||
RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds
|
||||
RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector
|
||||
RLAPI float GetGestureDragAngle(void); // Get gesture drag angle
|
||||
RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta
|
||||
@ -1228,8 +1244,8 @@ RLAPI Texture2D GetShapesTexture(void); // Get t
|
||||
RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing
|
||||
|
||||
// Basic shapes drawing functions
|
||||
RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel
|
||||
RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel (Vector version)
|
||||
RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care]
|
||||
RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care]
|
||||
RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line
|
||||
RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines)
|
||||
RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads)
|
||||
@ -1238,7 +1254,7 @@ RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color c
|
||||
RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle
|
||||
RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle
|
||||
RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline
|
||||
RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color color1, Color color2); // Draw a gradient-filled circle
|
||||
RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle
|
||||
RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version)
|
||||
RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline
|
||||
RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version)
|
||||
@ -1250,9 +1266,9 @@ RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color)
|
||||
RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version)
|
||||
RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle
|
||||
RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters
|
||||
RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a vertical-gradient-filled rectangle
|
||||
RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color color1, Color color2);// Draw a horizontal-gradient-filled rectangle
|
||||
RLAPI void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // Draw a gradient-filled rectangle with custom vertex colors
|
||||
RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle
|
||||
RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle
|
||||
RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors
|
||||
RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline
|
||||
RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters
|
||||
RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges
|
||||
@ -1289,13 +1305,13 @@ RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vect
|
||||
RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles
|
||||
RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles
|
||||
RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle
|
||||
RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2]
|
||||
RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle
|
||||
RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle
|
||||
RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle
|
||||
RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold]
|
||||
RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices
|
||||
RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference
|
||||
RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold]
|
||||
RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2]
|
||||
RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision
|
||||
|
||||
//------------------------------------------------------------------------------------
|
||||
@ -1306,13 +1322,12 @@ RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2);
|
||||
// NOTE: These functions do not require GPU access
|
||||
RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM)
|
||||
RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data
|
||||
RLAPI Image LoadImageSvg(const char *fileNameOrString, int width, int height); // Load image from SVG file data or string with specified size
|
||||
RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data)
|
||||
RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer
|
||||
RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png'
|
||||
RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data
|
||||
RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot)
|
||||
RLAPI bool IsImageReady(Image image); // Check if an image is ready
|
||||
RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters)
|
||||
RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM)
|
||||
RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success
|
||||
RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer
|
||||
@ -1398,9 +1413,9 @@ RLAPI Texture2D LoadTexture(const char *fileName);
|
||||
RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data
|
||||
RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported
|
||||
RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer)
|
||||
RLAPI bool IsTextureReady(Texture2D texture); // Check if a texture is ready
|
||||
RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU)
|
||||
RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM)
|
||||
RLAPI bool IsRenderTextureReady(RenderTexture2D target); // Check if a render texture is ready
|
||||
RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU)
|
||||
RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM)
|
||||
RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data
|
||||
RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data
|
||||
@ -1431,6 +1446,7 @@ RLAPI Color ColorBrightness(Color color, float factor); // G
|
||||
RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f
|
||||
RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f
|
||||
RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint
|
||||
RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f]
|
||||
RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value
|
||||
RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format
|
||||
RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer
|
||||
@ -1443,10 +1459,10 @@ RLAPI int GetPixelDataSize(int width, int height, int format); // G
|
||||
// Font loading/unloading functions
|
||||
RLAPI Font GetFontDefault(void); // Get the default Font
|
||||
RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM)
|
||||
RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set
|
||||
RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height
|
||||
RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style)
|
||||
RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf'
|
||||
RLAPI bool IsFontReady(Font font); // Check if a font is ready
|
||||
RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked)
|
||||
RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use
|
||||
RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info
|
||||
RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM)
|
||||
@ -1536,7 +1552,7 @@ RLAPI void DrawGrid(int slices, float spacing);
|
||||
// Model management functions
|
||||
RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials)
|
||||
RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material)
|
||||
RLAPI bool IsModelReady(Model model); // Check if a model is ready
|
||||
RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs)
|
||||
RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM)
|
||||
RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes)
|
||||
|
||||
@ -1545,6 +1561,8 @@ RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint);
|
||||
RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters
|
||||
RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set)
|
||||
RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters
|
||||
RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points
|
||||
RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters
|
||||
RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires)
|
||||
RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture
|
||||
RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source
|
||||
@ -1577,14 +1595,15 @@ RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize);
|
||||
// Material loading/unloading functions
|
||||
RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file
|
||||
RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps)
|
||||
RLAPI bool IsMaterialReady(Material material); // Check if a material is ready
|
||||
RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU)
|
||||
RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM)
|
||||
RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...)
|
||||
RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh
|
||||
|
||||
// Model animations loading/unloading functions
|
||||
RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file
|
||||
RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose
|
||||
RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU)
|
||||
RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning)
|
||||
RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data
|
||||
RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data
|
||||
RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match
|
||||
@ -1614,11 +1633,11 @@ RLAPI float GetMasterVolume(void); // Get mas
|
||||
// Wave/Sound loading/unloading functions
|
||||
RLAPI Wave LoadWave(const char *fileName); // Load wave data from file
|
||||
RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav'
|
||||
RLAPI bool IsWaveReady(Wave wave); // Checks if wave data is ready
|
||||
RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters)
|
||||
RLAPI Sound LoadSound(const char *fileName); // Load sound from file
|
||||
RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data
|
||||
RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data
|
||||
RLAPI bool IsSoundReady(Sound sound); // Checks if a sound is ready
|
||||
RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized)
|
||||
RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data
|
||||
RLAPI void UnloadWave(Wave wave); // Unload wave data
|
||||
RLAPI void UnloadSound(Sound sound); // Unload sound
|
||||
@ -1644,7 +1663,7 @@ RLAPI void UnloadWaveSamples(float *samples); // Unload
|
||||
// Music management functions
|
||||
RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file
|
||||
RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data
|
||||
RLAPI bool IsMusicReady(Music music); // Checks if a music stream is ready
|
||||
RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized)
|
||||
RLAPI void UnloadMusicStream(Music music); // Unload music stream
|
||||
RLAPI void PlayMusicStream(Music music); // Start music playing
|
||||
RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing
|
||||
@ -1661,7 +1680,7 @@ RLAPI float GetMusicTimePlayed(Music music); // Get cur
|
||||
|
||||
// AudioStream management functions
|
||||
RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data)
|
||||
RLAPI bool IsAudioStreamReady(AudioStream stream); // Checks if an audio stream is ready
|
||||
RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized)
|
||||
RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory
|
||||
RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data
|
||||
RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* raymath v1.5 - Math functions to work with Vector2, Vector3, Matrix and Quaternions
|
||||
* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions
|
||||
*
|
||||
* CONVENTIONS:
|
||||
* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all
|
||||
@ -12,7 +12,7 @@
|
||||
* - Functions are always self-contained, no function use another raymath function inside,
|
||||
* required code is directly re-implemented inside
|
||||
* - Functions input parameters are always received by value (2 unavoidable exceptions)
|
||||
* - Functions use always a "result" variable for return
|
||||
* - Functions use always a "result" variable for return (except C++ operators)
|
||||
* - Functions are always defined inline
|
||||
* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience)
|
||||
* - No compound literals used to make sure libray is compatible with C++
|
||||
@ -27,6 +27,8 @@
|
||||
* Define static inline functions code, so #include header suffices for use.
|
||||
* This may use up lots of memory.
|
||||
*
|
||||
* #define RAYMATH_DISABLE_CPP_OPERATORS
|
||||
* Disables C++ operator overloads for raymath types.
|
||||
*
|
||||
* LICENSE: zlib/libpng
|
||||
*
|
||||
@ -2567,7 +2569,13 @@ RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotatio
|
||||
if (!FloatEquals(det, 0))
|
||||
{
|
||||
clone.m0 /= s.x;
|
||||
clone.m4 /= s.x;
|
||||
clone.m8 /= s.x;
|
||||
clone.m1 /= s.y;
|
||||
clone.m5 /= s.y;
|
||||
clone.m9 /= s.y;
|
||||
clone.m2 /= s.z;
|
||||
clone.m6 /= s.z;
|
||||
clone.m10 /= s.z;
|
||||
|
||||
// Extract rotation
|
||||
@ -2580,4 +2588,354 @@ RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotatio
|
||||
}
|
||||
}
|
||||
|
||||
#endif // RAYMATH_H
|
||||
#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS)
|
||||
|
||||
// Optional C++ math operators
|
||||
//-------------------------------------------------------------------------------
|
||||
|
||||
// Vector2 operators
|
||||
static constexpr Vector2 Vector2Zeros = { 0, 0 };
|
||||
static constexpr Vector2 Vector2Ones = { 1, 1 };
|
||||
static constexpr Vector2 Vector2UnitX = { 1, 0 };
|
||||
static constexpr Vector2 Vector2UnitY = { 0, 1 };
|
||||
|
||||
inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
return Vector2Add(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
lhs = Vector2Add(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
return Vector2Subtract(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
lhs = Vector2Subtract(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector2 operator * (const Vector2& lhs, const float& rhs)
|
||||
{
|
||||
return Vector2Scale(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator *= (Vector2& lhs, const float& rhs)
|
||||
{
|
||||
lhs = Vector2Scale(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
return Vector2Multiply(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
lhs = Vector2Multiply(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs)
|
||||
{
|
||||
return Vector2Transform(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator -= (Vector2& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = Vector2Transform(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector2 operator / (const Vector2& lhs, const float& rhs)
|
||||
{
|
||||
return Vector2Scale(lhs, 1.0f / rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator /= (Vector2& lhs, const float& rhs)
|
||||
{
|
||||
lhs = Vector2Scale(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
return Vector2Divide(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
lhs = Vector2Divide(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline bool operator == (const Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y);
|
||||
}
|
||||
|
||||
inline bool operator != (const Vector2& lhs, const Vector2& rhs)
|
||||
{
|
||||
return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y);
|
||||
}
|
||||
|
||||
// Vector3 operators
|
||||
static constexpr Vector3 Vector3Zeros = { 0, 0, 0 };
|
||||
static constexpr Vector3 Vector3Ones = { 1, 1, 1 };
|
||||
static constexpr Vector3 Vector3UnitX = { 1, 0, 0 };
|
||||
static constexpr Vector3 Vector3UnitY = { 0, 1, 0 };
|
||||
static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 };
|
||||
|
||||
inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
return Vector3Add(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
lhs = Vector3Add(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
return Vector3Subtract(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
lhs = Vector3Subtract(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector3 operator * (const Vector3& lhs, const float& rhs)
|
||||
{
|
||||
return Vector3Scale(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator *= (Vector3& lhs, const float& rhs)
|
||||
{
|
||||
lhs = Vector3Scale(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
return Vector3Multiply(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
lhs = Vector3Multiply(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs)
|
||||
{
|
||||
return Vector3Transform(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator -= (Vector3& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = Vector3Transform(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector3 operator / (const Vector3& lhs, const float& rhs)
|
||||
{
|
||||
return Vector3Scale(lhs, 1.0f / rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator /= (Vector3& lhs, const float& rhs)
|
||||
{
|
||||
lhs = Vector3Scale(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
return Vector3Divide(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
lhs = Vector3Divide(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline bool operator == (const Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z);
|
||||
}
|
||||
|
||||
inline bool operator != (const Vector3& lhs, const Vector3& rhs)
|
||||
{
|
||||
return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z);
|
||||
}
|
||||
|
||||
// Vector4 operators
|
||||
static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 };
|
||||
static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 };
|
||||
static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 };
|
||||
static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 };
|
||||
static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 };
|
||||
static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 };
|
||||
|
||||
inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
return Vector4Add(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
lhs = Vector4Add(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
return Vector4Subtract(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
lhs = Vector4Subtract(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector4 operator * (const Vector4& lhs, const float& rhs)
|
||||
{
|
||||
return Vector4Scale(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector4& operator *= (Vector4& lhs, const float& rhs)
|
||||
{
|
||||
lhs = Vector4Scale(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
return Vector4Multiply(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
lhs = Vector4Multiply(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector4 operator / (const Vector4& lhs, const float& rhs)
|
||||
{
|
||||
return Vector4Scale(lhs, 1.0f / rhs);
|
||||
}
|
||||
|
||||
inline const Vector4& operator /= (Vector4& lhs, const float& rhs)
|
||||
{
|
||||
lhs = Vector4Scale(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
return Vector4Divide(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
lhs = Vector4Divide(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline bool operator == (const Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w);
|
||||
}
|
||||
|
||||
inline bool operator != (const Vector4& lhs, const Vector4& rhs)
|
||||
{
|
||||
return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w);
|
||||
}
|
||||
|
||||
// Quaternion operators
|
||||
static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 };
|
||||
static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 };
|
||||
static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 };
|
||||
|
||||
inline Quaternion operator + (const Quaternion& lhs, const float& rhs)
|
||||
{
|
||||
return QuaternionAddValue(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Quaternion& operator += (Quaternion& lhs, const float& rhs)
|
||||
{
|
||||
lhs = QuaternionAddValue(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Quaternion operator - (const Quaternion& lhs, const float& rhs)
|
||||
{
|
||||
return QuaternionSubtractValue(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs)
|
||||
{
|
||||
lhs = QuaternionSubtractValue(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs)
|
||||
{
|
||||
return QuaternionTransform(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = QuaternionTransform(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
// Matrix operators
|
||||
inline Matrix operator + (const Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
return MatrixAdd(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = MatrixAdd(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Matrix operator - (const Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
return MatrixSubtract(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = MatrixSubtract(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
|
||||
inline Matrix operator * (const Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
return MatrixMultiply(lhs, rhs);
|
||||
}
|
||||
|
||||
inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs)
|
||||
{
|
||||
lhs = MatrixMultiply(lhs, rhs);
|
||||
return lhs;
|
||||
}
|
||||
//-------------------------------------------------------------------------------
|
||||
#endif // C++ operators
|
||||
|
||||
#endif // RAYMATH_H
|
||||
|
450
renderers/sokol/sokol_clay.h
Normal file
@ -0,0 +1,450 @@
|
||||
#ifndef SOKOL_CLAY_INCLUDED
|
||||
#define SOKOL_CLAY_INCLUDED (1)
|
||||
/*
|
||||
sokol_clay.h -- drop-in Clay renderer for sokol_gfx.h
|
||||
|
||||
Do this:
|
||||
#define SOKOL_CLAY_IMPL
|
||||
|
||||
before you include this file in *one* C file to create the
|
||||
implementation.
|
||||
|
||||
Optionally provide the following configuration define both before including the
|
||||
the declaration and implementation:
|
||||
|
||||
SOKOL_CLAY_NO_SOKOL_APP - don't depend on sokol_app.h (see below for details)
|
||||
|
||||
Include the following headers before sokol_clay.h (both before including
|
||||
the declaration and implementation):
|
||||
|
||||
sokol_gl.h
|
||||
sokol_fontstash.h
|
||||
sokol_app.h (except SOKOL_CLAY_NO_SOKOL_APP)
|
||||
clay.h
|
||||
|
||||
FEATURE OVERVIEW:
|
||||
=================
|
||||
sokol_clay.h implements the rendering and event-handling code for Clay
|
||||
(https://github.com/nicbarker/clay) on top of sokol_gl.h and (optionally)
|
||||
sokol_app.h.
|
||||
|
||||
Since sokol_fontstash.h already depends on sokol_gl.h, the rendering is
|
||||
implemented using sokol_gl calls. (TODO: make fontstash optional?)
|
||||
|
||||
The sokol_app.h dependency is optional and used for input event handling.
|
||||
If you only use sokol_gfx.h but not sokol_app.h in your application,
|
||||
define SOKOL_CLAY_NO_SOKOL_APP before including the implementation
|
||||
of sokol_clay.h, this will remove any dependency to sokol_app.h, but
|
||||
you must call sclay_set_layout_dimensions and handle input yourself.
|
||||
|
||||
sokol_clay.h is not thread-safe, all calls must be made from the
|
||||
same thread where sokol_gfx.h is running.
|
||||
|
||||
HOWTO:
|
||||
======
|
||||
|
||||
--- To initialize sokol-clay, call sclay_setup(). This can be done
|
||||
before or after Clay_Initialize.
|
||||
|
||||
--- Create an array of sclay_font_t and fill it by calling one of:
|
||||
|
||||
sclay_font_t sclay_add_font(const char *filename);
|
||||
sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen);
|
||||
|
||||
The fontId value in Clay corresponds to indices in this array. After calling
|
||||
Clay_Initialize but before calling any layout code, do this:
|
||||
|
||||
Clay_SetMeasureTextFunction(sclay_measure_text, &fonts);
|
||||
|
||||
where `fonts` is the abovementioned array.
|
||||
|
||||
--- At the start of a frame, call sclay_new_frame() if you're using sokol_app.h.
|
||||
If you're not using sokol_app.h, call:
|
||||
|
||||
void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale);
|
||||
|
||||
at the start of the frame (or just when the window is resized.)
|
||||
|
||||
Either way, do some layout, then at the end of the frame call sclay_render:
|
||||
|
||||
sg_begin_pass(...)
|
||||
// other rendering...
|
||||
sclay_render(renderCommands, &fonts);
|
||||
// other rendering...
|
||||
sgl_draw();
|
||||
sg_end_pass();
|
||||
sg_commit();
|
||||
|
||||
One caveat: sclay_render assumes the default gl view matrix, and handles scaling
|
||||
automatically. If you've adjusted the view matrix, remember to first call:
|
||||
|
||||
sgl_matrix_mode_modelview();
|
||||
sgl_load_identity();
|
||||
|
||||
before calling sclay_render.
|
||||
|
||||
--- if you're using sokol_app.h, from inside the sokol_app.h event callback,
|
||||
call:
|
||||
|
||||
void sclay_handle_event(const sapp_event* ev);
|
||||
|
||||
Unfortunately Clay does not currently provide feedback on whether a mouse
|
||||
click was handled or not.
|
||||
|
||||
--- finally, on application shutdown, call
|
||||
|
||||
sclay_shutdown()
|
||||
*/
|
||||
#if !defined(SOKOL_CLAY_NO_SOKOL_APP) && !defined(SOKOL_APP_INCLUDED)
|
||||
#error "Please include sokol_app.h before sokol_clay.h (or define SOKOL_CLAY_NO_SOKOL_APP)"
|
||||
#endif
|
||||
|
||||
typedef int sclay_font_t;
|
||||
|
||||
void sclay_setup();
|
||||
void sclay_shutdown();
|
||||
|
||||
sclay_font_t sclay_add_font(const char *filename);
|
||||
sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen);
|
||||
Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData);
|
||||
|
||||
#ifndef SOKOL_CLAY_NO_SOKOL_APP
|
||||
void sclay_new_frame();
|
||||
void sclay_handle_event(const sapp_event *ev);
|
||||
#endif /* SOKOL_CLAY_NO_SOKOL_APP */
|
||||
|
||||
/* Use this if you don't call sclay_new_frame. `size` is the "virtual" size which
|
||||
* your layout is relative to (ie. the actual framebuffer size divided by dpi_scale.)
|
||||
* Set dpi_scale to 1 if you're not using high-dpi support. */
|
||||
void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale);
|
||||
|
||||
void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts);
|
||||
|
||||
#endif /* SOKOL_CLAY_INCLUDED */
|
||||
|
||||
#ifdef SOKOL_CLAY_IMPL
|
||||
#define SOKOL_CLAY_IMPL_INCLUDED (1)
|
||||
#ifndef SOKOL_GL_INCLUDED
|
||||
#error "Please include sokol_gl.h before sokol_clay.h"
|
||||
#endif
|
||||
#ifndef SOKOL_FONTSTASH_INCLUDED
|
||||
#error "Please include sokol_fontstash.h before sokol_clay.h"
|
||||
#endif
|
||||
#ifndef CLAY_HEADER
|
||||
#error "Please include clay.h before sokol_clay.h"
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
sgl_pipeline pip;
|
||||
#ifndef SOKOL_CLAY_NO_SOKOL_APP
|
||||
Clay_Vector2 mouse_pos, scroll;
|
||||
bool mouse_down;
|
||||
#endif
|
||||
Clay_Dimensions size;
|
||||
float dpi_scale;
|
||||
FONScontext *fonts;
|
||||
} _sclay_state_t;
|
||||
static _sclay_state_t _sclay;
|
||||
|
||||
void sclay_setup() {
|
||||
_sclay.pip = sgl_make_pipeline(&(sg_pipeline_desc){
|
||||
.colors[0] = {
|
||||
.blend = {
|
||||
.enabled = true,
|
||||
.src_factor_rgb = SG_BLENDFACTOR_SRC_ALPHA,
|
||||
.dst_factor_rgb = SG_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
|
||||
},
|
||||
}
|
||||
});
|
||||
#ifndef SOKOL_CLAY_NO_SOKOL_APP
|
||||
_sclay.mouse_pos = (Clay_Vector2){0, 0};
|
||||
_sclay.scroll = (Clay_Vector2){0, 0};
|
||||
_sclay.mouse_down = false;
|
||||
#endif
|
||||
_sclay.size = (Clay_Dimensions){1, 1};
|
||||
_sclay.dpi_scale = 1;
|
||||
_sclay.fonts = sfons_create(&(sfons_desc_t){ 0 });
|
||||
//TODO clay error handler?
|
||||
}
|
||||
|
||||
void sclay_shutdown() {
|
||||
sgl_destroy_pipeline(_sclay.pip);
|
||||
sfons_destroy(_sclay.fonts);
|
||||
}
|
||||
|
||||
#ifndef SOKOL_CLAY_NO_SOKOL_APP
|
||||
void sclay_handle_event(const sapp_event* ev) {
|
||||
switch(ev->type){
|
||||
case SAPP_EVENTTYPE_MOUSE_MOVE:
|
||||
_sclay.mouse_pos.x = ev->mouse_x / _sclay.dpi_scale;
|
||||
_sclay.mouse_pos.y = ev->mouse_y / _sclay.dpi_scale;
|
||||
break;
|
||||
case SAPP_EVENTTYPE_MOUSE_DOWN:
|
||||
_sclay.mouse_down = true;
|
||||
break;
|
||||
case SAPP_EVENTTYPE_MOUSE_UP:
|
||||
_sclay.mouse_down = false;
|
||||
break;
|
||||
case SAPP_EVENTTYPE_MOUSE_SCROLL:
|
||||
_sclay.scroll.x += ev->scroll_x;
|
||||
_sclay.scroll.y += ev->scroll_y;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void sclay_new_frame() {
|
||||
sclay_set_layout_dimensions((Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() },
|
||||
sapp_dpi_scale());
|
||||
Clay_SetPointerState(_sclay.mouse_pos, _sclay.mouse_down);
|
||||
Clay_UpdateScrollContainers(true, _sclay.scroll, sapp_frame_duration());
|
||||
_sclay.scroll = (Clay_Vector2){0, 0};
|
||||
}
|
||||
#endif /* SOKOL_CLAY_NO_SOKOL_APP */
|
||||
|
||||
void sclay_set_layout_dimensions(Clay_Dimensions size, float dpi_scale) {
|
||||
size.width /= dpi_scale;
|
||||
size.height /= dpi_scale;
|
||||
_sclay.size = size;
|
||||
if(_sclay.dpi_scale != dpi_scale){
|
||||
_sclay.dpi_scale = dpi_scale;
|
||||
Clay_ResetMeasureTextCache();
|
||||
}
|
||||
Clay_SetLayoutDimensions(size);
|
||||
}
|
||||
|
||||
sclay_font_t sclay_add_font(const char *filename) {
|
||||
//TODO log something if we get FONS_INVALID
|
||||
return fonsAddFont(_sclay.fonts, "", filename);
|
||||
}
|
||||
|
||||
sclay_font_t sclay_add_font_mem(unsigned char *data, int dataLen) {
|
||||
//TODO log something if we get FONS_INVALID
|
||||
return fonsAddFontMem(_sclay.fonts, "", data, dataLen, false);
|
||||
}
|
||||
|
||||
Clay_Dimensions sclay_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) {
|
||||
sclay_font_t *fonts = (sclay_font_t *)userData;
|
||||
if(!fonts) return (Clay_Dimensions){ 0 };
|
||||
fonsSetFont(_sclay.fonts, fonts[config->fontId]);
|
||||
fonsSetSize(_sclay.fonts, config->fontSize);
|
||||
fonsSetSpacing(_sclay.fonts, config->letterSpacing);
|
||||
float ascent, descent, lineh;
|
||||
fonsVertMetrics(_sclay.fonts, &ascent, &descent, &lineh);
|
||||
return (Clay_Dimensions) {
|
||||
.width = fonsTextBounds(_sclay.fonts, 0, 0, text.chars, text.chars + text.length, NULL),
|
||||
.height = ascent - descent
|
||||
};
|
||||
}
|
||||
|
||||
static void _draw_rect(float x, float y, float w, float h){
|
||||
sgl_v2f(x, y);
|
||||
sgl_v2f(x, y);
|
||||
sgl_v2f(x+w, y);
|
||||
sgl_v2f(x, y+h);
|
||||
sgl_v2f(x+w, y+h);
|
||||
sgl_v2f(x+w, y+h);
|
||||
}
|
||||
|
||||
static float _SIN[16] = {
|
||||
0.000000f, 0.104528f, 0.207912f, 0.309017f,
|
||||
0.406737f, 0.500000f, 0.587785f, 0.669131f,
|
||||
0.743145f, 0.809017f, 0.866025f, 0.913545f,
|
||||
0.951057f, 0.978148f, 0.994522f, 1.000000f,
|
||||
};
|
||||
|
||||
/* rx,ry = radius */
|
||||
static void _draw_corner(float x, float y, float rx, float ry){
|
||||
x -= rx;
|
||||
y -= ry;
|
||||
sgl_v2f(x, y);
|
||||
for(int i = 0; i < 16; ++i){
|
||||
sgl_v2f(x, y);
|
||||
sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i]));
|
||||
}
|
||||
sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15]));
|
||||
}
|
||||
|
||||
/* rx,ry = radius ix,iy = inner radius */
|
||||
static void _draw_corner_border(float x, float y, float rx, float ry, float ix, float iy){
|
||||
x -= rx;
|
||||
y -= ry;
|
||||
sgl_v2f(x+(ix*_SIN[15]), y+(iy*_SIN[0]));
|
||||
for(int i = 0; i < 16; ++i){
|
||||
sgl_v2f(x+(ix*_SIN[15-i]), y+(iy*_SIN[i]));
|
||||
sgl_v2f(x+(rx*_SIN[15-i]), y+(ry*_SIN[i]));
|
||||
}
|
||||
sgl_v2f(x+(rx*_SIN[0]), y+(ry*_SIN[15]));
|
||||
}
|
||||
|
||||
void sclay_render(Clay_RenderCommandArray renderCommands, sclay_font_t *fonts) {
|
||||
sgl_matrix_mode_modelview();
|
||||
sgl_translate(-1.0f, 1.0f, 0.0f);
|
||||
sgl_scale(2.0f/_sclay.size.width, -2.0f/_sclay.size.height, 1.0f);
|
||||
sgl_disable_texture();
|
||||
sgl_push_pipeline();
|
||||
sgl_load_pipeline(_sclay.pip);
|
||||
for (uint32_t i = 0; i < renderCommands.length; i++) {
|
||||
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i);
|
||||
Clay_BoundingBox bbox = renderCommand->boundingBox;
|
||||
switch (renderCommand->commandType) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle;
|
||||
sgl_c4f(config->backgroundColor.r / 255.0f,
|
||||
config->backgroundColor.g / 255.0f,
|
||||
config->backgroundColor.b / 255.0f,
|
||||
config->backgroundColor.a / 255.0f);
|
||||
Clay_CornerRadius r = config->cornerRadius;
|
||||
sgl_begin_triangle_strip();
|
||||
if(r.topLeft > 0 || r.topRight > 0){
|
||||
_draw_corner(bbox.x, bbox.y, -r.topLeft, -r.topLeft);
|
||||
_draw_corner(bbox.x+bbox.width, bbox.y, r.topRight, -r.topRight);
|
||||
_draw_rect(bbox.x+r.topLeft, bbox.y,
|
||||
bbox.width-r.topLeft-r.topRight, CLAY__MAX(r.topLeft, r.topRight));
|
||||
}
|
||||
if(r.bottomLeft > 0 || r.bottomRight > 0){
|
||||
_draw_corner(bbox.x, bbox.y+bbox.height, -r.bottomLeft, r.bottomLeft);
|
||||
_draw_corner(bbox.x+bbox.width, bbox.y+bbox.height, r.bottomRight, r.bottomRight);
|
||||
_draw_rect(bbox.x+r.bottomLeft,
|
||||
bbox.y+bbox.height-CLAY__MAX(r.bottomLeft, r.bottomRight),
|
||||
bbox.width-r.bottomLeft-r.bottomRight, CLAY__MAX(r.bottomLeft, r.bottomRight));
|
||||
}
|
||||
if(r.topLeft < r.bottomLeft){
|
||||
if(r.topLeft < r.topRight){
|
||||
_draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft);
|
||||
_draw_rect(bbox.x+r.topLeft, bbox.y+r.topRight,
|
||||
r.bottomLeft-r.topLeft, bbox.height-r.topRight-r.bottomLeft);
|
||||
} else {
|
||||
_draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft);
|
||||
}
|
||||
} else {
|
||||
if(r.bottomLeft < r.bottomRight){
|
||||
_draw_rect(bbox.x, bbox.y+r.topLeft, r.bottomLeft, bbox.height-r.topLeft-r.bottomLeft);
|
||||
_draw_rect(bbox.x+r.bottomLeft, bbox.y+r.topLeft,
|
||||
r.topLeft-r.bottomLeft, bbox.height-r.topLeft-r.bottomRight);
|
||||
} else {
|
||||
_draw_rect(bbox.x, bbox.y+r.topLeft, r.topLeft, bbox.height-r.topLeft-r.bottomLeft);
|
||||
}
|
||||
}
|
||||
if(r.topRight < r.bottomRight){
|
||||
if(r.topRight < r.topLeft){
|
||||
_draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topLeft,
|
||||
r.bottomRight-r.topRight, bbox.height-r.topLeft-r.bottomRight);
|
||||
_draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight,
|
||||
r.topRight, bbox.height-r.topRight-r.bottomRight);
|
||||
} else {
|
||||
_draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight,
|
||||
r.bottomRight, bbox.height-r.topRight-r.bottomRight);
|
||||
}
|
||||
} else {
|
||||
if(r.bottomRight < r.bottomLeft){
|
||||
_draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight,
|
||||
r.topRight-r.bottomRight, bbox.height-r.topRight-r.bottomLeft);
|
||||
_draw_rect(bbox.x+bbox.width-r.bottomRight, bbox.y+r.topRight,
|
||||
r.bottomRight, bbox.height-r.topRight-r.bottomRight);
|
||||
} else {
|
||||
_draw_rect(bbox.x+bbox.width-r.topRight, bbox.y+r.topRight,
|
||||
r.topRight, bbox.height-r.topRight-r.bottomRight);
|
||||
}
|
||||
}
|
||||
_draw_rect(bbox.x+CLAY__MAX(r.topLeft, r.bottomLeft),
|
||||
bbox.y+CLAY__MAX(r.topLeft, r.topRight),
|
||||
bbox.width-CLAY__MAX(r.topLeft, r.bottomLeft)-CLAY__MAX(r.topRight, r.bottomRight),
|
||||
bbox.height-CLAY__MAX(r.topLeft, r.topRight)-CLAY__MAX(r.bottomLeft, r.bottomRight));
|
||||
sgl_end();
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
if(!fonts) break;
|
||||
Clay_TextRenderData *config = &renderCommand->renderData.text;
|
||||
Clay_StringSlice text = config->stringContents;
|
||||
fonsSetFont(_sclay.fonts, fonts[config->fontId]);
|
||||
uint32_t color = sfons_rgba(
|
||||
config->textColor.r,
|
||||
config->textColor.g,
|
||||
config->textColor.b,
|
||||
config->textColor.a);
|
||||
fonsSetColor(_sclay.fonts, color);
|
||||
fonsSetSpacing(_sclay.fonts, config->letterSpacing * _sclay.dpi_scale);
|
||||
fonsSetAlign(_sclay.fonts, FONS_ALIGN_LEFT | FONS_ALIGN_TOP);
|
||||
fonsSetSize(_sclay.fonts, config->fontSize * _sclay.dpi_scale);
|
||||
sgl_matrix_mode_modelview();
|
||||
sgl_push_matrix();
|
||||
sgl_scale(1.0f/_sclay.dpi_scale, 1.0f/_sclay.dpi_scale, 1.0f);
|
||||
fonsDrawText(_sclay.fonts, bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale,
|
||||
text.chars, text.chars + text.length);
|
||||
sgl_pop_matrix();
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
|
||||
sgl_scissor_rectf(bbox.x*_sclay.dpi_scale, bbox.y*_sclay.dpi_scale,
|
||||
bbox.width*_sclay.dpi_scale, bbox.height*_sclay.dpi_scale,
|
||||
true);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
|
||||
sgl_scissor_rectf(0, 0,
|
||||
_sclay.size.width*_sclay.dpi_scale, _sclay.size.height*_sclay.dpi_scale,
|
||||
true);
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
|
||||
//TODO
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderRenderData *config = &renderCommand->renderData.border;
|
||||
sgl_c4f(config->color.r / 255.0f,
|
||||
config->color.g / 255.0f,
|
||||
config->color.b / 255.0f,
|
||||
config->color.a / 255.0f);
|
||||
Clay_BorderWidth w = config->width;
|
||||
Clay_CornerRadius r = config->cornerRadius;
|
||||
sgl_begin_triangle_strip();
|
||||
if(w.left > 0){
|
||||
_draw_rect(bbox.x, bbox.y + r.topLeft,
|
||||
w.left, bbox.height - r.topLeft - r.bottomLeft);
|
||||
}
|
||||
if(w.right > 0){
|
||||
_draw_rect(bbox.x + bbox.width - w.right, bbox.y + r.topRight,
|
||||
w.right, bbox.height - r.topRight - r.bottomRight);
|
||||
}
|
||||
if(w.top > 0){
|
||||
_draw_rect(bbox.x + r.topLeft, bbox.y,
|
||||
bbox.width - r.topLeft - r.topRight, w.top);
|
||||
}
|
||||
if(w.bottom > 0){
|
||||
_draw_rect(bbox.x + r.bottomLeft, bbox.y + bbox.height - w.bottom,
|
||||
bbox.width - r.bottomLeft - r.bottomRight, w.bottom);
|
||||
}
|
||||
if(r.topLeft > 0 && (w.top > 0 || w.left > 0)){
|
||||
_draw_corner_border(bbox.x, bbox.y,
|
||||
-r.topLeft, -r.topLeft,
|
||||
-r.topLeft+w.left, -r.topLeft+w.top);
|
||||
}
|
||||
if(r.topRight > 0 && (w.top > 0 || w.right > 0)){
|
||||
_draw_corner_border(bbox.x+bbox.width, bbox.y,
|
||||
r.topRight, -r.topRight,
|
||||
r.topRight-w.right, -r.topRight+w.top);
|
||||
}
|
||||
if(r.bottomLeft > 0 && (w.bottom > 0 || w.left > 0)){
|
||||
_draw_corner_border(bbox.x, bbox.y+bbox.height,
|
||||
-r.bottomLeft, r.bottomLeft,
|
||||
-r.bottomLeft+w.left, r.bottomLeft-w.bottom);
|
||||
}
|
||||
if(r.bottomRight > 0 && (w.bottom > 0 || w.right > 0)){
|
||||
_draw_corner_border(bbox.x+bbox.width, bbox.y+bbox.height,
|
||||
r.bottomRight, r.bottomRight,
|
||||
r.bottomRight-w.right, r.bottomRight-w.bottom);
|
||||
}
|
||||
sgl_end();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
sgl_pop_pipeline();
|
||||
sfons_flush(_sclay.fonts);
|
||||
}
|
||||
#endif /* SOKOL_CLAY_IMPL */
|