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