|
1 /* $Id$ */ |
|
2 |
|
3 #include "../stdafx.h" |
|
4 #include "../openttd.h" |
|
5 #include "../functions.h" |
|
6 #include "../gfx.h" |
|
7 #include "../macros.h" |
|
8 #include "../network/network.h" |
|
9 #include "../variables.h" |
|
10 #include "../win32.h" |
|
11 #include "../window.h" |
|
12 #include "win32_v.h" |
|
13 #include <windows.h> |
|
14 #include <tchar.h> |
|
15 |
|
16 static struct { |
|
17 HWND main_wnd; |
|
18 HBITMAP dib_sect; |
|
19 Pixel *bitmap_bits; |
|
20 Pixel *buffer_bits; |
|
21 Pixel *alloced_bits; |
|
22 HPALETTE gdi_palette; |
|
23 int width; |
|
24 int height; |
|
25 int width_org; |
|
26 int height_org; |
|
27 bool fullscreen; |
|
28 bool double_size; |
|
29 bool has_focus; |
|
30 bool running; |
|
31 } _wnd; |
|
32 |
|
33 bool _force_full_redraw; |
|
34 bool _double_size; |
|
35 bool _window_maximize; |
|
36 uint _display_hz; |
|
37 uint _fullscreen_bpp; |
|
38 static uint16 _bck_resolution[2]; |
|
39 |
|
40 static void MakePalette(void) |
|
41 { |
|
42 LOGPALETTE *pal; |
|
43 uint i; |
|
44 |
|
45 pal = alloca(sizeof(LOGPALETTE) + (256-1) * sizeof(PALETTEENTRY)); |
|
46 |
|
47 pal->palVersion = 0x300; |
|
48 pal->palNumEntries = 256; |
|
49 |
|
50 for (i = 0; i != 256; i++) { |
|
51 pal->palPalEntry[i].peRed = _cur_palette[i].r; |
|
52 pal->palPalEntry[i].peGreen = _cur_palette[i].g; |
|
53 pal->palPalEntry[i].peBlue = _cur_palette[i].b; |
|
54 pal->palPalEntry[i].peFlags = 0; |
|
55 |
|
56 } |
|
57 _wnd.gdi_palette = CreatePalette(pal); |
|
58 if (_wnd.gdi_palette == NULL) error("CreatePalette failed!\n"); |
|
59 } |
|
60 |
|
61 static void UpdatePalette(HDC dc, uint start, uint count) |
|
62 { |
|
63 RGBQUAD rgb[256]; |
|
64 uint i; |
|
65 |
|
66 for (i = 0; i != count; i++) { |
|
67 rgb[i].rgbRed = _cur_palette[start + i].r; |
|
68 rgb[i].rgbGreen = _cur_palette[start + i].g; |
|
69 rgb[i].rgbBlue = _cur_palette[start + i].b; |
|
70 rgb[i].rgbReserved = 0; |
|
71 } |
|
72 |
|
73 SetDIBColorTable(dc, start, count, rgb); |
|
74 } |
|
75 |
|
76 typedef struct { |
|
77 byte vk_from; |
|
78 byte vk_count; |
|
79 byte map_to; |
|
80 } VkMapping; |
|
81 |
|
82 #define AS(x, z) {x, 0, z} |
|
83 #define AM(x, y, z, w) {x, y - x, z} |
|
84 |
|
85 static const VkMapping _vk_mapping[] = { |
|
86 // Pageup stuff + up/down |
|
87 AM(VK_PRIOR,VK_DOWN, WKC_PAGEUP, WKC_DOWN), |
|
88 // Map letters & digits |
|
89 AM('A','Z','A','Z'), |
|
90 AM('0','9','0','9'), |
|
91 |
|
92 AS(VK_ESCAPE, WKC_ESC), |
|
93 AS(VK_PAUSE, WKC_PAUSE), |
|
94 AS(VK_BACK, WKC_BACKSPACE), |
|
95 AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE), |
|
96 |
|
97 AS(VK_SPACE, WKC_SPACE), |
|
98 AS(VK_RETURN, WKC_RETURN), |
|
99 AS(VK_TAB, WKC_TAB), |
|
100 |
|
101 // Function keys |
|
102 AM(VK_F1, VK_F12, WKC_F1, WKC_F12), |
|
103 |
|
104 // Numeric part. |
|
105 // What is the virtual keycode for numeric enter?? |
|
106 AM(VK_NUMPAD0, VK_NUMPAD9, WKC_NUM_0, WKC_NUM_9), |
|
107 AS(VK_DIVIDE, WKC_NUM_DIV), |
|
108 AS(VK_MULTIPLY, WKC_NUM_MUL), |
|
109 AS(VK_SUBTRACT, WKC_NUM_MINUS), |
|
110 AS(VK_ADD, WKC_NUM_PLUS), |
|
111 AS(VK_DECIMAL, WKC_NUM_DECIMAL) |
|
112 }; |
|
113 |
|
114 static uint MapWindowsKey(uint sym) |
|
115 { |
|
116 const VkMapping *map; |
|
117 uint key = 0; |
|
118 |
|
119 for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { |
|
120 if ((uint)(sym - map->vk_from) <= map->vk_count) { |
|
121 key = sym - map->vk_from + map->map_to; |
|
122 break; |
|
123 } |
|
124 } |
|
125 |
|
126 if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT; |
|
127 if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL; |
|
128 if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT; |
|
129 return key; |
|
130 } |
|
131 |
|
132 static bool AllocateDibSection(int w, int h); |
|
133 |
|
134 static void ClientSizeChanged(int w, int h) |
|
135 { |
|
136 if (_wnd.double_size) { |
|
137 w /= 2; |
|
138 h /= 2; |
|
139 } |
|
140 |
|
141 // allocate new dib section of the new size |
|
142 if (AllocateDibSection(w, h)) { |
|
143 // mark all palette colors dirty |
|
144 _pal_first_dirty = 0; |
|
145 _pal_last_dirty = 255; |
|
146 GameSizeChanged(); |
|
147 |
|
148 // redraw screen |
|
149 if (_wnd.running) { |
|
150 _screen.dst_ptr = _wnd.buffer_bits; |
|
151 UpdateWindows(); |
|
152 } |
|
153 } |
|
154 } |
|
155 |
|
156 #ifdef _DEBUG |
|
157 // Keep this function here.. |
|
158 // It allows you to redraw the screen from within the MSVC debugger |
|
159 int RedrawScreenDebug(void) |
|
160 { |
|
161 HDC dc,dc2; |
|
162 static int _fooctr; |
|
163 HBITMAP old_bmp; |
|
164 HPALETTE old_palette; |
|
165 |
|
166 _screen.dst_ptr = _wnd.buffer_bits; |
|
167 UpdateWindows(); |
|
168 |
|
169 dc = GetDC(_wnd.main_wnd); |
|
170 dc2 = CreateCompatibleDC(dc); |
|
171 |
|
172 old_bmp = SelectObject(dc2, _wnd.dib_sect); |
|
173 old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE); |
|
174 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY); |
|
175 SelectPalette(dc, old_palette, TRUE); |
|
176 SelectObject(dc2, old_bmp); |
|
177 DeleteDC(dc2); |
|
178 ReleaseDC(_wnd.main_wnd, dc); |
|
179 |
|
180 return _fooctr++; |
|
181 } |
|
182 #endif |
|
183 |
|
184 /* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */ |
|
185 #if !defined(WM_MOUSELEAVE) |
|
186 #define WM_MOUSELEAVE 0x02A3 |
|
187 #endif |
|
188 #define TID_POLLMOUSE 1 |
|
189 #define MOUSE_POLL_DELAY 75 |
|
190 |
|
191 static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT event, DWORD time) |
|
192 { |
|
193 RECT rc; |
|
194 POINT pt; |
|
195 |
|
196 /* Get the rectangle of our window and translate it to screen coordinates. |
|
197 * Compare this with the current screen coordinates of the mouse and if it |
|
198 * falls outside of the area or our window we have left the window. */ |
|
199 GetClientRect(hwnd, &rc); |
|
200 MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)(LPRECT)&rc, 2); |
|
201 GetCursorPos(&pt); |
|
202 |
|
203 if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) { |
|
204 KillTimer(hwnd, event); |
|
205 PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L); |
|
206 } |
|
207 } |
|
208 |
|
209 static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) |
|
210 { |
|
211 switch (msg) { |
|
212 case WM_CREATE: |
|
213 SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); |
|
214 break; |
|
215 |
|
216 case WM_PAINT: { |
|
217 PAINTSTRUCT ps; |
|
218 HDC dc,dc2; |
|
219 HBITMAP old_bmp; |
|
220 HPALETTE old_palette; |
|
221 |
|
222 BeginPaint(hwnd, &ps); |
|
223 dc = ps.hdc; |
|
224 dc2 = CreateCompatibleDC(dc); |
|
225 old_bmp = SelectObject(dc2, _wnd.dib_sect); |
|
226 old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE); |
|
227 |
|
228 if (_pal_last_dirty != -1) { |
|
229 UpdatePalette(dc2, _pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); |
|
230 _pal_last_dirty = -1; |
|
231 } |
|
232 |
|
233 BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY); |
|
234 SelectPalette(dc, old_palette, TRUE); |
|
235 SelectObject(dc2, old_bmp); |
|
236 DeleteDC(dc2); |
|
237 EndPaint(hwnd, &ps); |
|
238 return 0; |
|
239 } |
|
240 |
|
241 case WM_PALETTECHANGED: |
|
242 if ((HWND)wParam == hwnd) return 0; |
|
243 /* FALLTHROUGH */ |
|
244 |
|
245 case WM_QUERYNEWPALETTE: { |
|
246 HDC hDC = GetWindowDC(hwnd); |
|
247 HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE); |
|
248 UINT nChanged = RealizePalette(hDC); |
|
249 |
|
250 SelectPalette(hDC, hOldPalette, TRUE); |
|
251 ReleaseDC(hwnd, hDC); |
|
252 if (nChanged) InvalidateRect(hwnd, NULL, FALSE); |
|
253 return 0; |
|
254 } |
|
255 |
|
256 case WM_CLOSE: |
|
257 HandleExitGameRequest(); |
|
258 return 0; |
|
259 |
|
260 case WM_DESTROY: |
|
261 if (_window_maximize) { |
|
262 _cur_resolution[0] = _bck_resolution[0]; |
|
263 _cur_resolution[1] = _bck_resolution[1]; |
|
264 } |
|
265 return 0; |
|
266 |
|
267 case WM_LBUTTONDOWN: |
|
268 SetCapture(hwnd); |
|
269 _left_button_down = true; |
|
270 HandleMouseEvents(); |
|
271 return 0; |
|
272 |
|
273 case WM_LBUTTONUP: |
|
274 ReleaseCapture(); |
|
275 _left_button_down = false; |
|
276 _left_button_clicked = false; |
|
277 HandleMouseEvents(); |
|
278 return 0; |
|
279 |
|
280 case WM_RBUTTONDOWN: |
|
281 SetCapture(hwnd); |
|
282 _right_button_down = true; |
|
283 _right_button_clicked = true; |
|
284 HandleMouseEvents(); |
|
285 return 0; |
|
286 |
|
287 case WM_RBUTTONUP: |
|
288 ReleaseCapture(); |
|
289 _right_button_down = false; |
|
290 HandleMouseEvents(); |
|
291 return 0; |
|
292 |
|
293 case WM_MOUSELEAVE: |
|
294 UndrawMouseCursor(); |
|
295 _cursor.in_window = false; |
|
296 |
|
297 if (!_left_button_down && !_right_button_down) MyShowCursor(true); |
|
298 HandleMouseEvents(); |
|
299 return 0; |
|
300 |
|
301 case WM_MOUSEMOVE: { |
|
302 int x = (int16)LOWORD(lParam); |
|
303 int y = (int16)HIWORD(lParam); |
|
304 POINT pt; |
|
305 |
|
306 /* If the mouse was not in the window and it has moved it means it has |
|
307 * come into the window, so start drawing the mouse. Also start |
|
308 * tracking the mouse for exiting the window */ |
|
309 if (!_cursor.in_window) { |
|
310 _cursor.in_window = true; |
|
311 SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); |
|
312 |
|
313 DrawMouseCursor(); |
|
314 } |
|
315 |
|
316 if (_wnd.double_size) { |
|
317 x /= 2; |
|
318 y /= 2; |
|
319 } |
|
320 |
|
321 if (_cursor.fix_at) { |
|
322 int dx = x - _cursor.pos.x; |
|
323 int dy = y - _cursor.pos.y; |
|
324 if (dx != 0 || dy != 0) { |
|
325 _cursor.delta.x += dx; |
|
326 _cursor.delta.y += dy; |
|
327 |
|
328 pt.x = _cursor.pos.x; |
|
329 pt.y = _cursor.pos.y; |
|
330 |
|
331 if (_wnd.double_size) { |
|
332 pt.x *= 2; |
|
333 pt.y *= 2; |
|
334 } |
|
335 ClientToScreen(hwnd, &pt); |
|
336 SetCursorPos(pt.x, pt.y); |
|
337 } |
|
338 } else { |
|
339 _cursor.delta.x += x - _cursor.pos.x; |
|
340 _cursor.delta.y += y - _cursor.pos.y; |
|
341 _cursor.pos.x = x; |
|
342 _cursor.pos.y = y; |
|
343 _cursor.dirty = true; |
|
344 } |
|
345 MyShowCursor(false); |
|
346 HandleMouseEvents(); |
|
347 return 0; |
|
348 } |
|
349 |
|
350 case WM_KEYDOWN: { |
|
351 // this is the rewritten ascii input function |
|
352 // it disables windows deadkey handling --> more linux like :D |
|
353 wchar_t w = 0; |
|
354 byte ks[256]; |
|
355 uint scancode; |
|
356 uint32 pressed_key; |
|
357 |
|
358 GetKeyboardState(ks); |
|
359 if (ToUnicode(wParam, 0, ks, &w, 1, 0) != 1) { |
|
360 /* On win9x ToUnicode always fails, so fall back to ToAscii */ |
|
361 if (ToAscii(wParam, 0, ks, &w, 0) != 1) w = 0; // no translation was possible |
|
362 } |
|
363 |
|
364 pressed_key = w | MapWindowsKey(wParam) << 16; |
|
365 |
|
366 scancode = GB(lParam, 16, 8); |
|
367 if (scancode == 41) pressed_key = w | WKC_BACKQUOTE << 16; |
|
368 |
|
369 if (GB(pressed_key, 16, 16) == ('D' | WKC_CTRL) && !_wnd.fullscreen) { |
|
370 _double_size ^= 1; |
|
371 _wnd.double_size = _double_size; |
|
372 ClientSizeChanged(_wnd.width, _wnd.height); |
|
373 MarkWholeScreenDirty(); |
|
374 } |
|
375 HandleKeypress(pressed_key); |
|
376 break; |
|
377 } |
|
378 |
|
379 case WM_SYSKEYDOWN: /* user presses F10 or Alt, both activating the title-menu */ |
|
380 switch (wParam) { |
|
381 case VK_RETURN: |
|
382 case 'F': /* Full Screen on ALT + ENTER/F */ |
|
383 ToggleFullScreen(!_wnd.fullscreen); |
|
384 return 0; |
|
385 |
|
386 case VK_MENU: /* Just ALT */ |
|
387 return 0; // do nothing |
|
388 |
|
389 case VK_F10: /* F10, ignore activation of menu */ |
|
390 HandleKeypress(MapWindowsKey(wParam) << 16); |
|
391 return 0; |
|
392 |
|
393 default: /* ALT in combination with something else */ |
|
394 HandleKeypress(MapWindowsKey(wParam) << 16); |
|
395 break; |
|
396 } |
|
397 break; |
|
398 |
|
399 case WM_SIZE: |
|
400 if (wParam != SIZE_MINIMIZED) { |
|
401 /* Set maximized flag when we maximize (obviously), but also when we |
|
402 * switched to fullscreen from a maximized state */ |
|
403 _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen)); |
|
404 if (_window_maximize) { |
|
405 _bck_resolution[0] = _cur_resolution[0]; |
|
406 _bck_resolution[1] = _cur_resolution[1]; |
|
407 } |
|
408 ClientSizeChanged(LOWORD(lParam), HIWORD(lParam)); |
|
409 } |
|
410 return 0; |
|
411 |
|
412 case WM_SIZING: { |
|
413 RECT* r = (RECT*)lParam; |
|
414 RECT r2; |
|
415 int w, h; |
|
416 |
|
417 SetRect(&r2, 0, 0, 0, 0); |
|
418 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE); |
|
419 |
|
420 w = r->right - r->left - (r2.right - r2.left); |
|
421 h = r->bottom - r->top - (r2.bottom - r2.top); |
|
422 if (_wnd.double_size) { |
|
423 w /= 2; |
|
424 h /= 2; |
|
425 } |
|
426 w = clamp(w, 64, MAX_SCREEN_WIDTH); |
|
427 h = clamp(h, 64, MAX_SCREEN_HEIGHT); |
|
428 if (_wnd.double_size) { |
|
429 w *= 2; |
|
430 h *= 2; |
|
431 } |
|
432 SetRect(&r2, 0, 0, w, h); |
|
433 |
|
434 AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE); |
|
435 w = r2.right - r2.left; |
|
436 h = r2.bottom - r2.top; |
|
437 |
|
438 switch (wParam) { |
|
439 case WMSZ_BOTTOM: |
|
440 r->bottom = r->top + h; |
|
441 break; |
|
442 |
|
443 case WMSZ_BOTTOMLEFT: |
|
444 r->bottom = r->top + h; |
|
445 r->left = r->right - w; |
|
446 break; |
|
447 |
|
448 case WMSZ_BOTTOMRIGHT: |
|
449 r->bottom = r->top + h; |
|
450 r->right = r->left + w; |
|
451 break; |
|
452 |
|
453 case WMSZ_LEFT: |
|
454 r->left = r->right - w; |
|
455 break; |
|
456 |
|
457 case WMSZ_RIGHT: |
|
458 r->right = r->left + w; |
|
459 break; |
|
460 |
|
461 case WMSZ_TOP: |
|
462 r->top = r->bottom - h; |
|
463 break; |
|
464 |
|
465 case WMSZ_TOPLEFT: |
|
466 r->top = r->bottom - h; |
|
467 r->left = r->right - w; |
|
468 break; |
|
469 |
|
470 case WMSZ_TOPRIGHT: |
|
471 r->top = r->bottom - h; |
|
472 r->right = r->left + w; |
|
473 break; |
|
474 } |
|
475 return TRUE; |
|
476 } |
|
477 |
|
478 // needed for wheel |
|
479 #if !defined(WM_MOUSEWHEEL) |
|
480 # define WM_MOUSEWHEEL 0x020A |
|
481 #endif //WM_MOUSEWHEEL |
|
482 #if !defined(GET_WHEEL_DELTA_WPARAM) |
|
483 # define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam)) |
|
484 #endif //GET_WHEEL_DELTA_WPARAM |
|
485 |
|
486 case WM_MOUSEWHEEL: { |
|
487 int delta = GET_WHEEL_DELTA_WPARAM(wParam); |
|
488 |
|
489 if (delta < 0) { |
|
490 _cursor.wheel++; |
|
491 } else if (delta > 0) { |
|
492 _cursor.wheel--; |
|
493 } |
|
494 HandleMouseEvents(); |
|
495 return 0; |
|
496 } |
|
497 |
|
498 case WM_ACTIVATEAPP: |
|
499 _wnd.has_focus = (bool)wParam; |
|
500 break; |
|
501 } |
|
502 return DefWindowProc(hwnd, msg, wParam, lParam); |
|
503 } |
|
504 |
|
505 static void RegisterWndClass(void) |
|
506 { |
|
507 static bool registered = false; |
|
508 |
|
509 if (!registered) { |
|
510 HINSTANCE hinst = GetModuleHandle(NULL); |
|
511 WNDCLASS wnd = { |
|
512 0, |
|
513 WndProcGdi, |
|
514 0, |
|
515 0, |
|
516 hinst, |
|
517 LoadIcon(hinst, MAKEINTRESOURCE(100)), |
|
518 LoadCursor(NULL, IDC_ARROW), |
|
519 0, |
|
520 0, |
|
521 _T("OTTD") |
|
522 }; |
|
523 |
|
524 registered = true; |
|
525 if (!RegisterClass(&wnd)) error("RegisterClass failed"); |
|
526 } |
|
527 } |
|
528 |
|
529 static void MakeWindow(bool full_screen) |
|
530 { |
|
531 _fullscreen = full_screen; |
|
532 |
|
533 _wnd.double_size = _double_size && !full_screen; |
|
534 |
|
535 // recreate window? |
|
536 if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) { |
|
537 DestroyWindow(_wnd.main_wnd); |
|
538 _wnd.main_wnd = 0; |
|
539 } |
|
540 |
|
541 if (full_screen) { |
|
542 DEVMODE settings; |
|
543 |
|
544 memset(&settings, 0, sizeof(settings)); |
|
545 settings.dmSize = sizeof(settings); |
|
546 settings.dmFields = |
|
547 (_fullscreen_bpp != 0 ? DM_BITSPERPEL : 0) | |
|
548 DM_PELSWIDTH | |
|
549 DM_PELSHEIGHT | |
|
550 (_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0); |
|
551 settings.dmBitsPerPel = _fullscreen_bpp; |
|
552 settings.dmPelsWidth = _wnd.width_org; |
|
553 settings.dmPelsHeight = _wnd.height_org; |
|
554 settings.dmDisplayFrequency = _display_hz; |
|
555 |
|
556 if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { |
|
557 MakeWindow(false); |
|
558 return; |
|
559 } |
|
560 } else if (_wnd.fullscreen) { |
|
561 // restore display? |
|
562 ChangeDisplaySettings(NULL, 0); |
|
563 } |
|
564 |
|
565 { |
|
566 RECT r; |
|
567 DWORD style, showstyle; |
|
568 int x, y, w, h; |
|
569 |
|
570 showstyle = SW_SHOWNORMAL; |
|
571 _wnd.fullscreen = full_screen; |
|
572 if (_wnd.fullscreen) { |
|
573 style = WS_POPUP; |
|
574 SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org); |
|
575 } else { |
|
576 style = WS_OVERLAPPEDWINDOW; |
|
577 /* On window creation, check if we were in maximize mode before */ |
|
578 if (_window_maximize) showstyle = SW_SHOWMAXIMIZED; |
|
579 SetRect(&r, 0, 0, _wnd.width, _wnd.height); |
|
580 } |
|
581 |
|
582 AdjustWindowRect(&r, style, FALSE); |
|
583 w = r.right - r.left; |
|
584 h = r.bottom - r.top; |
|
585 x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2; |
|
586 y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2; |
|
587 |
|
588 if (_wnd.main_wnd) { |
|
589 ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL); // remove maximize-flag |
|
590 SetWindowPos(_wnd.main_wnd, 0, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); |
|
591 } else { |
|
592 extern const char _openttd_revision[]; |
|
593 TCHAR Windowtitle[50]; |
|
594 |
|
595 _sntprintf(Windowtitle, sizeof(Windowtitle), _T("OpenTTD %s"), MB_TO_WIDE(_openttd_revision)); |
|
596 |
|
597 _wnd.main_wnd = CreateWindow(_T("OTTD"), Windowtitle, style, x, y, w, h, 0, 0, GetModuleHandle(NULL), 0); |
|
598 if (_wnd.main_wnd == NULL) error("CreateWindow failed"); |
|
599 ShowWindow(_wnd.main_wnd, showstyle); |
|
600 } |
|
601 } |
|
602 GameSizeChanged(); // invalidate all windows, force redraw |
|
603 } |
|
604 |
|
605 static bool AllocateDibSection(int w, int h) |
|
606 { |
|
607 BITMAPINFO *bi; |
|
608 HDC dc; |
|
609 |
|
610 w = clamp(w, 64, MAX_SCREEN_WIDTH); |
|
611 h = clamp(h, 64, MAX_SCREEN_HEIGHT); |
|
612 |
|
613 if (w == _screen.width && h == _screen.height) |
|
614 return false; |
|
615 |
|
616 _screen.width = w; |
|
617 _screen.pitch = ALIGN(w, 4); |
|
618 _screen.height = h; |
|
619 |
|
620 if (_wnd.alloced_bits) { |
|
621 free(_wnd.alloced_bits); |
|
622 _wnd.alloced_bits = NULL; |
|
623 } |
|
624 |
|
625 bi = alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256); |
|
626 memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256); |
|
627 bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); |
|
628 |
|
629 if (_wnd.double_size) { |
|
630 w = ALIGN(w, 4); |
|
631 _wnd.alloced_bits = _wnd.buffer_bits = malloc(w * h); |
|
632 w *= 2; |
|
633 h *= 2; |
|
634 } |
|
635 |
|
636 bi->bmiHeader.biWidth = _wnd.width = w; |
|
637 bi->bmiHeader.biHeight = -(_wnd.height = h); |
|
638 |
|
639 bi->bmiHeader.biPlanes = 1; |
|
640 bi->bmiHeader.biBitCount = 8; |
|
641 bi->bmiHeader.biCompression = BI_RGB; |
|
642 |
|
643 if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect); |
|
644 |
|
645 dc = GetDC(0); |
|
646 _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.bitmap_bits, NULL, 0); |
|
647 if (_wnd.dib_sect == NULL) error("CreateDIBSection failed"); |
|
648 ReleaseDC(0, dc); |
|
649 |
|
650 if (!_wnd.double_size) _wnd.buffer_bits = _wnd.bitmap_bits; |
|
651 |
|
652 return true; |
|
653 } |
|
654 |
|
655 static const uint16 default_resolutions[][2] = { |
|
656 { 640, 480 }, |
|
657 { 800, 600 }, |
|
658 { 1024, 768 }, |
|
659 { 1152, 864 }, |
|
660 { 1280, 800 }, |
|
661 { 1280, 960 }, |
|
662 { 1280, 1024 }, |
|
663 { 1400, 1050 }, |
|
664 { 1600, 1200 }, |
|
665 { 1680, 1050 }, |
|
666 { 1920, 1200 } |
|
667 }; |
|
668 |
|
669 static void FindResolutions(void) |
|
670 { |
|
671 uint n = 0; |
|
672 uint i; |
|
673 DEVMODEA dm; |
|
674 |
|
675 /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95 |
|
676 * Doesn't really matter since we don't pass a string anyways, but still |
|
677 * a letdown */ |
|
678 for (i = 0; EnumDisplaySettingsA(NULL, i, &dm) != 0; i++) { |
|
679 if (dm.dmBitsPerPel == 8 && IS_INT_INSIDE(dm.dmPelsWidth, 640, MAX_SCREEN_WIDTH + 1) && |
|
680 IS_INT_INSIDE(dm.dmPelsHeight, 480, MAX_SCREEN_HEIGHT + 1)) { |
|
681 uint j; |
|
682 |
|
683 for (j = 0; j < n; j++) { |
|
684 if (_resolutions[j][0] == dm.dmPelsWidth && _resolutions[j][1] == dm.dmPelsHeight) break; |
|
685 } |
|
686 |
|
687 /* In the previous loop we have checked already existing/added resolutions if |
|
688 * they are the same as the new ones. If this is not the case (j == n); we have |
|
689 * looped all and found none, add the new one to the list. If we have reached the |
|
690 * maximum amount of resolutions, then quit querying the display */ |
|
691 if (j == n) { |
|
692 _resolutions[j][0] = dm.dmPelsWidth; |
|
693 _resolutions[j][1] = dm.dmPelsHeight; |
|
694 if (++n == lengthof(_resolutions)) break; |
|
695 } |
|
696 } |
|
697 } |
|
698 |
|
699 /* We have found no resolutions, show the default list */ |
|
700 if (n == 0) { |
|
701 memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); |
|
702 n = lengthof(default_resolutions); |
|
703 } |
|
704 |
|
705 _num_resolutions = n; |
|
706 SortResolutions(_num_resolutions); |
|
707 } |
|
708 |
|
709 |
|
710 static const char *Win32GdiStart(const char * const *parm) |
|
711 { |
|
712 memset(&_wnd, 0, sizeof(_wnd)); |
|
713 |
|
714 RegisterWndClass(); |
|
715 |
|
716 MakePalette(); |
|
717 |
|
718 FindResolutions(); |
|
719 |
|
720 // fullscreen uses those |
|
721 _wnd.width_org = _cur_resolution[0]; |
|
722 _wnd.height_org = _cur_resolution[1]; |
|
723 |
|
724 AllocateDibSection(_cur_resolution[0], _cur_resolution[1]); |
|
725 MarkWholeScreenDirty(); |
|
726 |
|
727 MakeWindow(_fullscreen); |
|
728 |
|
729 return NULL; |
|
730 } |
|
731 |
|
732 static void Win32GdiStop(void) |
|
733 { |
|
734 DeleteObject(_wnd.gdi_palette); |
|
735 DeleteObject(_wnd.dib_sect); |
|
736 DestroyWindow(_wnd.main_wnd); |
|
737 |
|
738 if (_wnd.fullscreen) ChangeDisplaySettings(NULL, 0); |
|
739 if (_double_size) { |
|
740 _cur_resolution[0] *= 2; |
|
741 _cur_resolution[1] *= 2; |
|
742 } |
|
743 |
|
744 MyShowCursor(true); |
|
745 } |
|
746 |
|
747 // simple upscaler by 2 |
|
748 static void filter(int left, int top, int width, int height) |
|
749 { |
|
750 uint p = _screen.pitch; |
|
751 const Pixel *s = _wnd.buffer_bits + top * p + left; |
|
752 Pixel *d = _wnd.bitmap_bits + top * p * 4 + left * 2; |
|
753 |
|
754 for (; height > 0; height--) { |
|
755 int i; |
|
756 |
|
757 for (i = 0; i != width; i++) { |
|
758 d[i * 2] = d[i * 2 + 1] = d[i * 2 + p * 2] = d[i * 2 + 1 + p * 2] = s[i]; |
|
759 } |
|
760 s += p; |
|
761 d += p * 4; |
|
762 } |
|
763 } |
|
764 |
|
765 static void Win32GdiMakeDirty(int left, int top, int width, int height) |
|
766 { |
|
767 RECT r = { left, top, left + width, top + height }; |
|
768 |
|
769 if (_wnd.double_size) { |
|
770 filter(left, top, width, height); |
|
771 r.left *= 2; |
|
772 r.top *= 2; |
|
773 r.right *= 2; |
|
774 r.bottom *= 2; |
|
775 } |
|
776 InvalidateRect(_wnd.main_wnd, &r, FALSE); |
|
777 } |
|
778 |
|
779 static void CheckPaletteAnim(void) |
|
780 { |
|
781 if (_pal_last_dirty == -1) |
|
782 return; |
|
783 InvalidateRect(_wnd.main_wnd, NULL, FALSE); |
|
784 } |
|
785 |
|
786 static void Win32GdiMainLoop(void) |
|
787 { |
|
788 MSG mesg; |
|
789 uint32 cur_ticks = GetTickCount(); |
|
790 uint32 next_tick = cur_ticks + 30; |
|
791 |
|
792 _wnd.running = true; |
|
793 |
|
794 for (;;) { |
|
795 uint32 prev_cur_ticks = cur_ticks; // to check for wrapping |
|
796 |
|
797 while (PeekMessage(&mesg, NULL, 0, 0, PM_REMOVE)) { |
|
798 InteractiveRandom(); // randomness |
|
799 DispatchMessage(&mesg); |
|
800 } |
|
801 if (_exit_game) return; |
|
802 |
|
803 #if defined(_DEBUG) |
|
804 if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 && |
|
805 #else |
|
806 /* Speed up using TAB, but disable for ALT+TAB of course */ |
|
807 if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 && |
|
808 #endif |
|
809 !_networking && _game_mode != GM_MENU) { |
|
810 _fast_forward |= 2; |
|
811 } else if (_fast_forward & 2) { |
|
812 _fast_forward = 0; |
|
813 } |
|
814 |
|
815 cur_ticks = GetTickCount(); |
|
816 if (cur_ticks >= next_tick || (_fast_forward && !_pause) || cur_ticks < prev_cur_ticks) { |
|
817 next_tick = cur_ticks + 30; |
|
818 _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0; |
|
819 _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0; |
|
820 #ifdef _DEBUG |
|
821 _dbg_screen_rect = _wnd.has_focus && GetAsyncKeyState(VK_CAPITAL)<0; |
|
822 #endif |
|
823 |
|
824 // determine which directional keys are down |
|
825 if (_wnd.has_focus) { |
|
826 _dirkeys = |
|
827 (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) + |
|
828 (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) + |
|
829 (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) + |
|
830 (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0); |
|
831 } else { |
|
832 _dirkeys = 0; |
|
833 } |
|
834 |
|
835 GameLoop(); |
|
836 _cursor.delta.x = _cursor.delta.y = 0; |
|
837 |
|
838 if (_force_full_redraw) MarkWholeScreenDirty(); |
|
839 |
|
840 GdiFlush(); |
|
841 _screen.dst_ptr = _wnd.buffer_bits; |
|
842 UpdateWindows(); |
|
843 CheckPaletteAnim(); |
|
844 } else { |
|
845 Sleep(1); |
|
846 GdiFlush(); |
|
847 _screen.dst_ptr = _wnd.buffer_bits; |
|
848 DrawTextMessage(); |
|
849 DrawMouseCursor(); |
|
850 } |
|
851 } |
|
852 } |
|
853 |
|
854 static bool Win32GdiChangeRes(int w, int h) |
|
855 { |
|
856 _wnd.width = _wnd.width_org = w; |
|
857 _wnd.height = _wnd.height_org = h; |
|
858 |
|
859 MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching |
|
860 |
|
861 return true; |
|
862 } |
|
863 |
|
864 static void Win32GdiFullScreen(bool full_screen) |
|
865 { |
|
866 MakeWindow(full_screen); |
|
867 } |
|
868 |
|
869 const HalVideoDriver _win32_video_driver = { |
|
870 Win32GdiStart, |
|
871 Win32GdiStop, |
|
872 Win32GdiMakeDirty, |
|
873 Win32GdiMainLoop, |
|
874 Win32GdiChangeRes, |
|
875 Win32GdiFullScreen, |
|
876 }; |