Files
panopainter/PanoPainter-OSX/main.cpp
2019-01-30 11:18:52 +01:00

539 lines
18 KiB
C++

#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 <CoreFoundation/CoreFoundation.h>
#include <Cocoa/Cocoa.h>
#include <CoreVideo/CoreVideo.h>
#include <OpenGL/OpenGL.h>
#import "objc_utils.h"
#import <HockeySDK/HockeySDK.h>
#include <deque>
#include <chrono>
std::deque<std::packaged_task<void()>> 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<NSString*>*)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];
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]);
CVDisplayLinkRelease(dl);
CGLUnlockContext([[self openGLContext] CGLContextObj]);
}
// 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<std::packaged_task<void()>> working_list;
{
std::lock_guard<std::mutex> 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)reshape
{
[super reshape];
// 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]);
// Get the view size in Points
NSRect viewRectPoints = [self bounds];
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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)scrollWheel:(NSEvent *)theEvent
{
auto mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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
{
App::I.terminate();
[[NSApplication sharedApplication] terminate:nil];
}
-(BOOL)windowShouldClose:(NSWindow *)sender
{
return App::I.request_close();
}
@end
@interface AppOSX : NSApplication<NSApplicationDelegate>
{
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]);
//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;
}