1 module framed.win32;
2 import framed;
3 
4 // dfmt off
5 version (Windows):
6 // dfmt on
7 
8 import core.stdc.stdlib;
9 import core.sys.windows.windows;
10 
11 nothrow:
12 
13 private:
14 
15 struct LinkedNode {
16 	Event ev;
17 	LinkedNode* next;
18 }
19 
20 struct Win32Data {
21 	HWND hwnd;
22 	uint[] buffer;
23 	LinkedNode* evqHead;
24 	LinkedNode* evqTail;
25 	int rWidth;
26 	int rHeight;
27 	bool[cast(size_t) KeyCode.Menu + 1] keysDown;
28 	bool[cast(size_t) MouseButton.Right + 1] buttonsDown;
29 	bool mouseOutside;
30 	bool cursorTracked;
31 }
32 
33 void advanceQueue(Win32Data* self) {
34 	MSG msg;
35 	while (PeekMessageW(&msg, self.hwnd, 0, 0, PM_REMOVE)) {
36 		TranslateMessage(&msg);
37 		DispatchMessage(&msg);
38 	}
39 }
40 
41 void addToEvq(Win32Data* self, Event ev) {
42 	LinkedNode* node = cast(LinkedNode*) malloc(LinkedNode.sizeof);
43 	node.ev = ev;
44 	node.next = null;
45 	if (self.evqTail == null) {
46 		self.evqHead = node;
47 	}
48 	else {
49 		self.evqTail.next = node;
50 	}
51 	self.evqTail = node;
52 }
53 
54 int getWidth(Win32Data* self) {
55 	RECT r;
56 	if (!GetClientRect(self.hwnd, &r)) {
57 		return 0;
58 	}
59 	return cast(int)(r.right - r.left);
60 }
61 
62 int getHeight(Win32Data* self) {
63 	RECT r;
64 	if (!GetClientRect(self.hwnd, &r)) {
65 		return 0;
66 	}
67 	return cast(int)(r.bottom - r.top);
68 }
69 
70 void close(Win32Data* self) {
71 	DestroyWindow(self.hwnd);
72 	free(self);
73 }
74 
75 void update(Win32Data* self, uint[] buffer) {
76 	self.buffer = buffer;
77 	InvalidateRect(self.hwnd, NULL, TRUE);
78 }
79 
80 void yield(Win32Data* self) {
81 	MSG msg;
82 	while (self.evqHead == null) {
83 		GetMessage(&msg, self.hwnd, 0, 0);
84 		TranslateMessage(&msg);
85 		DispatchMessage(&msg);
86 	}
87 }
88 
89 bool evqEmpty(Win32Data* self) {
90 	advanceQueue(self);
91 	return self.evqHead == null;
92 }
93 
94 Event evqFront(Win32Data* self) {
95 	advanceQueue(self);
96 	assert(self.evqHead != null);
97 	return self.evqHead.ev;
98 }
99 
100 void evqPopFront(Win32Data* self) {
101 	advanceQueue(self);
102 	auto next = self.evqHead.next;
103 	free(self.evqHead);
104 	self.evqHead = next;
105 	if (self.evqHead == null) {
106 		self.evqTail = null;
107 	}
108 }
109 
110 int translateKey(WPARAM wParam, LPARAM lParam) {
111 	WPARAM vk = wParam;
112 	UINT scancode = (lParam >> 16) & 0xFF;
113 	int extended = (lParam >> 24) & 1;
114 
115 	switch (wParam) {
116 	case VK_SHIFT:
117 		vk = MapVirtualKey(scancode, MAPVK_VSC_TO_VK_EX);
118 		break;
119 	case VK_CONTROL:
120 		vk = extended ? VK_RCONTROL : VK_LCONTROL;
121 		break;
122 	case VK_MENU:
123 		vk = extended ? VK_RMENU : VK_LMENU;
124 		break;
125 	default:
126 		break;
127 	}
128 
129 	// dfmt off
130 	switch (vk) {
131 	case VK_SPACE: return KeyCode.Space;
132 	case VK_OEM_7: return KeyCode.Quote;
133 	case VK_OEM_COMMA: return KeyCode.Comma;
134 	case VK_OEM_MINUS: return KeyCode.Minus;
135 	case VK_OEM_PERIOD: return KeyCode.Period;
136 	case VK_OEM_2: return KeyCode.Slash;
137 	case '0': .. case '9': return KeyCode.D0 + cast(int)(wParam - '0');
138 	case VK_OEM_1: return KeyCode.Semicolon;
139 	case VK_OEM_PLUS: return KeyCode.Equal;
140 	case 'A': .. case 'Z': return KeyCode.A + cast(int)(wParam - 'A');
141 	case VK_OEM_4: return KeyCode.LeftBracket;
142 	case VK_OEM_5: return KeyCode.Backslash;
143 	case VK_OEM_6: return KeyCode.RightBracket;
144 	case VK_OEM_3: return KeyCode.Backtick;
145 	case VK_ESCAPE: return KeyCode.Escape;
146 	case VK_RETURN: return KeyCode.Enter;
147 	case VK_TAB: return KeyCode.Tab;
148 	case VK_BACK: return KeyCode.Backspace;
149 	case VK_INSERT: return KeyCode.Insert;
150 	case VK_DELETE: return KeyCode.Delete;
151 	case VK_RIGHT: return KeyCode.Right;
152 	case VK_LEFT: return KeyCode.Left;
153 	case VK_DOWN: return KeyCode.Down;
154 	case VK_UP: return KeyCode.Up;
155 	case VK_PRIOR: return KeyCode.PageUp;
156 	case VK_NEXT: return KeyCode.PageDown;
157 	case VK_HOME: return KeyCode.Home;
158 	case VK_END: return KeyCode.End;
159 	case VK_CAPITAL: return KeyCode.CapsLock;
160 	case VK_SCROLL: return KeyCode.ScrollLock;
161 	case VK_NUMLOCK: return KeyCode.NumLock;
162 	case VK_SNAPSHOT: return KeyCode.PrintScreen;
163 	case VK_PAUSE: return KeyCode.Pause;
164 	case VK_F1: .. case VK_F24: return KeyCode.F1 + cast(int)(wParam - VK_F1);
165 	case VK_NUMPAD0: .. case VK_NUMPAD9: return KeyCode.Numpad0 + cast(int)(wParam - VK_NUMPAD0);
166 	case VK_DECIMAL: return KeyCode.NumpadPeriod;
167 	case VK_DIVIDE: return KeyCode.NumpadSlash;
168 	case VK_MULTIPLY: return KeyCode.NumpadMultiply;
169 	case VK_SUBTRACT: return KeyCode.NumpadMinus;
170 	case VK_ADD: return KeyCode.NumpadPlus;
171 	// TODO: KeyCode.NumpadEqual
172 	case VK_LSHIFT: return KeyCode.LeftShift;
173 	case VK_LCONTROL: return KeyCode.LeftCtrl;
174 	case VK_LMENU: return KeyCode.LeftAlt;
175 	case VK_LWIN: return KeyCode.LeftSuper;
176 	case VK_RSHIFT: return KeyCode.RightShift;
177 	case VK_RCONTROL: return KeyCode.RightCtrl;
178 	case VK_RMENU: return KeyCode.RightAlt;
179 	case VK_RWIN: return KeyCode.RightSuper;
180 	case VK_APPS: return KeyCode.Menu;
181 	default:
182 		import core.stdc.stdio : printf;
183 		debug printf("Unrecognized virtual key code: %x\n", cast(int) wParam);
184 		return -1;
185 	}
186 	// dfmt on
187 }
188 
189 extern (Windows) LRESULT wndProc(HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam) nothrow {
190 	Win32Data* self = cast(Win32Data*) GetWindowLongPtr(hwnd, GWLP_USERDATA);
191 
192 	if (self == null) {
193 		return DefWindowProc(hwnd, Msg, wParam, lParam);
194 	}
195 
196 	switch (Msg) {
197 	case WM_CLOSE:
198 		addToEvq(self, Event(EventType.CloseRequest));
199 		return 0;
200 	case WM_ENTERSIZEMOVE:
201 		self.rWidth = getWidth(self);
202 		self.rHeight = getHeight(self);
203 		break;
204 	case WM_EXITSIZEMOVE:
205 		auto ev = Event(EventType.Resize);
206 		if (self.rWidth != getWidth(self) || self.rHeight != getHeight(self)) {
207 			ev.width = self.rWidth;
208 			ev.height = self.rHeight;
209 			self.rWidth = getWidth(self);
210 			self.rHeight = getHeight(self);
211 			addToEvq(self, ev);
212 		}
213 		break;
214 	case WM_SIZE:
215 		if (wParam == 0 && self.rWidth != 0 && self.rHeight != 0)
216 			break;
217 		auto ev = Event(EventType.Resize);
218 		if (self.rWidth != getWidth(self) || self.rHeight != getHeight(self)) {
219 			ev.width = self.rWidth;
220 			ev.height = self.rHeight;
221 			self.rWidth = getWidth(self);
222 			self.rHeight = getHeight(self);
223 			addToEvq(self, ev);
224 		}
225 		break;
226 	case WM_MOUSEMOVE:
227 		if (!self.cursorTracked) {
228 			self.cursorTracked = true;
229 
230 			TRACKMOUSEEVENT track;
231 			track.cbSize = TRACKMOUSEEVENT.sizeof;
232 			track.dwFlags = TME_LEAVE;
233 			track.hwndTrack = self.hwnd;
234 			track.dwHoverTime = 0;
235 
236 			TrackMouseEvent(&track);
237 		}
238 
239 		auto ev = Event(EventType.MouseMove);
240 		ev.x = cast(int)(lParam & 0xFFFF);
241 		ev.y = cast(int)(lParam >> 16);
242 		if (self.mouseOutside) {
243 			self.mouseOutside = false;
244 			addToEvq(self, Event(EventType.MouseEnter));
245 		}
246 		addToEvq(self, ev);
247 		break;
248 	case WM_MOUSELEAVE:
249 		self.mouseOutside = true;
250 		self.cursorTracked = false;
251 		addToEvq(self, Event(EventType.MouseLeave));
252 		break;
253 	case WM_LBUTTONDOWN:
254 	case WM_MBUTTONDOWN:
255 	case WM_RBUTTONDOWN:
256 	case WM_LBUTTONUP:
257 	case WM_MBUTTONUP:
258 	case WM_RBUTTONUP:
259 		MouseButton button;
260 
261 		if (Msg == WM_LBUTTONDOWN || Msg == WM_LBUTTONUP) {
262 			button = MouseButton.Left;
263 		}
264 		else if (Msg == WM_MBUTTONDOWN || Msg == WM_MBUTTONUP) {
265 			button = MouseButton.Middle;
266 		}
267 		else if (Msg == WM_RBUTTONDOWN || Msg == WM_RBUTTONUP) {
268 			button = MouseButton.Right;
269 		}
270 
271 		EventType type;
272 
273 		bool set = true;
274 		foreach (v; self.buttonsDown) {
275 			if (v) {
276 				set = false;
277 				break;
278 			}
279 		}
280 
281 		if (set) {
282 			SetCapture(self.hwnd);
283 		}
284 
285 		if (Msg == WM_LBUTTONDOWN || Msg == WM_MBUTTONDOWN
286 			|| Msg == WM_RBUTTONDOWN) {
287 			type = EventType.MouseDown;
288 			if (self.buttonsDown[button])
289 				return 0;
290 			self.buttonsDown[button] = true;
291 		}
292 		else {
293 			type = EventType.MouseUp;
294 			if (!self.buttonsDown[button])
295 				return 0;
296 			self.buttonsDown[button] = false;
297 		}
298 
299 		set = true;
300 		foreach (v; self.buttonsDown) {
301 			if (v) {
302 				set = false;
303 				break;
304 			}
305 		}
306 
307 		if (set) {
308 			ReleaseCapture();
309 		}
310 
311 		auto ev = Event(type);
312 		ev.button = button;
313 		addToEvq(self, ev);
314 
315 		return 0;
316 	case WM_KEYDOWN:
317 	case WM_SYSKEYDOWN:
318 		auto ev = Event(EventType.KeyDown);
319 		int key = translateKey(wParam, lParam);
320 		if (key == -1) {
321 			break;
322 		}
323 		ev.key = cast(KeyCode) key;
324 		if (self.keysDown[ev.key]) {
325 			ev.type = EventType.KeyRepeat;
326 		}
327 		else {
328 			self.keysDown[ev.key] = true;
329 		}
330 		addToEvq(self, ev);
331 		break;
332 	case WM_KEYUP:
333 	case WM_SYSKEYUP:
334 		auto ev = Event(EventType.KeyUp);
335 		int key = translateKey(wParam, lParam);
336 		if (key == -1) {
337 			break;
338 		}
339 		ev.key = cast(KeyCode) key;
340 		self.keysDown[ev.key] = false;
341 		addToEvq(self, ev);
342 		break;
343 	case WM_ERASEBKGND:
344 		return TRUE;
345 	case WM_PAINT:
346 		PAINTSTRUCT ps;
347 		HDC hdc = BeginPaint(hwnd, &ps);
348 
349 		RECT r;
350 		GetClientRect(hwnd, &r);
351 
352 		int width = cast(int)(r.right - r.left);
353 		int height = cast(int)(r.bottom - r.top);
354 
355 		if (width == 0 || height == 0) {
356 			return 0;
357 		}
358 
359 		BYTE[BITMAPINFO.bmiColors.offsetof + (3 * DWORD.sizeof)] bitmapinfo;
360 		BITMAPINFOHEADER* bih = cast(BITMAPINFOHEADER*) bitmapinfo;
361 		bih.biSize = BITMAPINFOHEADER.sizeof;
362 		bih.biWidth = width;
363 		bih.biHeight = height;
364 		bih.biPlanes = 1;
365 		bih.biBitCount = 32;
366 		bih.biCompression = BI_BITFIELDS;
367 		DWORD* pMasks = cast(DWORD*)&bitmapinfo[bih.biSize];
368 		pMasks[0] = 0xFF0000;
369 		pMasks[1] = 0x00FF00;
370 		pMasks[2] = 0x0000FF;
371 
372 		if (self.buffer.length == width * cast(size_t) height) {
373 			StretchDIBits(hdc, 0, 0, width, height, 0, height + 1, width,
374 					-height, cast(void*) self.buffer.ptr,
375 					cast(BITMAPINFO*) bih, DIB_RGB_COLORS, SRCCOPY);
376 		}
377 
378 		EndPaint(hwnd, &ps);
379 
380 		return 0;
381 	default:
382 		break;
383 	}
384 
385 	return DefWindowProc(hwnd, Msg, wParam, lParam);
386 }
387 
388 HMODULE hInstance;
389 
390 bool win32Inited = false;
391 void win32Init() {
392 	if (win32Inited)
393 		return;
394 
395 	win32Inited = true;
396 
397 	hInstance = GetModuleHandle(NULL);
398 
399 	WNDCLASSEX wc;
400 	wc.cbSize = WNDCLASSEX.sizeof;
401 	wc.style = 0;
402 	wc.lpfnWndProc = &wndProc;
403 	wc.cbClsExtra = 0;
404 	wc.cbWndExtra = 0;
405 	wc.hInstance = hInstance;
406 	wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
407 	wc.hCursor = LoadCursor(NULL, IDC_ARROW);
408 	wc.hbrBackground = cast(HBRUSH) GetStockObject(BLACK_BRUSH);
409 	wc.lpszMenuName = NULL;
410 	wc.lpszClassName = "WindowClass"w.ptr;
411 	wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
412 
413 	if (!RegisterClassEx(&wc)) {
414 		assert(0);
415 	}
416 }
417 
418 wchar* toWStr(string s) {
419 	int length = MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, s.ptr, cast(int) s.length, NULL, 0,);
420 	wchar* result = cast(wchar*) calloc(length, wchar.sizeof);
421 	MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, s.ptr, cast(int) s.length, result, length,);
422 	return result;
423 }
424 
425 package:
426 
427 Framebuffer win32OpenWindow(WindowOptions options) {
428 	WindowData* data = cast(WindowData*) malloc(WindowData.sizeof);
429 	data.getWidth = cast(int function(void*) nothrow)&getWidth;
430 	data.getHeight = cast(int function(void*) nothrow)&getHeight;
431 	data.close = cast(void function(void*) nothrow)&close;
432 	data.update = cast(void function(void*, uint[]) nothrow)&update;
433 	data.yield = cast(void function(void*) nothrow)&yield;
434 	data.evqEmpty = cast(bool function(void*) nothrow)&evqEmpty;
435 	data.evqFront = cast(Event function(void*) nothrow)&evqFront;
436 	data.evqPopFront = cast(void function(void*) nothrow)&evqPopFront;
437 
438 	win32Init();
439 
440 	wchar* wtitle = options.title.toWStr;
441 
442 	int style = WS_OVERLAPPEDWINDOW;
443 	if (!options.resizable) {
444 		style ^= WS_THICKFRAME | WS_MAXIMIZEBOX;
445 	}
446 
447 	RECT r;
448 	r.left = 0;
449 	r.top = 0;
450 	r.right = options.initialWidth;
451 	r.bottom = options.initialHeight;
452 	AdjustWindowRect(&r, style, FALSE);
453 
454 	// dfmt off
455 	HWND hwnd = CreateWindowW(
456 		"WindowClass"w.ptr,
457 		wtitle,
458 		style,
459 		0, 0,
460 		r.right - r.left, r.bottom - r.top,
461 		GetDesktopWindow(),
462 		NULL, hInstance, NULL,
463 	);
464 	// dfmt on
465 
466 	free(wtitle);
467 
468 	Win32Data* self = cast(Win32Data*) calloc(Win32Data.sizeof, 1);
469 	self.hwnd = hwnd;
470 	self.rWidth = options.initialWidth;
471 	self.rHeight = options.initialHeight;
472 	self.mouseOutside = true;
473 	self.cursorTracked = false;
474 	self.buffer = null;
475 	self.evqHead = null;
476 	self.evqTail = null;
477 
478 	SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR) self);
479 
480 	data.udata = self;
481 
482 	ShowWindow(hwnd, SW_SHOW);
483 
484 	return Framebuffer(FramebufferType.Window, data);
485 }