#include "../DFPSR/api/imageAPI.h" #include "../DFPSR/gui/BackendWindow.h" /* Link to these dependencies for MS Windows: gdi32 user32 kernel32 comctl32 */ #include #include #include class Win32Window : public dsr::BackendWindow { public: // The native windows handle HWND hwnd; // Double buffering to allow drawing to a canvas while displaying the previous one // The image which can be drawn to, sharing memory with the X11 image dsr::AlignedImageRgbaU8 canvas; // Remembers the dimensions of the window from creation and resize events // This allow requesting the size of the window at any time int windowWidth = 0, windowHeight = 0; private: // Called before the application fetches events from the input queue // Closing the window, moving the mouse, pressing a key, et cetera void prefetchEvents() override; private: // Helper methods specific to calling XLib void updateTitle(); private: // Canvas methods dsr::AlignedImageRgbaU8 getCanvas() override { return this->canvas; } void resizeCanvas(int width, int height) override; // Window methods void setTitle(const dsr::String &newTitle) override { this->title = newTitle; this->updateTitle(); } void removeOldWindow(); void createWindow(const dsr::String& title, int width, int height); void createWindowed(const dsr::String& title, int width, int height); void createFullscreen(); void prepareWindow(); int windowState = 0; // 0=none, 1=windowed, 2=fullscreen public: // Constructors Win32Window(const Win32Window&) = delete; // Non-copyable because of pointer aliasing. Win32Window(const dsr::String& title, int width, int height); int getWidth() const override { return this->windowWidth; }; int getHeight() const override { return this->windowHeight; }; // Destructor ~Win32Window(); // Interface void setFullScreen(bool enabled) override; bool isFullScreen() override { return this->windowState == 2; } void redraw(HWND& hwnd); // HWND is passed by argument because drawing might be called before the constructor has assigned it to this->hwnd void showCanvas() override; }; static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); static TCHAR windowClassName[] = _T("DfpsrWindowApplication"); void Win32Window::updateTitle() { //XSetStandardProperties(this->display, this->window, this->title.toStdString().c_str(), "Icon", None, NULL, 0, NULL); } void Win32Window::setFullScreen(bool enabled) { if (this->windowState == 1 && enabled) { // Clean up any previous X11 window removeOldWindow(); // Create the new window and graphics context this->createFullscreen(); } else if (this->windowState == 2 && !enabled) { // Clean up any previous X11 window removeOldWindow(); // Create the new window and graphics context this->createWindowed(this->title, 800, 600); // TODO: Remember the dimensions from last windowed mode } } void Win32Window::removeOldWindow() { if (this->windowState != 0) { DestroyWindow(this->hwnd); } this->windowState = 0; } void Win32Window::prepareWindow() { // Reallocate the canvas this->resizeCanvas(this->windowWidth, this->windowHeight); // Show the window ShowWindow(this->hwnd, SW_NORMAL); // Repaint UpdateWindow(this->hwnd); } void Win32Window::createWindow(const dsr::String& title, int width, int height) { // Request to resize the canvas and interface according to the new window this->windowWidth = width; this->windowHeight = height; this->receivedWindowResize(width, height); // The Window structure WNDCLASSEX wincl; memset(&wincl, 0, sizeof(WNDCLASSEX)); wincl.hInstance = NULL; wincl.lpszClassName = windowClassName; wincl.lpfnWndProc = WindowProcedure; wincl.style = 0; wincl.cbSize = sizeof(WNDCLASSEX); // Use default icon and mouse-pointer wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; // No menu wincl.cbClsExtra = 0; // No extra bytes after the window class wincl.cbWndExtra = sizeof(LPVOID); // structure or the window instance // Use Windows's default color as the background of the window wincl.hbrBackground = (HBRUSH)COLOR_BACKGROUND; // TODO: Make black // Register the window class, and if it fails quit the program if (!RegisterClassEx (&wincl)) { dsr::throwError("Call to RegisterClassEx failed!\n"); } // The class is registered, let's create the program this->hwnd = CreateWindowEx( 0, // Extended possibilites for variation windowClassName, // Classname _T("MyWindow"), // Title Text WS_OVERLAPPEDWINDOW, // default window CW_USEDEFAULT, // Windows decides the position CW_USEDEFAULT, // where the window ends up on the screen width, // The programs width height, // and height in pixels HWND_DESKTOP, // The window is a child-window to desktop NULL, // No menu NULL, // Program Instance handler (LPVOID)this // Pointer to the window wrapper ); // TODO: Set the title this->updateTitle(); } void Win32Window::createWindowed(const dsr::String& title, int width, int height) { // Create the window this->createWindow(title, width, height); this->windowState = 1; this->prepareWindow(); } void Win32Window::createFullscreen() { // TODO: Implement borderless, decorationless, maximized full-screen createWindowed("Full-screen is not yet supported on Windows", 800, 600); this->windowState = 2; this->prepareWindow(); } Win32Window::Win32Window(const dsr::String& title, int width, int height) { bool fullScreen = false; if (width < 1 || height < 1) { fullScreen = true; } // Remember the title this->title = title; // Create a window if (fullScreen) { this->createFullscreen(); } else { this->createWindowed(title, width, height); } } static dsr::DsrKey getDsrKey(WPARAM keyCode) { dsr::DsrKey result = dsr::DsrKey_Unhandled; if (keyCode == VK_ESCAPE) { result = dsr::DsrKey_Escape; } else if (keyCode == VK_F1) { result = dsr::DsrKey_F1; } else if (keyCode == VK_F2) { result = dsr::DsrKey_F2; } else if (keyCode == VK_F3) { result = dsr::DsrKey_F3; } else if (keyCode == VK_F4) { result = dsr::DsrKey_F4; } else if (keyCode == VK_F5) { result = dsr::DsrKey_F5; } else if (keyCode == VK_F6) { result = dsr::DsrKey_F6; } else if (keyCode == VK_F7) { result = dsr::DsrKey_F7; } else if (keyCode == VK_F8) { result = dsr::DsrKey_F8; } else if (keyCode == VK_F9) { result = dsr::DsrKey_F9; } else if (keyCode == VK_F10) { result = dsr::DsrKey_F10; } else if (keyCode == VK_F11) { result = dsr::DsrKey_F11; } else if (keyCode == VK_F12) { result = dsr::DsrKey_F12; } else if (keyCode == VK_PAUSE) { result = dsr::DsrKey_Pause; } else if (keyCode == VK_SPACE) { result = dsr::DsrKey_Space; } else if (keyCode == VK_TAB) { result = dsr::DsrKey_Tab; } else if (keyCode == VK_RETURN) { result = dsr::DsrKey_Return; } else if (keyCode == VK_BACK) { result = dsr::DsrKey_BackSpace; } else if (keyCode == VK_LSHIFT) { result = dsr::DsrKey_LeftShift; } else if (keyCode == VK_RSHIFT) { result = dsr::DsrKey_RightShift; } else if (keyCode == VK_LCONTROL) { result = dsr::DsrKey_LeftControl; } else if (keyCode == VK_RCONTROL) { result = dsr::DsrKey_RightControl; } else if (keyCode == VK_LMENU) { result = dsr::DsrKey_LeftAlt; } else if (keyCode == VK_RMENU) { result = dsr::DsrKey_RightAlt; } else if (keyCode == VK_DELETE) { result = dsr::DsrKey_Delete; } else if (keyCode == VK_LEFT) { result = dsr::DsrKey_LeftArrow; } else if (keyCode == VK_RIGHT) { result = dsr::DsrKey_RightArrow; } else if (keyCode == VK_UP) { result = dsr::DsrKey_UpArrow; } else if (keyCode == VK_DOWN) { result = dsr::DsrKey_DownArrow; } else if (keyCode == 0x30) { result = dsr::DsrKey_0; } else if (keyCode == 0x31) { result = dsr::DsrKey_1; } else if (keyCode == 0x32) { result = dsr::DsrKey_2; } else if (keyCode == 0x33) { result = dsr::DsrKey_3; } else if (keyCode == 0x34) { result = dsr::DsrKey_4; } else if (keyCode == 0x35) { result = dsr::DsrKey_5; } else if (keyCode == 0x36) { result = dsr::DsrKey_6; } else if (keyCode == 0x37) { result = dsr::DsrKey_7; } else if (keyCode == 0x38) { result = dsr::DsrKey_8; } else if (keyCode == 0x39) { result = dsr::DsrKey_9; } else if (keyCode == 0x41) { result = dsr::DsrKey_A; } else if (keyCode == 0x42) { result = dsr::DsrKey_B; } else if (keyCode == 0x43) { result = dsr::DsrKey_C; } else if (keyCode == 0x44) { result = dsr::DsrKey_D; } else if (keyCode == 0x45) { result = dsr::DsrKey_E; } else if (keyCode == 0x46) { result = dsr::DsrKey_F; } else if (keyCode == 0x47) { result = dsr::DsrKey_G; } else if (keyCode == 0x48) { result = dsr::DsrKey_H; } else if (keyCode == 0x49) { result = dsr::DsrKey_I; } else if (keyCode == 0x4A) { result = dsr::DsrKey_J; } else if (keyCode == 0x4B) { result = dsr::DsrKey_K; } else if (keyCode == 0x4C) { result = dsr::DsrKey_L; } else if (keyCode == 0x4D) { result = dsr::DsrKey_M; } else if (keyCode == 0x4E) { result = dsr::DsrKey_N; } else if (keyCode == 0x4F) { result = dsr::DsrKey_O; } else if (keyCode == 0x50) { result = dsr::DsrKey_P; } else if (keyCode == 0x51) { result = dsr::DsrKey_Q; } else if (keyCode == 0x52) { result = dsr::DsrKey_R; } else if (keyCode == 0x53) { result = dsr::DsrKey_S; } else if (keyCode == 0x54) { result = dsr::DsrKey_T; } else if (keyCode == 0x55) { result = dsr::DsrKey_U; } else if (keyCode == 0x56) { result = dsr::DsrKey_V; } else if (keyCode == 0x57) { result = dsr::DsrKey_W; } else if (keyCode == 0x58) { result = dsr::DsrKey_X; } else if (keyCode == 0x59) { result = dsr::DsrKey_Y; } else if (keyCode == 0x5A) { result = dsr::DsrKey_Z; } return result; } // Called from DispatchMessage via prefetchEvents static LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { // Get the Win32Window owning the given hwnd Win32Window *parent = nullptr; if (message == WM_CREATE) { // Cast the pointer argument into CREATESTRUCT and get the lParam given to the window on creation CREATESTRUCT *createStruct = (CREATESTRUCT*)lParam; parent = (Win32Window*)createStruct->lpCreateParams; if (parent == nullptr) { dsr::throwError("Null handle retreived from lParam (", (intptr_t)parent, ") in WM_CREATE message.\n"); } SetWindowLongPtr(hwnd, GWLP_USERDATA, (intptr_t)parent); } else { // Get the parent parent = (Win32Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA); if (parent == nullptr) { // Don't try to handle global events unrelated to any window return DefWindowProc(hwnd, message, wParam, lParam); } } // Check that we're using the correct window instance (This might not be the case while toggling full-screen) // Handle the message int result = 0; switch (message) { case -1: dsr::throwError("Unknown error in Window handler!\n"); break; case WM_QUIT: PostQuitMessage(wParam); break; case WM_CLOSE: parent->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Close, parent->windowWidth, parent->windowHeight)); DestroyWindow(hwnd); break; case WM_LBUTTONDOWN: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Left, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_LBUTTONUP: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Left, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_RBUTTONDOWN: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Right, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_RBUTTONUP: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Right, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_MBUTTONDOWN: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseDown, dsr::MouseKeyEnum::Middle, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_MBUTTONUP: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, dsr::MouseKeyEnum::Middle, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_MOUSEMOVE: parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); break; case WM_MOUSEWHEEL: { int delta = GET_WHEEL_DELTA_WPARAM(wParam); if (delta > 0) { parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollUp, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); } else if (delta < 0) { parent->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::Scroll, dsr::MouseKeyEnum::ScrollDown, dsr::IVector2D(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)))); } } break; case WM_KEYDOWN: case WM_KEYUP: { char character = wParam; // System specific key-code dsr::DsrKey dsrKey = getDsrKey(wParam); // Portable key-code if (message == WM_KEYDOWN) { // Physical key down parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyDown, character, dsrKey)); // First press typing parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey)); } else { // message == WM_KEYUP // Physical key up parent->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyUp, character, dsrKey)); } } break; case WM_PAINT: parent->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Redraw, parent->windowWidth, parent->windowHeight)); // BeginPaint and EndPaint must be called with the given hwnd to prevent having the redraw message sent again parent->redraw(hwnd); // Passing on the event to prevent flooding with more messages. This is only a temporary solution. // TODO: Avoid overwriting the result with any background color. //result = DefWindowProc(hwnd, message, wParam, lParam); break; case WM_SIZE: // If there's no size during minimization, don't try to resize the canvas if (wParam != SIZE_MINIMIZED) { int width = LOWORD(lParam); int height = HIWORD(lParam); parent->windowWidth = width; parent->windowHeight = height; parent->receivedWindowResize(width, height); } // Resize the window as requested result = DefWindowProc(hwnd, message, wParam, lParam); break; // TODO: Keyboard presses & typing default: result = DefWindowProc(hwnd, message, wParam, lParam); } return result; } void Win32Window::prefetchEvents() { MSG messages; // Windows hangs unless we process application events for each window while (PeekMessage(&messages, NULL, 0, 0, PM_REMOVE)) { //dsr::printText("Received an event ", messages.message, "(", messages.wParam, ", ", (intptr_t)messages.lParam, ")\n"); TranslateMessage(&messages); DispatchMessage(&messages); // Calling WindowProcedure for each window instance } } // Locked because it overrides void Win32Window::resizeCanvas(int width, int height) { // Create a new canvas // Even thou Windows is using RGBA pack order for the window, the bitmap format used for drawing is using BGRA order this->canvas = dsr::image_create_RgbaU8_native(width, height, dsr::PackOrderIndex::BGRA); } Win32Window::~Win32Window() { // Destroy the native window DestroyWindow(this->hwnd); } void Win32Window::redraw(HWND& hwnd) { // Let the source bitmap use a padded width to safely handle the stride // Windows require 8-byte alignment, but the image format uses 16-byte alignment. int paddedWidth = dsr::image_getStride(this->canvas) / 4; int width = dsr::image_getWidth(this->canvas); int height = dsr::image_getHeight(this->canvas); InvalidateRect(this->hwnd, NULL, false); BITMAP bitmap; HBITMAP sourceBitmapHandle = CreateBitmap(paddedWidth, height, 1, 32, dsr::image_dangerous_getData(this->canvas)); PAINTSTRUCT paintStruct; HDC targetContext = BeginPaint(this->hwnd, &paintStruct); HDC sourceContext = CreateCompatibleDC(targetContext); HGDIOBJ sourceBitmap = SelectObject(sourceContext, sourceBitmapHandle); GetObject(sourceBitmapHandle, sizeof(BITMAP), &bitmap); BitBlt(targetContext, 0, 0, width, height, sourceContext, 0, 0, SRCCOPY); SelectObject(sourceContext, sourceBitmap); DeleteDC(sourceContext); EndPaint(this->hwnd, &paintStruct); } void Win32Window::showCanvas() { this->prefetchEvents(); this->redraw(this->hwnd); } std::shared_ptr createBackendWindow(const dsr::String& title, int width, int height) { auto backend = std::make_shared(title, width, height); return std::dynamic_pointer_cast(backend); }