1cb93a386Sopenharmony_ci/* 2cb93a386Sopenharmony_ci* Copyright 2019 Google Inc. 3cb93a386Sopenharmony_ci* 4cb93a386Sopenharmony_ci* Use of this source code is governed by a BSD-style license that can be 5cb93a386Sopenharmony_ci* found in the LICENSE file. 6cb93a386Sopenharmony_ci*/ 7cb93a386Sopenharmony_ci 8cb93a386Sopenharmony_ci#include <Carbon/Carbon.h> 9cb93a386Sopenharmony_ci 10cb93a386Sopenharmony_ci#include "include/core/SkTypes.h" 11cb93a386Sopenharmony_ci#include "tools/sk_app/mac/WindowContextFactory_mac.h" 12cb93a386Sopenharmony_ci#include "tools/sk_app/mac/Window_mac.h" 13cb93a386Sopenharmony_ci#include "tools/skui/ModifierKey.h" 14cb93a386Sopenharmony_ci 15cb93a386Sopenharmony_ci@interface WindowDelegate : NSObject<NSWindowDelegate> 16cb93a386Sopenharmony_ci 17cb93a386Sopenharmony_ci- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow; 18cb93a386Sopenharmony_ci 19cb93a386Sopenharmony_ci@end 20cb93a386Sopenharmony_ci 21cb93a386Sopenharmony_ci@interface MainView : NSView 22cb93a386Sopenharmony_ci 23cb93a386Sopenharmony_ci- (MainView*)initWithWindow:(sk_app::Window_mac*)initWindow; 24cb93a386Sopenharmony_ci 25cb93a386Sopenharmony_ci@end 26cb93a386Sopenharmony_ci 27cb93a386Sopenharmony_ci/////////////////////////////////////////////////////////////////////////////// 28cb93a386Sopenharmony_ci 29cb93a386Sopenharmony_ciusing sk_app::Window; 30cb93a386Sopenharmony_ci 31cb93a386Sopenharmony_cinamespace sk_app { 32cb93a386Sopenharmony_ci 33cb93a386Sopenharmony_ciSkTDynamicHash<Window_mac, NSInteger> Window_mac::gWindowMap; 34cb93a386Sopenharmony_ci 35cb93a386Sopenharmony_ciWindow* Window::CreateNativeWindow(void*) { 36cb93a386Sopenharmony_ci Window_mac* window = new Window_mac(); 37cb93a386Sopenharmony_ci if (!window->initWindow()) { 38cb93a386Sopenharmony_ci delete window; 39cb93a386Sopenharmony_ci return nullptr; 40cb93a386Sopenharmony_ci } 41cb93a386Sopenharmony_ci 42cb93a386Sopenharmony_ci return window; 43cb93a386Sopenharmony_ci} 44cb93a386Sopenharmony_ci 45cb93a386Sopenharmony_cibool Window_mac::initWindow() { 46cb93a386Sopenharmony_ci // we already have a window 47cb93a386Sopenharmony_ci if (fWindow) { 48cb93a386Sopenharmony_ci return true; 49cb93a386Sopenharmony_ci } 50cb93a386Sopenharmony_ci 51cb93a386Sopenharmony_ci // Create a delegate to track certain events 52cb93a386Sopenharmony_ci WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this]; 53cb93a386Sopenharmony_ci if (nil == delegate) { 54cb93a386Sopenharmony_ci return false; 55cb93a386Sopenharmony_ci } 56cb93a386Sopenharmony_ci 57cb93a386Sopenharmony_ci // Create Cocoa window 58cb93a386Sopenharmony_ci constexpr int initialWidth = 1280; 59cb93a386Sopenharmony_ci constexpr int initialHeight = 960; 60cb93a386Sopenharmony_ci NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight); 61cb93a386Sopenharmony_ci 62cb93a386Sopenharmony_ci NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | 63cb93a386Sopenharmony_ci NSMiniaturizableWindowMask); 64cb93a386Sopenharmony_ci 65cb93a386Sopenharmony_ci fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle 66cb93a386Sopenharmony_ci backing:NSBackingStoreBuffered defer:NO]; 67cb93a386Sopenharmony_ci if (nil == fWindow) { 68cb93a386Sopenharmony_ci [delegate release]; 69cb93a386Sopenharmony_ci return false; 70cb93a386Sopenharmony_ci } 71cb93a386Sopenharmony_ci 72cb93a386Sopenharmony_ci // create view 73cb93a386Sopenharmony_ci MainView* view = [[MainView alloc] initWithWindow:this]; 74cb93a386Sopenharmony_ci if (nil == view) { 75cb93a386Sopenharmony_ci [fWindow release]; 76cb93a386Sopenharmony_ci [delegate release]; 77cb93a386Sopenharmony_ci return false; 78cb93a386Sopenharmony_ci } 79cb93a386Sopenharmony_ci 80cb93a386Sopenharmony_ci [fWindow setContentView:view]; 81cb93a386Sopenharmony_ci [fWindow makeFirstResponder:view]; 82cb93a386Sopenharmony_ci [fWindow setDelegate:delegate]; 83cb93a386Sopenharmony_ci [fWindow setAcceptsMouseMovedEvents:YES]; 84cb93a386Sopenharmony_ci [fWindow setRestorable:NO]; 85cb93a386Sopenharmony_ci 86cb93a386Sopenharmony_ci // Should be retained by window now 87cb93a386Sopenharmony_ci [view release]; 88cb93a386Sopenharmony_ci 89cb93a386Sopenharmony_ci fWindowNumber = fWindow.windowNumber; 90cb93a386Sopenharmony_ci gWindowMap.add(this); 91cb93a386Sopenharmony_ci 92cb93a386Sopenharmony_ci return true; 93cb93a386Sopenharmony_ci} 94cb93a386Sopenharmony_ci 95cb93a386Sopenharmony_civoid Window_mac::closeWindow() { 96cb93a386Sopenharmony_ci if (nil != fWindow) { 97cb93a386Sopenharmony_ci gWindowMap.remove(fWindowNumber); 98cb93a386Sopenharmony_ci if (sk_app::Window_mac::gWindowMap.count() < 1) { 99cb93a386Sopenharmony_ci [NSApp terminate:fWindow]; 100cb93a386Sopenharmony_ci } 101cb93a386Sopenharmony_ci [fWindow close]; 102cb93a386Sopenharmony_ci fWindow = nil; 103cb93a386Sopenharmony_ci } 104cb93a386Sopenharmony_ci} 105cb93a386Sopenharmony_ci 106cb93a386Sopenharmony_civoid Window_mac::setTitle(const char* title) { 107cb93a386Sopenharmony_ci if (NSString* titleStr = [NSString stringWithUTF8String:title]) { 108cb93a386Sopenharmony_ci [fWindow setTitle:titleStr]; 109cb93a386Sopenharmony_ci } 110cb93a386Sopenharmony_ci} 111cb93a386Sopenharmony_ci 112cb93a386Sopenharmony_civoid Window_mac::show() { 113cb93a386Sopenharmony_ci [fWindow orderFront:nil]; 114cb93a386Sopenharmony_ci 115cb93a386Sopenharmony_ci [NSApp activateIgnoringOtherApps:YES]; 116cb93a386Sopenharmony_ci [fWindow makeKeyAndOrderFront:NSApp]; 117cb93a386Sopenharmony_ci} 118cb93a386Sopenharmony_ci 119cb93a386Sopenharmony_cibool Window_mac::attach(BackendType attachType) { 120cb93a386Sopenharmony_ci this->initWindow(); 121cb93a386Sopenharmony_ci 122cb93a386Sopenharmony_ci window_context_factory::MacWindowInfo info; 123cb93a386Sopenharmony_ci info.fMainView = [fWindow contentView]; 124cb93a386Sopenharmony_ci switch (attachType) { 125cb93a386Sopenharmony_ci#ifdef SK_DAWN 126cb93a386Sopenharmony_ci case kDawn_BackendType: 127cb93a386Sopenharmony_ci fWindowContext = MakeDawnMTLForMac(info, fRequestedDisplayParams); 128cb93a386Sopenharmony_ci break; 129cb93a386Sopenharmony_ci#endif 130cb93a386Sopenharmony_ci#ifdef SK_VULKAN 131cb93a386Sopenharmony_ci case kVulkan_BackendType: 132cb93a386Sopenharmony_ci fWindowContext = MakeVulkanForMac(info, fRequestedDisplayParams); 133cb93a386Sopenharmony_ci break; 134cb93a386Sopenharmony_ci#endif 135cb93a386Sopenharmony_ci#ifdef SK_METAL 136cb93a386Sopenharmony_ci case kMetal_BackendType: 137cb93a386Sopenharmony_ci fWindowContext = MakeMetalForMac(info, fRequestedDisplayParams); 138cb93a386Sopenharmony_ci break; 139cb93a386Sopenharmony_ci#ifdef SK_GRAPHITE_ENABLED 140cb93a386Sopenharmony_ci case kGraphiteMetal_BackendType: 141cb93a386Sopenharmony_ci fWindowContext = MakeGraphiteMetalForMac(info, fRequestedDisplayParams); 142cb93a386Sopenharmony_ci break; 143cb93a386Sopenharmony_ci#endif 144cb93a386Sopenharmony_ci#endif 145cb93a386Sopenharmony_ci#ifdef SK_GL 146cb93a386Sopenharmony_ci case kNativeGL_BackendType: 147cb93a386Sopenharmony_ci fWindowContext = MakeGLForMac(info, fRequestedDisplayParams); 148cb93a386Sopenharmony_ci break; 149cb93a386Sopenharmony_ci case kRaster_BackendType: 150cb93a386Sopenharmony_ci fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams); 151cb93a386Sopenharmony_ci break; 152cb93a386Sopenharmony_ci#endif 153cb93a386Sopenharmony_ci default: 154cb93a386Sopenharmony_ci SkASSERT_RELEASE(false); 155cb93a386Sopenharmony_ci } 156cb93a386Sopenharmony_ci this->onBackendCreated(); 157cb93a386Sopenharmony_ci 158cb93a386Sopenharmony_ci return SkToBool(fWindowContext); 159cb93a386Sopenharmony_ci} 160cb93a386Sopenharmony_ci 161cb93a386Sopenharmony_cifloat Window_mac::scaleFactor() const { 162cb93a386Sopenharmony_ci return sk_app::GetBackingScaleFactor(fWindow.contentView); 163cb93a386Sopenharmony_ci} 164cb93a386Sopenharmony_ci 165cb93a386Sopenharmony_civoid Window_mac::PaintWindows() { 166cb93a386Sopenharmony_ci gWindowMap.foreach([&](Window_mac* window) { 167cb93a386Sopenharmony_ci if (window->fIsContentInvalidated) { 168cb93a386Sopenharmony_ci window->onPaint(); 169cb93a386Sopenharmony_ci } 170cb93a386Sopenharmony_ci }); 171cb93a386Sopenharmony_ci} 172cb93a386Sopenharmony_ci 173cb93a386Sopenharmony_ci} // namespace sk_app 174cb93a386Sopenharmony_ci 175cb93a386Sopenharmony_ci/////////////////////////////////////////////////////////////////////////////// 176cb93a386Sopenharmony_ci 177cb93a386Sopenharmony_ci@implementation WindowDelegate { 178cb93a386Sopenharmony_ci sk_app::Window_mac* fWindow; 179cb93a386Sopenharmony_ci} 180cb93a386Sopenharmony_ci 181cb93a386Sopenharmony_ci- (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow { 182cb93a386Sopenharmony_ci fWindow = initWindow; 183cb93a386Sopenharmony_ci 184cb93a386Sopenharmony_ci return self; 185cb93a386Sopenharmony_ci} 186cb93a386Sopenharmony_ci 187cb93a386Sopenharmony_ci- (void)windowDidResize:(NSNotification *)notification { 188cb93a386Sopenharmony_ci NSView* view = fWindow->window().contentView; 189cb93a386Sopenharmony_ci CGFloat scale = sk_app::GetBackingScaleFactor(view); 190cb93a386Sopenharmony_ci fWindow->onResize(view.bounds.size.width * scale, view.bounds.size.height * scale); 191cb93a386Sopenharmony_ci fWindow->inval(); 192cb93a386Sopenharmony_ci} 193cb93a386Sopenharmony_ci 194cb93a386Sopenharmony_ci- (BOOL)windowShouldClose:(NSWindow*)sender { 195cb93a386Sopenharmony_ci fWindow->closeWindow(); 196cb93a386Sopenharmony_ci 197cb93a386Sopenharmony_ci return FALSE; 198cb93a386Sopenharmony_ci} 199cb93a386Sopenharmony_ci 200cb93a386Sopenharmony_ci@end 201cb93a386Sopenharmony_ci 202cb93a386Sopenharmony_ci/////////////////////////////////////////////////////////////////////////////// 203cb93a386Sopenharmony_ci 204cb93a386Sopenharmony_cistatic skui::Key get_key(unsigned short vk) { 205cb93a386Sopenharmony_ci // This will work with an ANSI QWERTY keyboard. 206cb93a386Sopenharmony_ci // Something more robust would be needed to support alternate keyboards. 207cb93a386Sopenharmony_ci static const struct { 208cb93a386Sopenharmony_ci unsigned short fVK; 209cb93a386Sopenharmony_ci skui::Key fKey; 210cb93a386Sopenharmony_ci } gPair[] = { 211cb93a386Sopenharmony_ci { kVK_Delete, skui::Key::kBack }, 212cb93a386Sopenharmony_ci { kVK_Return, skui::Key::kOK }, 213cb93a386Sopenharmony_ci { kVK_UpArrow, skui::Key::kUp }, 214cb93a386Sopenharmony_ci { kVK_DownArrow, skui::Key::kDown }, 215cb93a386Sopenharmony_ci { kVK_LeftArrow, skui::Key::kLeft }, 216cb93a386Sopenharmony_ci { kVK_RightArrow, skui::Key::kRight }, 217cb93a386Sopenharmony_ci { kVK_Tab, skui::Key::kTab }, 218cb93a386Sopenharmony_ci { kVK_PageUp, skui::Key::kPageUp }, 219cb93a386Sopenharmony_ci { kVK_PageDown, skui::Key::kPageDown }, 220cb93a386Sopenharmony_ci { kVK_Home, skui::Key::kHome }, 221cb93a386Sopenharmony_ci { kVK_End, skui::Key::kEnd }, 222cb93a386Sopenharmony_ci { kVK_ForwardDelete, skui::Key::kDelete }, 223cb93a386Sopenharmony_ci { kVK_Escape, skui::Key::kEscape }, 224cb93a386Sopenharmony_ci { kVK_Shift, skui::Key::kShift }, 225cb93a386Sopenharmony_ci { kVK_RightShift, skui::Key::kShift }, 226cb93a386Sopenharmony_ci { kVK_Control, skui::Key::kCtrl }, 227cb93a386Sopenharmony_ci { kVK_RightControl, skui::Key::kCtrl }, 228cb93a386Sopenharmony_ci { kVK_Option, skui::Key::kOption }, 229cb93a386Sopenharmony_ci { kVK_RightOption, skui::Key::kOption }, 230cb93a386Sopenharmony_ci { kVK_Command, skui::Key::kSuper }, 231cb93a386Sopenharmony_ci { kVK_RightCommand, skui::Key::kSuper }, 232cb93a386Sopenharmony_ci { kVK_ANSI_A, skui::Key::kA }, 233cb93a386Sopenharmony_ci { kVK_ANSI_C, skui::Key::kC }, 234cb93a386Sopenharmony_ci { kVK_ANSI_V, skui::Key::kV }, 235cb93a386Sopenharmony_ci { kVK_ANSI_X, skui::Key::kX }, 236cb93a386Sopenharmony_ci { kVK_ANSI_Y, skui::Key::kY }, 237cb93a386Sopenharmony_ci { kVK_ANSI_Z, skui::Key::kZ }, 238cb93a386Sopenharmony_ci }; 239cb93a386Sopenharmony_ci 240cb93a386Sopenharmony_ci for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { 241cb93a386Sopenharmony_ci if (gPair[i].fVK == vk) { 242cb93a386Sopenharmony_ci return gPair[i].fKey; 243cb93a386Sopenharmony_ci } 244cb93a386Sopenharmony_ci } 245cb93a386Sopenharmony_ci 246cb93a386Sopenharmony_ci return skui::Key::kNONE; 247cb93a386Sopenharmony_ci} 248cb93a386Sopenharmony_ci 249cb93a386Sopenharmony_cistatic skui::ModifierKey get_modifiers(const NSEvent* event) { 250cb93a386Sopenharmony_ci NSUInteger modifierFlags = [event modifierFlags]; 251cb93a386Sopenharmony_ci skui::ModifierKey modifiers = skui::ModifierKey::kNone; 252cb93a386Sopenharmony_ci 253cb93a386Sopenharmony_ci if (modifierFlags & NSEventModifierFlagCommand) { 254cb93a386Sopenharmony_ci modifiers |= skui::ModifierKey::kCommand; 255cb93a386Sopenharmony_ci } 256cb93a386Sopenharmony_ci if (modifierFlags & NSEventModifierFlagShift) { 257cb93a386Sopenharmony_ci modifiers |= skui::ModifierKey::kShift; 258cb93a386Sopenharmony_ci } 259cb93a386Sopenharmony_ci if (modifierFlags & NSEventModifierFlagControl) { 260cb93a386Sopenharmony_ci modifiers |= skui::ModifierKey::kControl; 261cb93a386Sopenharmony_ci } 262cb93a386Sopenharmony_ci if (modifierFlags & NSEventModifierFlagOption) { 263cb93a386Sopenharmony_ci modifiers |= skui::ModifierKey::kOption; 264cb93a386Sopenharmony_ci } 265cb93a386Sopenharmony_ci 266cb93a386Sopenharmony_ci if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) { 267cb93a386Sopenharmony_ci modifiers |= skui::ModifierKey::kFirstPress; 268cb93a386Sopenharmony_ci } 269cb93a386Sopenharmony_ci 270cb93a386Sopenharmony_ci return modifiers; 271cb93a386Sopenharmony_ci} 272cb93a386Sopenharmony_ci 273cb93a386Sopenharmony_ci@implementation MainView { 274cb93a386Sopenharmony_ci sk_app::Window_mac* fWindow; 275cb93a386Sopenharmony_ci // A TrackingArea prevents us from capturing events outside the view 276cb93a386Sopenharmony_ci NSTrackingArea* fTrackingArea; 277cb93a386Sopenharmony_ci // We keep track of the state of the modifier keys on each event in order to synthesize 278cb93a386Sopenharmony_ci // key-up/down events for each modifier. 279cb93a386Sopenharmony_ci skui::ModifierKey fLastModifiers; 280cb93a386Sopenharmony_ci} 281cb93a386Sopenharmony_ci 282cb93a386Sopenharmony_ci- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow { 283cb93a386Sopenharmony_ci self = [super init]; 284cb93a386Sopenharmony_ci 285cb93a386Sopenharmony_ci fWindow = initWindow; 286cb93a386Sopenharmony_ci fTrackingArea = nil; 287cb93a386Sopenharmony_ci 288cb93a386Sopenharmony_ci [self updateTrackingAreas]; 289cb93a386Sopenharmony_ci 290cb93a386Sopenharmony_ci return self; 291cb93a386Sopenharmony_ci} 292cb93a386Sopenharmony_ci 293cb93a386Sopenharmony_ci- (void)dealloc 294cb93a386Sopenharmony_ci{ 295cb93a386Sopenharmony_ci [fTrackingArea release]; 296cb93a386Sopenharmony_ci [super dealloc]; 297cb93a386Sopenharmony_ci} 298cb93a386Sopenharmony_ci 299cb93a386Sopenharmony_ci- (BOOL)isOpaque { 300cb93a386Sopenharmony_ci return YES; 301cb93a386Sopenharmony_ci} 302cb93a386Sopenharmony_ci 303cb93a386Sopenharmony_ci- (BOOL)canBecomeKeyView { 304cb93a386Sopenharmony_ci return YES; 305cb93a386Sopenharmony_ci} 306cb93a386Sopenharmony_ci 307cb93a386Sopenharmony_ci- (BOOL)acceptsFirstResponder { 308cb93a386Sopenharmony_ci return YES; 309cb93a386Sopenharmony_ci} 310cb93a386Sopenharmony_ci 311cb93a386Sopenharmony_ci- (void)updateTrackingAreas { 312cb93a386Sopenharmony_ci if (fTrackingArea != nil) { 313cb93a386Sopenharmony_ci [self removeTrackingArea:fTrackingArea]; 314cb93a386Sopenharmony_ci [fTrackingArea release]; 315cb93a386Sopenharmony_ci } 316cb93a386Sopenharmony_ci 317cb93a386Sopenharmony_ci const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited | 318cb93a386Sopenharmony_ci NSTrackingActiveInKeyWindow | 319cb93a386Sopenharmony_ci NSTrackingEnabledDuringMouseDrag | 320cb93a386Sopenharmony_ci NSTrackingCursorUpdate | 321cb93a386Sopenharmony_ci NSTrackingInVisibleRect | 322cb93a386Sopenharmony_ci NSTrackingAssumeInside; 323cb93a386Sopenharmony_ci 324cb93a386Sopenharmony_ci fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] 325cb93a386Sopenharmony_ci options:options 326cb93a386Sopenharmony_ci owner:self 327cb93a386Sopenharmony_ci userInfo:nil]; 328cb93a386Sopenharmony_ci 329cb93a386Sopenharmony_ci [self addTrackingArea:fTrackingArea]; 330cb93a386Sopenharmony_ci [super updateTrackingAreas]; 331cb93a386Sopenharmony_ci} 332cb93a386Sopenharmony_ci 333cb93a386Sopenharmony_ci- (skui::ModifierKey) updateModifierKeys:(NSEvent*) event { 334cb93a386Sopenharmony_ci using sknonstd::Any; 335cb93a386Sopenharmony_ci 336cb93a386Sopenharmony_ci skui::ModifierKey modifiers = get_modifiers(event); 337cb93a386Sopenharmony_ci skui::ModifierKey changed = modifiers ^ fLastModifiers; 338cb93a386Sopenharmony_ci fLastModifiers = modifiers; 339cb93a386Sopenharmony_ci 340cb93a386Sopenharmony_ci struct ModMap { 341cb93a386Sopenharmony_ci skui::ModifierKey modifier; 342cb93a386Sopenharmony_ci skui::Key key; 343cb93a386Sopenharmony_ci }; 344cb93a386Sopenharmony_ci 345cb93a386Sopenharmony_ci // Map each modifier bit to the equivalent skui Key and send key-up/down events. 346cb93a386Sopenharmony_ci for (const ModMap& cur : {ModMap{skui::ModifierKey::kCommand, skui::Key::kSuper}, 347cb93a386Sopenharmony_ci ModMap{skui::ModifierKey::kShift, skui::Key::kShift}, 348cb93a386Sopenharmony_ci ModMap{skui::ModifierKey::kControl, skui::Key::kCtrl}, 349cb93a386Sopenharmony_ci ModMap{skui::ModifierKey::kOption, skui::Key::kOption}}) { 350cb93a386Sopenharmony_ci if (Any(changed & cur.modifier)) { 351cb93a386Sopenharmony_ci const skui::InputState state = Any(modifiers & cur.modifier) ? skui::InputState::kDown 352cb93a386Sopenharmony_ci : skui::InputState::kUp; 353cb93a386Sopenharmony_ci (void) fWindow->onKey(cur.key, state, modifiers); 354cb93a386Sopenharmony_ci } 355cb93a386Sopenharmony_ci } 356cb93a386Sopenharmony_ci 357cb93a386Sopenharmony_ci return modifiers; 358cb93a386Sopenharmony_ci} 359cb93a386Sopenharmony_ci 360cb93a386Sopenharmony_ci- (BOOL)performKeyEquivalent:(NSEvent *)event { 361cb93a386Sopenharmony_ci [self updateModifierKeys:event]; 362cb93a386Sopenharmony_ci 363cb93a386Sopenharmony_ci // By default, unhandled key equivalents send -keyDown events; unfortunately, they do not send 364cb93a386Sopenharmony_ci // a matching -keyUp. In other words, we can claim that we didn't handle the event and OS X will 365cb93a386Sopenharmony_ci // turn this event into a -keyDown automatically, but we need to synthesize a matching -keyUp on 366cb93a386Sopenharmony_ci // a later frame. Since we only read the modifiers and key code from the event, we can reuse 367cb93a386Sopenharmony_ci // this "key-equivalent" event as a "key up". 368cb93a386Sopenharmony_ci [self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1]; 369cb93a386Sopenharmony_ci return NO; 370cb93a386Sopenharmony_ci} 371cb93a386Sopenharmony_ci 372cb93a386Sopenharmony_ci- (void)keyDown:(NSEvent *)event { 373cb93a386Sopenharmony_ci skui::ModifierKey modifiers = [self updateModifierKeys:event]; 374cb93a386Sopenharmony_ci 375cb93a386Sopenharmony_ci skui::Key key = get_key([event keyCode]); 376cb93a386Sopenharmony_ci if (key != skui::Key::kNONE) { 377cb93a386Sopenharmony_ci if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) { 378cb93a386Sopenharmony_ci if (skui::Key::kEscape == key) { 379cb93a386Sopenharmony_ci [NSApp terminate:fWindow->window()]; 380cb93a386Sopenharmony_ci } 381cb93a386Sopenharmony_ci } 382cb93a386Sopenharmony_ci } 383cb93a386Sopenharmony_ci 384cb93a386Sopenharmony_ci NSString* characters = [event charactersIgnoringModifiers]; 385cb93a386Sopenharmony_ci NSUInteger len = [characters length]; 386cb93a386Sopenharmony_ci if (len > 0) { 387cb93a386Sopenharmony_ci unichar* charBuffer = new unichar[len+1]; 388cb93a386Sopenharmony_ci [characters getCharacters:charBuffer range:NSMakeRange(0, len)]; 389cb93a386Sopenharmony_ci for (NSUInteger i = 0; i < len; ++i) { 390cb93a386Sopenharmony_ci (void) fWindow->onChar((SkUnichar) charBuffer[i], modifiers); 391cb93a386Sopenharmony_ci } 392cb93a386Sopenharmony_ci delete [] charBuffer; 393cb93a386Sopenharmony_ci } 394cb93a386Sopenharmony_ci} 395cb93a386Sopenharmony_ci 396cb93a386Sopenharmony_ci- (void)keyUp:(NSEvent *)event { 397cb93a386Sopenharmony_ci skui::ModifierKey modifiers = [self updateModifierKeys:event]; 398cb93a386Sopenharmony_ci 399cb93a386Sopenharmony_ci skui::Key key = get_key([event keyCode]); 400cb93a386Sopenharmony_ci if (key != skui::Key::kNONE) { 401cb93a386Sopenharmony_ci (void) fWindow->onKey(key, skui::InputState::kUp, modifiers); 402cb93a386Sopenharmony_ci } 403cb93a386Sopenharmony_ci} 404cb93a386Sopenharmony_ci 405cb93a386Sopenharmony_ci-(void)flagsChanged:(NSEvent *)event { 406cb93a386Sopenharmony_ci [self updateModifierKeys:event]; 407cb93a386Sopenharmony_ci} 408cb93a386Sopenharmony_ci 409cb93a386Sopenharmony_ci- (void)mouseDown:(NSEvent *)event { 410cb93a386Sopenharmony_ci NSView* view = fWindow->window().contentView; 411cb93a386Sopenharmony_ci CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view); 412cb93a386Sopenharmony_ci 413cb93a386Sopenharmony_ci skui::ModifierKey modifiers = [self updateModifierKeys:event]; 414cb93a386Sopenharmony_ci 415cb93a386Sopenharmony_ci const NSPoint pos = [event locationInWindow]; 416cb93a386Sopenharmony_ci const NSRect rect = [view frame]; 417cb93a386Sopenharmony_ci fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 418cb93a386Sopenharmony_ci skui::InputState::kDown, modifiers); 419cb93a386Sopenharmony_ci} 420cb93a386Sopenharmony_ci 421cb93a386Sopenharmony_ci- (void)mouseUp:(NSEvent *)event { 422cb93a386Sopenharmony_ci NSView* view = fWindow->window().contentView; 423cb93a386Sopenharmony_ci CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view); 424cb93a386Sopenharmony_ci 425cb93a386Sopenharmony_ci skui::ModifierKey modifiers = [self updateModifierKeys:event]; 426cb93a386Sopenharmony_ci 427cb93a386Sopenharmony_ci const NSPoint pos = [event locationInWindow]; 428cb93a386Sopenharmony_ci const NSRect rect = [view frame]; 429cb93a386Sopenharmony_ci fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 430cb93a386Sopenharmony_ci skui::InputState::kUp, modifiers); 431cb93a386Sopenharmony_ci} 432cb93a386Sopenharmony_ci 433cb93a386Sopenharmony_ci- (void)mouseDragged:(NSEvent *)event { 434cb93a386Sopenharmony_ci [self updateModifierKeys:event]; 435cb93a386Sopenharmony_ci [self mouseMoved:event]; 436cb93a386Sopenharmony_ci} 437cb93a386Sopenharmony_ci 438cb93a386Sopenharmony_ci- (void)mouseMoved:(NSEvent *)event { 439cb93a386Sopenharmony_ci NSView* view = fWindow->window().contentView; 440cb93a386Sopenharmony_ci CGFloat backingScaleFactor = sk_app::GetBackingScaleFactor(view); 441cb93a386Sopenharmony_ci 442cb93a386Sopenharmony_ci skui::ModifierKey modifiers = [self updateModifierKeys:event]; 443cb93a386Sopenharmony_ci 444cb93a386Sopenharmony_ci const NSPoint pos = [event locationInWindow]; 445cb93a386Sopenharmony_ci const NSRect rect = [view frame]; 446cb93a386Sopenharmony_ci fWindow->onMouse(pos.x * backingScaleFactor, (rect.size.height - pos.y) * backingScaleFactor, 447cb93a386Sopenharmony_ci skui::InputState::kMove, modifiers); 448cb93a386Sopenharmony_ci} 449cb93a386Sopenharmony_ci 450cb93a386Sopenharmony_ci- (void)scrollWheel:(NSEvent *)event { 451cb93a386Sopenharmony_ci skui::ModifierKey modifiers = [self updateModifierKeys:event]; 452cb93a386Sopenharmony_ci 453cb93a386Sopenharmony_ci // TODO: support hasPreciseScrollingDeltas? 454cb93a386Sopenharmony_ci fWindow->onMouseWheel([event scrollingDeltaY], modifiers); 455cb93a386Sopenharmony_ci} 456cb93a386Sopenharmony_ci 457cb93a386Sopenharmony_ci- (void)drawRect:(NSRect)rect { 458cb93a386Sopenharmony_ci fWindow->onPaint(); 459cb93a386Sopenharmony_ci} 460cb93a386Sopenharmony_ci 461cb93a386Sopenharmony_ci@end 462