|  |  | 
|  | /* | 
|  | * Copyright 2011 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #import "SkNSView.h" | 
|  | #include "SkCanvas.h" | 
|  | #include "SkCGUtils.h" | 
|  | #include "SkEvent.h" | 
|  | SK_COMPILE_ASSERT(SK_SUPPORT_GPU, not_implemented_for_non_gpu_build); | 
|  | #include <OpenGL/gl.h> | 
|  |  | 
|  | //#define FORCE_REDRAW | 
|  | // Can be dropped when we no longer support 10.6. | 
|  | #define RETINA_API_AVAILABLE (defined(MAC_OS_X_VERSION_10_7) && \ | 
|  | MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7) | 
|  | @implementation SkNSView | 
|  | @synthesize fWind, fTitle, fOptionsDelegate, fGLContext; | 
|  |  | 
|  | - (id)initWithCoder:(NSCoder*)coder { | 
|  | if ((self = [super initWithCoder:coder])) { | 
|  | self = [self initWithDefaults]; | 
|  | [self setUpWindow]; | 
|  | } | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (id)initWithFrame:(NSRect)frameRect { | 
|  | if ((self = [super initWithFrame:frameRect])) { | 
|  | self = [self initWithDefaults]; | 
|  | [self setUpWindow]; | 
|  | } | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (id)initWithDefaults { | 
|  | #if RETINA_API_AVAILABLE | 
|  | [self setWantsBestResolutionOpenGLSurface:YES]; | 
|  | #endif | 
|  | fRedrawRequestPending = false; | 
|  | fWind = NULL; | 
|  | return self; | 
|  | } | 
|  |  | 
|  | - (void)setUpWindow { | 
|  | [[NSNotificationCenter defaultCenter] addObserver:self | 
|  | selector:@selector(backingPropertiesChanged:) | 
|  | name:@"NSWindowDidChangeBackingPropertiesNotification" | 
|  | object:[self window]]; | 
|  | if (NULL != fWind) { | 
|  | fWind->setVisibleP(true); | 
|  | NSSize size = self.frame.size; | 
|  | #if RETINA_API_AVAILABLE | 
|  | size = [self convertSizeToBacking:self.frame.size]; | 
|  | #endif | 
|  | fWind->resize((int) size.width, (int) size.height, | 
|  | kN32_SkColorType); | 
|  | } | 
|  | } | 
|  |  | 
|  | -(BOOL) isFlipped { | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | - (BOOL)acceptsFirstResponder { | 
|  | return YES; | 
|  | } | 
|  |  | 
|  | - (float)scaleFactor { | 
|  | NSWindow *window = [self window]; | 
|  | #if RETINA_API_AVAILABLE | 
|  | if (window) { | 
|  | return [window backingScaleFactor]; | 
|  | } | 
|  | return [[NSScreen mainScreen] backingScaleFactor]; | 
|  | #else | 
|  | if (window) { | 
|  | return [window userSpaceScaleFactor]; | 
|  | } | 
|  | return [[NSScreen mainScreen] userSpaceScaleFactor]; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | - (void)backingPropertiesChanged:(NSNotification *)notification { | 
|  | CGFloat oldBackingScaleFactor = (CGFloat)[ | 
|  | [notification.userInfo objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue | 
|  | ]; | 
|  | CGFloat newBackingScaleFactor = [self scaleFactor]; | 
|  | if (oldBackingScaleFactor == newBackingScaleFactor) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // TODO: need a better way to force a refresh (that works). | 
|  | // [fGLContext update] does not appear to update if the point size has not changed, | 
|  | // even if the backing size has changed. | 
|  | [self setFrameSize:NSMakeSize(self.frame.size.width + 1, self.frame.size.height + 1)]; | 
|  | } | 
|  |  | 
|  | - (void)resizeSkView:(NSSize)newSize { | 
|  | #if RETINA_API_AVAILABLE | 
|  | newSize = [self convertSizeToBacking:newSize]; | 
|  | #endif | 
|  | if (NULL != fWind && | 
|  | (fWind->width()  != newSize.width || | 
|  | fWind->height() != newSize.height)) | 
|  | { | 
|  | fWind->resize((int) newSize.width, (int) newSize.height); | 
|  | if (NULL != fGLContext) { | 
|  | glClear(GL_STENCIL_BUFFER_BIT); | 
|  | [fGLContext update]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void) setFrameSize:(NSSize)newSize { | 
|  | [super setFrameSize:newSize]; | 
|  | [self resizeSkView:newSize]; | 
|  | } | 
|  |  | 
|  | - (void)dealloc { | 
|  | delete fWind; | 
|  | self.fGLContext = nil; | 
|  | self.fTitle = nil; | 
|  | [super dealloc]; | 
|  | } | 
|  |  | 
|  | //////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | - (void)drawSkia { | 
|  | fRedrawRequestPending = false; | 
|  | if (NULL != fWind) { | 
|  | SkAutoTUnref<SkCanvas> canvas(fWind->createCanvas()); | 
|  | fWind->draw(canvas); | 
|  | #ifdef FORCE_REDRAW | 
|  | fWind->inval(NULL); | 
|  | #endif | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)setSkTitle:(const char *)title { | 
|  | self.fTitle = [NSString stringWithUTF8String:title]; | 
|  | [[self window] setTitle:self.fTitle]; | 
|  | } | 
|  |  | 
|  | - (BOOL)onHandleEvent:(const SkEvent&)evt { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | #include "SkOSMenu.h" | 
|  | - (void)onAddMenu:(const SkOSMenu*)menu { | 
|  | [self.fOptionsDelegate view:self didAddMenu:menu]; | 
|  | } | 
|  |  | 
|  | - (void)onUpdateMenu:(const SkOSMenu*)menu { | 
|  | [self.fOptionsDelegate view:self didUpdateMenu:menu]; | 
|  | } | 
|  |  | 
|  | - (void)postInvalWithRect:(const SkIRect*)r { | 
|  | if (!fRedrawRequestPending) { | 
|  | fRedrawRequestPending = true; | 
|  | [self setNeedsDisplay:YES]; | 
|  | [self performSelector:@selector(drawSkia) withObject:nil afterDelay:0]; | 
|  | } | 
|  | } | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | #include "SkKey.h" | 
|  | enum { | 
|  | SK_MacReturnKey		= 36, | 
|  | SK_MacDeleteKey		= 51, | 
|  | SK_MacEndKey		= 119, | 
|  | SK_MacLeftKey		= 123, | 
|  | SK_MacRightKey		= 124, | 
|  | SK_MacDownKey		= 125, | 
|  | SK_MacUpKey			= 126, | 
|  | SK_Mac0Key          = 0x52, | 
|  | SK_Mac1Key          = 0x53, | 
|  | SK_Mac2Key          = 0x54, | 
|  | SK_Mac3Key          = 0x55, | 
|  | SK_Mac4Key          = 0x56, | 
|  | SK_Mac5Key          = 0x57, | 
|  | SK_Mac6Key          = 0x58, | 
|  | SK_Mac7Key          = 0x59, | 
|  | SK_Mac8Key          = 0x5b, | 
|  | SK_Mac9Key          = 0x5c | 
|  | }; | 
|  |  | 
|  | static SkKey raw2key(UInt32 raw) | 
|  | { | 
|  | static const struct { | 
|  | UInt32  fRaw; | 
|  | SkKey   fKey; | 
|  | } gKeys[] = { | 
|  | { SK_MacUpKey,		kUp_SkKey		}, | 
|  | { SK_MacDownKey,	kDown_SkKey		}, | 
|  | { SK_MacLeftKey,	kLeft_SkKey		}, | 
|  | { SK_MacRightKey,   kRight_SkKey	}, | 
|  | { SK_MacReturnKey,  kOK_SkKey		}, | 
|  | { SK_MacDeleteKey,  kBack_SkKey		}, | 
|  | { SK_MacEndKey,		kEnd_SkKey		}, | 
|  | { SK_Mac0Key,       k0_SkKey        }, | 
|  | { SK_Mac1Key,       k1_SkKey        }, | 
|  | { SK_Mac2Key,       k2_SkKey        }, | 
|  | { SK_Mac3Key,       k3_SkKey        }, | 
|  | { SK_Mac4Key,       k4_SkKey        }, | 
|  | { SK_Mac5Key,       k5_SkKey        }, | 
|  | { SK_Mac6Key,       k6_SkKey        }, | 
|  | { SK_Mac7Key,       k7_SkKey        }, | 
|  | { SK_Mac8Key,       k8_SkKey        }, | 
|  | { SK_Mac9Key,       k9_SkKey        } | 
|  | }; | 
|  |  | 
|  | for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++) | 
|  | if (gKeys[i].fRaw == raw) | 
|  | return gKeys[i].fKey; | 
|  | return kNONE_SkKey; | 
|  | } | 
|  |  | 
|  | - (void)keyDown:(NSEvent *)event { | 
|  | if (NULL == fWind) | 
|  | return; | 
|  |  | 
|  | SkKey key = raw2key([event keyCode]); | 
|  | if (kNONE_SkKey != key) | 
|  | fWind->handleKey(key); | 
|  | else{ | 
|  | unichar c = [[event characters] characterAtIndex:0]; | 
|  | fWind->handleChar((SkUnichar)c); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)keyUp:(NSEvent *)event { | 
|  | if (NULL == fWind) | 
|  | return; | 
|  |  | 
|  | SkKey key = raw2key([event keyCode]); | 
|  | if (kNONE_SkKey != key) | 
|  | fWind->handleKeyUp(key); | 
|  | // else | 
|  | //     unichar c = [[event characters] characterAtIndex:0]; | 
|  | } | 
|  |  | 
|  | static const struct { | 
|  | unsigned    fNSModifierMask; | 
|  | unsigned    fSkModifierMask; | 
|  | } gModifierMasks[] = { | 
|  | { NSAlphaShiftKeyMask,  kShift_SkModifierKey }, | 
|  | { NSShiftKeyMask,       kShift_SkModifierKey }, | 
|  | { NSControlKeyMask,     kControl_SkModifierKey }, | 
|  | { NSAlternateKeyMask,   kOption_SkModifierKey }, | 
|  | { NSCommandKeyMask,     kCommand_SkModifierKey }, | 
|  | }; | 
|  |  | 
|  | static unsigned convertNSModifiersToSk(NSUInteger nsModi) { | 
|  | unsigned skModi = 0; | 
|  | for (size_t i = 0; i < SK_ARRAY_COUNT(gModifierMasks); ++i) { | 
|  | if (nsModi & gModifierMasks[i].fNSModifierMask) { | 
|  | skModi |= gModifierMasks[i].fSkModifierMask; | 
|  | } | 
|  | } | 
|  | return skModi; | 
|  | } | 
|  |  | 
|  | - (void)mouseDown:(NSEvent *)event { | 
|  | NSPoint p = [event locationInWindow]; | 
|  | unsigned modi = convertNSModifiersToSk([event modifierFlags]); | 
|  |  | 
|  | if ([self mouse:p inRect:[self bounds]] && NULL != fWind) { | 
|  | NSPoint loc = [self convertPoint:p fromView:nil]; | 
|  | #if RETINA_API_AVAILABLE | 
|  | loc = [self convertPointToBacking:loc]; //y-up | 
|  | loc.y = -loc.y; | 
|  | #endif | 
|  | fWind->handleClick((int) loc.x, (int) loc.y, | 
|  | SkView::Click::kDown_State, self, modi); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)mouseDragged:(NSEvent *)event { | 
|  | NSPoint p = [event locationInWindow]; | 
|  | unsigned modi = convertNSModifiersToSk([event modifierFlags]); | 
|  |  | 
|  | if ([self mouse:p inRect:[self bounds]] && NULL != fWind) { | 
|  | NSPoint loc = [self convertPoint:p fromView:nil]; | 
|  | #if RETINA_API_AVAILABLE | 
|  | loc = [self convertPointToBacking:loc]; //y-up | 
|  | loc.y = -loc.y; | 
|  | #endif | 
|  | fWind->handleClick((int) loc.x, (int) loc.y, | 
|  | SkView::Click::kMoved_State, self, modi); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)mouseMoved:(NSEvent *)event { | 
|  | NSPoint p = [event locationInWindow]; | 
|  | unsigned modi = convertNSModifiersToSk([event modifierFlags]); | 
|  |  | 
|  | if ([self mouse:p inRect:[self bounds]] && NULL != fWind) { | 
|  | NSPoint loc = [self convertPoint:p fromView:nil]; | 
|  | #if RETINA_API_AVAILABLE | 
|  | loc = [self convertPointToBacking:loc]; //y-up | 
|  | loc.y = -loc.y; | 
|  | #endif | 
|  | fWind->handleClick((int) loc.x, (int) loc.y, | 
|  | SkView::Click::kMoved_State, self, modi); | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)mouseUp:(NSEvent *)event { | 
|  | NSPoint p = [event locationInWindow]; | 
|  | unsigned modi = convertNSModifiersToSk([event modifierFlags]); | 
|  |  | 
|  | if ([self mouse:p inRect:[self bounds]] && NULL != fWind) { | 
|  | NSPoint loc = [self convertPoint:p fromView:nil]; | 
|  | #if RETINA_API_AVAILABLE | 
|  | loc = [self convertPointToBacking:loc]; //y-up | 
|  | loc.y = -loc.y; | 
|  | #endif | 
|  | fWind->handleClick((int) loc.x, (int) loc.y, | 
|  | SkView::Click::kUp_State, self, modi); | 
|  | } | 
|  | } | 
|  |  | 
|  | /////////////////////////////////////////////////////////////////////////////// | 
|  | #include <OpenGL/OpenGL.h> | 
|  |  | 
|  | namespace { | 
|  | CGLContextObj createGLContext(int msaaSampleCount) { | 
|  | GLint major, minor; | 
|  | CGLGetVersion(&major, &minor); | 
|  |  | 
|  | static const CGLPixelFormatAttribute attributes[] = { | 
|  | kCGLPFAStencilSize, (CGLPixelFormatAttribute) 8, | 
|  | kCGLPFAAccelerated, | 
|  | kCGLPFADoubleBuffer, | 
|  | (CGLPixelFormatAttribute)0 | 
|  | }; | 
|  |  | 
|  | CGLPixelFormatObj format; | 
|  | GLint npix = 0; | 
|  | if (msaaSampleCount > 0) { | 
|  | static int kAttributeCount = SK_ARRAY_COUNT(attributes); | 
|  | CGLPixelFormatAttribute msaaAttributes[kAttributeCount + 5]; | 
|  | memcpy(msaaAttributes, attributes, sizeof(attributes)); | 
|  | SkASSERT(0 == msaaAttributes[kAttributeCount - 1]); | 
|  | msaaAttributes[kAttributeCount - 1] = kCGLPFASampleBuffers; | 
|  | msaaAttributes[kAttributeCount + 0] = (CGLPixelFormatAttribute)1; | 
|  | msaaAttributes[kAttributeCount + 1] = kCGLPFAMultisample; | 
|  | msaaAttributes[kAttributeCount + 2] = kCGLPFASamples; | 
|  | msaaAttributes[kAttributeCount + 3] = | 
|  | (CGLPixelFormatAttribute)msaaSampleCount; | 
|  | msaaAttributes[kAttributeCount + 4] = (CGLPixelFormatAttribute)0; | 
|  | CGLChoosePixelFormat(msaaAttributes, &format, &npix); | 
|  | } | 
|  | if (!npix) { | 
|  | CGLChoosePixelFormat(attributes, &format, &npix); | 
|  | } | 
|  | CGLContextObj ctx; | 
|  | CGLCreateContext(format, NULL, &ctx); | 
|  | CGLDestroyPixelFormat(format); | 
|  |  | 
|  | static const GLint interval = 1; | 
|  | CGLSetParameter(ctx, kCGLCPSwapInterval, &interval); | 
|  | CGLSetCurrentContext(ctx); | 
|  | return ctx; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)viewDidMoveToWindow { | 
|  | [super viewDidMoveToWindow]; | 
|  |  | 
|  | //Attaching view to fGLContext requires that the view to be part of a window, | 
|  | //and that the NSWindow instance must have a CoreGraphics counterpart (or | 
|  | //it must NOT be deferred or should have been on screen at least once) | 
|  | if ([fGLContext view] != self && nil != self.window) { | 
|  | [fGLContext setView:self]; | 
|  | } | 
|  | } | 
|  | - (bool)attach:(SkOSWindow::SkBackEndTypes)attachType | 
|  | withMSAASampleCount:(int) sampleCount | 
|  | andGetInfo:(SkOSWindow::AttachmentInfo*) info { | 
|  | if (nil == fGLContext) { | 
|  | CGLContextObj ctx = createGLContext(sampleCount); | 
|  | fGLContext = [[NSOpenGLContext alloc] initWithCGLContextObj:ctx]; | 
|  | CGLReleaseContext(ctx); | 
|  | if (NULL == fGLContext) { | 
|  | return false; | 
|  | } | 
|  | [fGLContext setView:self]; | 
|  | } | 
|  |  | 
|  | [fGLContext makeCurrentContext]; | 
|  | CGLPixelFormatObj format = CGLGetPixelFormat((CGLContextObj)[fGLContext CGLContextObj]); | 
|  | CGLDescribePixelFormat(format, 0, kCGLPFASamples, &info->fSampleCount); | 
|  | CGLDescribePixelFormat(format, 0, kCGLPFAStencilSize, &info->fStencilBits); | 
|  | NSSize size = self.bounds.size; | 
|  | #if RETINA_API_AVAILABLE | 
|  | size = [self convertSizeToBacking:size]; | 
|  | #endif | 
|  | glViewport(0, 0, (int) size.width, (int) size.height); | 
|  | glClearColor(0, 0, 0, 0); | 
|  | glClearStencil(0); | 
|  | glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | - (void)detach { | 
|  | [fGLContext release]; | 
|  | fGLContext = nil; | 
|  | } | 
|  |  | 
|  | - (void)present { | 
|  | if (nil != fGLContext) { | 
|  | [fGLContext flushBuffer]; | 
|  | } | 
|  | } | 
|  | @end |