mirror of
https://github.com/nicbarker/clay.git
synced 2025-04-15 10:48:04 +00:00
[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
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
This commit is contained in:
parent
a9e94e3be0
commit
87efc49f52
@ -9,6 +9,7 @@ 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)
|
||||||
|
|
||||||
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
|
message(STATUS "CLAY_INCLUDE_DEMOS: ${CLAY_INCLUDE_DEMOS}")
|
||||||
|
|
||||||
@ -37,6 +38,12 @@ 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(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
|
||||||
|
|
||||||
#add_library(${PROJECT_NAME} INTERFACE)
|
#add_library(${PROJECT_NAME} INTERFACE)
|
||||||
|
15
examples/win32_gdi/CMakeLists.txt
Normal file
15
examples/win32_gdi/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
project(win32_gdi C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 99)
|
||||||
|
|
||||||
|
add_executable(win32_gdi WIN32 main.c)
|
||||||
|
|
||||||
|
target_compile_options(win32_gdi PUBLIC)
|
||||||
|
target_include_directories(win32_gdi PUBLIC .)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET win32_gdi POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/resources
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/resources)
|
@ -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,
|
||||||
|
BIN
examples/win32_gdi/resources/Roboto-Regular.ttf
Normal file
BIN
examples/win32_gdi/resources/Roboto-Regular.ttf
Normal file
Binary file not shown.
@ -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,23 +351,44 @@ 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;
|
||||||
|
|
||||||
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(
|
__Clay_Win32_FillRoundRect(renderer_hdcMem, &r, rrd.backgroundColor, rrd.cornerRadius);
|
||||||
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
|
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;
|
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;
|
||||||
@ -238,4 +577,33 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
|
|||||||
textSize.height = textHeight;
|
textSize.height = textHeight;
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user