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/WindowContextFactory_mac.h" 12#include "tools/sk_app/mac/Window_mac.h" 13#include "tools/skui/ModifierKey.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 window_context_factory::MacWindowInfo info; 123 info.fMainView = [fWindow contentView]; 124 switch (attachType) { 125#ifdef SK_DAWN 126 case kDawn_BackendType: 127 fWindowContext = MakeDawnMTLForMac(info, fRequestedDisplayParams); 128 break; 129#endif 130#ifdef SK_VULKAN 131 case kVulkan_BackendType: 132 fWindowContext = MakeVulkanForMac(info, fRequestedDisplayParams); 133 break; 134#endif 135#ifdef SK_METAL 136 case kMetal_BackendType: 137 fWindowContext = MakeMetalForMac(info, fRequestedDisplayParams); 138 break; 139#ifdef SK_GRAPHITE_ENABLED 140 case kGraphiteMetal_BackendType: 141 fWindowContext = MakeGraphiteMetalForMac(info, fRequestedDisplayParams); 142 break; 143#endif 144#endif 145#ifdef SK_GL 146 case kNativeGL_BackendType: 147 fWindowContext = MakeGLForMac(info, fRequestedDisplayParams); 148 break; 149 case kRaster_BackendType: 150 fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams); 151 break; 152#endif 153 default: 154 SkASSERT_RELEASE(false); 155 } 156 this->onBackendCreated(); 157 158 return SkToBool(fWindowContext); 159} 160 161float Window_mac::scaleFactor() const { 162 return sk_app::GetBackingScaleFactor(fWindow.contentView); 163} 164 165void Window_mac::PaintWindows() { 166 gWindowMap.foreach([&](Window_mac* window) { 167 if (window->fIsContentInvalidated) { 168 window->onPaint(); 169 } 170 }); 171} 172 173} // namespace sk_app 174 175/////////////////////////////////////////////////////////////////////////////// 176 177@implementation WindowDelegate { 178 sk_app::Window_mac* fWindow; 179} 180 181- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow { 182 fWindow = initWindow; 183 184 return self; 185} 186 187- (void)windowDidResize:(NSNotification *)notification { 188 NSView* view = fWindow->window().contentView; 189 CGFloat scale = sk_app::GetBackingScaleFactor(view); 190 fWindow->onResize(view.bounds.size.width * scale, view.bounds.size.height * scale); 191 fWindow->inval(); 192} 193 194- (BOOL)windowShouldClose:(NSWindow*)sender { 195 fWindow->closeWindow(); 196 197 return FALSE; 198} 199 200@end 201 202/////////////////////////////////////////////////////////////////////////////// 203 204static skui::Key get_key(unsigned short vk) { 205 // This will work with an ANSI QWERTY keyboard. 206 // Something more robust would be needed to support alternate keyboards. 207 static const struct { 208 unsigned short fVK; 209 skui::Key fKey; 210 } gPair[] = { 211 { kVK_Delete, skui::Key::kBack }, 212 { kVK_Return, skui::Key::kOK }, 213 { kVK_UpArrow, skui::Key::kUp }, 214 { kVK_DownArrow, skui::Key::kDown }, 215 { kVK_LeftArrow, skui::Key::kLeft }, 216 { kVK_RightArrow, skui::Key::kRight }, 217 { kVK_Tab, skui::Key::kTab }, 218 { kVK_PageUp, skui::Key::kPageUp }, 219 { kVK_PageDown, skui::Key::kPageDown }, 220 { kVK_Home, skui::Key::kHome }, 221 { kVK_End, skui::Key::kEnd }, 222 { kVK_ForwardDelete, skui::Key::kDelete }, 223 { kVK_Escape, skui::Key::kEscape }, 224 { kVK_Shift, skui::Key::kShift }, 225 { kVK_RightShift, skui::Key::kShift }, 226 { kVK_Control, skui::Key::kCtrl }, 227 { kVK_RightControl, skui::Key::kCtrl }, 228 { kVK_Option, skui::Key::kOption }, 229 { kVK_RightOption, skui::Key::kOption }, 230 { kVK_Command, skui::Key::kSuper }, 231 { kVK_RightCommand, skui::Key::kSuper }, 232 { kVK_ANSI_A, skui::Key::kA }, 233 { kVK_ANSI_C, skui::Key::kC }, 234 { kVK_ANSI_V, skui::Key::kV }, 235 { kVK_ANSI_X, skui::Key::kX }, 236 { kVK_ANSI_Y, skui::Key::kY }, 237 { kVK_ANSI_Z, skui::Key::kZ }, 238 }; 239 240 for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { 241 if (gPair[i].fVK == vk) { 242 return gPair[i].fKey; 243 } 244 } 245 246 return skui::Key::kNONE; 247} 248 249static skui::ModifierKey get_modifiers(const NSEvent* event) { 250 NSUInteger modifierFlags = [event modifierFlags]; 251 skui::ModifierKey modifiers = skui::ModifierKey::kNone; 252 253 if (modifierFlags & NSEventModifierFlagCommand) { 254 modifiers |= skui::ModifierKey::kCommand; 255 } 256 if (modifierFlags & NSEventModifierFlagShift) { 257 modifiers |= skui::ModifierKey::kShift; 258 } 259 if (modifierFlags & NSEventModifierFlagControl) { 260 modifiers |= skui::ModifierKey::kControl; 261 } 262 if (modifierFlags & NSEventModifierFlagOption) { 263 modifiers |= skui::ModifierKey::kOption; 264 } 265 266 if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) { 267 modifiers |= skui::ModifierKey::kFirstPress; 268 } 269 270 return modifiers; 271} 272 273@implementation MainView { 274 sk_app::Window_mac* fWindow; 275 // A TrackingArea prevents us from capturing events outside the view 276 NSTrackingArea* fTrackingArea; 277 // We keep track of the state of the modifier keys on each event in order to synthesize 278 // key-up/down events for each modifier. 279 skui::ModifierKey fLastModifiers; 280} 281 282- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow { 283 self = [super init]; 284 285 fWindow = initWindow; 286 fTrackingArea = nil; 287 288 [self updateTrackingAreas]; 289 290 return self; 291} 292 293- (void)dealloc 294{ 295 [fTrackingArea release]; 296 [super dealloc]; 297} 298 299- (BOOL)isOpaque { 300 return YES; 301} 302 303- (BOOL)canBecomeKeyView { 304 return YES; 305} 306 307- (BOOL)acceptsFirstResponder { 308 return YES; 309} 310 311- (void)updateTrackingAreas { 312 if (fTrackingArea != nil) { 313 [self removeTrackingArea:fTrackingArea]; 314 [fTrackingArea release]; 315 } 316 317 const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | 318 NSTrackingActiveInKeyWindow | 319 NSTrackingEnabledDuringMouseDrag | 320 NSTrackingCursorUpdate | 321 NSTrackingInVisibleRect | 322 NSTrackingAssumeInside; 323 324 fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] 325 options:options 326 owner:self 327 userInfo:nil]; 328 329 [self addTrackingArea:fTrackingArea]; 330 [super updateTrackingAreas]; 331} 332 333- (skui::ModifierKey) updateModifierKeys:(NSEvent*) event { 334 using sknonstd::Any; 335 336 skui::ModifierKey modifiers = get_modifiers(event); 337 skui::ModifierKey changed = modifiers ^ fLastModifiers; 338 fLastModifiers = modifiers; 339 340 struct ModMap { 341 skui::ModifierKey modifier; 342 skui::Key key; 343 }; 344 345 // Map each modifier bit to the equivalent skui Key and send key-up/down events. 346 for (const ModMap& cur : {ModMap{skui::ModifierKey::kCommand, skui::Key::kSuper}, 347 ModMap{skui::ModifierKey::kShift, skui::Key::kShift}, 348 ModMap{skui::ModifierKey::kControl, skui::Key::kCtrl}, 349 ModMap{skui::ModifierKey::kOption, skui::Key::kOption}}) { 350 if (Any(changed & cur.modifier)) { 351 const skui::InputState state = Any(modifiers & cur.modifier) ? skui::InputState::kDown 352 : skui::InputState::kUp; 353 (void) fWindow->onKey(cur.key, state, modifiers); 354 } 355 } 356 357 return modifiers; 358} 359 360- (BOOL)performKeyEquivalent:(NSEvent *)event { 361 [self updateModifierKeys:event]; 362 363 // By default, unhandled key equivalents send -keyDown events; unfortunately, they do not send 364 // a matching -keyUp. In other words, we can claim that we didn't handle the event and OS X will 365 // turn this event into a -keyDown automatically, but we need to synthesize a matching -keyUp on 366 // a later frame. Since we only read the modifiers and key code from the event, we can reuse 367 // this "key-equivalent" event as a "key up". 368 [self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1]; 369 return NO; 370} 371 372- (void)keyDown:(NSEvent *)event { 373 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 374 375 skui::Key key = get_key([event keyCode]); 376 if (key != skui::Key::kNONE) { 377 if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) { 378 if (skui::Key::kEscape == key) { 379 [NSApp terminate:fWindow->window()]; 380 } 381 } 382 } 383 384 NSString* characters = [event charactersIgnoringModifiers]; 385 NSUInteger len = [characters length]; 386 if (len > 0) { 387 unichar* charBuffer = new unichar[len+1]; 388 [characters getCharacters:charBuffer range:NSMakeRange(0, len)]; 389 for (NSUInteger i = 0; i < len; ++i) { 390 (void) fWindow->onChar((SkUnichar) charBuffer[i], modifiers); 391 } 392 delete [] charBuffer; 393 } 394} 395 396- (void)keyUp:(NSEvent *)event { 397 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 398 399 skui::Key key = get_key([event keyCode]); 400 if (key != skui::Key::kNONE) { 401 (void) fWindow->onKey(key, skui::InputState::kUp, modifiers); 402 } 403} 404 405-(void)flagsChanged:(NSEvent *)event { 406 [self updateModifierKeys:event]; 407} 408 409- (void)mouseDown:(NSEvent *)event { 410 NSView* view = fWindow->window().contentView; 411 CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view); 412 413 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 414 415 const NSPoint pos = [event locationInWindow]; 416 const NSRect rect = [view frame]; 417 fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 418 skui::InputState::kDown, modifiers); 419} 420 421- (void)mouseUp:(NSEvent *)event { 422 NSView* view = fWindow->window().contentView; 423 CGFloat backingScaleFactor = sk_app::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::kUp, modifiers); 431} 432 433- (void)mouseDragged:(NSEvent *)event { 434 [self updateModifierKeys:event]; 435 [self mouseMoved:event]; 436} 437 438- (void)mouseMoved:(NSEvent *)event { 439 NSView* view = fWindow->window().contentView; 440 CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view); 441 442 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 443 444 const NSPoint pos = [event locationInWindow]; 445 const NSRect rect = [view frame]; 446 fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 447 skui::InputState::kMove, modifiers); 448} 449 450- (void)scrollWheel:(NSEvent *)event { 451 skui::ModifierKey modifiers = [self updateModifierKeys:event]; 452 453 // TODO: support hasPreciseScrollingDeltas? 454 fWindow->onMouseWheel([event scrollingDeltaY], modifiers); 455} 456 457- (void)drawRect:(NSRect)rect { 458 fWindow->onPaint(); 459} 460 461@end 462