Compare commits

...

6 Commits

Author SHA1 Message Date
bangbangsheshotmedown
432cb565c9
Merge ea3e29be5c into eb46688b82 2025-04-10 22:41:01 +03:00
Nathan Korth
eb46688b82
[Renderers/Sokol] Sokol renderer & examples (#373)
Some checks failed
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Has been cancelled
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Has been cancelled
2025-04-09 13:40:22 +12:00
Philosoph228
87efc49f52
[Renderers/WinGDI] Working on Win32 GDI renderer and example (#344)
Some checks are pending
CMake on multiple platforms / build (Release, cl, cl, windows-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, clang, clang++, ubuntu-latest) (push) Waiting to run
CMake on multiple platforms / build (Release, gcc, g++, ubuntu-latest) (push) Waiting to run
2025-04-09 11:31:33 +12:00
bangbangsheshotmedown
ea3e29be5c
Create README.md 2024-10-29 19:53:11 +00:00
bangbangsheshotmedown
134beca09c
Add C file 2024-10-29 19:50:51 +00:00
bangbangsheshotmedown
b3cdf90d39
Add D example 2024-10-29 19:50:13 +00:00
15 changed files with 1265 additions and 16 deletions

View File

@ -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,6 +38,16 @@ 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

5
bindings/D/README.md Normal file
View File

@ -0,0 +1,5 @@
### D Language Example
```
dmd main.d clay.c
```

2
bindings/D/clay.c Normal file
View File

@ -0,0 +1,2 @@
#define CLAY_IMPLEMENTATION
#include "../../clay.h"

88
bindings/D/main.d Normal file
View File

@ -0,0 +1,88 @@
import clay;
import core.stdc.stdlib;
__gshared:
Clay_LayoutConfig layoutElement = { padding: {5} };
extern(C) void main()
{
ulong totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = {
label: str("Clay Memory Arena"),
capacity: totalMemorySize,
memory: cast(char*)malloc(totalMemorySize)
};
Clay_Initialize(clayMemory, Clay_Dimensions(1024,768));
Clay_BeginLayout();
if (ClayBegin( Rectangle(color: Clay_Color(255,255,255,0)), Layout(layoutElement)))
{ }
ClayEnd();
}
// helper functions
Clay_String str(string it)
{
return Clay_String(cast(int)it.length, it.ptr);
}
bool ClayBegin(A...)(A configs)
{
Clay__OpenElement();
foreach(config; configs)
{
alias T = typeof(config);
static if (is(T == Clay_ElementId))
{
Clay__AttachId(config);
}
else static if(is(T == Clay_LayoutConfig*))
{
Clay__AttachLayoutConfig(config);
}
else static if(is(T == Clay_ElementConfig))
{
Clay__AttachElementConfig(config.config, config.type);
}
else static assert(0, "unsupported " ~ typeof(config).stringof);
}
Clay__ElementPostConfiguration();
return true;
}
void ClayEnd()
{
Clay__CloseElement();
}
Clay_ElementId Id(string label)
{
return Clay__HashString(str(label), 0, 0);
}
Clay_LayoutConfig* Layout(lazy Clay_Sizing sizing = Clay_Sizing.init)
{
Clay_LayoutConfig config;
config.sizing = sizing;
return Clay__StoreLayoutConfig(config);
}
Clay_LayoutConfig* Layout(Clay_LayoutConfig config)
{
return Clay__StoreLayoutConfig(config);
}
Clay_ElementConfig Rectangle(lazy Clay_Color color = Clay_Color.init)
{
Clay_RectangleElementConfig config;
config.color = color;
Clay_ElementConfig ret;
ret.type = Clay__ElementConfigType.CLAY__ELEMENT_CONFIG_TYPE_RECTANGLE;
ret.config.rectangleElementConfig = Clay__StoreRectangleElementConfig(config);
return ret;
}

View File

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

View File

@ -0,0 +1,110 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "util/sokol_gl.h"
#include "fontstash.h"
#include "util/sokol_fontstash.h"
#define SOKOL_CLAY_IMPL
#include "../../renderers/sokol/sokol_clay.h"
static void init() {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
sgl_setup(&(sgl_desc_t){
.logger.func = slog_func,
});
sclay_setup();
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0});
Clay_SetMeasureTextFunction(sclay_measure_text, NULL);
}
Clay_RenderCommandArray CornerRadiusTest(){
Clay_BeginLayout();
Clay_Sizing layoutExpand = {
.width = CLAY_SIZING_GROW(0),
.height = CLAY_SIZING_GROW(0)
};
CLAY({ .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,
};
}

View File

@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.27)
project(sokol_video_demo C)
include(FetchContent)
set(FETCHCONTENT_QUIET FALSE)
# Linux -pthread shenanigans
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
endif()
FetchContent_Declare(
fontstash
GIT_REPOSITORY "https://github.com/memononen/fontstash.git"
GIT_TAG "b5ddc9741061343740d85d636d782ed3e07cf7be"
GIT_PROGRESS TRUE
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fontstash)
FetchContent_Declare(
sokol
GIT_REPOSITORY "https://github.com/floooh/sokol.git"
GIT_TAG "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)

View File

@ -0,0 +1,77 @@
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#define CLAY_IMPLEMENTATION
#include "../../clay.h"
#include "util/sokol_gl.h"
#include "fontstash.h"
#include "util/sokol_fontstash.h"
#define SOKOL_CLAY_IMPL
#include "../../renderers/sokol/sokol_clay.h"
#include "../shared-layouts/clay-video-demo.c"
static ClayVideoDemo_Data demoData;
static sclay_font_t fonts[1];
static void init() {
sg_setup(&(sg_desc){
.environment = sglue_environment(),
.logger.func = slog_func,
});
sgl_setup(&(sgl_desc_t){
.logger.func = slog_func,
});
sclay_setup();
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions){ (float)sapp_width(), (float)sapp_height() }, (Clay_ErrorHandler){0});
fonts[FONT_ID_BODY_16] = sclay_add_font("resources/Roboto-Regular.ttf");
Clay_SetMeasureTextFunction(sclay_measure_text, &fonts);
demoData = ClayVideoDemo_Initialize();
}
static void frame() {
sclay_new_frame();
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demoData);
sg_begin_pass(&(sg_pass){ .swapchain = sglue_swapchain() });
sgl_matrix_mode_modelview();
sgl_load_identity();
sclay_render(renderCommands, fonts);
sgl_draw();
sg_end_pass();
sg_commit();
}
static void event(const sapp_event *ev) {
if(ev->type == SAPP_EVENTTYPE_KEY_DOWN && ev->key_code == SAPP_KEYCODE_D){
Clay_SetDebugModeEnabled(true);
} else {
sclay_handle_event(ev);
}
}
static void cleanup() {
sclay_shutdown();
sgl_shutdown();
sg_shutdown();
}
sapp_desc sokol_main(int argc, char **argv) {
return (sapp_desc){
.init_cb = init,
.frame_cb = frame,
.event_cb = event,
.cleanup_cb = cleanup,
.window_title = "Clay - Sokol Renderer Example",
.width = 800,
.height = 600,
.high_dpi = true,
.icon.sokol_default = true,
.logger.func = slog_func,
};
}

Binary file not shown.

View File

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

View File

@ -0,0 +1,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)

View File

@ -25,6 +25,14 @@ 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)
{
@ -113,7 +121,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
case WM_PAINT:
{
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
Clay_Win32_Render(hwnd, renderCommands);
Clay_Win32_Render(hwnd, renderCommands, fonts);
break;
}
@ -151,7 +159,12 @@ int APIENTRY WinMain(
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_SetMeasureTextFunction(Clay_Win32_MeasureText, NULL);
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;
@ -165,6 +178,10 @@ int APIENTRY WinMain(
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,
@ -172,8 +189,8 @@ int APIENTRY WinMain(
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
800, // CW_USEDEFAULT,
600, // CW_USEDEFAULT,
RECTWIDTH(rcWindow), // CW_USEDEFAULT,
RECTHEIGHT(rcWindow), // CW_USEDEFAULT,
0,
0,
hInstance,

Binary file not shown.

View 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 */

View File

@ -1,11 +1,290 @@
#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;
void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
#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};
@ -48,14 +327,22 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
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;
@ -64,23 +351,44 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
r.right = boundingBox.x + boundingBox.width;
r.bottom = boundingBox.y + boundingBox.height;
HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b));
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;
if (rrd.cornerRadius.topLeft > 0)
// 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)
{
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);
__Clay_Win32_FillRoundRect(renderer_hdcMem, &r, rrd.backgroundColor, rrd.cornerRadius);
}
else
{
FillRect(renderer_hdcMem, &r, recColor);
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);
}
DeleteObject(recColor);
break;
}
@ -216,6 +524,37 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
{
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;
@ -238,4 +577,33 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
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;
}