#define WIN32_LEAN_AND_MEAN // Reduces lots of clunk we aren't using here #include #include "../../clay.h" HDC renderer_hdcMem = {0}; HBITMAP renderer_hbmMem = {0}; HANDLE renderer_hOld = {0}; void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands) { bool is_clipping = false; HRGN clipping_region = {0}; PAINTSTRUCT ps; HDC hdc; RECT rc; // Top left of our window GetWindowRect(hwnd, &rc); hdc = BeginPaint(hwnd, &ps); int win_width = rc.right - rc.left, win_height = rc.bottom - rc.top; // Create an off-screen DC for double-buffering renderer_hdcMem = CreateCompatibleDC(hdc); renderer_hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height); renderer_hOld = SelectObject(renderer_hdcMem, renderer_hbmMem); // draw for (int j = 0; j < renderCommands.length; j++) { Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); Clay_BoundingBox boundingBox = renderCommand->boundingBox; switch (renderCommand->commandType) { case CLAY_RENDER_COMMAND_TYPE_TEXT: { Clay_Color c = renderCommand->renderData.text.textColor; SetTextColor(renderer_hdcMem, RGB(c.r, c.g, c.b)); SetBkMode(renderer_hdcMem, TRANSPARENT); RECT r = rc; r.left = boundingBox.x; r.top = boundingBox.y; r.right = boundingBox.x + boundingBox.width + r.right; r.bottom = boundingBox.y + boundingBox.height + r.bottom; DrawTextA(renderer_hdcMem, renderCommand->renderData.text.stringContents.chars, renderCommand->renderData.text.stringContents.length, &r, DT_TOP | DT_LEFT); break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { Clay_RectangleRenderData rrd = renderCommand->renderData.rectangle; RECT r = rc; r.left = boundingBox.x; r.top = boundingBox.y; r.right = boundingBox.x + boundingBox.width; r.bottom = boundingBox.y + boundingBox.height; HBRUSH recColor = CreateSolidBrush(RGB(rrd.backgroundColor.r, rrd.backgroundColor.g, rrd.backgroundColor.b)); if (rrd.cornerRadius.topLeft > 0) { HRGN roundedRectRgn = CreateRoundRectRgn( r.left, r.top, r.right + 1, r.bottom + 1, rrd.cornerRadius.topLeft * 2, rrd.cornerRadius.topLeft * 2); FillRgn(renderer_hdcMem, roundedRectRgn, recColor); DeleteObject(roundedRectRgn); } else { FillRect(renderer_hdcMem, &r, recColor); } DeleteObject(recColor); break; } // The renderer should begin clipping all future draw commands, only rendering content that falls within the provided boundingBox. case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { is_clipping = true; clipping_region = CreateRectRgn(boundingBox.x, boundingBox.y, boundingBox.x + boundingBox.width, boundingBox.y + boundingBox.height); SelectClipRgn(renderer_hdcMem, clipping_region); break; } // The renderer should finish any previously active clipping, and begin rendering elements in full again. case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { SelectClipRgn(renderer_hdcMem, NULL); if (clipping_region) { DeleteObject(clipping_region); } is_clipping = false; clipping_region = NULL; break; } // The renderer should draw a colored border inset into the bounding box. case CLAY_RENDER_COMMAND_TYPE_BORDER: { Clay_BorderRenderData brd = renderCommand->renderData.border; RECT r = rc; r.left = boundingBox.x; r.top = boundingBox.y; r.right = boundingBox.x + boundingBox.width; r.bottom = boundingBox.y + boundingBox.height; HPEN topPen = CreatePen(PS_SOLID, brd.width.top, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN leftPen = CreatePen(PS_SOLID, brd.width.left, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN bottomPen = CreatePen(PS_SOLID, brd.width.bottom, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN rightPen = CreatePen(PS_SOLID, brd.width.right, RGB(brd.color.r, brd.color.g, brd.color.b)); HPEN oldPen = SelectObject(renderer_hdcMem, topPen); if (brd.cornerRadius.topLeft == 0) { MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.top); SelectObject(renderer_hdcMem, leftPen); MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.left, r.bottom); SelectObject(renderer_hdcMem, bottomPen); MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); SelectObject(renderer_hdcMem, rightPen); MoveToEx(renderer_hdcMem, r.right, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); } else { // todo: i should be rounded MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.top); SelectObject(renderer_hdcMem, leftPen); MoveToEx(renderer_hdcMem, r.left, r.top, NULL); LineTo(renderer_hdcMem, r.left, r.bottom); SelectObject(renderer_hdcMem, bottomPen); MoveToEx(renderer_hdcMem, r.left, r.bottom, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); SelectObject(renderer_hdcMem, rightPen); MoveToEx(renderer_hdcMem, r.right, r.top, NULL); LineTo(renderer_hdcMem, r.right, r.bottom); } SelectObject(renderer_hdcMem, oldPen); DeleteObject(topPen); DeleteObject(leftPen); DeleteObject(bottomPen); DeleteObject(rightPen); break; } case CLAY_RENDER_COMMAND_TYPE_IMAGE: { // TODO: Add blending support by replacing StretchBlt with alphablend // Add corner radius support by setting alpha value of out-of-bounds // pixels to 0 // Add tinting support by creating an hbitmap with a specific color, // and alplhablending over the image. Clay_ImageRenderData *config = &renderCommand->renderData.image; HBITMAP img_hbmMem = *((HBITMAP *) config->imageData); HDC img_hdcMem = CreateCompatibleDC(NULL); SelectObject(img_hdcMem, img_hbmMem); int srcWidth = config->sourceDimensions.width; int srcHeight = config->sourceDimensions.height; float widthRatio = boundingBox.width / srcWidth; float heightRatio = boundingBox.height / srcHeight; int dstX = boundingBox.x; int dstY = boundingBox.y; int dstWidth; int dstHeight; // Win32 GDI's StretchBlt can scale our image, but will not preserve aspect ratio if (widthRatio > heightRatio) { dstHeight = boundingBox.height; dstWidth = (int) (srcWidth * widthRatio); if (dstWidth > boundingBox.width) { dstWidth = boundingBox.width; } } else { dstWidth = boundingBox.width; dstHeight = (int) (srcWidth * heightRatio); if (dstHeight > boundingBox.height) { dstHeight = boundingBox.height; } } SetStretchBltMode(renderer_hdcMem, HALFTONE); // Halftone gives really nice scaled images SetBrushOrgEx(renderer_hdcMem, dstX, dstY, NULL); // Necessary for halftone alignment StretchBlt(renderer_hdcMem, dstX, dstY, dstWidth, dstHeight, img_hdcMem, 0, 0, srcWidth, srcHeight, SRCCOPY); DeleteDC(img_hdcMem); break; } default: printf("Unhandled render command %d\r\n", renderCommand->commandType); break; } } BitBlt(hdc, 0, 0, win_width, win_height, renderer_hdcMem, 0, 0, SRCCOPY); // Free-up the off-screen DC SelectObject(renderer_hdcMem, renderer_hOld); DeleteObject(renderer_hbmMem); DeleteDC(renderer_hdcMem); EndPaint(hwnd, &ps); } /* Hacks due to the windows api not making sence to use.... may measure too large, but never too small */ #ifndef WIN32_FONT_HEIGHT #define WIN32_FONT_HEIGHT (16) #endif #ifndef WIN32_FONT_WIDTH #define WIN32_FONT_WIDTH (8) #endif static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, void *userData) { Clay_Dimensions textSize = {0}; float maxTextWidth = 0.0f; float lineTextWidth = 0; float textHeight = WIN32_FONT_HEIGHT; for (int i = 0; i < text.length; ++i) { if (text.chars[i] == '\n') { maxTextWidth = fmax(maxTextWidth, lineTextWidth); lineTextWidth = 0; continue; } lineTextWidth += WIN32_FONT_WIDTH; } maxTextWidth = fmax(maxTextWidth, lineTextWidth); textSize.width = maxTextWidth; textSize.height = textHeight; return textSize; }