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