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