mirror of
https://github.com/nicbarker/clay.git
synced 2025-04-15 10:48:04 +00:00
Compare commits
79 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5391a259f3 | ||
|
fe2d44a888 | ||
|
06167b4f4b | ||
|
eb46688b82 | ||
|
87efc49f52 | ||
|
a9e94e3be0 | ||
|
cbb50267da | ||
|
55792fdbec | ||
|
50aad568fa | ||
|
b4dc02c73a | ||
|
3f635cdd79 | ||
|
1204ac400b | ||
|
6a7ce77024 | ||
|
7c9506bc31 | ||
|
08e4c5b198 | ||
|
b1c72a0647 | ||
|
aee4baee1c | ||
|
47d1d84bc8 | ||
|
ad49977f1b | ||
|
61490e4557 | ||
|
982ade4cf9 | ||
|
d5af2c3dc0 | ||
|
2677bec854 | ||
|
05ac2810d8 | ||
|
1f8cab8d72 | ||
|
6186596b41 | ||
|
a7d46629b1 | ||
|
bee93bc7ba | ||
|
39fdd0e906 | ||
|
008d4d2519 | ||
|
3e39e444db | ||
|
8a57153700 | ||
|
09d581a523 | ||
|
f824ddfd25 | ||
|
82bb48a235 | ||
|
c06e01c1af | ||
|
6567f85eb3 | ||
|
a92ec772e1 | ||
|
a782df73a1 | ||
|
3a9172ec4c | ||
|
fabdad43f6 | ||
|
e856136a8e | ||
|
19a27b39f2 | ||
|
33b8e76903 | ||
|
ad4d00be33 | ||
|
22e8cc318c | ||
|
8e6640f7a2 | ||
|
4f8957d5d2 | ||
|
5009146c65 | ||
|
865b06d386 | ||
|
12319fc240 | ||
|
2d7d5bc082 | ||
|
cf97539612 | ||
|
c49593f1d3 | ||
|
c7703b7a50 | ||
|
adc31f82e8 | ||
|
ad363f986c | ||
|
3612431e82 | ||
|
02bce89d17 | ||
|
b5b086af13 | ||
|
375501fb89 | ||
|
5571c00a21 | ||
|
4ee501019c | ||
|
1fa8684e47 | ||
|
feead45f3e | ||
|
766325c395 | ||
|
5afdf3f8c9 | ||
|
a60b977946 | ||
|
20340f6544 | ||
|
ee99e5f0f2 | ||
|
256fc32549 | ||
|
28a8f59733 | ||
|
47c8e9178e | ||
|
a62ee15758 | ||
|
c73dffbb6f | ||
|
eb553962e8 | ||
|
d9e02ab1d3 | ||
|
bc2548e3ec | ||
|
eeb4520f48 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
github: [nicbarker]
|
2
.github/workflows/cmake-multi-platform.yml
vendored
2
.github/workflows/cmake-multi-platform.yml
vendored
@ -53,7 +53,7 @@ jobs:
|
||||
echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4.0.2
|
||||
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"
|
||||
|
@ -9,6 +9,8 @@ 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}")
|
||||
|
||||
@ -36,5 +38,18 @@ 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 .)
|
||||
|
106
README.md
106
README.md
@ -4,7 +4,7 @@
|
||||
### Major Features
|
||||
- Microsecond layout performance
|
||||
- Flex-box like layout model for complex, responsive layouts including text wrapping, scrolling containers and aspect ratio scaling
|
||||
- Single ~2k LOC **clay.h** file with **zero** dependencies (including no standard library)
|
||||
- Single ~4k LOC **clay.h** file with **zero** dependencies (including no standard library)
|
||||
- Wasm support: compile with clang to a 15kb uncompressed **.wasm** file for use in the browser
|
||||
- Static arena based memory use with no malloc / free, and low total memory overhead (e.g. ~3.5mb for 8192 layout elements).
|
||||
- React-like nested declarative syntax
|
||||
@ -89,7 +89,7 @@ int main() {
|
||||
CLAY({
|
||||
.id = CLAY_ID("SideBar"),
|
||||
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 },
|
||||
.backgroundColor = COLOR_LIGHT }
|
||||
.backgroundColor = COLOR_LIGHT
|
||||
}) {
|
||||
CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) {
|
||||
CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} } }) {}
|
||||
@ -474,6 +474,7 @@ Clay supports C preprocessor directives to modulate functionality at compile tim
|
||||
The supported directives are:
|
||||
|
||||
- `CLAY_WASM` - Required when targeting Web Assembly.
|
||||
- `CLAY_DLL` - Required when creating a .Dll file.
|
||||
|
||||
### Bindings for non C
|
||||
|
||||
@ -722,6 +723,15 @@ Returns [Clay_ScrollContainerData](#clay_scrollcontainerdata) for the scroll con
|
||||
|
||||
---
|
||||
|
||||
### Clay_GetElementData
|
||||
|
||||
`Clay_ElementData Clay_GetElementData(Clay_ElementId id)`
|
||||
|
||||
Returns [Clay_ElementData](#clay_elementdata) for the element matching the provided ID.
|
||||
Used to retrieve information about elements such as their final calculated bounding box.
|
||||
|
||||
---
|
||||
|
||||
### Clay_GetElementId
|
||||
|
||||
`Clay_ElementId Clay_GetElementId(Clay_String idString)`
|
||||
@ -887,18 +897,12 @@ Element is subject to [culling](#visibility-culling). Otherwise, multiple `Clay_
|
||||
|
||||
### CLAY_ID
|
||||
|
||||
**Usage**
|
||||
|
||||
`CLAY(CLAY_ID(char* idString)) {}`
|
||||
|
||||
**Lifecycle**
|
||||
|
||||
`Clay_BeginLayout()` -> `CLAY(` -> `CLAY_ID()` -> `)` -> `Clay_EndLayout()`
|
||||
|
||||
**Notes**
|
||||
`Clay_ElementId CLAY_ID(STRING_LITERAL idString)`
|
||||
|
||||
**CLAY_ID()** is used to generate and attach a [Clay_ElementId](#clay_elementid) to a layout element during declaration.
|
||||
|
||||
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID](#clay_sid).
|
||||
|
||||
To regenerate the same ID outside of layout declaration when using utility functions such as [Clay_PointerOver](#clay_pointerover), use the [Clay_GetElementId](#clay_getelementid) function.
|
||||
|
||||
**Examples**
|
||||
@ -921,11 +925,31 @@ if (buttonIsHovered && leftMouseButtonPressed) {
|
||||
|
||||
---
|
||||
|
||||
### CLAY_SID()
|
||||
|
||||
`Clay_ElementId CLAY_SID(Clay_String idString)`
|
||||
|
||||
A version of [CLAY_ID](#clay_id) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
|
||||
|
||||
---
|
||||
|
||||
### CLAY_IDI()
|
||||
|
||||
`Clay_ElementId CLAY_IDI(char *label, int32_t index)`
|
||||
`Clay_ElementId CLAY_IDI(STRING_LITERAL idString, int32_t index)`
|
||||
|
||||
An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
|
||||
An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`.
|
||||
|
||||
Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
|
||||
|
||||
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI](#clay_sidi).
|
||||
|
||||
---
|
||||
|
||||
### CLAY_SIDI()
|
||||
|
||||
`Clay_ElementId CLAY_SIDI(Clay_String idString, int32_t index)`
|
||||
|
||||
A version of [CLAY_IDI](#clay_idi) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
|
||||
|
||||
---
|
||||
|
||||
@ -933,7 +957,7 @@ An offset version of [CLAY_ID](#clay_id). Generates a [Clay_ElementId](#clay_ele
|
||||
|
||||
**Usage**
|
||||
|
||||
`CLAY(CLAY_ID_LOCAL(char* idString)) {}`
|
||||
`Clay_ElementId CLAY_ID_LOCAL(STRING_LITERAL idString)`
|
||||
|
||||
**Lifecycle**
|
||||
|
||||
@ -947,6 +971,8 @@ Unlike [CLAY_ID](#clay_id) which needs to be globally unique, a local ID is base
|
||||
|
||||
As a result, local id is suitable for use in reusable components and loops.
|
||||
|
||||
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SID_LOCAL](#clay_sid_local).
|
||||
|
||||
**Examples**
|
||||
|
||||
```C
|
||||
@ -966,11 +992,31 @@ for (int i = 0; i < headerButtons.length; i++) {
|
||||
|
||||
---
|
||||
|
||||
### CLAY_SID_LOCAL()
|
||||
|
||||
`Clay_ElementId CLAY_SID_LOCAL(Clay_String idString)`
|
||||
|
||||
A version of [CLAY_ID_LOCAL](#clay_id_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
|
||||
|
||||
---
|
||||
|
||||
### CLAY_IDI_LOCAL()
|
||||
|
||||
`Clay_ElementId CLAY_IDI_LOCAL(char *label, int32_t index)`
|
||||
`Clay_ElementId CLAY_IDI_LOCAL(STRING_LITERAL idString, int32_t index)`
|
||||
|
||||
An offset version of [CLAY_ID_LOCAL](#clay_local_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`. Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
|
||||
An offset version of [CLAY_ID_LOCAL](#clay_local_id). Generates a [Clay_ElementId](#clay_elementid) string id from the provided `char *label`, combined with the `int index`.
|
||||
|
||||
Used for generating ids for sequential elements (such as in a `for` loop) without having to construct dynamic strings at runtime.
|
||||
|
||||
Note this macro only works with String literals and won't compile if used with a `char*` variable. To use a heap allocated `char*` string as an ID, use [CLAY_SIDI_LOCAL](#clay_sidi_local).
|
||||
|
||||
---
|
||||
|
||||
### CLAY_SIDI_LOCAL()
|
||||
|
||||
`Clay_ElementId CLAY_SIDI_LOCAL(Clay_String idString, int32_t index)`
|
||||
|
||||
A version of [CLAY_IDI_LOCAL](#clay_idi_local) that can be used with heap allocated `char *` data. The underlying `char` data will not be copied internally and should live until at least the next frame.
|
||||
|
||||
---
|
||||
|
||||
@ -1240,7 +1286,7 @@ Used to perform **aspect ratio scaling** on the image element. As of this versio
|
||||
|
||||
```C
|
||||
// Load an image somewhere in your code
|
||||
Image profilePicture = LoadImage("profilePicture.png");
|
||||
YourImage profilePicture = LoadYourImage("profilePicture.png");
|
||||
// Note that when rendering, .imageData will be void* type.
|
||||
CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = { 60, 60 } } }) {}
|
||||
```
|
||||
@ -1249,7 +1295,7 @@ CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = { 60, 60 } }
|
||||
|
||||
```C
|
||||
// Load an image somewhere in your code
|
||||
Image profilePicture = LoadImage("profilePicture.png");
|
||||
YourImage profilePicture = LoadYourImage("profilePicture.png");
|
||||
// Declare a reusable image config
|
||||
Clay_ImageElementConfig imageConfig = (Clay_ImageElementConfig) { .imageData = &profilePicture, .sourceDimensions = {60, 60} };
|
||||
// Declare an image element using a reusable config
|
||||
@ -1257,7 +1303,7 @@ CLAY({ .image = imageConfig }) {}
|
||||
// Declare an image element using an inline config
|
||||
CLAY({ .image = { .imageData = &profilePicture, .sourceDimensions = {60, 60} } }) {}
|
||||
// Rendering example
|
||||
Image *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData;
|
||||
YourImage *imageToRender = renderCommand->elementConfig.imageElementConfig->imageData;
|
||||
```
|
||||
|
||||
**Rendering**
|
||||
@ -1967,16 +2013,34 @@ typedef union {
|
||||
### Clay_ScrollContainerData
|
||||
|
||||
```C
|
||||
typedef struct
|
||||
{
|
||||
// Data representing the current internal state of a scrolling element.
|
||||
typedef 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.
|
||||
Clay_Vector2 *scrollPosition;
|
||||
// The bounding box of the scroll element.
|
||||
Clay_Dimensions scrollContainerDimensions;
|
||||
// The outer dimensions of the inner scroll container content, including the padding of the parent scroll container.
|
||||
Clay_Dimensions contentDimensions;
|
||||
// The config that was originally passed to the scroll element.
|
||||
Clay_ScrollElementConfig config;
|
||||
// Indicates whether an actual scroll container matched the provided ID or if the default struct was returned.
|
||||
bool found;
|
||||
} Clay_ScrollContainerData;
|
||||
```
|
||||
|
||||
### Clay_ElementData
|
||||
|
||||
```C
|
||||
// Bounding box and other data for a specific UI element.
|
||||
typedef struct {
|
||||
// The rectangle that encloses this UI element, with the position relative to the root of the layout.
|
||||
Clay_BoundingBox boundingBox;
|
||||
// Indicates whether an actual Element matched the provided ID or if the default struct was returned.
|
||||
bool found;
|
||||
} Clay_ElementData;
|
||||
```
|
||||
|
||||
**Fields**
|
||||
|
||||
**`.scrollPosition`** - `Clay_Vector2 *`
|
||||
|
@ -1,6 +1,6 @@
|
||||
### 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.
|
||||
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
|
||||
|
||||
@ -8,21 +8,21 @@ Special thanks to
|
||||
- [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):
|
||||
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
|
||||
CLAY_RECTANGLE(CLAY_ID("SideBar"), CLAY_LAYOUT(.layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}), CLAY_RECTANGLE_CONFIG(.color = COLOR_LIGHT), {
|
||||
// Child elements here
|
||||
});
|
||||
// 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.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BOTTOM, sizing = { width = clay.SizingFixed(300), height = clay.SizingGrow({}) }, padding = {16, 16} }), clay.RectangleConfig({ color = COLOR_LIGHT })) {
|
||||
// Child elements here
|
||||
if clay.UI()({ id = clay.ID("Outer"), layout = { padding = clay.PaddingAll(16) }}) {
|
||||
// Child elements here
|
||||
}
|
||||
```
|
||||
|
||||
@ -34,111 +34,170 @@ if clay.Rectangle(clay.ID("SideBar"), clay.Layout({ layoutDirection = .TOP_TO_BO
|
||||
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(arena)](https://github.com/nicbarker/clay/blob/main/README.md#clay_initialize).
|
||||
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
|
||||
minMemorySize: u32 = clay.MinMemorySize()
|
||||
memory := make([^]u8, minMemorySize)
|
||||
arena: clay.Arena = clay.CreateArenaWithCapacityAndMemory(minMemorySize, memory)
|
||||
clay.Initialize(arena)
|
||||
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 `measureText(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.
|
||||
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
|
||||
measureText :: proc "c" (text: ^clay.String, config: ^clay.TextElementConfig) -> clay.Dimensions {
|
||||
// clay.TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
|
||||
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(measureText)
|
||||
```
|
||||
|
||||
4. **Optional** - Call [clay.SetPointerPosition(pointerPosition)](https://github.com/nicbarker/clay/blob/main/README.md#clay_setpointerposition) if you want to use mouse interactions.
|
||||
// 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.SetPointerPosition(clay.Vector2{ mousePositionX, mousePositionY })
|
||||
clay.SetPointerState(
|
||||
clay.Vector2 { mouse_pos_x, mouse_pos_y },
|
||||
is_mouse_down,
|
||||
)
|
||||
```
|
||||
|
||||
5. Call [clay.BeginLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_beginlayout) and declare your layout using the provided macros.
|
||||
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
|
||||
sidebarItemLayout := clay.LayoutConfig {
|
||||
sizing = {width = clay.SizingGrow({}), height = clay.SizingFixed(50)},
|
||||
sidebar_item_layout := clay.LayoutConfig {
|
||||
sizing = {
|
||||
width = clay.SizingGrow({}),
|
||||
height = clay.SizingFixed(50)
|
||||
},
|
||||
}
|
||||
|
||||
// Re-useable components are just normal functions
|
||||
SidebarItemComponent :: proc(index: u32) {
|
||||
if clay.Rectangle(clay.ID("SidebarBlob", index), &sidebarItemLayout, clay.RectangleConfig({color = COLOR_ORANGE})) {}
|
||||
// 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 begin the "root" of your layout tree
|
||||
CreateLayout :: proc() -> clay.ClayArray(clay.RenderCommand) {
|
||||
clay.BeginLayout(windowWidth, windowHeight)
|
||||
// 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.Rectangle(
|
||||
clay.ID("OuterContainer"),
|
||||
clay.Layout({sizing = {clay.SizingGrow({}), clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
|
||||
clay.RectangleConfig({color = {250, 250, 255, 255}}),
|
||||
) {
|
||||
if clay.Rectangle(
|
||||
clay.ID("SideBar"),
|
||||
clay.Layout({layoutDirection = .TOP_TO_BOTTOM, sizing = {width = clay.SizingFixed(300), height = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16}),
|
||||
clay.RectangleConfig({color = COLOR_LIGHT}),
|
||||
) {
|
||||
if clay.Rectangle(
|
||||
clay.ID("ProfilePictureOuter"),
|
||||
clay.Layout({sizing = {width = clay.SizingGrow({})}, padding = {16, 16}, childGap = 16, childAlignment = {y = .CENTER}}),
|
||||
clay.RectangleConfig({color = COLOR_RED}),
|
||||
) {
|
||||
if clay.Image(
|
||||
clay.ID("ProfilePicture"),
|
||||
clay.Layout({sizing = {width = clay.SizingFixed(60), height = clay.SizingFixed(60)}}),
|
||||
clay.ImageConfig({imageData = &profilePicture, sourceDimensions = {height = 60, width = 60}}),
|
||||
) {}
|
||||
clay.Text(clay.ID("ProfileTitle"), "Clay - UI Library", clay.TextConfig({fontSize = 24, textColor = {255, 255, 255, 255}}))
|
||||
// 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
|
||||
for i in 0..<10 {
|
||||
SidebarItemComponent(i)
|
||||
// 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.Rectangle(
|
||||
clay.ID("MainContent"),
|
||||
clay.Layout({sizing = {width = clay.SizingGrow({}), height = clay.SizingGrow({})}}),
|
||||
clay.RectangleConfig({color = COLOR_LIGHT}),
|
||||
) {}
|
||||
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 [clay.EndLayout(screenWidth, screenHeight)](https://github.com/nicbarker/clay/blob/main/README.md#clay_endlayout) and process the resulting [clay.RenderCommandArray](https://github.com/nicbarker/clay/blob/main/README.md#clay_rendercommandarray) in your choice of renderer.
|
||||
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
|
||||
renderCommands: clay.ClayArray(clay.RenderCommand) = clay.EndLayout(windowWidth, windowHeight)
|
||||
render_commands := create_layout()
|
||||
|
||||
for i: u32 = 0; i < renderCommands.length; i += 1 {
|
||||
renderCommand := clay.RenderCommandArray_Get(&renderCommands, cast(i32)i)
|
||||
for i in 0..<i32(render_commands.length) {
|
||||
render_command := clay.RenderCommandArray_Get(render_commands, i)
|
||||
|
||||
switch renderCommand.commandType {
|
||||
switch render_command.commandType {
|
||||
case .Rectangle:
|
||||
DrawRectangle(renderCommand.boundingBox, renderCommand.config.rectangleElementConfig.color)
|
||||
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_RECTANGLE` (C) -> `clay.Rectangle` (Odin)
|
||||
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).
|
||||
|
@ -1,13 +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 && ar r clay-odin/macos/clay.a clay.o;
|
||||
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 && ar r clay-odin/macos-arm64/clay.a clay.o;
|
||||
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 clay.c;
|
||||
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 && ar r clay-odin/linux/clay.a clay.o;
|
||||
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 clay.c;
|
||||
clang -c -DCLAY_IMPLEMENTATION -o clay-odin/wasm/clay.o -target wasm32 -nostdlib -static -O3 clay.c;
|
||||
rm clay.o;
|
||||
rm clay.c;
|
||||
|
@ -1,436 +1,470 @@
|
||||
package clay
|
||||
|
||||
import "core:c"
|
||||
import "core:strings"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
foreign import Clay "windows/clay.lib"
|
||||
foreign import Clay "windows/clay.lib"
|
||||
} else when ODIN_OS == .Linux {
|
||||
foreign import Clay "linux/clay.a"
|
||||
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"
|
||||
}
|
||||
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"
|
||||
foreign import Clay "wasm/clay.o"
|
||||
}
|
||||
|
||||
String :: struct {
|
||||
length: c.int32_t,
|
||||
chars: [^]c.char,
|
||||
isStaticallyAllocated: c.bool,
|
||||
length: c.int32_t,
|
||||
chars: [^]c.char,
|
||||
}
|
||||
|
||||
StringSlice :: struct {
|
||||
length: c.int32_t,
|
||||
chars: [^]c.char,
|
||||
baseChars: [^]c.char,
|
||||
length: c.int32_t,
|
||||
chars: [^]c.char,
|
||||
baseChars: [^]c.char,
|
||||
}
|
||||
|
||||
Vector2 :: [2]c.float
|
||||
|
||||
Dimensions :: struct {
|
||||
width: c.float,
|
||||
height: c.float,
|
||||
width: c.float,
|
||||
height: c.float,
|
||||
}
|
||||
|
||||
Arena :: struct {
|
||||
nextAllocation: uintptr,
|
||||
capacity: uintptr,
|
||||
memory: [^]c.char,
|
||||
nextAllocation: uintptr,
|
||||
capacity: c.size_t,
|
||||
memory: [^]c.char,
|
||||
}
|
||||
|
||||
BoundingBox :: struct {
|
||||
x: c.float,
|
||||
y: c.float,
|
||||
width: c.float,
|
||||
height: c.float,
|
||||
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,
|
||||
topLeft: c.float,
|
||||
topRight: c.float,
|
||||
bottomLeft: c.float,
|
||||
bottomRight: c.float,
|
||||
}
|
||||
|
||||
BorderData :: struct {
|
||||
width: u32,
|
||||
color: Color,
|
||||
width: u32,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
ElementId :: struct {
|
||||
id: u32,
|
||||
offset: u32,
|
||||
baseId: u32,
|
||||
stringId: String,
|
||||
id: u32,
|
||||
offset: u32,
|
||||
baseId: u32,
|
||||
stringId: String,
|
||||
}
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
EnumBackingType :: u32
|
||||
EnumBackingType :: u32
|
||||
} else {
|
||||
EnumBackingType :: u8
|
||||
EnumBackingType :: u8
|
||||
}
|
||||
|
||||
RenderCommandType :: enum EnumBackingType {
|
||||
None,
|
||||
Rectangle,
|
||||
Border,
|
||||
Text,
|
||||
Image,
|
||||
ScissorStart,
|
||||
ScissorEnd,
|
||||
Custom,
|
||||
None,
|
||||
Rectangle,
|
||||
Border,
|
||||
Text,
|
||||
Image,
|
||||
ScissorStart,
|
||||
ScissorEnd,
|
||||
Custom,
|
||||
}
|
||||
|
||||
RectangleElementConfig :: struct {
|
||||
color: Color,
|
||||
color: Color,
|
||||
}
|
||||
|
||||
TextWrapMode :: enum EnumBackingType {
|
||||
Words,
|
||||
Newlines,
|
||||
None,
|
||||
Words,
|
||||
Newlines,
|
||||
None,
|
||||
}
|
||||
|
||||
TextAlignment :: enum EnumBackingType {
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
Left,
|
||||
Center,
|
||||
Right,
|
||||
}
|
||||
|
||||
TextElementConfig :: struct {
|
||||
textColor: Color,
|
||||
fontId: u16,
|
||||
fontSize: u16,
|
||||
letterSpacing: u16,
|
||||
lineHeight: u16,
|
||||
wrapMode: TextWrapMode,
|
||||
textAlignment: TextAlignment,
|
||||
hashStringContents: bool,
|
||||
userData: rawptr,
|
||||
textColor: Color,
|
||||
fontId: u16,
|
||||
fontSize: u16,
|
||||
letterSpacing: u16,
|
||||
lineHeight: u16,
|
||||
wrapMode: TextWrapMode,
|
||||
textAlignment: TextAlignment,
|
||||
}
|
||||
|
||||
ImageElementConfig :: struct {
|
||||
imageData: rawptr,
|
||||
sourceDimensions: Dimensions,
|
||||
imageData: rawptr,
|
||||
sourceDimensions: Dimensions,
|
||||
}
|
||||
|
||||
CustomElementConfig :: struct {
|
||||
customData: rawptr,
|
||||
customData: rawptr,
|
||||
}
|
||||
|
||||
BorderWidth :: struct {
|
||||
left: u16,
|
||||
right: u16,
|
||||
top: u16,
|
||||
bottom: u16,
|
||||
betweenChildren: u16,
|
||||
left: u16,
|
||||
right: u16,
|
||||
top: u16,
|
||||
bottom: u16,
|
||||
betweenChildren: u16,
|
||||
}
|
||||
|
||||
BorderElementConfig :: struct {
|
||||
color: Color,
|
||||
width: BorderWidth,
|
||||
color: Color,
|
||||
width: BorderWidth,
|
||||
}
|
||||
|
||||
ScrollElementConfig :: struct {
|
||||
horizontal: bool,
|
||||
vertical: bool,
|
||||
horizontal: bool,
|
||||
vertical: bool,
|
||||
}
|
||||
|
||||
FloatingAttachPointType :: enum EnumBackingType {
|
||||
LeftTop,
|
||||
LeftCenter,
|
||||
LeftBottom,
|
||||
CenterTop,
|
||||
CenterCenter,
|
||||
CenterBottom,
|
||||
RightTop,
|
||||
RightCenter,
|
||||
RightBottom,
|
||||
LeftTop,
|
||||
LeftCenter,
|
||||
LeftBottom,
|
||||
CenterTop,
|
||||
CenterCenter,
|
||||
CenterBottom,
|
||||
RightTop,
|
||||
RightCenter,
|
||||
RightBottom,
|
||||
}
|
||||
|
||||
FloatingAttachPoints :: struct {
|
||||
element: FloatingAttachPointType,
|
||||
parent: FloatingAttachPointType,
|
||||
element: FloatingAttachPointType,
|
||||
parent: FloatingAttachPointType,
|
||||
}
|
||||
|
||||
PointerCaptureMode :: enum EnumBackingType {
|
||||
Capture,
|
||||
Passthrough,
|
||||
Capture,
|
||||
Passthrough,
|
||||
}
|
||||
|
||||
FloatingAttachToElement :: enum EnumBackingType {
|
||||
None,
|
||||
Parent,
|
||||
ElementWithId,
|
||||
Root,
|
||||
None,
|
||||
Parent,
|
||||
ElementWithId,
|
||||
Root,
|
||||
}
|
||||
|
||||
FloatingElementConfig :: struct {
|
||||
offset: Vector2,
|
||||
expand: Dimensions,
|
||||
parentId: u32,
|
||||
zIndex: i32,
|
||||
attachment: FloatingAttachPoints,
|
||||
pointerCaptureMode: PointerCaptureMode,
|
||||
attachTo: FloatingAttachToElement
|
||||
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,
|
||||
stringContents: StringSlice,
|
||||
textColor: Color,
|
||||
fontId: u16,
|
||||
fontSize: u16,
|
||||
letterSpacing: u16,
|
||||
lineHeight: u16,
|
||||
}
|
||||
|
||||
RectangleRenderData :: struct {
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
}
|
||||
|
||||
ImageRenderData :: struct {
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
sourceDimensions: Dimensions,
|
||||
imageData: rawptr,
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
sourceDimensions: Dimensions,
|
||||
imageData: rawptr,
|
||||
}
|
||||
|
||||
CustomRenderData :: struct {
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
customData: rawptr,
|
||||
backgroundColor: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
customData: rawptr,
|
||||
}
|
||||
|
||||
BorderRenderData :: struct {
|
||||
color: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
width: BorderWidth,
|
||||
color: Color,
|
||||
cornerRadius: CornerRadius,
|
||||
width: BorderWidth,
|
||||
}
|
||||
|
||||
RenderCommandData :: struct #raw_union {
|
||||
rectangle: RectangleRenderData,
|
||||
text: TextRenderData,
|
||||
image: ImageRenderData,
|
||||
custom: CustomRenderData,
|
||||
border: BorderRenderData,
|
||||
rectangle: RectangleRenderData,
|
||||
text: TextRenderData,
|
||||
image: ImageRenderData,
|
||||
custom: CustomRenderData,
|
||||
border: BorderRenderData,
|
||||
}
|
||||
|
||||
RenderCommand :: struct {
|
||||
boundingBox: BoundingBox,
|
||||
renderData: RenderCommandData,
|
||||
userData: rawptr,
|
||||
id: u32,
|
||||
zIndex: i16,
|
||||
commandType: RenderCommandType,
|
||||
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,
|
||||
// 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,
|
||||
Fit,
|
||||
Grow,
|
||||
Percent,
|
||||
Fixed,
|
||||
}
|
||||
|
||||
SizingConstraintsMinMax :: struct {
|
||||
min: c.float,
|
||||
max: c.float,
|
||||
min: c.float,
|
||||
max: c.float,
|
||||
}
|
||||
|
||||
SizingConstraints :: struct #raw_union {
|
||||
sizeMinMax: SizingConstraintsMinMax,
|
||||
sizePercent: c.float,
|
||||
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,
|
||||
// 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,
|
||||
width: SizingAxis,
|
||||
height: SizingAxis,
|
||||
}
|
||||
|
||||
Padding :: struct {
|
||||
left: u16,
|
||||
right: u16,
|
||||
top: u16,
|
||||
bottom: u16,
|
||||
left: u16,
|
||||
right: u16,
|
||||
top: u16,
|
||||
bottom: u16,
|
||||
}
|
||||
|
||||
LayoutDirection :: enum EnumBackingType {
|
||||
LeftToRight,
|
||||
TopToBottom,
|
||||
LeftToRight,
|
||||
TopToBottom,
|
||||
}
|
||||
|
||||
LayoutAlignmentX :: enum EnumBackingType {
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
Left,
|
||||
Right,
|
||||
Center,
|
||||
}
|
||||
|
||||
LayoutAlignmentY :: enum EnumBackingType {
|
||||
Top,
|
||||
Bottom,
|
||||
Center,
|
||||
Top,
|
||||
Bottom,
|
||||
Center,
|
||||
}
|
||||
|
||||
ChildAlignment :: struct {
|
||||
x: LayoutAlignmentX,
|
||||
y: LayoutAlignmentY,
|
||||
x: LayoutAlignmentX,
|
||||
y: LayoutAlignmentY,
|
||||
}
|
||||
|
||||
LayoutConfig :: struct {
|
||||
sizing: Sizing,
|
||||
padding: Padding,
|
||||
childGap: u16,
|
||||
childAlignment: ChildAlignment,
|
||||
layoutDirection: LayoutDirection,
|
||||
sizing: Sizing,
|
||||
padding: Padding,
|
||||
childGap: u16,
|
||||
childAlignment: ChildAlignment,
|
||||
layoutDirection: LayoutDirection,
|
||||
}
|
||||
|
||||
ClayArray :: struct($type: typeid) {
|
||||
capacity: i32,
|
||||
length: i32,
|
||||
internalArray: [^]type,
|
||||
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
|
||||
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,
|
||||
TextMeasurementFunctionNotProvided,
|
||||
ArenaCapacityExceeded,
|
||||
ElementsCapacityExceeded,
|
||||
TextMeasurementCapacityExceeded,
|
||||
DuplicateId,
|
||||
FloatingContainerParentNotFound,
|
||||
PercentageOver1,
|
||||
InternalError,
|
||||
}
|
||||
|
||||
ErrorData :: struct {
|
||||
errorType: ErrorType,
|
||||
errorText: String,
|
||||
userData: rawptr
|
||||
errorType: ErrorType,
|
||||
errorText: String,
|
||||
userData: rawptr,
|
||||
}
|
||||
|
||||
ErrorHandler :: struct {
|
||||
handler: proc "c" (errorData: ErrorData),
|
||||
userData: rawptr
|
||||
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 {
|
||||
MinMemorySize :: proc() -> u32 ---
|
||||
CreateArenaWithCapacityAndMemory :: proc(capacity: u32, offset: [^]u8) -> Arena ---
|
||||
SetPointerState :: proc(position: Vector2, pointerDown: bool) ---
|
||||
Initialize :: proc(arena: Arena, layoutDimensions: Dimensions, errorHandler: ErrorHandler) ---
|
||||
UpdateScrollContainers :: proc(enableDragScrolling: bool, scrollDelta: Vector2, deltaTime: c.float) ---
|
||||
SetLayoutDimensions :: proc(dimensions: Dimensions) ---
|
||||
BeginLayout :: proc() ---
|
||||
EndLayout :: proc() -> ClayArray(RenderCommand) ---
|
||||
Hovered :: proc() -> bool ---
|
||||
PointerOver :: proc(id: ElementId) -> bool ---
|
||||
GetElementId :: proc(id: String) -> ElementId ---
|
||||
GetScrollContainerData :: proc(id: ElementId) -> ScrollContainerData ---
|
||||
SetMeasureTextFunction :: proc(measureTextFunction: proc "c" (text: StringSlice, config: ^TextElementConfig, userData: uintptr) -> Dimensions, userData: uintptr) ---
|
||||
RenderCommandArray_Get :: proc(array: ^ClayArray(RenderCommand), index: i32) -> ^RenderCommand ---
|
||||
SetDebugModeEnabled :: proc(enabled: bool) ---
|
||||
GetCurrentContext :: proc() -> ^Context ---
|
||||
SetCurrentContext :: proc(ctx: ^Context) ---
|
||||
_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 {
|
||||
_OpenElement :: proc() ---
|
||||
_ConfigureOpenElement :: proc(config: ElementDeclaration) ---
|
||||
_CloseElement :: proc() ---
|
||||
_OpenTextElement :: proc(text: String, textConfig: ^TextElementConfig) ---
|
||||
_StoreTextElementConfig :: proc(config: TextElementConfig) -> ^TextElementConfig ---
|
||||
_HashString :: proc(toHash: String, index: u32, seed: u32) -> ElementId ---
|
||||
_GetOpenLayoutElementId :: proc() -> u32 ---
|
||||
}
|
||||
|
||||
ClayOpenElement :: struct {
|
||||
configure: proc (config: ElementDeclaration) -> bool
|
||||
_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;
|
||||
_ConfigureOpenElement(config)
|
||||
return true
|
||||
}
|
||||
|
||||
@(deferred_none = _CloseElement)
|
||||
UI :: proc() -> ClayOpenElement {
|
||||
_OpenElement()
|
||||
return { configure = ConfigureOpenElement }
|
||||
UI :: proc() -> proc (config: ElementDeclaration) -> bool {
|
||||
_OpenElement()
|
||||
return ConfigureOpenElement
|
||||
}
|
||||
|
||||
Text :: proc(text: string, config: ^TextElementConfig) {
|
||||
_OpenTextElement(MakeString(text), config)
|
||||
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)
|
||||
return _StoreTextElementConfig(config)
|
||||
}
|
||||
|
||||
PaddingAll :: proc(allPadding: u16) -> Padding {
|
||||
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
|
||||
return { left = allPadding, right = allPadding, top = allPadding, bottom = allPadding }
|
||||
}
|
||||
|
||||
CornerRadiusAll :: proc(radius: f32) -> CornerRadius {
|
||||
return CornerRadius{radius, radius, radius, radius}
|
||||
return CornerRadius{radius, radius, radius, radius}
|
||||
}
|
||||
|
||||
SizingFit :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
|
||||
return SizingAxis{type = SizingType.Fit, constraints = {sizeMinMax = sizeMinMax}}
|
||||
}
|
||||
|
||||
SizingGrow :: proc(sizeMinMax: SizingConstraintsMinMax) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
|
||||
return SizingAxis{type = SizingType.Grow, constraints = {sizeMinMax = sizeMinMax}}
|
||||
}
|
||||
|
||||
SizingFixed :: proc(size: c.float) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
|
||||
return SizingAxis{type = SizingType.Fixed, constraints = {sizeMinMax = {size, size}}}
|
||||
}
|
||||
|
||||
SizingPercent :: proc(sizePercent: c.float) -> SizingAxis {
|
||||
return SizingAxis{type = SizingType.Percent, constraints = {sizePercent = sizePercent}}
|
||||
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)}
|
||||
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)
|
||||
return _HashString(MakeString(label), index, 0)
|
||||
}
|
||||
|
||||
ID_LOCAL :: proc(label: string, index: u32 = 0) -> ElementId {
|
||||
return _HashString(MakeString(label), index, _GetParentElementId())
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -62,14 +62,14 @@ border2pxRed := clay.BorderElementConfig {
|
||||
color = COLOR_RED
|
||||
}
|
||||
|
||||
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: string, image: ^raylib.Texture2D) {
|
||||
if clay.UI().configure({
|
||||
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().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("CheckImage", index),
|
||||
layout = { sizing = { width = clay.SizingFixed(32) } },
|
||||
image = { imageData = image, sourceDimensions = { 128, 128 } },
|
||||
@ -79,27 +79,27 @@ LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Colo
|
||||
}
|
||||
|
||||
LandingPageDesktop :: proc() {
|
||||
if clay.UI().configure({
|
||||
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().configure({
|
||||
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().configure({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingPercent(0.55) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
|
||||
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().configure({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
|
||||
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().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("HeroImageOuter"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingPercent(0.45) }, childAlignment = { x = .Center }, childGap = 16 },
|
||||
}) {
|
||||
@ -114,7 +114,7 @@ LandingPageDesktop :: proc() {
|
||||
}
|
||||
|
||||
LandingPageMobile :: proc() {
|
||||
if clay.UI().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("LandingPage1Mobile"),
|
||||
layout = {
|
||||
layoutDirection = .TopToBottom,
|
||||
@ -124,18 +124,18 @@ LandingPageMobile :: proc() {
|
||||
childGap = 32,
|
||||
},
|
||||
}) {
|
||||
if clay.UI().configure({ id = clay.ID("LeftText"), layout = { sizing = { width = clay.SizingGrow({ }) }, layoutDirection = .TopToBottom, childGap = 8 } }) {
|
||||
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().configure({ layout = { sizing = { width = clay.SizingGrow({}), height = clay.SizingFixed(32) } } }) {}
|
||||
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().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("HeroImageOuter"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow({ }) }, childAlignment = { x = .Center }, childGap = 16 },
|
||||
}) {
|
||||
@ -150,17 +150,17 @@ LandingPageMobile :: proc() {
|
||||
|
||||
FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) {
|
||||
textConfig := clay.TextConfig({fontSize = 24, fontId = FONT_ID_BODY_24, textColor = COLOR_RED})
|
||||
if clay.UI().configure({
|
||||
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().configure({ id = clay.ID("HFileIncludeOuter"), layout = { padding = { 8, 8, 4, 4 } }, backgroundColor = COLOR_RED, cornerRadius = clay.CornerRadiusAll(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().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("BringYourOwnRendererOuter"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { width = widthSizing }, childAlignment = { y = .Center }, padding = { outerPadding, outerPadding, 32, 32 }, childGap = 8 },
|
||||
}) {
|
||||
@ -171,8 +171,8 @@ FeatureBlocks :: proc(widthSizing: clay.SizingAxis, outerPadding: u16) {
|
||||
}
|
||||
|
||||
FeatureBlocksDesktop :: proc() {
|
||||
if clay.UI().configure({ id = clay.ID("FeatureBlocksOuter"), layout = { sizing = { width = clay.SizingGrow({}) } } }) {
|
||||
if clay.UI().configure({
|
||||
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 },
|
||||
@ -183,7 +183,7 @@ FeatureBlocksDesktop :: proc() {
|
||||
}
|
||||
|
||||
FeatureBlocksMobile :: proc() {
|
||||
if clay.UI().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("FeatureBlocksInner"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { width = clay.SizingGrow({ }) } },
|
||||
border = { width = { betweenChildren = 2}, color = COLOR_RED },
|
||||
@ -193,9 +193,9 @@ FeatureBlocksMobile :: proc() {
|
||||
}
|
||||
|
||||
DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
|
||||
if clay.UI().configure({ id = clay.ID("SyntaxPageLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
|
||||
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().configure({ id = clay.ID("SyntaxSpacer"), layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
|
||||
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}),
|
||||
@ -209,8 +209,8 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
|
||||
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_28, textColor = COLOR_RED}),
|
||||
)
|
||||
}
|
||||
if clay.UI().configure({ id = clay.ID("SyntaxPageRightImage"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
|
||||
if clay.UI().configure({
|
||||
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 } },
|
||||
@ -219,11 +219,11 @@ DeclarativeSyntaxPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizi
|
||||
}
|
||||
|
||||
DeclarativeSyntaxPageDesktop :: proc() {
|
||||
if clay.UI().configure({
|
||||
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().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("SyntaxPage"),
|
||||
layout = { sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) }, childAlignment = { y = .Center }, padding = clay.PaddingAll(32), childGap = 32 },
|
||||
border = border2pxRed,
|
||||
@ -234,7 +234,7 @@ DeclarativeSyntaxPageDesktop :: proc() {
|
||||
}
|
||||
|
||||
DeclarativeSyntaxPageMobile :: proc() {
|
||||
if clay.UI().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("SyntaxPageMobile"),
|
||||
layout = {
|
||||
layoutDirection = .TopToBottom,
|
||||
@ -252,12 +252,12 @@ 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."
|
||||
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().configure({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
|
||||
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().configure({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } }}) {}
|
||||
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}),
|
||||
@ -271,20 +271,20 @@ HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementCon
|
||||
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_LIGHT}),
|
||||
)
|
||||
}
|
||||
if clay.UI().configure({ id = clay.ID("PerformanceRightImageOuter"), layout = { sizing = { width = widthSizing }, childAlignment = { x = .Center } } }) {
|
||||
if clay.UI().configure({
|
||||
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().configure({
|
||||
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().configure({
|
||||
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),
|
||||
@ -296,7 +296,7 @@ HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementCon
|
||||
}
|
||||
|
||||
HighPerformancePageDesktop :: proc(lerpValue: f32) {
|
||||
if clay.UI().configure({
|
||||
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,
|
||||
@ -306,7 +306,7 @@ HighPerformancePageDesktop :: proc(lerpValue: f32) {
|
||||
}
|
||||
|
||||
HighPerformancePageMobile :: proc(lerpValue: f32) {
|
||||
if clay.UI().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("PerformanceMobile"),
|
||||
layout = {
|
||||
layoutDirection = .TopToBottom,
|
||||
@ -321,8 +321,8 @@ HighPerformancePageMobile :: proc(lerpValue: f32) {
|
||||
}
|
||||
}
|
||||
|
||||
RendererButtonActive :: proc(index: i32, text: string) {
|
||||
if clay.UI().configure({
|
||||
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)
|
||||
@ -331,9 +331,9 @@ RendererButtonActive :: proc(index: i32, text: string) {
|
||||
}
|
||||
}
|
||||
|
||||
RendererButtonInactive :: proc(index: u32, text: string) {
|
||||
if clay.UI().configure({ border = border2pxRed }) {
|
||||
if clay.UI().configure({
|
||||
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,
|
||||
@ -345,9 +345,9 @@ RendererButtonInactive :: proc(index: u32, text: string) {
|
||||
}
|
||||
|
||||
RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
|
||||
if clay.UI().configure({ id = clay.ID("RendererLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
|
||||
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().configure({ layout = { sizing = { width = clay.SizingGrow({ max = 16 }) } } }) {}
|
||||
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}),
|
||||
@ -361,22 +361,22 @@ RendererPage :: proc(titleTextConfig: clay.TextElementConfig, widthSizing: clay.
|
||||
clay.TextConfig({fontSize = 28, fontId = FONT_ID_BODY_36, textColor = COLOR_RED}),
|
||||
)
|
||||
}
|
||||
if clay.UI().configure({
|
||||
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().configure({ layout = { sizing = { width = clay.SizingGrow({ max = 32 }) } } }) {}
|
||||
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ max = 32 }) } } }) {}
|
||||
RendererButtonActive(0, "Raylib Renderer")
|
||||
}
|
||||
}
|
||||
|
||||
RendererPageDesktop :: proc() {
|
||||
if clay.UI().configure({
|
||||
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().configure({
|
||||
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 } },
|
||||
@ -387,7 +387,7 @@ RendererPageDesktop :: proc() {
|
||||
}
|
||||
|
||||
RendererPageMobile :: proc() {
|
||||
if clay.UI().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("RendererMobile"),
|
||||
layout = {
|
||||
layoutDirection = .TopToBottom,
|
||||
@ -414,27 +414,27 @@ animationLerpValue: f32 = -1.0
|
||||
createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
|
||||
mobileScreen := windowWidth < 750
|
||||
clay.BeginLayout()
|
||||
if clay.UI().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("OuterContainer"),
|
||||
layout = { layoutDirection = .TopToBottom, sizing = { clay.SizingGrow({ }), clay.SizingGrow({ }) } },
|
||||
backgroundColor = COLOR_LIGHT,
|
||||
}) {
|
||||
if clay.UI().configure({
|
||||
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().configure({ layout = { sizing = { width = clay.SizingGrow({ }) } } }) {}
|
||||
if clay.UI()({ layout = { sizing = { width = clay.SizingGrow({ }) } } }) {}
|
||||
|
||||
if (!mobileScreen) {
|
||||
if clay.UI().configure({ id = clay.ID("LinkExamplesOuter"), backgroundColor = {0, 0, 0, 0} }) {
|
||||
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().configure({ id = clay.ID("LinkDocsOuter"), backgroundColor = {0, 0, 0, 0} }) {
|
||||
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().configure({
|
||||
if clay.UI()({
|
||||
id = clay.ID("LinkGithubOuter"),
|
||||
layout = { padding = { 16, 16, 6, 6 } },
|
||||
border = border2pxRed,
|
||||
@ -444,12 +444,12 @@ createLayout :: proc(lerpValue: f32) -> clay.ClayArray(clay.RenderCommand) {
|
||||
clay.Text("Github", clay.TextConfig({fontId = FONT_ID_BODY_24, fontSize = 24, textColor = {61, 26, 5, 255}}))
|
||||
}
|
||||
}
|
||||
if clay.UI().configure({ id = clay.ID("TopBorder1"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_5 } ) {}
|
||||
if clay.UI().configure({ id = clay.ID("TopBorder2"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_4 } ) {}
|
||||
if clay.UI().configure({ id = clay.ID("TopBorder3"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_3 } ) {}
|
||||
if clay.UI().configure({ id = clay.ID("TopBorder4"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_2 } ) {}
|
||||
if clay.UI().configure({ id = clay.ID("TopBorder5"), layout = { sizing = { clay.SizingGrow({ }), clay.SizingFixed(4) } }, backgroundColor = COLOR_TOP_BORDER_1 } ) {}
|
||||
if clay.UI().configure({
|
||||
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 },
|
||||
@ -489,13 +489,13 @@ errorHandler :: proc "c" (errorData: clay.ErrorData) {
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
minMemorySize: u32 = clay.MinMemorySize()
|
||||
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, 0)
|
||||
clay.SetMeasureTextFunction(measureText, nil)
|
||||
|
||||
raylib.SetConfigFlags({.VSYNC_HINT, .WINDOW_RESIZABLE, .WINDOW_HIGHDPI, .MSAA_4X_HINT})
|
||||
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")
|
||||
@ -509,12 +509,12 @@ main :: proc() {
|
||||
loadFont(FONT_ID_BODY_24, 24, "resources/Quicksand-Semibold.ttf")
|
||||
loadFont(FONT_ID_BODY_16, 16, "resources/Quicksand-Semibold.ttf")
|
||||
|
||||
syntaxImage = raylib.LoadTextureFromImage(raylib.LoadImage("resources/declarative.png"))
|
||||
checkImage1 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_1.png"))
|
||||
checkImage2 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_2.png"))
|
||||
checkImage3 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_3.png"))
|
||||
checkImage4 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_4.png"))
|
||||
checkImage5 = raylib.LoadTextureFromImage(raylib.LoadImage("resources/check_5.png"))
|
||||
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
|
||||
|
||||
|
@ -16,7 +16,7 @@ clayColorToRaylibColor :: proc(color: clay.Color) -> raylib.Color {
|
||||
|
||||
raylibFonts := [10]RaylibFont{}
|
||||
|
||||
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: uintptr) -> clay.Dimensions {
|
||||
measureText :: proc "c" (text: clay.StringSlice, config: ^clay.TextElementConfig, userData: rawptr) -> clay.Dimensions {
|
||||
// Measure string size for Font
|
||||
textSize: clay.Dimensions = {0, 0}
|
||||
|
||||
|
559
clay.h
559
clay.h
@ -1,4 +1,4 @@
|
||||
// VERSION: 0.12
|
||||
// VERSION: 0.13
|
||||
|
||||
/*
|
||||
NOTE: In order to use this library you must define
|
||||
@ -42,6 +42,12 @@
|
||||
#define CLAY_WASM_EXPORT(null)
|
||||
#endif
|
||||
|
||||
#ifdef CLAY_DLL
|
||||
#define CLAY_DLL_EXPORT __declspec(dllexport) __stdcall
|
||||
#else
|
||||
#define CLAY_DLL_EXPORT
|
||||
#endif
|
||||
|
||||
// Public Macro API ------------------------
|
||||
|
||||
#define CLAY__MAX(x, y) (((x) > (y)) ? (x) : (y))
|
||||
@ -65,25 +71,41 @@
|
||||
|
||||
#define CLAY_SIZING_PERCENT(percentOfParent) (CLAY__INIT(Clay_SizingAxis) { .size = { .percent = (percentOfParent) }, .type = CLAY__SIZING_TYPE_PERCENT })
|
||||
|
||||
// Note: If a compile error led you here, you might be trying to use CLAY_ID with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID instead.
|
||||
#define CLAY_ID(label) CLAY_IDI(label, 0)
|
||||
|
||||
#define CLAY_IDI(label, index) Clay__HashString(CLAY_STRING(label), index, 0)
|
||||
#define CLAY_SID(label) CLAY_SIDI(label, 0)
|
||||
|
||||
// Note: If a compile error led you here, you might be trying to use CLAY_IDI with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI instead.
|
||||
#define CLAY_IDI(label, index) CLAY_SIDI(CLAY_STRING(label), index)
|
||||
|
||||
#define CLAY_SIDI(label, index) Clay__HashString(label, index, 0)
|
||||
|
||||
// Note: If a compile error led you here, you might be trying to use CLAY_ID_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SID_LOCAL instead.
|
||||
#define CLAY_ID_LOCAL(label) CLAY_IDI_LOCAL(label, 0)
|
||||
|
||||
#define CLAY_IDI_LOCAL(label, index) Clay__HashString(CLAY_STRING(label), index, Clay__GetParentElementId())
|
||||
#define CLAY_SID_LOCAL(label) CLAY_SIDI_LOCAL(label, 0)
|
||||
|
||||
// Note: If a compile error led you here, you might be trying to use CLAY_IDI_LOCAL with something other than a string literal. To construct an ID with a dynamic string, use CLAY_SIDI_LOCAL instead.
|
||||
#define CLAY_IDI_LOCAL(label, index) CLAY_SIDI_LOCAL(CLAY_STRING(label), index)
|
||||
|
||||
#define CLAY_SIDI_LOCAL(label, index) Clay__HashString(label, index, Clay__GetParentElementId())
|
||||
|
||||
#define CLAY__STRING_LENGTH(s) ((sizeof(s) / sizeof((s)[0])) - sizeof((s)[0]))
|
||||
|
||||
#define CLAY__ENSURE_STRING_LITERAL(x) ("" x "")
|
||||
|
||||
// Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString)
|
||||
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
|
||||
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
|
||||
|
||||
#define CLAY_STRING_CONST(string) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
|
||||
#define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
|
||||
|
||||
static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
|
||||
|
||||
// GCC marks the above CLAY__ELEMENT_DEFINITION_LATCH as an unused variable for files that include clay.h but don't declare any layout
|
||||
// This is to suppress that warning
|
||||
static inline void Clay__SuppressUnusedLatchDefinitionVariableWarning(void) { (void) CLAY__ELEMENT_DEFINITION_LATCH; }
|
||||
|
||||
// Publicly visible layout element macros -----------------------------------------------------
|
||||
|
||||
/* This macro looks scary on the surface, but is actually quite simple.
|
||||
@ -114,7 +136,7 @@ static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
|
||||
for ( \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH = (Clay__OpenElement(), Clay__ConfigureOpenElement(CLAY__CONFIG_WRAPPER(Clay_ElementDeclaration, __VA_ARGS__)), 0); \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH < 1; \
|
||||
++CLAY__ELEMENT_DEFINITION_LATCH, Clay__CloseElement() \
|
||||
CLAY__ELEMENT_DEFINITION_LATCH=1, Clay__CloseElement() \
|
||||
)
|
||||
|
||||
// These macros exist to allow the CLAY() macro to be called both with an inline struct definition, such as
|
||||
@ -163,6 +185,9 @@ extern "C" {
|
||||
// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string,
|
||||
// but it is also used to represent slices.
|
||||
typedef struct {
|
||||
// Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program.
|
||||
// This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal.
|
||||
bool isStaticallyAllocated;
|
||||
int32_t length;
|
||||
// The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory.
|
||||
const char *chars;
|
||||
@ -248,7 +273,7 @@ typedef CLAY_PACKED_ENUM {
|
||||
CLAY_ALIGN_Y_TOP,
|
||||
// Aligns child elements to the bottom of this element, offset by padding.width.bottom
|
||||
CLAY_ALIGN_Y_BOTTOM,
|
||||
// Aligns child elements vertiically to the center of this element
|
||||
// Aligns child elements vertically to the center of this element
|
||||
CLAY_ALIGN_Y_CENTER,
|
||||
} Clay_LayoutAlignmentY;
|
||||
|
||||
@ -339,6 +364,8 @@ typedef CLAY_PACKED_ENUM {
|
||||
|
||||
// Controls various functionality related to text elements.
|
||||
typedef struct {
|
||||
// A pointer that will be transparently passed through to the resulting render command.
|
||||
void *userData;
|
||||
// The RGBA color of the font to render, conventionally specified as 0-255.
|
||||
Clay_Color textColor;
|
||||
// An integer transparently passed to Clay_MeasureText to identify the font to use.
|
||||
@ -360,10 +387,6 @@ typedef struct {
|
||||
// CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box.
|
||||
// CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
|
||||
Clay_TextAlignment textAlignment;
|
||||
// When set to true, clay will hash the entire text contents of this string as an identifier for its internal
|
||||
// text measurement cache, rather than just the pointer and length. This will incur significant performance cost for
|
||||
// long bodies of text.
|
||||
bool hashStringContents;
|
||||
} Clay_TextElementConfig;
|
||||
|
||||
CLAY__WRAPPER_STRUCT(Clay_TextElementConfig);
|
||||
@ -769,102 +792,102 @@ typedef struct {
|
||||
// Public API functions ------------------------------------------
|
||||
|
||||
// Returns the size, in bytes, of the minimum amount of memory Clay requires to operate at its current settings.
|
||||
uint32_t Clay_MinMemorySize(void);
|
||||
CLAY_DLL_EXPORT uint32_t Clay_MinMemorySize(void);
|
||||
// Creates an arena for clay to use for its internal allocations, given a certain capacity in bytes and a pointer to an allocation of at least that size.
|
||||
// Intended to be used with Clay_MinMemorySize in the following way:
|
||||
// uint32_t minMemoryRequired = Clay_MinMemorySize();
|
||||
// Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(minMemoryRequired, malloc(minMemoryRequired));
|
||||
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *memory);
|
||||
CLAY_DLL_EXPORT Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory);
|
||||
// Sets the state of the "pointer" (i.e. the mouse or touch) in Clay's internal data. Used for detecting and responding to mouse events in the debug view,
|
||||
// as well as for Clay_Hovered() and scroll element handling.
|
||||
void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown);
|
||||
CLAY_DLL_EXPORT void Clay_SetPointerState(Clay_Vector2 position, bool pointerDown);
|
||||
// Initialize Clay's internal arena and setup required data before layout can begin. Only needs to be called once.
|
||||
// - arena can be created using Clay_CreateArenaWithCapacityAndMemory()
|
||||
// - layoutDimensions are the initial bounding dimensions of the layout (i.e. the screen width and height for a full screen layout)
|
||||
// - errorHandler is used by Clay to inform you if something has gone wrong in configuration or layout.
|
||||
Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler);
|
||||
CLAY_DLL_EXPORT Clay_Context* Clay_Initialize(Clay_Arena arena, Clay_Dimensions layoutDimensions, Clay_ErrorHandler errorHandler);
|
||||
// Returns the Context that clay is currently using. Used when using multiple instances of clay simultaneously.
|
||||
Clay_Context* Clay_GetCurrentContext(void);
|
||||
CLAY_DLL_EXPORT Clay_Context* Clay_GetCurrentContext(void);
|
||||
// Sets the context that clay will use to compute the layout.
|
||||
// Used to restore a context saved from Clay_GetCurrentContext when using multiple instances of clay simultaneously.
|
||||
void Clay_SetCurrentContext(Clay_Context* context);
|
||||
CLAY_DLL_EXPORT void Clay_SetCurrentContext(Clay_Context* context);
|
||||
// Updates the state of Clay's internal scroll data, updating scroll content positions if scrollDelta is non zero, and progressing momentum scrolling.
|
||||
// - enableDragScrolling when set to true will enable mobile device like "touch drag" scroll of scroll containers, including momentum scrolling after the touch has ended.
|
||||
// - scrollDelta is the amount to scroll this frame on each axis in pixels.
|
||||
// - deltaTime is the time in seconds since the last "frame" (scroll update)
|
||||
void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime);
|
||||
CLAY_DLL_EXPORT void Clay_UpdateScrollContainers(bool enableDragScrolling, Clay_Vector2 scrollDelta, float deltaTime);
|
||||
// Updates the layout dimensions in response to the window or outer container being resized.
|
||||
void Clay_SetLayoutDimensions(Clay_Dimensions dimensions);
|
||||
CLAY_DLL_EXPORT void Clay_SetLayoutDimensions(Clay_Dimensions dimensions);
|
||||
// Called before starting any layout declarations.
|
||||
void Clay_BeginLayout(void);
|
||||
CLAY_DLL_EXPORT void Clay_BeginLayout(void);
|
||||
// Called when all layout declarations are finished.
|
||||
// Computes the layout and generates and returns the array of render commands to draw.
|
||||
Clay_RenderCommandArray Clay_EndLayout(void);
|
||||
CLAY_DLL_EXPORT Clay_RenderCommandArray Clay_EndLayout(void);
|
||||
// Calculates a hash ID from the given idString.
|
||||
// Generally only used for dynamic strings when CLAY_ID("stringLiteral") can't be used.
|
||||
Clay_ElementId Clay_GetElementId(Clay_String idString);
|
||||
CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementId(Clay_String idString);
|
||||
// Calculates a hash ID from the given idString and index.
|
||||
// - index is used to avoid constructing dynamic ID strings in loops.
|
||||
// Generally only used for dynamic strings when CLAY_IDI("stringLiteral", index) can't be used.
|
||||
Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index);
|
||||
CLAY_DLL_EXPORT Clay_ElementId Clay_GetElementIdWithIndex(Clay_String idString, uint32_t index);
|
||||
// Returns layout data such as the final calculated bounding box for an element with a given ID.
|
||||
// The returned Clay_ElementData contains a `found` bool that will be true if an element with the provided ID was found.
|
||||
// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings.
|
||||
Clay_ElementData Clay_GetElementData(Clay_ElementId id);
|
||||
CLAY_DLL_EXPORT Clay_ElementData Clay_GetElementData(Clay_ElementId id);
|
||||
// Returns true if the pointer position provided by Clay_SetPointerState is within the current element's bounding box.
|
||||
// Works during element declaration, e.g. CLAY({ .backgroundColor = Clay_Hovered() ? BLUE : RED });
|
||||
bool Clay_Hovered(void);
|
||||
CLAY_DLL_EXPORT bool Clay_Hovered(void);
|
||||
// Bind a callback that will be called when the pointer position provided by Clay_SetPointerState is within the current element's bounding box.
|
||||
// - onHoverFunction is a function pointer to a user defined function.
|
||||
// - userData is a pointer that will be transparently passed through when the onHoverFunction is called.
|
||||
void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData), intptr_t userData);
|
||||
CLAY_DLL_EXPORT void Clay_OnHover(void (*onHoverFunction)(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData), intptr_t userData);
|
||||
// An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box.
|
||||
// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings.
|
||||
bool Clay_PointerOver(Clay_ElementId elementId);
|
||||
CLAY_DLL_EXPORT bool Clay_PointerOver(Clay_ElementId elementId);
|
||||
// Returns data representing the state of the scrolling element with the provided ID.
|
||||
// The returned Clay_ScrollContainerData contains a `found` bool that will be true if a scroll element was found with the provided ID.
|
||||
// An imperative function that returns true if the pointer position provided by Clay_SetPointerState is within the element with the provided ID's bounding box.
|
||||
// This ID can be calculated either with CLAY_ID() for string literal IDs, or Clay_GetElementId for dynamic strings.
|
||||
Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id);
|
||||
CLAY_DLL_EXPORT Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id);
|
||||
// Binds a callback function that Clay will call to determine the dimensions of a given string slice.
|
||||
// - measureTextFunction is a user provided function that adheres to the interface Clay_Dimensions (Clay_StringSlice text, Clay_TextElementConfig *config, void *userData);
|
||||
// - userData is a pointer that will be transparently passed through when the measureTextFunction is called.
|
||||
void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData);
|
||||
CLAY_DLL_EXPORT void Clay_SetMeasureTextFunction(Clay_Dimensions (*measureTextFunction)(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData), void *userData);
|
||||
// Experimental - Used in cases where Clay needs to integrate with a system that manages its own scrolling containers externally.
|
||||
// Please reach out if you plan to use this function, as it may be subject to change.
|
||||
void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData);
|
||||
CLAY_DLL_EXPORT void Clay_SetQueryScrollOffsetFunction(Clay_Vector2 (*queryScrollOffsetFunction)(uint32_t elementId, void *userData), void *userData);
|
||||
// A bounds-checked "get" function for the Clay_RenderCommandArray returned from Clay_EndLayout().
|
||||
Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index);
|
||||
CLAY_DLL_EXPORT Clay_RenderCommand * Clay_RenderCommandArray_Get(Clay_RenderCommandArray* array, int32_t index);
|
||||
// Enables and disables Clay's internal debug tools.
|
||||
// This state is retained and does not need to be set each frame.
|
||||
void Clay_SetDebugModeEnabled(bool enabled);
|
||||
CLAY_DLL_EXPORT void Clay_SetDebugModeEnabled(bool enabled);
|
||||
// Returns true if Clay's internal debug tools are currently enabled.
|
||||
bool Clay_IsDebugModeEnabled(void);
|
||||
CLAY_DLL_EXPORT bool Clay_IsDebugModeEnabled(void);
|
||||
// Enables and disables visibility culling. By default, Clay will not generate render commands for elements whose bounding box is entirely outside the screen.
|
||||
void Clay_SetCullingEnabled(bool enabled);
|
||||
CLAY_DLL_EXPORT void Clay_SetCullingEnabled(bool enabled);
|
||||
// Returns the maximum number of UI elements supported by Clay's current configuration.
|
||||
int32_t Clay_GetMaxElementCount(void);
|
||||
CLAY_DLL_EXPORT int32_t Clay_GetMaxElementCount(void);
|
||||
// Modifies the maximum number of UI elements supported by Clay's current configuration.
|
||||
// This may require reallocating additional memory, and re-calling Clay_Initialize();
|
||||
void Clay_SetMaxElementCount(int32_t maxElementCount);
|
||||
CLAY_DLL_EXPORT void Clay_SetMaxElementCount(int32_t maxElementCount);
|
||||
// Returns the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
|
||||
int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
|
||||
CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
|
||||
// Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
|
||||
// This may require reallocating additional memory, and re-calling Clay_Initialize();
|
||||
void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
|
||||
// Resets Clay's internal text measurement cache, useful if memory to represent strings is being re-used.
|
||||
// Similar behaviour can be achieved on an individual text element level by using Clay_TextElementConfig.hashStringContents
|
||||
void Clay_ResetMeasureTextCache(void);
|
||||
CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
|
||||
// Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded.
|
||||
CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void);
|
||||
|
||||
// Internal API functions required by macros ----------------------
|
||||
|
||||
void Clay__OpenElement(void);
|
||||
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config);
|
||||
void Clay__CloseElement(void);
|
||||
Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed);
|
||||
void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig);
|
||||
Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config);
|
||||
uint32_t Clay__GetParentElementId(void);
|
||||
CLAY_DLL_EXPORT void Clay__OpenElement(void);
|
||||
CLAY_DLL_EXPORT void Clay__ConfigureOpenElement(const Clay_ElementDeclaration config);
|
||||
CLAY_DLL_EXPORT void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *config);
|
||||
CLAY_DLL_EXPORT void Clay__CloseElement(void);
|
||||
CLAY_DLL_EXPORT Clay_ElementId Clay__HashString(Clay_String key, uint32_t offset, uint32_t seed);
|
||||
CLAY_DLL_EXPORT void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig);
|
||||
CLAY_DLL_EXPORT Clay_TextElementConfig *Clay__StoreTextElementConfig(Clay_TextElementConfig config);
|
||||
CLAY_DLL_EXPORT uint32_t Clay__GetParentElementId(void);
|
||||
|
||||
extern Clay_Color Clay__debugViewHighlightColor;
|
||||
extern uint32_t Clay__debugViewWidth;
|
||||
@ -1131,6 +1154,7 @@ CLAY__ARRAY_DEFINE(Clay__MeasuredWord, Clay__MeasuredWordArray)
|
||||
typedef struct {
|
||||
Clay_Dimensions unwrappedDimensions;
|
||||
int32_t measuredWordsStartIndex;
|
||||
float minWidth;
|
||||
bool containsNewlines;
|
||||
// Hash map data
|
||||
uint32_t id;
|
||||
@ -1322,26 +1346,136 @@ Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const ui
|
||||
return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id"
|
||||
}
|
||||
|
||||
uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
|
||||
uint32_t hash = 0;
|
||||
uintptr_t pointerAsNumber = (uintptr_t)text->chars;
|
||||
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
|
||||
static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) {
|
||||
return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r));
|
||||
}
|
||||
|
||||
if (config->hashStringContents) {
|
||||
uint32_t maxLengthToHash = CLAY__MIN(text->length, 256);
|
||||
for (uint32_t i = 0; i < maxLengthToHash; i++) {
|
||||
hash += text->chars[i];
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) {
|
||||
*a = _mm_add_epi64(*a, *b);
|
||||
*b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a);
|
||||
}
|
||||
|
||||
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
|
||||
// Pinched these constants from the BLAKE implementation
|
||||
__m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL);
|
||||
__m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL);
|
||||
__m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL);
|
||||
__m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL);
|
||||
|
||||
uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs
|
||||
|
||||
while (length > 0) {
|
||||
__m128i msg;
|
||||
if (length >= 16) {
|
||||
msg = _mm_loadu_si128((const __m128i*)data);
|
||||
data += 16;
|
||||
length -= 16;
|
||||
}
|
||||
} else {
|
||||
hash += pointerAsNumber;
|
||||
else {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
overflowBuffer[i] = data[i];
|
||||
}
|
||||
msg = _mm_loadu_si128((const __m128i*)overflowBuffer);
|
||||
length = 0;
|
||||
}
|
||||
|
||||
v0 = _mm_xor_si128(v0, msg);
|
||||
Clay__SIMDARXMix(&v0, &v1);
|
||||
Clay__SIMDARXMix(&v2, &v3);
|
||||
|
||||
v0 = _mm_add_epi64(v0, v2);
|
||||
v1 = _mm_add_epi64(v1, v3);
|
||||
}
|
||||
|
||||
Clay__SIMDARXMix(&v0, &v1);
|
||||
Clay__SIMDARXMix(&v2, &v3);
|
||||
v0 = _mm_add_epi64(v0, v2);
|
||||
v1 = _mm_add_epi64(v1, v3);
|
||||
|
||||
uint64_t result[2];
|
||||
_mm_storeu_si128((__m128i*)result, v0);
|
||||
|
||||
return result[0] ^ result[1];
|
||||
}
|
||||
#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__)
|
||||
static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) {
|
||||
*a = vaddq_u64(*a, *b);
|
||||
*b = veorq_u64(vorrq_u64(vshlq_n_u64(*b, 17), vshrq_n_u64(*b, 64 - 17)), *a);
|
||||
}
|
||||
|
||||
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
|
||||
// Pinched these constants from the BLAKE implementation
|
||||
uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL);
|
||||
uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL);
|
||||
uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL);
|
||||
uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL);
|
||||
|
||||
uint8_t overflowBuffer[8] = { 0 };
|
||||
|
||||
while (length > 0) {
|
||||
uint64x2_t msg;
|
||||
if (length > 16) {
|
||||
msg = vld1q_u64((const uint64_t*)data);
|
||||
data += 16;
|
||||
length -= 16;
|
||||
}
|
||||
else if (length > 8) {
|
||||
msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0));
|
||||
data += 8;
|
||||
length -= 8;
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
overflowBuffer[i] = data[i];
|
||||
}
|
||||
uint8x8_t lower = vld1_u8(overflowBuffer);
|
||||
msg = vreinterpretq_u64_u8(vcombine_u8(lower, vdup_n_u8(0)));
|
||||
length = 0;
|
||||
}
|
||||
v0 = veorq_u64(v0, msg);
|
||||
Clay__SIMDARXMix(&v0, &v1);
|
||||
Clay__SIMDARXMix(&v2, &v3);
|
||||
|
||||
v0 = vaddq_u64(v0, v2);
|
||||
v1 = vaddq_u64(v1, v3);
|
||||
}
|
||||
|
||||
Clay__SIMDARXMix(&v0, &v1);
|
||||
Clay__SIMDARXMix(&v2, &v3);
|
||||
v0 = vaddq_u64(v0, v2);
|
||||
v1 = vaddq_u64(v1, v3);
|
||||
|
||||
uint64_t result[2];
|
||||
vst1q_u64(result, v0);
|
||||
|
||||
return result[0] ^ result[1];
|
||||
}
|
||||
#else
|
||||
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
|
||||
uint64_t hash = 0;
|
||||
|
||||
for (int32_t i = 0; i < length; i++) {
|
||||
hash += data[i];
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
#endif
|
||||
|
||||
hash += text->length;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
|
||||
uint32_t hash = 0;
|
||||
if (text->isStaticallyAllocated) {
|
||||
hash += (uintptr_t)text->chars;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
hash += text->length;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
} else {
|
||||
hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX;
|
||||
}
|
||||
|
||||
hash += config->fontId;
|
||||
hash += (hash << 10);
|
||||
@ -1351,18 +1485,10 @@ uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *con
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
|
||||
hash += config->lineHeight;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
|
||||
hash += config->letterSpacing;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
|
||||
hash += config->wrapMode;
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 11);
|
||||
hash += (hash << 15);
|
||||
@ -1397,7 +1523,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
return &Clay__MeasureTextCacheItem_DEFAULT;
|
||||
}
|
||||
#endif
|
||||
uint32_t id = Clay__HashTextWithConfig(text, config);
|
||||
uint32_t id = Clay__HashStringContentsWithConfig(text, config);
|
||||
uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32);
|
||||
int32_t elementIndexPrevious = 0;
|
||||
int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket];
|
||||
@ -1443,7 +1569,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
measured = Clay__MeasureTextCacheItemArray_Get(&context->measureTextHashMapInternal, newItemIndex);
|
||||
} else {
|
||||
if (context->measureTextHashMapInternal.length == context->measureTextHashMapInternal.capacity - 1) {
|
||||
if (context->booleanWarnings.maxTextMeasureCacheExceeded) {
|
||||
if (!context->booleanWarnings.maxTextMeasureCacheExceeded) {
|
||||
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
|
||||
.errorType = CLAY_ERROR_TYPE_ELEMENTS_CAPACITY_EXCEEDED,
|
||||
.errorText = CLAY_STRING("Clay ran out of capacity while attempting to measure text elements. Try using Clay_SetMaxElementCount() with a higher value."),
|
||||
@ -1479,6 +1605,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
if (current == ' ' || current == '\n') {
|
||||
int32_t length = end - start;
|
||||
Clay_Dimensions dimensions = Clay__MeasureText(CLAY__INIT(Clay_StringSlice) { .length = length, .chars = &text->chars[start], .baseChars = text->chars }, config, context->measureTextUserData);
|
||||
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
|
||||
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
|
||||
if (current == ' ') {
|
||||
dimensions.width += spaceWidth;
|
||||
@ -1504,6 +1631,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
|
||||
Clay__AddMeasuredWord(CLAY__INIT(Clay__MeasuredWord) { .startOffset = start, .length = end - start, .width = dimensions.width, .next = -1 }, previousWord);
|
||||
lineWidth += dimensions.width;
|
||||
measuredHeight = CLAY__MAX(measuredHeight, dimensions.height);
|
||||
measured->minWidth = CLAY__MAX(dimensions.width, measured->minWidth);
|
||||
}
|
||||
measuredWidth = CLAY__MAX(lineWidth, measuredWidth);
|
||||
|
||||
@ -1538,9 +1666,12 @@ Clay_LayoutElementHashMapItem* Clay__AddHashMapItem(Clay_ElementId elementId, Cl
|
||||
item.nextIndex = hashItem->nextIndex;
|
||||
if (hashItem->generation <= context->generation) { // First collision - assume this is the "same" element
|
||||
hashItem->elementId = elementId; // Make sure to copy this across. If the stringId reference has changed, we should update the hash item to use the new one.
|
||||
hashItem->idAlias = idAlias;
|
||||
hashItem->generation = context->generation + 1;
|
||||
hashItem->layoutElement = layoutElement;
|
||||
hashItem->debugData->collision = false;
|
||||
hashItem->onHoverFunction = NULL;
|
||||
hashItem->hoverFunctionUserData = 0;
|
||||
} else { // Multiple collisions this frame - two elements have the same ID
|
||||
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
|
||||
.errorType = CLAY_ERROR_TYPE_DUPLICATE_ID,
|
||||
@ -1598,6 +1729,25 @@ bool Clay__ElementHasConfig(Clay_LayoutElement *layoutElement, Clay__ElementConf
|
||||
return false;
|
||||
}
|
||||
|
||||
void Clay__UpdateAspectRatioBox(Clay_LayoutElement *layoutElement) {
|
||||
for (int32_t j = 0; j < layoutElement->elementConfigs.length; j++) {
|
||||
Clay_ElementConfig *config = Clay__ElementConfigArraySlice_Get(&layoutElement->elementConfigs, j);
|
||||
if (config->type == CLAY__ELEMENT_CONFIG_TYPE_IMAGE) {
|
||||
Clay_ImageElementConfig *imageConfig = config->config.imageElementConfig;
|
||||
if (imageConfig->sourceDimensions.width == 0 || imageConfig->sourceDimensions.height == 0) {
|
||||
break;
|
||||
}
|
||||
float aspect = imageConfig->sourceDimensions.width / imageConfig->sourceDimensions.height;
|
||||
if (layoutElement->dimensions.width == 0 && layoutElement->dimensions.height != 0) {
|
||||
layoutElement->dimensions.width = layoutElement->dimensions.height * aspect;
|
||||
} else if (layoutElement->dimensions.width != 0 && layoutElement->dimensions.height == 0) {
|
||||
layoutElement->dimensions.height = layoutElement->dimensions.height * (1 / aspect);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Clay__CloseElement(void) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
if (context->booleanWarnings.maxElementsExceeded) {
|
||||
@ -1614,24 +1764,30 @@ void Clay__CloseElement(void) {
|
||||
elementHasScrollVertical = config->config.scrollElementConfig->vertical;
|
||||
context->openClipElementStack.length--;
|
||||
break;
|
||||
} else if (config->type == CLAY__ELEMENT_CONFIG_TYPE_FLOATING) {
|
||||
context->openClipElementStack.length--;
|
||||
}
|
||||
}
|
||||
|
||||
float leftRightPadding = (float)(layoutConfig->padding.left + layoutConfig->padding.right);
|
||||
float topBottomPadding = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom);
|
||||
|
||||
// Attach children to the current open element
|
||||
openLayoutElement->childrenOrTextContent.children.elements = &context->layoutElementChildren.internalArray[context->layoutElementChildren.length];
|
||||
if (layoutConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) {
|
||||
openLayoutElement->dimensions.width = (float)(layoutConfig->padding.left + layoutConfig->padding.right);
|
||||
openLayoutElement->dimensions.width = leftRightPadding;
|
||||
openLayoutElement->minDimensions.width = leftRightPadding;
|
||||
for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) {
|
||||
int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i);
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
|
||||
openLayoutElement->dimensions.width += child->dimensions.width;
|
||||
openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom);
|
||||
openLayoutElement->dimensions.height = CLAY__MAX(openLayoutElement->dimensions.height, child->dimensions.height + topBottomPadding);
|
||||
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
|
||||
if (!elementHasScrollHorizontal) {
|
||||
openLayoutElement->minDimensions.width += child->minDimensions.width;
|
||||
}
|
||||
if (!elementHasScrollVertical) {
|
||||
openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + layoutConfig->padding.top + layoutConfig->padding.bottom);
|
||||
openLayoutElement->minDimensions.height = CLAY__MAX(openLayoutElement->minDimensions.height, child->minDimensions.height + topBottomPadding);
|
||||
}
|
||||
Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex);
|
||||
}
|
||||
@ -1640,18 +1796,19 @@ void Clay__CloseElement(void) {
|
||||
openLayoutElement->minDimensions.width += childGap;
|
||||
}
|
||||
else if (layoutConfig->layoutDirection == CLAY_TOP_TO_BOTTOM) {
|
||||
openLayoutElement->dimensions.height = (float)(layoutConfig->padding.top + layoutConfig->padding.bottom);
|
||||
openLayoutElement->dimensions.height = topBottomPadding;
|
||||
openLayoutElement->minDimensions.height = topBottomPadding;
|
||||
for (int32_t i = 0; i < openLayoutElement->childrenOrTextContent.children.length; i++) {
|
||||
int32_t childIndex = Clay__int32_tArray_GetValue(&context->layoutElementChildrenBuffer, (int)context->layoutElementChildrenBuffer.length - openLayoutElement->childrenOrTextContent.children.length + i);
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
|
||||
openLayoutElement->dimensions.height += child->dimensions.height;
|
||||
openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + layoutConfig->padding.left + layoutConfig->padding.right);
|
||||
openLayoutElement->dimensions.width = CLAY__MAX(openLayoutElement->dimensions.width, child->dimensions.width + leftRightPadding);
|
||||
// Minimum size of child elements doesn't matter to scroll containers as they can shrink and hide their contents
|
||||
if (!elementHasScrollVertical) {
|
||||
openLayoutElement->minDimensions.height += child->minDimensions.height;
|
||||
}
|
||||
if (!elementHasScrollHorizontal) {
|
||||
openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + layoutConfig->padding.left + layoutConfig->padding.right);
|
||||
openLayoutElement->minDimensions.width = CLAY__MAX(openLayoutElement->minDimensions.width, child->minDimensions.width + leftRightPadding);
|
||||
}
|
||||
Clay__int32_tArray_Add(&context->layoutElementChildren, childIndex);
|
||||
}
|
||||
@ -1684,6 +1841,8 @@ void Clay__CloseElement(void) {
|
||||
openLayoutElement->dimensions.height = 0;
|
||||
}
|
||||
|
||||
Clay__UpdateAspectRatioBox(openLayoutElement);
|
||||
|
||||
bool elementIsFloating = Clay__ElementHasConfig(openLayoutElement, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
|
||||
|
||||
// Close the currently open element
|
||||
@ -1730,7 +1889,7 @@ bool Clay__MemCmp(const char *s1, const char *s2, int32_t length);
|
||||
uint8x16_t v2 = vld1q_u8((const uint8_t *)s2);
|
||||
|
||||
// Compare vectors
|
||||
if (vminvq_u32(vceqq_u8(v1, v2)) != 0xFFFFFFFF) { // If there's a difference
|
||||
if (vminvq_u32(vreinterpretq_u32_u8(vceqq_u8(v1, v2))) != 0xFFFFFFFF) { // If there's a difference
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1801,7 +1960,7 @@ void Clay__OpenTextElement(Clay_String text, Clay_TextElementConfig *textConfig)
|
||||
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
|
||||
Clay_Dimensions textDimensions = { .width = textMeasured->unwrappedDimensions.width, .height = textConfig->lineHeight > 0 ? (float)textConfig->lineHeight : textMeasured->unwrappedDimensions.height };
|
||||
textElement->dimensions = textDimensions;
|
||||
textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->unwrappedDimensions.height, .height = textDimensions.height }; // TODO not sure this is the best way to decide min width for text
|
||||
textElement->minDimensions = CLAY__INIT(Clay_Dimensions) { .width = textMeasured->minWidth, .height = textDimensions.height };
|
||||
textElement->childrenOrTextContent.textElementData = Clay__TextElementDataArray_Add(&context->textElementData, CLAY__INIT(Clay__TextElementData) { .text = text, .preferredDimensions = textMeasured->unwrappedDimensions, .elementIndex = context->layoutElements.length - 1 });
|
||||
textElement->elementConfigs = CLAY__INIT(Clay__ElementConfigArraySlice) {
|
||||
.length = 1,
|
||||
@ -1820,62 +1979,62 @@ Clay_ElementId Clay__AttachId(Clay_ElementId elementId) {
|
||||
uint32_t idAlias = openLayoutElement->id;
|
||||
openLayoutElement->id = elementId.id;
|
||||
Clay__AddHashMapItem(elementId, openLayoutElement, idAlias);
|
||||
Clay__StringArray_Add(&context->layoutElementIdStrings, elementId.stringId);
|
||||
Clay__StringArray_Set(&context->layoutElementIdStrings, context->layoutElements.length - 1, elementId.stringId);
|
||||
return elementId;
|
||||
}
|
||||
|
||||
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
void Clay__ConfigureOpenElementPtr(const Clay_ElementDeclaration *declaration) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
Clay_LayoutElement *openLayoutElement = Clay__GetOpenLayoutElement();
|
||||
openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration.layout);
|
||||
if ((declaration.layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration.layout.sizing.width.size.percent > 1) || (declaration.layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration.layout.sizing.height.size.percent > 1)) {
|
||||
openLayoutElement->layoutConfig = Clay__StoreLayoutConfig(declaration->layout);
|
||||
if ((declaration->layout.sizing.width.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.width.size.percent > 1) || (declaration->layout.sizing.height.type == CLAY__SIZING_TYPE_PERCENT && declaration->layout.sizing.height.size.percent > 1)) {
|
||||
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
|
||||
.errorType = CLAY_ERROR_TYPE_PERCENTAGE_OVER_1,
|
||||
.errorText = CLAY_STRING("An element was configured with CLAY_SIZING_PERCENT, but the provided percentage value was over 1.0. Clay expects a value between 0 and 1, i.e. 20% is 0.2."),
|
||||
.userData = context->errorHandler.userData });
|
||||
}
|
||||
|
||||
Clay_ElementId openLayoutElementId = declaration.id;
|
||||
Clay_ElementId openLayoutElementId = declaration->id;
|
||||
|
||||
openLayoutElement->elementConfigs.internalArray = &context->elementConfigs.internalArray[context->elementConfigs.length];
|
||||
Clay_SharedElementConfig *sharedConfig = NULL;
|
||||
if (declaration.backgroundColor.a > 0) {
|
||||
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration.backgroundColor });
|
||||
if (declaration->backgroundColor.a > 0) {
|
||||
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .backgroundColor = declaration->backgroundColor });
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
|
||||
}
|
||||
if (!Clay__MemCmp((char *)(&declaration.cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) {
|
||||
if (!Clay__MemCmp((char *)(&declaration->cornerRadius), (char *)(&Clay__CornerRadius_DEFAULT), sizeof(Clay_CornerRadius))) {
|
||||
if (sharedConfig) {
|
||||
sharedConfig->cornerRadius = declaration.cornerRadius;
|
||||
sharedConfig->cornerRadius = declaration->cornerRadius;
|
||||
} else {
|
||||
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration.cornerRadius });
|
||||
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .cornerRadius = declaration->cornerRadius });
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
|
||||
}
|
||||
}
|
||||
if (declaration.userData != 0) {
|
||||
if (declaration->userData != 0) {
|
||||
if (sharedConfig) {
|
||||
sharedConfig->userData = declaration.userData;
|
||||
sharedConfig->userData = declaration->userData;
|
||||
} else {
|
||||
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration.userData });
|
||||
sharedConfig = Clay__StoreSharedElementConfig(CLAY__INIT(Clay_SharedElementConfig) { .userData = declaration->userData });
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .sharedElementConfig = sharedConfig }, CLAY__ELEMENT_CONFIG_TYPE_SHARED);
|
||||
}
|
||||
}
|
||||
if (declaration.image.imageData) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration.image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE);
|
||||
if (declaration->image.imageData) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .imageElementConfig = Clay__StoreImageElementConfig(declaration->image) }, CLAY__ELEMENT_CONFIG_TYPE_IMAGE);
|
||||
Clay__int32_tArray_Add(&context->imageElementPointers, context->layoutElements.length - 1);
|
||||
}
|
||||
if (declaration.floating.attachTo != CLAY_ATTACH_TO_NONE) {
|
||||
Clay_FloatingElementConfig floatingConfig = declaration.floating;
|
||||
if (declaration->floating.attachTo != CLAY_ATTACH_TO_NONE) {
|
||||
Clay_FloatingElementConfig floatingConfig = declaration->floating;
|
||||
// This looks dodgy but because of the auto generated root element the depth of the tree will always be at least 2 here
|
||||
Clay_LayoutElement *hierarchicalParent = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 2));
|
||||
if (hierarchicalParent) {
|
||||
uint32_t clipElementId = 0;
|
||||
if (declaration.floating.attachTo == CLAY_ATTACH_TO_PARENT) {
|
||||
if (declaration->floating.attachTo == CLAY_ATTACH_TO_PARENT) {
|
||||
// Attach to the element's direct hierarchical parent
|
||||
floatingConfig.parentId = hierarchicalParent->id;
|
||||
if (context->openClipElementStack.length > 0) {
|
||||
clipElementId = Clay__int32_tArray_GetValue(&context->openClipElementStack, (int)context->openClipElementStack.length - 1);
|
||||
}
|
||||
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) {
|
||||
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ELEMENT_WITH_ID) {
|
||||
Clay_LayoutElementHashMapItem *parentItem = Clay__GetHashMapItem(floatingConfig.parentId);
|
||||
if (!parentItem) {
|
||||
context->errorHandler.errorHandlerFunction(CLAY__INIT(Clay_ErrorData) {
|
||||
@ -1883,25 +2042,28 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
.errorText = CLAY_STRING("A floating element was declared with a parentId, but no element with that ID was found."),
|
||||
.userData = context->errorHandler.userData });
|
||||
} else {
|
||||
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, parentItem->layoutElement - context->layoutElements.internalArray);
|
||||
clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(parentItem->layoutElement - context->layoutElements.internalArray));
|
||||
}
|
||||
} else if (declaration.floating.attachTo == CLAY_ATTACH_TO_ROOT) {
|
||||
} else if (declaration->floating.attachTo == CLAY_ATTACH_TO_ROOT) {
|
||||
floatingConfig.parentId = Clay__HashString(CLAY_STRING("Clay__RootContainer"), 0, 0).id;
|
||||
}
|
||||
if (!openLayoutElementId.id) {
|
||||
openLayoutElementId = Clay__HashString(CLAY_STRING("Clay__FloatingContainer"), context->layoutElementTreeRoots.length, 0);
|
||||
}
|
||||
int32_t currentElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1);
|
||||
Clay__int32_tArray_Set(&context->layoutElementClipElementIds, currentElementIndex, clipElementId);
|
||||
Clay__int32_tArray_Add(&context->openClipElementStack, clipElementId);
|
||||
Clay__LayoutElementTreeRootArray_Add(&context->layoutElementTreeRoots, CLAY__INIT(Clay__LayoutElementTreeRoot) {
|
||||
.layoutElementIndex = Clay__int32_tArray_GetValue(&context->openLayoutElementStack, context->openLayoutElementStack.length - 1),
|
||||
.parentId = floatingConfig.parentId,
|
||||
.clipElementId = clipElementId,
|
||||
.zIndex = floatingConfig.zIndex,
|
||||
});
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(declaration.floating) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .floatingElementConfig = Clay__StoreFloatingElementConfig(floatingConfig) }, CLAY__ELEMENT_CONFIG_TYPE_FLOATING);
|
||||
}
|
||||
}
|
||||
if (declaration.custom.customData) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration.custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
|
||||
if (declaration->custom.customData) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .customElementConfig = Clay__StoreCustomElementConfig(declaration->custom) }, CLAY__ELEMENT_CONFIG_TYPE_CUSTOM);
|
||||
}
|
||||
|
||||
if (openLayoutElementId.id != 0) {
|
||||
@ -1910,8 +2072,8 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
openLayoutElementId = Clay__GenerateIdForAnonymousElement(openLayoutElement);
|
||||
}
|
||||
|
||||
if (declaration.scroll.horizontal | declaration.scroll.vertical) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration.scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
|
||||
if (declaration->scroll.horizontal | declaration->scroll.vertical) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .scrollElementConfig = Clay__StoreScrollElementConfig(declaration->scroll) }, CLAY__ELEMENT_CONFIG_TYPE_SCROLL);
|
||||
Clay__int32_tArray_Add(&context->openClipElementStack, (int)openLayoutElement->id);
|
||||
// Retrieve or create cached data to track scroll position across frames
|
||||
Clay__ScrollContainerDataInternal *scrollOffset = CLAY__NULL;
|
||||
@ -1930,11 +2092,15 @@ void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
scrollOffset->scrollPosition = Clay__QueryScrollOffset(scrollOffset->elementId, context->queryScrollOffsetUserData);
|
||||
}
|
||||
}
|
||||
if (!Clay__MemCmp((char *)(&declaration.border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration.border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER);
|
||||
if (!Clay__MemCmp((char *)(&declaration->border.width), (char *)(&Clay__BorderWidth_DEFAULT), sizeof(Clay_BorderWidth))) {
|
||||
Clay__AttachElementConfig(CLAY__INIT(Clay_ElementConfigUnion) { .borderElementConfig = Clay__StoreBorderElementConfig(declaration->border) }, CLAY__ELEMENT_CONFIG_TYPE_BORDER);
|
||||
}
|
||||
}
|
||||
|
||||
void Clay__ConfigureOpenElement(const Clay_ElementDeclaration declaration) {
|
||||
Clay__ConfigureOpenElementPtr(&declaration);
|
||||
}
|
||||
|
||||
void Clay__InitializeEphemeralMemory(Clay_Context* context) {
|
||||
int32_t maxElementCount = context->maxElementCount;
|
||||
// Ephemeral Memory - reset every frame
|
||||
@ -1991,54 +2157,11 @@ void Clay__InitializePersistentMemory(Clay_Context* context) {
|
||||
context->arenaResetOffset = arena->nextAllocation;
|
||||
}
|
||||
|
||||
void Clay__CompressChildrenAlongAxis(bool xAxis, float totalSizeToDistribute, Clay__int32_tArray resizableContainerBuffer) {
|
||||
Clay_Context* context = Clay_GetCurrentContext();
|
||||
Clay__int32_tArray largestContainers = context->openClipElementStack;
|
||||
const float CLAY__EPSILON = 0.01;
|
||||
|
||||
while (totalSizeToDistribute > 0.1) {
|
||||
largestContainers.length = 0;
|
||||
float largestSize = 0;
|
||||
float targetSize = 0;
|
||||
for (int32_t i = 0; i < resizableContainerBuffer.length; ++i) {
|
||||
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
|
||||
float childSize = xAxis ? childElement->dimensions.width : childElement->dimensions.height;
|
||||
if ((childSize - largestSize) < 0.1 && (childSize - largestSize) > -0.1) {
|
||||
Clay__int32_tArray_Add(&largestContainers, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
|
||||
} else if (childSize > largestSize) {
|
||||
targetSize = largestSize;
|
||||
largestSize = childSize;
|
||||
largestContainers.length = 0;
|
||||
Clay__int32_tArray_Add(&largestContainers, Clay__int32_tArray_GetValue(&resizableContainerBuffer, i));
|
||||
}
|
||||
else if (childSize > targetSize) {
|
||||
targetSize = childSize;
|
||||
}
|
||||
}
|
||||
|
||||
if (largestContainers.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
targetSize = CLAY__MAX(targetSize, (largestSize * largestContainers.length) - totalSizeToDistribute) / largestContainers.length;
|
||||
|
||||
for (int32_t childOffset = 0; childOffset < largestContainers.length; childOffset++) {
|
||||
int32_t childIndex = Clay__int32_tArray_GetValue(&largestContainers, childOffset);
|
||||
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, childIndex);
|
||||
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
|
||||
float childMinSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
|
||||
float oldChildSize = *childSize;
|
||||
*childSize = CLAY__MAX(childMinSize, targetSize);
|
||||
totalSizeToDistribute -= (oldChildSize - *childSize);
|
||||
if (*childSize == childMinSize) {
|
||||
for (int32_t i = 0; i < resizableContainerBuffer.length; i++) {
|
||||
if (Clay__int32_tArray_GetValue(&resizableContainerBuffer, i) == childIndex) {
|
||||
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool Clay__FloatEqual(float left, float right) {
|
||||
float subtracted = left - right;
|
||||
return subtracted < CLAY__EPSILON && subtracted > -CLAY__EPSILON;
|
||||
}
|
||||
|
||||
void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
@ -2076,7 +2199,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
int32_t growContainerCount = 0;
|
||||
float parentSize = xAxis ? parent->dimensions.width : parent->dimensions.height;
|
||||
float parentPadding = (float)(xAxis ? (parent->layoutConfig->padding.left + parent->layoutConfig->padding.right) : (parent->layoutConfig->padding.top + parent->layoutConfig->padding.bottom));
|
||||
float innerContentSize = 0, growContainerContentSize = 0, totalPaddingAndChildGaps = parentPadding;
|
||||
float innerContentSize = 0, totalPaddingAndChildGaps = parentPadding;
|
||||
bool sizingAlongAxis = (xAxis && parentStyleConfig->layoutDirection == CLAY_LEFT_TO_RIGHT) || (!xAxis && parentStyleConfig->layoutDirection == CLAY_TOP_TO_BOTTOM);
|
||||
resizableContainerBuffer.length = 0;
|
||||
float parentChildGap = parentStyleConfig->childGap;
|
||||
@ -2102,7 +2225,6 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
if (sizingAlongAxis) {
|
||||
innerContentSize += (childSizing.type == CLAY__SIZING_TYPE_PERCENT ? 0 : childSize);
|
||||
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
|
||||
growContainerContentSize += childSize;
|
||||
growContainerCount++;
|
||||
}
|
||||
if (childOffset > 0) {
|
||||
@ -2125,6 +2247,7 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
if (sizingAlongAxis) {
|
||||
innerContentSize += *childSize;
|
||||
}
|
||||
Clay__UpdateAspectRatioBox(childElement);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2140,25 +2263,83 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
}
|
||||
}
|
||||
// Scrolling containers preferentially compress before others
|
||||
Clay__CompressChildrenAlongAxis(xAxis, -sizeToDistribute, resizableContainerBuffer);
|
||||
while (sizeToDistribute < -CLAY__EPSILON && resizableContainerBuffer.length > 0) {
|
||||
float largest = 0;
|
||||
float secondLargest = 0;
|
||||
float widthToAdd = sizeToDistribute;
|
||||
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
|
||||
float childSize = xAxis ? child->dimensions.width : child->dimensions.height;
|
||||
if (Clay__FloatEqual(childSize, largest)) { continue; }
|
||||
if (childSize > largest) {
|
||||
secondLargest = largest;
|
||||
largest = childSize;
|
||||
}
|
||||
if (childSize < largest) {
|
||||
secondLargest = CLAY__MAX(secondLargest, childSize);
|
||||
widthToAdd = secondLargest - largest;
|
||||
}
|
||||
}
|
||||
|
||||
widthToAdd = CLAY__MAX(widthToAdd, sizeToDistribute / resizableContainerBuffer.length);
|
||||
|
||||
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
|
||||
float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height;
|
||||
float minSize = xAxis ? child->minDimensions.width : child->minDimensions.height;
|
||||
float previousWidth = *childSize;
|
||||
if (Clay__FloatEqual(*childSize, largest)) {
|
||||
*childSize += widthToAdd;
|
||||
if (*childSize <= minSize) {
|
||||
*childSize = minSize;
|
||||
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
|
||||
}
|
||||
sizeToDistribute -= (*childSize - previousWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The content is too small, allow SIZING_GROW containers to expand
|
||||
} else if (sizeToDistribute > 0 && growContainerCount > 0) {
|
||||
float targetSize = (sizeToDistribute + growContainerContentSize) / (float)growContainerCount;
|
||||
for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) {
|
||||
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset));
|
||||
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
|
||||
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
|
||||
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
|
||||
float *minSize = xAxis ? &childElement->minDimensions.width : &childElement->minDimensions.height;
|
||||
if (targetSize < *minSize) {
|
||||
growContainerContentSize -= *minSize;
|
||||
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childOffset);
|
||||
growContainerCount--;
|
||||
targetSize = (sizeToDistribute + growContainerContentSize) / (float)growContainerCount;
|
||||
childOffset = -1;
|
||||
continue;
|
||||
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
|
||||
Clay__SizingType childSizing = xAxis ? child->layoutConfig->sizing.width.type : child->layoutConfig->sizing.height.type;
|
||||
if (childSizing != CLAY__SIZING_TYPE_GROW) {
|
||||
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
|
||||
}
|
||||
}
|
||||
while (sizeToDistribute > CLAY__EPSILON && resizableContainerBuffer.length > 0) {
|
||||
float smallest = CLAY__MAXFLOAT;
|
||||
float secondSmallest = CLAY__MAXFLOAT;
|
||||
float widthToAdd = sizeToDistribute;
|
||||
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
|
||||
float childSize = xAxis ? child->dimensions.width : child->dimensions.height;
|
||||
if (Clay__FloatEqual(childSize, smallest)) { continue; }
|
||||
if (childSize < smallest) {
|
||||
secondSmallest = smallest;
|
||||
smallest = childSize;
|
||||
}
|
||||
if (childSize > smallest) {
|
||||
secondSmallest = CLAY__MIN(secondSmallest, childSize);
|
||||
widthToAdd = secondSmallest - smallest;
|
||||
}
|
||||
}
|
||||
|
||||
widthToAdd = CLAY__MIN(widthToAdd, sizeToDistribute / resizableContainerBuffer.length);
|
||||
|
||||
for (int childIndex = 0; childIndex < resizableContainerBuffer.length; childIndex++) {
|
||||
Clay_LayoutElement *child = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childIndex));
|
||||
float *childSize = xAxis ? &child->dimensions.width : &child->dimensions.height;
|
||||
float maxSize = xAxis ? child->layoutConfig->sizing.width.size.minMax.max : child->layoutConfig->sizing.height.size.minMax.max;
|
||||
float previousWidth = *childSize;
|
||||
if (Clay__FloatEqual(*childSize, smallest)) {
|
||||
*childSize += widthToAdd;
|
||||
if (*childSize >= maxSize) {
|
||||
*childSize = maxSize;
|
||||
Clay__int32_tArray_RemoveSwapback(&resizableContainerBuffer, childIndex--);
|
||||
}
|
||||
sizeToDistribute -= (*childSize - previousWidth);
|
||||
}
|
||||
*childSize = targetSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2167,25 +2348,25 @@ void Clay__SizeContainersAlongAxis(bool xAxis) {
|
||||
for (int32_t childOffset = 0; childOffset < resizableContainerBuffer.length; childOffset++) {
|
||||
Clay_LayoutElement *childElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&resizableContainerBuffer, childOffset));
|
||||
Clay_SizingAxis childSizing = xAxis ? childElement->layoutConfig->sizing.width : childElement->layoutConfig->sizing.height;
|
||||
float minSize = xAxis ? childElement->minDimensions.width : childElement->minDimensions.height;
|
||||
float *childSize = xAxis ? &childElement->dimensions.width : &childElement->dimensions.height;
|
||||
|
||||
if (!xAxis && Clay__ElementHasConfig(childElement, CLAY__ELEMENT_CONFIG_TYPE_IMAGE)) {
|
||||
continue; // Currently we don't support resizing aspect ratio images on the Y axis because it would break the ratio
|
||||
}
|
||||
|
||||
// If we're laying out the children of a scroll panel, grow containers expand to the height of the inner content, not the outer container
|
||||
float maxSize = parentSize - parentPadding;
|
||||
// If we're laying out the children of a scroll panel, grow containers expand to the size of the inner content, not the outer container
|
||||
if (Clay__ElementHasConfig(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL)) {
|
||||
Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(parent, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig;
|
||||
if (((xAxis && scrollElementConfig->horizontal) || (!xAxis && scrollElementConfig->vertical))) {
|
||||
maxSize = CLAY__MAX(maxSize, innerContentSize);
|
||||
}
|
||||
}
|
||||
if (childSizing.type == CLAY__SIZING_TYPE_FIT) {
|
||||
*childSize = CLAY__MAX(childSizing.size.minMax.min, CLAY__MIN(*childSize, maxSize));
|
||||
} else if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
|
||||
if (childSizing.type == CLAY__SIZING_TYPE_GROW) {
|
||||
*childSize = CLAY__MIN(maxSize, childSizing.size.minMax.max);
|
||||
}
|
||||
*childSize = CLAY__MAX(minSize, CLAY__MIN(*childSize, maxSize));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2638,7 +2819,7 @@ void Clay__CalculateFinalLayout(void) {
|
||||
.letterSpacing = textElementConfig->letterSpacing,
|
||||
.lineHeight = textElementConfig->lineHeight,
|
||||
}},
|
||||
.userData = sharedConfig->userData,
|
||||
.userData = textElementConfig->userData,
|
||||
.id = Clay__HashNumber(lineIndex, currentElement->id).id,
|
||||
.zIndex = root->zIndex,
|
||||
.commandType = CLAY_RENDER_COMMAND_TYPE_TEXT,
|
||||
@ -3567,17 +3748,17 @@ uint32_t Clay_MinMemorySize(void) {
|
||||
Clay_Context* currentContext = Clay_GetCurrentContext();
|
||||
if (currentContext) {
|
||||
fakeContext.maxElementCount = currentContext->maxElementCount;
|
||||
fakeContext.maxMeasureTextCacheWordCount = currentContext->maxElementCount;
|
||||
fakeContext.maxMeasureTextCacheWordCount = currentContext->maxMeasureTextCacheWordCount;
|
||||
}
|
||||
// Reserve space in the arena for the context, important for calculating min memory size correctly
|
||||
Clay__Context_Allocate_Arena(&fakeContext.internalArena);
|
||||
Clay__InitializePersistentMemory(&fakeContext);
|
||||
Clay__InitializeEphemeralMemory(&fakeContext);
|
||||
return fakeContext.internalArena.nextAllocation + 128;
|
||||
return (uint32_t)fakeContext.internalArena.nextAllocation + 128;
|
||||
}
|
||||
|
||||
CLAY_WASM_EXPORT("Clay_CreateArenaWithCapacityAndMemory")
|
||||
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(uint32_t capacity, void *memory) {
|
||||
Clay_Arena Clay_CreateArenaWithCapacityAndMemory(size_t capacity, void *memory) {
|
||||
Clay_Arena arena = {
|
||||
.capacity = capacity,
|
||||
.memory = (char *)memory
|
||||
@ -3626,11 +3807,13 @@ void Clay_SetPointerState(Clay_Vector2 position, bool isPointerDown) {
|
||||
context->treeNodeVisited.internalArray[dfsBuffer.length - 1] = true;
|
||||
Clay_LayoutElement *currentElement = Clay_LayoutElementArray_Get(&context->layoutElements, Clay__int32_tArray_GetValue(&dfsBuffer, (int)dfsBuffer.length - 1));
|
||||
Clay_LayoutElementHashMapItem *mapItem = Clay__GetHashMapItem(currentElement->id); // TODO think of a way around this, maybe the fact that it's essentially a binary tree limits the cost, but the worst case is not great
|
||||
Clay_BoundingBox elementBox = mapItem->boundingBox;
|
||||
elementBox.x -= root->pointerOffset.x;
|
||||
elementBox.y -= root->pointerOffset.y;
|
||||
int32_t clipElementId = Clay__int32_tArray_GetValue(&context->layoutElementClipElementIds, (int32_t)(currentElement - context->layoutElements.internalArray));
|
||||
Clay_LayoutElementHashMapItem *clipItem = Clay__GetHashMapItem(clipElementId);
|
||||
if (mapItem) {
|
||||
if ((Clay__PointIsInsideRect(position, elementBox))) {
|
||||
Clay_BoundingBox elementBox = mapItem->boundingBox;
|
||||
elementBox.x -= root->pointerOffset.x;
|
||||
elementBox.y -= root->pointerOffset.y;
|
||||
if ((Clay__PointIsInsideRect(position, elementBox)) && (clipElementId == 0 || (Clay__PointIsInsideRect(position, clipItem->boundingBox)))) {
|
||||
if (mapItem->onHoverFunction) {
|
||||
mapItem->onHoverFunction(mapItem->elementId, context->pointerInfo, mapItem->hoverFunctionUserData);
|
||||
}
|
||||
@ -3935,11 +4118,15 @@ Clay_ScrollContainerData Clay_GetScrollContainerData(Clay_ElementId id) {
|
||||
for (int32_t i = 0; i < context->scrollContainerDatas.length; ++i) {
|
||||
Clay__ScrollContainerDataInternal *scrollContainerData = Clay__ScrollContainerDataInternalArray_Get(&context->scrollContainerDatas, i);
|
||||
if (scrollContainerData->elementId == id.id) {
|
||||
Clay_ScrollElementConfig *scrollElementConfig = Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig;
|
||||
if (!scrollElementConfig) { // This can happen on the first frame before a scroll container is declared
|
||||
return CLAY__INIT(Clay_ScrollContainerData) CLAY__DEFAULT_STRUCT;
|
||||
}
|
||||
return CLAY__INIT(Clay_ScrollContainerData) {
|
||||
.scrollPosition = &scrollContainerData->scrollPosition,
|
||||
.scrollContainerDimensions = { scrollContainerData->boundingBox.width, scrollContainerData->boundingBox.height },
|
||||
.contentDimensions = scrollContainerData->contentSize,
|
||||
.config = *Clay__FindElementConfigWithType(scrollContainerData->layoutElement, CLAY__ELEMENT_CONFIG_TYPE_SCROLL).scrollElementConfig,
|
||||
.config = *scrollElementConfig,
|
||||
.found = true
|
||||
};
|
||||
}
|
||||
|
@ -17,6 +17,41 @@ 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());
|
||||
@ -48,9 +83,15 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
SDL_Window *window = NULL;
|
||||
SDL_Renderer *renderer = NULL;
|
||||
if (SDL_CreateWindowAndRenderer(800, 600, SDL_WINDOW_RESIZABLE, &window, &renderer) < 0) {
|
||||
fprintf(stderr, "Error: could not create window and renderer: %s", SDL_GetError());
|
||||
}
|
||||
|
||||
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));
|
||||
@ -67,6 +108,18 @@ int main(int argc, char *argv[]) {
|
||||
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;
|
||||
|
@ -14,7 +14,7 @@ set(FETCHCONTENT_QUIET FALSE)
|
||||
FetchContent_Declare(
|
||||
SDL
|
||||
GIT_REPOSITORY https://github.com/libsdl-org/SDL.git
|
||||
GIT_TAG preview-3.1.6
|
||||
GIT_TAG release-3.2.4
|
||||
GIT_SHALLOW TRUE
|
||||
GIT_PROGRESS TRUE
|
||||
)
|
||||
@ -34,11 +34,24 @@ 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(
|
||||
|
@ -19,13 +19,16 @@ static const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
|
||||
|
||||
typedef struct app_state {
|
||||
SDL_Window *window;
|
||||
SDL_Renderer *renderer;
|
||||
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 *font = gFonts[config->fontId];
|
||||
TTF_Font **fonts = userData;
|
||||
TTF_Font *font = fonts[config->fontId];
|
||||
int width, height;
|
||||
|
||||
if (!TTF_GetStringSize(font, text.chars, text.length, &width, &height)) {
|
||||
@ -54,19 +57,33 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
}
|
||||
*appstate = state;
|
||||
|
||||
if (!SDL_CreateWindowAndRenderer("Clay Demo", 640, 480, 0, &state->window, &state->renderer)) {
|
||||
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;
|
||||
}
|
||||
|
||||
gFonts[FONT_ID] = font;
|
||||
state->rendererData.fonts[FONT_ID] = font;
|
||||
|
||||
sample_image = IMG_Load("resources/sample.png");
|
||||
|
||||
/* Initialize Clay */
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
@ -78,7 +95,7 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
|
||||
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, 0);
|
||||
Clay_SetMeasureTextFunction(SDL_MeasureText, state->rendererData.fonts);
|
||||
|
||||
state->demoData = ClayVideoDemo_Initialize();
|
||||
|
||||
@ -99,10 +116,14 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event)
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
Clay_SetPointerState((Clay_Vector2) { event->motion.x, event->motion.y },
|
||||
event->motion.state & SDL_BUTTON_LEFT);
|
||||
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->motion.xrel, event->motion.yrel }, 0.01f);
|
||||
Clay_UpdateScrollContainers(true, (Clay_Vector2) { event->wheel.x, event->wheel.y }, 0.01f);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@ -117,12 +138,12 @@ SDL_AppResult SDL_AppIterate(void *appstate)
|
||||
|
||||
Clay_RenderCommandArray render_commands = ClayVideoDemo_CreateLayout(&state->demoData);
|
||||
|
||||
SDL_SetRenderDrawColor(state->renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(state->renderer);
|
||||
SDL_SetRenderDrawColor(state->rendererData.renderer, 0, 0, 0, 255);
|
||||
SDL_RenderClear(state->rendererData.renderer);
|
||||
|
||||
SDL_RenderClayCommands(state->renderer, &render_commands);
|
||||
SDL_Clay_RenderClayCommands(&state->rendererData, &render_commands);
|
||||
|
||||
SDL_RenderPresent(state->renderer);
|
||||
SDL_RenderPresent(state->rendererData.renderer);
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
}
|
||||
@ -138,12 +159,23 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result)
|
||||
AppState *state = appstate;
|
||||
|
||||
if (state) {
|
||||
if (state->renderer)
|
||||
SDL_DestroyRenderer(state->renderer);
|
||||
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/sample.png
Normal file
BIN
examples/SDL3-simple-demo/resources/sample.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 850 B |
@ -119,6 +119,7 @@
|
||||
{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' },
|
||||
|
Binary file not shown.
@ -119,6 +119,7 @@
|
||||
{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' },
|
||||
|
@ -50,4 +50,6 @@ int main(void) {
|
||||
Clay_Raylib_Render(renderCommands, fonts);
|
||||
EndDrawing();
|
||||
}
|
||||
// This function is new since the video was published
|
||||
Clay_Raylib_Close();
|
||||
}
|
||||
|
@ -72,4 +72,6 @@ int main(void) {
|
||||
Clay_Raylib_Render(renderCommandsBottom, fonts);
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
Clay_Raylib_Close();
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ Clay_RenderCommandArray CreateLayout(void) {
|
||||
CLAY_TEXT(CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."),
|
||||
CLAY_TEXT_CONFIG({ .fontId = FONT_ID_BODY_24, .fontSize = 24, .textColor = {0,0,0,255} }));
|
||||
|
||||
CLAY({ .id = CLAY_ID("Photos2"), .layout = { .childGap = 16, .padding = { 16, 16, 16, 16 }}, .backgroundColor = {180, 180, 220, 255} }) {
|
||||
CLAY({ .id = CLAY_ID("Photos2"), .layout = { .childGap = 16, .padding = { 16, 16, 16, 16 }}, .backgroundColor = {180, 180, 220, Clay_Hovered() ? 120 : 255} }) {
|
||||
CLAY({ .id = CLAY_ID("Picture4"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {}
|
||||
CLAY({ .id = CLAY_ID("Picture5"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {}
|
||||
CLAY({ .id = CLAY_ID("Picture6"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}, .image = { .imageData = &profilePicture, .sourceDimensions = {120, 120} }}) {}
|
||||
@ -226,8 +226,8 @@ int main(void) {
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
|
||||
Clay_Initialize(clayMemory, (Clay_Dimensions) { (float)GetScreenWidth(), (float)GetScreenHeight() }, (Clay_ErrorHandler) { HandleClayErrors, 0 });
|
||||
Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_WINDOW_HIGHDPI | FLAG_MSAA_4X_HINT);
|
||||
profilePicture = LoadTextureFromImage(LoadImage("resources/profile-picture.png"));
|
||||
Clay_Raylib_Initialize(1024, 768, "Clay - Raylib Renderer Example", FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT);
|
||||
profilePicture = LoadTexture("resources/profile-picture.png");
|
||||
|
||||
Font fonts[2];
|
||||
fonts[FONT_ID_BODY_24] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
|
||||
@ -250,5 +250,6 @@ int main(void) {
|
||||
}
|
||||
UpdateDrawFrame(fonts);
|
||||
}
|
||||
Clay_Raylib_Close();
|
||||
return 0;
|
||||
}
|
||||
|
10
examples/sokol-corner-radius/CMakeLists.txt
Normal file
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
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
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
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
BIN
examples/sokol-video-demo/resources/Roboto-Regular.ttf
Normal file
Binary file not shown.
24
examples/sokol-video-demo/sokol.c
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
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
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
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
BIN
examples/win32_gdi/resources/Roboto-Regular.ttf
Normal file
Binary file not shown.
@ -3,6 +3,11 @@
|
||||
#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
|
||||
|
||||
@ -48,8 +53,8 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer* renderer, const SDL_FRect re
|
||||
|
||||
int indexCount = 0, vertexCount = 0;
|
||||
|
||||
const float minRadius = SDL_min(rect.w, rect.h) / 2.0f;
|
||||
const float clampedRadius = SDL_min(cornerRadius, minRadius);
|
||||
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);
|
||||
|
||||
@ -141,6 +146,121 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer* renderer, const SDL_FRect re
|
||||
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)
|
||||
@ -228,35 +348,74 @@ static void Clay_SDL2_Render(SDL_Renderer *renderer, Clay_RenderCommandArray ren
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
|
||||
Clay_BorderRenderData *config = &renderCommand->renderData.border;
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
|
||||
if (config->width.left > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
SDL_FRect rect = { boundingBox.x, boundingBox.y + config->cornerRadius.topLeft, config->width.left, boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
if(boundingBox.width > 0 & boundingBox.height > 0){
|
||||
const float maxRadius = SDL_min(boundingBox.width, boundingBox.height) / 2.0f;
|
||||
|
||||
if (config->width.right > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
SDL_FRect rect = { boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + config->cornerRadius.topRight, config->width.right, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
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.right > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
SDL_FRect rect = { boundingBox.x + boundingBox.width - config->width.right, boundingBox.y + config->cornerRadius.topRight, config->width.right, boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
if (config->width.top > 0 & config->cornerRadius.topRight> 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 1, config->color);
|
||||
}
|
||||
|
||||
if (config->width.top > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
SDL_FRect rect = { boundingBox.x + config->cornerRadius.topLeft, boundingBox.y, boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight, config->width.top };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
}
|
||||
if (config->width.bottom > 0 & config->cornerRadius.bottomRight > 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 2, config->color);
|
||||
}
|
||||
|
||||
if (config->width.bottom > 0) {
|
||||
SDL_SetRenderDrawColor(renderer, CLAY_COLOR_TO_SDL_COLOR_ARGS(config->color));
|
||||
SDL_FRect rect = { boundingBox.x + config->cornerRadius.bottomLeft, boundingBox.y + boundingBox.height - config->width.bottom, boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight, config->width.bottom };
|
||||
SDL_RenderFillRectF(renderer, &rect);
|
||||
if (config->width.bottom > 0 & config->cornerRadius.bottomLeft > 0) {
|
||||
SDL_RenderCornerBorder(renderer, &boundingBox, config, 3, config->color);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -1,4 +0,0 @@
|
||||
Please note, the SDL3 renderer is not 100% feature complete. It is currently missing:
|
||||
|
||||
- Images
|
||||
- Scroll / Scissor handling
|
@ -2,16 +2,20 @@
|
||||
#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;
|
||||
|
||||
/* This needs to be global because the "MeasureText" callback doesn't have a
|
||||
* user data parameter */
|
||||
static TTF_Font *gFonts[1];
|
||||
/* 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) {
|
||||
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;
|
||||
@ -109,11 +113,11 @@ static void SDL_RenderFillRoundedRect(SDL_Renderer *renderer, const SDL_FRect re
|
||||
indices[indexCount++] = vertexCount - 1; //LT
|
||||
|
||||
// Render everything
|
||||
SDL_RenderGeometry(renderer, NULL, vertices, vertexCount, indices, indexCount);
|
||||
SDL_RenderGeometry(rendererData->renderer, NULL, vertices, vertexCount, indices, indexCount);
|
||||
}
|
||||
|
||||
static void SDL_RenderArc(SDL_Renderer *renderer, const SDL_FPoint center, const float radius, const float startAngle, const float endAngle, const float thickness, const Clay_Color color) {
|
||||
SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
|
||||
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);
|
||||
@ -133,38 +137,37 @@ static void SDL_RenderArc(SDL_Renderer *renderer, const SDL_FPoint center, const
|
||||
SDL_roundf(center.x + SDL_cosf(angle) * clampedRadius),
|
||||
SDL_roundf(center.y + SDL_sinf(angle) * clampedRadius) };
|
||||
}
|
||||
SDL_RenderLines(renderer, points, numCircleSegments + 1);
|
||||
SDL_RenderLines(rendererData->renderer, points, numCircleSegments + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArray *rcommands)
|
||||
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 = { bounding_box.x, bounding_box.y, bounding_box.width, bounding_box.height };
|
||||
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_SetRenderDrawColor(renderer, config->backgroundColor.r, config->backgroundColor.g, config->backgroundColor.b, config->backgroundColor.a);
|
||||
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_RenderFillRoundedRect(renderer, rect, config->cornerRadius.topLeft, config->backgroundColor);
|
||||
SDL_Clay_RenderFillRoundedRect(rendererData, rect, config->cornerRadius.topLeft, config->backgroundColor);
|
||||
} else {
|
||||
SDL_RenderFillRect(renderer, &rect);
|
||||
SDL_RenderFillRect(rendererData->renderer, &rect);
|
||||
}
|
||||
} break;
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
Clay_TextRenderData *config = &rcmd->renderData.text;
|
||||
const SDL_Color color = { config->textColor.r, config->textColor.g, config->textColor.b, config->textColor.a };
|
||||
|
||||
TTF_Font *font = gFonts[config->fontId];
|
||||
SDL_Surface *surface = TTF_RenderText_Blended(font, config->stringContents.chars, config->stringContents.length, color);
|
||||
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
|
||||
SDL_RenderTexture(renderer, texture, NULL, &rect);
|
||||
|
||||
SDL_DestroySurface(surface);
|
||||
SDL_DestroyTexture(texture);
|
||||
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;
|
||||
@ -177,61 +180,85 @@ static void SDL_RenderClayCommands(SDL_Renderer *renderer, Clay_RenderCommandArr
|
||||
.bottomRight = SDL_min(config->cornerRadius.bottomRight, minRadius)
|
||||
};
|
||||
//edges
|
||||
SDL_SetRenderDrawColor(renderer, config->color.r, config->color.g, config->color.b, config->color.a);
|
||||
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(renderer, &line);
|
||||
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(renderer, &line);
|
||||
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(renderer, &line);
|
||||
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(renderer, config->color.r, config->color.g, config->color.b, config->color.a);
|
||||
SDL_RenderFillRect(renderer, &line);
|
||||
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_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, 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_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, 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_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomLeft,
|
||||
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_RenderArc(renderer, (SDL_FPoint){centerX, centerY}, clampedRadii.bottomRight,
|
||||
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);
|
||||
}
|
||||
|
@ -91,8 +91,11 @@ static inline Clay_Dimensions Raylib_MeasureText(Clay_StringSlice text, Clay_Tex
|
||||
float textHeight = config->fontSize;
|
||||
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
|
||||
if (!fontToUse.glyphs) return textSize;
|
||||
// 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();
|
||||
}
|
||||
|
||||
float scaleFactor = config->fontSize/(float)fontToUse.baseSize;
|
||||
|
||||
@ -122,6 +125,21 @@ void Clay_Raylib_Initialize(int width, int height, const char *title, unsigned i
|
||||
// EnableEventWaiting();
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
for (int j = 0; j < renderCommands.length; j++)
|
||||
@ -131,14 +149,23 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
|
||||
switch (renderCommand->commandType)
|
||||
{
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
|
||||
// Raylib uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator
|
||||
Clay_TextRenderData *textData = &renderCommand->renderData.text;
|
||||
char *cloned = (char *)malloc(textData->stringContents.length + 1);
|
||||
memcpy(cloned, textData->stringContents.chars, textData->stringContents.length);
|
||||
cloned[textData->stringContents.length] = '\0';
|
||||
Font fontToUse = fonts[textData->fontId];
|
||||
DrawTextEx(fontToUse, cloned, (Vector2){boundingBox.x, boundingBox.y}, (float)textData->fontSize, (float)textData->letterSpacing, CLAY_COLOR_TO_RAYLIB_COLOR(textData->textColor));
|
||||
free(cloned);
|
||||
|
||||
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
|
||||
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: {
|
||||
@ -198,7 +225,7 @@ void Clay_Raylib_Render(Clay_RenderCommandArray renderCommands, Font* fonts)
|
||||
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->width.top), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->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->width.bottom), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->color));
|
||||
|
450
renderers/sokol/sokol_clay.h
Normal file
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 */
|
5
renderers/win32_gdi/README.md
Normal file
5
renderers/win32_gdi/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
The windows GDI renderer example is missing the following:
|
||||
|
||||
- Images
|
||||
- Rendering Rounded Rectangle borders
|
||||
- Custom Fonts (font size)
|
609
renderers/win32_gdi/clay_renderer_gdi.c
Normal file
609
renderers/win32_gdi/clay_renderer_gdi.c
Normal file
@ -0,0 +1,609 @@
|
||||
#include <Windows.h>
|
||||
|
||||
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
|
||||
#include <immintrin.h> // AVX intrinsincs for faster sqrtf
|
||||
#endif
|
||||
|
||||
#include "../../clay.h"
|
||||
|
||||
HDC renderer_hdcMem = {0};
|
||||
HBITMAP renderer_hbmMem = {0};
|
||||
HANDLE renderer_hOld = {0};
|
||||
DWORD g_dwGdiRenderFlags;
|
||||
|
||||
#ifndef RECTWIDTH
|
||||
#define RECTWIDTH(rc) ((rc).right - (rc).left)
|
||||
#endif
|
||||
#ifndef RECTHEIGHT
|
||||
#define RECTHEIGHT(rc) ((rc).bottom - (rc).top)
|
||||
#endif
|
||||
|
||||
// Renderer options bit flags
|
||||
// RF clearly stated in the name to avoid confusion with possible macro definitions for other purposes
|
||||
#define CLAYGDI_RF_ALPHABLEND 0x00000001
|
||||
#define CLAYGDI_RF_SMOOTHCORNERS 0x00000002
|
||||
// These are bitflags, not indexes. Next would be 0x00000004
|
||||
|
||||
inline DWORD Clay_Win32_GetRendererFlags() { return g_dwGdiRenderFlags; }
|
||||
|
||||
// Replaces the rendering flags with new ones provided
|
||||
inline void Clay_Win32_SetRendererFlags(DWORD dwFlags) { g_dwGdiRenderFlags = dwFlags; }
|
||||
|
||||
// Returns `true` if flags were modified
|
||||
inline bool Clay_Win32_ModifyRendererFlags(DWORD dwRemove, DWORD dwAdd)
|
||||
{
|
||||
DWORD dwSavedFlags = g_dwGdiRenderFlags;
|
||||
DWORD dwNewFlags = (dwSavedFlags & ~dwRemove) | dwAdd;
|
||||
|
||||
if (dwSavedFlags == dwNewFlags)
|
||||
return false;
|
||||
|
||||
Clay_Win32_SetRendererFlags(dwNewFlags);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*----------------------------------------------------------------------------+
|
||||
| Math stuff start |
|
||||
+----------------------------------------------------------------------------*/
|
||||
// Intrinsincs wrappers
|
||||
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
|
||||
inline float intrin_sqrtf(const float f)
|
||||
{
|
||||
__m128 temp = _mm_set_ss(f);
|
||||
temp = _mm_sqrt_ss(temp);
|
||||
return _mm_cvtss_f32(temp);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Use fast inverse square root
|
||||
#if defined(USE_FAST_SQRT)
|
||||
float fast_inv_sqrtf(float number)
|
||||
{
|
||||
const float threehalfs = 1.5f;
|
||||
|
||||
float x2 = number * 0.5f;
|
||||
float y = number;
|
||||
|
||||
// Evil bit-level hacking
|
||||
uint32_t i = *(uint32_t*)&y;
|
||||
i = 0x5f3759df - (i >> 1); // Initial guess for Newton's method
|
||||
y = *(float*)&i;
|
||||
|
||||
// One iteration of Newton's method
|
||||
y = y * (threehalfs - (x2 * y * y)); // y = y * (1.5 - 0.5 * x * y^2)
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
// Fast square root approximation using the inverse square root
|
||||
float fast_sqrtf(float number)
|
||||
{
|
||||
if (number < 0.0f) return 0.0f; // Handle negative input
|
||||
return number * fast_inv_sqrtf(number);
|
||||
}
|
||||
#endif
|
||||
|
||||
// sqrtf_impl implementation chooser
|
||||
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
|
||||
#define sqrtf_impl(x) intrin_sqrtf(x)
|
||||
#elif defined(USE_FAST_SQRT)
|
||||
#define sqrtf_impl(x) fast_sqrtf(x)
|
||||
#else
|
||||
#define sqrtf_impl(x) sqrtf(x) // Fallback to std sqrtf
|
||||
#endif
|
||||
/*----------------------------------------------------------------------------+
|
||||
| Math stuff end |
|
||||
+----------------------------------------------------------------------------*/
|
||||
|
||||
static inline Clay_Color ColorBlend(Clay_Color base, Clay_Color overlay, float factor)
|
||||
{
|
||||
Clay_Color blended;
|
||||
|
||||
// Normalize alpha values for multiplications
|
||||
float base_a = base.a / 255.0f;
|
||||
float overlay_a = overlay.a / 255.0f;
|
||||
|
||||
overlay_a *= factor;
|
||||
|
||||
float out_a = overlay_a + base_a * (1.0f - overlay_a);
|
||||
|
||||
// Avoid division by zero and fully transparent cases
|
||||
if (out_a <= 0.0f)
|
||||
{
|
||||
return (Clay_Color) { .a = 0, .r = 0, .g = 0, .b = 0 };
|
||||
}
|
||||
|
||||
blended.r = (overlay.r * overlay_a + base.r * base_a * (1.0f - overlay_a)) / out_a;
|
||||
blended.g = (overlay.g * overlay_a + base.g * base_a * (1.0f - overlay_a)) / out_a;
|
||||
blended.b = (overlay.b * overlay_a + base.b * base_a * (1.0f - overlay_a)) / out_a;
|
||||
blended.a = out_a * 255.0f; // Denormalize alpha back
|
||||
|
||||
return blended;
|
||||
}
|
||||
|
||||
static float RoundedRectPixelCoverage(int x, int y, const Clay_CornerRadius radius, int width, int height) {
|
||||
// Check if the pixel is in one of the four rounded corners
|
||||
if (x < radius.topLeft && y < radius.topLeft) {
|
||||
// Top-left corner
|
||||
float dx = radius.topLeft - x - 1;
|
||||
float dy = radius.topLeft - y - 1;
|
||||
float distance = sqrtf_impl(dx * dx + dy * dy);
|
||||
if (distance > radius.topLeft)
|
||||
return 0.0f;
|
||||
if (distance <= radius.topLeft - 1)
|
||||
return 1.0f;
|
||||
return radius.topLeft - distance;
|
||||
}
|
||||
else if (x >= width - radius.topRight && y < radius.topRight) {
|
||||
// Top-right corner
|
||||
float dx = x - (width - radius.topRight);
|
||||
float dy = radius.topRight - y - 1;
|
||||
float distance = sqrtf_impl(dx * dx + dy * dy);
|
||||
if (distance > radius.topRight)
|
||||
return 0.0f;
|
||||
if (distance <= radius.topRight - 1)
|
||||
return 1.0f;
|
||||
return radius.topRight - distance;
|
||||
}
|
||||
else if (x < radius.bottomLeft && y >= height - radius.bottomLeft) {
|
||||
// Bottom-left corner
|
||||
float dx = radius.bottomLeft - x - 1;
|
||||
float dy = y - (height - radius.bottomLeft);
|
||||
float distance = sqrtf_impl(dx * dx + dy * dy);
|
||||
if (distance > radius.bottomLeft)
|
||||
return 0.0f;
|
||||
if (distance <= radius.bottomLeft - 1)
|
||||
return 1.0f;
|
||||
return radius.bottomLeft - distance;
|
||||
}
|
||||
else if (x >= width - radius.bottomRight && y >= height - radius.bottomRight) {
|
||||
// Bottom-right corner
|
||||
float dx = x - (width - radius.bottomRight);
|
||||
float dy = y - (height - radius.bottomRight);
|
||||
float distance = sqrtf_impl(dx * dx + dy * dy);
|
||||
if (distance > radius.bottomRight)
|
||||
return 0.0f;
|
||||
if (distance <= radius.bottomRight - 1)
|
||||
return 1.0f;
|
||||
return radius.bottomRight - distance;
|
||||
}
|
||||
else {
|
||||
// Not in a corner, full coverage
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
HDC hdcMem;
|
||||
HBITMAP hbmMem;
|
||||
HBITMAP hbmMemPrev;
|
||||
void* pBits;
|
||||
SIZE size;
|
||||
} HDCSubstitute;
|
||||
|
||||
static void CreateHDCSubstitute(HDCSubstitute* phdcs, HDC hdcSrc, PRECT prc)
|
||||
{
|
||||
if (prc == NULL)
|
||||
return;
|
||||
|
||||
phdcs->size = (SIZE){ RECTWIDTH(*prc), RECTHEIGHT(*prc) };
|
||||
if (phdcs->size.cx <= 0 || phdcs->size.cy <= 0)
|
||||
return;
|
||||
|
||||
phdcs->hdcMem = CreateCompatibleDC(hdcSrc);
|
||||
if (phdcs->hdcMem == NULL)
|
||||
return;
|
||||
|
||||
// Create a 32-bit DIB section for the memory DC
|
||||
BITMAPINFO bmi = { 0 };
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = phdcs->size.cx;
|
||||
bmi.bmiHeader.biHeight = -phdcs->size.cy; // I think it's faster? Probably
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
phdcs->pBits = NULL;
|
||||
|
||||
phdcs->hbmMem = CreateDIBSection(phdcs->hdcMem, &bmi, DIB_RGB_COLORS, &phdcs->pBits, NULL, 0);
|
||||
if (phdcs->hbmMem == NULL)
|
||||
{
|
||||
DeleteDC(phdcs->hdcMem);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select the DIB section into the memory DC
|
||||
phdcs->hbmMemPrev = SelectObject(phdcs->hdcMem, phdcs->hbmMem);
|
||||
|
||||
// Copy the content of the target DC to the memory DC
|
||||
BitBlt(phdcs->hdcMem, 0, 0, phdcs->size.cx, phdcs->size.cy, hdcSrc, prc->left, prc->top, SRCCOPY);
|
||||
}
|
||||
|
||||
static void DestroyHDCSubstitute(HDCSubstitute* phdcs)
|
||||
{
|
||||
if (phdcs == NULL)
|
||||
return;
|
||||
|
||||
// Clean up
|
||||
SelectObject(phdcs->hdcMem, phdcs->hbmMemPrev);
|
||||
DeleteObject(phdcs->hbmMem);
|
||||
DeleteDC(phdcs->hdcMem);
|
||||
|
||||
ZeroMemory(phdcs, sizeof(HDCSubstitute));
|
||||
}
|
||||
|
||||
static void __Clay_Win32_FillRoundRect(HDC hdc, PRECT prc, Clay_Color color, Clay_CornerRadius radius)
|
||||
{
|
||||
HDCSubstitute substitute = { 0 };
|
||||
CreateHDCSubstitute(&substitute, hdc, prc);
|
||||
|
||||
bool has_corner_radius = radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
|
||||
|
||||
if (has_corner_radius)
|
||||
{
|
||||
// Limit the corner radius to the minimum of half the width and half the height
|
||||
float max_radius = (float)fmin(substitute.size.cx / 2.0f, substitute.size.cy / 2.0f);
|
||||
if (radius.topLeft > max_radius) radius.topLeft = max_radius;
|
||||
if (radius.topRight > max_radius) radius.topRight = max_radius;
|
||||
if (radius.bottomLeft > max_radius) radius.bottomLeft = max_radius;
|
||||
if (radius.bottomRight > max_radius) radius.bottomRight = max_radius;
|
||||
}
|
||||
|
||||
// Iterate over each pixel in the DIB section
|
||||
uint32_t* pixels = (uint32_t*)substitute.pBits;
|
||||
for (int y = 0; y < substitute.size.cy; ++y)
|
||||
{
|
||||
for (int x = 0; x < substitute.size.cx; ++x)
|
||||
{
|
||||
float coverage = 1.0f;
|
||||
if (has_corner_radius)
|
||||
coverage = RoundedRectPixelCoverage(x, y, radius, substitute.size.cx, substitute.size.cy);
|
||||
|
||||
if (coverage > 0.0f)
|
||||
{
|
||||
uint32_t pixel = pixels[y * substitute.size.cx + x];
|
||||
Clay_Color dst_color = {
|
||||
.r = (float)((pixel >> 16) & 0xFF), // Red
|
||||
.g = (float)((pixel >> 8) & 0xFF), // Green
|
||||
.b = (float)(pixel & 0xFF), // Blue
|
||||
.a = 255.0f // Fully opaque
|
||||
};
|
||||
Clay_Color blended = ColorBlend(dst_color, color, coverage);
|
||||
|
||||
pixels[y * substitute.size.cx + x] =
|
||||
((uint32_t)(blended.b) << 0) |
|
||||
((uint32_t)(blended.g) << 8) |
|
||||
((uint32_t)(blended.r) << 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the blended content back to the target DC
|
||||
BitBlt(hdc, prc->left, prc->top, substitute.size.cx, substitute.size.cy, substitute.hdcMem, 0, 0, SRCCOPY);
|
||||
DestroyHDCSubstitute(&substitute);
|
||||
}
|
||||
|
||||
void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands, HFONT* fonts)
|
||||
{
|
||||
bool is_clipping = false;
|
||||
HRGN clipping_region = {0};
|
||||
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc;
|
||||
RECT rc; // Top left of our window
|
||||
|
||||
GetWindowRect(hwnd, &rc);
|
||||
|
||||
hdc = BeginPaint(hwnd, &ps);
|
||||
|
||||
int win_width = rc.right - rc.left,
|
||||
win_height = rc.bottom - rc.top;
|
||||
|
||||
// Create an off-screen DC for double-buffering
|
||||
renderer_hdcMem = CreateCompatibleDC(hdc);
|
||||
renderer_hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height);
|
||||
|
||||
renderer_hOld = SelectObject(renderer_hdcMem, renderer_hbmMem);
|
||||
|
||||
// draw
|
||||
|
||||
for (int j = 0; j < renderCommands.length; j++)
|
||||
{
|
||||
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j);
|
||||
Clay_BoundingBox boundingBox = renderCommand->boundingBox;
|
||||
|
||||
switch (renderCommand->commandType)
|
||||
{
|
||||
case CLAY_RENDER_COMMAND_TYPE_TEXT:
|
||||
{
|
||||
Clay_Color c = renderCommand->renderData.text.textColor;
|
||||
SetTextColor(renderer_hdcMem, RGB(c.r, c.g, c.b));
|
||||
SetBkMode(renderer_hdcMem, TRANSPARENT);
|
||||
|
||||
RECT r = rc;
|
||||
r.left = boundingBox.x;
|
||||
r.top = boundingBox.y;
|
||||
r.right = boundingBox.x + boundingBox.width + r.right;
|
||||
r.bottom = boundingBox.y + boundingBox.height + r.bottom;
|
||||
|
||||
uint16_t font_id = renderCommand->renderData.text.fontId;
|
||||
HFONT hFont = fonts[font_id];
|
||||
HFONT hPrevFont = SelectObject(renderer_hdcMem, hFont);
|
||||
|
||||
// Actually draw text
|
||||
DrawTextA(renderer_hdcMem, renderCommand->renderData.text.stringContents.chars,
|
||||
renderCommand->renderData.text.stringContents.length,
|
||||
&r, DT_TOP | DT_LEFT);
|
||||
|
||||
SelectObject(renderer_hdcMem, hPrevFont);
|
||||
|
||||
break;
|
||||
}
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE:
|
||||
{
|
||||
DWORD dwFlags = Clay_Win32_GetRendererFlags();
|
||||
Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle;
|
||||
RECT r = rc;
|
||||
|
||||
r.left = boundingBox.x;
|
||||
r.top = boundingBox.y;
|
||||
r.right = boundingBox.x + boundingBox.width;
|
||||
r.bottom = boundingBox.y + boundingBox.height;
|
||||
|
||||
bool translucid = false;
|
||||
// There is need to check that only if alphablending is enabled.
|
||||
// In other case the blending will be always opaque and we can jump to simpler FillRgn/Rect
|
||||
if (dwFlags & CLAYGDI_RF_ALPHABLEND)
|
||||
translucid = rrd.backgroundColor.a > 0.0f && rrd.backgroundColor.a < 255.0f;
|
||||
|
||||
bool has_rounded_corners = rrd.cornerRadius.topLeft > 0.0f
|
||||
|| rrd.cornerRadius.topRight > 0.0f
|
||||
|| rrd.cornerRadius.bottomLeft > 0.0f
|
||||
|| rrd.cornerRadius.bottomRight > 0.0f;
|
||||
|
||||
// We go here if CLAYGDI_RF_SMOOTHCORNERS flag is set and one of the corners is rounded
|
||||
// Also we go here if GLAYGDI_RF_ALPHABLEND flag is set and the fill color is translucid
|
||||
if ((dwFlags & CLAYGDI_RF_ALPHABLEND) && translucid || (dwFlags & CLAYGDI_RF_SMOOTHCORNERS) && has_rounded_corners)
|
||||
{
|
||||
__Clay_Win32_FillRoundRect(renderer_hdcMem, &r, rrd.backgroundColor, rrd.cornerRadius);
|
||||
}
|
||||
else
|
||||
{
|
||||
HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b));
|
||||
|
||||
if (has_rounded_corners)
|
||||
{
|
||||
HRGN roundedRectRgn = CreateRoundRectRgn(
|
||||
r.left, r.top, r.right + 1, r.bottom + 1,
|
||||
rrd.cornerRadius.topLeft * 2, rrd.cornerRadius.topLeft * 2);
|
||||
|
||||
FillRgn(renderer_hdcMem, roundedRectRgn, recColor);
|
||||
DeleteObject(roundedRectRgn);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillRect(renderer_hdcMem, &r, recColor);
|
||||
}
|
||||
|
||||
DeleteObject(recColor);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox.
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START:
|
||||
{
|
||||
is_clipping = true;
|
||||
|
||||
clipping_region = CreateRectRgn(boundingBox.x,
|
||||
boundingBox.y,
|
||||
boundingBox.x + boundingBox.width,
|
||||
boundingBox.y + boundingBox.height);
|
||||
|
||||
SelectClipRgn(renderer_hdcMem, clipping_region);
|
||||
break;
|
||||
}
|
||||
|
||||
// The renderer should finish any previously active clipping, and begin rendering elements in full again.
|
||||
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END:
|
||||
{
|
||||
SelectClipRgn(renderer_hdcMem, NULL);
|
||||
|
||||
if (clipping_region)
|
||||
{
|
||||
DeleteObject(clipping_region);
|
||||
}
|
||||
|
||||
is_clipping = false;
|
||||
clipping_region = NULL;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// The renderer should draw a colored border inset into the bounding box.
|
||||
case CLAY_RENDER_COMMAND_TYPE_BORDER:
|
||||
{
|
||||
Clay_BorderRenderData brd = renderCommand->renderData.border;
|
||||
RECT r = rc;
|
||||
|
||||
r.left = boundingBox.x;
|
||||
r.top = boundingBox.y;
|
||||
r.right = boundingBox.x + boundingBox.width;
|
||||
r.bottom = boundingBox.y + boundingBox.height;
|
||||
|
||||
HPEN topPen = CreatePen(PS_SOLID, brd.width.top, RGB(brd.color.r, brd.color.g, brd.color.b));
|
||||
HPEN leftPen = CreatePen(PS_SOLID, brd.width.left, RGB(brd.color.r, brd.color.g, brd.color.b));
|
||||
HPEN bottomPen = CreatePen(PS_SOLID, brd.width.bottom, RGB(brd.color.r, brd.color.g, brd.color.b));
|
||||
HPEN rightPen = CreatePen(PS_SOLID, brd.width.right, RGB(brd.color.r, brd.color.g, brd.color.b));
|
||||
|
||||
HPEN oldPen = SelectObject(renderer_hdcMem, topPen);
|
||||
|
||||
if (brd.cornerRadius.topLeft == 0)
|
||||
{
|
||||
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
|
||||
LineTo(renderer_hdcMem, r.right, r.top);
|
||||
|
||||
SelectObject(renderer_hdcMem, leftPen);
|
||||
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
|
||||
LineTo(renderer_hdcMem, r.left, r.bottom);
|
||||
|
||||
SelectObject(renderer_hdcMem, bottomPen);
|
||||
MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL);
|
||||
LineTo(renderer_hdcMem, r.right, r.bottom);
|
||||
|
||||
SelectObject(renderer_hdcMem, rightPen);
|
||||
MoveToEx(renderer_hdcMem, r.right, r.top, NULL);
|
||||
LineTo(renderer_hdcMem, r.right, r.bottom);
|
||||
}
|
||||
else
|
||||
{
|
||||
// todo: i should be rounded
|
||||
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
|
||||
LineTo(renderer_hdcMem, r.right, r.top);
|
||||
|
||||
SelectObject(renderer_hdcMem, leftPen);
|
||||
MoveToEx(renderer_hdcMem, r.left, r.top, NULL);
|
||||
LineTo(renderer_hdcMem, r.left, r.bottom);
|
||||
|
||||
SelectObject(renderer_hdcMem, bottomPen);
|
||||
MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL);
|
||||
LineTo(renderer_hdcMem, r.right, r.bottom);
|
||||
|
||||
SelectObject(renderer_hdcMem, rightPen);
|
||||
MoveToEx(renderer_hdcMem, r.right, r.top, NULL);
|
||||
LineTo(renderer_hdcMem, r.right, r.bottom);
|
||||
|
||||
}
|
||||
|
||||
SelectObject(renderer_hdcMem, oldPen);
|
||||
DeleteObject(topPen);
|
||||
DeleteObject(leftPen);
|
||||
DeleteObject(bottomPen);
|
||||
DeleteObject(rightPen);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// case CLAY_RENDER_COMMAND_TYPE_IMAGE:
|
||||
// {
|
||||
// // TODO: i couldnt get the win 32 api to load a bitmap.... So im punting on this one :(
|
||||
// break;
|
||||
// }
|
||||
|
||||
default:
|
||||
printf("Unhandled render command %d\r\n", renderCommand->commandType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
BitBlt(hdc, 0, 0, win_width, win_height, renderer_hdcMem, 0, 0, SRCCOPY);
|
||||
|
||||
// Free-up the off-screen DC
|
||||
SelectObject(renderer_hdcMem, renderer_hOld);
|
||||
DeleteObject(renderer_hbmMem);
|
||||
DeleteDC(renderer_hdcMem);
|
||||
|
||||
EndPaint(hwnd, &ps);
|
||||
}
|
||||
|
||||
/*
|
||||
Hacks due to the windows api not making sence to use.... may measure too large, but never too small
|
||||
*/
|
||||
|
||||
#ifndef WIN32_FONT_HEIGHT
|
||||
#define WIN32_FONT_HEIGHT (16)
|
||||
#endif
|
||||
|
||||
#ifndef WIN32_FONT_WIDTH
|
||||
#define WIN32_FONT_WIDTH (8)
|
||||
#endif
|
||||
|
||||
static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData)
|
||||
{
|
||||
Clay_Dimensions textSize = {0};
|
||||
|
||||
if (userData != NULL)
|
||||
{
|
||||
HFONT* fonts = (HFONT*)userData;
|
||||
HFONT hFont = fonts[config->fontId];
|
||||
|
||||
if (hFont != NULL)
|
||||
{
|
||||
HDC hScreenDC = GetDC(NULL);
|
||||
HDC hTempDC = CreateCompatibleDC(hScreenDC);
|
||||
|
||||
if (hTempDC != NULL)
|
||||
{
|
||||
HFONT hPrevFont = SelectObject(hTempDC, hFont);
|
||||
|
||||
SIZE size;
|
||||
GetTextExtentPoint32(hTempDC, text.chars, text.length, &size);
|
||||
|
||||
textSize.width = size.cx;
|
||||
textSize.height = size.cy;
|
||||
|
||||
SelectObject(hScreenDC, hPrevFont);
|
||||
DeleteDC(hTempDC);
|
||||
|
||||
return textSize;
|
||||
}
|
||||
|
||||
ReleaseDC(HWND_DESKTOP, hScreenDC);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for system bitmap font
|
||||
float maxTextWidth = 0.0f;
|
||||
float lineTextWidth = 0;
|
||||
float textHeight = WIN32_FONT_HEIGHT;
|
||||
|
||||
for (int i = 0; i < text.length; ++i)
|
||||
{
|
||||
if (text.chars[i] == '\n')
|
||||
{
|
||||
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
|
||||
lineTextWidth = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
lineTextWidth += WIN32_FONT_WIDTH;
|
||||
}
|
||||
|
||||
maxTextWidth = fmax(maxTextWidth, lineTextWidth);
|
||||
|
||||
textSize.width = maxTextWidth;
|
||||
textSize.height = textHeight;
|
||||
|
||||
return textSize;
|
||||
}
|
||||
|
||||
HFONT Clay_Win32_SimpleCreateFont(const char* filePath, const char* family, int height, int weight)
|
||||
{
|
||||
// Add the font resource to the application instance
|
||||
int fontAdded = AddFontResourceEx(filePath, FR_PRIVATE, NULL);
|
||||
if (fontAdded == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int fontHeight = height;
|
||||
|
||||
// If negative, treat height as Pt rather than pixels
|
||||
if (height < 0) {
|
||||
// Get the screen DPI
|
||||
HDC hScreenDC = GetDC(NULL);
|
||||
int iScreenDPI = GetDeviceCaps(hScreenDC, LOGPIXELSY);
|
||||
ReleaseDC(HWND_DESKTOP, hScreenDC);
|
||||
|
||||
// Convert font height from points to pixels
|
||||
fontHeight = MulDiv(height, iScreenDPI, 72);
|
||||
}
|
||||
|
||||
// Create the font using the calculated height and the font name
|
||||
HFONT hFont = CreateFont(fontHeight, 0, 0, 0, weight, FALSE, FALSE, FALSE,
|
||||
ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
|
||||
DEFAULT_PITCH, family);
|
||||
|
||||
return hFont;
|
||||
}
|
Loading…
Reference in New Issue
Block a user