diff --git a/examples/mpm128.py b/examples/mpm128.py index 2e7f207080b9e..ffdfdacf3da4a 100644 --- a/examples/mpm128.py +++ b/examples/mpm128.py @@ -98,7 +98,7 @@ def reset(): Jp.fill(1) C.fill(0) -print("[Hint] Use WSAD/arrow keys to control gravity. Use left/right mouse bottons to attract/repel. (OS X not yet supported)") +print("[Hint] Use WSAD/arrow keys to control gravity. Use left/right mouse bottons to attract/repel.") gui = ti.GUI("Taichi MLS-MPM-128", res=512, background_color=0x112F41) reset() diff --git a/taichi/gui/cocoa.cpp b/taichi/gui/cocoa.cpp index fc8975b03379b..dd0736565619d 100644 --- a/taichi/gui/cocoa.cpp +++ b/taichi/gui/cocoa.cpp @@ -6,6 +6,11 @@ #include +#include +#include +#include +#include + // https://stackoverflow.com/questions/4356441/mac-os-cocoa-draw-a-simple-pixel-on-a-canvas // http://cocoadevcentral.com/d/intro_to_quartz/ // Modified based on @@ -15,6 +20,7 @@ // https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc #include +#include #include #include #include @@ -23,6 +29,109 @@ namespace { using taichi::mac::call; using taichi::mac::cast_call; using taichi::mac::clscall; + +std::string str_tolower(std::string s) { + // https://en.cppreference.com/w/cpp/string/byte/tolower + std::transform(s.begin(), s.end(), s.begin(), + [](unsigned char c) { return std::tolower(c); }); + return s; +} + +std::optional try_get_alnum(ushort keycode) { +// Can someone tell me the reason why Apple didn't make these consecutive... +#define CASE(i) \ + { kVK_ANSI_##i, str_tolower(#i) } + static const std::unordered_map key2str = { + CASE(0), CASE(1), CASE(2), CASE(3), CASE(4), CASE(5), CASE(6), CASE(7), + CASE(8), CASE(9), CASE(A), CASE(B), CASE(C), CASE(D), CASE(E), CASE(F), + CASE(G), CASE(H), CASE(I), CASE(J), CASE(K), CASE(L), CASE(M), CASE(N), + CASE(O), CASE(P), CASE(Q), CASE(R), CASE(S), CASE(T), CASE(U), CASE(V), + CASE(W), CASE(X), CASE(Y), CASE(Z), + }; +#undef CASE + const auto iter = key2str.find(keycode); + if (iter == key2str.end()) { + return std::nullopt; + } + return iter->second; +} + +std::optional try_get_fnkey(ushort keycode) { + // Or these... +#define STRINGIFY(x) #x +#define CASE(i) \ + { kVK_F##i, STRINGIFY(F##i) } + static const std::unordered_map key2str = { + CASE(1), CASE(2), CASE(3), CASE(4), CASE(5), CASE(6), + CASE(7), CASE(8), CASE(9), CASE(10), CASE(11), CASE(12), + CASE(13), CASE(14), CASE(15), CASE(16), + }; +#undef CASE +#undef STRINGIFY + const auto iter = key2str.find(keycode); + if (iter == key2str.end()) { + return std::nullopt; + } + return iter->second; +} + +std::string lookup_keysym(ushort keycode) { + // Full enum definition: + // https://github.com/phracker/MacOSX-SDKs/blob/ef9fe35d5691b6dd383c8c46d867a499817a01b6/MacOSX10.6.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h#L198-L315 + switch (keycode) { + case kVK_LeftArrow: + return "Left"; + case kVK_RightArrow: + return "Right"; + case kVK_UpArrow: + return "Up"; + case kVK_DownArrow: + return "Down"; + case kVK_Tab: + return "Tab"; + case kVK_Return: + return "Return"; + // Mac Delete = Backspace on other platforms + // Mac ForwardDelete (Fn + Delete) = Delete on other platforms + case kVK_Delete: + return "BackSpace"; + case kVK_Escape: + return "Escape"; + case kVK_Shift: + return "Shift_L"; + case kVK_RightShift: + return "Shift_R"; + // Shall we interpret Command key as Ctrl? + case kVK_Control: + return "Control_L"; + case kVK_RightControl: + return "Control_R"; + // Mac Option = Alt on other platforms + case kVK_Option: + return "Alt_L"; + case kVK_RightOption: + return "Alt_R"; + case kVK_CapsLock: + return "Caps_Lock"; + default: + break; + } + auto val_opt = try_get_alnum(keycode); + if (val_opt.has_value()) { + return *val_opt; + } + val_opt = try_get_fnkey(keycode); + if (val_opt.has_value()) { + return *val_opt; + } + return "Vk" + std::to_string((int)keycode); +} + +// TODO(k-ye): Define all the magic numbers for Obj-C enums here +constexpr int NSApplicationActivationPolicyRegular = 0; +constexpr int NSEventTypeKeyDown = 10; +constexpr int NSEventTypeKeyUp = 11; + } // namespace extern id NSApp; @@ -113,6 +222,20 @@ void GUI::create_window() { fprintf(stderr, "Failed to initialized NSApplication.\nterminating.\n"); return; } + // I finally found how to bring the NSWindow to the front and to handle + // keyboard events in these posts: + // https://stackoverflow.com/a/11010614/12003165 + // http://www.cocoawithlove.com/2010/09/minimalist-cocoa-programming.html + // + // The problem was that, a Cocoa app without NIB files (app bundle, + // info.plist, whatever the meta files are) by default has a policy of + // NSApplicationActivationPolicyProhibited. + // (https://developer.apple.com/documentation/appkit/nsapplicationactivationpolicy/nsapplicationactivationpolicyprohibited?language=objc) + call(NSApp, "setActivationPolicy:", NSApplicationActivationPolicyRegular); + // This doesn't seem necessary, but in case there's some weird bug causing the + // Window not to be brought to the front, try enable this. + // https://stackoverflow.com/a/7460187/12003165 + // call(NSApp, "activateIgnoringOtherApps:", YES); img_data_length = width * height * 4; img_data.resize(img_data_length); auto appDelObj = clscall("AppDelegate", "alloc"); @@ -145,14 +268,28 @@ void GUI::process_event() { call(NSApp, "sendEvent:", event); call(NSApp, "updateWindows"); auto p = cast_call(event, "locationInWindow"); + ushort keycode = 0; + std::string keysym; switch (event_type) { case 1: // NSLeftMouseDown set_mouse_pos(p.x, p.y); mouse_event(MouseEvent{MouseEvent::Type::press, cursor_pos}); + key_events.push_back( + GUI::KeyEvent{GUI::KeyEvent::Type::press, "LMB", cursor_pos}); break; case 2: // NSLeftMouseUp set_mouse_pos(p.x, p.y); mouse_event(MouseEvent{MouseEvent::Type::release, cursor_pos}); + key_events.push_back( + GUI::KeyEvent{GUI::KeyEvent::Type::release, "LMB", cursor_pos}); + break; + case 3: // NSEventTypeRightMouseDown + key_events.push_back( + GUI::KeyEvent{GUI::KeyEvent::Type::press, "RMB", cursor_pos}); + break; + case 4: // NSEventTypeRightMouseUp + key_events.push_back( + GUI::KeyEvent{GUI::KeyEvent::Type::release, "RMB", cursor_pos}); break; case 5: // NSMouseMoved case 6: // NSLeftMouseDragged @@ -161,6 +298,15 @@ void GUI::process_event() { set_mouse_pos(p.x, p.y); mouse_event(MouseEvent{MouseEvent::Type::move, Vector2i(p.x, p.y)}); break; + case NSEventTypeKeyDown: + case NSEventTypeKeyUp: + keycode = cast_call(event, "keyCode"); + keysym = lookup_keysym(keycode); + auto kev_type = (event_type == NSEventTypeKeyDown) + ? KeyEvent::Type::press + : KeyEvent::Type::release; + key_events.push_back(KeyEvent{kev_type, keysym, cursor_pos}); + break; } } else { break;