diff --git a/CMakeLists.txt b/CMakeLists.txt index d988c57..d1d6692 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,13 @@ find_library(FOUNDATION_LIBRARY Foundation) find_library(COCOA_LIBRARY Cocoa) find_library(COREGRAPHICS_LIBRARY CoreGraphics) find_library(OPENGL_LIBRARY OpenGL) +find_library(QUARTZCORE_LIBRARY QuartzCore) -set(libs ${FOUNDATION_LIBRARY} ${COCOA_LIBRARY} ${COREGRAPHICS_LIBRARY} ${OPENGL_LIBRARY}) +set(libs ${FOUNDATION_LIBRARY} ${COCOA_LIBRARY} ${COREGRAPHICS_LIBRARY} ${OPENGL_LIBRARY} ${QUARTZCORE_LIBRARY}) +set(${LIBNAME}_LIBRARIES ${LIBNAME} ${libs} CACHE INTERNAL "") target_link_libraries(${LIBNAME} ${libs}) +target_include_directories(${LIBNAME} PUBLIC ".") install(TARGETS ${LIBNAME} DESTINATION lib) install(FILES ${headers} DESTINATION include/J) diff --git a/NSWrapper.hpp b/NSWrapper.hpp index c325bfc..2902287 100644 --- a/NSWrapper.hpp +++ b/NSWrapper.hpp @@ -10,12 +10,18 @@ #include #include +#include +#include #include struct GLContext; struct ContextParameters{ - GLContext & context; + GLContext & context; + + // These two functions will be set when invoking the draw_callback (and should be used) + std::function bind_framebuffer; // function to bind the systems framebuffer and set the viewport + std::function flush_drawable; // function to flush the drawable }; struct GLViewFunctionality { @@ -26,16 +32,27 @@ struct GLViewFunctionality { std::function resize_callback; }; +struct Control { + std::string name = "default"; + double start = 0, min = 0, max = 1; + std::function callback; +}; + +struct ControlFunctionality { + std::vector sliders; +}; + // returns a GLViewFunctionality with only its draw function set. GLViewFunctionality simple_draw(std::function f); struct NSAppWrapper { NSAppWrapper(); ~NSAppWrapper(); - + void run(); void create_window(GLViewFunctionality const &); - + void create_window(ControlFunctionality const &); + std::unique_ptr impl; }; diff --git a/NSWrapper.mm b/NSWrapper.mm index f0459bb..f21ec6d 100644 --- a/NSWrapper.mm +++ b/NSWrapper.mm @@ -25,55 +25,128 @@ GLViewFunctionality simple_draw(std::function f){ @property GLViewFunctionality functionality; @end +@interface MySlider : NSSlider +- (instancetype) initWithFrame:(NSRect)frame; +- (void)action:(MySlider*)sender; +@property std::function 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; - [window center]; - [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 run() { - [app run]; - } - - NSApplication * app; - std::vector windows; - NSString * appName; + 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 windows; + NSString * appName; }; NSAppWrapper::NSAppWrapper() @@ -88,7 +161,11 @@ void NSAppWrapper::run() { } void NSAppWrapper::create_window(GLViewFunctionality const & f){ - impl->create_window(f); + impl->create_window(f); +} + +void NSAppWrapper::create_window(ControlFunctionality const & f){ + impl->create_window(f); } @@ -109,39 +186,39 @@ void NSAppWrapper::create_window(GLViewFunctionality const & f){ - (id) initWithFrame:(NSRect)frameRect { if(self = [super initWithFrame:frameRect]){ - context = std::unique_ptr(new GLContext); - - self.pixelFormat = [[NSOpenGLPixelFormat alloc] initWithCGLPixelFormatObj: context->pix]; - self.openGLContext = [[NSOpenGLContext alloc] initWithCGLContextObj: context->ctx]; - self.wantsBestResolutionOpenGLSurface = YES; // Opt-in for retina + context = std::unique_ptr(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}); + functionality.initialize_callback({*context, nullptr, nullptr}); } - + // Create a display link capable of being used with all active displays linky = std::unique_ptr(new CVDisplayLinky(*context)); linky->callback = [=]{ - // will be called in a different thread => need new autoreleasepool - @autoreleasepool { - [self drawView]; - } + // 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:) @@ -152,37 +229,37 @@ void NSAppWrapper::create_window(GLViewFunctionality const & f){ - (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}, viewRectPixels.size.width, viewRectPixels.size.height); + 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]; } @@ -192,24 +269,27 @@ void NSAppWrapper::create_window(GLViewFunctionality const & f){ - (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]; - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(0, 0, (int)viewRectPixels.size.width, (int)viewRectPixels.size.height); - - if(functionality.draw_callback){ - functionality.draw_callback({*context}); - } - - CGLFlushDrawable(context->ctx); - context->unlock(); + 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