// Copyright (c) 2016-2018 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 "window.h" #include "logs.h" #define WINDOW_STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX #define WINDOW_FULLSCREEN_STYLE WS_CLIPCHILDREN | WS_POPUP #define KGE_WND_CLASS_NAME L"KiwanoAppWnd" namespace kiwano { namespace { MONITORINFOEX GetMoniterInfoEx(HWND hwnd); void AdjustWindow(UINT width, UINT height, DWORD style, UINT* win_width, UINT* win_height); void ChangeFullScreenResolution(int width, int height, WCHAR* device_name); void RestoreResolution(WCHAR* device_name); } Window::Window() : handle_(nullptr) , width_(0) , height_(0) , device_name_(nullptr) , is_fullscreen_(false) , mouse_cursor_(MouseCursor(-1)) { } Window::~Window() { if (is_fullscreen_) RestoreResolution(device_name_); if (device_name_) { delete[] device_name_; device_name_ = nullptr; } if (handle_) { ::DestroyWindow(handle_); handle_ = nullptr; } } HRESULT Window::Create(String const& title, int width, int height, LPCWSTR icon, bool fullscreen, WNDPROC proc) { HINSTANCE hinst = GetModuleHandleW(nullptr); WNDCLASSEX wcex = { 0 }; wcex.cbSize = sizeof(WNDCLASSEX); wcex.lpszClassName = KGE_WND_CLASS_NAME; wcex.style = CS_HREDRAW | CS_VREDRAW /* | CS_DBLCLKS */; wcex.lpfnWndProc = proc; wcex.hIcon = nullptr; wcex.cbClsExtra = 0; wcex.cbWndExtra = sizeof(LONG_PTR); wcex.hInstance = hinst; wcex.hbrBackground = nullptr; wcex.lpszMenuName = nullptr; wcex.hCursor = nullptr; if (icon) { wcex.hIcon = (HICON)::LoadImageW(hinst, 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[len + 1]; lstrcpyW(device_name_, monitor_info_ex.szDevice); int left = -1; int top = -1; is_fullscreen_ = fullscreen; if (is_fullscreen_) { top = monitor_info_ex.rcMonitor.top; left = monitor_info_ex.rcMonitor.left; if (width > monitor_info_ex.rcWork.right - left) width = monitor_info_ex.rcWork.right - left; if (height > monitor_info_ex.rcWork.bottom - top) height = monitor_info_ex.rcWork.bottom - top; } else { UINT screenw = monitor_info_ex.rcWork.right - monitor_info_ex.rcWork.left; UINT screenh = monitor_info_ex.rcWork.bottom - monitor_info_ex.rcWork.top; UINT win_width, win_height; AdjustWindow( width, height, GetWindowStyle(), &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; } handle_ = ::CreateWindowExW( is_fullscreen_ ? WS_EX_TOPMOST : 0, KGE_WND_CLASS_NAME, title.c_str(), GetWindowStyle(), left, top, width, height, nullptr, nullptr, hinst, nullptr ); if (handle_ == nullptr) { ::UnregisterClass(KGE_WND_CLASS_NAME, hinst); return HRESULT_FROM_WIN32(GetLastError()); } RECT rc; GetClientRect(handle_, &rc); width_ = rc.right - rc.left; height_ = rc.bottom - rc.top; SetMouseCursor(MouseCursor::Arrow); return S_OK; } void Window::Prepare() { ::ShowWindow(handle_, SW_SHOWNORMAL); ::UpdateWindow(handle_); if (is_fullscreen_) { ChangeFullScreenResolution(width_, height_, device_name_); } } String Window::GetTitle() const { if (handle_) { wchar_t title[256]; ::GetWindowTextW(handle_, title, 256); return title; } return String(); } void Window::SetTitle(String const& title) { if (handle_) ::SetWindowTextW(handle_, title.c_str()); } Size Window::GetSize() const { return Size{ static_cast(width_), static_cast(height_) }; } float Window::GetWidth() const { return static_cast(width_); } float Window::GetHeight() const { return static_cast(height_); } void Window::SetIcon(LPCWSTR icon_resource) { if (handle_) { HINSTANCE hinstance = GetModuleHandle(nullptr); HICON icon = (HICON)::LoadImage( hinstance, 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 Window::Resize(int width, int height) { if (handle_ && !is_fullscreen_) { RECT rc = { 0, 0, int(width), int(height) }; ::AdjustWindowRect(&rc, GetWindowStyle(), 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 Window::SetFullscreen(bool fullscreen, int width, int height) { if (is_fullscreen_ != fullscreen || width != width_ || height != height_) { is_fullscreen_ = fullscreen; 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, GetWindowStyle()); ::SetWindowPos(handle_, HWND_TOPMOST, info.rcMonitor.top, info.rcMonitor.left, width, height, SWP_NOACTIVATE); width_ = width; height_ = height; } else { RestoreResolution(device_name_); MONITORINFOEX info = GetMoniterInfoEx(handle_); UINT screenw = info.rcWork.right - info.rcWork.left; UINT screenh = info.rcWork.bottom - info.rcWork.top; UINT win_width, win_height; AdjustWindow(width, height, GetWindowStyle(), &win_width, &win_height); int left = screenw > win_width ? ((screenw - win_width) / 2) : 0; int top = screenh > win_height ? ((screenh - win_height) / 2) : 0; ::SetWindowLongPtr(handle_, GWL_STYLE, GetWindowStyle()); ::SetWindowPos(handle_, HWND_NOTOPMOST, left, top, win_width, win_height, SWP_DRAWFRAME | SWP_FRAMECHANGED); UpdateWindowRect(); } ::ShowWindow(handle_, SW_SHOWNORMAL); } } void Window::SetMouseCursor(MouseCursor cursor) { if (mouse_cursor_ != cursor) { mouse_cursor_ = cursor; LPTSTR win32_cursor = IDC_ARROW; switch (cursor) { case MouseCursor::Arrow: win32_cursor = IDC_ARROW; break; case MouseCursor::TextInput: win32_cursor = IDC_IBEAM; break; case MouseCursor::SizeAll: win32_cursor = IDC_SIZEALL; break; case MouseCursor::SizeWE: win32_cursor = IDC_SIZEWE; break; case MouseCursor::SizeNS: win32_cursor = IDC_SIZENS; break; case MouseCursor::SizeNESW: win32_cursor = IDC_SIZENESW; break; case MouseCursor::SizeNWSE: win32_cursor = IDC_SIZENWSE; break; case MouseCursor::Hand: win32_cursor = IDC_HAND; break; } ::SetCursor(::LoadCursorW(nullptr, win32_cursor)); } } HWND Window::GetHandle() const { return handle_; } DWORD Window::GetWindowStyle() const { return is_fullscreen_ ? (WINDOW_FULLSCREEN_STYLE) : (WINDOW_STYLE); } void Window::UpdateWindowRect() { if (!handle_) return; RECT rc; ::GetClientRect(handle_, &rc); width_ = rc.right - rc.left; height_ = rc.bottom - rc.top; } void Window::SetActive(bool actived) { if (!handle_) return; if (is_fullscreen_) { if (actived) { ChangeFullScreenResolution(width_, height_, device_name_); MONITORINFOEX info = GetMoniterInfoEx(handle_); ::SetWindowPos(handle_, HWND_TOPMOST, info.rcMonitor.top, info.rcMonitor.left, width_, height_, SWP_NOACTIVATE); } else { RestoreResolution(device_name_); ::SetWindowPos(handle_, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE); ::ShowWindow(handle_, SW_MINIMIZE); } } } 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(UINT width, UINT height, DWORD style, UINT* win_width, UINT* 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); UINT screenw = info.rcWork.right - info.rcWork.left; UINT screenh = info.rcWork.bottom - info.rcWork.top; if (*win_width > screenw) *win_width = screenw; if (*win_height > screenh) *win_height = screenh; } void ChangeFullScreenResolution(int width, int 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_LOG(L"ChangeDisplaySettings failed"); } void RestoreResolution(WCHAR* device_name) { ::ChangeDisplaySettingsExW(device_name, NULL, NULL, 0, NULL); } } }