Compare commits

...

4 Commits

Author SHA1 Message Date
Eigen Lenk
e20cd91100
Merge e5cf195d8a into eb46688b82 2025-04-10 22:41:22 +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
Eigen Lenk
e5cf195d8a [Renderers/Allegro4] Add Allegro 4.x renderer and demo 2025-02-20 11:47:25 +02:00
21 changed files with 1993 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_RAYLIB_EXAMPLES "Build raylib examples" OFF)
option(CLAY_INCLUDE_SDL2_EXAMPLES "Build SDL 2 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_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}") 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)) if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES))
add_subdirectory("examples/SDL3-simple-demo") add_subdirectory("examples/SDL3-simple-demo")
endif() 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_subdirectory("examples/cairo-pdf-rendering") Some issue with github actions populating cairo, disable for now

View File

@ -0,0 +1,3 @@
# Allegro 4 demo
To build this demo on your system of choice, include all source files in this directory, as well as in "renderers/alleg4" and link with a 4.x version of Allegro. It's probably easiest to go with a pre-built binary.

View File

@ -0,0 +1,486 @@
#include "renderers/alleg4/alleg4.h"
#include <stdio.h>
#include <math.h>
// Locked 50 FPS
#define TICKRATE_MS 20
#define DEG_TO_RAD(x) ((x) / 180.0 * M_PI)
static volatile int pending_main_loop_update = 0;
void main_loop_ticker() {
pending_main_loop_update = 1;
}
END_OF_FUNCTION(main_loop_ticker);
static const int FONT_ID_BODY_16 = 0;
static const int FONT_ID_LARGE_TITLE = 1;
static Clay_Color COLOR_BLACK = { 0, 0, 0, 255 };
static Clay_Color COLOR_WHITE = { 255, 255, 255, 255 };
static Clay_Color COLOR_RED = { 255, 0, 0, 255 };
#define EXPANDING_LAYOUT { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) }
#define BORDER_DEPTH { .width = { 1, 2, 1, 2, 0 }, .color = COLOR_BLACK }
#define BEVELED(PADDING, BG, WIDTH, ...) \
CLAY({ \
.backgroundColor = BG, \
.border = { .width = { 1, 0, 1, 0, 0 }, .color = COLOR_WHITE }, \
.layout.sizing.height = CLAY_SIZING_GROW(0), \
.layout.sizing.width = WIDTH > 0 ? CLAY_SIZING_FIXED(WIDTH) : WIDTH == 0 ? CLAY_SIZING_GROW() : CLAY_SIZING_FIT() \
}) { \
CLAY({ \
.layout.padding.left = PADDING, \
.layout.padding.right = PADDING, \
.layout.sizing.width = CLAY_SIZING_GROW(), \
.layout.sizing.height = CLAY_SIZING_GROW(), \
.layout.childAlignment.x = CLAY_ALIGN_X_CENTER, \
.layout.childAlignment.y = CLAY_ALIGN_Y_CENTER, \
.border = { .width = { 0, 1, 0, 1, 0 }, .color = { 128, 128, 128, 255 }, } \
}) { \
__VA_ARGS__ \
} \
}
static struct {
BITMAP *lib_logo;
BITMAP *environment_category;
BITMAP *coral_reef;
} R;
static void draw_bouncy_ball(BITMAP *buffer, Clay_BoundingBox box, void *user_data) {
#define BALL_R 4
#define BORDER 1
static int x = BALL_R+BORDER, y = BALL_R+BORDER;
static Clay_Vector2 dir = { 1, 1 };
circlefill(buffer, box.x + x, box.y + y, BALL_R, ALLEGCOLOR(COLOR_RED));
x += dir.x;
y += dir.y;
if (x <= BALL_R+BORDER || x >= box.width - (BALL_R+BORDER)) {
dir.x *= -1;
}
if (y <= BALL_R+BORDER || y >= box.height - (BALL_R+BORDER)) {
dir.y *= -1;
}
}
static Clay_String test_article;
typedef struct {
intptr_t offset;
intptr_t memory;
} EncartaDemo_Arena;
typedef struct {
EncartaDemo_Arena frameArena;
unsigned int counter;
} EncartaDemo_Data;
EncartaDemo_Data EncartaDemo_Initialize() {
test_article = CLAY_STRING("A coral reef is an underwater ecosystem characterized by reef-building corals. Reefs are formed of colonies of coral polyps held together by calcium carbonate. Most coral reefs are built from stony corals, whose polyps cluster in groups.\n\nCoral belongs to the class Anthozoa in the animal phylum Cnidaria, which includes sea anemones and jellyfish. Unlike sea anemones, corals secrete hard carbonate exoskeletons that support and protect the coral. Most reefs grow best in warm, shallow, clear, sunny and agitated water. Coral reefs first appeared 485 million years ago, at the dawn of the Early Ordovician, displacing the microbial and sponge reefs of the Cambrian.\n\nSometimes called rainforests of the sea, shallow coral reefs form some of Earth's most diverse ecosystems. They occupy less than 0.1% of the world's ocean area, about half the area of France, yet they provide a home for at least 25% of all marine species, including fish, mollusks, worms, crustaceans, echinoderms, sponges, tunicates and other cnidarians. Coral reefs flourish in ocean waters that provide few nutrients. They are most commonly found at shallow depths in tropical waters, but deep water and cold water coral reefs exist on smaller scales in other areas.\n\nShallow tropical coral reefs have declined by 50% since 1950, partly because they are sensitive to water conditions. They are under threat from excess nutrients (nitrogen and phosphorus), rising ocean heat content and acidification, overfishing (e.g., from blast fishing, cyanide fishing, spearfishing on scuba), sunscreen use, and harmful land-use practices, including runoff and seeps (e.g., from injection wells and cesspools).\n\nCoral reefs deliver ecosystem services for tourism, fisheries and shoreline protection. The annual global economic value of coral reefs has been estimated at anywhere from US$30-375 billion (1997 and 2003 estimates) to US$2.7 trillion (a 2020 estimate) to US$9.9 trillion (a 2014 estimate).\n\nThough the shallow water tropical coral reefs are best known, there are also deeper water reef-forming corals, which live in colder water and in temperate seas.\n\n\n(( Material ))\n\nAs the name implies, coral reefs are made up of coral skeletons from mostly intact coral colonies. As other chemical elements present in corals become incorporated into the calcium carbonate deposits, aragonite is formed. However, shell fragments and the remains of coralline algae such as the green-segmented genus Halimeda can add to the reef's ability to withstand damage from storms and other threats. Such mixtures are visible in structures such as Eniwetok Atoll.");
EncartaDemo_Data data = {
.frameArena = { .memory = (intptr_t)malloc(1024) },
.counter = 0
};
return data;
}
void BeveledButton(Clay_String text, int padding, int fixedWidth) {
BEVELED(padding, Clay_Hovered() ? ((Clay_Color) { 224, 224, 224, 255 }) : ((Clay_Color) { 192, 192, 192, 255 }), fixedWidth, {
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.fontSize = 16,
.textColor = COLOR_BLACK,
.textAlignment = CLAY_TEXT_ALIGN_CENTER,
.lineHeight = 12
}));
})
}
static void menubar(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("menubar"),
.backgroundColor = {64, 64, 64, 255 },
.border = {
.width = { 0, 0, 0, 1, 0 },
.color = COLOR_BLACK
},
.layout = {
.sizing = {
.height = CLAY_SIZING_FIXED(24),
.width = CLAY_SIZING_GROW(0)
},
}
}) {
BeveledButton(CLAY_STRING("Menu"), 12, -1);
BeveledButton(CLAY_STRING("Contents"), 12, -1);
BeveledButton(CLAY_STRING("Find"), 12, -1);
BeveledButton(CLAY_STRING("Go Back"), 12, -1);
BeveledButton(CLAY_STRING("Gallery"), 12, -1);
BeveledButton(CLAY_STRING("Atlas"), 12, -1);
BeveledButton(CLAY_STRING("Timeline"), 12, -1);
BeveledButton(CLAY_STRING("Help"), 12, -1);
BEVELED(0, ((Clay_Color) { 192, 192, 192, 255 }), 0, { });
}
}
static void largeArticleHeading(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("titlebar"),
.layout = {
.sizing.width = CLAY_SIZING_GROW(),
.padding = { 8, 8, 8, 0 },
}
}) {
CLAY({
.backgroundColor = {255, 255, 128, 255 },
.cornerRadius = { 12, 0, 0, 12 },
.layout = {
.sizing = {
.height = CLAY_SIZING_GROW(),
.width = CLAY_SIZING_GROW()
},
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }
},
.border = { .width = CLAY_BORDER_ALL(1), .color = COLOR_BLACK }
}) {
CLAY_TEXT(CLAY_STRING("Coral Reef"), CLAY_TEXT_CONFIG({
.fontId = FONT_ID_LARGE_TITLE,
.textColor = COLOR_BLACK
}));
}
}
}
static void demoTitleBox(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("DemoHeader"),
.backgroundColor = COLOR_WHITE,
.layout.sizing.width = CLAY_SIZING_FIXED(250),
.layout.padding = CLAY_PADDING_ALL(5),
.layout.childGap = 5,
.border = BORDER_DEPTH,
.cornerRadius = { 8, 8, 0, 0 }
}) {
CLAY({
.id = CLAY_ID("LibraryLogo"),
.layout = { .sizing = CLAY_SIZING_FIXED(28) },
.image = { .imageData = R.lib_logo, .sourceDimensions = {28, 28} },
.backgroundColor = { 128+sin(DEG_TO_RAD(data->counter))*127, 0, 128+sin(DEG_TO_RAD(data->counter/2))*127, 128 }
}) {}
CLAY_TEXT(CLAY_STRING("Clay Encarta 95:\nAllegro4 Demo"), CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.textColor = COLOR_BLACK
}));
}
}
static void categoryBox(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("CategoryImage"),
.backgroundColor = { 90, 90, 90, 255 },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = {
.width = CLAY_SIZING_FIXED(250),
.height = CLAY_SIZING_FIT()
}
},
.border = BORDER_DEPTH
}) {
CLAY({
.image = {
.imageData = R.environment_category,
.sourceDimensions = {250, 120}
},
.layout.sizing.height = CLAY_SIZING_FIXED(120),
.layout.padding = CLAY_PADDING_ALL(6),
.layout.childAlignment.y = CLAY_ALIGN_Y_BOTTOM
}) {
CLAY_TEXT(CLAY_STRING("Environment"), CLAY_TEXT_CONFIG({
.fontId = FONT_ID_LARGE_TITLE,
.textColor = COLOR_BLACK
}));
}
CLAY({
.backgroundColor = {64, 64, 64, 255 },
.layout = {
.padding = { 1, 2, 1, 2 },
.sizing = {
.width = CLAY_SIZING_GROW(),
.height = CLAY_SIZING_FIT(32),
}
}
}) {
BeveledButton(CLAY_STRING("Show List"), 8, 0);
BeveledButton(CLAY_STRING("Change\nCategory"), 8, 0);
BeveledButton(CLAY_STRING("<"), 8, 28);
BeveledButton(CLAY_STRING(">"), 8, 28);
}
}
}
static void articleBox(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("ArticleImage"),
.backgroundColor = { 90, 90, 90, 255 },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing.width = CLAY_SIZING_FIXED(250)
},
.border = BORDER_DEPTH
}) {
CLAY({
.image = {
.imageData = R.coral_reef,
.sourceDimensions = {250, 230}
},
.layout.sizing.height = CLAY_SIZING_FIXED(230)
}) {}
CLAY({
.layout.sizing.height = CLAY_SIZING_FIXED(20),
.layout.sizing.width = CLAY_SIZING_GROW(),
.layout.padding = { 1, 1, 1, 0 },
}) {
BEVELED(0, ((Clay_Color) { 192, 192, 192, 255 }), 0, {
CLAY_TEXT(CLAY_STRING("Biodiversity of a coral reef"), CLAY_TEXT_CONFIG({
.fontId = -1,
.textColor = COLOR_BLACK
}));
});
}
CLAY({
.backgroundColor = {64, 64, 64, 255 },
.layout = {
.padding = { 1, 2, 1, 2 },
.sizing = {
.height = CLAY_SIZING_FIT(32),
.width = CLAY_SIZING_GROW()
}
}
}) {
BeveledButton(CLAY_STRING("Copy"), 8, 0);
BeveledButton(CLAY_STRING("Print"), 8, 0);
BeveledButton(CLAY_STRING("<"), 8, 28);
BeveledButton(CLAY_STRING(">"), 8, 28);
}
}
}
static void sidebar(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("sidebar"),
.layout = {
.childGap = 8,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing.width = CLAY_SIZING_FIT()
}
}) {
demoTitleBox(data);
categoryBox(data);
articleBox(data);
}
}
static void article(EncartaDemo_Data *data) {
CLAY({
.id = CLAY_ID("article"),
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = EXPANDING_LAYOUT
},
.backgroundColor = COLOR_WHITE,
.border = BORDER_DEPTH,
.cornerRadius = { 8, 8, 0, 0 },
}) {
CLAY({
.scroll.vertical = true,
.layout.padding = CLAY_PADDING_ALL(8),
}) {
CLAY_TEXT(test_article, CLAY_TEXT_CONFIG({
.fontId = FONT_ID_BODY_16,
.textColor = COLOR_BLACK
}));
}
CLAY({
.backgroundColor = {64, 64, 64, 255 },
.layout = {
.padding = { 1, 2, 1, 2 },
.sizing = {
.height = CLAY_SIZING_FIT(32),
.width = CLAY_SIZING_GROW()
}
}
}) {
BeveledButton(CLAY_STRING("Outline"), 8, 0);
BeveledButton(CLAY_STRING("See Also"), 8, 0);
BeveledButton(CLAY_STRING("Copy"), 8, 0);
BeveledButton(CLAY_STRING("Print"), 8, 0);
BEVELED(0, ((Clay_Color) { 192, 192, 192, 255 }), 64, {
alleg4_custom_element *ball_renderer = (alleg4_custom_element *)(data->frameArena.memory + data->frameArena.offset);
data->frameArena.offset += sizeof(alleg4_custom_element);
*ball_renderer = (alleg4_custom_element) {
&draw_bouncy_ball, NULL
};
CLAY({
.layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } },
.custom = { .customData = ball_renderer }
}) { }
})
}
}
}
Clay_RenderCommandArray EncartaDemo_CreateLayout(EncartaDemo_Data *data) {
data->frameArena.offset = 0;
Clay_BeginLayout();
// Build UI here
CLAY({
.id = CLAY_ID("outer-container"),
.backgroundColor = { 64, 64, 64, 255 },
.layout = {
.layoutDirection = CLAY_TOP_TO_BOTTOM,
.sizing = EXPANDING_LAYOUT
}
}) {
menubar(data);
largeArticleHeading(data);
CLAY({
.id = CLAY_ID("lower-content"),
.backgroundColor = { 64, 64, 64, 255 },
.layout = {
.sizing = EXPANDING_LAYOUT,
.childGap = 8,
.padding = CLAY_PADDING_ALL(8)
}
}) {
sidebar(data);
article(data);
}
}
return Clay_EndLayout();
}
void HandleClayErrors(Clay_ErrorData errorData) {
printf("%s", errorData.errorText.chars);
}
int main(int argc, char *argv[]) {
EncartaDemo_Data demoData = EncartaDemo_Initialize();
BITMAP *buffer;
if (allegro_init() != 0) {
return 1;
}
install_keyboard();
install_mouse();
install_timer();
set_color_depth(16);
int driver = GFX_AUTODETECT;
#ifdef _WIN32
driver = GFX_AUTODETECT_WINDOWED;
#endif
set_window_title("Clay Encarta 95");
if (set_gfx_mode(driver, 800, 600, 0, 0) != 0) {
if (set_gfx_mode(GFX_SAFE, 800, 600, 0, 0) != 0) {
set_gfx_mode(GFX_TEXT, 0, 0, 0, 0);
allegro_message("Unable to set any graphic mode\n%s\n", allegro_error);
return 2;
}
}
if (install_int(main_loop_ticker, TICKRATE_MS) < 0) {
allegro_message("Error installing interrupt\n%s\n", allegro_error);
return 4;
}
LOCK_VARIABLE(pending_main_loop_update);
LOCK_FUNCTION(main_loop_ticker);
alleg4_init_fonts(2); /* Allocate space for 1 font */
alleg4_set_font(FONT_ID_BODY_16, load_font("res/8x16.pcx", NULL, NULL));
alleg4_set_font(FONT_ID_LARGE_TITLE, load_font("res/ex06.pcx", NULL, NULL));
buffer = create_bitmap(SCREEN_W, SCREEN_H);
R.lib_logo = load_bitmap("res/liblogo.pcx", NULL);
R.environment_category = load_bitmap("res/env.pcx", NULL);
R.coral_reef = load_bitmap("res/coral.pcx", NULL);
uint64_t totalMemorySize = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
Clay_Initialize(clayMemory, (Clay_Dimensions) {
(float)SCREEN_W, (float)SCREEN_H
}, (Clay_ErrorHandler) {
HandleClayErrors
});
Clay_SetMeasureTextFunction(alleg4_measure_text, NULL);
show_os_cursor(MOUSE_CURSOR_ARROW);
enable_hardware_cursor();
int _mouse_w = mouse_w, _mouse_z = mouse_z;
while (!keypressed()) {
if (!pending_main_loop_update) {
continue;
}
pending_main_loop_update = false;
clear_to_color(buffer, makecol(0, 0, 0));
if (mouse_needs_poll()) {
poll_mouse();
}
Clay_SetLayoutDimensions((Clay_Dimensions) {
(float)SCREEN_W, (float)SCREEN_H
});
Clay_SetPointerState((Clay_Vector2) {
mouse_x, mouse_y
}, mouse_b & 1);
Clay_UpdateScrollContainers(true, (Clay_Vector2) {
mouse_w - _mouse_w, mouse_z - _mouse_z
}, (float)TICKRATE_MS / 1000.f);
_mouse_w = mouse_w;
_mouse_z = mouse_z;
Clay_RenderCommandArray renderCommands = EncartaDemo_CreateLayout(&demoData);
alleg4_render(buffer, renderCommands);
demoData.counter ++;
if (gfx_capabilities & GFX_HW_CURSOR) {
blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
} else {
show_mouse(NULL);
blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);
show_mouse(screen);
}
}
return 0;
}
END_OF_MAIN()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,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; long lastMsgTime = 0;
bool ui_debug_mode; 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) 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: case WM_PAINT:
{ {
Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data); Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
Clay_Win32_Render(hwnd, renderCommands); Clay_Win32_Render(hwnd, renderCommands, fonts);
break; break;
} }
@ -151,7 +159,12 @@ int APIENTRY WinMain(
uint64_t clayRequiredMemory = Clay_MinMemorySize(); uint64_t clayRequiredMemory = Clay_MinMemorySize();
Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory)); 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_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); ZeroMemory(&wc, sizeof wc);
wc.hInstance = hInstance; wc.hInstance = hInstance;
@ -165,6 +178,10 @@ int APIENTRY WinMain(
if (FALSE == RegisterClass(&wc)) if (FALSE == RegisterClass(&wc))
return 0; 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( hwnd = CreateWindow(
szAppName, szAppName,
@ -172,8 +189,8 @@ int APIENTRY WinMain(
WS_OVERLAPPEDWINDOW | WS_VISIBLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
800, // CW_USEDEFAULT, RECTWIDTH(rcWindow), // CW_USEDEFAULT,
600, // CW_USEDEFAULT, RECTHEIGHT(rcWindow), // CW_USEDEFAULT,
0, 0,
0, 0,
hInstance, hInstance,

Binary file not shown.

306
renderers/alleg4/alleg4.c Normal file
View File

@ -0,0 +1,306 @@
#define CLAY_IMPLEMENTATION
#include "alleg4.h"
#define CLIPPED_OPERATION(BUF, X, Y, W, H, CODE) { \
int _X0, _Y0, _X1, _Y1; \
get_clip_rect(BUF, &_X0, &_Y0, &_X1, &_Y1); \
set_clip_rect(BUF, X, Y, X+W-1, Y+H-1); \
CODE; \
set_clip_rect(BUF, _X0, _Y0, _X1, _Y1); \
}
typedef struct alleg4_font_store {
size_t size;
FONT *fonts[];
} alleg4_font_store;
static alleg4_font_store *font_store = NULL;
void alleg4_init_fonts(size_t size) {
font_store = malloc(sizeof(alleg4_font_store) + sizeof(FONT*[size]));
font_store->size = size;
for (size_t i = 0; i < size; ++i) {
font_store->fonts[i] = NULL;
}
}
void alleg4_set_font(unsigned int font_id, FONT *font_object) {
font_store->fonts[font_id] = font_object;
}
static inline FONT* get_font(unsigned int font_id) {
if (font_id >= 0 && font_id < font_store->size) {
return font_store->fonts[font_id];
} else {
return font; /* Default built-in font */
}
}
static inline void arc_thickness(
BITMAP *dst,
int x,
int y,
fixed from,
fixed to,
int r,
int color,
int thickness
) {
do {
arc(dst, x, y, from, to, r--, color);
} while (--thickness);
}
static inline void rectfill_wh(
BITMAP *dst,
int x,
int y,
int w,
int h,
int color
) {
// rectfill uses stard and end coordinates instead of size, so we'd have to -1 all over the place
if (w == 1) { vline(dst, x, y, y+h-1, color); }
else if (h == 1) { hline(dst, x, y, x+w-1, color); }
else { rectfill(dst, x, y, x+w-1, y+h-1, color); }
}
/* Radiuses array contains corner radiuses in clockwise order starting from top-left */
static inline void roundrectfill(
BITMAP *dst,
int x,
int y,
int w,
int h,
int color,
int r[]
) {
int top = CLAY__MAX(r[0],r[1]);
int bottom = CLAY__MAX(r[2],r[3]);
int left = CLAY__MAX(r[0],r[3]);
int right = CLAY__MAX(r[1],r[2]);
if (r[0]) {
CLIPPED_OPERATION(dst, x, y, r[0], r[0], {
circlefill(dst, x+r[0], y+r[0], r[0], color);
});
}
if (r[1]) {
CLIPPED_OPERATION(dst, x+w-r[1], y, r[1], r[1], {
circlefill(dst, x+w-1-r[1], y+r[1], r[1], color);
});
}
if (r[2]) {
CLIPPED_OPERATION(dst, x+w-r[2], y+h-r[2], r[2], r[2], {
circlefill(dst, x+w-1-r[2], y+h-1-r[2], r[2], color);
});
}
if (r[3]) {
CLIPPED_OPERATION(dst, x, y+h-r[3], r[3], r[3], {
circlefill(dst, x+r[3], y+h-1-r[3], r[3], color);
});
}
if (top) {
rectfill_wh(dst, x+r[0], y, w-r[0]-r[1], top, color);
}
if (bottom) {
rectfill_wh(dst, x+r[3], y+h-bottom, w-r[2]-r[3], bottom, color);
}
if (left) {
rectfill_wh(dst, x, y+top, left, h-top-bottom, color);
}
if (right) {
rectfill_wh(dst, x+w-right, y+top, right, h-top-bottom, color);
}
rectfill_wh(dst, x+left, y+top, w-left-right, h-top-bottom, color);
}
void alleg4_render(
BITMAP *buffer,
Clay_RenderCommandArray renderCommands
) {
static int crx0, cry0, crx1, cry1;
for (uint32_t i = 0; i < renderCommands.length; i++) {
Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, i);
Clay_BoundingBox box = renderCommand->boundingBox;
switch (renderCommand->commandType) {
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
Clay_RectangleRenderData *config = &renderCommand->renderData.rectangle;
Clay_Color color = config->backgroundColor;
int radiuses[] = {
config->cornerRadius.topLeft,
config->cornerRadius.topRight,
config->cornerRadius.bottomRight,
config->cornerRadius.bottomLeft
};
if (radiuses[0] + radiuses[1] + radiuses[2] + radiuses[3] > 0) {
roundrectfill(buffer, box.x, box.y, box.width, box.height, ALLEGCOLOR(config->backgroundColor), radiuses);
} else {
rectfill_wh(buffer, box.x, box.y, box.width, box.height, ALLEGCOLOR(config->backgroundColor));
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_TEXT: {
Clay_TextRenderData *config = &renderCommand->renderData.text;
char *slice = (char *)calloc(config->stringContents.length + 1, 1);
memcpy(slice, config->stringContents.chars, config->stringContents.length);
FONT *font_object = get_font(config->fontId);
if (is_color_font(font_object)) {
textout_ex(buffer, get_font(config->fontId), slice, box.x, box.y, -1, -1);
} else {
textout_ex(buffer, get_font(config->fontId), slice, box.x, box.y, ALLEGCOLOR(config->textColor), -1);
}
free(slice);
break;
}
case CLAY_RENDER_COMMAND_TYPE_IMAGE: {
Clay_ImageRenderData *config = &renderCommand->renderData.image;
Clay_Color tintColor = config->backgroundColor;
BITMAP *image = (BITMAP *)config->imageData;
if (tintColor.r + tintColor.g + tintColor.b == 0) {
draw_sprite(buffer, image, box.x, box.y);
} else {
set_trans_blender(tintColor.r, tintColor.g, tintColor.b, 0);
draw_lit_sprite(buffer, image, box.x, box.y, tintColor.a);
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: {
get_clip_rect(buffer, &crx0, &cry0, &crx1, &cry1); /* Save current clip rect coordinates */
set_clip_rect(buffer, box.x, box.y, box.x + box.width, box.y + box.height);
break;
}
case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: {
set_clip_rect(buffer, crx0, cry0, crx1, cry1);
break;
}
case CLAY_RENDER_COMMAND_TYPE_BORDER: {
Clay_BorderRenderData *config = &renderCommand->renderData.border;
const float tl = config->cornerRadius.topLeft;
const float tr = config->cornerRadius.topRight;
const float bl = config->cornerRadius.bottomLeft;
const float br = config->cornerRadius.bottomRight;
if (config->width.left > 0) {
rectfill_wh(buffer, box.x, box.y + tl, config->width.left, box.height-tl-bl, ALLEGCOLOR(config->color));
/* Top-left half-arc */
if (tl > 0) {
CLIPPED_OPERATION(buffer, box.x, box.y+tl/2, tl, tl/2, {
arc_thickness(buffer, box.x+tl, box.y+tl, itofix(64), itofix(128), tl, ALLEGCOLOR(config->color), config->width.left);
});
}
/* Bottom-left half-arc */
if (bl > 0) {
const int y = box.y+box.height-bl;
CLIPPED_OPERATION(buffer, box.x, y, bl, bl/2, {
arc_thickness(buffer, box.x+bl, y-1, itofix(128), itofix(192), bl, ALLEGCOLOR(config->color), config->width.left);
});
}
}
if (config->width.right > 0) {
rectfill_wh(buffer, box.x+box.width-config->width.right, box.y+tr, config->width.right, box.height-br-tr, ALLEGCOLOR(config->color));
/* Top-right half-arc */
if (tr > 0) {
const int x = box.x+box.width-tr;
CLIPPED_OPERATION(buffer, x, box.y+tr/2, tr, tr/2, {
arc_thickness(buffer, x-1, box.y+tr, itofix(0), itofix(64), tr, ALLEGCOLOR(config->color), config->width.right);
});
}
/* Bottom-right half-arc */
if (br > 0) {
const int y = box.y+box.height-br;
const int x = box.x+box.width-br;
CLIPPED_OPERATION(buffer, x, y, br, br/2, {
arc_thickness(buffer, x-1, y-1, itofix(192), itofix(256), br, ALLEGCOLOR(config->color), config->width.right);
});
}
}
if (config->width.top > 0) {
rectfill_wh(buffer, box.x + tl, box.y, box.width - tr - tl, config->width.top, ALLEGCOLOR(config->color));
/* Top-left half-arc */
if (tl > 0) {
CLIPPED_OPERATION(buffer, box.x, box.y, tl, tl/2, {
arc_thickness(buffer, box.x+tl, box.y+tl, itofix(64), itofix(128), tl, ALLEGCOLOR(config->color), config->width.top);
});
}
/* Top-right half-arc */
if (tr > 0) {
const int x = box.x+box.width-tr;
CLIPPED_OPERATION(buffer, x, box.y, tr, tr/2, {
arc_thickness(buffer, x-1, box.y+tr, itofix(0), itofix(64), tr, ALLEGCOLOR(config->color), config->width.top);
});
}
}
if (config->width.bottom > 0) {
rectfill_wh(buffer, box.x+bl, box.y+box.height-config->width.bottom, box.width-br-bl, config->width.bottom, ALLEGCOLOR(config->color));
/* Bottom-left half-arc */
if (bl > 0) {
const int y = box.y+box.height-bl;
CLIPPED_OPERATION(buffer, box.x, y+bl/2, bl, bl/2, {
arc_thickness(buffer, box.x+bl, y-1, itofix(128), itofix(192), bl, ALLEGCOLOR(config->color), config->width.bottom);
});
}
/* Bottom-right half-arc */
if (br > 0) {
const int y = box.y+box.height-br;
const int x = box.x+box.width-br;
CLIPPED_OPERATION(buffer, x, y+br/2, br, br/2, {
arc_thickness(buffer, x-1, y-1, itofix(192), itofix(256), br, ALLEGCOLOR(config->color), config->width.bottom);
});
}
}
break;
}
case CLAY_RENDER_COMMAND_TYPE_CUSTOM: {
Clay_CustomRenderData *config = &renderCommand->renderData.custom;
alleg4_custom_element *callback_info = (alleg4_custom_element *)config->customData;
callback_info->render(buffer, box, callback_info->user_data);
break;
}
}
}
}
Clay_Dimensions alleg4_measure_text(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) {
FONT *font_object = get_font(config->fontId);
char *slice = (char *)calloc(text.length + 1, 1);
memcpy(slice, text.chars, text.length);
const Clay_Dimensions d = (Clay_Dimensions) {
.width = text_length(font_object, slice),
.height = text_height(font_object)
};
free(slice);
return d;
}

28
renderers/alleg4/alleg4.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef CLAY_ALLEG4_HEADER
#define CLAY_ALLEG4_HEADER
#include "clay.h"
#include <allegro.h>
#define ALLEGCOLOR(COLOR) makecol(COLOR.r, COLOR.g, COLOR.b)
typedef struct {
void (*render)(BITMAP *buffer, Clay_BoundingBox box, void *user_data);
void *user_data;
} alleg4_custom_element;
void alleg4_init_fonts(size_t);
void alleg4_set_font(unsigned int, FONT *);
void alleg4_render(
BITMAP *buffer,
Clay_RenderCommandArray renderCommands
);
Clay_Dimensions alleg4_measure_text(
Clay_StringSlice text,
Clay_TextElementConfig *config,
void *userData
);
#endif

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> #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" #include "../../clay.h"
HDC renderer_hdcMem = {0}; HDC renderer_hdcMem = {0};
HBITMAP renderer_hbmMem = {0}; HBITMAP renderer_hbmMem = {0};
HANDLE renderer_hOld = {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; bool is_clipping = false;
HRGN clipping_region = {0}; 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.right = boundingBox.x + boundingBox.width + r.right;
r.bottom = boundingBox.y + boundingBox.height + r.bottom; 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, DrawTextA(renderer_hdcMem, renderCommand->renderData.text.stringContents.chars,
renderCommand->renderData.text.stringContents.length, renderCommand->renderData.text.stringContents.length,
&r, DT_TOP | DT_LEFT); &r, DT_TOP | DT_LEFT);
SelectObject(renderer_hdcMem, hPrevFont);
break; break;
} }
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: case CLAY_RENDER_COMMAND_TYPE_RECTANGLE:
{ {
DWORD dwFlags = Clay_Win32_GetRendererFlags();
Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle; Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle;
RECT r = rc; RECT r = rc;
@ -64,9 +351,28 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
r.right = boundingBox.x + boundingBox.width; r.right = boundingBox.x + boundingBox.width;
r.bottom = boundingBox.y + boundingBox.height; 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)); HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b));
if (rrd.cornerRadius.topLeft > 0) if (has_rounded_corners)
{ {
HRGN roundedRectRgn = CreateRoundRectRgn( HRGN roundedRectRgn = CreateRoundRectRgn(
r.left, r.top, r.right + 1, r.bottom + 1, r.left, r.top, r.right + 1, r.bottom + 1,
@ -81,6 +387,8 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
} }
DeleteObject(recColor); DeleteObject(recColor);
}
break; break;
} }
@ -216,6 +524,37 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
{ {
Clay_Dimensions textSize = {0}; 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 maxTextWidth = 0.0f;
float lineTextWidth = 0; float lineTextWidth = 0;
float textHeight = WIN32_FONT_HEIGHT; float textHeight = WIN32_FONT_HEIGHT;
@ -239,3 +578,32 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
return textSize; 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;
}