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