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