Compare commits

..

4 Commits

Author SHA1 Message Date
Philosoph228
addea58fea
Merge branch 'nicbarker:main' into win32-gdi 2025-03-25 11:40:09 +05:00
Philosoph228
fd02c528d3
Add missing font resource 2025-03-25 09:49:24 +05:00
Philosoph228
b2bbdf8760
Implement accurate rectangle blending and rounding
The implementation works by handling special cases where rectangles have semi-transparent background colors or rounded corners. For these cases, it first copies the underlying image region from the window device context, blends the rectangle on top of this captured background, and then draws the final result back to the window DC because we can't access and modify window dc bits directly. Also this approach requires custom pixel processing for color blending and uses sqrtf for precise anti-aliased corners, which isn't ideal for performance. A `gdi_fabulous` flag is provided to toggle this feature when needed.
2025-03-25 09:47:04 +05:00
Nic Barker
47d1d84bc8
[Core] Switch text content hashing to default behaviour (#335)
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-03-25 10:13:04 +13:00
10 changed files with 424 additions and 46 deletions

View File

@ -17,6 +17,7 @@ when ODIN_OS == .Windows {
}
String :: struct {
isStaticallyAllocated: c.bool,
length: c.int32_t,
chars: [^]c.char,
}
@ -419,7 +420,13 @@ UI :: proc() -> proc (config: ElementDeclaration) -> bool {
return ConfigureOpenElement
}
Text :: proc(text: string, config: ^TextElementConfig) {
Text :: proc($text: string, config: ^TextElementConfig) {
wrapped := MakeString(text)
wrapped.isStaticallyAllocated = true
_OpenTextElement(wrapped, config)
}
TextDynamic :: proc(text: string, config: ^TextElementConfig) {
_OpenTextElement(MakeString(text), config)
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -62,7 +62,7 @@ border2pxRed := clay.BorderElementConfig {
color = COLOR_RED
}
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, text: string, image: ^raylib.Texture2D) {
LandingPageBlob :: proc(index: u32, fontSize: u16, fontId: u16, color: clay.Color, $text: string, image: ^raylib.Texture2D) {
if clay.UI()({
id = clay.ID("HeroBlob", index),
layout = { sizing = { width = clay.SizingGrow({ max = 480 }) }, padding = clay.PaddingAll(16), childGap = 16, childAlignment = clay.ChildAlignment{ y = .Center } },
@ -252,7 +252,7 @@ ColorLerp :: proc(a: clay.Color, b: clay.Color, amount: f32) -> clay.Color {
return clay.Color{a.r + (b.r - a.r) * amount, a.g + (b.g - a.g) * amount, a.b + (b.b - a.b) * amount, a.a + (b.a - a.a) * amount}
}
LOREM_IPSUM_TEXT := "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
LOREM_IPSUM_TEXT :: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
HighPerformancePage :: proc(lerpValue: f32, titleTextConfig: clay.TextElementConfig, widthSizing: clay.SizingAxis) {
if clay.UI()({ id = clay.ID("PerformanceLeftText"), layout = { sizing = { width = widthSizing }, layoutDirection = .TopToBottom, childGap = 8 } }) {
@ -321,7 +321,7 @@ HighPerformancePageMobile :: proc(lerpValue: f32) {
}
}
RendererButtonActive :: proc(index: i32, text: string) {
RendererButtonActive :: proc(index: i32, $text: string) {
if clay.UI()({
layout = { sizing = { width = clay.SizingFixed(300) }, padding = clay.PaddingAll(16) },
backgroundColor = COLOR_RED,
@ -331,7 +331,7 @@ RendererButtonActive :: proc(index: i32, text: string) {
}
}
RendererButtonInactive :: proc(index: u32, text: string) {
RendererButtonInactive :: proc(index: u32, $text: string) {
if clay.UI()({ border = border2pxRed }) {
if clay.UI()({
id = clay.ID("RendererButtonInactiveInner", index),

166
clay.h
View File

@ -96,9 +96,9 @@
#define CLAY__ENSURE_STRING_LITERAL(x) ("" x "")
// Note: If an error led you here, it's because CLAY_STRING can only be used with string literals, i.e. CLAY_STRING("SomeString") and not CLAY_STRING(yourString)
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING(string) (CLAY__INIT(Clay_String) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) })
#define CLAY_STRING_CONST(string) { .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
#define CLAY_STRING_CONST(string) { .isStaticallyAllocated = true, .length = CLAY__STRING_LENGTH(CLAY__ENSURE_STRING_LITERAL(string)), .chars = (string) }
static uint8_t CLAY__ELEMENT_DEFINITION_LATCH;
@ -185,6 +185,9 @@ extern "C" {
// Note: Clay_String is not guaranteed to be null terminated. It may be if created from a literal C string,
// but it is also used to represent slices.
typedef struct {
// Set this boolean to true if the char* data underlying this string will live for the entire lifetime of the program.
// This will automatically be set for strings created with CLAY_STRING, as the macro requires a string literal.
bool isStaticallyAllocated;
int32_t length;
// The underlying character memory. Note: this will not be copied and will not extend the lifetime of the underlying memory.
const char *chars;
@ -384,10 +387,6 @@ typedef struct {
// CLAY_TEXT_ALIGN_CENTER - Horizontally aligns wrapped lines of text to the center of their bounding box.
// CLAY_TEXT_ALIGN_RIGHT - Horizontally aligns wrapped lines of text to the right hand side of their bounding box.
Clay_TextAlignment textAlignment;
// When set to true, clay will hash the entire text contents of this string as an identifier for its internal
// text measurement cache, rather than just the pointer and length. This will incur significant performance cost for
// long bodies of text.
bool hashStringContents;
} Clay_TextElementConfig;
CLAY__WRAPPER_STRUCT(Clay_TextElementConfig);
@ -876,8 +875,7 @@ CLAY_DLL_EXPORT int32_t Clay_GetMaxMeasureTextCacheWordCount(void);
// Modifies the maximum number of measured "words" (whitespace seperated runs of characters) that Clay can store in its internal text measurement cache.
// This may require reallocating additional memory, and re-calling Clay_Initialize();
CLAY_DLL_EXPORT void Clay_SetMaxMeasureTextCacheWordCount(int32_t maxMeasureTextCacheWordCount);
// Resets Clay's internal text measurement cache, useful if memory to represent strings is being re-used.
// Similar behaviour can be achieved on an individual text element level by using Clay_TextElementConfig.hashStringContents
// Resets Clay's internal text measurement cache. Useful if font mappings have changed or fonts have been reloaded.
CLAY_DLL_EXPORT void Clay_ResetMeasureTextCache(void);
// Internal API functions required by macros ----------------------
@ -1348,26 +1346,140 @@ Clay_ElementId Clay__HashString(Clay_String key, const uint32_t offset, const ui
return CLAY__INIT(Clay_ElementId) { .id = hash + 1, .offset = offset, .baseId = base + 1, .stringId = key }; // Reserve the hash result of zero as "null id"
}
uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
uint32_t hash = 0;
uintptr_t pointerAsNumber = (uintptr_t)text->chars;
#if !defined(CLAY_DISABLE_SIMD) && (defined(__x86_64__) || defined(_M_X64) || defined(_M_AMD64))
static inline __m128i Clay__SIMDRotateLeft(__m128i x, int r) {
return _mm_or_si128(_mm_slli_epi64(x, r), _mm_srli_epi64(x, 64 - r));
}
if (config->hashStringContents) {
uint32_t maxLengthToHash = CLAY__MIN(text->length, 256);
for (uint32_t i = 0; i < maxLengthToHash; i++) {
hash += text->chars[i];
hash += (hash << 10);
hash ^= (hash >> 6);
static inline void Clay__SIMDARXMix(__m128i* a, __m128i* b) {
*a = _mm_add_epi64(*a, *b);
*b = _mm_xor_si128(Clay__SIMDRotateLeft(*b, 17), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
__m128i v0 = _mm_set1_epi64x(0x6a09e667f3bcc908ULL);
__m128i v1 = _mm_set1_epi64x(0xbb67ae8584caa73bULL);
__m128i v2 = _mm_set1_epi64x(0x3c6ef372fe94f82bULL);
__m128i v3 = _mm_set1_epi64x(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[16] = { 0 }; // Temporary buffer for small inputs
while (length > 0) {
__m128i msg;
if (length >= 16) {
msg = _mm_loadu_si128((const __m128i*)data);
data += 16;
length -= 16;
}
} else {
hash += pointerAsNumber;
else {
for (int i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
msg = _mm_loadu_si128((const __m128i*)overflowBuffer);
length = 0;
}
v0 = _mm_xor_si128(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = _mm_add_epi64(v0, v2);
v1 = _mm_add_epi64(v1, v3);
uint64_t result[2];
_mm_storeu_si128((__m128i*)result, v0);
return result[0] ^ result[1];
}
#elif !defined(CLAY_DISABLE_SIMD) && defined(__aarch64__)
static inline uint64x2_t Clay__SIMDRotateLeft(uint64x2_t x, int r) {
return vorrq_u64(vshlq_n_u64(x, 17), vshrq_n_u64(x, 64 - 17));
}
static inline void Clay__SIMDARXMix(uint64x2_t* a, uint64x2_t* b) {
*a = vaddq_u64(*a, *b);
*b = veorq_u64(Clay__SIMDRotateLeft(*b, 17), *a);
}
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
// Pinched these constants from the BLAKE implementation
uint64x2_t v0 = vdupq_n_u64(0x6a09e667f3bcc908ULL);
uint64x2_t v1 = vdupq_n_u64(0xbb67ae8584caa73bULL);
uint64x2_t v2 = vdupq_n_u64(0x3c6ef372fe94f82bULL);
uint64x2_t v3 = vdupq_n_u64(0xa54ff53a5f1d36f1ULL);
uint8_t overflowBuffer[8] = { 0 };
while (length > 0) {
uint64x2_t msg;
if (length > 16) {
msg = vld1q_u64((const uint64_t*)data);
data += 16;
length -= 16;
}
else if (length > 8) {
msg = vcombine_u64(vld1_u64((const uint64_t*)data), vdup_n_u64(0));
data += 8;
length -= 8;
}
else {
for (int i = 0; i < length; i++) {
overflowBuffer[i] = data[i];
}
uint8x8_t lower = vld1_u8(overflowBuffer);
msg = vcombine_u8(lower, vdup_n_u8(0));
length = 0;
}
v0 = veorq_u64(v0, msg);
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
}
Clay__SIMDARXMix(&v0, &v1);
Clay__SIMDARXMix(&v2, &v3);
v0 = vaddq_u64(v0, v2);
v1 = vaddq_u64(v1, v3);
uint64_t result[2];
vst1q_u64(result, v0);
return result[0] ^ result[1];
}
#else
uint64_t Clay__HashData(const uint8_t* data, size_t length) {
uint64_t hash = 0;
for (int32_t i = 0; i < length; i++) {
hash += data[i];
hash += (hash << 10);
hash ^= (hash >> 6);
}
return hash;
}
#endif
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
uint32_t Clay__HashStringContentsWithConfig(Clay_String *text, Clay_TextElementConfig *config) {
uint32_t hash = 0;
if (text->isStaticallyAllocated) {
hash += (uintptr_t)text->chars;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += text->length;
hash += (hash << 10);
hash ^= (hash >> 6);
} else {
hash = Clay__HashData((const uint8_t *)text->chars, text->length) % UINT32_MAX;
}
hash += config->fontId;
hash += (hash << 10);
@ -1377,18 +1489,10 @@ uint32_t Clay__HashTextWithConfig(Clay_String *text, Clay_TextElementConfig *con
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->lineHeight;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->letterSpacing;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += config->wrapMode;
hash += (hash << 10);
hash ^= (hash >> 6);
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
@ -1423,7 +1527,7 @@ Clay__MeasureTextCacheItem *Clay__MeasureTextCached(Clay_String *text, Clay_Text
return &Clay__MeasureTextCacheItem_DEFAULT;
}
#endif
uint32_t id = Clay__HashTextWithConfig(text, config);
uint32_t id = Clay__HashStringContentsWithConfig(text, config);
uint32_t hashBucket = id % (context->maxMeasureTextCacheWordCount / 32);
int32_t elementIndexPrevious = 0;
int32_t elementIndex = context->measureTextHashMap.internalArray[hashBucket];

Binary file not shown.

View File

@ -1,9 +1,262 @@
#include <Windows.h>
// #define USE_INTRINSICS
// #define USE_FAST_SQRT
#if defined(USE_INTRINSICS)
#include <immintrin.h>
#endif
#include "../../clay.h"
HDC renderer_hdcMem = {0};
HBITMAP renderer_hbmMem = {0};
HANDLE renderer_hOld = {0};
bool gdi_fabulous = true;
#define RECTWIDTH(rc) ((rc).right - (rc).left)
#define RECTHEIGHT(rc) ((rc).bottom - (rc).top)
/*----------------------------------------------------------------------------+
| Math stuff start |
+----------------------------------------------------------------------------*/
#if defined(USE_INTRINSICS)
#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
// Use intrinsics
#if defined(USE_INTRINSICS)
inline float intrin_sqrtf(const float f)
{
__m128 temp = _mm_set_ss(f);
temp = _mm_sqrt_ss(temp);
return _mm_cvtss_f32(temp);
}
#endif // defined(USE_INTRINSICS)
// 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
/*----------------------------------------------------------------------------+
| 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)
{
@ -71,23 +324,37 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands, HFONT*
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 = 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)
if (gdi_fabulous && (translucid || 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;
}