Browse Source

Ported basic window functionality to MS-Windows

Dawoodoz 5 years ago
parent
commit
053489737e
1 changed files with 477 additions and 0 deletions
  1. 477 0
      Source/windowManagers/Win32Window.cpp

+ 477 - 0
Source/windowManagers/Win32Window.cpp

@@ -0,0 +1,477 @@
+
+#include "../DFPSR/api/imageAPI.h"
+#include "../DFPSR/gui/BackendWindow.h"
+
+/*
+Link to these dependencies for MS Windows:
+	gdi32
+	user32
+	kernel32
+	comctl32
+*/
+
+#include <tchar.h>
+#include <windows.h>
+#include <windowsx.h>
+
+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<dsr::BackendWindow> createBackendWindow(const dsr::String& title, int width, int height) {
+	auto backend = std::make_shared<Win32Window>(title, width, height);
+	return std::dynamic_pointer_cast<dsr::BackendWindow>(backend);
+}