Newer
Older
Import / research / ui / toolkit / src / 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)

  printf("destorying back buffer\n");
	DestroyBackBuffer(&m_data->m_backBuffer);
  printf("creating new back buffer\n");
	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;
	}
  printf("created new back buffer\n");

	//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


#define GL_SILENCE_DEPRECATION
#include "ObjectiveC.h"
#include <ApplicationServices/ApplicationServices.h>
#include <CoreVideo/CoreVideo.h>
#include <OpenGL/gl.h>
#include <pthread.h>
#include <unistd.h>


#ifndef _NDEBUG
//#define DEBUG_LOG         printf
#define DEBUG_LOG(...)
#else
#define DEBUG_LOG(...)
#endif


extern id NSApp;


BEGIN_NAMESPACE


struct WindowData
{
  ObjectiveC::Object m_nativeHandle      = ObjectiveC::Object{(id)nullptr};
  ObjectiveC::Object m_view              = ObjectiveC::Object{(id)nullptr};
	Point m_mousePos                       = (Point){ 0, 0 };
	MouseButtons m_mouseButtons            = MB_None;
	Size m_size                            = (Size){ 1, 1 };
	bool m_active                          = true;
	TimerId 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;
  CVDisplayLinkRef m_displayLink; // display link for managing rendering thread
  std::map<TimerId,id> m_activeTimers;
};


struct NSPoint { double x, y; };
struct NSSize { double width, height; };
struct NSRect { double x, y, width, height; };
struct NSEvent;
typedef uint32_t unichar;
/*
void WindowOnDraw(id self, SEL _cmd, NSRect rect);
void WindowOnResize(id self, SEL _cmd, NSSize newSize);
void WindowOnMouseEvent(id self, SEL _cmd, NSEvent* event);
void ProcessTimerEvent(id self, SEL _cmd, void* timerEvent);
*/

void PrintClassOfId(id self)
{
  printf("class: %s\n", class_getName(object_getClass(self)));
}

void WindowOnReshape(id self, SEL _cmd)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  Window* winPtr = ObjectiveC::Object(self).get<Window*>("WindowPtr");
  if (winPtr)
    winPtr->reshape();
}

bool WindowAcceptsKeyInput(id self, SEL _cmd)
{
  DEBUG_LOG("WindowAcceptsKeyInput called\n");
  return true;
}

void WindowOnKeyInput(id self, SEL _cmd)
{
  DEBUG_LOG("WindowOnKeyInput called\n");
}

void WindowOnDraw(id self, SEL _cmd, NSRect rect)
{
  DEBUG_LOG("WindowOnDraw called\n");
  ObjectiveC::AutoReleasePool autoReleasePool;
  Window* winPtr = ObjectiveC::Object(self).get<Window*>("WindowPtr");
  if (winPtr)
    winPtr->paintUpdate();
}

void WindowOnResize(id self, SEL _cmd, NSSize newSize)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  Window* winPtr = ObjectiveC::Object(self).get<Window*>("WindowPtr");
  if (winPtr)
    winPtr->resize(int(newSize.width), int(newSize.height));
}

void WindowOnKeyEvent(id self, SEL _cmd, NSEvent* event)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  Window* winPtr = ObjectiveC::Object(self).get<Window*>("WindowPtr");

  unichar ch = ObjectiveC::Object((id)event)["charactersIgnoringModifiers"]()["characterAtIndex:"].call<unichar>(0);
  printf("got char: %i\n", ch); 
  if (winPtr)
  {
    winPtr->keyUpdate(ch, 1);//  keyUpdate(int(pnt.x), h-int(pnt.y), 0, MB_Left, (buttons & 1) ? MS_Down : MS_Up);
    winPtr->keyUpdate(ch, 0);//  keyUpdate(int(pnt.x), h-int(pnt.y), 0, MB_Left, (buttons & 1) ? MS_Down : MS_Up);
  }
}

void WindowOnMouseEvent(id self, SEL _cmd, NSEvent* event)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  Window* winPtr = ObjectiveC::Object(self).get<Window*>("WindowPtr");
  NSPoint pnt = ObjectiveC::Object((id)event)["locationInWindow"].call<NSPoint>();
  static unsigned lastButtons = 0;
  unsigned buttons = ObjectiveC::Object(ObjectiveC::classCache("NSEvent"))["pressedMouseButtons"].call<unsigned>();

  if (winPtr)
  {
    int h = winPtr->targetBuffer().m_height;
#if USE_RETINA
    if (c_useRetina)
    {
      h /= 2;//c_retinaScale;
      //h >>= 1;
    }
#endif
    if ((buttons & 1) || (lastButtons & 1))
    {
      winPtr->mouseUpdate(int(pnt.x), h-int(pnt.y), 0, MB_Left, (buttons & 1) ? MS_Down : MS_Up);
    }
    if ((buttons & 2) || (lastButtons & 2))
    {
      winPtr->mouseUpdate(int(pnt.x), h-int(pnt.y), 0, MB_Right, (buttons & 2) ? MS_Down : MS_Up);
    }
    if ( buttons == 0 )
    {
      winPtr->mouseUpdate(int(pnt.x), h-int(pnt.y), 0, MB_Left, MS_Up);
    }
  }

  lastButtons = buttons;
}

extern bool s_started;

CVReturn OnCanRepaint(CVDisplayLinkRef displayLink,
    const CVTimeStamp *inNow, const CVTimeStamp *inOutputTime,
    CVOptionFlags flagsIn, CVOptionFlags *flagsOut, void *callbackData)
{
  //if (!s_started)
  //  return 0;

  DEBUG_LOG("OnCanRepaint called\n");
  
  Window* winPtr = (Window*)callbackData;
  winPtr->repaint();
  
  //ObjectiveC::Object* winPtr = (ObjectiveC::Object*)callbackData;
  //(*winPtr)["drawRect:"]();
  
  return 0;
}


void UpdateMouseTrackingArea(id self, SEL _cmd)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  ObjectiveC::Object obj(self);
  Window* winPtr = obj.get<Window*>("WindowPtr");
  NSRect bounds{ 0, 0, (double)winPtr->width(), (double)winPtr->height() };
  ObjectiveC::Object area = ObjectiveC::Object("NSTrackingArea")["alloc"]();
  area["initWithRect:options:owner:userInfo:"](bounds, 0x82, self, nil);
  obj["addTrackingArea:"](area.m_object);
}


void ProcessTimerEvent(id self, SEL _cmd, id timer)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  Window* winPtr = ObjectiveC::Object(self).get<Window*>("WindowPtr");
  TimerId timerId = ObjectiveC::Object(timer)["userInfo"]().get<TimerId>("TimerId");
  if (winPtr)
  {
    winPtr->timerUpdate(timerId);
  }
}


// TODO: Need a nice way to wrap this
void RegisterWindowClass()
{
  Class WindowClass;
  WindowClass = objc_allocateClassPair((Class)objc_getClass("NSWindow"), "Window", 0);
  class_addMethod(WindowClass, sel_getUid("canBecomeKeyWindow"), (IMP)WindowAcceptsKeyInput, "v@:");
  class_addMethod(WindowClass, sel_getUid("canBecomeMainWindow"), (IMP)WindowAcceptsKeyInput, "v@:");
  class_addMethod(WindowClass, sel_getUid("keyDown:"), (IMP)WindowOnKeyEvent, "v@:");
  class_addIvar(WindowClass, "WindowPtr", sizeof(Window*), sizeof(Window*), "Window*");
  objc_registerClassPair(WindowClass);

  Class PanelClass;
  PanelClass = objc_allocateClassPair((Class)objc_getClass("NSPanel"), "Panel", 0);
  class_addMethod(PanelClass, sel_getUid("canBecomeKeyWindow"), (IMP)WindowAcceptsKeyInput, "v@:");
  class_addMethod(PanelClass, sel_getUid("keyDown:"), (IMP)WindowOnKeyEvent, "v@:");
  class_addIvar(PanelClass, "WindowPtr", sizeof(Window*), sizeof(Window*), "Window*");
  objc_registerClassPair(PanelClass);

  Class TimerClass;
  TimerClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "Timer", 0);
  class_addIvar(TimerClass, "TimerId", sizeof(TimerId), sizeof(TimerId), "TimerId");
  objc_registerClassPair(TimerClass);

  Class ViewClass;
#if 1 // USE_OPENGL
  ViewClass = objc_allocateClassPair((Class)objc_getClass("NSOpenGLView"), "View", 0);
  class_addMethod(ViewClass, sel_getUid("reshape:"), (IMP)WindowOnReshape, "v@:");
#else
  ViewClass = objc_allocateClassPair((Class)objc_getClass("NSView"), "View", 0);
#endif
  class_addMethod(ViewClass, sel_getUid("drawRect:"), (IMP)WindowOnDraw, "v@:");
  class_addMethod(ViewClass, sel_getUid("setFrameSize:"), (IMP)WindowOnResize, "v@:");
  class_addMethod(ViewClass, sel_getUid("keyDown:"), (IMP)WindowOnKeyEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("mouseDown:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("mouseDragged:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("mouseUp:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("mouseMoved:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("rightMouseDown:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("rightMouseDragged:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("rightMouseUp:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("rightMouseMoved:"), (IMP)WindowOnMouseEvent, "v@:");
  class_addMethod(ViewClass, sel_getUid("updateTrackingAreas"), (IMP)UpdateMouseTrackingArea, "v@:");
  class_addMethod(ViewClass, sel_getUid("processTimerEvent:"), (IMP)ProcessTimerEvent, "v@:");
  class_addIvar(ViewClass, "WindowPtr", sizeof(Window*), sizeof(Window*), "Window*");
  objc_registerClassPair(ViewClass);
}


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_isRetina = false;
#if USE_RETINA
  if (c_useRetina)
  {
    int w = a_w * 2;//c_retinaScale;
    int h = a_h * 2;//c_retinaScale;
    a_backBuffer.m_pixels = new uint32_t[w * h];
    a_backBuffer.m_strideBytes = w * sizeof(uint32_t);
    a_backBuffer.m_width  = w;
    a_backBuffer.m_height = h;
    a_backBuffer.m_isRetina = true;
  }
  else
  {
    a_backBuffer.m_pixels = new uint32_t[a_w * a_h];
  }
#else
  a_backBuffer.m_pixels = new uint32_t[a_w * a_h];
#endif
}


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


/*
namespace ObjC
{

// Wrapper idea - could do this for main ObjC classes
class NSWindow
{
public:
  NSWindow(double w, double h, unsigned windowFlags)
  {
    obj = ObjectiveC::Object("NSWindow")["alloc"]()["initWithContentRect:styleMask:backing:defer:"]((NSRect){ 0, 0, w, h }, windowFlags, 2, false);
  }
  inline void setContentView(id view) { obj["setContentView:"](view); }
  inline void becomeFirstResponder()  { obj["becomeFirstResponder"](); }
  inline void makeKeyAndOrderFront()  { obj["makeKeyAndOrderFront:"](obj.m_object); }
private:
  ObjectiveC::Object obj;
};

} // namespace ObjC
*/


ObjectiveC::Object createView(Window* winPtr, double w, double h, ObjectiveC::Object window)
{
  typedef uint32_t NSOpenGLPixelFormatAttribute;

  const NSOpenGLPixelFormatAttribute NSOpenGLPFADoubleBuffer = 5;
  const NSOpenGLPixelFormatAttribute NSOpenGLPFAColorSize = 8;
  const NSOpenGLPixelFormatAttribute NSOpenGLPFAAlphaSize = 11;
  const NSOpenGLPixelFormatAttribute NSOpenGLPFADepthSize = 12;
  const NSOpenGLPixelFormatAttribute NSOpenGLPFAStencilSize = 13;
  const NSOpenGLPixelFormatAttribute NSOpenGLPFAWindow = 80;

  NSOpenGLPixelFormatAttribute pixelFormatAttrs[] =
  {
    NSOpenGLPFAWindow,
    NSOpenGLPFADoubleBuffer,
    NSOpenGLPFAColorSize, 24,
    NSOpenGLPFAAlphaSize, 8,
    NSOpenGLPFADepthSize, 24,
    NSOpenGLPFAStencilSize, 8,
    0
  };

  ObjectiveC::Object format = ObjectiveC::Object("NSOpenGLPixelFormat")["alloc"]()["initWithAttributes:"](pixelFormatAttrs); 
  ObjectiveC::Object view = ObjectiveC::Object("View")["alloc"]()["initWithFrame:pixelFormat:"]((NSRect){ 0, 0, w, h }, format.m_object);
  view.set("WindowPtr", winPtr);
  window["setContentView:"](view.m_object);

#if USE_RETINA
  if (c_useRetina)
  {
    // Make use of retina display higher resolution
    //  - TODO: need to make some major code changes to take advantage of this
    //          - need to work out the scale factor if this is supported
    //          - then need to make a buffer with the larger size and adjust drawing to scale to this
    //          - may need to change APIs to make things more screen/device
    //             independent - eg not based on pixel positions/sizes but on physical sizes / DPI
    view["setWantsBestResolutionOpenGLSurface:"].call<void>(YES);
  }
  else
  {
    view["setWantsBestResolutionOpenGLSurface:"].call<void>(NO);
  }
#endif

  return view;
}


Window::Window(const char* a_title, bool a_fullscreen, WindowFlags a_flags)
  : Widget(a_title, true)
{
  ObjectiveC::AutoReleasePool autoReleasePool;

  // 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);

  ObjectiveC::Object win((id)0);
  
  // NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSWindowStyleMaskFullSizeContentView
  unsigned windowFlags = (1<<0) | (1<<1) | (1<<2) | (1<<3);// | (1<<15);

  if (a_flags == WF_NoTitle)
  {
    windowFlags = (1<<0) | (1<<4) | (1<<15);
    win = ObjectiveC::Object("Panel")["alloc"]()["initWithContentRect:styleMask:backing:defer:"]((NSRect){ 0, -h, w, h }, windowFlags, 2, false);
    win["setTitlebarAppearsTransparent:"](YES);
  }
  else
  {
    win = ObjectiveC::Object("Window")["alloc"]()["initWithContentRect:styleMask:backing:defer:"]((NSRect){ 0, -h, w, h }, windowFlags, 2, false);
    win["setTitle:"](ObjectiveC::Object("NSString")["stringWithUTF8String:"]("Hello World"));
  }

  m_data->m_nativeHandle = win;

  // Setting the window title:
  win.set("WindowPtr", this);

  m_data->m_view = createView(this, w, h, win);

  // win["becomeFirstResponder"]();
  //win["makeFirstResponder:"](m_data->m_view);
  //win["makeFirstResponder:"](win.m_object);
  win["makeKeyAndOrderFront:"](win.m_object);
  win["makeKeyWindow"]();
  //win["makeMainWindow"]();
  //m_data->m_nativeHandle["orderFrontRegardless"]();
  
  //m_data->m_nativeHandle["orderBack:"](m_data->m_nativeHandle.m_object);
  //m_data->m_nativeHandle["orderFront:"](m_data->m_nativeHandle.m_object);
  //m_data->m_nativeHandle["makeKeyWindow"]();
  //m_data->m_nativeHandle["makeKeyAndOrderFront:"](m_data->m_nativeHandle.m_object);


  // Set up a callback for when we can repaint again (as far as possible - gameloop style mode)
  CVReturn error = CVDisplayLinkCreateWithActiveCGDisplays(&m_data->m_displayLink);
  error = error || CVDisplayLinkSetOutputCallback(m_data->m_displayLink, OnCanRepaint, this);
  //error = error || CVDisplayLinkSetOutputCallback(m_data->m_displayLink, OnCanRepaint, &m_data->m_view);
  error = error || CVDisplayLinkStart(m_data->m_displayLink);
  if (error)
  {
    DEBUG_LOG("DisplayLink set up had error: %d", error);
    m_data->m_displayLink = NULL;
  }

  //ObjectiveC::Object app(NSApp);
  //app["setMainWindow:"](win);

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


Window::~Window()
{
  CVDisplayLinkStop(m_data->m_displayLink);
  CVDisplayLinkRelease(m_data->m_displayLink);
  m_data->m_displayLink = NULL;

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


void Window::deactivate()
{
	if (m_data->m_active)
	{
    m_data->m_nativeHandle["close"]();
		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++;

  ObjectiveC::Object timer = ObjectiveC::Object("Timer")["alloc"]();
  timer.set("TimerId", m_data->m_nextTimerId);

  id timerId =
      ObjectiveC::Object(ObjectiveC::classCache("NSTimer"))["scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"](
          a_milliSeconds / 1000.0, m_data->m_view.m_object, ObjectiveC::methodCache("processTimerEvent:"), timer.m_object, YES).m_object;

  m_data->m_activeTimers[m_data->m_nextTimerId] = timerId;

	return (TimerId)m_data->m_nextTimerId;
}


void Window::killTimer(TimerId a_timerId)
{
  ObjectiveC::Object timer = m_data->m_activeTimers[a_timerId];
  timer["invalidate"]();
  m_data->m_activeTimers.erase(a_timerId);
  // TODO: release?
}


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)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
	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)});
  }
}


Rectangle Window::worldGeometry()
{
  ObjectiveC::AutoReleasePool autoReleasePool;

  Class objectsClass = object_getClass(m_data->m_nativeHandle.m_object);
  objc_property_t property = class_getProperty(objectsClass, "frame");
  //DEBUG_LOG("%s %s\n", property_getName(property), property_getAttributes(property));
  printf("%s %s\n", property_getName(property), property_getAttributes(property));
/*
*/  
/*  
  //Class objectsClass = (Class)objc_getClass("NSView");
  Protocol* proto = objc_getProtocol("NSWindow");

  unsigned outCount, i;
  objc_property_t *properties = protocol_copyPropertyList(proto, &outCount);
  //objc_property_t *properties = class_copyPropertyList(objectsClass, &outCount);
  //DEBUG_LOG("cls: %s count: %i\n", class_getName(objectsClass), outCount);
  for (i = 0; i < outCount; i++) {
    objc_property_t property = properties[i];
    DEBUG_LOG("%s %s\n", property_getName(property), property_getAttributes(property));
  }
*/
  //[propertyName cStringUsingEncoding:NSASCIIStringEnCoding] );
  SEL getter;
  const char* getterName = property_copyAttributeValue(property, "G");
  //printf("getter: %s\n", getterName);

  if (getterName==NULL)
  {
    //getter = sel_getUid( "frame");
    //getter = sel_registerName("frame");
    //getter = NSSelectorFromString("frame");// propertyName );
  }
  else
  {
    getter = sel_getUid( getterName );
  }
  
  //ObjectiveC::Method md(m_data->m_nativeHandle.m_object, getter);
  //NSRect r = md.call<NSRect>();

  
  //id value = objc_msgSend(m_data->m_nativeHandle, getter);

  //ObjectiveC::Object f = ObjectiveC::Object("NSString")["stringWithUTF8String:"]("frame");
  //NSRect r = m_data->m_nativeHandle["valueForKey:"].call<NSRect>(f);
  
  //NSRect r = m_data->m_nativeHandle["valueForKey:"].call<NSRect>("frame");

  // id object = [customObject valueForKey:[NSString stringWithUTF8String:propertyName]];
  
  // NSRect r = m_data->m_nativeHandle.get<NSRect>("getFrame");
  NSSize r = m_data->m_nativeHandle.get<NSSize>("contentSize");
  //NSRect r;
  //NSRect r = m_data->m_view.get<NSRect>("getFrame");
  //if (pr)
    //r = *pr;
  //NSRect r = m_data->m_nativeHandle["frame"].call<NSRect>();

  NSPoint origin{ 0, 0 };
  NSPoint p = m_data->m_nativeHandle["convertBaseToScreen:"].call<NSPoint>( origin );

  Rectangle rect;
  rect.m_x = 10;//r.x;
  rect.m_y = 500;//r.y;
  rect.m_width = r.width;
  rect.m_height = r.height;
  rect.m_x = p.x;
  rect.m_y = p.y;
  //DEBUG_LOG("Got frame rect: %i,%i, %i x %i\n", rect.m_x, rect.m_y,
  printf("Got frame rect: %i,%i, %i x %i\n", rect.m_x, rect.m_y,
                     rect.m_width, rect.m_height);
  return rect;
}


void Window::setPosition(int a_x, int a_y)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
  m_data->m_nativeHandle["setFrameTopLeftPoint:"]((NSPoint){double(a_x), double(a_y)});
}


void Window::reshape()
{
  DEBUG_LOG("reshape called\n");
}


void Window::resize(int a_newW, int a_newH)
{
  ObjectiveC::AutoReleasePool autoReleasePool;
	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 )
  {
    //DEBUG_LOG("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 = createView(this, double(a_newW), double(a_newH), m_data->m_nativeHandle);

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

    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();
  }
}


void Window::paintUpdate()
{
  DEBUG_LOG("paintUpdate called\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 } } };

//  if (!s_started)
//    return;
  
  if (width < 3 || height < 3)
    return;

  if (!pixels)
  {
    DEBUG_LOG("paintUpdate called with null pixels\n");
    return;
  }

  Event ev;
	ev.m_type = Event::ET_PaintEvent;
	ev.m_paintEvent.m_rectangle = a_rectangle;
	event(ev);

#if 1 // USE_OPENGL
  ObjectiveC::Object ctx = m_data->m_view["openGLContext"]();
  if (!ctx.m_object)
    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

  // Can remove this if doing only opaque drawing
  glClearColor(1.0f, 1.0f, 1.0f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT);

  // Only allow GL painting on the first child of a window, usually the root widget which had a window created for it
  std::list<Widget*> child = children<Widget>();
  if (child.size() == 1)
    child.front()->glPaint();

  // Allow drawing over the top
  glEnable(GL_BLEND);
  //glBlendFunc(GL_DST_COLOR, GL_ZERO);
  //glBlendFunc(GL_SRC_ALPHA, GL_ZERO);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glEnable(GL_TEXTURE_2D);

  // Texture way to do it. Probably more efficient
  if (m_data->m_texState != 2)
  {
    if (m_data->m_texState == 1)
    {
      glDeleteTextures(1, &m_data->m_texId);
    }
    if (m_data->m_texState != 2)
    {
      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;
  }
  // else
  {
    glViewport(0, 0, width, height);
    glBindTexture(GL_TEXTURE_2D, m_data->m_texId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_BGRA, 
              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();
  }

/*
  // Only allow GL painting on the first child of a window, usually the root widget which had a window created for it
  std::list<Widget*> child = children<Widget>();
  if (child.size() == 1)
    child.front()->glPaint();
*/

# 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)ObjectiveC::Object(ObjectiveC::classCache("NSGraphicsContext"))["currentContext"]()["graphicsPort"]().m_object;
  CGContextDrawImage(ctx, CGRectMake(0, 0, width, height), imageRef);
  CGImageRelease(imageRef);
#endif

  m_data->m_invalidated = false;
}


void Window::update(Rectangle& a_rectangle)
{
  DEBUG_LOG("window update\n");
  static int retryCount = 0;
  if (!m_data->m_invalidated || retryCount > 100)
  {
    DEBUG_LOG("invalidating\n");
    ObjectiveC::AutoReleasePool autoReleasePool;
    //m_data->m_view["setNeedsDisplay"]();
    m_data->m_view["setNeedsDisplayInRect:"](
                 (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;
  }
  else
  {
    retryCount++;
  }
}


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


END_NAMESPACE


#endif