1/* 2* Copyright 2019 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#include <Carbon/Carbon.h> 9 10#include "include/core/SkTypes.h" 11#include "tools/sk_app/mac/Window_mac.h" 12#include "tools/skui/ModifierKey.h" 13#include "tools/window/DisplayParams.h" 14#include "tools/window/WindowContext.h" 15#include "tools/window/mac/MacWindowInfo.h" 16 17#if defined(SK_GANESH) && defined(SK_ANGLE) 18#include "tools/window/mac/GaneshANGLEWindowContext_mac.h" 19#endif 20 21#if defined(SK_GANESH) && defined(SK_GL) 22#include "tools/window/mac/GaneshGLWindowContext_mac.h" 23#include "tools/window/mac/RasterWindowContext_mac.h" 24#endif 25 26#if defined(SK_GANESH) && defined(SK_METAL) 27#include "tools/window/mac/GaneshMetalWindowContext_mac.h" 28#endif 29 30#if defined(SK_GRAPHITE) && defined(SK_METAL) 31#include "tools/window/mac/GraphiteNativeMetalWindowContext_mac.h" 32#endif 33 34#if defined(SK_GRAPHITE) && defined(SK_DAWN) 35#include "tools/window/mac/GraphiteDawnMetalWindowContext_mac.h" 36#endif 37 38@interface WindowDelegate : NSObject<NSWindowDelegate> 39 40- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow; 41 42@end 43 44@interface MainView : NSView 45 46- (MainView*)initWithWindow:(sk_app::Window_mac*)initWindow; 47 48@end 49 50/////////////////////////////////////////////////////////////////////////////// 51 52using sk_app::Window; 53using skwindow::DisplayParams; 54 55namespace sk_app { 56 57SkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap; 58 59Window* Windows::CreateNativeWindow(void*) { 60 Window_mac* window = new Window_mac(); 61 if (!window->initWindow()) { 62 delete window; 63 return nullptr; 64 } 65 66 return window; 67} 68 69bool Window_mac::initWindow() { 70 // we already have a window 71 if (fWindow) { 72 return true; 73 } 74 75 // Create a delegate to track certain events 76 WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this]; 77 if (nil == delegate) { 78 return false; 79 } 80 81 // Create Cocoa window 82 constexpr int initialWidth = 1280; 83 constexpr int initialHeight = 960; 84 NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight); 85 86 NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | 87 NSMiniaturizableWindowMask); 88 89 fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle 90 backing:NSBackingStoreBuffered defer:NO]; 91 if (nil == fWindow) { 92 [delegate release]; 93 return false; 94 } 95 96 // create view 97 MainView* view = [[MainView alloc] initWithWindow:this]; 98 if (nil == view) { 99 [fWindow release]; 100 [delegate release]; 101 return false; 102 } 103 104 [fWindow setContentView:view]; 105 [fWindow makeFirstResponder:view]; 106 [fWindow setDelegate:delegate]; 107 [fWindow setAcceptsMouseMovedEvents:YES]; 108 [fWindow setRestorable:NO]; 109 110 // Should be retained by window now 111 [view release]; 112 113 fWindowNumber = fWindow.windowNumber; 114 gWindowMap.add(this); 115 116 return true; 117} 118 119void Window_mac::closeWindow() { 120 if (nil != fWindow) { 121 gWindowMap.remove(fWindowNumber); 122 if (sk_app::Window_mac::gWindowMap.count() < 1) { 123 [NSApp terminate:fWindow]; 124 } 125 [fWindow close]; 126 fWindow = nil; 127 } 128} 129 130void Window_mac::setTitle(const char* title) { 131 if (NSString* titleStr = [NSString stringWithUTF8String:title]) { 132 [fWindow setTitle:titleStr]; 133 } 134} 135 136void Window_mac::show() { 137 [fWindow orderFront:nil]; 138 139 [NSApp activateIgnoringOtherApps:YES]; 140 [fWindow makeKeyAndOrderFront:NSApp]; 141} 142 143bool Window_mac::attach(BackendType attachType) { 144 this->initWindow(); 145 146 skwindow::MacWindowInfo info; 147 info.fMainView = [fWindow contentView]; 148 switch (attachType) { 149#if defined(SK_GANESH) && defined(SK_ANGLE) 150 case kANGLE_BackendType: 151 fWindowContext = 152 skwindow::MakeGaneshANGLEForMac(info, fRequestedDisplayParams->clone()); 153 break; 154#endif 155#if defined(SK_GRAPHITE) && defined(SK_DAWN) 156 case kGraphiteDawn_BackendType: 157 fWindowContext = MakeGraphiteDawnMetalForMac(info, fRequestedDisplayParams->clone()); 158 break; 159#endif 160#if defined(SK_GANESH) && defined(SK_METAL) 161 case kMetal_BackendType: 162 fWindowContext = MakeGaneshMetalForMac(info, fRequestedDisplayParams->clone()); 163 break; 164#endif 165#if defined(SK_GRAPHITE) && defined(SK_METAL) 166 case kGraphiteMetal_BackendType: 167 fWindowContext = MakeGraphiteNativeMetalForMac(info, fRequestedDisplayParams->clone()); 168 break; 169#endif 170#if defined(SK_GANESH) && defined(SK_GL) 171 case kNativeGL_BackendType: 172 fWindowContext = MakeGaneshGLForMac(info, fRequestedDisplayParams->clone()); 173 break; 174 case kRaster_BackendType: 175 // The Raster IMPL requires GL 176 fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams->clone()); 177 break; 178#endif 179 default: 180 SK_ABORT("Unknown backend"); 181 } 182 this->onBackendCreated(); 183 184 return SkToBool(fWindowContext); 185} 186 187float Window_mac::scaleFactor() const { 188 return skwindow::GetBackingScaleFactor(fWindow.contentView); 189} 190 191void Window_mac::PaintWindows() { 192 gWindowMap.foreach([&](Window_mac* window) { 193 if (window->fIsContentInvalidated) { 194 window->onPaint(); 195 } 196 }); 197} 198 199} // namespace sk_app 200 201/////////////////////////////////////////////////////////////////////////////// 202 203@implementation WindowDelegate { 204 sk_app::Window_mac* fWindow; 205} 206 207- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow { 208 fWindow = initWindow; 209 210 return self; 211} 212 213- (void)windowDidResize:(NSNotification *)notification { 214 NSView* view = fWindow->window().contentView; 215 CGFloat scale = skwindow::GetBackingScaleFactor(view); 216 fWindow->onResize(view.bounds.size.width * scale, view.bounds.size.height * scale); 217 fWindow->inval(); 218} 219 220- (void)windowDidChangeScreen:(NSNotification *)notification { 221 NSView* view = fWindow->window().contentView; 222 CGFloat scale = skwindow::GetBackingScaleFactor(view); 223 fWindow->onResize(view.bounds.size.width * scale, view.bounds.size.height * scale); 224 fWindow->inval(); 225} 226 227- (BOOL)windowShouldClose:(NSWindow*)sender { 228 fWindow->closeWindow(); 229 230 return FALSE; 231} 232 233@end 234 235/////////////////////////////////////////////////////////////////////////////// 236 237static skui::Key get_key(unsigned short vk) { 238 // This will work with an ANSI QWERTY keyboard. 239 // Something more robust would be needed to support alternate keyboards. 240 static const struct { 241 unsigned short fVK; 242 skui::Key fKey; 243 } gPair[] = { 244 { kVK_Delete, skui::Key::kBack }, 245 { kVK_Return, skui::Key::kOK }, 246 { kVK_UpArrow, skui::Key::kUp }, 247 { kVK_DownArrow, skui::Key::kDown }, 248 { kVK_LeftArrow, skui::Key::kLeft }, 249 { kVK_RightArrow, skui::Key::kRight }, 250 { kVK_Tab, skui::Key::kTab }, 251 { kVK_PageUp, skui::Key::kPageUp }, 252 { kVK_PageDown, skui::Key::kPageDown }, 253 { kVK_Home, skui::Key::kHome }, 254 { kVK_End, skui::Key::kEnd }, 255 { kVK_ForwardDelete, skui::Key::kDelete }, 256 { kVK_Escape, skui::Key::kEscape }, 257 { kVK_Shift, skui::Key::kShift }, 258 { kVK_RightShift, skui::Key::kShift }, 259 { kVK_Control, skui::Key::kCtrl }, 260 { kVK_RightControl, skui::Key::kCtrl }, 261 { kVK_Option, skui::Key::kOption }, 262 { kVK_RightOption, skui::Key::kOption }, 263 { kVK_Command, skui::Key::kSuper }, 264 { kVK_RightCommand, skui::Key::kSuper }, 265 { kVK_ANSI_A, skui::Key::kA }, 266 { kVK_ANSI_C, skui::Key::kC }, 267 { kVK_ANSI_V, skui::Key::kV }, 268 { kVK_ANSI_X, skui::Key::kX }, 269 { kVK_ANSI_Y, skui::Key::kY }, 270 { kVK_ANSI_Z, skui::Key::kZ }, 271 }; 272 273 for (size_t i = 0; i < std::size(gPair); i++) { 274 if (gPair[i].fVK == vk) { 275 return gPair[i].fKey; 276 } 277 } 278 279 return skui::Key::kNONE; 280} 281 282static skui::ModifierKey get_modifiers(const NSEvent* event) { 283 NSUInteger modifierFlags = [event modifierFlags]; 284 skui::ModifierKey modifiers = skui::ModifierKey::kNone; 285 286 if (modifierFlags & NSEventModifierFlagCommand) { 287 modifiers |= skui::ModifierKey::kCommand; 288 } 289 if (modifierFlags & NSEventModifierFlagShift) { 290 modifiers |= skui::ModifierKey::kShift; 291 } 292 if (modifierFlags & NSEventModifierFlagControl) { 293 modifiers |= skui::ModifierKey::kControl; 294 } 295 if (modifierFlags & NSEventModifierFlagOption) { 296 modifiers |= skui::ModifierKey::kOption; 297 } 298 299 if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) { 300 modifiers |= skui::ModifierKey::kFirstPress; 301 } 302 303 return modifiers; 304} 305 306@implementation MainView { 307 sk_app::Window_mac* fWindow; 308 // A TrackingArea prevents us from capturing events outside the view 309 NSTrackingArea* fTrackingArea; 310 // We keep track of the state of the modifier keys on each event in order to synthesize 311 // key-up/down events for each modifier. 312 skui::ModifierKey fLastModifiers; 313} 314 315- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow { 316 self = [super init]; 317 318 fWindow = initWindow; 319 fTrackingArea = nil; 320 321 [self updateTrackingAreas]; 322 323 return self; 324} 325 326- (void)dealloc 327{ 328 [fTrackingArea release]; 329 [super dealloc]; 330} 331 332- (BOOL)isOpaque { 333 return YES; 334} 335 336- (BOOL)canBecomeKeyView { 337 return YES; 338} 339 340- (BOOL)acceptsFirstResponder { 341 return YES; 342} 343 344- (void)updateTrackingAreas { 345 if (fTrackingArea != nil) { 346 [self removeTrackingArea:fTrackingArea]; 347 [fTrackingArea release]; 348 } 349 350 const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | 351 NSTrackingActiveInKeyWindow | 352 NSTrackingEnabledDuringMouseDrag | 353 NSTrackingCursorUpdate | 354 NSTrackingInVisibleRect | 355 NSTrackingAssumeInside; 356 357 fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] 358 options:options 359 owner:self 360 userInfo:nil]; 361 362 [self addTrackingArea:fTrackingArea]; 363 [super updateTrackingAreas]; 364} 365 366- (skui::ModifierKey) updateModifierKeys:(NSEvent*) event { 367 using sknonstd::Any; 368 369 skui::ModifierKey modifiers = get_modifiers(event); 370 skui::ModifierKey changed = modifiers ^ fLastModifiers; 371 fLastModifiers = modifiers; 372 373 struct ModMap { 374 skui::ModifierKey modifier; 375 skui::Key key; 376 }; 377 378 // Map each modifier bit to the equivalent skui Key and send key-up/down events. 379 for (const ModMap& cur : {ModMap{skui::ModifierKey::kCommand, skui::Key::kSuper}, 380 ModMap{skui::ModifierKey::kShift, skui::Key::kShift}, 381 ModMap{skui::ModifierKey::kControl, skui::Key::kCtrl}, 382 ModMap{skui::ModifierKey::kOption, skui::Key::kOption}}) { 383 if (Any(changed & cur.modifier)) { 384 const skui::InputState state = Any(modifiers & cur.modifier) ? skui::InputState::kDown 385 : skui::InputState::kUp; 386 (void) fWindow->onKey(cur.key, state, modifiers); 387 } 388 } 389 390 return modifiers; 391} 392 393- (BOOL)performKeyEquivalent:(NSEvent *)event { 394 [self updateModifierKeys:event]; 395 396 // By default, unhandled key equivalents send -keyDown events; unfortunately, they do not send 397 // a matching -keyUp. In other words, we can claim that we didn't handle the event and OS X will 398 // turn this event into a -keyDown automatically, but we need to synthesize a matching -keyUp on 399 // a later frame. Since we only read the modifiers and key code from the event, we can reuse 400 // this "key-equivalent" event as a "key up". 401 [self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1]; 402 return NO; 403} 404 405- (void)keyDown:(NSEvent *)event { 406 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 407 408 skui::Key key = get_key([event keyCode]); 409 if (key != skui::Key::kNONE) { 410 if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) { 411 if (skui::Key::kEscape == key) { 412 [NSApp terminate:fWindow->window()]; 413 } 414 } 415 } 416 417 NSString* characters = [event charactersIgnoringModifiers]; 418 NSUInteger len = [characters length]; 419 if (len > 0) { 420 unichar* charBuffer = new unichar[len+1]; 421 [characters getCharacters:charBuffer range:NSMakeRange(0, len)]; 422 for (NSUInteger i = 0; i < len; ++i) { 423 (void) fWindow->onChar((SkUnichar) charBuffer[i], modifiers); 424 } 425 delete [] charBuffer; 426 } 427} 428 429- (void)keyUp:(NSEvent *)event { 430 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 431 432 skui::Key key = get_key([event keyCode]); 433 if (key != skui::Key::kNONE) { 434 (void) fWindow->onKey(key, skui::InputState::kUp, modifiers); 435 } 436} 437 438-(void)flagsChanged:(NSEvent *)event { 439 [self updateModifierKeys:event]; 440} 441 442- (void)mouseDown:(NSEvent *)event { 443 NSView* view = fWindow->window().contentView; 444 CGFloat backingScaleFactor = skwindow::GetBackingScaleFactor(view); 445 446 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 447 448 const NSPoint pos = [event locationInWindow]; 449 const NSRect rect = [view frame]; 450 fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 451 skui::InputState::kDown, modifiers); 452} 453 454- (void)mouseUp:(NSEvent *)event { 455 NSView* view = fWindow->window().contentView; 456 CGFloat backingScaleFactor = skwindow::GetBackingScaleFactor(view); 457 458 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 459 460 const NSPoint pos = [event locationInWindow]; 461 const NSRect rect = [view frame]; 462 fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 463 skui::InputState::kUp, modifiers); 464} 465 466- (void)mouseDragged:(NSEvent *)event { 467 [self updateModifierKeys:event]; 468 [self mouseMoved:event]; 469} 470 471- (void)mouseMoved:(NSEvent *)event { 472 NSView* view = fWindow->window().contentView; 473 CGFloat backingScaleFactor = skwindow::GetBackingScaleFactor(view); 474 475 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 476 477 const NSPoint pos = [event locationInWindow]; 478 const NSRect rect = [view frame]; 479 fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 480 skui::InputState::kMove, modifiers); 481} 482 483- (void)scrollWheel:(NSEvent *)event { 484 NSView* view = fWindow->window().contentView; 485 CGFloat backingScaleFactor = skwindow::GetBackingScaleFactor(view); 486 487 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 488 489 // TODO: support hasPreciseScrollingDeltas? 490 const NSPoint pos = [event locationInWindow]; 491 const NSRect rect = [view frame]; 492 fWindow->onMouseWheel([event scrollingDeltaY], 493 pos.x * backingScaleFactor, 494 (rect.size.height - pos.y) * backingScaleFactor, 495 modifiers); 496} 497 498- (void)drawRect:(NSRect)rect { 499 fWindow->onPaint(); 500} 501 502@end 503