6fc05d8b37
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>
442 lines
13 KiB
Plaintext
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
|