| // Copyright 2014 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build darwin |
| // +build !ios |
| |
| #include "_cgo_export.h" |
| #include <pthread.h> |
| #include <stdio.h> |
| |
| #import <Cocoa/Cocoa.h> |
| #import <Foundation/Foundation.h> |
| #import <OpenGL/gl3.h> |
| |
| void makeCurrentContext(GLintptr context) { |
| NSOpenGLContext* ctx = (NSOpenGLContext*)context; |
| [ctx makeCurrentContext]; |
| } |
| |
| uint64 threadID() { |
| uint64 id; |
| if (pthread_threadid_np(pthread_self(), &id)) { |
| abort(); |
| } |
| return id; |
| } |
| |
| @interface MobileGLView : NSOpenGLView<NSApplicationDelegate, NSWindowDelegate> |
| { |
| } |
| @end |
| |
| @implementation MobileGLView |
| - (void)prepareOpenGL { |
| [self setWantsBestResolutionOpenGLSurface:YES]; |
| GLint swapInt = 1; |
| [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; |
| |
| // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. |
| // But VBAs don't exist in ES 2. So we bind a default one. |
| GLuint vba; |
| glGenVertexArrays(1, &vba); |
| glBindVertexArray(vba); |
| |
| startloop((GLintptr)[self openGLContext]); |
| } |
| |
| - (void)reshape { |
| [super reshape]; |
| |
| // Calculate screen PPI. |
| // |
| // Note that the backingScaleFactor converts from logical |
| // pixels to actual pixels, but both of these units vary |
| // independently from real world size. E.g. |
| // |
| // 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15 |
| // 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06 |
| // 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51 |
| // 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03 |
| NSScreen *screen = [NSScreen mainScreen]; |
| double screenPixW = [screen frame].size.width * [screen backingScaleFactor]; |
| |
| CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue]; |
| CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters |
| float ppi = 25.4 * screenPixW / screenSizeMM.width; |
| float pixelsPerPt = ppi/72.0; |
| |
| // The width and height reported to the geom package are the |
| // bounds of the OpenGL view. Several steps are necessary. |
| // First, [self bounds] gives us the number of logical pixels |
| // in the view. Multiplying this by the backingScaleFactor |
| // gives us the number of actual pixels. |
| NSRect r = [self bounds]; |
| int w = r.size.width * [screen backingScaleFactor]; |
| int h = r.size.height * [screen backingScaleFactor]; |
| |
| setGeom(pixelsPerPt, w, h); |
| } |
| |
| - (void)drawRect:(NSRect)theRect { |
| // Called during resize. This gets rid of flicker when resizing. |
| drawgl(); |
| } |
| |
| - (void)mouseDown:(NSEvent *)theEvent { |
| double scale = [[NSScreen mainScreen] backingScaleFactor]; |
| NSPoint p = [theEvent locationInWindow]; |
| eventMouseDown(p.x * scale, p.y * scale); |
| } |
| |
| - (void)mouseUp:(NSEvent *)theEvent { |
| double scale = [[NSScreen mainScreen] backingScaleFactor]; |
| NSPoint p = [theEvent locationInWindow]; |
| eventMouseEnd(p.x * scale, p.y * scale); |
| } |
| |
| - (void)mouseDragged:(NSEvent *)theEvent { |
| double scale = [[NSScreen mainScreen] backingScaleFactor]; |
| NSPoint p = [theEvent locationInWindow]; |
| eventMouseDragged(p.x * scale, p.y * scale); |
| } |
| |
| - (void)windowDidBecomeKey:(NSNotification *)notification { |
| lifecycleFocused(); |
| } |
| |
| - (void)windowDidResignKey:(NSNotification *)notification { |
| if (![NSApp isHidden]) { |
| lifecycleVisible(); |
| } |
| } |
| |
| - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { |
| lifecycleAlive(); |
| [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; |
| [self.window makeKeyAndOrderFront:self]; |
| lifecycleVisible(); |
| } |
| |
| - (void)applicationWillTerminate:(NSNotification *)aNotification { |
| lifecycleDead(); |
| } |
| |
| - (void)applicationDidHide:(NSNotification *)aNotification { |
| lifecycleAlive(); |
| } |
| |
| - (void)applicationWillUnhide:(NSNotification *)notification { |
| lifecycleVisible(); |
| } |
| |
| - (void)windowWillClose:(NSNotification *)notification { |
| lifecycleAlive(); |
| } |
| @end |
| |
| @interface MobileResponder : NSResponder |
| { |
| } |
| @end |
| |
| @implementation MobileResponder |
| - (void)keyDown:(NSEvent *)theEvent { |
| [self key:theEvent]; |
| } |
| - (void)keyUp:(NSEvent *)theEvent { |
| [self key:theEvent]; |
| } |
| - (void)key:(NSEvent *)theEvent { |
| NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0]; |
| |
| uint8_t buf[4] = {0, 0, 0, 0}; |
| if (![theEvent.characters getBytes:buf |
| maxLength:4 |
| usedLength:nil |
| encoding:NSUTF32LittleEndianStringEncoding |
| options:NSStringEncodingConversionAllowLossy |
| range:range |
| remainingRange:nil]) { |
| NSLog(@"failed to read key event %@", theEvent); |
| return; |
| } |
| |
| uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24; |
| |
| uint8_t direction; |
| if ([theEvent isARepeat]) { |
| direction = 0; |
| } else if (theEvent.type == NSKeyDown) { |
| direction = 1; |
| } else { |
| direction = 2; |
| } |
| eventKey((int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags); |
| } |
| |
| - (void)flagsChanged:(NSEvent *)theEvent { |
| eventFlags(theEvent.modifierFlags); |
| } |
| @end |
| |
| void |
| runApp(void) { |
| [NSAutoreleasePool new]; |
| [NSApplication sharedApplication]; |
| [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; |
| |
| id menuBar = [[NSMenu new] autorelease]; |
| id menuItem = [[NSMenuItem new] autorelease]; |
| [menuBar addItem:menuItem]; |
| [NSApp setMainMenu:menuBar]; |
| |
| id menu = [[NSMenu new] autorelease]; |
| id name = [[NSProcessInfo processInfo] processName]; |
| |
| id hideMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Hide" |
| action:@selector(hide:) keyEquivalent:@"h"] |
| autorelease]; |
| [menu addItem:hideMenuItem]; |
| |
| id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Quit" |
| action:@selector(terminate:) keyEquivalent:@"q"] |
| autorelease]; |
| [menu addItem:quitMenuItem]; |
| [menuItem setSubmenu:menu]; |
| |
| NSRect rect = NSMakeRect(0, 0, 600, 800); |
| |
| NSWindow* window = [[[NSWindow alloc] initWithContentRect:rect |
| styleMask:NSTitledWindowMask |
| backing:NSBackingStoreBuffered |
| defer:NO] |
| autorelease]; |
| window.styleMask |= NSResizableWindowMask; |
| window.styleMask |= NSMiniaturizableWindowMask ; |
| window.styleMask |= NSClosableWindowMask; |
| window.title = name; |
| [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; |
| |
| NSOpenGLPixelFormatAttribute attr[] = { |
| NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, |
| NSOpenGLPFAColorSize, 24, |
| NSOpenGLPFAAlphaSize, 8, |
| NSOpenGLPFADepthSize, 16, |
| NSOpenGLPFAAccelerated, |
| NSOpenGLPFADoubleBuffer, |
| NSOpenGLPFAAllowOfflineRenderers, |
| 0 |
| }; |
| id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; |
| MobileGLView* view = [[MobileGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; |
| [window setContentView:view]; |
| [window setDelegate:view]; |
| [NSApp setDelegate:view]; |
| |
| window.nextResponder = [[[MobileResponder alloc] init] autorelease]; |
| |
| [NSApp run]; |
| } |
| |
| void stopApp(void) { |
| [NSApp terminate:nil]; |
| } |