A-_ha_! The Windows Rectangle() call appears to get uppity if asked
[sgt/puzzles] / windows.c
1 /*
2 * windows.c: Windows front end for my puzzle collection.
3 */
4
5 #include <windows.h>
6
7 #include <stdio.h>
8 #include <stdarg.h>
9 #include <stdlib.h>
10 #include <time.h>
11
12 #include "puzzles.h"
13
14 struct frontend {
15 midend_data *me;
16 HWND hwnd;
17 HBITMAP bitmap, prevbm;
18 HDC hdc_bm;
19 COLORREF *colours;
20 HBRUSH *brushes;
21 HPEN *pens;
22 UINT timer;
23 };
24
25 void fatal(char *fmt, ...)
26 {
27 char buf[2048];
28 va_list ap;
29
30 va_start(ap, fmt);
31 vsprintf(buf, fmt, ap);
32 va_end(ap);
33
34 MessageBox(NULL, buf, "Fatal error", MB_ICONEXCLAMATION | MB_OK);
35
36 exit(1);
37 }
38
39 void frontend_default_colour(frontend *fe, float *output)
40 {
41 DWORD c = GetSysColor(COLOR_MENU); /* ick */
42
43 output[0] = (float)(GetRValue(c) / 255.0);
44 output[1] = (float)(GetGValue(c) / 255.0);
45 output[2] = (float)(GetBValue(c) / 255.0);
46 }
47
48 void draw_rect(frontend *fe, int x, int y, int w, int h, int colour)
49 {
50 if (w == 1 && h == 1) {
51 /*
52 * Rectangle() appears to get uppity if asked to draw a 1x1
53 * rectangle, presumably on the grounds that that's beneath
54 * its dignity and you ought to be using SetPixel instead.
55 * So I will.
56 */
57 SetPixel(fe->hdc_bm, x, y, fe->colours[colour]);
58 } else {
59 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
60 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
61 Rectangle(fe->hdc_bm, x, y, x+w, y+h);
62 SelectObject(fe->hdc_bm, oldbrush);
63 SelectObject(fe->hdc_bm, oldpen);
64 }
65 }
66
67 void draw_line(frontend *fe, int x1, int y1, int x2, int y2, int colour)
68 {
69 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
70 MoveToEx(fe->hdc_bm, x1, y1, NULL);
71 LineTo(fe->hdc_bm, x2, y2);
72 SetPixel(fe->hdc_bm, x2, y2, fe->colours[colour]);
73 SelectObject(fe->hdc_bm, oldpen);
74 }
75
76 void draw_polygon(frontend *fe, int *coords, int npoints,
77 int fill, int colour)
78 {
79 POINT *pts = snewn(npoints+1, POINT);
80 int i;
81
82 for (i = 0; i <= npoints; i++) {
83 int j = (i < npoints ? i : 0);
84 pts[i].x = coords[j*2];
85 pts[i].y = coords[j*2+1];
86 }
87
88 if (fill) {
89 HBRUSH oldbrush = SelectObject(fe->hdc_bm, fe->brushes[colour]);
90 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
91 Polygon(fe->hdc_bm, pts, npoints);
92 SelectObject(fe->hdc_bm, oldbrush);
93 SelectObject(fe->hdc_bm, oldpen);
94 } else {
95 HPEN oldpen = SelectObject(fe->hdc_bm, fe->pens[colour]);
96 Polyline(fe->hdc_bm, pts, npoints+1);
97 SelectObject(fe->hdc_bm, oldpen);
98 }
99
100 sfree(pts);
101 }
102
103 void start_draw(frontend *fe)
104 {
105 HDC hdc_win;
106 hdc_win = GetDC(fe->hwnd);
107 fe->hdc_bm = CreateCompatibleDC(hdc_win);
108 fe->prevbm = SelectObject(fe->hdc_bm, fe->bitmap);
109 ReleaseDC(fe->hwnd, hdc_win);
110 }
111
112 void draw_update(frontend *fe, int x, int y, int w, int h)
113 {
114 RECT r;
115
116 r.left = x;
117 r.top = y;
118 r.right = x + w;
119 r.bottom = y + h;
120
121 InvalidateRect(fe->hwnd, &r, FALSE);
122 }
123
124 void end_draw(frontend *fe)
125 {
126 SelectObject(fe->hdc_bm, fe->prevbm);
127 DeleteDC(fe->hdc_bm);
128 }
129
130 void deactivate_timer(frontend *fe)
131 {
132 KillTimer(fe->hwnd, fe->timer);
133 fe->timer = 0;
134 }
135
136 void activate_timer(frontend *fe)
137 {
138 fe->timer = SetTimer(fe->hwnd, fe->timer, 20, NULL);
139 }
140
141 static frontend *new_window(HINSTANCE inst)
142 {
143 frontend *fe;
144 int x, y;
145 RECT r;
146 HDC hdc;
147
148 fe = snew(frontend);
149 fe->me = midend_new(fe);
150 midend_new_game(fe->me, NULL);
151 midend_size(fe->me, &x, &y);
152
153 fe->timer = 0;
154
155 {
156 int i, ncolours;
157 float *colours;
158
159 colours = midend_colours(fe->me, &ncolours);
160
161 fe->colours = snewn(ncolours, COLORREF);
162 fe->brushes = snewn(ncolours, HBRUSH);
163 fe->pens = snewn(ncolours, HPEN);
164
165 for (i = 0; i < ncolours; i++) {
166 fe->colours[i] = RGB(255 * colours[i*3+0],
167 255 * colours[i*3+1],
168 255 * colours[i*3+2]);
169 fe->brushes[i] = CreateSolidBrush(fe->colours[i]);
170 if (!fe->brushes[i])
171 MessageBox(fe->hwnd, "ooh", "eck", MB_OK);
172 fe->pens[i] = CreatePen(PS_SOLID, 1, fe->colours[i]);
173 }
174 }
175
176 r.left = r.top = 0;
177 r.right = x;
178 r.bottom = y;
179 AdjustWindowRectEx(&r, WS_OVERLAPPEDWINDOW &~
180 (WS_THICKFRAME | WS_MAXIMIZEBOX | WS_OVERLAPPED),
181 FALSE, 0);
182
183 fe->hwnd = CreateWindowEx(0, "puzzle", "puzzle",
184 WS_OVERLAPPEDWINDOW &~
185 (WS_THICKFRAME | WS_MAXIMIZEBOX),
186 CW_USEDEFAULT, CW_USEDEFAULT,
187 r.right - r.left, r.bottom - r.top,
188 NULL, NULL, inst, NULL);
189
190 hdc = GetDC(fe->hwnd);
191 fe->bitmap = CreateCompatibleBitmap(hdc, x, y);
192 ReleaseDC(fe->hwnd, hdc);
193
194 SetWindowLong(fe->hwnd, GWL_USERDATA, (LONG)fe);
195
196 ShowWindow(fe->hwnd, SW_NORMAL);
197 SetForegroundWindow(fe->hwnd);
198
199 midend_redraw(fe->me);
200
201 return fe;
202 }
203
204 static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
205 WPARAM wParam, LPARAM lParam)
206 {
207 frontend *fe = (frontend *)GetWindowLong(hwnd, GWL_USERDATA);
208
209 switch (message) {
210 case WM_CLOSE:
211 DestroyWindow(hwnd);
212 return 0;
213 case WM_DESTROY:
214 PostQuitMessage(0);
215 return 0;
216 case WM_PAINT:
217 {
218 PAINTSTRUCT p;
219 HDC hdc, hdc2;
220 HBITMAP prevbm;
221
222 hdc = BeginPaint(hwnd, &p);
223 hdc2 = CreateCompatibleDC(hdc);
224 prevbm = SelectObject(hdc2, fe->bitmap);
225 BitBlt(hdc,
226 p.rcPaint.left, p.rcPaint.top,
227 p.rcPaint.right - p.rcPaint.left,
228 p.rcPaint.bottom - p.rcPaint.top,
229 hdc2,
230 p.rcPaint.left, p.rcPaint.top,
231 SRCCOPY);
232 SelectObject(hdc2, prevbm);
233 DeleteDC(hdc2);
234 EndPaint(hwnd, &p);
235 }
236 return 0;
237 case WM_KEYDOWN:
238 {
239 int key = -1;
240
241 switch (wParam) {
242 case VK_LEFT: key = CURSOR_LEFT; break;
243 case VK_RIGHT: key = CURSOR_RIGHT; break;
244 case VK_UP: key = CURSOR_UP; break;
245 case VK_DOWN: key = CURSOR_DOWN; break;
246 }
247
248 if (key != -1) {
249 if (!midend_process_key(fe->me, -1, -1, key))
250 PostQuitMessage(0);
251 }
252 }
253 break;
254 case WM_LBUTTONDOWN:
255 case WM_RBUTTONDOWN:
256 case WM_MBUTTONDOWN:
257 if (!midend_process_key(fe->me, LOWORD(lParam), HIWORD(lParam),
258 (message == WM_LBUTTONDOWN ? LEFT_BUTTON :
259 message == WM_RBUTTONDOWN ? RIGHT_BUTTON :
260 MIDDLE_BUTTON)))
261 PostQuitMessage(0);
262
263 break;
264 case WM_CHAR:
265 if (!midend_process_key(fe->me, -1, -1, (unsigned char)wParam))
266 PostQuitMessage(0);
267 return 0;
268 case WM_TIMER:
269 if (fe->timer)
270 midend_timer(fe->me, (float)0.02);
271 return 0;
272 }
273
274 return DefWindowProc(hwnd, message, wParam, lParam);
275 }
276
277 int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
278 {
279 MSG msg;
280
281 srand(time(NULL));
282
283 if (!prev) {
284 WNDCLASS wndclass;
285
286 wndclass.style = 0;
287 wndclass.lpfnWndProc = WndProc;
288 wndclass.cbClsExtra = 0;
289 wndclass.cbWndExtra = 0;
290 wndclass.hInstance = inst;
291 wndclass.hIcon = LoadIcon(inst, IDI_APPLICATION);
292 wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
293 wndclass.hbrBackground = NULL;
294 wndclass.lpszMenuName = NULL;
295 wndclass.lpszClassName = "puzzle";
296
297 RegisterClass(&wndclass);
298 }
299
300 new_window(inst);
301
302 while (GetMessage(&msg, NULL, 0, 0)) {
303 TranslateMessage(&msg);
304 DispatchMessage(&msg);
305 }
306
307 return msg.wParam;
308 }