Newer
Older
invertedlogic / InvertedLogic / iLFramework / toolkit / src / Window.cpp
@John Ryland John Ryland on 10 Nov 2019 15 KB rename
// C++ Headers
#include <cstdio>
#include <map>

// Windows Headers
#define Rectangle WinRectangle
#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#undef Rectangle 

// Project Headers
#include "Window.h"
#include "Containers.h"
#include "MemoryMapping.h"

#define GL_EXPERIMENTS
#ifdef GL_EXPERIMENTS
#include "GL/ImmediateMode.h"
#include "GL/Program.h"
#endif


BEGIN_NAMESPACE


int g_windowCount = 0;
HashMap<HWND,Window*>	g_windowMap;
//static const wchar_t*				 g_windowClass = L"CUTEWindowClass";


struct BackBuffer
{
	HBITMAP				m_hbitmap;
	CUTE_PaintTarget	m_target;
};


struct WindowData
{
	HWND m_hWnd;
	HDC m_handle;
	HGLRC m_glContext;
	BackBuffer m_backBuffer;
	Point m_mousePos;
	MouseButtons m_mouseButtons;
	Size m_size;
	bool m_active;
	int m_nextTimerId;
};


static void InitializeBackBuffer(BackBuffer* a_backBuffer, int a_w, int a_h, int a_depth)
{
#ifdef GL_EXPERIMENTS
	int newW = nextPowerOfTwo(a_w); // Needed for textures with OpenGL
	int newH = nextPowerOfTwo(a_h); // Needed for textures with OpenGL
#else
	int newW = a_w;
	int newH = a_h;
#endif

	BITMAPINFO info = { { sizeof(BITMAPINFOHEADER), newW, -newH, 1, WORD(a_depth), BI_RGB, 0 } };
	a_backBuffer->m_target.m_width  = a_w;
	a_backBuffer->m_target.m_strideBytes = newW * sizeof(int);
	a_backBuffer->m_target.m_height = a_h;
	a_backBuffer->m_target.m_format = (CUTE_PixelFormat)a_depth;
	a_backBuffer->m_hbitmap = CreateDIBSection(0, &info, DIB_RGB_COLORS, (void**)&(a_backBuffer->m_target.m_pixels), 0, 0);
}


static void DestroyBackBuffer(BackBuffer* a_backBuffer)
{
	DeleteObject(a_backBuffer->m_target.m_pixels);
	DeleteObject(a_backBuffer->m_hbitmap);
}


#ifdef GL_EXPERIMENTS

void Window::initialize(HWND a_hwnd)
{
	AppInit(a_hwnd, targetBuffer());
	m_data->m_glContext = wglGetCurrentContext();
}


void Window::paintGL(HWND a_hwnd)
{
	/*
	CUTE::Rectangle rectangle = { {0, 0}, {window->width(), window->height()} };
	Event ev;
	ev.m_type = Event::ET_PaintEvent;
	ev.m_paintEvent.m_rectangle = rectangle;
	window->event(ev);
	*/

	//wglMakeCurrent(glhdc, m_data->m_glContext);
	AppPaint(targetBuffer());
	//wglMakeCurrent(NULL, NULL);
}


void Window::destroy(HWND a_hwnd)
{
	AppDestroy(a_hwnd);
}

#else

void Window::initialize(HWND a_hwnd)
{
}
void Window::paintGL(HWND a_hwnd)
{
	paintUpdate();
}
void Window::destroy(HWND a_hwnd)
{
}
void AppContruct()
{
}
void AppMouseUpdate(int a_x, int a_y, int a_buttonMask, MouseButtons a_mouseButton, MouseState a_mouseState)
{
}
void AppWheelUpdate(int)
{
}
void glDrawText(int x, int y, const char* text, uint32_t a_color, uint32_t a_dropColor, int32_t a_fontSize, bool a_invertColors)
{
}
#endif


#define WINDOW_TRACE(...)


LRESULT CALLBACK MainWndProc(
    HWND hwnd,        // handle to window
    UINT uMsg,        // message identifier
    WPARAM wParam,    // first message parameter
    LPARAM lParam)    // second message parameter
{ 
	Window* window = g_windowMap[hwnd];
	if (!window)
	{
        if (uMsg == WM_CREATE && lParam)
		{
			WINDOW_TRACE("WM_CREATE (during CreateWindow)");
			window = (Window*)((CREATESTRUCT*)lParam)->lpCreateParams;
			window->initialize(hwnd);
		}
		//if (!window)
		{
			// When new windows are added, these messages are seen
			// straight away inside the the call to CreateWindowW():
			//   WM_GETMINMAXINFO  // 36
			//   WM_NCCREATE       // 129
			//   WM_NCCALCSIZE     // 131
			//   WM_CREATE         // 1
			//printf("window not found!\n");
			return DefWindowProc(hwnd, uMsg, wParam, lParam);
		}
	}
	//printf("window found!\n");

	int mouseCase = 0;
	bool keyState = false;
	MouseButtons mbLUT[] = { MB_None, MB_Left, MB_Middle, MB_Right };
	SizeOptions sizeOpts;
 
    switch (uMsg) 
    {
		/*
		case WM_ACTIVATE:
			if (wParam == WA_INACTIVE)
				ReleaseCapture();
			else
				SetCapture(hwnd);
			return 1;
		*/

		case WM_TIMER:
			WINDOW_TRACE("WM_TIMER");
			window->timerUpdate((TimerId)wParam);
            return 0; 

        case WM_CREATE:
			WINDOW_TRACE("WM_CREATE");
			// Initialize the window.
			window->initialize(hwnd);
            return 0; 
 
        case WM_PAINT:
			WINDOW_TRACE("WM_PAINT");
            // Paint the window's client area. 
			window->paintGL(hwnd);
			WINDOW_TRACE("WM_PAINT end");
            return 0; 
 
		case WM_SIZING:
			WINDOW_TRACE("WM_SIZING");
			window->sizeOptions(sizeOpts);
			{
				int constrainWidth = 0, constrainHeight = 0;
				if (((RECT *)lParam)->right - ((RECT *)lParam)->left > sizeOpts.m_maximum.m_width)
					constrainWidth = sizeOpts.m_maximum.m_width;
				if (((RECT *)lParam)->right - ((RECT *)lParam)->left < sizeOpts.m_minimum.m_width)
					constrainWidth = sizeOpts.m_minimum.m_width;
				if (((RECT *)lParam)->bottom - ((RECT *)lParam)->top > sizeOpts.m_maximum.m_height)
					constrainHeight = sizeOpts.m_maximum.m_height;
				if (((RECT *)lParam)->bottom - ((RECT *)lParam)->top < sizeOpts.m_minimum.m_height)
					constrainHeight = sizeOpts.m_minimum.m_height;
				if (constrainWidth)
				{
					if (wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_LEFT || wParam == WMSZ_TOPLEFT)
						((RECT *)lParam)->left = ((RECT *)lParam)->right - constrainWidth - 1;
					else
						((RECT *)lParam)->right = ((RECT *)lParam)->left + constrainWidth + 1;
				}
				if (constrainHeight)
				{
					if (wParam == WMSZ_TOPLEFT || wParam == WMSZ_TOP || wParam == WMSZ_TOPRIGHT)
						((RECT *)lParam)->top = ((RECT *)lParam)->bottom - constrainHeight - 1;
					else
						((RECT *)lParam)->bottom = ((RECT *)lParam)->top + constrainHeight + 1;
				}
			}
			return 0;

		case WM_SIZE: 
			WINDOW_TRACE("WM_SIZE  %i  %i", LOWORD(lParam) + 1, HIWORD(lParam) + 1);
			printf("WM_SIZE  %i  %i", LOWORD(lParam) + 1, HIWORD(lParam) + 1);
			window->destroy(hwnd);
			window->resize(LOWORD(lParam) + 1, HIWORD(lParam) + 1);
            // Set the size and position of the window. 
			window->initialize(hwnd); // Recreate the GL context
            return 0; 
 
		case WM_RBUTTONDBLCLK:	mouseCase++;
		case WM_RBUTTONDOWN:	mouseCase++;
		case WM_RBUTTONUP:		mouseCase++;

		case WM_MBUTTONDBLCLK:	mouseCase++;
		case WM_MBUTTONDOWN:	mouseCase++;
		case WM_MBUTTONUP:		mouseCase++;

		case WM_LBUTTONDBLCLK:	mouseCase++;
		case WM_LBUTTONDOWN:	mouseCase++;
		case WM_LBUTTONUP:      mouseCase+=3;

		case WM_MOUSEMOVE:
			AppMouseUpdate(LOWORD(lParam), HIWORD(lParam), (int)wParam, mbLUT[mouseCase / 3], (MouseState)(mouseCase % 3));
			window->mouseUpdate(LOWORD(lParam), HIWORD(lParam), (int)wParam, mbLUT[mouseCase / 3], (MouseState)(mouseCase % 3));
			return 0;

		case WM_MOUSEWHEEL:
			AppWheelUpdate((short)HIWORD(wParam));
			window->wheelUpdate((short)HIWORD(wParam));
			window->paintGL(hwnd);
			return 0;

		case WM_KEYDOWN:
			keyState = true;
		case WM_KEYUP:
			window->keyUpdate((int)wParam, keyState);
			return 0;
 
        case WM_DESTROY:
			WINDOW_TRACE("WM_DESTROY");
			window->deactivate();
            // Clean up window-specific data objects.
			window->destroy(hwnd);
			if ( !g_windowCount )
				PostQuitMessage(1);
            return 0; 
 
		case WM_SETCURSOR:
 			SetCursor(LoadCursor(NULL, IDC_ARROW));
			return 1;

        // 
        // Process other messages. 
        // 
 
        default: 
            return DefWindowProc(hwnd, uMsg, wParam, lParam); 
    }

    return -1;
} 


Window::Window(const char* a_title, bool a_fullscreen) : Widget(0, true, 0, 0, 0, 0)
{
	AppContruct();

	g_windowCount++;
	m_data = new WindowData;
	m_data->m_handle = NULL;
	m_data->m_mouseButtons = MB_None;
	m_data->m_mousePos.m_x = 0;
	m_data->m_mousePos.m_y = 0;
	m_data->m_nextTimerId = 1;
	InitializeBackBuffer(&m_data->m_backBuffer, 1, 1, 32);

	/*
	if ( a_fullscreen )
	{
		//m_data->m_hWnd = CreateWindowEx( 0, L"STATIC", L"", WS_POPUP, 0, 0, m_data->w, m_data->h, NULL, NULL, m_data->hInstance, NULL);
	}
	else
	*/
	{
		m_data->m_hWnd = CreateWindowW(L"MainWClass", String(a_title).toWString().c_str(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT , NULL, NULL, GetModuleHandle(0), this); 
	}
	g_windowMap[m_data->m_hWnd] = this;
	//g_windowMap.insert(m_data->m_hWnd, this); // this doesn't work when using std::map, somehow different to above line
	ShowWindow(m_data->m_hWnd, SW_SHOW);
	UpdateWindow(m_data->m_hWnd);
	m_data->m_active = true;

	SetCursor(LoadCursor(NULL, IDC_ARROW));
}


Window::~Window()
{
	if (m_data->m_active)
	{
		//DestroyWindow(m_data->m_hWnd);
		deactivate();
	}
	delete m_data;
}


void Window::deactivate()
{
	if (m_data->m_active)
	{
		m_data->m_active = false;
		g_windowMap.erase(m_data->m_hWnd);
		DestroyWindow(m_data->m_hWnd);
		g_windowCount--;
		DestroyBackBuffer(&m_data->m_backBuffer);
		DeleteDC(m_data->m_handle);
	}
}


void Window::sizeOptions(SizeOptions& a_sizeOptions)
{
	Widget::sizeOptions(a_sizeOptions);

	RECT wRect, cRect;
	GetWindowRect(m_data->m_hWnd, &wRect);
	GetClientRect(m_data->m_hWnd, &cRect);
	int windowBorderHeight = wRect.bottom - cRect.bottom + cRect.top - wRect.top;
	int windowBorderWidth = wRect.right - cRect.right + cRect.left - wRect.left;
	a_sizeOptions.m_minimum.m_height += windowBorderHeight;
	a_sizeOptions.m_preferred.m_height += windowBorderHeight;
	a_sizeOptions.m_maximum.m_height += windowBorderHeight;
	a_sizeOptions.m_minimum.m_width += windowBorderWidth;
	a_sizeOptions.m_preferred.m_width += windowBorderWidth;
	a_sizeOptions.m_maximum.m_width += windowBorderWidth;
}


TimerId Window::startTimer(int a_milliSeconds)
{
	return (TimerId)SetTimer(m_data->m_hWnd, m_data->m_nextTimerId++, a_milliSeconds, 0);
}


void Window::killTimer(TimerId a_timerId)
{
	KillTimer(m_data->m_hWnd, (UINT_PTR)a_timerId);
}


void Window::timerUpdate(TimerId a_timerId)
{
	Event ev;
	ev.m_type = Event::ET_TimerEvent;
	ev.m_timerEvent.m_timerId = a_timerId;
	event(ev);
}


void Window::mouseUpdate(int a_x, int a_y, int a_buttonMask, MouseButtons a_mouseButton, MouseState a_mouseState)
{
	Event ev;
	ev.m_type = Event::ET_MouseEvent;
	ev.m_mouseEvent.m_oldPosition = m_data->m_mousePos;
	ev.m_mouseEvent.m_oldButtons = m_data->m_mouseButtons;
	
/*
	POINT mousePos;
	GetCursorPos(&mousePos);
	m_data->m_mouseX = mousePos.x;
	m_data->m_mouseY = mousePos.y;
*/

	m_data->m_mousePos.m_x = a_x;
	m_data->m_mousePos.m_y = a_y;
	if ( a_mouseState == MS_Up )
		m_data->m_mouseButtons = MouseButtons((int)m_data->m_mouseButtons & ~(int)a_mouseButton);
	else
		m_data->m_mouseButtons |= a_mouseButton;

	ev.m_mouseEvent.m_position = m_data->m_mousePos;
	ev.m_mouseEvent.m_buttons = m_data->m_mouseButtons;
	event(ev);
}


void Window::wheelUpdate(int a_wheelVal)
{
	Event ev;
	ev.m_type = Event::ET_WheelEvent;
	ev.m_wheelEvent.m_degreesRotation = a_wheelVal;
	event(ev);
}


void Window::keyUpdate(int a_keyCode, bool a_keyDown)
{
	Event ev;
	ev.m_type = Event::ET_KeyEvent;
	ev.m_keyEvent.m_key = (Key)a_keyCode; // TODO
	ev.m_keyEvent.m_state = (a_keyDown) ? KS_Pressed : KS_Released;
	ev.m_keyEvent.m_modifiers = M_None; // TODO
	event(ev);
}


void Window::resize(int a_newW, int a_newH)
{
	Event ev;
	ev.m_type = Event::ET_SizeEvent;
	ev.m_sizeEvent.m_old = m_data->m_size;

	RECT rect;
	GetClientRect(m_data->m_hWnd, &rect);
	m_data->m_size.m_width = rect.right - rect.left + 1;
	m_data->m_size.m_height = rect.bottom - rect.top + 1;

	if ( a_newW != m_data->m_size.m_width || a_newH != m_data->m_size.m_height )
		printf("resizing to %i %i   %i %i\n", a_newW, a_newH, m_data->m_size.m_width, m_data->m_size.m_height);

	//if (m_data->m_backBuffer)
	DestroyBackBuffer(&m_data->m_backBuffer);
	InitializeBackBuffer(&m_data->m_backBuffer, m_data->m_size.m_width, m_data->m_size.m_height, 32);
	if (!m_data->m_backBuffer.m_target.m_pixels)
	{
		printf("error making back buffer\n");
		m_data->m_backBuffer.m_target.m_width = 0;
		m_data->m_backBuffer.m_target.m_height = 0;
	}

	//m_data->lastTick = clock();
	HDC hdc = GetDC(m_data->m_hWnd);
	DeleteDC(m_data->m_handle);
	m_data->m_handle = CreateCompatibleDC(hdc);
	SelectObject(m_data->m_handle, m_data->m_backBuffer.m_hbitmap);
	ReleaseDC(m_data->m_hWnd, hdc);

	//sizeEvent(a_newW, a_newH);

	updateLayout(); // perhaps this could be let to cascade through the recursive SizeEvent processing?

	ev.m_sizeEvent.m_new = m_data->m_size;
	event(ev);	
}



/*
// Game loop style update
void Window::doUpdate()
{
	printf("updating\n");
	SHORT space  = GetAsyncKeyState(VK_SPACE);
	SHORT left   = GetAsyncKeyState(VK_LBUTTON);
	SHORT right  = GetAsyncKeyState(VK_RBUTTON);
	SHORT middle = GetAsyncKeyState(VK_MBUTTON);	
	int keyState = ((space) ? 8 : 0) | ((left) ? 4 : 0) | ((right) ? 2 : 0)  | ((middle) ? 1 : 0);
}
*/


void Window::paintUpdate()
{
	RECT rect;
	GetUpdateRect(m_data->m_hWnd, &rect, false);
	Rectangle a_rectangle = { { { rect.left, rect.top } },
		{ { rect.right - rect.left + 1, rect.bottom - rect.top + 1 } } };
	/*
	update(rectangle);
	printf("paintUpdate end\n");
	*/

	Event ev;
	ev.m_type = Event::ET_PaintEvent;
	ev.m_paintEvent.m_rectangle = a_rectangle;

	// TODO: when inside an update, clip the painting to the update rect.
	// Clipping can be achieved by fiddling with the paintTarget
	// This is clipping within our portion of the drawing code, not the win32 api part

	// This sends out paintEvents
	event(ev);
	//paintEvent(&m_data->m_backBuffer.m_target, m_data->m_mouseX, m_data->m_mouseY, keyState);

	// Using BeginPaint will make the drawing portion with the win32 APIs clipped
	HDC hdc = GetDC(m_data->m_hWnd);
	//StretchBlt(hdc, 0, 0, m_data->m_backBuffer.m_target.m_width, m_data->m_backBuffer.m_target.m_height, m_data->m_handle, 0, 0, 640, 480, SRCCOPY);
	BitBlt(hdc, 0, 0, m_data->m_backBuffer.m_target.m_width, m_data->m_backBuffer.m_target.m_height, m_data->m_handle, 0, 0, SRCCOPY);
	ReleaseDC(m_data->m_hWnd, hdc);

	// Also using BeginPaint/EndPaint avoid the need to call ValidateRect
	// but using BeginPaint/EndPaint and InvalidateRect may make the
	// code end up less refactored
	RECT r = { a_rectangle.m_x, a_rectangle.m_y, 
		a_rectangle.m_x + a_rectangle.m_width - 1, a_rectangle.m_y + a_rectangle.m_height - 1 };
	ValidateRect(m_data->m_hWnd, &r);
}


void Window::update(Rectangle& a_rectangle)
{
	RECT r = { a_rectangle.m_x, a_rectangle.m_y, 
		a_rectangle.m_x + a_rectangle.m_width - 1, a_rectangle.m_y + a_rectangle.m_height - 1 };
	InvalidateRect(m_data->m_hWnd, &r, false);
/*
	printf("update\n");
	Event ev;
	ev.m_type = Event::ET_PaintEvent;
	ev.m_paintEvent.m_rectangle = a_rectangle;

	// TODO: when inside an update, clip the painting to the update rect.
	// Clipping can be achieved by fiddling with the paintTarget
	// This is clipping within our portion of the drawing code, not the win32 api part

	// This sends out paintEvents
	printf("update2\n");
	event(ev);
	printf("update3\n");
	//paintEvent(&m_data->m_backBuffer.m_target, m_data->m_mouseX, m_data->m_mouseY, keyState);

	// Using BeginPaint will make the drawing portion with the win32 APIs clipped
	HDC hdc = GetDC(m_data->m_hWnd);
	//StretchBlt(hdc, 0, 0, m_data->m_backBuffer.m_target.m_width, m_data->m_backBuffer.m_target.m_height, m_data->m_handle, 0, 0, 640, 480, SRCCOPY);
	BitBlt(hdc, 0, 0, m_data->m_backBuffer.m_target.m_width, m_data->m_backBuffer.m_target.m_height, m_data->m_handle, 0, 0, SRCCOPY);
	ReleaseDC(m_data->m_hWnd, hdc);

	// Also using BeginPaint/EndPaint avoid the need to call ValidateRect
	// but using BeginPaint/EndPaint and InvalidateRect may make the
	// code end up less refactored
	printf("update4\n");
	RECT r = { a_rectangle.m_x, a_rectangle.m_y, 
		a_rectangle.m_x + a_rectangle.m_width - 1, a_rectangle.m_y + a_rectangle.m_height - 1 };
	ValidateRect(m_data->m_hWnd, &r);
	*/
}


CUTE_PaintTarget& Window::targetBuffer()
{
	return m_data->m_backBuffer.m_target;
}


END_NAMESPACE