/* * Copyright 2019 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "SkUtils.h" #include "WindowContextFactory_mac.h" #include "Window_mac.h" @interface WindowDelegate : NSObject - (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow; @end @interface MainView : NSView @end /////////////////////////////////////////////////////////////////////////////// using sk_app::Window; namespace sk_app { SkTDynamicHash Window_mac::gWindowMap; Window* Window::CreateNativeWindow(void*) { Window_mac* window = new Window_mac(); if (!window->initWindow()) { delete window; return nullptr; } return window; } bool Window_mac::initWindow() { if (fRequestedDisplayParams.fMSAASampleCount != fMSAASampleCount) { this->closeWindow(); } // we already have a window if (fWindow) { return true; } constexpr int initialWidth = 1280; constexpr int initialHeight = 960; NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask | NSMiniaturizableWindowMask); NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight); fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle backing:NSBackingStoreBuffered defer:NO]; if (nil == fWindow) { return false; } [fWindow setAcceptsMouseMovedEvents:YES]; WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this]; [fWindow setDelegate:delegate]; [delegate release]; // create view MainView* view = [[MainView alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)] ; if (nil == view) { [fWindow release]; fWindow = nil; return false; } // attach view to window [fWindow setContentView:view]; fWindowNumber = fWindow.windowNumber; gWindowMap.add(this); return true; } void Window_mac::closeWindow() { if (nil != fWindow) { gWindowMap.remove(fWindowNumber); if (sk_app::Window_mac::gWindowMap.count() < 1) { [NSApp terminate:fWindow]; } [fWindow release]; fWindow = nil; } } void Window_mac::setTitle(const char* title) { NSString *titleString = [NSString stringWithCString:title encoding:NSUTF8StringEncoding]; [fWindow setTitle:titleString]; } void Window_mac::show() { [NSApp activateIgnoringOtherApps:YES]; [fWindow makeKeyAndOrderFront:NSApp]; } bool Window_mac::attach(BackendType attachType) { this->initWindow(); window_context_factory::MacWindowInfo info; info.fMainView = [fWindow contentView]; switch (attachType) { case kRaster_BackendType: fWindowContext = NewRasterForMac(info, fRequestedDisplayParams); break; #ifdef SK_VULKAN case kVulkan_BackendType: fWindowContext = NewVulkanForMac(info, fRequestedDisplayParams); break; #endif #ifdef SK_METAL case kMetal_BackendType: fWindowContext = NewMetalForMac(info, fRequestedDisplayParams); break; #endif case kNativeGL_BackendType: default: fWindowContext = NewGLForMac(info, fRequestedDisplayParams); break; } this->onBackendCreated(); return (SkToBool(fWindowContext)); } static Window::Key get_key(unsigned short vk) { // This will work with an ANSI QWERTY keyboard. // Something more robust would be needed to support alternate keyboards. static const struct { unsigned short fVK; Window::Key fKey; } gPair[] = { { 0x33, Window::Key::kBack }, { 0x24, Window::Key::kOK }, { 0x7E, Window::Key::kUp }, { 0x7D, Window::Key::kDown }, { 0x7B, Window::Key::kLeft }, { 0x7C, Window::Key::kRight }, { 0x30, Window::Key::kTab }, { 0x74, Window::Key::kPageUp }, { 0x79, Window::Key::kPageDown }, { 0x73, Window::Key::kHome }, { 0x77, Window::Key::kEnd }, { 0x75, Window::Key::kDelete }, { 0x35, Window::Key::kEscape }, { 0x38, Window::Key::kShift }, { 0x3C, Window::Key::kShift }, { 0x3B, Window::Key::kCtrl }, { 0x3E, Window::Key::kCtrl }, { 0x3A, Window::Key::kOption }, { 0x3D, Window::Key::kOption }, { 0x00, Window::Key::kA }, { 0x08, Window::Key::kC }, { 0x09, Window::Key::kV }, { 0x07, Window::Key::kX }, { 0x10, Window::Key::kY }, { 0x06, Window::Key::kZ }, }; for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) { if (gPair[i].fVK == vk) { return gPair[i].fKey; } } return Window::Key::kNONE; } static uint32_t get_modifiers(const NSEvent* event) { NSUInteger modifierFlags = [event modifierFlags]; auto modifiers = 0; if (modifierFlags & NSEventModifierFlagShift) { modifiers |= Window::kShift_ModifierKey; } if (modifierFlags & NSEventModifierFlagControl) { modifiers |= Window::kControl_ModifierKey; } if (modifierFlags & NSEventModifierFlagOption) { modifiers |= Window::kOption_ModifierKey; } if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && NO == [event isARepeat]) { modifiers |= Window::kFirstPress_ModifierKey; } return modifiers; } void Window_mac::PaintWindows() { SkTDynamicHash::Iter iter(&gWindowMap); while (!iter.done()) { if ((*iter).fIsContentInvalidated) { (*iter).onPaint(); } ++iter; } } void Window_mac::HandleWindowEvent(const NSEvent* event) { Window_mac* win = gWindowMap.find(event.window.windowNumber); if (win) { win->handleEvent(event); } } void Window_mac::handleEvent(const NSEvent* event) { switch (event.type) { case NSEventTypeKeyDown: { Window::Key key = get_key([event keyCode]); if (key != Window::Key::kNONE) { if (!this->onKey(key, Window::kDown_InputState, get_modifiers(event))) { if (Window::Key::kEscape == key) { [NSApp terminate:fWindow]; } } } NSString* characters = [event charactersIgnoringModifiers]; NSUInteger len = [characters length]; if (len > 0) { unichar* charBuffer = new unichar[len+1]; [characters getCharacters:charBuffer range:NSMakeRange(0, len)]; for (NSUInteger i = 0; i < len; ++i) { (void) this->onChar((SkUnichar) charBuffer[i], get_modifiers(event)); } delete [] charBuffer; } break; } case NSEventTypeKeyUp: { Window::Key key = get_key([event keyCode]); if (key != Window::Key::kNONE) { (void) this->onKey(key, Window::kUp_InputState, get_modifiers(event)); } break; } case NSEventTypeLeftMouseDown: { const NSPoint pos = [event locationInWindow]; const NSRect rect = [fWindow.contentView frame]; if (NSPointInRect(pos, rect)) { // This might be a resize event -- until we know we'll store the event // and reset later if need be fIsMouseDown = true; fMouseDownPos = pos; fMouseModifiers = get_modifiers(event); this->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState, fMouseModifiers); } break; } case NSEventTypeLeftMouseUp: { const NSPoint pos = [event locationInWindow]; const NSRect rect = [fWindow.contentView frame]; this->onMouse(pos.x, rect.size.height - pos.y, Window::kUp_InputState, get_modifiers(event)); break; } case NSEventTypeMouseMoved: case NSEventTypeLeftMouseDragged: { const NSPoint pos = [event locationInWindow]; const NSRect rect = [fWindow.contentView frame]; this->onMouse(pos.x, rect.size.height - pos.y, Window::kMove_InputState, get_modifiers(event)); break; } case NSEventTypeScrollWheel: // TODO: support hasPreciseScrollingDeltas? this->onMouseWheel([event scrollingDeltaY], get_modifiers(event)); break; default: break; } } void Window_mac::resetMouse() { if (fIsMouseDown) { // We're resizing so just send a mouse up event in the same place const NSRect rect = [fWindow.contentView frame]; this->onMouse(fMouseDownPos.x, rect.size.height - fMouseDownPos.y, Window::kUp_InputState, fMouseModifiers); fIsMouseDown = false; } } } // namespace sk_app /////////////////////////////////////////////////////////////////////////////// @implementation WindowDelegate { sk_app::Window_mac* fWindow; } - (WindowDelegate*)initWithWindow:(sk_app::Window_mac *)initWindow { fWindow = initWindow; return self; } - (void)windowDidResize:(NSNotification *)notification { const NSRect mainRect = [fWindow->window().contentView bounds]; fWindow->onResize(mainRect.size.width, mainRect.size.height); fWindow->resetMouse(); } - (BOOL)windowShouldClose:(NSWindow*)sender { fWindow->closeWindow(); return FALSE; } @end /////////////////////////////////////////////////////////////////////////////// @implementation MainView - (BOOL)isOpaque { return YES; } - (BOOL)canBecomeKeyView { return YES; } - (BOOL)acceptsFirstResponder { return YES; } // We keep these around to prevent beeping when the system can't determine // where the focus for key events is. - (void)keyDown:(NSEvent *)event { } - (void)keyUp:(NSEvent *)event { } @end