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