mirror of
				https://github.com/nicbarker/clay.git
				synced 2025-11-04 00:26:17 +00:00 
			
		
		
		
	Added win32 samples (first pass)
This commit is contained in:
		
							parent
							
								
									fabdad43f6
								
							
						
					
					
						commit
						a782df73a1
					
				
							
								
								
									
										4
									
								
								examples/win32_gdi/build.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								examples/win32_gdi/build.ps1
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,4 @@
 | 
			
		|||
 | 
			
		||||
# to build this, install mingw
 | 
			
		||||
 | 
			
		||||
gcc main.c -ggdb -omain -lgdi32 -lmingw32 # -mwindows # comment -mwindows out for console output
 | 
			
		||||
							
								
								
									
										222
									
								
								examples/win32_gdi/main.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								examples/win32_gdi/main.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,222 @@
 | 
			
		|||
 | 
			
		||||
#define WIN32_LEAN_AND_MEAN
 | 
			
		||||
#include <windows.h>
 | 
			
		||||
#include <windowsx.h>
 | 
			
		||||
 | 
			
		||||
#include <stdio.h>
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
 | 
			
		||||
#include "clay_renderer_gdi.c"
 | 
			
		||||
 | 
			
		||||
#define CLAY_IMPLEMENTATION
 | 
			
		||||
#include "../clay/clay.h"
 | 
			
		||||
 | 
			
		||||
#include "../shared-layouts/clay-video-demo.c"
 | 
			
		||||
 | 
			
		||||
ClayVideoDemo_Data demo_data;
 | 
			
		||||
 | 
			
		||||
#define APPNAME "Clay GDI Example"
 | 
			
		||||
char szAppName[] = APPNAME; // The name of this application
 | 
			
		||||
char szTitle[] = APPNAME;   // The title bar text
 | 
			
		||||
 | 
			
		||||
void CenterWindow(HWND hWnd);
 | 
			
		||||
 | 
			
		||||
long lastMsgTime = 0;
 | 
			
		||||
bool ui_debug_mode;
 | 
			
		||||
 | 
			
		||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
    switch (message)
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    // ----------------------- first and last
 | 
			
		||||
    case WM_CREATE:
 | 
			
		||||
        CenterWindow(hwnd);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case WM_DESTROY:
 | 
			
		||||
        PostQuitMessage(0);
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    case WM_MOUSEWHEEL: // scrolling data
 | 
			
		||||
    {
 | 
			
		||||
        short zDelta = GET_WHEEL_DELTA_WPARAM(wParam);
 | 
			
		||||
        // todo: i think GetMessageTime can roll over, so something like if(lastmsgtime > now) ... may be needed
 | 
			
		||||
        long now = GetMessageTime();
 | 
			
		||||
        float dt = (now - lastMsgTime) / 1000.00;
 | 
			
		||||
 | 
			
		||||
        lastMsgTime = now;
 | 
			
		||||
 | 
			
		||||
        // little hacky hack to make scrolling *feel* right
 | 
			
		||||
        if (abs(zDelta) > 100)
 | 
			
		||||
        {
 | 
			
		||||
            zDelta = zDelta * .012;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Clay_UpdateScrollContainers(true, (Clay_Vector2){.x = 0, .y = zDelta}, dt);
 | 
			
		||||
 | 
			
		||||
        InvalidateRect(hwnd, NULL, false); // force a wm_paint event
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case WM_RBUTTONUP:
 | 
			
		||||
    case WM_LBUTTONUP:
 | 
			
		||||
    case WM_LBUTTONDOWN:
 | 
			
		||||
    case WM_RBUTTONDOWN:
 | 
			
		||||
    case WM_MOUSEMOVE: // mouse events
 | 
			
		||||
    {
 | 
			
		||||
        short mouseX = GET_X_LPARAM(lParam);
 | 
			
		||||
        short mouseY = GET_Y_LPARAM(lParam);
 | 
			
		||||
        short mouseButtons = LOWORD(wParam);
 | 
			
		||||
 | 
			
		||||
        Clay_SetPointerState((Clay_Vector2){mouseX, mouseY}, mouseButtons & 0b01);
 | 
			
		||||
 | 
			
		||||
        InvalidateRect(hwnd, NULL, false); // force a wm_paint event
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case WM_SIZE: // resize events
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
        RECT r = {0};
 | 
			
		||||
        if (GetClientRect(hwnd, &r))
 | 
			
		||||
        {
 | 
			
		||||
            Clay_Dimensions dim = (Clay_Dimensions){.height = r.bottom - r.top, .width = r.right - r.left};
 | 
			
		||||
            Clay_SetLayoutDimensions(dim);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        InvalidateRect(hwnd, NULL, false); // force a wm_paint event
 | 
			
		||||
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    case WM_KEYDOWN:
 | 
			
		||||
        if (VK_ESCAPE == wParam)
 | 
			
		||||
        {
 | 
			
		||||
            DestroyWindow(hwnd);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (wParam == VK_F12)
 | 
			
		||||
        {
 | 
			
		||||
            Clay_SetDebugModeEnabled(ui_debug_mode = !ui_debug_mode);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        printf("Key Pressed: %d\r\n", wParam);
 | 
			
		||||
        InvalidateRect(hwnd, NULL, false); // force a wm_paint event
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
    // ----------------------- render
 | 
			
		||||
    case WM_PAINT:
 | 
			
		||||
    {
 | 
			
		||||
        Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data);
 | 
			
		||||
        Clay_Win32_Render(hwnd, renderCommands);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // ----------------------- let windows do all other stuff
 | 
			
		||||
    default:
 | 
			
		||||
        return DefWindowProc(hwnd, message, wParam, lParam);
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool didAllocConsole = false;
 | 
			
		||||
 | 
			
		||||
void HandleClayErrors(Clay_ErrorData errorData)
 | 
			
		||||
{
 | 
			
		||||
    if (!didAllocConsole)
 | 
			
		||||
    {
 | 
			
		||||
        didAllocConsole = AllocConsole();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    printf("Handle Clay Errors: %s\r\n", errorData.errorText.chars);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int APIENTRY WinMain(
 | 
			
		||||
    HINSTANCE hInstance,
 | 
			
		||||
    HINSTANCE hPrevInstance,
 | 
			
		||||
    LPSTR lpCmdLine,
 | 
			
		||||
    int nCmdShow)
 | 
			
		||||
{
 | 
			
		||||
    MSG msg;
 | 
			
		||||
    WNDCLASS wc;
 | 
			
		||||
    HWND hwnd;
 | 
			
		||||
 | 
			
		||||
    demo_data = ClayVideoDemo_Initialize();
 | 
			
		||||
 | 
			
		||||
    uint64_t clayRequiredMemory = Clay_MinMemorySize();
 | 
			
		||||
    Clay_Arena clayMemory = Clay_CreateArenaWithCapacityAndMemory(clayRequiredMemory, malloc(clayRequiredMemory));
 | 
			
		||||
    Clay_Initialize(clayMemory, (Clay_Dimensions){.width = 800, .height = 600}, (Clay_ErrorHandler){HandleClayErrors}); // This final argument is new since the video was published
 | 
			
		||||
 | 
			
		||||
    // Font fonts[1];
 | 
			
		||||
    // fonts[FONT_ID_BODY_16] = LoadFontEx("resources/Roboto-Regular.ttf", 48, 0, 400);
 | 
			
		||||
 | 
			
		||||
    Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, NULL); // was gettings fonts[] passed in
 | 
			
		||||
 | 
			
		||||
    ZeroMemory(&wc, sizeof wc);
 | 
			
		||||
    wc.hInstance = hInstance;
 | 
			
		||||
    wc.lpszClassName = szAppName;
 | 
			
		||||
    wc.lpfnWndProc = (WNDPROC)WndProc;
 | 
			
		||||
    wc.style = CS_DBLCLKS | CS_VREDRAW | CS_HREDRAW;
 | 
			
		||||
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
 | 
			
		||||
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
 | 
			
		||||
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
 | 
			
		||||
 | 
			
		||||
    if (FALSE == RegisterClass(&wc))
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    hwnd = CreateWindow(
 | 
			
		||||
        szAppName,
 | 
			
		||||
        szTitle,
 | 
			
		||||
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
 | 
			
		||||
        CW_USEDEFAULT,
 | 
			
		||||
        CW_USEDEFAULT,
 | 
			
		||||
        800, // CW_USEDEFAULT,
 | 
			
		||||
        600, // CW_USEDEFAULT,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        hInstance,
 | 
			
		||||
        0);
 | 
			
		||||
 | 
			
		||||
    if (hwnd == NULL)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    // Main message loop:
 | 
			
		||||
    while (GetMessage(&msg, NULL, 0, 0) > 0)
 | 
			
		||||
    {
 | 
			
		||||
        TranslateMessage(&msg);
 | 
			
		||||
        DispatchMessage(&msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return msg.wParam;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void CenterWindow(HWND hwnd_self)
 | 
			
		||||
{
 | 
			
		||||
    HWND hwnd_parent;
 | 
			
		||||
    RECT rw_self, rc_parent, rw_parent;
 | 
			
		||||
    int xpos, ypos;
 | 
			
		||||
 | 
			
		||||
    hwnd_parent = GetParent(hwnd_self);
 | 
			
		||||
    if (NULL == hwnd_parent)
 | 
			
		||||
        hwnd_parent = GetDesktopWindow();
 | 
			
		||||
 | 
			
		||||
    GetWindowRect(hwnd_parent, &rw_parent);
 | 
			
		||||
    GetClientRect(hwnd_parent, &rc_parent);
 | 
			
		||||
    GetWindowRect(hwnd_self, &rw_self);
 | 
			
		||||
 | 
			
		||||
    xpos = rw_parent.left + (rc_parent.right + rw_self.left - rw_self.right) / 2;
 | 
			
		||||
    ypos = rw_parent.top + (rc_parent.bottom + rw_self.top - rw_self.bottom) / 2;
 | 
			
		||||
 | 
			
		||||
    SetWindowPos(
 | 
			
		||||
        hwnd_self, NULL,
 | 
			
		||||
        xpos, ypos, 0, 0,
 | 
			
		||||
        SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//+---------------------------------------------------------------------------
 | 
			
		||||
							
								
								
									
										178
									
								
								renderers/win32_gdi/clay_renderer_gdi.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								renderers/win32_gdi/clay_renderer_gdi.c
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,178 @@
 | 
			
		|||
#include <Windows.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:
 | 
			
		||||
        {
 | 
			
		||||
           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;
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in a new issue