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 "src/core/SkUtils.h" 9#include "tools/ModifierKey.h" 10#include "tools/sk_app/mac/WindowContextFactory_mac.h" 11#include "tools/sk_app/mac/Window_mac.h" 12 13@interface WindowDelegate : NSObject<NSWindowDelegate> 14 15- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow; 16 17@end 18 19@interface MainView : NSView 20 21- (MainView*)initWithWindow:(sk_app::Window_mac*)initWindow; 22 23@end 24 25/////////////////////////////////////////////////////////////////////////////// 26 27using sk_app::Window; 28 29namespace sk_app { 30 31SkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap; 32 33Window* Window::CreateNativeWindow(void*) { 34 Window_mac* window = new Window_mac(); 35 if (!window->initWindow()) { 36 delete window; 37 return nullptr; 38 } 39 40 return window; 41} 42 43bool Window_mac::initWindow() { 44 // we already have a window 45 if (fWindow) { 46 return true; 47 } 48 49 // Create a delegate to track certain events 50 WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this]; 51 if (nil == delegate) { 52 return false; 53 } 54 55 // Create Cocoa window 56 constexpr int initialWidth = 1280; 57 constexpr int initialHeight = 960; 58 NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight); 59 60 NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | 61 NSMiniaturizableWindowMask); 62 63 fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle 64 backing:NSBackingStoreBuffered defer:NO]; 65 if (nil == fWindow) { 66 [delegate release]; 67 return false; 68 } 69 70 // create view 71 MainView* view = [[MainView alloc] initWithWindow:this] ; 72 if (nil == view) { 73 [fWindow release]; 74 [delegate release]; 75 return false; 76 } 77 78 [fWindow setContentView:view]; 79 [fWindow makeFirstResponder:view]; 80 [fWindow setDelegate:delegate]; 81 [fWindow setAcceptsMouseMovedEvents:YES]; 82 [fWindow setRestorable:NO]; 83 84 // Should be retained by window now 85 [view release]; 86 87 fWindowNumber = fWindow.windowNumber; 88 gWindowMap.add(this); 89 90 return true; 91} 92 93void Window_mac::closeWindow() { 94 if (nil != fWindow) { 95 gWindowMap.remove(fWindowNumber); 96 if (sk_app::Window_mac::gWindowMap.count() < 1) { 97 [NSApp terminate:fWindow]; 98 } 99 [fWindow close]; 100 fWindow = nil; 101 } 102} 103 104void Window_mac::setTitle(const char* title) { 105 NSString *titleString = [NSString stringWithCString:title encoding:NSUTF8StringEncoding]; 106 [fWindow setTitle:titleString]; 107} 108 109void Window_mac::show() { 110 [fWindow orderFront:nil]; 111 112 [NSApp activateIgnoringOtherApps:YES]; 113 [fWindow makeKeyAndOrderFront:NSApp]; 114} 115 116bool Window_mac::attach(BackendType attachType) { 117 this->initWindow(); 118 119 window_context_factory::MacWindowInfo info; 120 info.fMainView = [fWindow contentView]; 121 switch (attachType) { 122 case kRaster_BackendType: 123 fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams); 124 break; 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 case kNativeGL_BackendType: 141 default: 142 fWindowContext = MakeGLForMac(info, fRequestedDisplayParams); 143 break; 144 } 145 this->onBackendCreated(); 146 147 return (SkToBool(fWindowContext)); 148} 149 150void Window_mac::PaintWindows() { 151 SkTDynamicHash<Window_mac, NSInteger>::Iter iter(&gWindowMap); 152 while (!iter.done()) { 153 if ((*iter).fIsContentInvalidated) { 154 (*iter).onPaint(); 155 } 156 ++iter; 157 } 158} 159 160} // namespace sk_app 161 162/////////////////////////////////////////////////////////////////////////////// 163 164@implementation WindowDelegate { 165 sk_app::Window_mac* fWindow; 166} 167 168- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow { 169 fWindow = initWindow; 170 171 return self; 172} 173 174- (void)windowDidResize:(NSNotification *)notification { 175 const NSRect mainRect = [fWindow->window().contentView bounds]; 176 177 fWindow->onResize(mainRect.size.width, mainRect.size.height); 178 fWindow->inval(); 179} 180 181- (BOOL)windowShouldClose:(NSWindow*)sender { 182 fWindow->closeWindow(); 183 184 return FALSE; 185} 186 187@end 188 189/////////////////////////////////////////////////////////////////////////////// 190 191static Window::Key get_key(unsigned short vk) { 192 // This will work with an ANSI QWERTY keyboard. 193 // Something more robust would be needed to support alternate keyboards. 194 static const struct { 195 unsigned short fVK; 196 Window::Key fKey; 197 } gPair[] = { 198 { 0x33, Window::Key::kBack }, 199 { 0x24, Window::Key::kOK }, 200 { 0x7E, Window::Key::kUp }, 201 { 0x7D, Window::Key::kDown }, 202 { 0x7B, Window::Key::kLeft }, 203 { 0x7C, Window::Key::kRight }, 204 { 0x30, Window::Key::kTab }, 205 { 0x74, Window::Key::kPageUp }, 206 { 0x79, Window::Key::kPageDown }, 207 { 0x73, Window::Key::kHome }, 208 { 0x77, Window::Key::kEnd }, 209 { 0x75, Window::Key::kDelete }, 210 { 0x35, Window::Key::kEscape }, 211 { 0x38, Window::Key::kShift }, 212 { 0x3C, Window::Key::kShift }, 213 { 0x3B, Window::Key::kCtrl }, 214 { 0x3E, Window::Key::kCtrl }, 215 { 0x3A, Window::Key::kOption }, 216 { 0x3D, Window::Key::kOption }, 217 { 0x00, Window::Key::kA }, 218 { 0x08, Window::Key::kC }, 219 { 0x09, Window::Key::kV }, 220 { 0x07, Window::Key::kX }, 221 { 0x10, Window::Key::kY }, 222 { 0x06, Window::Key::kZ }, 223 }; 224 for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { 225 if (gPair[i].fVK == vk) { 226 return gPair[i].fKey; 227 } 228 } 229 230 return Window::Key::kNONE; 231} 232 233static ModifierKey get_modifiers(const NSEvent* event) { 234 NSUInteger modifierFlags = [event modifierFlags]; 235 ModifierKey modifiers = ModifierKey::kNone; 236 237 if (modifierFlags & NSEventModifierFlagCommand) { 238 modifiers |= ModifierKey::kCommand; 239 } 240 if (modifierFlags & NSEventModifierFlagShift) { 241 modifiers |= ModifierKey::kShift; 242 } 243 if (modifierFlags & NSEventModifierFlagControl) { 244 modifiers |= ModifierKey::kControl; 245 } 246 if (modifierFlags & NSEventModifierFlagOption) { 247 modifiers |= ModifierKey::kOption; 248 } 249 250 if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && 251 NO == [event isARepeat]) { 252 modifiers |= ModifierKey::kFirstPress; 253 } 254 255 return modifiers; 256} 257 258@implementation MainView { 259 sk_app::Window_mac* fWindow; 260 // A TrackingArea prevents us from capturing events outside the view 261 NSTrackingArea* fTrackingArea; 262} 263 264- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow { 265 self = [super init]; 266 267 fWindow = initWindow; 268 fTrackingArea = nil; 269 270 [self updateTrackingAreas]; 271 272 return self; 273} 274 275- (void)dealloc 276{ 277 [fTrackingArea release]; 278 [super dealloc]; 279} 280 281- (BOOL)isOpaque { 282 return YES; 283} 284 285- (BOOL)canBecomeKeyView { 286 return YES; 287} 288 289- (BOOL)acceptsFirstResponder { 290 return YES; 291} 292 293- (void)updateTrackingAreas { 294 if (fTrackingArea != nil) { 295 [self removeTrackingArea:fTrackingArea]; 296 [fTrackingArea release]; 297 } 298 299 const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | 300 NSTrackingActiveInKeyWindow | 301 NSTrackingEnabledDuringMouseDrag | 302 NSTrackingCursorUpdate | 303 NSTrackingInVisibleRect | 304 NSTrackingAssumeInside; 305 306 fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] 307 options:options 308 owner:self 309 userInfo:nil]; 310 311 [self addTrackingArea:fTrackingArea]; 312 [super updateTrackingAreas]; 313} 314 315- (void)keyDown:(NSEvent *)event { 316 Window::Key key = get_key([event keyCode]); 317 if (key != Window::Key::kNONE) { 318 if (!fWindow->onKey(key, InputState::kDown, get_modifiers(event))) { 319 if (Window::Key::kEscape == key) { 320 [NSApp terminate:fWindow->window()]; 321 } 322 } 323 } 324 325 NSString* characters = [event charactersIgnoringModifiers]; 326 NSUInteger len = [characters length]; 327 if (len > 0) { 328 unichar* charBuffer = new unichar[len+1]; 329 [characters getCharacters:charBuffer range:NSMakeRange(0, len)]; 330 for (NSUInteger i = 0; i < len; ++i) { 331 (void) fWindow->onChar((SkUnichar) charBuffer[i], get_modifiers(event)); 332 } 333 delete [] charBuffer; 334 } 335} 336 337- (void)keyUp:(NSEvent *)event { 338 Window::Key key = get_key([event keyCode]); 339 if (key != Window::Key::kNONE) { 340 (void) fWindow->onKey(key, InputState::kUp, get_modifiers(event)); 341 } 342} 343 344- (void)mouseDown:(NSEvent *)event { 345 const NSPoint pos = [event locationInWindow]; 346 const NSRect rect = [fWindow->window().contentView frame]; 347 fWindow->onMouse(pos.x, rect.size.height - pos.y, InputState::kDown, 348 get_modifiers(event)); 349} 350 351- (void)mouseUp:(NSEvent *)event { 352 const NSPoint pos = [event locationInWindow]; 353 const NSRect rect = [fWindow->window().contentView frame]; 354 fWindow->onMouse(pos.x, rect.size.height - pos.y, InputState::kUp, 355 get_modifiers(event)); 356} 357 358- (void)mouseDragged:(NSEvent *)event { 359 [self mouseMoved:event]; 360} 361 362- (void)mouseMoved:(NSEvent *)event { 363 const NSPoint pos = [event locationInWindow]; 364 const NSRect rect = [fWindow->window().contentView frame]; 365 fWindow->onMouse(pos.x, rect.size.height - pos.y, InputState::kMove, 366 get_modifiers(event)); 367} 368 369- (void)scrollWheel:(NSEvent *)event { 370 // TODO: support hasPreciseScrollingDeltas? 371 fWindow->onMouseWheel([event scrollingDeltaY], get_modifiers(event)); 372} 373 374- (void)drawRect:(NSRect)rect { 375 fWindow->onPaint(); 376} 377 378@end 379