//
// Window.mm
// Platform
//
// Created by John Ryland on 5/10/17.
// Copyright © 2017 John Ryland. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>
#import <Cocoa/Cocoa.h>
#include <OpenGL/gl.h>
#include <mutex>
#include "Window.h"
void* CreateWindow()
{
NSRect frame = NSMakeRect(100, 200, 640, 480);
NSWindow* window = [[NSWindow alloc] initWithContentRect:frame
styleMask: NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable
backing:NSBackingStoreBuffered
defer:NO];
/*
NSRect rect = [window contentRectForFrameRect:[window frame]];
NSView* view = [[NSView alloc] initWithFrame:rect];
[window setOpaque:YES];
[window setContentView:view];
*/
return (void*)CFBridgingRetain(window);
}
void DestroyWindow(void* a_platformHandle)
{
CFBridgingRelease(a_platformHandle);
}
void SetWindowStyle(void* a_platformHandle, int a_styleFlags)
{
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
window.styleMask = a_styleFlags;
}
void SetWindowGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
[window setFrame:NSMakeRect(a_x, a_y, a_width, a_height) display: window.visible];
}
void SetWindowTitle(void* a_platformHandle, const char* a_title)
{
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
window.title = [NSString stringWithUTF8String:a_title];
}
void ShowWindow(void* a_platformHandle)
{
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
[window makeKeyAndOrderFront:NSApp];
}
@interface MouseResponder : NSResponder
@property NSView* m_view;
@property MouseEvent m_mouseState;
@property std::function<void(const MouseEvent&)> m_mouseEventCallback;
@end
@implementation MouseResponder
- (id)initWithView:(NSView*)view
{
self = [super init];
self.m_mouseEventCallback = [](const MouseEvent&){};
self.m_mouseState = MouseEvent{ 0.0f, 0.0f, 0 };
self.m_view = view;
return self;
}
- (void)sendMouseEvent:(NSEvent*)event
{
MouseEvent state = self.m_mouseState;
if (event.type == NSEventTypeLeftMouseUp)
state.m_buttons &= ~LeftButton;
if (event.type == NSEventTypeOtherMouseUp)
state.m_buttons &= ~MiddleButton;
if (event.type == NSEventTypeRightMouseUp)
state.m_buttons &= ~RightButton;
if (event.type == NSEventTypeLeftMouseDown)
state.m_buttons |= LeftButton;
if (event.type == NSEventTypeOtherMouseDown)
state.m_buttons |= MiddleButton;
if (event.type == NSEventTypeRightMouseDown)
state.m_buttons |= RightButton;
NSPoint locationInView = [self.m_view convertPoint:[event locationInWindow] fromView:nil];
state.m_x = (locationInView.x - self.m_view.frame.origin.x) / self.m_view.frame.size.width;
state.m_y = (locationInView.y - self.m_view.frame.origin.y) / self.m_view.frame.size.height;
self.m_mouseEventCallback(state);
self.m_mouseState = state;
}
- (void)mouseMoved:(NSEvent *)event
{
[self sendMouseEvent:event];
}
- (void)mouseUp:(NSEvent *)event
{
[self sendMouseEvent:event];
}
- (void)rightMouseUp:(NSEvent *)event
{
[self sendMouseEvent:event];
}
- (void)otherMouseUp:(NSEvent *)event
{
[self sendMouseEvent:event];
}
- (void)mouseDown:(NSEvent *)event
{
[self sendMouseEvent:event];
}
- (void)rightMouseDown:(NSEvent *)event
{
[self sendMouseEvent:event];
}
- (void)otherMouseDown:(NSEvent *)event
{
[self sendMouseEvent:event];
}
@end
@interface CustomView : NSBox
@property MouseResponder* m_mouseHandler;
@end
@implementation CustomView
@end
void* CreateView(void* a_platformHandle)
{
CustomView* view = [[CustomView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
[window.contentView addSubview:view];
view.fillColor = [NSColor colorWithRed:0.9 green:0.0 blue:0.0 alpha:1.0];
view.borderType = NSNoBorder;
view.boxType = NSBoxCustom;
view.m_mouseHandler = [[MouseResponder alloc] initWithView:view];
view.nextResponder = view.m_mouseHandler;
return (void*)CFBridgingRetain(view);
}
void DestroyView(void* a_platformHandle)
{
CFBridgingRelease(a_platformHandle);
}
void SetViewGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
CustomView* view = (__bridge CustomView*)a_platformHandle;
[view setFrame:NSMakeRect(a_x, a_y, a_width, a_height)];
}
void SetViewColor(void* a_platformHandle, float a_red, float a_green, float a_blue, float a_alpha)
{
CustomView* view = (__bridge CustomView*)a_platformHandle;
view.fillColor = [NSColor colorWithRed:a_red green:a_green blue:a_blue alpha:a_alpha];
}
void SetViewOnMouseEvent(void* a_platformHandle, std::function<void(const MouseEvent&)> a_callback)
{
CustomView* view = (__bridge CustomView*)a_platformHandle;
view.m_mouseHandler.m_mouseEventCallback = a_callback;
}
@interface CustomGLView : NSOpenGLView
{
CVDisplayLinkRef m_displayLink; // display link for managing rendering thread
std::mutex m_mutex;
float m_w, m_h;
}
@property MouseResponder* m_mouseHandler;
@property std::function<void()> m_prepareCallback;
@property std::function<void()> m_updateCallback;
@property std::function<void()> m_drawCallback;
@property std::function<void()> m_closeCallback;
@property std::function<void(float a_width, float a_height)> m_resizeCallback;
@end
@implementation CustomGLView
- (id)initWithFrame:(NSRect)rect
{
NSOpenGLPixelFormatAttribute attrs[] = {
NSOpenGLPFAAccelerated,
NSOpenGLPFADoubleBuffer,
NSOpenGLPFADepthSize, 24,
NSOpenGLPFAAllowOfflineRenderers, // lets OpenGL know this context is offline renderer aware
NSOpenGLPFAMultisample, 1,
NSOpenGLPFASampleBuffers, 1,
NSOpenGLPFASamples, 4,
NSOpenGLPFAColorSize, 32,
0
};
NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
self = [super initWithFrame:rect pixelFormat:pixelFormat];
self.m_prepareCallback = [](){};
self.m_updateCallback = [](){};
self.m_drawCallback = [](){};
self.m_closeCallback = [](){};
self.m_resizeCallback = [](float a_width, float a_height){};
return self;
}
- (void)prepareOpenGL
{
[super prepareOpenGL];
[self.openGLContext makeCurrentContext];
GLint swapInterval = 1;
[[self openGLContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval];
m_mutex.lock();
CGLLockContext(self.openGLContext.CGLContextObj);
m_w = float(self.frame.size.width);
m_h = float(self.frame.size.height);
self.m_resizeCallback(float(self.frame.size.width), float(self.frame.size.height));
self.m_prepareCallback();
CVDisplayLinkCreateWithActiveCGDisplays(&m_displayLink);
CVDisplayLinkSetOutputCallback(m_displayLink, &displayLinkCallback, (__bridge void *)self);
CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(m_displayLink, self.openGLContext.CGLContextObj, self.pixelFormat.CGLPixelFormatObj);
CGLUnlockContext(self.openGLContext.CGLContextObj);
CVDisplayLinkStart(m_displayLink);
m_mutex.unlock();
}
// This is the renderer output callback function
static CVReturn displayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime,
CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext)
{
CustomGLView *view = (__bridge CustomGLView*)displayLinkContext;
[view drawRect:NSMakeRect(0, 0, view->m_w, view->m_h)];
return kCVReturnSuccess;
}
- (void)dealloc
{
// TODO: figure out why this doesn't appear to be getting called
m_mutex.lock();
CVDisplayLinkRelease(m_displayLink);
self.m_closeCallback();
m_mutex.unlock();
}
- (void)reshape
{
m_w = float(self.frame.size.width);
m_h = float(self.frame.size.height);
self.m_resizeCallback(float(self.frame.size.width), float(self.frame.size.height));
}
- (void) drawRect: (NSRect) theRect
{
m_mutex.lock();
[[self openGLContext] makeCurrentContext];
if (self.m_updateCallback)
self.m_updateCallback();
CGLLockContext([[self openGLContext] CGLContextObj]);
glViewport(0,0,m_w,m_h);
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
if (self.m_drawCallback)
self.m_drawCallback(); // Draw the scene. This doesn't need to be in the drawRect method.
glFlush();
[[self openGLContext] flushBuffer];
CGLFlushDrawable([[self openGLContext] CGLContextObj]);
CGLUnlockContext([[self openGLContext] CGLContextObj]);
m_mutex.unlock();
}
@end
void* CreateGLView(void* a_platformHandle)
{
CustomGLView* view = [[CustomGLView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
[window.contentView addSubview:view];
view.m_mouseHandler = [[MouseResponder alloc] initWithView:view];
view.nextResponder = view.m_mouseHandler;
return (void*)CFBridgingRetain(view);
}
void DestroyGLView(void* a_platformHandle)
{
CFBridgingRelease(a_platformHandle);
}
void SetGLViewGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
[view setFrame:NSMakeRect(a_x, a_y, a_width, a_height)];
}
void SetGLViewOnPrepare(void* a_platformHandle, std::function<void()> a_callback)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
view.m_prepareCallback = a_callback;
}
void SetGLViewOnUpdate(void* a_platformHandle, std::function<void()> a_callback)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
view.m_updateCallback = a_callback;
}
void SetGLViewOnDraw(void* a_platformHandle, std::function<void()> a_callback)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
view.m_drawCallback = a_callback;
}
void SetGLViewOnClose(void* a_platformHandle, std::function<void()> a_callback)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
view.m_closeCallback = a_callback;
}
void SetGLViewOnResize(void* a_platformHandle, std::function<void(float a_width, float a_height)> a_callback)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
view.m_resizeCallback = a_callback;
}
void SetGLViewOnMouseEvent(void* a_platformHandle, std::function<void(const MouseEvent&)> a_callback)
{
CustomGLView* view = (__bridge CustomGLView*)a_platformHandle;
view.m_mouseHandler.m_mouseEventCallback = a_callback;
}
@interface CustomLabel : NSTextField
@property MouseResponder* m_mouseHandler;
@end
@implementation CustomLabel
@end
void* CreateWindowLabel(void* a_platformHandle)
{
CustomLabel* text = [[CustomLabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 50.0)];
NSWindow* window = (__bridge NSWindow*)a_platformHandle;
[window.contentView addSubview:text];
text.bordered = false;
text.editable = false;
//text.autoresizingMask = NSViewWidthSizable;
//text.lineBreakMode = NSLineBreakByClipping;
//text.cell.scrollable = true;
text.alignment = NSTextAlignmentCenter;
text.m_mouseHandler = [[MouseResponder alloc] initWithView:text];
text.nextResponder = text.m_mouseHandler;
return (void*)CFBridgingRetain(text);
}
void* CreateViewLabel(void* a_platformHandle)
{
CustomLabel* text = [[CustomLabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 50.0)];
NSBox* view = (__bridge NSBox*)a_platformHandle;
[view addSubview:text];
text.bordered = false;
text.editable = false;
text.alignment = NSTextAlignmentCenter;
text.m_mouseHandler = [[MouseResponder alloc] initWithView:text];
text.nextResponder = text.m_mouseHandler;
return (void*)CFBridgingRetain(text);
}
void DestroyLabel(void* a_platformHandle)
{
CFBridgingRelease(a_platformHandle);
}
void SetLabelGeometry(void* a_platformHandle, int a_x, int a_y, int a_width, int a_height)
{
NSTextField* text = (__bridge NSTextField*)a_platformHandle;
[text setFrame:NSMakeRect(a_x, a_y, a_width, a_height)];
}
void SetLabelColor(void* a_platformHandle, float a_red, float a_green, float a_blue, float a_alpha)
{
NSTextField* text = (__bridge NSTextField*)a_platformHandle;
text.backgroundColor = [NSColor colorWithRed:a_red green:a_green blue:a_blue alpha:a_alpha];
}
void SetLabelText(void* a_platformHandle, const char* a_text)
{
NSTextField* text = (__bridge NSTextField*)a_platformHandle;
text.cell.title = [NSString stringWithUTF8String:a_text];
}
void SetLabelFontSize(void* a_platformHandle, int a_size)
{
NSTextField* text = (__bridge NSTextField*)a_platformHandle;
text.font = [NSFont fontWithName: text.font.fontName size: a_size];
}
void SetLabelOnMouseEvent(void* a_platformHandle, std::function<void(const MouseEvent&)> a_callback)
{
CustomLabel* text = (__bridge CustomLabel*)a_platformHandle;
text.m_mouseHandler.m_mouseEventCallback = a_callback;
}