raze/libraries/ZWidget/src/window/win32/win32window.cpp

679 lines
17 KiB
C++

#include "win32window.h"
#include <windowsx.h>
#include <stdexcept>
#include <cmath>
#include <vector>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE ((USHORT) 0x02)
#endif
#ifndef HID_USAGE_GENERIC_JOYSTICK
#define HID_USAGE_GENERIC_JOYSTICK ((USHORT) 0x04)
#endif
#ifndef HID_USAGE_GENERIC_GAMEPAD
#define HID_USAGE_GENERIC_GAMEPAD ((USHORT) 0x05)
#endif
#ifndef RIDEV_INPUTSINK
#define RIDEV_INPUTSINK (0x100)
#endif
static std::string from_utf16(const std::wstring& str)
{
if (str.empty()) return {};
int needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0, nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
std::string result;
result.resize(needed);
needed = WideCharToMultiByte(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size(), nullptr, nullptr);
if (needed == 0)
throw std::runtime_error("WideCharToMultiByte failed");
return result;
}
static std::wstring to_utf16(const std::string& str)
{
if (str.empty()) return {};
int needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), nullptr, 0);
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
std::wstring result;
result.resize(needed);
needed = MultiByteToWideChar(CP_UTF8, 0, str.data(), (int)str.size(), &result[0], (int)result.size());
if (needed == 0)
throw std::runtime_error("MultiByteToWideChar failed");
return result;
}
Win32Window::Win32Window(DisplayWindowHost* windowHost) : WindowHost(windowHost)
{
Windows.push_front(this);
WindowsIterator = Windows.begin();
WNDCLASSEX classdesc = {};
classdesc.cbSize = sizeof(WNDCLASSEX);
classdesc.hInstance = GetModuleHandle(0);
classdesc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS;
classdesc.lpszClassName = L"ZWidgetWindow";
classdesc.lpfnWndProc = &Win32Window::WndProc;
RegisterClassEx(&classdesc);
// Microsoft logic at its finest:
// WS_EX_DLGMODALFRAME hides the sysmenu icon
// WS_CAPTION shows the caption (yay! actually a flag that does what it says it does!)
// WS_SYSMENU shows the min/max/close buttons
// WS_THICKFRAME makes the window resizable
CreateWindowEx(WS_EX_APPWINDOW | WS_EX_DLGMODALFRAME, L"ZWidgetWindow", L"", WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX, 0, 0, 100, 100, 0, 0, GetModuleHandle(0), this);
/*
RAWINPUTDEVICE rid;
rid.usUsagePage = HID_USAGE_PAGE_GENERIC;
rid.usUsage = HID_USAGE_GENERIC_MOUSE;
rid.dwFlags = RIDEV_INPUTSINK;
rid.hwndTarget = WindowHandle;
BOOL result = RegisterRawInputDevices(&rid, 1, sizeof(RAWINPUTDEVICE));
*/
}
Win32Window::~Win32Window()
{
if (WindowHandle)
{
DestroyWindow(WindowHandle);
WindowHandle = 0;
}
Windows.erase(WindowsIterator);
}
void Win32Window::SetWindowTitle(const std::string& text)
{
SetWindowText(WindowHandle, to_utf16(text).c_str());
}
void Win32Window::SetBorderColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 34/*DWMWA_BORDER_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetCaptionColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 35/*DWMWA_CAPTION_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetCaptionTextColor(uint32_t bgra8)
{
bgra8 = bgra8 & 0x00ffffff;
DwmSetWindowAttribute(WindowHandle, 36/*DWMWA_TEXT_COLOR*/, &bgra8, sizeof(uint32_t));
}
void Win32Window::SetWindowFrame(const Rect& box)
{
double dpiscale = GetDpiScale();
SetWindowPos(WindowHandle, nullptr, (int)std::round(box.x * dpiscale), (int)std::round(box.y * dpiscale), (int)std::round(box.width * dpiscale), (int)std::round(box.height * dpiscale), SWP_NOACTIVATE | SWP_NOZORDER);
}
void Win32Window::SetClientFrame(const Rect& box)
{
// This function is currently unused but needs to be disabled because it contains Windows API calls that were only added in Windows 10.
#if 0
double dpiscale = GetDpiScale();
RECT rect = {};
rect.left = (int)std::round(box.x * dpiscale);
rect.top = (int)std::round(box.y * dpiscale);
rect.right = rect.left + (int)std::round(box.width * dpiscale);
rect.bottom = rect.top + (int)std::round(box.height * dpiscale);
DWORD style = (DWORD)GetWindowLongPtr(WindowHandle, GWL_STYLE);
DWORD exstyle = (DWORD)GetWindowLongPtr(WindowHandle, GWL_EXSTYLE);
AdjustWindowRectExForDpi(&rect, style, FALSE, exstyle, GetDpiForWindow(WindowHandle));
SetWindowPos(WindowHandle, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOACTIVATE | SWP_NOZORDER);
#endif
}
void Win32Window::Show()
{
ShowWindow(WindowHandle, SW_SHOW);
}
void Win32Window::ShowFullscreen()
{
HDC screenDC = GetDC(0);
int width = GetDeviceCaps(screenDC, HORZRES);
int height = GetDeviceCaps(screenDC, VERTRES);
ReleaseDC(0, screenDC);
SetWindowLongPtr(WindowHandle, GWL_EXSTYLE, WS_EX_APPWINDOW);
SetWindowLongPtr(WindowHandle, GWL_STYLE, WS_OVERLAPPED);
SetWindowPos(WindowHandle, HWND_TOP, 0, 0, width, height, SWP_FRAMECHANGED | SWP_SHOWWINDOW);
Fullscreen = true;
}
void Win32Window::ShowMaximized()
{
ShowWindow(WindowHandle, SW_SHOWMAXIMIZED);
}
void Win32Window::ShowMinimized()
{
ShowWindow(WindowHandle, SW_SHOWMINIMIZED);
}
void Win32Window::ShowNormal()
{
ShowWindow(WindowHandle, SW_NORMAL);
}
void Win32Window::Hide()
{
ShowWindow(WindowHandle, SW_HIDE);
}
void Win32Window::Activate()
{
SetFocus(WindowHandle);
}
void Win32Window::ShowCursor(bool enable)
{
}
void Win32Window::LockCursor()
{
if (!MouseLocked)
{
MouseLocked = true;
GetCursorPos(&MouseLockPos);
::ShowCursor(FALSE);
}
}
void Win32Window::UnlockCursor()
{
if (MouseLocked)
{
MouseLocked = false;
SetCursorPos(MouseLockPos.x, MouseLockPos.y);
::ShowCursor(TRUE);
}
}
void Win32Window::CaptureMouse()
{
SetCapture(WindowHandle);
}
void Win32Window::ReleaseMouseCapture()
{
ReleaseCapture();
}
void Win32Window::Update()
{
InvalidateRect(WindowHandle, nullptr, FALSE);
}
bool Win32Window::GetKeyState(EInputKey key)
{
return ::GetKeyState((int)key) & 0x8000; // High bit (0x8000) means key is down, Low bit (0x0001) means key is sticky on (like Caps Lock, Num Lock, etc.)
}
void Win32Window::SetCursor(StandardCursor cursor)
{
if (cursor != CurrentCursor)
{
CurrentCursor = cursor;
UpdateCursor();
}
}
Rect Win32Window::GetWindowFrame() const
{
RECT box = {};
GetWindowRect(WindowHandle, &box);
double dpiscale = GetDpiScale();
return Rect(box.left / dpiscale, box.top / dpiscale, box.right / dpiscale, box.bottom / dpiscale);
}
Size Win32Window::GetClientSize() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
double dpiscale = GetDpiScale();
return Size(box.right / dpiscale, box.bottom / dpiscale);
}
int Win32Window::GetPixelWidth() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
return box.right;
}
int Win32Window::GetPixelHeight() const
{
RECT box = {};
GetClientRect(WindowHandle, &box);
return box.bottom;
}
typedef UINT(WINAPI* GetDpiForWindow_t)(HWND);
double Win32Window::GetDpiScale() const
{
static GetDpiForWindow_t pGetDpiForWindow = nullptr;
if (!pGetDpiForWindow)
{
HMODULE hMod = LoadLibrary(TEXT("User32.dll"));
pGetDpiForWindow = reinterpret_cast<GetDpiForWindow_t>(GetProcAddress(hMod, "GetDpiForWindow"));
}
if (pGetDpiForWindow)
return pGetDpiForWindow(WindowHandle) / 96.0;
else
return 1.0;
}
std::string Win32Window::GetClipboardText()
{
BOOL result = OpenClipboard(WindowHandle);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
HANDLE handle = GetClipboardData(CF_UNICODETEXT);
if (handle == 0)
{
CloseClipboard();
return std::string();
}
std::wstring::value_type* data = (std::wstring::value_type*)GlobalLock(handle);
if (data == 0)
{
CloseClipboard();
return std::string();
}
std::string str = from_utf16(data);
GlobalUnlock(handle);
CloseClipboard();
return str;
}
void Win32Window::SetClipboardText(const std::string& text)
{
std::wstring text16 = to_utf16(text);
BOOL result = OpenClipboard(WindowHandle);
if (result == FALSE)
throw std::runtime_error("Unable to open clipboard");
result = EmptyClipboard();
if (result == FALSE)
{
CloseClipboard();
throw std::runtime_error("Unable to empty clipboard");
}
unsigned int length = (text16.length() + 1) * sizeof(std::wstring::value_type);
HANDLE handle = GlobalAlloc(GMEM_MOVEABLE, length);
if (handle == 0)
{
CloseClipboard();
throw std::runtime_error("Unable to allocate clipboard memory");
}
void* data = GlobalLock(handle);
if (data == 0)
{
GlobalFree(handle);
CloseClipboard();
throw std::runtime_error("Unable to lock clipboard memory");
}
memcpy(data, text16.c_str(), length);
GlobalUnlock(handle);
HANDLE data_result = SetClipboardData(CF_UNICODETEXT, handle);
if (data_result == 0)
{
GlobalFree(handle);
CloseClipboard();
throw std::runtime_error("Unable to set clipboard data");
}
CloseClipboard();
}
void Win32Window::PresentBitmap(int width, int height, const uint32_t* pixels)
{
BITMAPV5HEADER header = {};
header.bV5Size = sizeof(BITMAPV5HEADER);
header.bV5Width = width;
header.bV5Height = -height;
header.bV5Planes = 1;
header.bV5BitCount = 32;
header.bV5Compression = BI_BITFIELDS;
header.bV5AlphaMask = 0xff000000;
header.bV5RedMask = 0x00ff0000;
header.bV5GreenMask = 0x0000ff00;
header.bV5BlueMask = 0x000000ff;
header.bV5SizeImage = width * height * sizeof(uint32_t);
header.bV5CSType = LCS_sRGB;
HDC dc = PaintDC;
if (dc != 0)
{
int result = SetDIBitsToDevice(dc, 0, 0, width, height, 0, 0, 0, height, pixels, (const BITMAPINFO*)&header, BI_RGB);
ReleaseDC(WindowHandle, dc);
}
}
LRESULT Win32Window::OnWindowMessage(UINT msg, WPARAM wparam, LPARAM lparam)
{
LPARAM result = 0;
if (DwmDefWindowProc(WindowHandle, msg, wparam, lparam, &result))
return result;
if (msg == WM_INPUT)
{
bool hasFocus = GetFocus() != 0;
HRAWINPUT handle = (HRAWINPUT)lparam;
UINT size = 0;
UINT result = GetRawInputData(handle, RID_INPUT, 0, &size, sizeof(RAWINPUTHEADER));
if (result == 0 && size > 0)
{
size *= 2;
std::vector<uint8_t*> buffer(size);
result = GetRawInputData(handle, RID_INPUT, buffer.data(), &size, sizeof(RAWINPUTHEADER));
if (result >= 0)
{
RAWINPUT* rawinput = (RAWINPUT*)buffer.data();
if (rawinput->header.dwType == RIM_TYPEMOUSE)
{
if (hasFocus)
WindowHost->OnWindowRawMouseMove(rawinput->data.mouse.lLastX, rawinput->data.mouse.lLastY);
}
}
}
return DefWindowProc(WindowHandle, msg, wparam, lparam);
}
else if (msg == WM_PAINT)
{
PAINTSTRUCT paintStruct = {};
PaintDC = BeginPaint(WindowHandle, &paintStruct);
if (PaintDC)
{
WindowHost->OnWindowPaint();
EndPaint(WindowHandle, &paintStruct);
PaintDC = 0;
}
return 0;
}
else if (msg == WM_ACTIVATE)
{
WindowHost->OnWindowActivated();
}
else if (msg == WM_MOUSEMOVE)
{
if (MouseLocked && GetFocus() != 0)
{
RECT box = {};
GetClientRect(WindowHandle, &box);
POINT center = {};
center.x = box.right / 2;
center.y = box.bottom / 2;
ClientToScreen(WindowHandle, &center);
SetCursorPos(center.x, center.y);
}
else
{
UpdateCursor();
}
WindowHost->OnWindowMouseMove(GetLParamPos(lparam));
}
else if (msg == WM_LBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_LBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_LBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_LeftMouse);
}
else if (msg == WM_MBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_MBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_MBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_MiddleMouse);
}
else if (msg == WM_RBUTTONDOWN)
{
WindowHost->OnWindowMouseDown(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_RBUTTONDBLCLK)
{
WindowHost->OnWindowMouseDoubleclick(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_RBUTTONUP)
{
WindowHost->OnWindowMouseUp(GetLParamPos(lparam), IK_RightMouse);
}
else if (msg == WM_MOUSEWHEEL)
{
double delta = GET_WHEEL_DELTA_WPARAM(wparam) / (double)WHEEL_DELTA;
// Note: WM_MOUSEWHEEL uses screen coordinates. GetLParamPos assumes client coordinates.
double dpiscale = GetDpiScale();
POINT pos;
pos.x = GET_X_LPARAM(lparam);
pos.y = GET_Y_LPARAM(lparam);
ScreenToClient(WindowHandle, &pos);
WindowHost->OnWindowMouseWheel(Point(pos.x / dpiscale, pos.y / dpiscale), delta < 0.0 ? IK_MouseWheelDown : IK_MouseWheelUp);
}
else if (msg == WM_CHAR)
{
wchar_t buf[2] = { (wchar_t)wparam, 0 };
WindowHost->OnWindowKeyChar(from_utf16(buf));
}
else if (msg == WM_KEYDOWN)
{
WindowHost->OnWindowKeyDown((EInputKey)wparam);
}
else if (msg == WM_KEYUP)
{
WindowHost->OnWindowKeyUp((EInputKey)wparam);
}
else if (msg == WM_SETFOCUS)
{
if (MouseLocked)
{
::ShowCursor(FALSE);
}
}
else if (msg == WM_KILLFOCUS)
{
if (MouseLocked)
{
::ShowCursor(TRUE);
}
}
else if (msg == WM_CLOSE)
{
WindowHost->OnWindowClose();
return 0;
}
else if (msg == WM_SIZE)
{
WindowHost->OnWindowGeometryChanged();
return 0;
}
/*else if (msg == WM_NCCALCSIZE && wparam == TRUE) // calculate client area for the window
{
NCCALCSIZE_PARAMS* calcsize = (NCCALCSIZE_PARAMS*)lparam;
return WVR_REDRAW;
}*/
return DefWindowProc(WindowHandle, msg, wparam, lparam);
}
void Win32Window::UpdateCursor()
{
LPCWSTR cursor = IDC_ARROW;
switch (CurrentCursor)
{
case StandardCursor::arrow: cursor = IDC_ARROW; break;
case StandardCursor::appstarting: cursor = IDC_APPSTARTING; break;
case StandardCursor::cross: cursor = IDC_CROSS; break;
case StandardCursor::hand: cursor = IDC_HAND; break;
case StandardCursor::ibeam: cursor = IDC_IBEAM; break;
case StandardCursor::no: cursor = IDC_NO; break;
case StandardCursor::size_all: cursor = IDC_SIZEALL; break;
case StandardCursor::size_nesw: cursor = IDC_SIZENESW; break;
case StandardCursor::size_ns: cursor = IDC_SIZENS; break;
case StandardCursor::size_nwse: cursor = IDC_SIZENWSE; break;
case StandardCursor::size_we: cursor = IDC_SIZEWE; break;
case StandardCursor::uparrow: cursor = IDC_UPARROW; break;
case StandardCursor::wait: cursor = IDC_WAIT; break;
default: break;
}
::SetCursor((HCURSOR)LoadImage(0, cursor, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_DEFAULTSIZE, LR_SHARED));
}
Point Win32Window::GetLParamPos(LPARAM lparam) const
{
double dpiscale = GetDpiScale();
return Point(GET_X_LPARAM(lparam) / dpiscale, GET_Y_LPARAM(lparam) / dpiscale);
}
LRESULT Win32Window::WndProc(HWND windowhandle, UINT msg, WPARAM wparam, LPARAM lparam)
{
if (msg == WM_CREATE)
{
CREATESTRUCT* createstruct = (CREATESTRUCT*)lparam;
Win32Window* viewport = (Win32Window*)createstruct->lpCreateParams;
viewport->WindowHandle = windowhandle;
SetWindowLongPtr(windowhandle, GWLP_USERDATA, (LONG_PTR)viewport);
return viewport->OnWindowMessage(msg, wparam, lparam);
}
else
{
Win32Window* viewport = (Win32Window*)GetWindowLongPtr(windowhandle, GWLP_USERDATA);
if (viewport)
{
LRESULT result = viewport->OnWindowMessage(msg, wparam, lparam);
if (msg == WM_DESTROY)
{
SetWindowLongPtr(windowhandle, GWLP_USERDATA, 0);
viewport->WindowHandle = 0;
}
return result;
}
else
{
return DefWindowProc(windowhandle, msg, wparam, lparam);
}
}
}
void Win32Window::ProcessEvents()
{
while (true)
{
MSG msg = {};
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
void Win32Window::RunLoop()
{
while (!ExitRunLoop && !Windows.empty())
{
MSG msg = {};
if (GetMessage(&msg, 0, 0, 0) <= 0)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
ExitRunLoop = false;
}
void Win32Window::ExitLoop()
{
ExitRunLoop = true;
}
Size Win32Window::GetScreenSize()
{
HDC screenDC = GetDC(0);
int screenWidth = GetDeviceCaps(screenDC, HORZRES);
int screenHeight = GetDeviceCaps(screenDC, VERTRES);
double dpiScale = GetDeviceCaps(screenDC, LOGPIXELSX) / 96.0;
ReleaseDC(0, screenDC);
return Size(screenWidth / dpiScale, screenHeight / dpiScale);
}
static void CALLBACK Win32TimerCallback(HWND handle, UINT message, UINT_PTR timerID, DWORD timestamp)
{
auto it = Win32Window::Timers.find(timerID);
if (it != Win32Window::Timers.end())
{
it->second();
}
}
void* Win32Window::StartTimer(int timeoutMilliseconds, std::function<void()> onTimer)
{
UINT_PTR result = SetTimer(0, 0, timeoutMilliseconds, Win32TimerCallback);
if (result == 0)
throw std::runtime_error("Could not create timer");
Timers[result] = std::move(onTimer);
return (void*)result;
}
void Win32Window::StopTimer(void* timerID)
{
auto it = Timers.find((UINT_PTR)timerID);
if (it != Timers.end())
{
Timers.erase(it);
KillTimer(0, (UINT_PTR)timerID);
}
}
std::list<Win32Window*> Win32Window::Windows;
bool Win32Window::ExitRunLoop;
std::unordered_map<UINT_PTR, std::function<void()>> Win32Window::Timers;