diff --git a/CMakeLists.txt b/CMakeLists.txt index ecb29d7..68b3305 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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_SDL2_EXAMPLES "Build SDL 2 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}") @@ -37,4 +38,10 @@ if(NOT MSVC AND (CLAY_INCLUDE_ALL_EXAMPLES OR CLAY_INCLUDE_SDL3_EXAMPLES)) add_subdirectory("examples/SDL3-simple-demo") 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 diff --git a/examples/win32_gdi/CMakeLists.txt b/examples/win32_gdi/CMakeLists.txt new file mode 100644 index 0000000..e401e1d --- /dev/null +++ b/examples/win32_gdi/CMakeLists.txt @@ -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) diff --git a/examples/win32_gdi/main.c b/examples/win32_gdi/main.c index 761c59e..03ff247 100644 --- a/examples/win32_gdi/main.c +++ b/examples/win32_gdi/main.c @@ -25,6 +25,10 @@ void CenterWindow(HWND hWnd); long lastMsgTime = 0; bool ui_debug_mode; +HFONT fonts[1]; + +#define RECTWIDTH(rc) ((rc).right - (rc).left) +#define RECTHEIGHT(rc) ((rc).bottom - (rc).top) LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { @@ -113,7 +117,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) case WM_PAINT: { Clay_RenderCommandArray renderCommands = ClayVideoDemo_CreateLayout(&demo_data); - Clay_Win32_Render(hwnd, renderCommands); + Clay_Win32_Render(hwnd, renderCommands, fonts); break; } @@ -151,7 +155,10 @@ int APIENTRY WinMain( 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 - Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, NULL); + + // 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); wc.hInstance = hInstance; @@ -165,6 +172,10 @@ int APIENTRY WinMain( if (FALSE == RegisterClass(&wc)) 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( szAppName, @@ -172,8 +183,8 @@ int APIENTRY WinMain( WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, - 800, // CW_USEDEFAULT, - 600, // CW_USEDEFAULT, + RECTWIDTH(rcWindow), // CW_USEDEFAULT, + RECTHEIGHT(rcWindow), // CW_USEDEFAULT, 0, 0, hInstance, diff --git a/renderers/win32_gdi/clay_renderer_gdi.c b/renderers/win32_gdi/clay_renderer_gdi.c index e031189..25cfdfd 100644 --- a/renderers/win32_gdi/clay_renderer_gdi.c +++ b/renderers/win32_gdi/clay_renderer_gdi.c @@ -5,7 +5,7 @@ HDC renderer_hdcMem = {0}; HBITMAP renderer_hbmMem = {0}; HANDLE renderer_hOld = {0}; -void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands) +void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands, HFONT* fonts) { bool is_clipping = false; HRGN clipping_region = {0}; @@ -48,10 +48,17 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands) r.right = boundingBox.x + boundingBox.width + r.right; 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, renderCommand->renderData.text.stringContents.length, &r, DT_TOP | DT_LEFT); + SelectObject(renderer_hdcMem, hPrevFont); + break; } case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: @@ -216,6 +223,37 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay { Clay_Dimensions textSize = {0}; + if (userData != NULL) + { + HFONT* fonts = (HFONT*)userData; + HFONT hFont = fonts[config->fontId]; + + if (hFont != NULL) + { + HDC hScreenDC = GetDC(HWND_DESKTOP); + 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 lineTextWidth = 0; float textHeight = WIN32_FONT_HEIGHT; @@ -238,4 +276,46 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay textSize.height = textHeight; return textSize; -} \ No newline at end of file +} + +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(HWND_DESKTOP); + 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, // Height + 0, // Width (0 means default width) + 0, // Escapement angle + 0, // Orientation angle + weight, // Font weight + FALSE, // Italic + FALSE, // Underline + FALSE, // Strikeout + ANSI_CHARSET, // Character set + OUT_DEFAULT_PRECIS, // Output precision + CLIP_DEFAULT_PRECIS, // Clipping precision + DEFAULT_QUALITY, // Font quality + DEFAULT_PITCH, // Pitch and family + family // Font name + ); + + return hFont; +}