|
|
@ -25,55 +25,128 @@ GLViewFunctionality simple_draw(std::function<void()> f){ |
|
|
|
@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; |
|
|
|
[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<NSWindow *> 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<NSWindow *> 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<GLContext>(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<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}); |
|
|
|
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]; |
|
|
|
} |
|
|
|
// 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 |
|
|
|