aboutsummaryrefslogtreecommitdiff
path: root/src/libui_sdl/libui/windows/d2dscratch.cpp
blob: 6dc2ba5fd3332ae270277749ea960dcf06a865d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// 17 april 2016
#include "uipriv_windows.hpp"

// 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
// You can optionally also handle msgD2DScratchLButtonDown, which is sent when the left mouse button is either pressed for the first time or held while the mouse is moving.
// - wParam - position in DIPs, as D2D1_POINT_2F *
// - lParam - size of render target in DIPs, as D2D1_SIZE_F *
// - 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;

	rt->BeginDraw();

	// 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
	// TODO now that -RTCc has just been deprecated entirely, should we switch back?
	bgcolor.g = ((float) ((BYTE) ((bgcolorref & 0xFF00) >> 8))) / 255.0;
	bgcolor.b = ((float) GetBValue(bgcolorref)) / 255.0;
	bgcolor.a = 1.0;
	rt->Clear(&bgcolor);

	SendMessageW(hwnd, msgD2DScratchPaint, 0, (LPARAM) rt);

	return rt->EndDraw(NULL, NULL);
}

static void d2dScratchDoLButtonDown(HWND hwnd, ID2D1RenderTarget *rt, LPARAM lParam)
{
	double xpix, ypix;
	FLOAT dpix, dpiy;
	D2D1_POINT_2F pos;
	D2D1_SIZE_F size;

	xpix = (double) GET_X_LPARAM(lParam);
	ypix = (double) GET_Y_LPARAM(lParam);
	// these are in pixels; we need points
	// TODO separate the function from areautil.cpp?
	rt->GetDpi(&dpix, &dpiy);
	pos.x = (xpix * 96) / dpix;
	pos.y = (ypix * 96) / dpiy;

	size = realGetSize(rt);

	SendMessageW(hwnd, msgD2DScratchLButtonDown, (WPARAM) (&pos), (LPARAM) (&size));
}

static LRESULT CALLBACK d2dScratchWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	LONG_PTR init;
	ID2D1HwndRenderTarget *rt;
	ID2D1DCRenderTarget *dcrt;
	RECT client;
	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:
		rt->Release();
		SetWindowLongPtrW(hwnd, 0, (LONG_PTR) FALSE);
		break;
	case WM_PAINT:
		hr = d2dScratchDoPaint(hwnd, rt);
		switch (hr) {
		case S_OK:
			if (ValidateRect(hwnd, NULL) == 0)
				logLastError(L"error validating D2D scratch control rect");
			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?
			rt->Release();
			SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR) NULL);
			break;
		default:
			logHRESULT(L"error drawing D2D scratch window", hr);
		}
		return 0;
	case WM_PRINTCLIENT:
		uiWindowsEnsureGetClientRect(hwnd, &client);
		dcrt = makeHDCRenderTarget((HDC) wParam, &client);
		hr = d2dScratchDoPaint(hwnd, dcrt);
		if (hr != S_OK)
			logHRESULT(L"error printing D2D scratch window client area", hr);
		dcrt->Release();
		return 0;
	case WM_LBUTTONDOWN:
		d2dScratchDoLButtonDown(hwnd, rt, lParam);
		return 0;
	case WM_MOUSEMOVE:
		// also send LButtonDowns when dragging
		if ((wParam & MK_LBUTTON) != 0)
			d2dScratchDoLButtonDown(hwnd, rt, lParam);
		return 0;
	}
	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);
	wc.cbWndExtra = sizeof (LONG_PTR);			// for the init status
	return RegisterClassW(&wc);
}

void unregisterD2DScratchClass(void)
{
	if (UnregisterClassW(d2dScratchClass, hInstance) == 0)
		logLastError(L"error unregistering D2D scratch window class");
}

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)
		// TODO return decoy window
		logLastError(L"error creating D2D scratch window");
	if (SetWindowSubclass(hwnd, subclass, 0, subclassData) == FALSE)
		logLastError(L"error subclassing D2D scratch window");
	return hwnd;
}