#include "pch.h" #include "log.h" #include "shader.h" #include "shape.h" #include "texture.h" #include "image.h" #include "app.h" #include "keymap.h" #include "main.h" #include #include #include #include #import "objc_utils.h" #import #include #include std::deque> tasklist; std::mutex task_mutex; @implementation View { NSSharingService *airdrop_service; } - (void)hockeyapp_crash { [[[BITHockeyManager sharedHockeyManager] crashManager] generateTestCrash]; } - (void)async_lock { CGLLockContext([glctx CGLContextObj]); [glctx makeCurrentContext]; } - (void)async_unlock { CGLUnlockContext([glctx CGLContextObj]); } - (void)async_swap { CGLFlushDrawable([glctx CGLContextObj]); } - (void)close { [[NSApplication sharedApplication] terminate:nil]; } - (std::string)pick_file:(NSArray*)types { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:NO]; [panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed //NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil]; [panel setAllowedFileTypes:types]; NSInteger clicked = [panel runModal]; std::string ret; if (clicked == NSFileHandlingPanelOKButton) { for (NSURL *url in [panel URLs]) { LOG("selected file: %s", [[url path] cStringUsingEncoding:NSUTF8StringEncoding]); ret = [[url path] cStringUsingEncoding:NSUTF8StringEncoding]; } } return ret; } - (std::string)pick_dir { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:NO]; [panel setCanChooseDirectories:YES]; [panel setAllowsMultipleSelection:NO]; // yes if more than one dir is allowed NSInteger clicked = [panel runModal]; std::string ret; if (clicked == NSFileHandlingPanelOKButton) { for (NSURL *url in [panel URLs]) { LOG("selected path: %s", [[url path] cStringUsingEncoding:NSUTF8StringEncoding]); ret = [[url path] cStringUsingEncoding:NSUTF8StringEncoding]; } } return ret; } - (void)share_file:(NSString*)file_path { NSURL *url = [NSURL fileURLWithPath:file_path]; NSArray *objectsToShare = @[url]; [airdrop_service performWithItems:objectsToShare]; } - (NSWindow *)sharingService:(NSSharingService *)sharingService sourceWindowForShareItems:(NSArray *)items sharingContentScope:(NSSharingContentScope *)sharingContentScope { return self.window; } - (NSView *)anchoringViewForSharingService:(NSSharingService *)sharingService showRelativeToRect:(NSRect *)positioningRect preferredEdge:(NSRectEdge *)preferredEdge { return self; } - (instancetype)initWithFrame:(NSRect)frameRect { airdrop_service = [NSSharingService sharingServiceNamed:NSSharingServiceNameSendViaAirDrop]; airdrop_service.delegate = self; gl_ready = false; NSOpenGLPixelFormatAttribute attrs[] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFADepthSize, 24, // Must specify the 3.2 Core Profile to use OpenGL 3.2 NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, // Multisample NSOpenGLPFAMultisample, NSOpenGLPFASamples, 2, NSOpenGLPFASampleBuffers, 1, 0 }; NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; NSOpenGLContext* context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; self = [super initWithFrame:frameRect pixelFormat:pf]; [self setPixelFormat:pf]; [self setOpenGLContext:context]; self->trackingArea = [[NSTrackingArea alloc] initWithRect:frameRect options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow ) owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; return self; } - (void)prepareOpenGL { NSLog(@"prepare"); // Synchronize buffer swaps with vertical refresh rate GLint swapInt = 1; [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; // Create a display link capable of being used with all active displays CVDisplayLinkCreateWithActiveCGDisplays(&dl); // Set the renderer output callback function CVDisplayLinkSetOutputCallback(dl, &MyDisplayLinkCallback, (__bridge void*)self); // Set the display link for the current renderer CGLContextObj cglContext = [[self openGLContext] CGLContextObj]; CGLPixelFormatObj cglPixelFormat = [[self pixelFormat] CGLPixelFormatObj]; CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(dl, cglContext, cglPixelFormat); CGLEnable([self.openGLContext CGLContextObj], kCGLCECrashOnRemovedFunctions); // Activate the display link CVDisplayLinkStart(dl); cgl = [[self openGLContext] CGLContextObj]; glctx = [self openGLContext]; CGLLockContext([[self openGLContext] CGLContextObj]); App::I.init(); [self.window setTitle:[NSString stringWithFormat:@"%s - %s", g_window_title, glGetString(GL_RENDERER)]]; CGLUnlockContext([[self openGLContext] CGLContextObj]); gl_ready = true; if ([file2open length] > 0) { LOG("open file %s", [file2open UTF8String]); App::I.open_document([file2open UTF8String]); } } - (void)terminateGL { CGLLockContext([[self openGLContext] CGLContextObj]); App::I.terminate(); CGLUnlockContext([[self openGLContext] CGLContextObj]); CVDisplayLinkRelease(dl); } // This is the renderer output callback function static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { CVReturn result = [(__bridge View*)displayLinkContext getFrameForTime:outputTime]; return result; } - (CVReturn)getFrameForTime:(const CVTimeStamp*)outputTime { static double _timeFreq = CVGetHostClockFrequency(); static double _prevTime = (double)outputTime->hostTime / _timeFreq; static double elapsed = 0; double hostTime = (double)outputTime->hostTime; double now = hostTime / _timeFreq; double dt = now - _prevTime; _prevTime = now; // this will not update unless 1/30th of a second has passed since the last update //if (now < _prevTime + (1.0 / 30.0)) { std::deque> working_list; { std::lock_guard lock(task_mutex); working_list = std::move(tasklist); } // 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 CGLLockContext([glctx CGLContextObj]); [glctx makeCurrentContext]; while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } App::I.tick(dt); if (App::I.redraw) { App::I.clear(); App::I.update(elapsed); elapsed = 0; CGLFlushDrawable([glctx CGLContextObj]); } //[[self openGLContext] flushBuffer]; // returning NO will cause the layer to NOT be redrawn CGLUnlockContext([glctx CGLContextObj]); return NO; } return kCVReturnSuccess; } - (void)dealloc { // Release the display link CVDisplayLinkRelease(dl); } - (void)drawRect:(NSRect)dirtyRect { NSLog(@"drawRect"); // 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 CGLLockContext(cgl); [glctx makeCurrentContext]; App::I.redraw = true; App::I.clear(); App::I.update(0); //[[self openGLContext] flushBuffer]; // returning NO will cause the layer to NOT be redrawn CGLFlushDrawable(cgl); CGLUnlockContext(cgl); } - (void)updateTrackingAreas { if(self->trackingArea != nil) [self removeTrackingArea:self->trackingArea]; self->trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] options: (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow) owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; } - (void)reshape { [super reshape]; // Get the view size in Points NSRect viewRectPoints = [self bounds]; // We draw on a secondary thread through the display link. However, when // resizing the view, -drawRect is called on the main thread. // Add a mutex around to avoid the threads accessing the context // simultaneously when resizing. CGLLockContext([[self openGLContext] CGLContextObj]); #if SUPPORT_RETINA_RESOLUTION // Rendering at retina resolutions will reduce aliasing, but at the potential // cost of framerate and battery life due to the GPU needing to render more // pixels. // Any calculations the renderer does which use pixel dimentions, must be // in "retina" space. [NSView convertRectToBacking] converts point sizes // to pixel sizes. Thus the renderer gets the size in pixels, not points, // so that it can set it's viewport and perform and other pixel based // calculations appropriately. // viewRectPixels will be larger than viewRectPoints for retina displays. // viewRectPixels will be the same as viewRectPoints for non-retina displays NSRect viewRectPixels = [self convertRectToBacking:viewRectPoints]; #else //if !SUPPORT_RETINA_RESOLUTION // App will typically render faster and use less power rendering at // non-retina resolutions since the GPU needs to render less pixels. // There is the cost of more aliasing, but it will be no-worse than // on a Mac without a retina display. // Points:Pixels is always 1:1 when not supporting retina resolutions NSRect viewRectPixels = viewRectPoints; #endif // !SUPPORT_RETINA_RESOLUTION App::I.resize(viewRectPixels.size.width, viewRectPixels.size.height); CGLUnlockContext([[self openGLContext] CGLContextObj]); } - (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)mouseDown:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_down(0, mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse, 0); }); } - (void)rightMouseDown:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_down(1, mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse, 0); }); } - (void)mouseUp:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc] { App::I.mouse_up(0, mouseLoc.x, App::I.height - mouseLoc.y - 1, kEventSource::Mouse, 0); }); } - (void)rightMouseUp:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc] { App::I.mouse_up(1, mouseLoc.x, App::I.height - mouseLoc.y - 1, kEventSource::Mouse, 0); }); } - (void)mouseMoved:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_move(mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse, 0); }); } -(void)mouseDragged:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_move(mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse, 0); }); } - (void)rightMouseDragged:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,p=theEvent.pressure] { App::I.mouse_move(mouseLoc.x, App::I.height - mouseLoc.y - 1, p, kEventSource::Mouse, 0); }); } -(void)mouseExited:(NSEvent *)event { self->cursor_visible = CGCursorIsVisible(); if (!self->cursor_visible) [NSCursor unhide]; } -(void)mouseEntered:(NSEvent *)event { if (!self->cursor_visible) [NSCursor hide]; } - (void)scrollWheel:(NSEvent *)theEvent { auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil]; std::lock_guard lock(task_mutex); tasklist.emplace_back([mouseLoc,d=[theEvent deltaY]] { App::I.mouse_scroll(mouseLoc.x, App::I.height - mouseLoc.y - 1, d); }); } - (void)keyDown:(NSEvent *)theEvent { auto keyCode = [theEvent keyCode]; auto chars = [theEvent characters]; const char* c_str = [chars cStringUsingEncoding:NSASCIIStringEncoding]; std::string s = c_str ? c_str : ""; std::lock_guard lock(task_mutex); tasklist.emplace_back([keyCode, s] { if (App::I.keys[(int)kKey::KeyCtrl]) { App::I.key_down(convert_key(keyCode)); App::I.key_up(convert_key(keyCode)); } else { App::I.key_down(convert_key(keyCode)); if (!s.empty()) App::I.key_char(s[0]); } }); } - (void)keyUp:(NSEvent *)theEvent { auto keyCode = [theEvent keyCode]; std::lock_guard lock(task_mutex); tasklist.emplace_back([keyCode] { App::I.key_up(convert_key(keyCode)); }); } - (void)flagsChanged:(NSEvent *)event { [super flagsChanged:event]; auto flags = [event modifierFlags]; std::lock_guard lock(task_mutex); tasklist.emplace_back([flags] { static bool pressed_shift = false; if (pressed_shift != (flags & NSShiftKeyMask)) { pressed_shift = (flags & NSShiftKeyMask); pressed_shift ? App::I.key_down(kKey::KeyShift) : App::I.key_up(kKey::KeyShift); } static bool pressed_alt = false; if (pressed_alt != (flags & NSAlternateKeyMask)) { pressed_alt = (flags & NSAlternateKeyMask); pressed_alt ? App::I.key_down(kKey::KeyAlt) : App::I.key_up(kKey::KeyAlt); } static bool pressed_cmd = false; if (pressed_cmd != (flags & NSCommandKeyMask)) { pressed_cmd = (flags & NSCommandKeyMask); pressed_cmd ? App::I.key_down(kKey::KeyCtrl) : App::I.key_up(kKey::KeyCtrl); } }); } @end @implementation Window @end @implementation Controller - (void)windowWillClose:(NSNotification *)notification { [[NSApplication sharedApplication] terminate:nil]; } -(BOOL)windowShouldClose:(NSWindow *)sender { return App::I.request_close(); } @end @interface AppOSX : NSApplication { Window* window; Controller* controller; View* view; NSString* file2open; } @end @implementation AppOSX - (instancetype)init { self = [super init]; [self setActivationPolicy:NSApplicationActivationPolicyRegular]; // make it to the front [self setDelegate:self]; return self; } - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { LOG("open file %s", [filename UTF8String]); if (view && view->gl_ready) App::I.open_document([filename UTF8String]); file2open = filename; return YES; } - (void)applicationWillTerminate:(NSNotification *)notification { [view terminateGL]; [window close]; } - (void)applicationDidFinishLaunching:(NSNotification *)notification { //[[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"NSApplicationCrashOnExceptions": @YES }]; [[BITHockeyManager sharedHockeyManager] configureWithIdentifier:@"1500ff4a32eb4076be3b5147e2d8e27e"]; // Do some additional configuration if needed here [[BITHockeyManager sharedHockeyManager] startManager]; if (!App::I.check_license()) return; App::I.initLog(); App::I.create(); NSRect r = NSMakeRect(0, 0, App::I.width, App::I.height); view = [[View alloc] initWithFrame:r]; view->file2open = file2open; controller = [[Controller alloc] initWithWindow:window]; App::I.osx_view = view; auto style = NSTitledWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask|NSClosableWindowMask; window = [[Window alloc] initWithContentRect:r styleMask:style backing:NSBackingStoreBuffered defer:NO]; [window setDelegate:controller]; [window setTitle:[NSString stringWithUTF8String:g_window_title]]; [window center]; [window makeKeyAndOrderFront:controller]; [window setContentView:view]; [window setAcceptsMouseMovedEvents:true]; [window makeFirstResponder:view]; view->wnd = window; auto menubar = [NSMenu new]; auto appMenuItem = [NSMenuItem new]; [menubar addItem:appMenuItem]; [self setMainMenu:menubar]; auto appMenu = [NSMenu new]; auto appName = [[NSProcessInfo processInfo] processName]; auto quitTitle = [@"Quit " stringByAppendingString:appName]; auto quitMenuItem = [[NSMenuItem alloc] initWithTitle:quitTitle action:@selector(terminate:) keyEquivalent:@"q"]; [appMenu addItem:quitMenuItem]; [appMenuItem setSubmenu:appMenu]; NSLog(@"app launched"); } @end int main(int argc, const char * argv[]) { install_global_handlers(); AppOSX* app = [AppOSX sharedApplication]; [app run]; return 0; }