Implemented text and clipping primitives in the frontend, and added
[sgt/puzzles] / windows.c
CommitLineData
720a8fb7 1/*
2 * windows.c: Windows front end for my puzzle collection.
3 */
4e7ef6e6 4
5#include <windows.h>
6
7#include <stdio.h>
4efb3868 8#include <assert.h>
4e7ef6e6 9#include <stdarg.h>
10#include <stdlib.h>
11#include <time.h>
12
13#include "puzzles.h"
14
eb2ad6f1 15#define IDM_NEW 0x0010
16#define IDM_RESTART 0x0020
17#define IDM_UNDO 0x0030
18#define IDM_REDO 0x0040
19#define IDM_QUIT 0x0050
20#define IDM_PRESETS 0x0100
21
c71454c0 22#ifdef DEBUG
23static FILE *debug_fp = NULL;
24static HANDLE debug_hdl = INVALID_HANDLE_VALUE;
25static int debug_got_console = 0;
26
27void dputs(char *buf)
28{
29 DWORD dw;
30
31 if (!debug_got_console) {
32 if (AllocConsole()) {
33 debug_got_console = 1;
34 debug_hdl = GetStdHandle(STD_OUTPUT_HANDLE);
35 }
36 }
37 if (!debug_fp) {
38 debug_fp = fopen("debug.log", "w");
39 }
40
41 if (debug_hdl != INVALID_HANDLE_VALUE) {
42 WriteFile(debug_hdl, buf, strlen(buf), &dw, NULL);
43 }
44 fputs(buf, debug_fp);
45 fflush(debug_fp);
46}
47
48void debug_printf(char *fmt, ...)
49{
50 char buf[4096];
51 va_list ap;
52
53 va_start(ap, fmt);
54 vsprintf(buf, fmt, ap);
55 dputs(buf);
56 va_end(ap);
57}
58
59#define debug(x) (debug_printf x)
60
61#else
62
63#define debug(x)
64
65#endif
66
4efb3868 67struct font {
68 HFONT font;
69 int type;
70 int size;
71};
72
4e7ef6e6 73struct frontend {
74 midend_data *me;
75 HWND hwnd;
76 HBITMAP bitmap, prevbm;
77 HDC hdc_bm;
78 COLORREF *colours;
79 HBRUSH *brushes;
80 HPEN *pens;
4efb3868 81 HRGN clip;
4e7ef6e6 82 UINT timer;
eb2ad6f1 83 int npresets;
84 game_params **presets;
4efb3868 85 struct font *fonts;
86 int nfonts, fontsize;
4e7ef6e6 87};
88
89void fatal(char *fmt, ...)
90{
91 char buf[2048];
92 va_list ap;
93
94 va_start(ap, fmt);
95 vsprintf(buf, fmt, ap);
96 va_end(ap);
97
98 MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
99
100 exit(1);
101}
102
103void frontend_default_colour(frontend *fe, float *output)
104{
105 DWORD c = GetSysColor(COLOR_MENU); /* ick */
106
107 output[0] = (float)(GetRValue(c) / 255.0);
108 output[1] = (float)(GetGValue(c) / 255.0);
109 output[2] = (float)(GetBValue(c) / 255.0);
110}
111
4efb3868 112void clip(frontend *fe, int x, int y, int w, int h)
113{
114 if (!fe->clip) {
115 fe->clip = CreateRectRgn(0, 0, 1, 1);
116 GetClipRgn(fe->hdc_bm, fe->clip);
117 }
118
119 IntersectClipRect(fe->hdc_bm, x, y, x+w, y+h);
120}
121
122void unclip(frontend *fe)
123{
124 assert(fe->clip);
125 SelectClipRgn(fe->hdc_bm, fe->clip);
126}
127
128void draw_text(frontend *fe, int x, int y, int fonttype, int fontsize,
129 int align, int colour, char *text)
130{
131 int i;
132
133 /*
134 * Find or create the font.
135 */
136 for (i = 0; i < fe->nfonts; i++)
137 if (fe->fonts[i].type == fonttype && fe->fonts[i].size == fontsize)
138 break;
139
140 if (i == fe->nfonts) {
141 if (fe->fontsize <= fe->nfonts) {
142 fe->fontsize = fe->nfonts + 10;
143 fe->fonts = sresize(fe->fonts, fe->fontsize, struct font);
144 }
145
146 fe->nfonts++;
147
148 fe->fonts[i].type = fonttype;
149 fe->fonts[i].size = fontsize;
150
151 /*
152 * FIXME: Really I should make at least _some_ effort to
153 * pick the correct font.
154 */
155 fe->fonts[i].font = CreateFont(-fontsize, 0, 0, 0, 0,
156 FALSE, FALSE, FALSE, DEFAULT_CHARSET,
157 OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
158 DEFAULT_QUALITY,
159 (fonttype == FONT_FIXED ?
160 FIXED_PITCH | FF_DONTCARE :
161 VARIABLE_PITCH | FF_SWISS),
162 NULL);
163 }
164
165 /*
166 * Position and draw the text.
167 */
168 {
169 HFONT oldfont;
170 TEXTMETRIC tm;
171 SIZE size;
172
173 oldfont = SelectObject(fe->hdc_bm, fe->fonts[i].font);
174 if (GetTextMetrics(fe->hdc_bm, &tm)) {
175 if (align & ALIGN_VCENTRE)
176 y -= (tm.tmAscent+tm.tmDescent)/2;
177 else
178 y -= tm.tmAscent;
179 }
180 if (GetTextExtentPoint32(fe->hdc_bm, text, strlen(text), &size)) {
181 if (align & ALIGN_HCENTRE)
182 x -= size.cx / 2;
183 else if (align & ALIGN_HRIGHT)
184 x -= size.cx;
185 }
186 SetBkMode(fe->hdc_bm, TRANSPARENT);
187 TextOut(fe->hdc_bm, x, y, text, strlen(text));
188 SelectObject(fe->hdc_bm, oldfont);
189 }
190}
191
4e7ef6e6 192void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
193{
fbdd7610 194 if (w == 1 && h == 1) {
195 /*
196 * Rectangle() appears to get uppity if asked to draw a 1x1
197 * rectangle, presumably on the grounds that that's beneath
198 * its dignity and you ought to be using SetPixel instead.
199 * So I will.
200 */
201 SetPixel(fe->hdc_bm, x, y, fe->colours[colour]);
202 } else {
203 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
204 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
205 Rectangle(fe->hdc_bm, x, y, x+w, y+h);
206 SelectObject(fe->hdc_bm, oldbrush);
207 SelectObject(fe->hdc_bm, oldpen);
208 }
4e7ef6e6 209}
210
211void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
212{
213 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
214 MoveToEx(fe->hdc_bm, x1, y1, NULL);
215 LineTo(fe->hdc_bm, x2, y2);
216 SetPixel(fe->hdc_bm, x2, y2, fe->colours[colour]);
217 SelectObject(fe->hdc_bm, oldpen);
218}
219
220void draw_polygon(frontend *fe, int *coords, int npoints,
221 int fill, int colour)
222{
223 POINT *pts = snewn(npoints+1, POINT);
224 int i;
225
226 for (i = 0; i <= npoints; i++) {
227 int j = (i < npoints ? i : 0);
228 pts[i].x = coords[j*2];
229 pts[i].y = coords[j*2+1];
230 }
231
232 if (fill) {
233 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
234 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
235 Polygon(fe->hdc_bm, pts, npoints);
236 SelectObject(fe->hdc_bm, oldbrush);
237 SelectObject(fe->hdc_bm, oldpen);
238 } else {
239 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
240 Polyline(fe->hdc_bm, pts, npoints+1);
241 SelectObject(fe->hdc_bm, oldpen);
242 }
243
244 sfree(pts);
245}
246
247void start_draw(frontend *fe)
248{
249 HDC hdc_win;
250 hdc_win = GetDC(fe->hwnd);
251 fe->hdc_bm = CreateCompatibleDC(hdc_win);
252 fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
253 ReleaseDC(fe->hwnd, hdc_win);
4efb3868 254 fe->clip = NULL;
4e7ef6e6 255}
256
257void draw_update(frontend *fe, int x, int y, int w, int h)
258{
259 RECT r;
260
261 r.left = x;
262 r.top = y;
263 r.right = x + w;
264 r.bottom = y + h;
265
266 InvalidateRect(fe->hwnd, &r, FALSE);
267}
268
269void end_draw(frontend *fe)
270{
271 SelectObject(fe->hdc_bm, fe->prevbm);
272 DeleteDC(fe->hdc_bm);
4efb3868 273 if (fe->clip) {
274 DeleteObject(fe->clip);
275 fe->clip = NULL;
276 }
4e7ef6e6 277}
278
279void deactivate_timer(frontend *fe)
280{
281 KillTimer(fe->hwnd, fe->timer);
282 fe->timer = 0;
283}
284
285void activate_timer(frontend *fe)
286{
287 fe->timer = SetTimer(fe->hwnd, fe->timer, 20, NULL);
288}
289
290static frontend *new_window(HINSTANCE inst)
291{
292 frontend *fe;
293 int x, y;
294 RECT r;
295 HDC hdc;
296
297 fe = snew(frontend);
298 fe->me = midend_new(fe);
299 midend_new_game(fe->me, NULL);
300 midend_size(fe->me, &x, &y);
301
302 fe->timer = 0;
303
304 {
305 int i, ncolours;
306 float *colours;
307
308 colours = midend_colours(fe->me, &ncolours);
309
310 fe->colours = snewn(ncolours, COLORREF);
311 fe->brushes = snewn(ncolours, HBRUSH);
312 fe->pens = snewn(ncolours, HPEN);
313
314 for (i = 0; i < ncolours; i++) {
315 fe->colours[i] = RGB(255 * colours[i*3+0],
316 255 * colours[i*3+1],
317 255 * colours[i*3+2]);
318 fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
319 if (!fe->brushes[i])
320 MessageBox(fe->hwnd, "ooh", "eck", MB_OK);
321 fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
322 }
323 }
324
325 r.left = r.top = 0;
326 r.right = x;
327 r.bottom = y;
328 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
329 (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
eb2ad6f1 330 TRUE, 0);
4e7ef6e6 331
0c490335 332 fe->hwnd = CreateWindowEx(0, game_name, game_name,
4e7ef6e6 333 WS_OVERLAPPEDWINDOW &~
334 (WS_THICKFRAME | WS_MAXIMIZEBOX),
335 CW_USEDEFAULT, CW_USEDEFAULT,
336 r.right - r.left, r.bottom - r.top,
337 NULL, NULL, inst, NULL);
338
eb2ad6f1 339 {
340 HMENU bar = CreateMenu();
341 HMENU menu = CreateMenu();
342
343 AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT)menu, "Game");
344 AppendMenu(menu, MF_ENABLED, IDM_NEW, "New");
345 AppendMenu(menu, MF_ENABLED, IDM_RESTART, "Restart");
346
347 if ((fe->npresets = midend_num_presets(fe->me)) > 0) {
348 HMENU sub = CreateMenu();
349 int i;
350
351 AppendMenu(menu, MF_ENABLED|MF_POPUP, (UINT)sub, "Type");
352
353 fe->presets = snewn(fe->npresets, game_params *);
354
355 for (i = 0; i < fe->npresets; i++) {
356 char *name;
357
358 midend_fetch_preset(fe->me, i, &name, &fe->presets[i]);
359
360 /*
361 * FIXME: we ought to go through and do something
362 * with ampersands here.
363 */
364
365 AppendMenu(sub, MF_ENABLED, IDM_PRESETS + 0x10 * i, name);
366 }
367 }
368
369 AppendMenu(menu, MF_SEPARATOR, 0, 0);
370 AppendMenu(menu, MF_ENABLED, IDM_UNDO, "Undo");
371 AppendMenu(menu, MF_ENABLED, IDM_REDO, "Redo");
372 AppendMenu(menu, MF_SEPARATOR, 0, 0);
373 AppendMenu(menu, MF_ENABLED, IDM_QUIT, "Exit");
374 SetMenu(fe->hwnd, bar);
375 }
376
4e7ef6e6 377 hdc = GetDC(fe->hwnd);
378 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
379 ReleaseDC(fe->hwnd, hdc);
380
381 SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
382
383 ShowWindow(fe->hwnd, SW_NORMAL);
384 SetForegroundWindow(fe->hwnd);
385
386 midend_redraw(fe->me);
387
388 return fe;
389}
390
391static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
392 WPARAM wParam, LPARAM lParam)
393{
394 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
395
396 switch (message) {
397 case WM_CLOSE:
398 DestroyWindow(hwnd);
399 return 0;
eb2ad6f1 400 case WM_COMMAND:
401 switch (wParam & ~0xF) { /* low 4 bits reserved to Windows */
402 case IDM_NEW:
403 if (!midend_process_key(fe->me, 0, 0, 'n'))
404 PostQuitMessage(0);
405 break;
406 case IDM_RESTART:
407 if (!midend_process_key(fe->me, 0, 0, 'r'))
408 PostQuitMessage(0);
409 break;
410 case IDM_UNDO:
411 if (!midend_process_key(fe->me, 0, 0, 'u'))
412 PostQuitMessage(0);
413 break;
414 case IDM_REDO:
415 if (!midend_process_key(fe->me, 0, 0, '\x12'))
416 PostQuitMessage(0);
417 break;
418 case IDM_QUIT:
419 if (!midend_process_key(fe->me, 0, 0, 'q'))
420 PostQuitMessage(0);
421 break;
422 default:
423 {
424 int p = ((wParam &~ 0xF) - IDM_PRESETS) / 0x10;
425
426 if (p >= 0 && p < fe->npresets) {
427 RECT r;
428 HDC hdc;
429 int x, y;
430
431 midend_set_params(fe->me, fe->presets[p]);
432 midend_new_game(fe->me, NULL);
433 midend_size(fe->me, &x, &y);
434
435 r.left = r.top = 0;
436 r.right = x;
437 r.bottom = y;
438 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
439 (WS_THICKFRAME | WS_MAXIMIZEBOX |
440 WS_OVERLAPPED),
441 TRUE, 0);
442
443 SetWindowPos(fe->hwnd, NULL, 0, 0,
444 r.right - r.left, r.bottom - r.top,
445 SWP_NOMOVE | SWP_NOZORDER);
446
447 DeleteObject(fe->bitmap);
448
449 hdc = GetDC(fe->hwnd);
450 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
451 ReleaseDC(fe->hwnd, hdc);
452
453 midend_redraw(fe->me);
454 }
455 }
456 break;
457 }
458 break;
4e7ef6e6 459 case WM_DESTROY:
460 PostQuitMessage(0);
461 return 0;
462 case WM_PAINT:
463 {
464 PAINTSTRUCT p;
465 HDC hdc, hdc2;
466 HBITMAP prevbm;
467
468 hdc = BeginPaint(hwnd, &p);
469 hdc2 = CreateCompatibleDC(hdc);
470 prevbm = SelectObject(hdc2, fe->bitmap);
471 BitBlt(hdc,
472 p.rcPaint.left, p.rcPaint.top,
473 p.rcPaint.right - p.rcPaint.left,
474 p.rcPaint.bottom - p.rcPaint.top,
475 hdc2,
476 p.rcPaint.left, p.rcPaint.top,
477 SRCCOPY);
478 SelectObject(hdc2, prevbm);
479 DeleteDC(hdc2);
480 EndPaint(hwnd, &p);
481 }
482 return 0;
483 case WM_KEYDOWN:
484 {
485 int key = -1;
486
487 switch (wParam) {
488 case VK_LEFT: key = CURSOR_LEFT; break;
489 case VK_RIGHT: key = CURSOR_RIGHT; break;
490 case VK_UP: key = CURSOR_UP; break;
491 case VK_DOWN: key = CURSOR_DOWN; break;
c71454c0 492 /*
493 * Diagonal keys on the numeric keypad.
494 */
495 case VK_PRIOR:
496 if (!(lParam & 0x01000000)) key = CURSOR_UP_RIGHT;
497 break;
498 case VK_NEXT:
499 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_RIGHT;
500 break;
501 case VK_HOME:
502 if (!(lParam & 0x01000000)) key = CURSOR_UP_LEFT;
503 break;
504 case VK_END:
505 if (!(lParam & 0x01000000)) key = CURSOR_DOWN_LEFT;
506 break;
507 /*
508 * Numeric keypad keys with Num Lock on.
509 */
510 case VK_NUMPAD4: key = CURSOR_LEFT; break;
511 case VK_NUMPAD6: key = CURSOR_RIGHT; break;
512 case VK_NUMPAD8: key = CURSOR_UP; break;
513 case VK_NUMPAD2: key = CURSOR_DOWN; break;
514 case VK_NUMPAD9: key = CURSOR_UP_RIGHT; break;
515 case VK_NUMPAD3: key = CURSOR_DOWN_RIGHT; break;
516 case VK_NUMPAD7: key = CURSOR_UP_LEFT; break;
517 case VK_NUMPAD1: key = CURSOR_DOWN_LEFT; break;
4e7ef6e6 518 }
519
520 if (key != -1) {
eb2ad6f1 521 if (!midend_process_key(fe->me, 0, 0, key))
4e7ef6e6 522 PostQuitMessage(0);
c71454c0 523 } else {
524 MSG m;
525 m.hwnd = hwnd;
526 m.message = WM_KEYDOWN;
527 m.wParam = wParam;
528 m.lParam = lParam & 0xdfff;
529 TranslateMessage(&m);
4e7ef6e6 530 }
531 }
532 break;
533 case WM_LBUTTONDOWN:
534 case WM_RBUTTONDOWN:
535 case WM_MBUTTONDOWN:
b0f06719 536 {
537 int button;
538
539 /*
540 * Shift-clicks count as middle-clicks, since otherwise
541 * two-button Windows users won't have any kind of
542 * middle click to use.
543 */
544 if (message == WM_MBUTTONDOWN || (wParam & MK_SHIFT))
545 button = MIDDLE_BUTTON;
546 else if (message == WM_LBUTTONDOWN)
547 button = LEFT_BUTTON;
548 else
549 button = RIGHT_BUTTON;
550
551 if (!midend_process_key(fe->me, LOWORD(lParam),
552 HIWORD(lParam), button))
553 PostQuitMessage(0);
554 }
4e7ef6e6 555 break;
556 case WM_CHAR:
eb2ad6f1 557 if (!midend_process_key(fe->me, 0, 0, (unsigned char)wParam))
4e7ef6e6 558 PostQuitMessage(0);
559 return 0;
560 case WM_TIMER:
561 if (fe->timer)
562 midend_timer(fe->me, (float)0.02);
563 return 0;
564 }
565
566 return DefWindowProc(hwnd, message, wParam, lParam);
567}
568
569int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
570{
571 MSG msg;
572
573 srand(time(NULL));
574
575 if (!prev) {
576 WNDCLASS wndclass;
577
578 wndclass.style = 0;
579 wndclass.lpfnWndProc = WndProc;
580 wndclass.cbClsExtra = 0;
581 wndclass.cbWndExtra = 0;
582 wndclass.hInstance = inst;
583 wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
584 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
585 wndclass.hbrBackground = NULL;
586 wndclass.lpszMenuName = NULL;
0c490335 587 wndclass.lpszClassName = game_name;
4e7ef6e6 588
589 RegisterClass(&wndclass);
590 }
591
592 new_window(inst);
593
594 while (GetMessage(&msg, NULL, 0, 0)) {
4e7ef6e6 595 DispatchMessage(&msg);
596 }
597
598 return msg.wParam;
599}