// C/C++ API for Mac OS X Windowing - windowing.cpp
// Created by John Ryland (jryland@xiaofrog.com), 29/10/2017
// Copyright (c) 2017 InvertedLogic
// All rights reserved.
#include <cstdlib>
#include <cstdio>
#define OBJC_OLD_DISPATCH_PROTOTYPES 1
#include <objc/message.h>
#include <objc/runtime.h>
#include <ApplicationServices/ApplicationServices.h>
#include <OpenGL/gl.h>
#include "windowing.h"
#include "common.h"
struct NSEvent;
struct NSPoint { double x, y; };
struct NSSize { double width, height; };
struct NSRect { double x, y, width, height; };
BOOL AppDidFinishLaunching(id self, SEL _cmd, id notification);
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 WindowOnTimerEvent(id self, SEL _cmd, void* param);
Application::Application()
{
Class AppDelClass;
AppDelClass = objc_allocateClassPair((Class)objc_getClass("NSObject"), "AppDelegate", 0);
class_addMethod(AppDelClass, sel_getUid("applicationDidFinishLaunching:"), (IMP)AppDidFinishLaunching, "i@:@");
class_addIvar(AppDelClass, "ApplicationPtr", sizeof(Application*), sizeof(Application*), "Application*");
objc_registerClassPair(AppDelClass);
Class ViewClass;
#if USE_OPENGL
ViewClass = objc_allocateClassPair((Class)objc_getClass("NSOpenGLView"), "View", 0);
#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("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("processTimerEvent:"), (IMP)WindowOnTimerEvent, "v@:");
class_addIvar(ViewClass, "WindowPtr", sizeof(Window*), sizeof(Window*), "Window*");
objc_registerClassPair(ViewClass);
objc_msgSend((id)objc_getClass("NSApplication"), sel_getUid("sharedApplication"));
}
int Application::run()
{
extern id NSApp;
id pool = objc_msgSend((id)objc_getClass("NSAutoreleasePool"), sel_getUid("alloc"));
pool = objc_msgSend(pool, sel_getUid("init"));
id appDelObj = objc_msgSend((id)objc_getClass("AppDelegate"), sel_getUid("alloc"));
appDelObj = objc_msgSend(appDelObj, sel_getUid("init"));
object_setInstanceVariable(appDelObj, "ApplicationPtr", (void*)this);
objc_msgSend(NSApp, sel_getUid("setDelegate:"), appDelObj);
objc_msgSend(NSApp, sel_getUid("run"));
objc_msgSend(pool, sel_getUid("release"));
return EXIT_SUCCESS;
}
void Window::create(int w, int h)
{
// NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask | NSWindowStyleMaskFullSizeContentView
unsigned windowFlags = (1<<0) | (1<<1) | (1<<2) | (1<<3) | (1<<15);
Window::onResize(w, h);
window = objc_msgSend((id)objc_getClass("NSWindow"), sel_getUid("alloc"));
window = objc_msgSend((id)window, sel_getUid("initWithContentRect:styleMask:backing:defer:"), (NSRect){0,0,w,h}, windowFlags, 2, false);
view = objc_msgSend(objc_msgSend((id)objc_getClass("View"), sel_getUid("alloc")), sel_getUid("initWithFrame:"), (NSRect){0,0,w,h});
object_setInstanceVariable((id)view, "WindowPtr", (void*)this);
objc_msgSend((id)window, sel_getUid("setContentView:"), view);
objc_msgSend((id)window, sel_getUid("becomeFirstResponder"));
objc_msgSend((id)window, sel_getUid("makeKeyAndOrderFront:"), window);
// Enable retina
#if USE_RETINA
objc_msgSend((id)view, sel_getUid("setWantsBestResolutionOpenGLSurface:"), YES);
#endif
// Using timer events to drive the refresh
objc_msgSend((id)objc_getClass("NSTimer"), sel_getUid("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"),
0.020, view, sel_getUid("processTimerEvent:"), nil, YES);
}
Window::~Window()
{
}
void Window::flush()
{
#if USE_OPENGL
id ctx = objc_msgSend((id)view, sel_getUid("openGLContext"));
objc_msgSend(ctx, sel_getUid("makeCurrentContext"));
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawPixels(buffer.w, buffer.h, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer.pixels.data());
glFlush();
objc_msgSend(ctx, sel_getUid("flushBuffer"));
#else
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef bitmapContext = CGBitmapContextCreate(buffer.pixels.data(), buffer.w, buffer.h, 8, 4*buffer.w, colorSpace, kCGImageAlphaNoneSkipLast);
CFRelease(colorSpace);
CGImageRef imageRef = CGBitmapContextCreateImage(bitmapContext);
id gfxContext = objc_msgSend((id)objc_getClass("NSGraphicsContext"), sel_getUid("currentContext"));
// graphicsPort is deprecated and since 10.10, docs say the new API is CGContext
CGContextRef ctx = (CGContextRef)objc_msgSend(gfxContext, sel_getUid("graphicsPort"));
CGContextDrawImage(ctx, CGRectMake(0, 0, buffer.w, buffer.h), imageRef);
CGImageRelease(imageRef);
#endif
}
void Window::onResize(int w, int h)
{
// Enable retina
#if USE_RETINA
w *= 2;
h *= 2;
#endif
buffer.pixels.resize(w*h);
buffer.w = w;
buffer.h = h;
}
void Window::refresh()
{
objc_msgSend((id)view, sel_getUid("setNeedsDisplay:"), YES);
//objc_msgSend((id)view, sel_getUid("setNeedsDisplay:"));
}
/*
static void *refreshThread(void *arg)
{
CanvasWindow* win = (CanvasWindow*)arg;
win->drawn = false;
while (true) {
sleepMilli(win->delay);
if (win->drawn) {
win->refresh();
}
}
}
*/
CanvasWindow::CanvasWindow()
: Window(), delay(33) // 33ms -> 30 fps
{
// Using a thread doesn't seem to work, the objective-C calls need to be
// dispatched to the main thread to cause a redraw. Using timer events instead.
// startThread(refreshThread, this);
}
void CanvasWindow::onDraw(int x1, int y1, int x2, int y2)
{
drawn = true;
drawCanvas(buffer, x1, y1, canvas);
}
void CanvasWindow::setFrameRate(int a_fps)
{
delay = 1000 / a_fps;
}
BOOL AppDidFinishLaunching(id self, SEL _cmd, id notification)
{
Application* appPtr = 0;
object_getInstanceVariable(self, "ApplicationPtr", (void**)&appPtr);
//printf("AppDidFinishLaunching %i\n", __LINE__);
appPtr->onStart();
//printf("AppDidFinishLaunching %i\n", __LINE__);
return YES;
}
void WindowOnDraw(id self, SEL _cmd, NSRect rect)
{
Window* winPtr = 0;
object_getInstanceVariable(self, "WindowPtr", (void**)&winPtr);
winPtr->onDraw(int(rect.x), int(rect.y), int(rect.x + rect.width), int(rect.y + rect.height));
winPtr->flush();
}
void WindowOnResize(id self, SEL _cmd, NSSize newSize)
{
Window* winPtr = 0;
object_getInstanceVariable(self, "WindowPtr", (void**)&winPtr);
winPtr->onResize(int(newSize.width), int(newSize.height));
winPtr->onDraw(0, 0, winPtr->getWidth(), winPtr->getHeight());
}
void WindowOnMouseEvent(id self, SEL _cmd, NSEvent* event)
{
Window* winPtr = 0;
object_getInstanceVariable(self, "WindowPtr", (void**)&winPtr);
NSPoint pnt = (*(NSPoint(*)(id, SEL, ...))&objc_msgSend)((id)event, sel_getUid("locationInWindow"));
unsigned buttons = (*(unsigned(*)(id, SEL, ...))&objc_msgSend)((id)objc_getClass("NSEvent"), sel_getUid("pressedMouseButtons"));
#if USE_RETINA
int x = int(pnt.x);
int y = (winPtr->getHeight()/2)-int(pnt.y);
//x *= 2;
//y *= 2;
#else
int x = int(pnt.x);
int y = winPtr->getHeight()-int(pnt.y);
#endif
winPtr->onMouseEvent(x, y, buttons);
}
void WindowOnTimerEvent(id self, SEL _cmd, void* param)
{
Window* winPtr = 0;
object_getInstanceVariable(self, "WindowPtr", (void**)&winPtr);
winPtr->onTimerEvent();
winPtr->onDraw(0, 0, winPtr->getWidth(), winPtr->getHeight());
winPtr->flush();
}