diff --git a/src/video/win32_v.cpp b/src/video/win32_v.cpp new file mode 100644 --- /dev/null +++ b/src/video/win32_v.cpp @@ -0,0 +1,876 @@ +/* $Id$ */ + +#include "../stdafx.h" +#include "../openttd.h" +#include "../functions.h" +#include "../gfx.h" +#include "../macros.h" +#include "../network/network.h" +#include "../variables.h" +#include "../win32.h" +#include "../window.h" +#include "win32_v.h" +#include +#include + +static struct { + HWND main_wnd; + HBITMAP dib_sect; + Pixel *bitmap_bits; + Pixel *buffer_bits; + Pixel *alloced_bits; + HPALETTE gdi_palette; + int width; + int height; + int width_org; + int height_org; + bool fullscreen; + bool double_size; + bool has_focus; + bool running; +} _wnd; + +bool _force_full_redraw; +bool _double_size; +bool _window_maximize; +uint _display_hz; +uint _fullscreen_bpp; +static uint16 _bck_resolution[2]; + +static void MakePalette(void) +{ + LOGPALETTE *pal; + uint i; + + pal = alloca(sizeof(LOGPALETTE) + (256-1) * sizeof(PALETTEENTRY)); + + pal->palVersion = 0x300; + pal->palNumEntries = 256; + + for (i = 0; i != 256; i++) { + pal->palPalEntry[i].peRed = _cur_palette[i].r; + pal->palPalEntry[i].peGreen = _cur_palette[i].g; + pal->palPalEntry[i].peBlue = _cur_palette[i].b; + pal->palPalEntry[i].peFlags = 0; + + } + _wnd.gdi_palette = CreatePalette(pal); + if (_wnd.gdi_palette == NULL) error("CreatePalette failed!\n"); +} + +static void UpdatePalette(HDC dc, uint start, uint count) +{ + RGBQUAD rgb[256]; + uint i; + + for (i = 0; i != count; i++) { + rgb[i].rgbRed = _cur_palette[start + i].r; + rgb[i].rgbGreen = _cur_palette[start + i].g; + rgb[i].rgbBlue = _cur_palette[start + i].b; + rgb[i].rgbReserved = 0; + } + + SetDIBColorTable(dc, start, count, rgb); +} + +typedef struct { + byte vk_from; + byte vk_count; + byte map_to; +} VkMapping; + +#define AS(x, z) {x, 0, z} +#define AM(x, y, z, w) {x, y - x, z} + +static const VkMapping _vk_mapping[] = { + // Pageup stuff + up/down + AM(VK_PRIOR,VK_DOWN, WKC_PAGEUP, WKC_DOWN), + // Map letters & digits + AM('A','Z','A','Z'), + AM('0','9','0','9'), + + AS(VK_ESCAPE, WKC_ESC), + AS(VK_PAUSE, WKC_PAUSE), + AS(VK_BACK, WKC_BACKSPACE), + AM(VK_INSERT, VK_DELETE, WKC_INSERT, WKC_DELETE), + + AS(VK_SPACE, WKC_SPACE), + AS(VK_RETURN, WKC_RETURN), + AS(VK_TAB, WKC_TAB), + + // Function keys + AM(VK_F1, VK_F12, WKC_F1, WKC_F12), + + // Numeric part. + // What is the virtual keycode for numeric enter?? + AM(VK_NUMPAD0, VK_NUMPAD9, WKC_NUM_0, WKC_NUM_9), + AS(VK_DIVIDE, WKC_NUM_DIV), + AS(VK_MULTIPLY, WKC_NUM_MUL), + AS(VK_SUBTRACT, WKC_NUM_MINUS), + AS(VK_ADD, WKC_NUM_PLUS), + AS(VK_DECIMAL, WKC_NUM_DECIMAL) +}; + +static uint MapWindowsKey(uint sym) +{ + const VkMapping *map; + uint key = 0; + + for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { + if ((uint)(sym - map->vk_from) <= map->vk_count) { + key = sym - map->vk_from + map->map_to; + break; + } + } + + if (GetAsyncKeyState(VK_SHIFT) < 0) key |= WKC_SHIFT; + if (GetAsyncKeyState(VK_CONTROL) < 0) key |= WKC_CTRL; + if (GetAsyncKeyState(VK_MENU) < 0) key |= WKC_ALT; + return key; +} + +static bool AllocateDibSection(int w, int h); + +static void ClientSizeChanged(int w, int h) +{ + if (_wnd.double_size) { + w /= 2; + h /= 2; + } + + // allocate new dib section of the new size + if (AllocateDibSection(w, h)) { + // mark all palette colors dirty + _pal_first_dirty = 0; + _pal_last_dirty = 255; + GameSizeChanged(); + + // redraw screen + if (_wnd.running) { + _screen.dst_ptr = _wnd.buffer_bits; + UpdateWindows(); + } + } +} + +#ifdef _DEBUG +// Keep this function here.. +// It allows you to redraw the screen from within the MSVC debugger +int RedrawScreenDebug(void) +{ + HDC dc,dc2; + static int _fooctr; + HBITMAP old_bmp; + HPALETTE old_palette; + + _screen.dst_ptr = _wnd.buffer_bits; + UpdateWindows(); + + dc = GetDC(_wnd.main_wnd); + dc2 = CreateCompatibleDC(dc); + + old_bmp = SelectObject(dc2, _wnd.dib_sect); + old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE); + BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY); + SelectPalette(dc, old_palette, TRUE); + SelectObject(dc2, old_bmp); + DeleteDC(dc2); + ReleaseDC(_wnd.main_wnd, dc); + + return _fooctr++; +} +#endif + +/* Windows 95 will not have a WM_MOUSELEAVE message, so define it if needed */ +#if !defined(WM_MOUSELEAVE) +#define WM_MOUSELEAVE 0x02A3 +#endif +#define TID_POLLMOUSE 1 +#define MOUSE_POLL_DELAY 75 + +static void CALLBACK TrackMouseTimerProc(HWND hwnd, UINT msg, UINT event, DWORD time) +{ + RECT rc; + POINT pt; + + /* Get the rectangle of our window and translate it to screen coordinates. + * Compare this with the current screen coordinates of the mouse and if it + * falls outside of the area or our window we have left the window. */ + GetClientRect(hwnd, &rc); + MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)(LPRECT)&rc, 2); + GetCursorPos(&pt); + + if (!PtInRect(&rc, pt) || (WindowFromPoint(pt) != hwnd)) { + KillTimer(hwnd, event); + PostMessage(hwnd, WM_MOUSELEAVE, 0, 0L); + } +} + +static LRESULT CALLBACK WndProcGdi(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_CREATE: + SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); + break; + + case WM_PAINT: { + PAINTSTRUCT ps; + HDC dc,dc2; + HBITMAP old_bmp; + HPALETTE old_palette; + + BeginPaint(hwnd, &ps); + dc = ps.hdc; + dc2 = CreateCompatibleDC(dc); + old_bmp = SelectObject(dc2, _wnd.dib_sect); + old_palette = SelectPalette(dc, _wnd.gdi_palette, FALSE); + + if (_pal_last_dirty != -1) { + UpdatePalette(dc2, _pal_first_dirty, _pal_last_dirty - _pal_first_dirty + 1); + _pal_last_dirty = -1; + } + + BitBlt(dc, 0, 0, _wnd.width, _wnd.height, dc2, 0, 0, SRCCOPY); + SelectPalette(dc, old_palette, TRUE); + SelectObject(dc2, old_bmp); + DeleteDC(dc2); + EndPaint(hwnd, &ps); + return 0; + } + + case WM_PALETTECHANGED: + if ((HWND)wParam == hwnd) return 0; + /* FALLTHROUGH */ + + case WM_QUERYNEWPALETTE: { + HDC hDC = GetWindowDC(hwnd); + HPALETTE hOldPalette = SelectPalette(hDC, _wnd.gdi_palette, FALSE); + UINT nChanged = RealizePalette(hDC); + + SelectPalette(hDC, hOldPalette, TRUE); + ReleaseDC(hwnd, hDC); + if (nChanged) InvalidateRect(hwnd, NULL, FALSE); + return 0; + } + + case WM_CLOSE: + HandleExitGameRequest(); + return 0; + + case WM_DESTROY: + if (_window_maximize) { + _cur_resolution[0] = _bck_resolution[0]; + _cur_resolution[1] = _bck_resolution[1]; + } + return 0; + + case WM_LBUTTONDOWN: + SetCapture(hwnd); + _left_button_down = true; + HandleMouseEvents(); + return 0; + + case WM_LBUTTONUP: + ReleaseCapture(); + _left_button_down = false; + _left_button_clicked = false; + HandleMouseEvents(); + return 0; + + case WM_RBUTTONDOWN: + SetCapture(hwnd); + _right_button_down = true; + _right_button_clicked = true; + HandleMouseEvents(); + return 0; + + case WM_RBUTTONUP: + ReleaseCapture(); + _right_button_down = false; + HandleMouseEvents(); + return 0; + + case WM_MOUSELEAVE: + UndrawMouseCursor(); + _cursor.in_window = false; + + if (!_left_button_down && !_right_button_down) MyShowCursor(true); + HandleMouseEvents(); + return 0; + + case WM_MOUSEMOVE: { + int x = (int16)LOWORD(lParam); + int y = (int16)HIWORD(lParam); + POINT pt; + + /* If the mouse was not in the window and it has moved it means it has + * come into the window, so start drawing the mouse. Also start + * tracking the mouse for exiting the window */ + if (!_cursor.in_window) { + _cursor.in_window = true; + SetTimer(hwnd, TID_POLLMOUSE, MOUSE_POLL_DELAY, (TIMERPROC)TrackMouseTimerProc); + + DrawMouseCursor(); + } + + if (_wnd.double_size) { + x /= 2; + y /= 2; + } + + if (_cursor.fix_at) { + int dx = x - _cursor.pos.x; + int dy = y - _cursor.pos.y; + if (dx != 0 || dy != 0) { + _cursor.delta.x += dx; + _cursor.delta.y += dy; + + pt.x = _cursor.pos.x; + pt.y = _cursor.pos.y; + + if (_wnd.double_size) { + pt.x *= 2; + pt.y *= 2; + } + ClientToScreen(hwnd, &pt); + SetCursorPos(pt.x, pt.y); + } + } else { + _cursor.delta.x += x - _cursor.pos.x; + _cursor.delta.y += y - _cursor.pos.y; + _cursor.pos.x = x; + _cursor.pos.y = y; + _cursor.dirty = true; + } + MyShowCursor(false); + HandleMouseEvents(); + return 0; + } + + case WM_KEYDOWN: { + // this is the rewritten ascii input function + // it disables windows deadkey handling --> more linux like :D + wchar_t w = 0; + byte ks[256]; + uint scancode; + uint32 pressed_key; + + GetKeyboardState(ks); + if (ToUnicode(wParam, 0, ks, &w, 1, 0) != 1) { + /* On win9x ToUnicode always fails, so fall back to ToAscii */ + if (ToAscii(wParam, 0, ks, &w, 0) != 1) w = 0; // no translation was possible + } + + pressed_key = w | MapWindowsKey(wParam) << 16; + + scancode = GB(lParam, 16, 8); + if (scancode == 41) pressed_key = w | WKC_BACKQUOTE << 16; + + if (GB(pressed_key, 16, 16) == ('D' | WKC_CTRL) && !_wnd.fullscreen) { + _double_size ^= 1; + _wnd.double_size = _double_size; + ClientSizeChanged(_wnd.width, _wnd.height); + MarkWholeScreenDirty(); + } + HandleKeypress(pressed_key); + break; + } + + case WM_SYSKEYDOWN: /* user presses F10 or Alt, both activating the title-menu */ + switch (wParam) { + case VK_RETURN: + case 'F': /* Full Screen on ALT + ENTER/F */ + ToggleFullScreen(!_wnd.fullscreen); + return 0; + + case VK_MENU: /* Just ALT */ + return 0; // do nothing + + case VK_F10: /* F10, ignore activation of menu */ + HandleKeypress(MapWindowsKey(wParam) << 16); + return 0; + + default: /* ALT in combination with something else */ + HandleKeypress(MapWindowsKey(wParam) << 16); + break; + } + break; + + case WM_SIZE: + if (wParam != SIZE_MINIMIZED) { + /* Set maximized flag when we maximize (obviously), but also when we + * switched to fullscreen from a maximized state */ + _window_maximize = (wParam == SIZE_MAXIMIZED || (_window_maximize && _fullscreen)); + if (_window_maximize) { + _bck_resolution[0] = _cur_resolution[0]; + _bck_resolution[1] = _cur_resolution[1]; + } + ClientSizeChanged(LOWORD(lParam), HIWORD(lParam)); + } + return 0; + + case WM_SIZING: { + RECT* r = (RECT*)lParam; + RECT r2; + int w, h; + + SetRect(&r2, 0, 0, 0, 0); + AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE); + + w = r->right - r->left - (r2.right - r2.left); + h = r->bottom - r->top - (r2.bottom - r2.top); + if (_wnd.double_size) { + w /= 2; + h /= 2; + } + w = clamp(w, 64, MAX_SCREEN_WIDTH); + h = clamp(h, 64, MAX_SCREEN_HEIGHT); + if (_wnd.double_size) { + w *= 2; + h *= 2; + } + SetRect(&r2, 0, 0, w, h); + + AdjustWindowRect(&r2, GetWindowLong(hwnd, GWL_STYLE), FALSE); + w = r2.right - r2.left; + h = r2.bottom - r2.top; + + switch (wParam) { + case WMSZ_BOTTOM: + r->bottom = r->top + h; + break; + + case WMSZ_BOTTOMLEFT: + r->bottom = r->top + h; + r->left = r->right - w; + break; + + case WMSZ_BOTTOMRIGHT: + r->bottom = r->top + h; + r->right = r->left + w; + break; + + case WMSZ_LEFT: + r->left = r->right - w; + break; + + case WMSZ_RIGHT: + r->right = r->left + w; + break; + + case WMSZ_TOP: + r->top = r->bottom - h; + break; + + case WMSZ_TOPLEFT: + r->top = r->bottom - h; + r->left = r->right - w; + break; + + case WMSZ_TOPRIGHT: + r->top = r->bottom - h; + r->right = r->left + w; + break; + } + return TRUE; + } + +// needed for wheel +#if !defined(WM_MOUSEWHEEL) +# define WM_MOUSEWHEEL 0x020A +#endif //WM_MOUSEWHEEL +#if !defined(GET_WHEEL_DELTA_WPARAM) +# define GET_WHEEL_DELTA_WPARAM(wparam) ((short)HIWORD(wparam)) +#endif //GET_WHEEL_DELTA_WPARAM + + case WM_MOUSEWHEEL: { + int delta = GET_WHEEL_DELTA_WPARAM(wParam); + + if (delta < 0) { + _cursor.wheel++; + } else if (delta > 0) { + _cursor.wheel--; + } + HandleMouseEvents(); + return 0; + } + + case WM_ACTIVATEAPP: + _wnd.has_focus = (bool)wParam; + break; + } + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +static void RegisterWndClass(void) +{ + static bool registered = false; + + if (!registered) { + HINSTANCE hinst = GetModuleHandle(NULL); + WNDCLASS wnd = { + 0, + WndProcGdi, + 0, + 0, + hinst, + LoadIcon(hinst, MAKEINTRESOURCE(100)), + LoadCursor(NULL, IDC_ARROW), + 0, + 0, + _T("OTTD") + }; + + registered = true; + if (!RegisterClass(&wnd)) error("RegisterClass failed"); + } +} + +static void MakeWindow(bool full_screen) +{ + _fullscreen = full_screen; + + _wnd.double_size = _double_size && !full_screen; + + // recreate window? + if ((full_screen || _wnd.fullscreen) && _wnd.main_wnd) { + DestroyWindow(_wnd.main_wnd); + _wnd.main_wnd = 0; + } + + if (full_screen) { + DEVMODE settings; + + memset(&settings, 0, sizeof(settings)); + settings.dmSize = sizeof(settings); + settings.dmFields = + (_fullscreen_bpp != 0 ? DM_BITSPERPEL : 0) | + DM_PELSWIDTH | + DM_PELSHEIGHT | + (_display_hz != 0 ? DM_DISPLAYFREQUENCY : 0); + settings.dmBitsPerPel = _fullscreen_bpp; + settings.dmPelsWidth = _wnd.width_org; + settings.dmPelsHeight = _wnd.height_org; + settings.dmDisplayFrequency = _display_hz; + + if (ChangeDisplaySettings(&settings, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL) { + MakeWindow(false); + return; + } + } else if (_wnd.fullscreen) { + // restore display? + ChangeDisplaySettings(NULL, 0); + } + + { + RECT r; + DWORD style, showstyle; + int x, y, w, h; + + showstyle = SW_SHOWNORMAL; + _wnd.fullscreen = full_screen; + if (_wnd.fullscreen) { + style = WS_POPUP; + SetRect(&r, 0, 0, _wnd.width_org, _wnd.height_org); + } else { + style = WS_OVERLAPPEDWINDOW; + /* On window creation, check if we were in maximize mode before */ + if (_window_maximize) showstyle = SW_SHOWMAXIMIZED; + SetRect(&r, 0, 0, _wnd.width, _wnd.height); + } + + AdjustWindowRect(&r, style, FALSE); + w = r.right - r.left; + h = r.bottom - r.top; + x = (GetSystemMetrics(SM_CXSCREEN) - w) / 2; + y = (GetSystemMetrics(SM_CYSCREEN) - h) / 2; + + if (_wnd.main_wnd) { + ShowWindow(_wnd.main_wnd, SW_SHOWNORMAL); // remove maximize-flag + SetWindowPos(_wnd.main_wnd, 0, x, y, w, h, SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + } else { + extern const char _openttd_revision[]; + TCHAR Windowtitle[50]; + + _sntprintf(Windowtitle, sizeof(Windowtitle), _T("OpenTTD %s"), MB_TO_WIDE(_openttd_revision)); + + _wnd.main_wnd = CreateWindow(_T("OTTD"), Windowtitle, style, x, y, w, h, 0, 0, GetModuleHandle(NULL), 0); + if (_wnd.main_wnd == NULL) error("CreateWindow failed"); + ShowWindow(_wnd.main_wnd, showstyle); + } + } + GameSizeChanged(); // invalidate all windows, force redraw +} + +static bool AllocateDibSection(int w, int h) +{ + BITMAPINFO *bi; + HDC dc; + + w = clamp(w, 64, MAX_SCREEN_WIDTH); + h = clamp(h, 64, MAX_SCREEN_HEIGHT); + + if (w == _screen.width && h == _screen.height) + return false; + + _screen.width = w; + _screen.pitch = ALIGN(w, 4); + _screen.height = h; + + if (_wnd.alloced_bits) { + free(_wnd.alloced_bits); + _wnd.alloced_bits = NULL; + } + + bi = alloca(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256); + memset(bi, 0, sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256); + bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + + if (_wnd.double_size) { + w = ALIGN(w, 4); + _wnd.alloced_bits = _wnd.buffer_bits = malloc(w * h); + w *= 2; + h *= 2; + } + + bi->bmiHeader.biWidth = _wnd.width = w; + bi->bmiHeader.biHeight = -(_wnd.height = h); + + bi->bmiHeader.biPlanes = 1; + bi->bmiHeader.biBitCount = 8; + bi->bmiHeader.biCompression = BI_RGB; + + if (_wnd.dib_sect) DeleteObject(_wnd.dib_sect); + + dc = GetDC(0); + _wnd.dib_sect = CreateDIBSection(dc, bi, DIB_RGB_COLORS, (VOID**)&_wnd.bitmap_bits, NULL, 0); + if (_wnd.dib_sect == NULL) error("CreateDIBSection failed"); + ReleaseDC(0, dc); + + if (!_wnd.double_size) _wnd.buffer_bits = _wnd.bitmap_bits; + + return true; +} + +static const uint16 default_resolutions[][2] = { + { 640, 480 }, + { 800, 600 }, + { 1024, 768 }, + { 1152, 864 }, + { 1280, 800 }, + { 1280, 960 }, + { 1280, 1024 }, + { 1400, 1050 }, + { 1600, 1200 }, + { 1680, 1050 }, + { 1920, 1200 } +}; + +static void FindResolutions(void) +{ + uint n = 0; + uint i; + DEVMODEA dm; + + /* XXX - EnumDisplaySettingsW crashes with unicows.dll on Windows95 + * Doesn't really matter since we don't pass a string anyways, but still + * a letdown */ + for (i = 0; EnumDisplaySettingsA(NULL, i, &dm) != 0; i++) { + if (dm.dmBitsPerPel == 8 && IS_INT_INSIDE(dm.dmPelsWidth, 640, MAX_SCREEN_WIDTH + 1) && + IS_INT_INSIDE(dm.dmPelsHeight, 480, MAX_SCREEN_HEIGHT + 1)) { + uint j; + + for (j = 0; j < n; j++) { + if (_resolutions[j][0] == dm.dmPelsWidth && _resolutions[j][1] == dm.dmPelsHeight) break; + } + + /* In the previous loop we have checked already existing/added resolutions if + * they are the same as the new ones. If this is not the case (j == n); we have + * looped all and found none, add the new one to the list. If we have reached the + * maximum amount of resolutions, then quit querying the display */ + if (j == n) { + _resolutions[j][0] = dm.dmPelsWidth; + _resolutions[j][1] = dm.dmPelsHeight; + if (++n == lengthof(_resolutions)) break; + } + } + } + + /* We have found no resolutions, show the default list */ + if (n == 0) { + memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); + n = lengthof(default_resolutions); + } + + _num_resolutions = n; + SortResolutions(_num_resolutions); +} + + +static const char *Win32GdiStart(const char * const *parm) +{ + memset(&_wnd, 0, sizeof(_wnd)); + + RegisterWndClass(); + + MakePalette(); + + FindResolutions(); + + // fullscreen uses those + _wnd.width_org = _cur_resolution[0]; + _wnd.height_org = _cur_resolution[1]; + + AllocateDibSection(_cur_resolution[0], _cur_resolution[1]); + MarkWholeScreenDirty(); + + MakeWindow(_fullscreen); + + return NULL; +} + +static void Win32GdiStop(void) +{ + DeleteObject(_wnd.gdi_palette); + DeleteObject(_wnd.dib_sect); + DestroyWindow(_wnd.main_wnd); + + if (_wnd.fullscreen) ChangeDisplaySettings(NULL, 0); + if (_double_size) { + _cur_resolution[0] *= 2; + _cur_resolution[1] *= 2; + } + + MyShowCursor(true); +} + +// simple upscaler by 2 +static void filter(int left, int top, int width, int height) +{ + uint p = _screen.pitch; + const Pixel *s = _wnd.buffer_bits + top * p + left; + Pixel *d = _wnd.bitmap_bits + top * p * 4 + left * 2; + + for (; height > 0; height--) { + int i; + + for (i = 0; i != width; i++) { + d[i * 2] = d[i * 2 + 1] = d[i * 2 + p * 2] = d[i * 2 + 1 + p * 2] = s[i]; + } + s += p; + d += p * 4; + } +} + +static void Win32GdiMakeDirty(int left, int top, int width, int height) +{ + RECT r = { left, top, left + width, top + height }; + + if (_wnd.double_size) { + filter(left, top, width, height); + r.left *= 2; + r.top *= 2; + r.right *= 2; + r.bottom *= 2; + } + InvalidateRect(_wnd.main_wnd, &r, FALSE); +} + +static void CheckPaletteAnim(void) +{ + if (_pal_last_dirty == -1) + return; + InvalidateRect(_wnd.main_wnd, NULL, FALSE); +} + +static void Win32GdiMainLoop(void) +{ + MSG mesg; + uint32 cur_ticks = GetTickCount(); + uint32 next_tick = cur_ticks + 30; + + _wnd.running = true; + + for (;;) { + uint32 prev_cur_ticks = cur_ticks; // to check for wrapping + + while (PeekMessage(&mesg, NULL, 0, 0, PM_REMOVE)) { + InteractiveRandom(); // randomness + DispatchMessage(&mesg); + } + if (_exit_game) return; + +#if defined(_DEBUG) + if (_wnd.has_focus && GetAsyncKeyState(VK_SHIFT) < 0 && +#else + /* Speed up using TAB, but disable for ALT+TAB of course */ + if (_wnd.has_focus && GetAsyncKeyState(VK_TAB) < 0 && GetAsyncKeyState(VK_MENU) >= 0 && +#endif + !_networking && _game_mode != GM_MENU) { + _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = GetTickCount(); + if (cur_ticks >= next_tick || (_fast_forward && !_pause) || cur_ticks < prev_cur_ticks) { + next_tick = cur_ticks + 30; + _ctrl_pressed = _wnd.has_focus && GetAsyncKeyState(VK_CONTROL)<0; + _shift_pressed = _wnd.has_focus && GetAsyncKeyState(VK_SHIFT)<0; +#ifdef _DEBUG + _dbg_screen_rect = _wnd.has_focus && GetAsyncKeyState(VK_CAPITAL)<0; +#endif + + // determine which directional keys are down + if (_wnd.has_focus) { + _dirkeys = + (GetAsyncKeyState(VK_LEFT) < 0 ? 1 : 0) + + (GetAsyncKeyState(VK_UP) < 0 ? 2 : 0) + + (GetAsyncKeyState(VK_RIGHT) < 0 ? 4 : 0) + + (GetAsyncKeyState(VK_DOWN) < 0 ? 8 : 0); + } else { + _dirkeys = 0; + } + + GameLoop(); + _cursor.delta.x = _cursor.delta.y = 0; + + if (_force_full_redraw) MarkWholeScreenDirty(); + + GdiFlush(); + _screen.dst_ptr = _wnd.buffer_bits; + UpdateWindows(); + CheckPaletteAnim(); + } else { + Sleep(1); + GdiFlush(); + _screen.dst_ptr = _wnd.buffer_bits; + DrawTextMessage(); + DrawMouseCursor(); + } + } +} + +static bool Win32GdiChangeRes(int w, int h) +{ + _wnd.width = _wnd.width_org = w; + _wnd.height = _wnd.height_org = h; + + MakeWindow(_fullscreen); // _wnd.fullscreen screws up ingame resolution switching + + return true; +} + +static void Win32GdiFullScreen(bool full_screen) +{ + MakeWindow(full_screen); +} + +const HalVideoDriver _win32_video_driver = { + Win32GdiStart, + Win32GdiStop, + Win32GdiMakeDirty, + Win32GdiMainLoop, + Win32GdiChangeRes, + Win32GdiFullScreen, +};