diff --git a/windows/GNUfiles.mk b/windows/GNUfiles.mk index b88c61b6..48fe7da3 100644 --- a/windows/GNUfiles.mk +++ b/windows/GNUfiles.mk @@ -14,6 +14,7 @@ CFILES += \ windows/combobox.c \ windows/container.c \ windows/control.c \ + windows/d2dscratch.c \ windows/datetimepicker.c \ windows/debug.c \ windows/draw.c \ diff --git a/windows/d2dscratch.c b/windows/d2dscratch.c new file mode 100644 index 00000000..8b1851b8 --- /dev/null +++ b/windows/d2dscratch.c @@ -0,0 +1,124 @@ +// 17 april 2016 +#include "uipriv_windows.h" + +// The Direct2D scratch window is a utility for libui internal use to do quick things with Direct2D. +// To use, call newD2DScratch() passing in a subclass procedure. This subclass procedure should handle the msgD2DScratchPaint message, which has the following usage: +// - wParam - 0 +// - lParam - ID2D1RenderTarget * +// - lResult - 0 +// Other messages can also be handled here. + +// TODO allow resize + +#define d2dScratchClass L"libui_d2dScratchClass" + +// TODO clip rect +static HRESULT d2dScratchDoPaint(HWND hwnd, ID2D1RenderTarget *rt) +{ + COLORREF bgcolorref; + D2D1_COLOR_F bgcolor; + + ID2D1RenderTarget_BeginDraw(rt); + + // TODO only clear the clip area + // TODO clear with actual background brush + bgcolorref = GetSysColor(COLOR_BTNFACE); + bgcolor.r = ((float) GetRValue(bgcolorref)) / 255.0; + // due to utter apathy on Microsoft's part, GetGValue() does not work with MSVC's Run-Time Error Checks + // it has not worked since 2008 and they have *never* fixed it + bgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0; + bgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0; + bgcolor.a = 1.0; + ID2D1RenderTarget_Clear(rt, &bgcolor); + + SendMessageW(hwnd, msgD2DScratchPaint, 0, (LPARAM) rt); + + return ID2D1RenderTarget_EndDraw(rt, NULL, NULL); +} + +static LRESULT CALLBACK d2dScratchWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + LONG_PTR init; + ID2D1HwndRenderTarget *rt; + HRESULT hr; + + init = GetWindowLongPtrW(hwnd, 0); + if (!init) { + if (uMsg == WM_CREATE) + SetWindowLongPtrW(hwnd, 0, (LONG_PTR) TRUE); + return DefWindowProcW(hwnd, uMsg, wParam, lParam); + } + + rt = (ID2D1HwndRenderTarget *) GetWindowLongPtrW(hwnd, GWLP_USERDATA); + if (rt == NULL) { + rt = makeHWNDRenderTarget(hwnd); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) rt); + } + + switch (uMsg) { + case WM_DESTROY: + ID2D1HwndRenderTarget_Release(rt); + SetWindowLongPtrW(hwnd, 0, (LONG_PTR) FALSE); + break; + case WM_PAINT: + hr = d2dScratchDoPaint(hwnd, (ID2D1RenderTarget *) rt); + switch (hr) { + case S_OK: + if (ValidateRect(hwnd, NULL) == 0) + logLastError("error validating D2D scratch control rect in d2dScratchWndProc()"); + break; + case D2DERR_RECREATE_TARGET: + // DON'T validate the rect + // instead, simply drop the render target + // we'll get another WM_PAINT and make the render target again + // TODO would this require us to invalidate the entire client area? + ID2D1HwndRenderTarget_Release(rt); + SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) NULL); + break; + default: + logHRESULT("error drawing D2D scratch window in d2dScratchWndProc()", hr); + } + return 0; + case WM_PRINTCLIENT: + // TODO + break; + } + return DefWindowProcW(hwnd, uMsg, wParam, lParam); +} + +ATOM registerD2DScratchClass(HICON hDefaultIcon, HCURSOR hDefaultCursor) +{ + WNDCLASSW wc; + + ZeroMemory(&wc, sizeof (WNDCLASSW)); + wc.lpszClassName = d2dScratchClass; + wc.lpfnWndProc = d2dScratchWndProc; + wc.hInstance = hInstance; + wc.hIcon = hDefaultIcon; + wc.hCursor = hDefaultCursor; + wc.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1); + return RegisterClassW(&wc); +} + +void unregisterD2DScratchClass(void) +{ + if (UnregisterClassW(d2dScratchClass, hInstance) == 0) + logLastError("error unregistering D2D scratch window class in unregisterD2DScratchClass()"); +} + +HWND newD2DScratch(HWND parent, RECT *rect, HMENU controlID, SUBCLASSPROC subclass, DWORD_PTR subclassData) +{ + HWND hwnd; + + hwnd = CreateWindowExW(0, + d2dScratchClass, L"", + WS_CHILD | WS_VISIBLE, + rect->left, rect->top, + rect->right - rect->left, rect->bottom - rect->top, + parent, controlID, hInstance, NULL); + if (hwnd == NULL) + logLastError("error creating D2D scratch window in newD2DScratch()"); + if (SetWindowSubclass(hwnd, subclass, 0, subclassData) == FALSE) + logLastError("error subclassing D2D scratch window in newD2DScratch()"); + return hwnd; +} diff --git a/windows/init.c b/windows/init.c index 17cc3645..b049ee0f 100644 --- a/windows/init.c +++ b/windows/init.c @@ -130,6 +130,7 @@ const char *uiInit(uiInitOptions *o) if (hMessageFont == NULL) return loadLastError("loading default messagebox font; this is the default UI font"); + // TODO rewrite this error message if (initContainer(hDefaultIcon, hDefaultCursor) == 0) return loadLastError("initializing uiMakeContainer() window class"); @@ -165,12 +166,16 @@ const char *uiInit(uiInitOptions *o) if (registerAreaFilter() == 0) return loadLastError("registering uiArea message filter"); + if (registerD2DScratchClass(hDefaultIcon, hDefaultCursor) == 0) + return loadLastError("initializing D2D scratch window class"); + return NULL; } void uiUninit(void) { uninitMenus(); + unregisterD2DScratchClass(); unregisterArea(); uninitDrawText(); uninitDraw(); diff --git a/windows/uipriv_windows.h b/windows/uipriv_windows.h index 48760c5c..5d920293 100644 --- a/windows/uipriv_windows.h +++ b/windows/uipriv_windows.h @@ -18,6 +18,7 @@ enum { msgHSCROLL, msgConsoleEndSession, msgQueued, + msgD2DScratchPaint, }; // init.c @@ -159,6 +160,11 @@ extern void doDrawText(ID2D1RenderTarget *rt, ID2D1Brush *black, double x, doubl // fontdialog.cpp extern void showFontDialog(HWND parent); +// d2dscratch.c +extern ATOM registerD2DScratchClass(HICON, HCURSOR); +extern void unregisterD2DScratchClass(void); +extern HWND newD2DScratch(HWND parent, RECT *rect, HMENU controlID, SUBCLASSPROC subclass, DWORD_PTR subclassData); + #ifdef __cplusplus } #endif