mirror of
https://github.com/nicbarker/clay.git
synced 2025-04-14 02:08:04 +00:00
Resizeable Images + Example Image Loader for Win32 GDI
This commit is contained in:
parent
ad49977f1b
commit
8e0d180b4e
BIN
examples/win32_gdi/image_demo/Reef.jpg
Normal file
BIN
examples/win32_gdi/image_demo/Reef.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 209 KiB |
BIN
examples/win32_gdi/image_demo/Vacuum.jpg
Normal file
BIN
examples/win32_gdi/image_demo/Vacuum.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 762 KiB |
4
examples/win32_gdi/image_demo/build.ps1
Normal file
4
examples/win32_gdi/image_demo/build.ps1
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
# to build this, install mingw
|
||||
|
||||
gcc image_demo.c -ggdb -oimgdemo -lgdi32 -lmingw32 -lole32 -lwindowscodecs # -mwindows # comment -mwindows out for console output
|
386
examples/win32_gdi/image_demo/clay-win32-gdi-image-example.c
Normal file
386
examples/win32_gdi/image_demo/clay-win32-gdi-image-example.c
Normal file
@ -0,0 +1,386 @@
|
||||
#include "../../../clay.h"
|
||||
#include <stdlib.h>
|
||||
#include <wincodec.h> // Required for windows image decoding
|
||||
|
||||
typedef struct {
|
||||
HBITMAP hBitmap;
|
||||
struct HBitmapNode* next;
|
||||
} HBitmapNode;
|
||||
|
||||
HBitmapNode* hBitmapNodeHead = NULL;
|
||||
|
||||
// Stores bitmap handles so we can clean them up later.
|
||||
HBITMAP* pushHBitmap(HBITMAP hBitmap)
|
||||
{
|
||||
HBitmapNode* newHead = (HBitmapNode *) malloc(sizeof(HBitmapNode));
|
||||
newHead->hBitmap = hBitmap;
|
||||
newHead->next = hBitmapNodeHead;
|
||||
hBitmapNodeHead = newHead;
|
||||
return &(newHead->hBitmap);
|
||||
}
|
||||
|
||||
// Deallocates Win32 GDI bitmap handle memory, as well as memory used by the linkedlist
|
||||
void cleanupHBitmaps()
|
||||
{
|
||||
HBitmapNode* curr = hBitmapNodeHead;
|
||||
HBitmapNode* next = NULL;
|
||||
while(curr != NULL)
|
||||
{
|
||||
next = curr->next;
|
||||
DeleteObject(curr->hBitmap);
|
||||
free(curr);
|
||||
curr = next;
|
||||
}
|
||||
hBitmapNodeHead = NULL;
|
||||
}
|
||||
|
||||
// This may be slow for loading huge numbers of images,
|
||||
// consider allocating and initializing interfaces once
|
||||
// outside of the function if that becomes an issue
|
||||
//
|
||||
// Handles PNGs, JPEGs, BMPs, ICOs, TIFFs, HD Photos, and GIFs (only loads the first frame)
|
||||
// Images need to be destroyed with DeleteObject(myImage) after they are no longer needed
|
||||
Clay_ImageElementConfig LoadClayImage(LPCWSTR filename)
|
||||
{
|
||||
// Windows codec stuff
|
||||
CoInitialize(NULL);
|
||||
IWICImagingFactory *imgFactory = NULL;
|
||||
IWICBitmapDecoder *bmpDecoder = NULL;
|
||||
IWICBitmapFrameDecode *bmpFrame = NULL;
|
||||
IWICFormatConverter *formatConverter = NULL;
|
||||
|
||||
// Storage stuff
|
||||
HBITMAP hBitmap = NULL;
|
||||
void *bits = NULL;
|
||||
UINT width = 0;
|
||||
UINT height = 0;
|
||||
BITMAPINFO bmi = { 0 };
|
||||
|
||||
// Start decoding
|
||||
CoCreateInstance(
|
||||
&CLSID_WICImagingFactory,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
&IID_IWICImagingFactory,
|
||||
(void **) &imgFactory);
|
||||
imgFactory->lpVtbl->CreateDecoderFromFilename(
|
||||
imgFactory,
|
||||
filename,
|
||||
NULL,
|
||||
GENERIC_READ,
|
||||
WICDecodeMetadataCacheOnLoad,
|
||||
&bmpDecoder);
|
||||
bmpDecoder->lpVtbl->GetFrame(bmpDecoder, 0, &bmpFrame);
|
||||
imgFactory->lpVtbl->CreateFormatConverter(imgFactory, &formatConverter);
|
||||
|
||||
// Convert to a BitBlt-friendly format
|
||||
formatConverter->lpVtbl->Initialize(
|
||||
formatConverter,
|
||||
(IWICBitmapSource *) bmpFrame,
|
||||
&GUID_WICPixelFormat32bppPBGRA,
|
||||
WICBitmapDitherTypeNone,
|
||||
NULL,
|
||||
0.0,
|
||||
WICBitmapPaletteTypeCustom);
|
||||
bmpFrame->lpVtbl->GetSize(bmpFrame, &width, &height);
|
||||
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -((LONG) height); // Negative makes it a top down bitmap
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
hBitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
|
||||
formatConverter->lpVtbl->CopyPixels(formatConverter, NULL, width * 4, width * height * 4, (BYTE*) bits);
|
||||
|
||||
if (formatConverter)
|
||||
formatConverter->lpVtbl->Release(formatConverter);
|
||||
if (bmpFrame)
|
||||
bmpFrame->lpVtbl->Release(bmpFrame);
|
||||
if (bmpDecoder)
|
||||
bmpDecoder->lpVtbl->Release(bmpDecoder);
|
||||
if (imgFactory)
|
||||
imgFactory->lpVtbl->Release(imgFactory);
|
||||
CoUninitialize();
|
||||
|
||||
// Add to dealloc list
|
||||
HBITMAP* hBitmap_ptr = pushHBitmap(hBitmap);
|
||||
Clay_ImageElementConfig clayImage = { .sourceDimensions = { .height = height, .width = width }, .imageData = hBitmap_ptr };
|
||||
|
||||
return clayImage;
|
||||
}
|
||||
|
||||
Clay_ImageElementConfig reefImg, vacImg;
|
||||
|
||||
const int FONT_ID_BODY_16 = 0;
|
||||
Clay_Color COLOR_WHITE = { 255, 255, 255, 255};
|
||||
|
||||
void RenderHeaderButton(Clay_String text) {
|
||||
CLAY({
|
||||
.layout = { .padding = { 16, 16, 8, 8 }},
|
||||
.backgroundColor = { 140, 140, 140, 255 },
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(5)
|
||||
}) {
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 16,
|
||||
.textColor = { 255, 255, 255, 255 }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void RenderDropdownMenuItem(Clay_String text) {
|
||||
CLAY({.layout = { .padding = CLAY_PADDING_ALL(16)}}) {
|
||||
CLAY_TEXT(text, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 16,
|
||||
.textColor = { 255, 255, 255, 255 }
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Clay_String title;
|
||||
Clay_ImageElementConfig image;
|
||||
Clay_String contents;
|
||||
} Document;
|
||||
|
||||
typedef struct {
|
||||
Document *documents;
|
||||
uint32_t length;
|
||||
} DocumentArray;
|
||||
|
||||
Document documentsRaw[5];
|
||||
|
||||
DocumentArray documents = {
|
||||
.length = 5,
|
||||
.documents = documentsRaw
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
intptr_t offset;
|
||||
intptr_t memory;
|
||||
} ClayVideoDemo_Arena;
|
||||
|
||||
typedef struct {
|
||||
int32_t selectedDocumentIndex;
|
||||
float yOffset;
|
||||
ClayVideoDemo_Arena frameArena;
|
||||
} ClayVideoDemo_Data;
|
||||
|
||||
typedef struct {
|
||||
int32_t requestedDocumentIndex;
|
||||
int32_t* selectedDocumentIndex;
|
||||
} SidebarClickData;
|
||||
|
||||
void HandleSidebarInteraction(
|
||||
Clay_ElementId elementId,
|
||||
Clay_PointerData pointerData,
|
||||
intptr_t userData
|
||||
) {
|
||||
SidebarClickData *clickData = (SidebarClickData*)userData;
|
||||
// If this button was clicked
|
||||
if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) {
|
||||
if (clickData->requestedDocumentIndex >= 0 && clickData->requestedDocumentIndex < documents.length) {
|
||||
// Select the corresponding document
|
||||
*clickData->selectedDocumentIndex = clickData->requestedDocumentIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClayVideoDemo_Data ClayVideoDemo_Initialize() {
|
||||
reefImg = LoadClayImage(L"reef.jpg");
|
||||
vacImg = LoadClayImage(L"Vacuum.jpg");
|
||||
documents.documents[0] = (Document){ .image = reefImg, .title = CLAY_STRING("Squirrels"), .contents = CLAY_STRING("There is an image here. It should be of a reef. Try resizing this image.") };
|
||||
documents.documents[1] = (Document){ .image = vacImg, .title = CLAY_STRING("Images in this example"), .contents = CLAY_STRING("In order to properly render images, make sure that you don't dereference a null or uninitialized pointer. .image attributes should be pointers to HBITMAP values. This example loads images in the same folder as the executable to HBITMAPS stored in a linkedlist, and then on close, the linkedlist is deallocated, and the HBITMAPS are destroyed. You can of course load and destroy HBITMAPS however you like, just make sure .image attributes are pointers to HBITMAP!\n\nIn this example, there is a custom document struct with an image attribute. The render commands always render the document's image, so we have to make sure we give each document a valid image! Otherwise, undefined behavior could happen, most likely in the form of dereferencing an uninitialized pointer.") };
|
||||
documents.documents[2] = (Document){ .image = vacImg, .title = CLAY_STRING("How to use Images with GDI + Clay"), .contents = CLAY_STRING("Images can be loaded in a fashion similar to the LoadClayImage function in this example for external files, or they can be compiled into the executable as a resource via a .rc file, and retrieved via windows' LoadImage function. This approach lets you skip lengthy allocations, but requires a bit more compilation messing around stuff.\n\n Load HBITMAPS however you like, and remember to destroy them before exiting the program!") };
|
||||
documents.documents[3] = (Document){ .image = reefImg, .title = CLAY_STRING("Article 4"), .contents = CLAY_STRING("Article 4") };
|
||||
documents.documents[4] = (Document){ .image = vacImg, .title = CLAY_STRING("Article 5"), .contents = CLAY_STRING("Article 5") };
|
||||
|
||||
ClayVideoDemo_Data data = {
|
||||
.frameArena = { .memory = (intptr_t)malloc(1024) }
|
||||
};
|
||||
return data;
|
||||
}
|
||||
|
||||
ClayVideoDemo_Data ClayVideoDemo_Uninitialize() {
|
||||
cleanupHBitmaps();
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray ClayVideoDemo_CreateLayout(ClayVideoDemo_Data *data) {
|
||||
data->frameArena.offset = 0;
|
||||
|
||||
Clay_BeginLayout();
|
||||
|
||||
Clay_Sizing layoutExpand = {
|
||||
.width = CLAY_SIZING_GROW(0),
|
||||
.height = CLAY_SIZING_GROW(0)
|
||||
};
|
||||
|
||||
Clay_Color contentBackgroundColor = { 90, 90, 90, 255 };
|
||||
|
||||
// Build UI here
|
||||
CLAY({ .id = CLAY_ID("OuterContainer"),
|
||||
.backgroundColor = {43, 41, 51, 255 },
|
||||
.layout = {
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
.sizing = layoutExpand,
|
||||
.padding = CLAY_PADDING_ALL(16),
|
||||
.childGap = 16
|
||||
}
|
||||
}) {
|
||||
// Child elements go inside braces
|
||||
CLAY({ .id = CLAY_ID("HeaderBar"),
|
||||
.layout = {
|
||||
.sizing = {
|
||||
.height = CLAY_SIZING_FIXED(60),
|
||||
.width = CLAY_SIZING_GROW(0)
|
||||
},
|
||||
.padding = { 16, 16, 0, 0 },
|
||||
.childGap = 16,
|
||||
.childAlignment = {
|
||||
.y = CLAY_ALIGN_Y_CENTER
|
||||
}
|
||||
},
|
||||
.backgroundColor = contentBackgroundColor,
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
}) {
|
||||
// Header buttons go here
|
||||
CLAY({ .id = CLAY_ID("FileButton"),
|
||||
.layout = { .padding = { 16, 16, 8, 8 }},
|
||||
.backgroundColor = {140, 140, 140, 255 },
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(5)
|
||||
}) {
|
||||
CLAY_TEXT(CLAY_STRING("File"), CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 16,
|
||||
.textColor = { 255, 255, 255, 255 }
|
||||
}));
|
||||
|
||||
bool fileMenuVisible =
|
||||
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileButton")))
|
||||
||
|
||||
Clay_PointerOver(Clay_GetElementId(CLAY_STRING("FileMenu")));
|
||||
|
||||
if (fileMenuVisible) { // Below has been changed slightly to fix the small bug where the menu would dismiss when mousing over the top gap
|
||||
CLAY({ .id = CLAY_ID("FileMenu"),
|
||||
.floating = {
|
||||
.attachTo = CLAY_ATTACH_TO_PARENT,
|
||||
.attachPoints = {
|
||||
.parent = CLAY_ATTACH_POINT_LEFT_BOTTOM
|
||||
},
|
||||
},
|
||||
.layout = {
|
||||
.padding = {0, 0, 8, 8 }
|
||||
}
|
||||
}) {
|
||||
CLAY({
|
||||
.layout = {
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(200)
|
||||
},
|
||||
},
|
||||
.backgroundColor = {40, 40, 40, 255 },
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
}) {
|
||||
// Render dropdown items here
|
||||
RenderDropdownMenuItem(CLAY_STRING("New"));
|
||||
RenderDropdownMenuItem(CLAY_STRING("Open"));
|
||||
RenderDropdownMenuItem(CLAY_STRING("Close"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RenderHeaderButton(CLAY_STRING("Edit"));
|
||||
CLAY({ .layout = { .sizing = { CLAY_SIZING_GROW(0) }}}) {}
|
||||
RenderHeaderButton(CLAY_STRING("Upload"));
|
||||
RenderHeaderButton(CLAY_STRING("Media"));
|
||||
RenderHeaderButton(CLAY_STRING("Support"));
|
||||
}
|
||||
|
||||
CLAY({
|
||||
.id = CLAY_ID("LowerContent"),
|
||||
.layout = { .sizing = layoutExpand, .childGap = 16 }
|
||||
}) {
|
||||
CLAY({
|
||||
.id = CLAY_ID("Sidebar"),
|
||||
.backgroundColor = contentBackgroundColor,
|
||||
.layout = {
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
.padding = CLAY_PADDING_ALL(16),
|
||||
.childGap = 8,
|
||||
.sizing = {
|
||||
.width = CLAY_SIZING_FIXED(250),
|
||||
.height = CLAY_SIZING_GROW(0)
|
||||
}
|
||||
}
|
||||
}) {
|
||||
for (int i = 0; i < documents.length; i++) {
|
||||
Document document = documents.documents[i];
|
||||
Clay_LayoutConfig sidebarButtonLayout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(0) },
|
||||
.padding = CLAY_PADDING_ALL(16)
|
||||
};
|
||||
|
||||
if (i == data->selectedDocumentIndex) {
|
||||
CLAY({
|
||||
.layout = sidebarButtonLayout,
|
||||
.backgroundColor = {120, 120, 120, 255 },
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(8)
|
||||
}) {
|
||||
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 20,
|
||||
.textColor = { 255, 255, 255, 255 }
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
SidebarClickData *clickData = (SidebarClickData *)(data->frameArena.memory + data->frameArena.offset);
|
||||
*clickData = (SidebarClickData) { .requestedDocumentIndex = i, .selectedDocumentIndex = &data->selectedDocumentIndex };
|
||||
data->frameArena.offset += sizeof(SidebarClickData);
|
||||
CLAY({ .layout = sidebarButtonLayout, .backgroundColor = (Clay_Color) { 120, 120, 120, Clay_Hovered() ? 120 : 0 }, .cornerRadius = CLAY_CORNER_RADIUS(8) }) {
|
||||
Clay_OnHover(HandleSidebarInteraction, (intptr_t)clickData);
|
||||
CLAY_TEXT(document.title, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 20,
|
||||
.textColor = { 255, 255, 255, 255 }
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CLAY({ .id = CLAY_ID("MainContent"),
|
||||
.backgroundColor = contentBackgroundColor,
|
||||
.scroll = { .vertical = true },
|
||||
.layout = {
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM,
|
||||
.childGap = 16,
|
||||
.padding = CLAY_PADDING_ALL(16),
|
||||
.sizing = layoutExpand
|
||||
}
|
||||
}) {
|
||||
Document selectedDocument = documents.documents[data->selectedDocumentIndex];
|
||||
CLAY_TEXT(selectedDocument.title, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 24,
|
||||
.textColor = COLOR_WHITE
|
||||
}));
|
||||
CLAY({ .image = selectedDocument.image, .layout = { .sizing = layoutExpand } });
|
||||
CLAY_TEXT(selectedDocument.contents, CLAY_TEXT_CONFIG({
|
||||
.fontId = FONT_ID_BODY_16,
|
||||
.fontSize = 24,
|
||||
.textColor = COLOR_WHITE
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
|
||||
for (int32_t i = 0; i < renderCommands.length; i++) {
|
||||
Clay_RenderCommandArray_Get(&renderCommands, i)->boundingBox.y += data->yOffset;
|
||||
}
|
||||
return renderCommands;
|
||||
}
|
220
examples/win32_gdi/image_demo/image_demo.c
Normal file
220
examples/win32_gdi/image_demo/image_demo.c
Normal file
@ -0,0 +1,220 @@
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <windowsx.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include "../../../renderers/win32_gdi/clay_renderer_gdi.c"
|
||||
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "../../../clay.h"
|
||||
|
||||
#include "clay-win32-gdi-image-example.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
|
||||
Clay_SetMeasureTextFunction(Clay_Win32_MeasureText, NULL);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
ClayVideoDemo_Uninitialize(); // Clean up image memory and such
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
//+---------------------------------------------------------------------------
|
161
examples/win32_gdi/quickstart.c
Normal file
161
examples/win32_gdi/quickstart.c
Normal file
@ -0,0 +1,161 @@
|
||||
// Must be defined in one file, _before_ #include "clay.h"
|
||||
#define CLAY_IMPLEMENTATION
|
||||
#include "../../clay.h"
|
||||
|
||||
const Clay_Color COLOR_LIGHT = (Clay_Color) {224, 215, 210, 255};
|
||||
const Clay_Color COLOR_RED = (Clay_Color) {168, 66, 28, 255};
|
||||
const Clay_Color COLOR_ORANGE = (Clay_Color) {225, 138, 50, 255};
|
||||
|
||||
void HandleClayErrors(Clay_ErrorData errorData) {
|
||||
// See the Clay_ErrorData struct for more information
|
||||
printf("%s", errorData.errorText.chars);
|
||||
switch(errorData.errorType) {
|
||||
// etc
|
||||
}
|
||||
}
|
||||
|
||||
// Example measure text function
|
||||
static inline Clay_Dimensions MeasureText(Clay_StringSlice text, Clay_TextElementConfig *config, uintptr_t userData) {
|
||||
// Clay_TextElementConfig contains members such as fontId, fontSize, letterSpacing etc
|
||||
// Note: Clay_String->chars is not guaranteed to be null terminated
|
||||
return (Clay_Dimensions) {
|
||||
.width = text.length * config->fontSize, // <- this will only work for monospace fonts, see the renderers/ directory for more advanced text measurement
|
||||
.height = config->fontSize
|
||||
};
|
||||
}
|
||||
|
||||
// Layout config is just a struct that can be declared statically, or inline
|
||||
Clay_ElementDeclaration sidebarItemConfig = (Clay_ElementDeclaration) {
|
||||
.layout = {
|
||||
.sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIXED(50) }
|
||||
},
|
||||
.backgroundColor = COLOR_ORANGE
|
||||
};
|
||||
|
||||
// Re-useable components are just normal functions
|
||||
void SidebarItemComponent() {
|
||||
CLAY(sidebarItemConfig) {
|
||||
// children go here...
|
||||
}
|
||||
}
|
||||
|
||||
// This may be slow for large numbers of images,
|
||||
// consider allocating and initializing interfaces
|
||||
// once outside of this function if it becomes an issue
|
||||
HBITMAP LoadImageToBitmap(LPCWSTR filename)
|
||||
{
|
||||
IWICImagingFactory *imgFactory = NULL;
|
||||
IWICBitmapDecoder *bmpDecoder = NULL;
|
||||
IWICBitmapFrameDecode *bmpFrame = NULL;
|
||||
IWICFormatConverter *formatConverter = NULL;
|
||||
|
||||
HBITMAP hBitmap = NULL;
|
||||
void *bits = NULL;
|
||||
UINT width = 0;
|
||||
UINT height = 0;
|
||||
BITMAPINFO bmi = {0};
|
||||
|
||||
CoInitialize(NULL); // Lets us use Windows COM correctly
|
||||
CoCreateInstance(
|
||||
&CLSID_WICImagingFactory,
|
||||
NULL,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
&IID_IWICImagingFactory,
|
||||
(void **) &imgFactory);
|
||||
imgFactory->lpVtbl->CreateDecoderFromFilename(
|
||||
imgFactory,
|
||||
filename,
|
||||
NULL,
|
||||
GENERIC_READ,
|
||||
WICDecodeMetadataCacheOnLoad,
|
||||
&bmpDecoder);
|
||||
bmpDecoder->lpVtbl->GetFrame(decoder, 0, &bmpFrame);
|
||||
imgFactory->lpVtbl->CreateFormatConverter(factory, &converter);
|
||||
formatConverter->lpVtbl->Initialize(
|
||||
formatConverter,
|
||||
(IWICBitmapSource*) bmpFrame,
|
||||
&GUID_WICPixelFormat32bppPBGRA,
|
||||
WICBitmapDitherTypeNone,
|
||||
NULL,
|
||||
0.0,
|
||||
WICBitmapPaletteTypeCustom);
|
||||
frame->lpVtbl->GetSize(frame, &width, &height);
|
||||
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -((LONG) height); // Negative makes it a top down bitmap
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
hBitmap = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &bits, NULL, 0);
|
||||
formatConverter->lpVtble->CopyPixels(converter, NULL, width * 4, width * height * 4, (BYTE*) bits);
|
||||
if (formatConverter) converter->lpVtbl->Release(formatConverter);
|
||||
if (bmpFrame) frame->lpVtbl->Release(bmpFrame);
|
||||
if (bmpDecoder) decoder->lpVtbl->Release(bmpDecoder);
|
||||
if (imgFactory) factory->lpVtbl->Release(imgFactory);
|
||||
CoUninitialize();
|
||||
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Note: malloc is only used here as an example, any allocator that provides
|
||||
// a pointer to addressable memory of at least totalMemorySize will work
|
||||
uint64_t totalMemorySize = Clay_MinMemorySize();
|
||||
Clay_Arena arena = Clay_CreateArenaWithCapacityAndMemory(totalMemorySize, malloc(totalMemorySize));
|
||||
|
||||
// Note: screenWidth and screenHeight will need to come from your environment, Clay doesn't handle window related tasks
|
||||
Clay_Initialize(arena, (Clay_Dimensions) { screenWidth, screenHeight }, (Clay_ErrorHandler) { HandleClayErrors });
|
||||
|
||||
// Load image resource
|
||||
hBitmap profilePicture = LoadImage(L"reef.jpg");
|
||||
|
||||
while(renderLoop()) { // Will be different for each renderer / environment
|
||||
// Optional: Update internal layout dimensions to support resizing
|
||||
Clay_SetLayoutDimensions((Clay_Dimensions) { screenWidth, screenHeight });
|
||||
// Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling & debug tools
|
||||
Clay_SetPointerState((Clay_Vector2) { mousePositionX, mousePositionY }, isMouseDown);
|
||||
// Optional: Update internal pointer position for handling mouseover / click / touch events - needed for scrolling and debug tools
|
||||
Clay_UpdateScrollContainers(true, (Clay_Vector2) { mouseWheelX, mouseWheelY }, deltaTime);
|
||||
|
||||
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
|
||||
Clay_BeginLayout();
|
||||
|
||||
// An example of laying out a UI with a fixed width sidebar and flexible width main content
|
||||
CLAY({ .id = CLAY_ID("OuterContainer"), .layout = { .sizing = {CLAY_SIZING_GROW(0), CLAY_SIZING_GROW(0)}, .padding = CLAY_PADDING_ALL(16), .childGap = 16 }, .backgroundColor = {250,250,255,255} }) {
|
||||
CLAY({
|
||||
.id = CLAY_ID("SideBar"),
|
||||
.layout = { .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16 },
|
||||
.backgroundColor = COLOR_LIGHT
|
||||
}) {
|
||||
CLAY({ .id = CLAY_ID("ProfilePictureOuter"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0) }, .padding = CLAY_PADDING_ALL(16), .childGap = 16, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }, .backgroundColor = COLOR_RED }) {
|
||||
CLAY({ .id = CLAY_ID("ProfilePicture"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}, .image = { .imageData = profilePicture, .sourceDimensions = {60, 60} } }) {}
|
||||
CLAY_TEXT(CLAY_STRING("Clay - UI Library"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255, 255, 255, 255} }));
|
||||
}
|
||||
|
||||
// Standard C code like loops etc work inside components
|
||||
for (int i = 0; i < 5; i++) {
|
||||
SidebarItemComponent();
|
||||
}
|
||||
|
||||
CLAY({ .id = CLAY_ID("MainContent"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0) } }, .backgroundColor = COLOR_LIGHT }) {}
|
||||
}
|
||||
}
|
||||
|
||||
// All clay layouts are declared between Clay_BeginLayout and Clay_EndLayout
|
||||
Clay_RenderCommandArray renderCommands = Clay_EndLayout();
|
||||
|
||||
// More comprehensive rendering examples can be found in the renderers/ directory
|
||||
for (int i = 0; i < renderCommands.length; i++) {
|
||||
Clay_RenderCommand *renderCommand = &renderCommands.internalArray[i];
|
||||
|
||||
switch (renderCommand->commandType) {
|
||||
case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: {
|
||||
DrawRectangle( renderCommand->boundingBox, renderCommand->renderData.rectangle.backgroundColor);
|
||||
}
|
||||
// ... Implement handling of other command types
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
#define WIN32_LEAN_AND_MEAN // Reduces lots of clunk we aren't using here
|
||||
#include <Windows.h>
|
||||
#include "../../clay.h"
|
||||
|
||||
@ -177,12 +178,58 @@ void Clay_Win32_Render(HWND hwnd, Clay_RenderCommandArray renderCommands)
|
||||
|
||||
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.
|
||||
|
||||
// case CLAY_RENDER_COMMAND_TYPE_IMAGE:
|
||||
// {
|
||||
// // TODO: i couldnt get the win 32 api to load a bitmap.... So im punting on this one :(
|
||||
// break;
|
||||
// }
|
||||
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);
|
||||
@ -238,4 +285,4 @@ static inline Clay_Dimensions Clay_Win32_MeasureText(Clay_StringSlice text, Clay
|
||||
textSize.height = textHeight;
|
||||
|
||||
return textSize;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user