skia2/tools/sk_app/mac/Window_mac.mm
John Stiles 6fc05d8b37 Allow system key equivalents to work again.
The changes in http://review.skia.org/300182 caused cmd-Q to stop
working normally in Viewer, because the Mac window would report that it
had processed all key equivalents. This prevented the NSApp from
handling key equivalents in the menu bar. This CL now forwards on all
key equivalents to the system for processing (even as it passes them to
ImGui), allowing cmd-Q to work again.

This CL also simplifies the pattern for updating modifier keys slightly.

Change-Id: I2285839b41dd361e34694eccbc6d581662b24648
Bug: skia:10338
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/300651
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
2020-07-06 19:02:24 +00:00

442 lines
13 KiB
Plaintext

/*
* 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 <Carbon/Carbon.h>
#include "src/core/SkUtils.h"
#include "tools/sk_app/mac/WindowContextFactory_mac.h"
#include "tools/sk_app/mac/Window_mac.h"
#include "tools/skui/ModifierKey.h"
@interface WindowDelegate : NSObject<NSWindowDelegate>
- (WindowDelegate*)initWithWindow:(sk_app::Window_mac*)initWindow;
@end
@interface MainView : NSView
- (MainView*)initWithWindow:(sk_app::Window_mac*)initWindow;
@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() {
// we already have a window
if (fWindow) {
return true;
}
// Create a delegate to track certain events
WindowDelegate* delegate = [[WindowDelegate alloc] initWithWindow:this];
if (nil == delegate) {
return false;
}
// Create Cocoa window
constexpr int initialWidth = 1280;
constexpr int initialHeight = 960;
NSRect windowRect = NSMakeRect(100, 100, initialWidth, initialHeight);
NSUInteger windowStyle = (NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask |
NSMiniaturizableWindowMask);
fWindow = [[NSWindow alloc] initWithContentRect:windowRect styleMask:windowStyle
backing:NSBackingStoreBuffered defer:NO];
if (nil == fWindow) {
[delegate release];
return false;
}
// create view
MainView* view = [[MainView alloc] initWithWindow:this];
if (nil == view) {
[fWindow release];
[delegate release];
return false;
}
[fWindow setContentView:view];
[fWindow makeFirstResponder:view];
[fWindow setDelegate:delegate];
[fWindow setAcceptsMouseMovedEvents:YES];
[fWindow setRestorable:NO];
// Should be retained by window now
[view release];
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 close];
fWindow = nil;
}
}
void Window_mac::setTitle(const char* title) {
if (NSString* titleStr = [NSString stringWithUTF8String:title]) {
[fWindow setTitle:titleStr];
}
}
void Window_mac::show() {
[fWindow orderFront:nil];
[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) {
#ifdef SK_DAWN
case kDawn_BackendType:
fWindowContext = MakeDawnMTLForMac(info, fRequestedDisplayParams);
break;
#endif
#ifdef SK_VULKAN
case kVulkan_BackendType:
fWindowContext = MakeVulkanForMac(info, fRequestedDisplayParams);
break;
#endif
#ifdef SK_METAL
case kMetal_BackendType:
fWindowContext = MakeMetalForMac(info, fRequestedDisplayParams);
break;
#endif
#ifdef SK_GL
case kNativeGL_BackendType:
default:
fWindowContext = MakeGLForMac(info, fRequestedDisplayParams);
break;
#else
default:
#endif
case kRaster_BackendType:
fWindowContext = MakeRasterForMac(info, fRequestedDisplayParams);
break;
}
this->onBackendCreated();
return SkToBool(fWindowContext);
}
void Window_mac::PaintWindows() {
gWindowMap.foreach([&](Window_mac* window) {
if (window->fIsContentInvalidated) {
window->onPaint();
}
});
}
} // 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->inval();
}
- (BOOL)windowShouldClose:(NSWindow*)sender {
fWindow->closeWindow();
return FALSE;
}
@end
///////////////////////////////////////////////////////////////////////////////
static skui::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;
skui::Key fKey;
} gPair[] = {
{ kVK_Delete, skui::Key::kBack },
{ kVK_Return, skui::Key::kOK },
{ kVK_UpArrow, skui::Key::kUp },
{ kVK_DownArrow, skui::Key::kDown },
{ kVK_LeftArrow, skui::Key::kLeft },
{ kVK_RightArrow, skui::Key::kRight },
{ kVK_Tab, skui::Key::kTab },
{ kVK_PageUp, skui::Key::kPageUp },
{ kVK_PageDown, skui::Key::kPageDown },
{ kVK_Home, skui::Key::kHome },
{ kVK_End, skui::Key::kEnd },
{ kVK_ForwardDelete, skui::Key::kDelete },
{ kVK_Escape, skui::Key::kEscape },
{ kVK_Shift, skui::Key::kShift },
{ kVK_RightShift, skui::Key::kShift },
{ kVK_Control, skui::Key::kCtrl },
{ kVK_RightControl, skui::Key::kCtrl },
{ kVK_Option, skui::Key::kOption },
{ kVK_RightOption, skui::Key::kOption },
{ kVK_Command, skui::Key::kSuper },
{ kVK_RightCommand, skui::Key::kSuper },
{ kVK_ANSI_A, skui::Key::kA },
{ kVK_ANSI_C, skui::Key::kC },
{ kVK_ANSI_V, skui::Key::kV },
{ kVK_ANSI_X, skui::Key::kX },
{ kVK_ANSI_Y, skui::Key::kY },
{ kVK_ANSI_Z, skui::Key::kZ },
};
for (size_t i = 0; i < SK_ARRAY_COUNT(gPair); i++) {
if (gPair[i].fVK == vk) {
return gPair[i].fKey;
}
}
return skui::Key::kNONE;
}
static skui::ModifierKey get_modifiers(const NSEvent* event) {
NSUInteger modifierFlags = [event modifierFlags];
skui::ModifierKey modifiers = skui::ModifierKey::kNone;
if (modifierFlags & NSEventModifierFlagCommand) {
modifiers |= skui::ModifierKey::kCommand;
}
if (modifierFlags & NSEventModifierFlagShift) {
modifiers |= skui::ModifierKey::kShift;
}
if (modifierFlags & NSEventModifierFlagControl) {
modifiers |= skui::ModifierKey::kControl;
}
if (modifierFlags & NSEventModifierFlagOption) {
modifiers |= skui::ModifierKey::kOption;
}
if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) {
modifiers |= skui::ModifierKey::kFirstPress;
}
return modifiers;
}
@implementation MainView {
sk_app::Window_mac* fWindow;
// A TrackingArea prevents us from capturing events outside the view
NSTrackingArea* fTrackingArea;
// We keep track of the state of the modifier keys on each event in order to synthesize
// key-up/down events for each modifier.
skui::ModifierKey fLastModifiers;
}
- (MainView*)initWithWindow:(sk_app::Window_mac *)initWindow {
self = [super init];
fWindow = initWindow;
fTrackingArea = nil;
[self updateTrackingAreas];
return self;
}
- (void)dealloc
{
[fTrackingArea release];
[super dealloc];
}
- (BOOL)isOpaque {
return YES;
}
- (BOOL)canBecomeKeyView {
return YES;
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (void)updateTrackingAreas {
if (fTrackingArea != nil) {
[self removeTrackingArea:fTrackingArea];
[fTrackingArea release];
}
const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
NSTrackingActiveInKeyWindow |
NSTrackingEnabledDuringMouseDrag |
NSTrackingCursorUpdate |
NSTrackingInVisibleRect |
NSTrackingAssumeInside;
fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:options
owner:self
userInfo:nil];
[self addTrackingArea:fTrackingArea];
[super updateTrackingAreas];
}
- (skui::ModifierKey) updateModifierKeys:(NSEvent*) event {
using sknonstd::Any;
skui::ModifierKey modifiers = get_modifiers(event);
skui::ModifierKey changed = modifiers ^ fLastModifiers;
fLastModifiers = modifiers;
struct ModMap {
skui::ModifierKey modifier;
skui::Key key;
};
// Map each modifier bit to the equivalent skui Key and send key-up/down events.
for (const ModMap& cur : {ModMap{skui::ModifierKey::kCommand, skui::Key::kSuper},
ModMap{skui::ModifierKey::kShift, skui::Key::kShift},
ModMap{skui::ModifierKey::kControl, skui::Key::kCtrl},
ModMap{skui::ModifierKey::kOption, skui::Key::kOption}}) {
if (Any(changed & cur.modifier)) {
const skui::InputState state = Any(modifiers & cur.modifier) ? skui::InputState::kDown
: skui::InputState::kUp;
(void) fWindow->onKey(cur.key, state, modifiers);
}
}
return modifiers;
}
- (BOOL)performKeyEquivalent:(NSEvent *)event {
[self updateModifierKeys:event];
// By default, unhandled key equivalents send -keyDown events; unfortunately, they do not send
// a matching -keyUp. In other words, we can claim that we didn't handle the event and OS X will
// turn this event into a -keyDown automatically, but we need to synthesize a matching -keyUp on
// a later frame. Since we only read the modifiers and key code from the event, we can reuse
// this "key-equivalent" event as a "key up".
[self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1];
return NO;
}
- (void)keyDown:(NSEvent *)event {
skui::ModifierKey modifiers = [self updateModifierKeys:event];
skui::Key key = get_key([event keyCode]);
if (key != skui::Key::kNONE) {
if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) {
if (skui::Key::kEscape == key) {
[NSApp terminate:fWindow->window()];
}
}
}
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) fWindow->onChar((SkUnichar) charBuffer[i], modifiers);
}
delete [] charBuffer;
}
}
- (void)keyUp:(NSEvent *)event {
skui::ModifierKey modifiers = [self updateModifierKeys:event];
skui::Key key = get_key([event keyCode]);
if (key != skui::Key::kNONE) {
(void) fWindow->onKey(key, skui::InputState::kUp, modifiers);
}
}
-(void)flagsChanged:(NSEvent *)event {
[self updateModifierKeys:event];
}
- (void)mouseDown:(NSEvent *)event {
skui::ModifierKey modifiers = [self updateModifierKeys:event];
const NSPoint pos = [event locationInWindow];
const NSRect rect = [fWindow->window().contentView frame];
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kDown, modifiers);
}
- (void)mouseUp:(NSEvent *)event {
skui::ModifierKey modifiers = [self updateModifierKeys:event];
const NSPoint pos = [event locationInWindow];
const NSRect rect = [fWindow->window().contentView frame];
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kUp, modifiers);
}
- (void)mouseDragged:(NSEvent *)event {
[self updateModifierKeys:event];
[self mouseMoved:event];
}
- (void)mouseMoved:(NSEvent *)event {
skui::ModifierKey modifiers = [self updateModifierKeys:event];
const NSPoint pos = [event locationInWindow];
const NSRect rect = [fWindow->window().contentView frame];
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kMove, modifiers);
}
- (void)scrollWheel:(NSEvent *)event {
skui::ModifierKey modifiers = [self updateModifierKeys:event];
// TODO: support hasPreciseScrollingDeltas?
fWindow->onMouseWheel([event scrollingDeltaY], modifiers);
}
- (void)drawRect:(NSRect)rect {
fWindow->onPaint();
}
@end