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 "SkUtils.h" 9#include "WindowContextFactory_mac.h" 10#include "Window_mac.h" 11 12@interface WindowDelegate : NSObject<NSWindowDelegate> 13 14- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow; 15 16@end 17 18@interface MainView : NSView 19@end 20 21/////////////////////////////////////////////////////////////////////////////// 22 23using sk_app::Window; 24 25namespace sk_app { 26 27SkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap; 28 29Window* Window::CreateNativeWindow(void*) { 30 Window_mac* window = new Window_mac(); 31 if (!window->initWindow()) { 32 delete window; 33 return nullptr; 34 } 35 36 return window; 37} 38 39bool Window_mac::initWindow() { 40 if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) { 41 this->closeWindow(); 42 } 43 44 // we already have a window 45 if (fWindow) { 46 return true; 47 } 48 49 constexpr int initialWidth = 1280; 50 constexpr int initialHeight = 960; 51 52 NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | 53 NSMiniaturizableWindowMask); 54 55 NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight); 56 fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle 57 backing:NSBackingStoreBuffered defer:NO]; 58 if (nil == fWindow) { 59 return false; 60 } 61 [fWindow setAcceptsMouseMovedEvents:YES]; 62 63 WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this]; 64 [fWindow setDelegate:delegate]; 65 [delegate release]; 66 67 // create view 68 MainView* view = [[MainView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)] ; 69 if (nil == view) { 70 [fWindow release]; 71 fWindow = nil; 72 return false; 73 } 74 75 // attach view to window 76 [fWindow setContentView:view]; 77 78 fWindowNumber = fWindow.windowNumber; 79 gWindowMap.add(this); 80 81 return true; 82} 83 84void Window_mac::closeWindow() { 85 if (nil != fWindow) { 86 gWindowMap.remove(fWindowNumber); 87 if (sk_app::Window_mac::gWindowMap.count() < 1) { 88 [NSApp terminate:fWindow]; 89 } 90 [fWindow release]; 91 fWindow = nil; 92 } 93} 94 95void Window_mac::setTitle(const char* title) { 96 NSString *titleString = [NSString stringWithCString:title encoding:NSUTF8StringEncoding]; 97 [fWindow setTitle:titleString]; 98} 99 100void Window_mac::show() { 101 [NSApp activateIgnoringOtherApps:YES]; 102 103 [fWindow makeKeyAndOrderFront:NSApp]; 104} 105 106bool Window_mac::attach(BackendType attachType) { 107 this->initWindow(); 108 109 window_context_factory::MacWindowInfo info; 110 info.fMainView = [fWindow contentView]; 111 switch (attachType) { 112 case kRaster_BackendType: 113 fWindowContext = NewRasterForMac(info, fRequestedDisplayParams); 114 break; 115#ifdef SK_VULKAN 116 case kVulkan_BackendType: 117 fWindowContext = NewVulkanForMac(info, fRequestedDisplayParams); 118 break; 119#endif 120#ifdef SK_METAL 121 case kMetal_BackendType: 122 fWindowContext = NewMetalForMac(info, fRequestedDisplayParams); 123 break; 124#endif 125 case kNativeGL_BackendType: 126 default: 127 fWindowContext = NewGLForMac(info, fRequestedDisplayParams); 128 break; 129 } 130 this->onBackendCreated(); 131 132 return (SkToBool(fWindowContext)); 133} 134 135static Window::Key get_key(unsigned short vk) { 136 // This will work with an ANSI QWERTY keyboard. 137 // Something more robust would be needed to support alternate keyboards. 138 static const struct { 139 unsigned short fVK; 140 Window::Key fKey; 141 } gPair[] = { 142 { 0x33, Window::Key::kBack }, 143 { 0x24, Window::Key::kOK }, 144 { 0x7E, Window::Key::kUp }, 145 { 0x7D, Window::Key::kDown }, 146 { 0x7B, Window::Key::kLeft }, 147 { 0x7C, Window::Key::kRight }, 148 { 0x30, Window::Key::kTab }, 149 { 0x74, Window::Key::kPageUp }, 150 { 0x79, Window::Key::kPageDown }, 151 { 0x73, Window::Key::kHome }, 152 { 0x77, Window::Key::kEnd }, 153 { 0x75, Window::Key::kDelete }, 154 { 0x35, Window::Key::kEscape }, 155 { 0x38, Window::Key::kShift }, 156 { 0x3C, Window::Key::kShift }, 157 { 0x3B, Window::Key::kCtrl }, 158 { 0x3E, Window::Key::kCtrl }, 159 { 0x3A, Window::Key::kOption }, 160 { 0x3D, Window::Key::kOption }, 161 { 0x00, Window::Key::kA }, 162 { 0x08, Window::Key::kC }, 163 { 0x09, Window::Key::kV }, 164 { 0x07, Window::Key::kX }, 165 { 0x10, Window::Key::kY }, 166 { 0x06, Window::Key::kZ }, 167 }; 168 for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { 169 if (gPair[i].fVK == vk) { 170 return gPair[i].fKey; 171 } 172 } 173 174 return Window::Key::kNONE; 175} 176 177static uint32_t get_modifiers(const NSEvent* event) { 178 NSUInteger modifierFlags = [event modifierFlags]; 179 auto modifiers = 0; 180 181 if (modifierFlags & NSEventModifierFlagShift) { 182 modifiers |= Window::kShift_ModifierKey; 183 } 184 if (modifierFlags & NSEventModifierFlagControl) { 185 modifiers |= Window::kControl_ModifierKey; 186 } 187 if (modifierFlags & NSEventModifierFlagOption) { 188 modifiers |= Window::kOption_ModifierKey; 189 } 190 191 if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && 192 NO == [event isARepeat]) { 193 modifiers |= Window::kFirstPress_ModifierKey; 194 } 195 196 return modifiers; 197} 198 199void Window_mac::PaintWindows() { 200 SkTDynamicHash<Window_mac, NSInteger>::Iter iter(&gWindowMap); 201 while (!iter.done()) { 202 if ((*iter).fIsContentInvalidated) { 203 (*iter).onPaint(); 204 } 205 ++iter; 206 } 207} 208 209void Window_mac::HandleWindowEvent(const NSEvent* event) { 210 Window_mac* win = gWindowMap.find(event.window.windowNumber); 211 if (win) { 212 win->handleEvent(event); 213 } 214} 215 216void Window_mac::handleEvent(const NSEvent* event) { 217 switch (event.type) { 218 case NSEventTypeKeyDown: { 219 Window::Key key = get_key([event keyCode]); 220 if (key != Window::Key::kNONE) { 221 if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) { 222 if (Window::Key::kEscape == key) { 223 [NSApp terminate:fWindow]; 224 } 225 } 226 } 227 228 NSString* characters = [event charactersIgnoringModifiers]; 229 NSUInteger len = [characters length]; 230 if (len > 0) { 231 unichar* charBuffer = new unichar[len+1]; 232 [characters getCharacters:charBuffer range:NSMakeRange(0, len)]; 233 for (NSUInteger i = 0; i < len; ++i) { 234 (void) this->onChar((SkUnichar) charBuffer[i], get_modifiers(event)); 235 } 236 delete [] charBuffer; 237 } 238 break; 239 } 240 case NSEventTypeKeyUp: { 241 Window::Key key = get_key([event keyCode]); 242 if (key != Window::Key::kNONE) { 243 (void) this->onKey(key, Window::kUp_InputState, get_modifiers(event)); 244 } 245 break; 246 } 247 case NSEventTypeLeftMouseDown: { 248 const NSPoint pos = [event locationInWindow]; 249 const NSRect rect = [fWindow.contentView frame]; 250 if (NSPointInRect(pos, rect)) { 251 // This might be a resize event -- until we know we'll store the event 252 // and reset later if need be 253 fIsMouseDown = true; 254 fMouseDownPos = pos; 255 fMouseModifiers = get_modifiers(event); 256 this->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState, 257 fMouseModifiers); 258 } 259 break; 260 } 261 case NSEventTypeLeftMouseUp: { 262 const NSPoint pos = [event locationInWindow]; 263 const NSRect rect = [fWindow.contentView frame]; 264 this->onMouse(pos.x, rect.size.height - pos.y, Window::kUp_InputState, 265 get_modifiers(event)); 266 break; 267 } 268 case NSEventTypeMouseMoved: 269 case NSEventTypeLeftMouseDragged: { 270 const NSPoint pos = [event locationInWindow]; 271 const NSRect rect = [fWindow.contentView frame]; 272 this->onMouse(pos.x, rect.size.height - pos.y, Window::kMove_InputState, 273 get_modifiers(event)); 274 break; 275 } 276 case NSEventTypeScrollWheel: 277 // TODO: support hasPreciseScrollingDeltas? 278 this->onMouseWheel([event scrollingDeltaY], get_modifiers(event)); 279 break; 280 281 default: 282 break; 283 } 284} 285 286void Window_mac::resetMouse() { 287 if (fIsMouseDown) { 288 // We're resizing so just send a mouse up event in the same place 289 const NSRect rect = [fWindow.contentView frame]; 290 this->onMouse(fMouseDownPos.x, rect.size.height - fMouseDownPos.y, Window::kUp_InputState, 291 fMouseModifiers); 292 fIsMouseDown = false; 293 } 294} 295 296} // namespace sk_app 297 298/////////////////////////////////////////////////////////////////////////////// 299 300@implementation WindowDelegate { 301 sk_app::Window_mac* fWindow; 302} 303 304- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow { 305 fWindow = initWindow; 306 307 return self; 308} 309 310- (void)windowDidResize:(NSNotification *)notification { 311 const NSRect mainRect = [fWindow->window().contentView bounds]; 312 313 fWindow->onResize(mainRect.size.width, mainRect.size.height); 314 fWindow->resetMouse(); 315} 316 317- (BOOL)windowShouldClose:(NSWindow*)sender { 318 fWindow->closeWindow(); 319 320 return FALSE; 321} 322 323@end 324 325/////////////////////////////////////////////////////////////////////////////// 326 327@implementation MainView 328- (BOOL)isOpaque { 329 return YES; 330} 331 332- (BOOL)canBecomeKeyView { 333 return YES; 334} 335 336- (BOOL)acceptsFirstResponder { 337 return YES; 338} 339 340// We keep these around to prevent beeping when the system can't determine 341// where the focus for key events is. 342- (void)keyDown:(NSEvent *)event { 343} 344 345- (void)keyUp:(NSEvent *)event { 346} 347@end 348