// Copyright (c) 2016-2020 Kiwano - Nomango // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #include #if defined(KGE_PLATFORM_WINDOWS) #include #include #include #include #include #include #include #include #include // GET_X_LPARAM, GET_Y_LPARAM #include // ImmAssociateContext #pragma comment(lib, "imm32.lib") namespace kiwano { KGE_DECLARE_SMART_PTR(WindowWin32Impl); class KGE_API WindowWin32Impl : public Window { public: WindowWin32Impl(); virtual ~WindowWin32Impl(); void Init(const String& title, uint32_t width, uint32_t height, uint32_t icon, bool resizable, bool fullscreen); void SetTitle(const String& title) override; void SetIcon(uint32_t icon_resource) override; void Resize(uint32_t width, uint32_t height) override; void SetFullscreen(bool fullscreen) override; void SetCursor(CursorType cursor) override; void Destroy() override; void PumpEvents() override; DWORD GetStyle() const; void UpdateCursor(); void SetActive(bool actived); LRESULT MessageProc(HWND, UINT32, WPARAM, LPARAM); static LRESULT CALLBACK StaticWndProc(HWND, UINT32, WPARAM, LPARAM); private: bool resizable_; bool is_fullscreen_; wchar_t* device_name_; CursorType mouse_cursor_; std::array key_map_; }; WindowPtr Window::Create(const String& title, uint32_t width, uint32_t height, uint32_t icon, bool resizable, bool fullscreen) { WindowWin32ImplPtr ptr = new (std::nothrow) WindowWin32Impl; if (ptr) { ptr->Init(title, width, height, icon, resizable, fullscreen); } return ptr; } } // namespace kiwano namespace kiwano { #define WINDOW_FIXED_STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX #define WINDOW_RESIZABLE_STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX | WS_MAXIMIZEBOX #define WINDOW_FULLSCREEN_STYLE WS_CLIPCHILDREN | WS_POPUP namespace { MONITORINFOEX GetMoniterInfoEx(HWND hwnd) { HMONITOR monitor = ::MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST); MONITORINFOEX monitor_info; memset(&monitor_info, 0, sizeof(MONITORINFOEX)); monitor_info.cbSize = sizeof(MONITORINFOEX); ::GetMonitorInfoW(monitor, &monitor_info); return monitor_info; } void AdjustWindow(uint32_t width, uint32_t height, DWORD style, uint32_t* win_width, uint32_t* win_height) { RECT rc; ::SetRect(&rc, 0, 0, (int)width, (int)height); ::AdjustWindowRect(&rc, style, false); *win_width = rc.right - rc.left; *win_height = rc.bottom - rc.top; MONITORINFOEX info = GetMoniterInfoEx(NULL); uint32_t screenw = info.rcWork.right - info.rcWork.left; uint32_t screenh = info.rcWork.bottom - info.rcWork.top; if (*win_width > screenw) *win_width = screenw; if (*win_height > screenh) *win_height = screenh; } void ChangeFullScreenResolution(uint32_t width, uint32_t height, WCHAR* device_name) { DEVMODE mode; memset(&mode, 0, sizeof(mode)); mode.dmSize = sizeof(DEVMODE); mode.dmBitsPerPel = ::GetDeviceCaps(::GetDC(0), BITSPIXEL); ; mode.dmPelsWidth = width; mode.dmPelsHeight = height; mode.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; if (::ChangeDisplaySettingsExW(device_name, &mode, NULL, CDS_FULLSCREEN, NULL) != DISP_CHANGE_SUCCESSFUL) KGE_ERROR("ChangeDisplaySettings failed"); } void RestoreResolution(WCHAR* device_name) { ::ChangeDisplaySettingsExW(device_name, NULL, NULL, 0, NULL); } } // namespace WindowWin32Impl::WindowWin32Impl() : device_name_(nullptr) , is_fullscreen_(false) , resizable_(false) , mouse_cursor_(CursorType::Arrow) , key_map_{} { // Keys key_map_[VK_UP] = KeyCode::Up; key_map_[VK_LEFT] = KeyCode::Left; key_map_[VK_RIGHT] = KeyCode::Right; key_map_[VK_DOWN] = KeyCode::Down; key_map_[VK_RETURN] = KeyCode::Enter; key_map_[VK_SPACE] = KeyCode::Space; key_map_[VK_ESCAPE] = KeyCode::Esc; key_map_[VK_CONTROL] = KeyCode::Ctrl; key_map_[VK_SHIFT] = KeyCode::Shift; key_map_[VK_MENU] = KeyCode::Alt; key_map_[VK_TAB] = KeyCode::Tab; key_map_[VK_DELETE] = KeyCode::Delete; key_map_[VK_BACK] = KeyCode::Back; // VK_L* and VK_R* key_map_[VK_LCONTROL] = KeyCode::Ctrl; key_map_[VK_RCONTROL] = KeyCode::Ctrl; key_map_[VK_LSHIFT] = KeyCode::Shift; key_map_[VK_RSHIFT] = KeyCode::Shift; key_map_[VK_LMENU] = KeyCode::Alt; key_map_[VK_RMENU] = KeyCode::Alt; key_map_[VK_LWIN] = KeyCode::Super; key_map_[VK_RWIN] = KeyCode::Super; // A - Z for (size_t i = 0, size = ('Z' - 'A'); i <= size; ++i) key_map_['A' + i] = KeyCode(size_t(KeyCode::A) + i); // Num 0 - 9 for (size_t i = 0; i < 9; ++i) key_map_['0' + i] = KeyCode(size_t(KeyCode::Num0) + i); // Numpad 0 - 9 for (size_t i = 0; i < 9; ++i) key_map_[VK_NUMPAD0 + i] = KeyCode(size_t(KeyCode::Numpad0) + i); // F1 - F12 for (size_t i = 0; i < 12; ++i) key_map_[VK_F1 + i] = KeyCode(size_t(KeyCode::F1) + i); } WindowWin32Impl::~WindowWin32Impl() {} void WindowWin32Impl::Init(const String& title, uint32_t width, uint32_t height, uint32_t icon, bool resizable, bool fullscreen) { HINSTANCE hinst = GetModuleHandleW(nullptr); WNDCLASSEX wcex = { 0 }; wcex.cbSize = sizeof(WNDCLASSEX); wcex.lpszClassName = L"KiwanoAppWnd"; wcex.style = CS_HREDRAW | CS_VREDRAW /* | CS_DBLCLKS */; wcex.lpfnWndProc = WindowWin32Impl::StaticWndProc; wcex.hIcon = nullptr; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(LONG_PTR); wcex.hInstance = hinst; wcex.hbrBackground = (HBRUSH)(::GetStockObject(BLACK_BRUSH)); wcex.lpszMenuName = nullptr; wcex.hCursor = ::LoadCursorW(hinst, IDC_ARROW); if (icon) { wcex.hIcon = (HICON)::LoadImageW(hinst, MAKEINTRESOURCE(icon), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE); } ::RegisterClassExW(&wcex); // Get the nearest monitor to this window HMONITOR monitor = ::MonitorFromPoint(POINT{ 0, 0 }, MONITOR_DEFAULTTOPRIMARY); // Get the target monitor info MONITORINFOEX monitor_info_ex; memset(&monitor_info_ex, 0, sizeof(MONITORINFOEX)); monitor_info_ex.cbSize = sizeof(MONITORINFOEX); ::GetMonitorInfoW(monitor, &monitor_info_ex); // Save the device name int len = lstrlenW(monitor_info_ex.szDevice); device_name_ = new wchar_t[len + 1]; lstrcpyW(device_name_, monitor_info_ex.szDevice); int left = -1, top = -1; resizable_ = resizable; is_fullscreen_ = fullscreen; if (is_fullscreen_) { top = monitor_info_ex.rcMonitor.top; left = monitor_info_ex.rcMonitor.left; if (width > static_cast(monitor_info_ex.rcWork.right - left)) width = static_cast(monitor_info_ex.rcWork.right - left); if (height > static_cast(monitor_info_ex.rcWork.bottom - top)) height = static_cast(monitor_info_ex.rcWork.bottom - top); } else { uint32_t screenw = monitor_info_ex.rcWork.right - monitor_info_ex.rcWork.left; uint32_t screenh = monitor_info_ex.rcWork.bottom - monitor_info_ex.rcWork.top; uint32_t win_width, win_height; AdjustWindow(width, height, GetStyle(), &win_width, &win_height); left = monitor_info_ex.rcWork.left + (screenw - win_width) / 2; top = monitor_info_ex.rcWork.top + (screenh - win_height) / 2; width = win_width; height = win_height; } WideString wide_title = string::ToWide(title); handle_ = ::CreateWindowExW(is_fullscreen_ ? WS_EX_TOPMOST : 0, L"KiwanoAppWnd", wide_title.c_str(), GetStyle(), left, top, width, height, nullptr, nullptr, hinst, nullptr); if (handle_ == nullptr) { ::UnregisterClassW(L"KiwanoAppWnd", hinst); KGE_THROW_SYSTEM_ERROR(HRESULT_FROM_WIN32(GetLastError()), "Create window failed"); } width_ = width; height_ = height; // disable imm ::ImmAssociateContext(handle_, nullptr); // use Application instance in message loop ::SetWindowLongPtr(handle_, GWLP_USERDATA, LONG_PTR(this)); ::ShowWindow(handle_, SW_SHOWNORMAL); ::UpdateWindow(handle_); if (is_fullscreen_) { ChangeFullScreenResolution(width, height, device_name_); } } void WindowWin32Impl::PumpEvents() { MSG msg; while (::PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { ::TranslateMessage(&msg); ::DispatchMessageW(&msg); } } void WindowWin32Impl::SetTitle(const String& title) { if (handle_) { WideString wide_title = string::ToWide(title); ::SetWindowTextW(handle_, wide_title.c_str()); } } void WindowWin32Impl::SetIcon(uint32_t icon_resource) { if (handle_) { HINSTANCE hinstance = GetModuleHandle(nullptr); HICON icon = (HICON)::LoadImageW(hinstance, MAKEINTRESOURCE(icon_resource), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_CREATEDIBSECTION | LR_DEFAULTSIZE); ::SendMessage(handle_, WM_SETICON, ICON_BIG, (LPARAM)icon); ::SendMessage(handle_, WM_SETICON, ICON_SMALL, (LPARAM)icon); } } void WindowWin32Impl::Resize(uint32_t width, uint32_t height) { if (handle_ && !is_fullscreen_) { RECT rc = { 0, 0, LONG(width), LONG(height) }; ::AdjustWindowRect(&rc, GetStyle(), false); width = rc.right - rc.left; height = rc.bottom - rc.top; ::SetWindowPos(handle_, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); } } void WindowWin32Impl::SetFullscreen(bool fullscreen) { if (is_fullscreen_ != fullscreen) { is_fullscreen_ = fullscreen; uint32_t width = GetWidth(); uint32_t height = GetHeight(); if (is_fullscreen_) { // move window to (0, 0) before display switch ::SetWindowPos(handle_, HWND_TOPMOST, 0, 0, width, height, SWP_NOACTIVATE); ChangeFullScreenResolution(width, height, device_name_); MONITORINFOEX info = GetMoniterInfoEx(handle_); ::SetWindowLongPtr(handle_, GWL_STYLE, GetStyle()); ::SetWindowPos(handle_, HWND_TOPMOST, info.rcMonitor.top, info.rcMonitor.left, width, height, SWP_NOACTIVATE); } else { RestoreResolution(device_name_); MONITORINFOEX info = GetMoniterInfoEx(handle_); uint32_t screenw = uint32_t(info.rcWork.right - info.rcWork.left); uint32_t screenh = uint32_t(info.rcWork.bottom - info.rcWork.top); int left = screenw > width ? ((screenw - width) / 2) : 0; int top = screenh > height ? ((screenh - height) / 2) : 0; ::SetWindowLongPtr(handle_, GWL_STYLE, GetStyle()); ::SetWindowPos(handle_, HWND_NOTOPMOST, left, top, width, height, SWP_DRAWFRAME | SWP_FRAMECHANGED); } ::ShowWindow(handle_, SW_SHOWNORMAL); } } void WindowWin32Impl::SetCursor(CursorType cursor) { mouse_cursor_ = cursor; } void WindowWin32Impl::Destroy() { if (is_fullscreen_) RestoreResolution(device_name_); if (device_name_) { delete[] device_name_; device_name_ = nullptr; } if (handle_) { ::DestroyWindow(handle_); handle_ = nullptr; } Window::Destroy(); } DWORD WindowWin32Impl::GetStyle() const { return is_fullscreen_ ? (WINDOW_FULLSCREEN_STYLE) : (resizable_ ? (WINDOW_RESIZABLE_STYLE) : (WINDOW_FIXED_STYLE)); } void WindowWin32Impl::UpdateCursor() { LPTSTR win32_cursor = IDC_ARROW; switch (mouse_cursor_) { case CursorType::Arrow: win32_cursor = IDC_ARROW; break; case CursorType::TextInput: win32_cursor = IDC_IBEAM; break; case CursorType::SizeAll: win32_cursor = IDC_SIZEALL; break; case CursorType::SizeWE: win32_cursor = IDC_SIZEWE; break; case CursorType::SizeNS: win32_cursor = IDC_SIZENS; break; case CursorType::SizeNESW: win32_cursor = IDC_SIZENESW; break; case CursorType::SizeNWSE: win32_cursor = IDC_SIZENWSE; break; case CursorType::Hand: win32_cursor = IDC_HAND; break; } ::SetCursor(::LoadCursorW(nullptr, win32_cursor)); } void WindowWin32Impl::SetActive(bool actived) { if (!handle_) return; if (is_fullscreen_) { if (actived) { ChangeFullScreenResolution(GetWidth(), GetHeight(), device_name_); MONITORINFOEX info = GetMoniterInfoEx(handle_); ::SetWindowPos(handle_, HWND_TOPMOST, info.rcMonitor.top, info.rcMonitor.left, GetWidth(), GetHeight(), SWP_NOACTIVATE); } else { RestoreResolution(device_name_); ::SetWindowPos(handle_, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); ::ShowWindow(handle_, SW_MINIMIZE); } } } LRESULT WindowWin32Impl::MessageProc(HWND hwnd, UINT32 msg, WPARAM wparam, LPARAM lparam) { switch (msg) { case WM_KEYDOWN: case WM_SYSKEYDOWN: { KeyCode key = this->key_map_[size_t(wparam)]; if (key != KeyCode::Unknown) { KeyDownEventPtr evt = new KeyDownEvent; evt->code = key; this->PushEvent(evt); } } break; case WM_KEYUP: case WM_SYSKEYUP: { KeyCode key = this->key_map_[size_t(wparam)]; if (key != KeyCode::Unknown) { KeyUpEventPtr evt = new KeyUpEvent; evt->code = key; this->PushEvent(evt); } } break; case WM_CHAR: { KeyCharEventPtr evt = new KeyCharEvent; evt->value = char(wparam); this->PushEvent(evt); } break; case WM_LBUTTONDOWN: case WM_LBUTTONDBLCLK: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: { MouseDownEventPtr evt = new MouseDownEvent; evt->pos = Point((float)GET_X_LPARAM(lparam), (float)GET_Y_LPARAM(lparam)); if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONDBLCLK) { evt->button = MouseButton::Left; } else if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { evt->button = MouseButton::Right; } else if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { evt->button = MouseButton::Middle; } this->PushEvent(evt); } break; case WM_LBUTTONUP: case WM_MBUTTONUP: case WM_RBUTTONUP: { MouseUpEventPtr evt = new MouseUpEvent; evt->pos = Point((float)GET_X_LPARAM(lparam), (float)GET_Y_LPARAM(lparam)); if (msg == WM_LBUTTONUP) { evt->button = MouseButton::Left; } else if (msg == WM_RBUTTONUP) { evt->button = MouseButton::Right; } else if (msg == WM_MBUTTONUP) { evt->button = MouseButton::Middle; } this->PushEvent(evt); } break; case WM_MOUSEMOVE: { MouseMoveEventPtr evt = new MouseMoveEvent; evt->pos = Point((float)GET_X_LPARAM(lparam), (float)GET_Y_LPARAM(lparam)); this->PushEvent(evt); } break; case WM_MOUSEWHEEL: { MouseWheelEventPtr evt = new MouseWheelEvent; evt->pos = Point((float)GET_X_LPARAM(lparam), (float)GET_Y_LPARAM(lparam)); evt->wheel = GET_WHEEL_DELTA_WPARAM(wparam) / (float)WHEEL_DELTA; this->PushEvent(evt); } break; case WM_SIZE: { if (SIZE_MAXHIDE == wparam || SIZE_MINIMIZED == wparam) { KGE_SYS_LOG("Window minimized"); } else { // KGE_SYS_LOG("Window resized"); this->width_ = ((uint32_t)(short)LOWORD(lparam)); this->height_ = ((uint32_t)(short)HIWORD(lparam)); WindowResizedEventPtr evt = new WindowResizedEvent; evt->width = this->GetWidth(); evt->height = this->GetHeight(); this->PushEvent(evt); } } break; case WM_MOVE: { WindowMovedEventPtr evt = new WindowMovedEvent; evt->x = GET_X_LPARAM(lparam); evt->y = GET_Y_LPARAM(lparam); this->PushEvent(evt); } break; case WM_ACTIVATE: { bool active = (LOWORD(wparam) != WA_INACTIVE); this->SetActive(active); WindowFocusChangedEventPtr evt = new WindowFocusChangedEvent; evt->focus = active; this->PushEvent(evt); } break; case WM_SETTEXT: { KGE_SYS_LOG("Window title changed"); this->title_ = string::ToNarrow(reinterpret_cast(lparam)); WindowTitleChangedEventPtr evt = new WindowTitleChangedEvent; evt->title = this->title_; this->PushEvent(evt); } break; case WM_SETICON: { KGE_SYS_LOG("Window icon changed"); } break; case WM_DISPLAYCHANGE: { KGE_SYS_LOG("The display resolution has changed"); ::InvalidateRect(hwnd, nullptr, FALSE); } break; case WM_SETCURSOR: { this->UpdateCursor(); } break; case WM_CLOSE: { KGE_SYS_LOG("Window is closing"); WindowClosedEventPtr evt = new WindowClosedEvent; this->PushEvent(evt); this->SetShouldClose(true); return 0; } break; case WM_DESTROY: { KGE_SYS_LOG("Window was destroyed"); ::PostQuitMessage(0); return 0; } break; } return ::DefWindowProcW(hwnd, msg, wparam, lparam); } LRESULT CALLBACK WindowWin32Impl::StaticWndProc(HWND hwnd, UINT32 msg, WPARAM wparam, LPARAM lparam) { LONG_PTR ptr = static_cast(::GetWindowLongPtrW(hwnd, GWLP_USERDATA)); if (ptr) { WindowWin32Impl* window = reinterpret_cast(ptr); return window->MessageProc(hwnd, msg, wparam, lparam); } return ::DefWindowProcW(hwnd, msg, wparam, lparam); } } // namespace kiwano #endif