/* * Copyright 2011-2014 Branimir Karadzic. All rights reserved. * License: http://www.opensource.org/licenses/BSD-2-Clause */ #include "entry_p.h" #if ENTRY_CONFIG_USE_NATIVE && BX_PLATFORM_WINDOWS #include #include #include #include #include #include #include #include enum { WM_USER_WINDOW_CREATE = WM_USER, WM_USER_WINDOW_DESTROY, WM_USER_WINDOW_SET_TITLE, WM_USER_WINDOW_SET_POS, WM_USER_WINDOW_SET_SIZE, WM_USER_WINDOW_TOGGLE_FRAME, WM_USER_WINDOW_MOUSE_LOCK, }; namespace entry { struct TranslateKeyModifiers { int m_vk; Modifier::Enum m_modifier; }; static const TranslateKeyModifiers s_translateKeyModifiers[8] = { { VK_LMENU, Modifier::LeftAlt }, { VK_RMENU, Modifier::RightAlt }, { VK_LCONTROL, Modifier::LeftCtrl }, { VK_RCONTROL, Modifier::RightCtrl }, { VK_LSHIFT, Modifier::LeftShift }, { VK_RSHIFT, Modifier::RightShift }, { VK_LWIN, Modifier::LeftMeta }, { VK_RWIN, Modifier::RightMeta }, }; static uint8_t translateKeyModifiers() { uint8_t modifiers = 0; for (uint32_t ii = 0; ii < BX_COUNTOF(s_translateKeyModifiers); ++ii) { const TranslateKeyModifiers& tkm = s_translateKeyModifiers[ii]; modifiers |= 0 > GetKeyState(tkm.m_vk) ? tkm.m_modifier : Modifier::None; } return modifiers; } static uint8_t s_translateKey[256]; static Key::Enum translateKey(WPARAM _wparam) { return (Key::Enum)s_translateKey[_wparam&0xff]; } struct MainThreadEntry { int m_argc; char** m_argv; static int32_t threadFunc(void* _userData); }; struct Msg { Msg() : m_x(0) , m_y(0) , m_width(0) , m_height(0) , m_flags(0) { } int32_t m_x; int32_t m_y; uint32_t m_width; uint32_t m_height; uint32_t m_flags; tinystl::string m_title; }; struct Context { Context() : m_mz(0) , m_frame(true) , m_mouseLock(NULL) , m_init(false) , m_exit(false) { memset(s_translateKey, 0, sizeof(s_translateKey) ); s_translateKey[VK_ESCAPE] = Key::Esc; s_translateKey[VK_RETURN] = Key::Return; s_translateKey[VK_TAB] = Key::Tab; s_translateKey[VK_BACK] = Key::Backspace; s_translateKey[VK_SPACE] = Key::Space; s_translateKey[VK_UP] = Key::Up; s_translateKey[VK_DOWN] = Key::Down; s_translateKey[VK_LEFT] = Key::Left; s_translateKey[VK_RIGHT] = Key::Right; s_translateKey[VK_PRIOR] = Key::PageUp; s_translateKey[VK_NEXT] = Key::PageUp; s_translateKey[VK_HOME] = Key::Home; s_translateKey[VK_END] = Key::End; s_translateKey[VK_SNAPSHOT] = Key::Print; s_translateKey[VK_OEM_PLUS] = Key::Plus; s_translateKey[VK_OEM_MINUS] = Key::Minus; s_translateKey[VK_F1] = Key::F1; s_translateKey[VK_F2] = Key::F2; s_translateKey[VK_F3] = Key::F3; s_translateKey[VK_F4] = Key::F4; s_translateKey[VK_F5] = Key::F5; s_translateKey[VK_F6] = Key::F6; s_translateKey[VK_F7] = Key::F7; s_translateKey[VK_F8] = Key::F8; s_translateKey[VK_F9] = Key::F9; s_translateKey[VK_F10] = Key::F10; s_translateKey[VK_F11] = Key::F11; s_translateKey[VK_F12] = Key::F12; s_translateKey[VK_NUMPAD0] = Key::NumPad0; s_translateKey[VK_NUMPAD1] = Key::NumPad1; s_translateKey[VK_NUMPAD2] = Key::NumPad2; s_translateKey[VK_NUMPAD3] = Key::NumPad3; s_translateKey[VK_NUMPAD4] = Key::NumPad4; s_translateKey[VK_NUMPAD5] = Key::NumPad5; s_translateKey[VK_NUMPAD6] = Key::NumPad6; s_translateKey[VK_NUMPAD7] = Key::NumPad7; s_translateKey[VK_NUMPAD8] = Key::NumPad8; s_translateKey[VK_NUMPAD9] = Key::NumPad9; s_translateKey['0'] = Key::Key0; s_translateKey['1'] = Key::Key1; s_translateKey['2'] = Key::Key2; s_translateKey['3'] = Key::Key3; s_translateKey['4'] = Key::Key4; s_translateKey['5'] = Key::Key5; s_translateKey['6'] = Key::Key6; s_translateKey['7'] = Key::Key7; s_translateKey['8'] = Key::Key8; s_translateKey['9'] = Key::Key9; s_translateKey['A'] = Key::KeyA; s_translateKey['B'] = Key::KeyB; s_translateKey['C'] = Key::KeyC; s_translateKey['D'] = Key::KeyD; s_translateKey['E'] = Key::KeyE; s_translateKey['F'] = Key::KeyF; s_translateKey['G'] = Key::KeyG; s_translateKey['H'] = Key::KeyH; s_translateKey['I'] = Key::KeyI; s_translateKey['J'] = Key::KeyJ; s_translateKey['K'] = Key::KeyK; s_translateKey['L'] = Key::KeyL; s_translateKey['M'] = Key::KeyM; s_translateKey['N'] = Key::KeyN; s_translateKey['O'] = Key::KeyO; s_translateKey['P'] = Key::KeyP; s_translateKey['Q'] = Key::KeyQ; s_translateKey['R'] = Key::KeyR; s_translateKey['S'] = Key::KeyS; s_translateKey['T'] = Key::KeyT; s_translateKey['U'] = Key::KeyU; s_translateKey['V'] = Key::KeyV; s_translateKey['W'] = Key::KeyW; s_translateKey['X'] = Key::KeyX; s_translateKey['Y'] = Key::KeyY; s_translateKey['Z'] = Key::KeyZ; } int32_t run(int _argc, char** _argv) { SetDllDirectory("."); HINSTANCE instance = (HINSTANCE)GetModuleHandle(NULL); WNDCLASSEX wnd; memset(&wnd, 0, sizeof(wnd) ); wnd.cbSize = sizeof(wnd); wnd.style = CS_HREDRAW | CS_VREDRAW; wnd.lpfnWndProc = wndProc; wnd.hInstance = instance; wnd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wnd.hCursor = LoadCursor(NULL, IDC_ARROW); wnd.lpszClassName = "bgfx"; wnd.hIconSm = LoadIcon(NULL, IDI_APPLICATION); RegisterClassExA(&wnd); m_windowAlloc.alloc(); m_hwnd[0] = CreateWindowA("bgfx" , "BGFX" , WS_OVERLAPPEDWINDOW|WS_VISIBLE , 0 , 0 , ENTRY_DEFAULT_WIDTH , ENTRY_DEFAULT_HEIGHT , NULL , NULL , instance , 0 ); m_flags[0] = ENTRY_WINDOW_FLAG_ASPECT_RATIO; bgfx::winSetHwnd(m_hwnd[0]); adjust(m_hwnd[0], ENTRY_DEFAULT_WIDTH, ENTRY_DEFAULT_HEIGHT, true); clear(m_hwnd[0]); m_width = ENTRY_DEFAULT_WIDTH; m_height = ENTRY_DEFAULT_HEIGHT; m_oldWidth = ENTRY_DEFAULT_WIDTH; m_oldHeight = ENTRY_DEFAULT_HEIGHT; MainThreadEntry mte; mte.m_argc = _argc; mte.m_argv = _argv; bx::Thread thread; thread.init(mte.threadFunc, &mte); m_init = true; m_eventQueue.postSizeEvent(findHandle(m_hwnd[0]), m_width, m_height); MSG msg; msg.message = WM_NULL; while (!m_exit) { WaitMessage(); while (0 != PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE) ) { TranslateMessage(&msg); DispatchMessage(&msg); } } thread.shutdown(); DestroyWindow(m_hwnd[0]); return 0; } LRESULT process(HWND _hwnd, UINT _id, WPARAM _wparam, LPARAM _lparam) { if (m_init) { switch (_id) { case WM_USER_WINDOW_CREATE: { Msg* msg = (Msg*)_lparam; HWND hwnd = CreateWindowA("bgfx" , msg->m_title.c_str() , WS_OVERLAPPEDWINDOW|WS_VISIBLE , msg->m_x , msg->m_y , msg->m_width , msg->m_height , m_hwnd[0] , NULL , (HINSTANCE)GetModuleHandle(NULL) , 0 ); clear(hwnd); m_hwnd[_wparam] = hwnd; m_flags[_wparam] = msg->m_flags; WindowHandle handle = { (uint16_t)_wparam }; m_eventQueue.postSizeEvent(handle, msg->m_width, msg->m_height); m_eventQueue.postWindowEvent(handle, hwnd); delete msg; } break; case WM_USER_WINDOW_DESTROY: { WindowHandle handle = { (uint16_t)_wparam }; PostMessageA(m_hwnd[_wparam], WM_CLOSE, 0, 0); m_eventQueue.postWindowEvent(handle); DestroyWindow(m_hwnd[_wparam]); m_hwnd[_wparam] = 0; } break; case WM_USER_WINDOW_SET_TITLE: { Msg* msg = (Msg*)_lparam; SetWindowTextA(m_hwnd[_wparam], msg->m_title.c_str() ); delete msg; } break; case WM_USER_WINDOW_SET_POS: { Msg* msg = (Msg*)_lparam; SetWindowPos(m_hwnd[_wparam], 0, msg->m_x, msg->m_y, 0, 0 , SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE ); delete msg; } break; case WM_USER_WINDOW_SET_SIZE: { uint32_t width = GET_X_LPARAM(_lparam); uint32_t height = GET_Y_LPARAM(_lparam); adjust(m_hwnd[_wparam], width, height, true); } break; case WM_USER_WINDOW_TOGGLE_FRAME: { if (m_frame) { m_oldWidth = m_width; m_oldHeight = m_height; } adjust(m_hwnd[_wparam], m_oldWidth, m_oldHeight, !m_frame); } break; case WM_USER_WINDOW_MOUSE_LOCK: setMouseLock(m_hwnd[_wparam], !!_lparam); break; case WM_DESTROY: break; case WM_QUIT: case WM_CLOSE: if (_hwnd == m_hwnd[0]) { m_exit = true; m_eventQueue.postExitEvent(); } else { destroyWindow(findHandle(_hwnd) ); } // Don't process message. Window will be destroyed later. return 0; case WM_SIZING: { WindowHandle handle = findHandle(_hwnd); if (isValid(handle) && ENTRY_WINDOW_FLAG_ASPECT_RATIO & m_flags[handle.idx]) { RECT& rect = *(RECT*)_lparam; uint32_t width = rect.right - rect.left - m_frameWidth; uint32_t height = rect.bottom - rect.top - m_frameHeight; // Recalculate size according to aspect ratio switch (_wparam) { case WMSZ_LEFT: case WMSZ_RIGHT: { float aspectRatio = 1.0f/m_aspectRatio; width = bx::uint32_max(ENTRY_DEFAULT_WIDTH/4, width); height = uint32_t(float(width)*aspectRatio); } break; default: { float aspectRatio = m_aspectRatio; height = bx::uint32_max(ENTRY_DEFAULT_HEIGHT/4, height); width = uint32_t(float(height)*aspectRatio); } break; } // Recalculate position using different anchor points switch(_wparam) { case WMSZ_LEFT: case WMSZ_TOPLEFT: case WMSZ_BOTTOMLEFT: rect.left = rect.right - width - m_frameWidth; rect.bottom = rect.top + height + m_frameHeight; break; default: rect.right = rect.left + width + m_frameWidth; rect.bottom = rect.top + height + m_frameHeight; break; } m_eventQueue.postSizeEvent(findHandle(_hwnd), width, height); } } return 0; case WM_SIZE: { WindowHandle handle = findHandle(_hwnd); if (isValid(handle) ) { uint32_t width = GET_X_LPARAM(_lparam); uint32_t height = GET_Y_LPARAM(_lparam); m_width = width; m_height = height; m_eventQueue.postSizeEvent(handle, m_width, m_height); } } break; case WM_SYSCOMMAND: switch (_wparam) { case SC_MINIMIZE: case SC_RESTORE: { HWND parent = GetWindow(_hwnd, GW_OWNER); if (NULL != parent) { PostMessage(parent, _id, _wparam, _lparam); } } } break; case WM_MOUSEMOVE: { int32_t mx = GET_X_LPARAM(_lparam); int32_t my = GET_Y_LPARAM(_lparam); if (_hwnd == m_mouseLock) { mx -= m_mx; my -= m_my; if (0 == mx && 0 == my) { break; } setMousePos(_hwnd, m_mx, m_my); } m_eventQueue.postMouseEvent(findHandle(_hwnd), mx, my, m_mz); } break; case WM_MOUSEWHEEL: { POINT pt = { GET_X_LPARAM(_lparam), GET_Y_LPARAM(_lparam) }; ScreenToClient(_hwnd, &pt); int32_t mx = pt.x; int32_t my = pt.y; m_mz += GET_WHEEL_DELTA_WPARAM(_wparam); m_eventQueue.postMouseEvent(findHandle(_hwnd), mx, my, m_mz); } break; case WM_LBUTTONDOWN: case WM_LBUTTONUP: case WM_LBUTTONDBLCLK: { int32_t mx = GET_X_LPARAM(_lparam); int32_t my = GET_Y_LPARAM(_lparam); m_eventQueue.postMouseEvent(findHandle(_hwnd), mx, my, m_mz, MouseButton::Left, _id == WM_LBUTTONDOWN); } break; case WM_MBUTTONDOWN: case WM_MBUTTONUP: case WM_MBUTTONDBLCLK: { int32_t mx = GET_X_LPARAM(_lparam); int32_t my = GET_Y_LPARAM(_lparam); m_eventQueue.postMouseEvent(findHandle(_hwnd), mx, my, m_mz, MouseButton::Middle, _id == WM_MBUTTONDOWN); } break; case WM_RBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: { int32_t mx = GET_X_LPARAM(_lparam); int32_t my = GET_Y_LPARAM(_lparam); m_eventQueue.postMouseEvent(findHandle(_hwnd), mx, my, m_mz, MouseButton::Right, _id == WM_RBUTTONDOWN); } break; case WM_KEYDOWN: case WM_SYSKEYDOWN: case WM_KEYUP: case WM_SYSKEYUP: { uint8_t modifiers = translateKeyModifiers(); Key::Enum key = translateKey(_wparam); WindowHandle handle = findHandle(_hwnd); if (Key::Print == key && 0x3 == ( (uint32_t)(_lparam)>>30) ) { // VK_SNAPSHOT doesn't generate keydown event. Fire on down event when previous // key state bit is set to 1 and transition state bit is set to 1. // // http://msdn.microsoft.com/en-us/library/windows/desktop/ms646280%28v=vs.85%29.aspx m_eventQueue.postKeyEvent(handle, key, modifiers, true); } m_eventQueue.postKeyEvent(handle, key, modifiers, _id == WM_KEYDOWN || _id == WM_SYSKEYDOWN); } break; default: break; } } return DefWindowProc(_hwnd, _id, _wparam, _lparam); } WindowHandle findHandle(HWND _hwnd) { bx::LwMutexScope scope(m_lock); for (uint32_t ii = 0, num = m_windowAlloc.getNumHandles(); ii < num; ++ii) { uint16_t idx = m_windowAlloc.getHandleAt(ii); if (_hwnd == m_hwnd[idx]) { WindowHandle handle = { idx }; return handle; } } WindowHandle invalid = { UINT16_MAX }; return invalid; } void clear(HWND _hwnd) { RECT rect; GetWindowRect(_hwnd, &rect); HBRUSH brush = CreateSolidBrush(RGB(0, 0, 0) ); HDC hdc = GetDC(_hwnd); SelectObject(hdc, brush); FillRect(hdc, &rect, brush); } void adjust(HWND _hwnd, uint32_t _width, uint32_t _height, bool _windowFrame) { m_width = _width; m_height = _height; m_aspectRatio = float(_width)/float(_height); ShowWindow(_hwnd, SW_SHOWNORMAL); RECT rect; RECT newrect = {0, 0, (LONG)_width, (LONG)_height}; DWORD style = WS_POPUP|WS_SYSMENU; if (m_frame) { GetWindowRect(_hwnd, &m_rect); m_style = GetWindowLong(_hwnd, GWL_STYLE); } if (_windowFrame) { rect = m_rect; style = m_style; } else { #if defined(__MINGW32__) rect = m_rect; style = m_style; #else HMONITOR monitor = MonitorFromWindow(_hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFO mi; mi.cbSize = sizeof(mi); GetMonitorInfo(monitor, &mi); newrect = mi.rcMonitor; rect = mi.rcMonitor; #endif // !defined(__MINGW__) } SetWindowLong(_hwnd, GWL_STYLE, style); uint32_t prewidth = newrect.right - newrect.left; uint32_t preheight = newrect.bottom - newrect.top; AdjustWindowRect(&newrect, style, FALSE); m_frameWidth = (newrect.right - newrect.left) - prewidth; m_frameHeight = (newrect.bottom - newrect.top) - preheight; UpdateWindow(_hwnd); if (rect.left == -32000 || rect.top == -32000) { rect.left = 0; rect.top = 0; } int32_t left = rect.left; int32_t top = rect.top; int32_t width = (newrect.right-newrect.left); int32_t height = (newrect.bottom-newrect.top); if (!_windowFrame) { float aspectRatio = 1.0f/m_aspectRatio; width = bx::uint32_max(ENTRY_DEFAULT_WIDTH/4, width); height = uint32_t(float(width)*aspectRatio); left = newrect.left+(newrect.right-newrect.left-width)/2; top = newrect.top+(newrect.bottom-newrect.top-height)/2; } HWND parent = GetWindow(_hwnd, GW_OWNER); if (NULL != parent) { if (_windowFrame) { SetWindowPos(parent , HWND_TOP , -32000 , -32000 , 0 , 0 , SWP_SHOWWINDOW ); } else { SetWindowPos(parent , HWND_TOP , newrect.left , newrect.top , newrect.right-newrect.left , newrect.bottom-newrect.top , SWP_SHOWWINDOW ); } } SetWindowPos(_hwnd , HWND_TOP , left , top , width , height , SWP_SHOWWINDOW ); ShowWindow(_hwnd, SW_RESTORE); m_frame = _windowFrame; } void setMousePos(HWND _hwnd, int32_t _mx, int32_t _my) { POINT pt = { _mx, _my }; ClientToScreen(_hwnd, &pt); SetCursorPos(pt.x, pt.y); } void setMouseLock(HWND _hwnd, bool _lock) { if (_hwnd != m_mouseLock) { if (_lock) { m_mx = m_width/2; m_my = m_height/2; ShowCursor(false); setMousePos(_hwnd, m_mx, m_my); } else { setMousePos(_hwnd, m_mx, m_my); ShowCursor(true); } m_mouseLock = _hwnd; } } static LRESULT CALLBACK wndProc(HWND _hwnd, UINT _id, WPARAM _wparam, LPARAM _lparam); EventQueue m_eventQueue; bx::LwMutex m_lock; bx::HandleAllocT m_windowAlloc; HWND m_hwnd[ENTRY_CONFIG_MAX_WINDOWS]; uint32_t m_flags[ENTRY_CONFIG_MAX_WINDOWS]; RECT m_rect; DWORD m_style; uint32_t m_width; uint32_t m_height; uint32_t m_oldWidth; uint32_t m_oldHeight; uint32_t m_frameWidth; uint32_t m_frameHeight; float m_aspectRatio; int32_t m_mx; int32_t m_my; int32_t m_mz; bool m_frame; HWND m_mouseLock; bool m_init; bool m_exit; }; static Context s_ctx; LRESULT CALLBACK Context::wndProc(HWND _hwnd, UINT _id, WPARAM _wparam, LPARAM _lparam) { return s_ctx.process(_hwnd, _id, _wparam, _lparam); } const Event* poll() { return s_ctx.m_eventQueue.poll(); } const Event* poll(WindowHandle _handle) { return s_ctx.m_eventQueue.poll(_handle); } void release(const Event* _event) { s_ctx.m_eventQueue.release(_event); } WindowHandle createWindow(int32_t _x, int32_t _y, uint32_t _width, uint32_t _height, uint32_t _flags, const char* _title) { bx::LwMutexScope scope(s_ctx.m_lock); WindowHandle handle = { s_ctx.m_windowAlloc.alloc() }; if (UINT16_MAX != handle.idx) { Msg* msg = new Msg; msg->m_x = _x; msg->m_y = _y; msg->m_width = _width; msg->m_height = _height; msg->m_title = _title; msg->m_flags = _flags; PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_CREATE, handle.idx, (LPARAM)msg); } return handle; } void destroyWindow(WindowHandle _handle) { if (UINT16_MAX != _handle.idx) { PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_DESTROY, _handle.idx, 0); bx::LwMutexScope scope(s_ctx.m_lock); s_ctx.m_windowAlloc.free(_handle.idx); } } void setWindowPos(WindowHandle _handle, int32_t _x, int32_t _y) { Msg* msg = new Msg; msg->m_x = _x; msg->m_y = _y; PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_SET_POS, _handle.idx, (LPARAM)msg); } void setWindowSize(WindowHandle _handle, uint32_t _width, uint32_t _height) { PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_SET_SIZE, _handle.idx, (_height<<16) | (_width&0xffff) ); } void setWindowTitle(WindowHandle _handle, const char* _title) { Msg* msg = new Msg; msg->m_title = _title; PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_SET_TITLE, _handle.idx, (LPARAM)msg); } void toggleWindowFrame(WindowHandle _handle) { PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_TOGGLE_FRAME, _handle.idx, 0); } void setMouseLock(WindowHandle _handle, bool _lock) { PostMessage(s_ctx.m_hwnd[0], WM_USER_WINDOW_MOUSE_LOCK, _handle.idx, _lock); } int32_t MainThreadEntry::threadFunc(void* _userData) { MainThreadEntry* self = (MainThreadEntry*)_userData; int32_t result = main(self->m_argc, self->m_argv); PostMessage(s_ctx.m_hwnd[0], WM_QUIT, 0, 0); return result; } } // namespace entry int main(int _argc, char** _argv) { using namespace entry; return s_ctx.run(_argc, _argv); } #endif // BX_PLATFORM_WINDOWS