Newer
Older
Import / research / ui / toolkit / src / backup / Window.cpp
// C++ Headers
#include <cstdio>
#include <map>

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

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

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


#if _WIN32


BEGIN_NAMESPACE



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


struct BackBuffer
{
	HBITMAP				m_hbitmap;
	PixelBuffer	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 = (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)
{
	/*
	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(a_title, true)
{
	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);
	*/
}


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


END_NAMESPACE


#else


#include <objc/message.h>
#include <objc/runtime.h>
#include <ApplicationServices/ApplicationServices.h>
#include <OpenGL/gl.h>
#include <pthread.h>
#include <unistd.h>
#include <unordered_map>



//////////////////////////////////////////////////////////////////
//
// Objective-C Wrapper code to make it a bit easier to
// call ObjC code directly and efficiently from C++
//
// Copyright 2017
// John Ryland
//
//////////////////////////////////////////////////////////////////
SEL selCache(const char* funcName) 
{
  static std::unordered_map<std::string, SEL> cache;
  if (cache.find(funcName) == cache.end())                
    cache[funcName] = sel_getUid(funcName);
  return cache[funcName];
}

id classCache(const char* className) 
{
  static std::unordered_map<std::string, id> cache;
  if (cache.find(className) == cache.end())                
    cache[className] = objc_getClass(className);
  return cache[className];
}

class ObjcCallable;
class ObjcObj
{
public:
  ObjcObj(const char* className) : m_obj(objc_msgSend(classCache(className), selCache("alloc"))) {}
  ObjcObj(id a_obj) : m_obj(a_obj) {}
  ObjcCallable operator[](const char* func);
  id m_obj;

  template <typename T>
  T get(const char* propertyName)
  {
    T val = 0;
    object_getInstanceVariable(m_obj, propertyName, (void**)&val);
    return val;
  }

  template <typename T>
  void set(const char* propertyName, T val)
  {
    object_setInstanceVariable(m_obj, propertyName, (void*)val);
  }
};

class ObjcCallable
{
public:
  ObjcCallable(id a_obj, SEL a_sel) : m_obj(a_obj), m_sel(a_sel) {}

  template <typename ...Ts>
  ObjcObj operator()(Ts... args)
  {
    return ObjcObj(objc_msgSend(m_obj, m_sel, args...));
  }

  // Calling ObjC from C++ isn't very type safe, it is up to the caller to
  // know/check what the appropriate input and output types are to the given function
  template <typename R, typename ...Ts>
  R call(Ts... args)
  {
    return (*(R(*)(id, SEL, ...))&objc_msgSend)(m_obj, m_sel, args...);
  }
private:
  id m_obj;
  SEL m_sel;
};

inline ObjcCallable ObjcObj::operator[](const char* func)
{
  return ObjcCallable(m_obj, selCache(func));
}
//////////////////////////////////////////////////////////////////



BEGIN_NAMESPACE


struct NSRect { double x, y, width, height; };
void WindowOnDraw(id self, SEL _cmd, NSRect rect)
{
  Window* winPtr = ObjcObj(self).get<Window*>("WindowPtr");
  if (winPtr)
    winPtr->paintUpdate();
}

struct NSSize { double width, height; };
void WindowOnResize(id self, SEL _cmd, NSSize newSize)
{
  Window* winPtr = ObjcObj(self).get<Window*>("WindowPtr");
  if (winPtr)
    winPtr->resize(int(newSize.width), int(newSize.height));
}

struct NSEvent;
struct NSPoint { double x, y; };
void WindowOnMouseEvent(id self, SEL _cmd, NSEvent* event)
{
  Window* winPtr = ObjcObj(self).get<Window*>("WindowPtr");
  NSPoint pnt = ObjcObj((id)event)["locationInWindow"].call<NSPoint>();
  unsigned buttons = ObjcObj(classCache("NSEvent"))["pressedMouseButtons"].call<unsigned>();
  if (winPtr)
  {
    int h = winPtr->targetBuffer().m_height;
    winPtr->mouseUpdate(int(pnt.x), h-int(pnt.y), 0, MB_Left, (buttons) ? MS_Down : MS_Up);
  }
}

void ProcessTimerEvent(id self, SEL _cmd, void* param)
{
  printf("timer event\n");
  // TODO: send to timerUpdate with param
  Window* winPtr = ObjcObj(self).get<Window*>("WindowPtr");
  if (winPtr)
  {
    winPtr->repaint();
    //winPtr->timerUpdate((TimerId)param);
  }
}


struct WindowData
{
  ObjcObj m_nativeHandle      = ObjcObj{(id)nullptr};
  ObjcObj m_view              = ObjcObj{(id)nullptr};
	Point m_mousePos            = (Point){ 0, 0 };
	MouseButtons m_mouseButtons = MB_None;
	Size m_size                 = (Size){ 1, 1 };
	bool m_active               = true;
	int m_nextTimerId           = 1;
	bool m_invalidated          = false;
  int m_texState              = 0; // 0 - not-created, 1 - re-create, 2 - valid
  GLuint m_texId              = 0;
	PixelBuffer m_backBuffer;
};


static void InitializeBackBuffer(PixelBuffer& a_backBuffer, int a_w, int a_h, int a_depth)
{
	a_backBuffer.m_width  = a_w;
	a_backBuffer.m_strideBytes = a_w * sizeof(int);
	a_backBuffer.m_height = a_h;
	a_backBuffer.m_format = (PixelFormat)a_depth;
  a_backBuffer.m_pixels = new uint32_t[a_w * a_h];
}


static void DestroyBackBuffer(PixelBuffer& a_backBuffer)
{
	delete[] a_backBuffer.m_pixels;
}


Window::Window(const char* a_title, bool a_fullscreen)
  : Widget(a_title, true)
{
  // TODO: this shouldn't need to have to match the size the window becomes
  double w = 1, h = 1; // initialize as 1x1 in size
	m_data = new WindowData;
	InitializeBackBuffer(m_data->m_backBuffer, w, h, 32);

  // NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSWindowStyleMaskFullSizeContentView
  unsigned windowFlags = (1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<15);

  ObjcObj win = ObjcObj("NSWindow")["initWithContentRect:styleMask:backing:defer:"]((NSRect){ 0, 0, w, h }, windowFlags, 2, false);
  m_data->m_nativeHandle = win;
  m_data->m_view = ObjcObj("View")["initWithFrame:"]((NSRect){ 0, 0, w, h });
  m_data->m_view.set("WindowPtr", this);
  win["setContentView:"](m_data->m_view.m_obj);
  win["becomeFirstResponder"]();
  win["makeKeyAndOrderFront:"](win.m_obj);

  // Periodic update timer - TODO: probably don't need, but works as a way to test setting up and using timers
  ObjcObj(classCache("NSTimer"))["scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"](
    2.0, m_data->m_view.m_obj, selCache("processTimerEvent:"), nil, YES);
}


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


void Window::deactivate()
{
	if (m_data->m_active)
	{
		m_data->m_active = false;
		// DestroyWindow(m_data->m_hWnd);
		DestroyBackBuffer(m_data->m_backBuffer);
	}
}


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 = 40; // wRect.bottom - cRect.bottom + cRect.top - wRect.top;
	int windowBorderWidth = 2; // 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)
{
  m_data->m_nextTimerId++;
  //ObjcObj(classCache("NSTimer"))["scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"](
  //    a_milliSeconds / 1000.0, m_data->m_view.m_obj, selCache("processTimerEvent:"), m_data->m_nextTimerId, YES);
	return (TimerId)m_data->m_nextTimerId;
}


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::setNewSize(int a_newW, int a_newH)
{
	if ( a_newW != m_data->m_size.m_width || a_newH != m_data->m_size.m_height )
  {
    m_data->m_nativeHandle["setContentSize:"]((NSSize){double(a_newW), double(a_newH)});
  }
}


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;

	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);
    m_data->m_size.m_width = a_newW;
    m_data->m_size.m_height = a_newH;

    // Re-create the view
    m_data->m_texState = 1; // 1 = re-create

    m_data->m_view = ObjcObj("View")["initWithFrame:"]((NSRect){ 0, 0, double(a_newW), double(a_newH) });
    m_data->m_view.set("WindowPtr", this);
    m_data->m_nativeHandle["setContentView:"](m_data->m_view.m_obj);

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

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

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

    m_data->m_invalidated = false;
    repaint();
    //paintUpdate();
  }
}


extern bool s_started;


void Window::paintUpdate()
{
  printf("paint update\n");

  PixelBuffer& target = targetBuffer();
  int width = target.m_width;
  int height = target.m_height;
  uint32_t* pixels = target.m_pixels;
	Rectangle a_rectangle = { { { 0, 0 } }, { { width, height } } };
	Event ev;
	ev.m_type = Event::ET_PaintEvent;
	ev.m_paintEvent.m_rectangle = a_rectangle;
	event(ev);

  if (!s_started)
    return;

#if 1 // USE_OPENGL
  ObjcObj ctx = m_data->m_view["openGLContext"]();
  if (!ctx.m_obj)
    return;
  ctx["makeCurrentContext"]();

# if USE_GL_DRAW_PIXELS

  // One way to do it, less lines of code, but less efficient
  glClearColor(0, 0, 0, 0);
  glClear(GL_COLOR_BUFFER_BIT);

  // Flip upside-down - TODO: perhaps can instead change the coordinate system so bottom-left is 0,0
  uint32_t* tmpBuf = new uint32_t[width*height];
  for (int j = 0; j < height; j++)
    for (int i = 0; i < width; i++)
      tmpBuf[j*width + i] = pixels[(height-1-j) * width + i];
  glDrawPixels(width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, tmpBuf);
  delete[] tmpBuf;

# else

  // Texture way to do it. Probably more efficient
  if (m_data->m_texState != 2)
  {
    glEnable(GL_TEXTURE_2D);
    if (m_data->m_texState == 1)
    {
      glDeleteTextures(1, &m_data->m_texId);
    }
    glGenTextures(1, &m_data->m_texId);
    glBindTexture(GL_TEXTURE_2D, m_data->m_texId);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    m_data->m_texState = 2;
  }

  glBindTexture(GL_TEXTURE_2D, m_data->m_texId);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, 
             GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
  glActiveTexture(GL_TEXTURE0);

  glBegin(GL_TRIANGLES);
    glTexCoord2f( 0.0f,  2.0f );
    glVertex3f(  -1.0f, -3.0f,  0.0f );
    glTexCoord2f( 0.0f,  0.0f );
    glVertex3f(  -1.0f,  1.0f,  0.0f );
    glTexCoord2f( 2.0f,  0.0f  );
    glVertex3f(   3.0f,  1.0f,  0.0f );
  glEnd();

# endif

  glFlush();
  ctx["flushBuffer"]();
#else
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  CGContextRef bitmapContext = CGBitmapContextCreate(pixels, width, height, 8, sizeof(uint32_t)*width, colorSpace, kCGImageAlphaNoneSkipLast);
  CFRelease(colorSpace);
  CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
  // graphicsPort is deprecated and since 10.10, docs say the new API is CGContext
  CGContextRef ctx = (CGContextRef)ObjcObj(classCache("NSGraphicsContext"))["currentContext"]()["graphicsPort"]().m_obj;
  CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), imageRef);
  CGImageRelease(imageRef);
#endif

  m_data->m_invalidated = false;
}


void Window::update(Rectangle& a_rectangle)
{
  if (!m_data->m_invalidated)
  {
    printf("invalidating\n");
    m_data->m_view["setNeedsDisplay:"](
                 (NSRect){ double(a_rectangle.m_x), double(a_rectangle.m_y),
                 double(a_rectangle.m_width), double(a_rectangle.m_height) });
    m_data->m_invalidated = true;
  }
}


PixelBuffer& Window::targetBuffer()
{
  return m_data->m_backBuffer;
}


END_NAMESPACE


#endif