Fix modifier key handling in OS X to allow command-keys to work.

A variety of modifier key handling issues are addressed in this CL:
- Added a skui::Key for the Super key (this is ImGui's name for command)
- Added OS X event handling for `flagsChanged` (sent when modifier keys
  are pressed)
- OS X manually tracks modifier key state and sends key-up and key-down
  events to the ImGuiLayer as necessary
- OS X does not send key-up events when hotkeys are pressed, so these
  are manually synthesized and sent to ImGui (otherwise hotkeys are
  repeated forever)
- Replaced hardcoded Virtual Key valus in OS X code with named constants
- Our custom bitmask type was lacking the ability to XOR

This CL does NOT enable the OS X clipboard; this uses the ImGui internal
clipboard.

Change-Id: I76b55215858bfb6441dbef18ad638426fa8bc073
Bug: skia:10338
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/300182
Commit-Queue: Jim Van Verth <jvanverth@google.com>
Reviewed-by: Jim Van Verth <jvanverth@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
This commit is contained in:
John Stiles 2020-07-01 11:12:19 -04:00 committed by Skia Commit-Bot
parent e381036051
commit d2f870c911
4 changed files with 121 additions and 47 deletions

View File

@ -40,6 +40,17 @@ std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E&> constexpr operator&=(E
return l = l & r;
}
template <typename E>
std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E> constexpr operator^(E l, E r) {
using U = std::underlying_type_t<E>;
return static_cast<E>(static_cast<U>(l) ^ static_cast<U>(r));
}
template <typename E>
std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E&> constexpr operator^=(E& l, E r) {
return l = l ^ r;
}
template <typename E>
std::enable_if_t<sknonstd::is_bitmask_enum<E>::value, E> constexpr operator~(E e) {
return static_cast<E>(~static_cast<std::underlying_type_t<E>>(e));

View File

@ -5,6 +5,8 @@
* 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"
@ -68,7 +70,7 @@ bool Window_mac::initWindow() {
}
// create view
MainView* view = [[MainView alloc] initWithWindow:this] ;
MainView* view = [[MainView alloc] initWithWindow:this];
if (nil == view) {
[fWindow release];
[delegate release];
@ -102,8 +104,9 @@ void Window_mac::closeWindow() {
}
void Window_mac::setTitle(const char* title) {
NSString *titleString = [NSString stringWithCString:title encoding:NSUTF8StringEncoding];
[fWindow setTitle:titleString];
if (NSString* titleStr = [NSString stringWithUTF8String:title]) {
[fWindow setTitle:titleStr];
}
}
void Window_mac::show() {
@ -148,7 +151,7 @@ bool Window_mac::attach(BackendType attachType) {
}
this->onBackendCreated();
return (SkToBool(fWindowContext));
return SkToBool(fWindowContext);
}
void Window_mac::PaintWindows() {
@ -195,34 +198,37 @@ static skui::Key get_key(unsigned short vk) {
// Something more robust would be needed to support alternate keyboards.
static const struct {
unsigned short fVK;
skui::Key fKey;
skui::Key fKey;
} gPair[] = {
{ 0x33, skui::Key::kBack },
{ 0x24, skui::Key::kOK },
{ 0x7E, skui::Key::kUp },
{ 0x7D, skui::Key::kDown },
{ 0x7B, skui::Key::kLeft },
{ 0x7C, skui::Key::kRight },
{ 0x30, skui::Key::kTab },
{ 0x74, skui::Key::kPageUp },
{ 0x79, skui::Key::kPageDown },
{ 0x73, skui::Key::kHome },
{ 0x77, skui::Key::kEnd },
{ 0x75, skui::Key::kDelete },
{ 0x35, skui::Key::kEscape },
{ 0x38, skui::Key::kShift },
{ 0x3C, skui::Key::kShift },
{ 0x3B, skui::Key::kCtrl },
{ 0x3E, skui::Key::kCtrl },
{ 0x3A, skui::Key::kOption },
{ 0x3D, skui::Key::kOption },
{ 0x00, skui::Key::kA },
{ 0x08, skui::Key::kC },
{ 0x09, skui::Key::kV },
{ 0x07, skui::Key::kX },
{ 0x10, skui::Key::kY },
{ 0x06, skui::Key::kZ },
{ 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;
@ -249,8 +255,7 @@ static skui::ModifierKey get_modifiers(const NSEvent* event) {
modifiers |= skui::ModifierKey::kOption;
}
if ((NSKeyDown == [event type] || NSKeyUp == [event type]) &&
NO == [event isARepeat]) {
if ((NSKeyDown == [event type] || NSKeyUp == [event type]) && ![event isARepeat]) {
modifiers |= skui::ModifierKey::kFirstPress;
}
@ -261,6 +266,9 @@ static skui::ModifierKey get_modifiers(const NSEvent* event) {
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 {
@ -306,18 +314,54 @@ static skui::ModifierKey get_modifiers(const NSEvent* event) {
NSTrackingAssumeInside;
fTrackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:options
owner:self
userInfo:nil];
options:options
owner:self
userInfo:nil];
[self addTrackingArea:fTrackingArea];
[super updateTrackingAreas];
}
- (void)updateModifierKeys:(skui::ModifierKey) modifiers {
using sknonstd::Any;
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);
}
}
}
- (BOOL)performKeyEquivalent:(NSEvent *)event {
// By default, key equivalents send -keyDown events without a matching -keyUp. Let's synthesize
// a matching -keyUp on a later frame. We only read the modifiers and key code from the event,
// so we can get away with reusing a "key down" event as a "key up".
[self keyDown:event];
[self performSelector:@selector(keyUp:) withObject:event afterDelay:0.1];
return YES;
}
- (void)keyDown:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
skui::Key key = get_key([event keyCode]);
if (key != skui::Key::kNONE) {
if (!fWindow->onKey(key, skui::InputState::kDown, get_modifiers(event))) {
if (!fWindow->onKey(key, skui::InputState::kDown, modifiers)) {
if (skui::Key::kEscape == key) {
[NSApp terminate:fWindow->window()];
}
@ -330,31 +374,43 @@ static skui::ModifierKey get_modifiers(const NSEvent* event) {
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], get_modifiers(event));
(void) fWindow->onChar((SkUnichar) charBuffer[i], modifiers);
}
delete [] charBuffer;
}
}
- (void)keyUp:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
skui::Key key = get_key([event keyCode]);
if (key != skui::Key::kNONE) {
(void) fWindow->onKey(key, skui::InputState::kUp, get_modifiers(event));
(void) fWindow->onKey(key, skui::InputState::kUp, modifiers);
}
}
-(void)flagsChanged:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
}
- (void)mouseDown:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
const NSPoint pos = [event locationInWindow];
const NSRect rect = [fWindow->window().contentView frame];
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kDown,
get_modifiers(event));
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kDown, modifiers);
}
- (void)mouseUp:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
const NSPoint pos = [event locationInWindow];
const NSRect rect = [fWindow->window().contentView frame];
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kUp,
get_modifiers(event));
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kUp, modifiers);
}
- (void)mouseDragged:(NSEvent *)event {
@ -362,15 +418,20 @@ static skui::ModifierKey get_modifiers(const NSEvent* event) {
}
- (void)mouseMoved:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
const NSPoint pos = [event locationInWindow];
const NSRect rect = [fWindow->window().contentView frame];
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kMove,
get_modifiers(event));
fWindow->onMouse(pos.x, rect.size.height - pos.y, skui::InputState::kMove, modifiers);
}
- (void)scrollWheel:(NSEvent *)event {
skui::ModifierKey modifiers = get_modifiers(event);
[self updateModifierKeys:modifiers];
// TODO: support hasPreciseScrollingDeltas?
fWindow->onMouseWheel([event scrollingDeltaY], get_modifiers(event));
fWindow->onMouseWheel([event scrollingDeltaY], modifiers);
}
- (void)drawRect:(NSRect)rect {

View File

@ -41,6 +41,7 @@ enum class Key {
kShift,
kCtrl,
kOption, // AKA Alt
kSuper, // AKA Command
kA,
kC,
kV,

View File

@ -106,9 +106,10 @@ void ImGuiLayer::onPrePaint() {
io.DisplaySize.x = static_cast<float>(fWindow->width());
io.DisplaySize.y = static_cast<float>(fWindow->height());
io.KeyAlt = io.KeysDown[static_cast<int>(skui::Key::kOption)];
io.KeyCtrl = io.KeysDown[static_cast<int>(skui::Key::kCtrl)];
io.KeyAlt = io.KeysDown[static_cast<int>(skui::Key::kOption)];
io.KeyCtrl = io.KeysDown[static_cast<int>(skui::Key::kCtrl)];
io.KeyShift = io.KeysDown[static_cast<int>(skui::Key::kShift)];
io.KeySuper = io.KeysDown[static_cast<int>(skui::Key::kSuper)];
ImGui::NewFrame();
}