C++ wrapper for NSView/GLView and mac stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 
 

296 lines
8.3 KiB

//
// NSWrapper.mm
// XcodeOpenCL
//
// Created by Joshua Moerman on 13/04/14.
//
//
#include "NSWrapper.hpp"
#include "NSGLWrapper.hpp"
#include <vector>
#import <Cocoa/Cocoa.h>
#import <GLKit/GLKit.h>
GLViewFunctionality simple_draw(std::function<void()> f){
return {nullptr, [f](ContextParameters){ f(); }, nullptr};
}
@interface GLView : NSOpenGLView
// All three will only be called with the current gl context already set
// May be called from different threads (but the properties are atomic by default)
@property GLViewFunctionality functionality;
@end
@interface MySlider : NSSlider
- (instancetype) initWithFrame:(NSRect)frame;
- (void)action:(MySlider*)sender;
@property std::function<void(CGFloat)> callback;
@end
@implementation MySlider
- (instancetype) initWithFrame:(NSRect)frame{
if(self = [super initWithFrame: frame]){
self.target = self;
self.action = @selector(action:);
}
return self;
}
- (void)action:(MySlider*)sender{
if(self.callback){
self.callback(sender.doubleValue);
}
}
@end
struct NSAppWrapperImpl {
NSAppWrapperImpl() {
app = [NSApplication sharedApplication];
app.activationPolicy = NSApplicationActivationPolicyRegular;
appName = [[NSProcessInfo processInfo] processName];
}
void set_up_menu() {
NSMenu* appMenu = [NSMenu new];
[appMenu addItemWithTitle:[@"Quit " stringByAppendingString:appName]
action:@selector(terminate:)
keyEquivalent:@"q"];
NSMenuItem * menuItem = [NSMenuItem new];
menuItem.submenu = appMenu;
app.mainMenu = [NSMenu new];
[app.mainMenu addItem:menuItem];
}
void create_window(GLViewFunctionality const & f) {
NSWindow * window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 500, 500)
styleMask:NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask
backing:NSBackingStoreBuffered
defer:YES];
window.title = appName;
window.collectionBehavior |= NSWindowCollectionBehaviorFullScreenPrimary;
if(windows.empty()){
[window center];
} else {
NSWindow * previous_window = windows.back();
NSPoint top_right = previous_window.frame.origin;
top_right.x += previous_window.frame.size.width;
top_right.y += previous_window.frame.size.height;
[window setFrameTopLeftPoint: top_right];
}
[window makeKeyAndOrderFront:nil];
NSRect frame = [window contentRectForFrameRect:window.frame];
frame.origin = {0, 0};
GLView * view = [[GLView alloc] initWithFrame:frame];
view.functionality = f;
window.contentView = view;
windows.push_back(window);
}
void create_window(ControlFunctionality const & f) {
const CGFloat width = 200;
NSWindow * window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, width, 500)
styleMask:NSTitledWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask
backing:NSBackingStoreBuffered
defer:YES];
window.title = appName;
if(windows.empty()){
[window center];
} else {
NSWindow * previous_window = windows.back();
NSPoint top_right = previous_window.frame.origin;
top_right.x += previous_window.frame.size.width;
top_right.y += previous_window.frame.size.height;
[window setFrameTopLeftPoint: top_right];
}
[window makeKeyAndOrderFront:nil];
NSRect frame = [window contentRectForFrameRect:window.frame];
frame.origin = {0, 0};
NSView * view = [[NSView alloc] initWithFrame:frame];
CGFloat y = 0;
CGFloat h = 20;
CGFloat padding = 8;
for(auto&& c : f.sliders){
NSTextField * label = [[NSTextField alloc] initWithFrame:{padding, y+padding, 20, h}];
label.editable = NO;
label.bezeled = NO;
label.drawsBackground = NO;
label.stringValue = [NSString stringWithUTF8String: c.name.c_str()];
[view addSubview: label];
MySlider * slider = [[MySlider alloc] initWithFrame:{20+padding, y+padding, width-20-2*padding, h}];
slider.minValue = c.min;
slider.maxValue = c.max;
slider.doubleValue = c.start;
slider.callback = c.callback;
[view addSubview: slider];
y += h;
}
window.contentView = view;
windows.push_back(window);
}
void run() {
[app run];
}
NSApplication * app;
std::vector<NSWindow *> windows;
NSString * appName;
};
NSAppWrapper::NSAppWrapper()
: impl(new NSAppWrapperImpl){
impl->set_up_menu();
}
NSAppWrapper::~NSAppWrapper() = default;
void NSAppWrapper::run() {
impl->run();
}
void NSAppWrapper::create_window(GLViewFunctionality const & f){
impl->create_window(f);
}
void NSAppWrapper::create_window(ControlFunctionality const & f){
impl->create_window(f);
}
/*
Some general notes about GLView:
- It is in a multithreaded context. The CVDisplayLink will run on a diferent thread than the main thread. However the main thread *will also draw* (eg. when reshaping, to avoid flickering). So there are some locks here and there.
- Hopefully the C++ objects are destructed nicely
- There is syntax for the constructor, so I used unique_ptr...
*/
@implementation GLView {
std::unique_ptr<GLContext> context;
std::unique_ptr<CVDisplayLinky> linky;
}
@synthesize functionality;
- (id) initWithFrame:(NSRect)frameRect {
if(self = [super initWithFrame:frameRect]){
context = std::unique_ptr<GLContext>(new GLContext);
self.pixelFormat = [[NSOpenGLPixelFormat alloc] initWithCGLPixelFormatObj: context->pix];
self.openGLContext = [[NSOpenGLContext alloc] initWithCGLContextObj: context->ctx];
self.wantsBestResolutionOpenGLSurface = YES; // Opt-in for retina
}
return self;
}
- (void) prepareOpenGL{
[super prepareOpenGL];
context->set_as_current_context();
// Synchronize buffer swaps with vertical refresh rate
GLint swapInt = 1;
[[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval];
if(functionality.initialize_callback){
functionality.initialize_callback({*context, nullptr, nullptr});
}
// Create a display link capable of being used with all active displays
linky = std::unique_ptr<CVDisplayLinky>(new CVDisplayLinky(*context));
linky->callback = [=]{
// will be called in a different thread => need new autoreleasepool
@autoreleasepool {
[self drawView];
}
};
linky->start();
// Register to be notified when the window closes so we can stop the displaylink
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(windowWillClose:)
name:NSWindowWillCloseNotification
object:[self window]];
}
- (void) windowWillClose:(NSNotification*)notification{
// The default OpenGL render buffers will be destroyed. If display link
// continues to fire without renderbuffers, OpenGL draw calls will set errors.
linky->stop();
}
- (void) reshape{
[super reshape];
context->lock();
// [NSView convertRectToBacking] converts point sizes to pixel sizes.
// viewRectPixels will be larger (2x) than viewRectPoints for retina displays.
// viewRectPixels will be the same as viewRectPoints for non-retina displays
NSRect viewRectPixels = [self convertRectToBacking:self.bounds];
if(functionality.resize_callback){
functionality.resize_callback({*context, nullptr, nullptr}, viewRectPixels.size.width, viewRectPixels.size.height);
}
context->unlock();
}
- (void)renewGState{
// Called whenever graphics state updated (such as window resize)
// OpenGL rendering is not synchronous with other rendering on the OSX.
// Therefore, call disableScreenUpdatesUntilFlush so the window server
// doesn't render non-OpenGL content in the window asynchronously from
// OpenGL content, which could cause flickering. (non-OpenGL content
// includes the title bar and drawing done by the app with other APIs)
[[self window] disableScreenUpdatesUntilFlush];
[super renewGState];
}
- (void) drawRect: (NSRect) theRect{
[self drawView];
}
- (void) drawView{
context->set_as_current_context();
// We draw on a secondary thread through the display link
// When resizing the view, -reshape is called automatically on the main
// thread. Add a mutex around to avoid the threads accessing the context
// simultaneously when resizing
context->lock();
NSRect viewRectPixels = [self convertRectToBacking:self.bounds];
auto& ctx = context->ctx;
if(functionality.draw_callback){
functionality.draw_callback({*context, [viewRectPixels]{
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, (int)viewRectPixels.size.width, (int)viewRectPixels.size.height);
}, [&ctx]{
CGLFlushDrawable(ctx);
}
});
}
context->unlock();
}
@end