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