| /* |
| * 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<NSWindowDelegate> |
| |
| - (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow; |
| |
| @end |
| |
| @interface MainView : NSView |
| @end |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| using sk_app::Window; |
| |
| namespace sk_app { |
| |
| SkTDynamicHash<Window_mac, NSInteger> 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_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<Window_mac, NSInteger>::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]; |
| this->onMouse(pos.x, rect.size.height - pos.y, Window::kDown_InputState, |
| get_modifiers(event)); |
| 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; |
| } |
| } |
| |
| } // 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); |
| } |
| |
| - (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 |