1/* 2 * Copyright 2011 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#import "SkNSView.h" 9#include "SkCanvas.h" 10#include "SkSurface.h" 11#include "SkCGUtils.h" 12#include "SkEvent.h" 13static_assert(SK_SUPPORT_GPU, "not_implemented_for_non_gpu_build"); 14#include <OpenGL/gl.h> 15 16//#define FORCE_REDRAW 17// Can be dropped when we no longer support 10.6. 18#if defined(MAC_OS_X_VERSION_10_7) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 19 #define RETINA_API_AVAILABLE 1 20#else 21 #define RETINA_API_AVAILABLE 0 22#endif 23 24@implementation SkNSView 25@synthesize fWind, fTitle, fOptionsDelegate, fGLContext; 26 27BOOL fRedrawRequestPending; 28 29- (id)initWithCoder:(NSCoder*)coder { 30 if ((self = [super initWithCoder:coder])) { 31 self = [self initWithDefaults]; 32 [self setUpWindow]; 33 } 34 return self; 35} 36 37- (id)initWithFrame:(NSRect)frameRect { 38 if ((self = [super initWithFrame:frameRect])) { 39 self = [self initWithDefaults]; 40 [self setUpWindow]; 41 } 42 return self; 43} 44 45- (id)initWithDefaults { 46#if RETINA_API_AVAILABLE 47 [self setWantsBestResolutionOpenGLSurface:YES]; 48#endif 49 fRedrawRequestPending = false; 50 fWind = NULL; 51 return self; 52} 53 54- (void)setUpWindow { 55 [[NSNotificationCenter defaultCenter] addObserver:self 56 selector:@selector(backingPropertiesChanged:) 57 name:@"NSWindowDidChangeBackingPropertiesNotification" 58 object:[self window]]; 59 if (fWind) { 60 fWind->setVisibleP(true); 61 NSSize size = self.frame.size; 62#if RETINA_API_AVAILABLE 63 size = [self convertSizeToBacking:self.frame.size]; 64#endif 65 fWind->resize((int) size.width, (int) size.height); 66 [[self window] setAcceptsMouseMovedEvents:YES]; 67 } 68} 69 70-(BOOL) isFlipped { 71 return YES; 72} 73 74- (BOOL)acceptsFirstResponder { 75 return YES; 76} 77 78- (float)scaleFactor { 79 NSWindow *window = [self window]; 80#if RETINA_API_AVAILABLE 81 if (window) { 82 return [window backingScaleFactor]; 83 } 84 return [[NSScreen mainScreen] backingScaleFactor]; 85#else 86 if (window) { 87 return [window userSpaceScaleFactor]; 88 } 89 return [[NSScreen mainScreen] userSpaceScaleFactor]; 90#endif 91} 92 93- (void)backingPropertiesChanged:(NSNotification *)notification { 94 CGFloat oldBackingScaleFactor = (CGFloat)[ 95 [notification.userInfo objectForKey:@"NSBackingPropertyOldScaleFactorKey"] doubleValue 96 ]; 97 CGFloat newBackingScaleFactor = [self scaleFactor]; 98 if (oldBackingScaleFactor == newBackingScaleFactor) { 99 return; 100 } 101 102 // TODO: need a better way to force a refresh (that works). 103 // [fGLContext update] does not appear to update if the point size has not changed, 104 // even if the backing size has changed. 105 [self setFrameSize:NSMakeSize(self.frame.size.width + 1, self.frame.size.height + 1)]; 106} 107 108- (void)resizeSkView:(NSSize)newSize { 109#if RETINA_API_AVAILABLE 110 newSize = [self convertSizeToBacking:newSize]; 111#endif 112 if (fWind && (fWind->width() != newSize.width || fWind->height() != newSize.height)) { 113 fWind->resize((int) newSize.width, (int) newSize.height); 114 if (fGLContext) { 115 glClear(GL_STENCIL_BUFFER_BIT); 116 [fGLContext update]; 117 } 118 } 119} 120 121- (void) setFrameSize:(NSSize)newSize { 122 [super setFrameSize:newSize]; 123 [self resizeSkView:newSize]; 124} 125 126- (void)dealloc { 127 [self freeNativeWind]; 128 self.fGLContext = nil; 129 self.fTitle = nil; 130 [super dealloc]; 131} 132 133- (void)freeNativeWind { 134 delete fWind; 135 fWind = nil; 136} 137 138//////////////////////////////////////////////////////////////////////////////// 139 140- (void)drawSkia { 141 fRedrawRequestPending = false; 142 if (fWind) { 143 sk_sp<SkSurface> surface(fWind->makeSurface()); 144 fWind->draw(surface->getCanvas()); 145#ifdef FORCE_REDRAW 146 fWind->inval(NULL); 147#endif 148 } 149} 150 151- (void)setSkTitle:(const char *)title { 152 self.fTitle = [NSString stringWithUTF8String:title]; 153 [[self window] setTitle:self.fTitle]; 154} 155 156- (BOOL)onHandleEvent:(const SkEvent&)evt { 157 return false; 158} 159 160#include "SkOSMenu.h" 161- (void)onAddMenu:(const SkOSMenu*)menu { 162 [self.fOptionsDelegate view:self didAddMenu:menu]; 163} 164 165- (void)onUpdateMenu:(const SkOSMenu*)menu { 166 [self.fOptionsDelegate view:self didUpdateMenu:menu]; 167} 168 169- (void)postInvalWithRect:(const SkIRect*)r { 170 if (!fRedrawRequestPending) { 171 fRedrawRequestPending = true; 172 [self setNeedsDisplay:YES]; 173 [self performSelector:@selector(drawSkia) withObject:nil afterDelay:0]; 174 } 175} 176/////////////////////////////////////////////////////////////////////////////// 177 178#include "SkKey.h" 179enum { 180 SK_MacReturnKey = 36, 181 SK_MacDeleteKey = 51, 182 SK_MacEndKey = 119, 183 SK_MacLeftKey = 123, 184 SK_MacRightKey = 124, 185 SK_MacDownKey = 125, 186 SK_MacUpKey = 126, 187 SK_Mac0Key = 0x52, 188 SK_Mac1Key = 0x53, 189 SK_Mac2Key = 0x54, 190 SK_Mac3Key = 0x55, 191 SK_Mac4Key = 0x56, 192 SK_Mac5Key = 0x57, 193 SK_Mac6Key = 0x58, 194 SK_Mac7Key = 0x59, 195 SK_Mac8Key = 0x5b, 196 SK_Mac9Key = 0x5c 197}; 198 199static SkKey raw2key(UInt32 raw) 200{ 201 static const struct { 202 UInt32 fRaw; 203 SkKey fKey; 204 } gKeys[] = { 205 { SK_MacUpKey, kUp_SkKey }, 206 { SK_MacDownKey, kDown_SkKey }, 207 { SK_MacLeftKey, kLeft_SkKey }, 208 { SK_MacRightKey, kRight_SkKey }, 209 { SK_MacReturnKey, kOK_SkKey }, 210 { SK_MacDeleteKey, kBack_SkKey }, 211 { SK_MacEndKey, kEnd_SkKey }, 212 { SK_Mac0Key, k0_SkKey }, 213 { SK_Mac1Key, k1_SkKey }, 214 { SK_Mac2Key, k2_SkKey }, 215 { SK_Mac3Key, k3_SkKey }, 216 { SK_Mac4Key, k4_SkKey }, 217 { SK_Mac5Key, k5_SkKey }, 218 { SK_Mac6Key, k6_SkKey }, 219 { SK_Mac7Key, k7_SkKey }, 220 { SK_Mac8Key, k8_SkKey }, 221 { SK_Mac9Key, k9_SkKey } 222 }; 223 224 for (unsigned i = 0; i < SK_ARRAY_COUNT(gKeys); i++) 225 if (gKeys[i].fRaw == raw) 226 return gKeys[i].fKey; 227 return kNONE_SkKey; 228} 229 230- (void)keyDown:(NSEvent *)event { 231 if (NULL == fWind) 232 return; 233 234 SkKey key = raw2key([event keyCode]); 235 if (kNONE_SkKey != key) 236 fWind->handleKey(key); 237 else{ 238 unichar c = [[event characters] characterAtIndex:0]; 239 fWind->handleChar((SkUnichar)c); 240 } 241} 242 243- (void)keyUp:(NSEvent *)event { 244 if (NULL == fWind) 245 return; 246 247 SkKey key = raw2key([event keyCode]); 248 if (kNONE_SkKey != key) 249 fWind->handleKeyUp(key); 250 // else 251 // unichar c = [[event characters] characterAtIndex:0]; 252} 253 254static const struct { 255 unsigned fNSModifierMask; 256 unsigned fSkModifierMask; 257} gModifierMasks[] = { 258 { NSAlphaShiftKeyMask, kShift_SkModifierKey }, 259 { NSShiftKeyMask, kShift_SkModifierKey }, 260 { NSControlKeyMask, kControl_SkModifierKey }, 261 { NSAlternateKeyMask, kOption_SkModifierKey }, 262 { NSCommandKeyMask, kCommand_SkModifierKey }, 263}; 264 265static unsigned convertNSModifiersToSk(NSUInteger nsModi) { 266 unsigned skModi = 0; 267 for (size_t i = 0; i < SK_ARRAY_COUNT(gModifierMasks); ++i) { 268 if (nsModi & gModifierMasks[i].fNSModifierMask) { 269 skModi |= gModifierMasks[i].fSkModifierMask; 270 } 271 } 272 return skModi; 273} 274 275- (void)mouseDown:(NSEvent *)event { 276 NSPoint p = [event locationInWindow]; 277 unsigned modi = convertNSModifiersToSk([event modifierFlags]); 278 279 if ([self mouse:p inRect:[self bounds]] && fWind) { 280 NSPoint loc = [self convertPoint:p fromView:nil]; 281#if RETINA_API_AVAILABLE 282 loc = [self convertPointToBacking:loc]; //y-up 283 loc.y = -loc.y; 284#endif 285 fWind->handleClick((int) loc.x, (int) loc.y, 286 SkView::Click::kDown_State, self, modi); 287 } 288} 289 290- (void)mouseDragged:(NSEvent *)event { 291 NSPoint p = [event locationInWindow]; 292 unsigned modi = convertNSModifiersToSk([event modifierFlags]); 293 294 if ([self mouse:p inRect:[self bounds]] && fWind) { 295 NSPoint loc = [self convertPoint:p fromView:nil]; 296#if RETINA_API_AVAILABLE 297 loc = [self convertPointToBacking:loc]; //y-up 298 loc.y = -loc.y; 299#endif 300 fWind->handleClick((int) loc.x, (int) loc.y, 301 SkView::Click::kMoved_State, self, modi); 302 } 303} 304 305- (void)mouseMoved:(NSEvent *)event { 306 NSPoint p = [event locationInWindow]; 307 unsigned modi = convertNSModifiersToSk([event modifierFlags]); 308 309 if ([self mouse:p inRect:[self bounds]] && fWind) { 310 NSPoint loc = [self convertPoint:p fromView:nil]; 311#if RETINA_API_AVAILABLE 312 loc = [self convertPointToBacking:loc]; //y-up 313 loc.y = -loc.y; 314#endif 315 fWind->handleClick((int) loc.x, (int) loc.y, 316 SkView::Click::kMoved_State, self, modi); 317 } 318} 319 320- (void)mouseUp:(NSEvent *)event { 321 NSPoint p = [event locationInWindow]; 322 unsigned modi = convertNSModifiersToSk([event modifierFlags]); 323 324 if ([self mouse:p inRect:[self bounds]] && fWind) { 325 NSPoint loc = [self convertPoint:p fromView:nil]; 326#if RETINA_API_AVAILABLE 327 loc = [self convertPointToBacking:loc]; //y-up 328 loc.y = -loc.y; 329#endif 330 fWind->handleClick((int) loc.x, (int) loc.y, 331 SkView::Click::kUp_State, self, modi); 332 } 333} 334 335/////////////////////////////////////////////////////////////////////////////// 336#include <OpenGL/OpenGL.h> 337 338static CGLContextObj createGLContext(int msaaSampleCount) { 339 GLint major, minor; 340 CGLGetVersion(&major, &minor); 341 342 static const CGLPixelFormatAttribute attributes[] = { 343 kCGLPFAStencilSize, (CGLPixelFormatAttribute) 8, 344 kCGLPFAAccelerated, 345 kCGLPFADoubleBuffer, 346 kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute) kCGLOGLPVersion_3_2_Core, 347 (CGLPixelFormatAttribute)0 348 }; 349 350 CGLPixelFormatObj format; 351 GLint npix = 0; 352 if (msaaSampleCount > 0) { 353 static const int kAttributeCount = SK_ARRAY_COUNT(attributes); 354 CGLPixelFormatAttribute msaaAttributes[kAttributeCount + 5]; 355 memcpy(msaaAttributes, attributes, sizeof(attributes)); 356 SkASSERT(0 == msaaAttributes[kAttributeCount - 1]); 357 msaaAttributes[kAttributeCount - 1] = kCGLPFASampleBuffers; 358 msaaAttributes[kAttributeCount + 0] = (CGLPixelFormatAttribute)1; 359 msaaAttributes[kAttributeCount + 1] = kCGLPFAMultisample; 360 msaaAttributes[kAttributeCount + 2] = kCGLPFASamples; 361 msaaAttributes[kAttributeCount + 3] = 362 (CGLPixelFormatAttribute)msaaSampleCount; 363 msaaAttributes[kAttributeCount + 4] = (CGLPixelFormatAttribute)0; 364 CGLChoosePixelFormat(msaaAttributes, &format, &npix); 365 } 366 if (!npix) { 367 CGLChoosePixelFormat(attributes, &format, &npix); 368 } 369 CGLContextObj ctx; 370 CGLCreateContext(format, NULL, &ctx); 371 CGLDestroyPixelFormat(format); 372 373 static const GLint interval = 1; 374 CGLSetParameter(ctx, kCGLCPSwapInterval, &interval); 375 CGLSetCurrentContext(ctx); 376 return ctx; 377} 378 379- (void)viewDidMoveToWindow { 380 [super viewDidMoveToWindow]; 381 382 //Attaching view to fGLContext requires that the view to be part of a window, 383 //and that the NSWindow instance must have a CoreGraphics counterpart (or 384 //it must NOT be deferred or should have been on screen at least once) 385 if ([fGLContext view] != self && nil != self.window) { 386 [fGLContext setView:self]; 387 } 388} 389- (bool)attach:(SkOSWindow::SkBackEndTypes)attachType 390 withMSAASampleCount:(int) sampleCount 391 andGetInfo:(SkOSWindow::AttachmentInfo*) info { 392 if (nil == fGLContext) { 393 CGLContextObj ctx = createGLContext(sampleCount); 394 SkASSERT(ctx); 395 fGLContext = [[NSOpenGLContext alloc] initWithCGLContextObj:ctx]; 396 CGLReleaseContext(ctx); 397 if (NULL == fGLContext) { 398 return false; 399 } 400 [fGLContext setView:self]; 401 } 402 403 [fGLContext makeCurrentContext]; 404 CGLPixelFormatObj format = CGLGetPixelFormat((CGLContextObj)[fGLContext CGLContextObj]); 405 CGLDescribePixelFormat(format, 0, kCGLPFASamples, &info->fSampleCount); 406 CGLDescribePixelFormat(format, 0, kCGLPFAStencilSize, &info->fStencilBits); 407 NSSize size = self.bounds.size; 408#if RETINA_API_AVAILABLE 409 size = [self convertSizeToBacking:size]; 410#endif 411 glViewport(0, 0, (int) size.width, (int) size.height); 412 glClearColor(0, 0, 0, 0); 413 glClearStencil(0); 414 glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); 415 return true; 416} 417 418- (void)detach { 419 [fGLContext release]; 420 fGLContext = nil; 421} 422 423- (void)present { 424 if (nil != fGLContext) { 425 [fGLContext flushBuffer]; 426 } 427} 428 429- (void)setVSync:(bool)enable { 430 if (fGLContext) { 431 GLint interval = enable ? 1 : 0; 432 CGLContextObj ctx = (CGLContextObj)[fGLContext CGLContextObj]; 433 CGLSetParameter(ctx, kCGLCPSwapInterval, &interval); 434 } 435} 436@end 437