diff --git a/include/event.hpp b/include/event.hpp index a816088b..4e314c18 100644 --- a/include/event.hpp +++ b/include/event.hpp @@ -111,13 +111,37 @@ struct PositionBase { /** An event prototype with a GLFW key. */ struct KeyBase { - /** GLFW_KEY_* */ + /** The key corresponding to what it would be called in its position on a QWERTY US keyboard. + For example, the WASD directional keys used for first-person shooters will always be reported as "WASD", regardless if they say "ZQSD" on an AZERTY keyboard. + You should usually not use these for printable characters such as "Ctrl+V" key commands. Instead, use `keyName`. + You *should* use these for non-printable keys, such as Escape, arrow keys, Home, F1-12, etc. + You should also use this for Enter, Tab, and Space. Although they are printable keys, they do not appear in `keyName`. + See GLFW_KEY_* for the list of possible values. + */ int key; - /** GLFW_KEY_*. You should usually use `key` instead. */ + /** Platform-dependent "software" key code. + This variable is only included for completion. There should be no reason for you to use this. + You should instead use `key` (for non-printable characters) or `keyName` (for printable characters). + Values are platform independent and can change between different keyboards or keyboard layouts on the same OS. + */ int scancode; - /** GLFW_RELEASE, GLFW_PRESS, GLFW_REPEAT, or RACK_HELD */ + /** String containing the lowercase key name, if it produces a printable character. + This is the only variable that correctly represents the label printed on any keyboard layout, whether it's QWERTY, AZERTY, QWERTZ, Dvorak, etc. + For example, if the user presses the key labeled "q" regardless of the key position, `keyName` will be "q". + For non-printable characters this is an empty string. + Enter, Tab, and Space do not give a `keyName`. Use `key` instead. + Shift has no effect on the key name. Shift+1 results in "1", Shift+q results in "q", etc. + */ + std::string keyName; + /** The type of event occurring with the key. + Possible values are GLFW_RELEASE, GLFW_PRESS, GLFW_REPEAT, or RACK_HELD. + RACK_HELD is sent every frame while the key is held. + */ int action; - /** GLFW_MOD_* */ + /** Bitwise OR of key modifiers, such as Ctrl or Shift. + Use (mods & RACK_MOD_MASK) == RACK_MOD_CTRL to check for Ctrl on Linux and Windows but Cmd on Mac. + See GLFW_MOD_* for the list of possible values. + */ int mods; }; diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index 224e3247..beafe558 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -623,19 +623,17 @@ inline void TagItem::step() { inline void BrowserSearchField::onSelectKey(const event::SelectKey& e) { if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { - switch (e.key) { - case GLFW_KEY_ESCAPE: { - BrowserOverlay* overlay = getAncestorOfType(); - overlay->hide(); + if (e.key == GLFW_KEY_ESCAPE) { + BrowserOverlay* overlay = getAncestorOfType(); + overlay->hide(); + e.consume(this); + } + if (e.key == GLFW_KEY_BACKSPACE) { + if (text == "") { + ModuleBrowser* browser = getAncestorOfType(); + browser->clear(); e.consume(this); - } break; - case GLFW_KEY_BACKSPACE: { - if (text == "") { - ModuleBrowser* browser = getAncestorOfType(); - browser->clear(); - e.consume(this); - } - } break; + } } } diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index d7c69692..7da17c6f 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -397,61 +397,40 @@ void ModuleWidget::onHoverKey(const event::HoverKey& e) { return; if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { - switch (e.key) { - case GLFW_KEY_I: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - resetAction(); - e.consume(this); - } - } break; - case GLFW_KEY_R: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - randomizeAction(); - e.consume(this); - } - } break; - case GLFW_KEY_C: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - copyClipboard(); - e.consume(this); - } - } break; - case GLFW_KEY_V: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - pasteClipboardAction(); - e.consume(this); - } - } break; - case GLFW_KEY_D: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - cloneAction(); - e.consume(this); - } - } break; - case GLFW_KEY_U: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - disconnectAction(); - e.consume(this); - } - } break; - case GLFW_KEY_E: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - bypassAction(); - e.consume(this); - } - } break; + if (e.keyName == "i" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + resetAction(); + e.consume(this); + } + if (e.keyName == "r" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + randomizeAction(); + e.consume(this); + } + if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + copyClipboard(); + e.consume(this); + } + if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + pasteClipboardAction(); + e.consume(this); + } + if (e.keyName == "d" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + cloneAction(); + e.consume(this); + } + if (e.keyName == "u" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + disconnectAction(); + e.consume(this); + } + if (e.keyName == "e" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + bypassAction(); + e.consume(this); } } if (e.action == RACK_HELD) { - switch (e.key) { - case GLFW_KEY_DELETE: - case GLFW_KEY_BACKSPACE: { - if ((e.mods & RACK_MOD_MASK) == 0) { - removeAction(); - e.consume(NULL); - } - } break; + if ((e.key == GLFW_KEY_DELETE || e.key == GLFW_KEY_BACKSPACE) && (e.mods & RACK_MOD_MASK) == 0) { + removeAction(); + e.consume(NULL); } } } diff --git a/src/app/RackScrollWidget.cpp b/src/app/RackScrollWidget.cpp index 532fd5f4..1e88dfd2 100644 --- a/src/app/RackScrollWidget.cpp +++ b/src/app/RackScrollWidget.cpp @@ -108,23 +108,21 @@ void RackScrollWidget::onHoverKey(const event::HoverKey& e) { arrowSpeed /= 4.0; if (e.action == RACK_HELD) { - switch (e.key) { - case GLFW_KEY_LEFT: { - offset.x -= arrowSpeed; - e.consume(this); - } break; - case GLFW_KEY_RIGHT: { - offset.x += arrowSpeed; - e.consume(this); - } break; - case GLFW_KEY_UP: { - offset.y -= arrowSpeed; - e.consume(this); - } break; - case GLFW_KEY_DOWN: { - offset.y += arrowSpeed; - e.consume(this); - } break; + if (e.key == GLFW_KEY_LEFT) { + offset.x -= arrowSpeed; + e.consume(this); + } + if (e.key == GLFW_KEY_RIGHT) { + offset.x += arrowSpeed; + e.consume(this); + } + if (e.key == GLFW_KEY_UP) { + offset.y -= arrowSpeed; + e.consume(this); + } + if (e.key == GLFW_KEY_DOWN) { + offset.y += arrowSpeed; + e.consume(this); } } } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 367bfd32..a227a6ab 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -121,13 +121,9 @@ void RackWidget::onHoverKey(const event::HoverKey& e) { return; if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { - switch (e.key) { - case GLFW_KEY_V: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - pastePresetClipboardAction(); - e.consume(this); - } - } break; + if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + pastePresetClipboardAction(); + e.consume(this); } } } diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 8bc1e260..ddc7c400 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -97,39 +97,40 @@ void Scene::onHoverKey(const event::HoverKey& e) { return; if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { - if (e.key == GLFW_KEY_N && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + // DEBUG("key '%d '%c' scancode %d '%c' keyName '%s'", e.key, e.key, e.scancode, e.scancode, e.keyName.c_str()); + if (e.keyName == "n" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { APP->patch->loadTemplateDialog(); e.consume(this); } - else if (e.key == GLFW_KEY_Q && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "q" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { APP->window->close(); e.consume(this); } - else if (e.key == GLFW_KEY_O && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { APP->patch->loadDialog(); e.consume(this); } - else if (e.key == GLFW_KEY_O && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + if (e.keyName == "o" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { APP->patch->revertDialog(); e.consume(this); } - else if (e.key == GLFW_KEY_S && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { APP->patch->saveDialog(); e.consume(this); } - else if (e.key == GLFW_KEY_S && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + if (e.keyName == "s" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { APP->patch->saveAsDialog(); e.consume(this); } - else if (e.key == GLFW_KEY_Z && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { APP->history->undo(); e.consume(this); } - else if (e.key == GLFW_KEY_Z && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { + if (e.keyName == "z" && (e.mods & RACK_MOD_MASK) == (RACK_MOD_CTRL | GLFW_MOD_SHIFT)) { APP->history->redo(); e.consume(this); } - else if ((e.key == GLFW_KEY_MINUS || e.key == GLFW_KEY_KP_SUBTRACT) && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "-" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { float zoom = settings::zoom; zoom *= 2; zoom = std::ceil(zoom - 0.01f) - 1; @@ -137,7 +138,8 @@ void Scene::onHoverKey(const event::HoverKey& e) { settings::zoom = zoom; e.consume(this); } - else if ((e.key == GLFW_KEY_EQUAL || e.key == GLFW_KEY_KP_ADD) && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + // Numpad has a "+" key, but the main keyboard section hides it under "=" + if ((e.keyName == "=" || e.keyName == "+") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { float zoom = settings::zoom; zoom *= 2; zoom = std::floor(zoom + 0.01f) + 1; @@ -145,31 +147,31 @@ void Scene::onHoverKey(const event::HoverKey& e) { settings::zoom = zoom; e.consume(this); } - else if ((e.key == GLFW_KEY_0 || e.key == GLFW_KEY_KP_0) && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if ((e.keyName == "0") && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { settings::zoom = 0.f; e.consume(this); } - else if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { + if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { moduleBrowser->show(); e.consume(this); } - else if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_F1 && (e.mods & RACK_MOD_MASK) == 0) { std::thread t([] { system::openBrowser("https://vcvrack.com/manual/"); }); t.detach(); e.consume(this); } - else if (e.key == GLFW_KEY_F3 && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_F3 && (e.mods & RACK_MOD_MASK) == 0) { settings::cpuMeter ^= true; e.consume(this); } - else if (e.key == GLFW_KEY_F11 && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_F11 && (e.mods & RACK_MOD_MASK) == 0) { APP->window->setFullScreen(!APP->window->isFullScreen()); e.consume(this); } // Alternate key command for exiting fullscreen, since F11 doesn't work reliably on Mac due to "Show desktop" OS binding. - else if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { if (APP->window->isFullScreen()) APP->window->setFullScreen(false); e.consume(this); diff --git a/src/event.cpp b/src/event.cpp index 1c93a491..7471ba79 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -303,6 +303,9 @@ bool State::handleKey(math::Vec pos, int key, int scancode, int action, int mods eSelectKey.context = &cSelectKey; eSelectKey.key = key; eSelectKey.scancode = scancode; + const char* keyName = glfwGetKeyName(key, scancode); + if (keyName) + eSelectKey.keyName = keyName; eSelectKey.action = action; eSelectKey.mods = mods; selectedWidget->onSelectKey(eSelectKey); @@ -317,6 +320,9 @@ bool State::handleKey(math::Vec pos, int key, int scancode, int action, int mods eHoverKey.pos = pos; eHoverKey.key = key; eHoverKey.scancode = scancode; + const char* keyName = glfwGetKeyName(key, scancode); + if (keyName) + eHoverKey.keyName = keyName; eHoverKey.action = action; eHoverKey.mods = mods; rootWidget->onHoverKey(eHoverKey); diff --git a/src/ui/ScrollWidget.cpp b/src/ui/ScrollWidget.cpp index 4550a15d..c9fc3d2a 100644 --- a/src/ui/ScrollWidget.cpp +++ b/src/ui/ScrollWidget.cpp @@ -122,34 +122,34 @@ void ScrollWidget::onHoverKey(const event::HoverKey& e) { offset.y -= box.size.y * 0.5; e.consume(this); } - else if (e.key == GLFW_KEY_PAGE_UP && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + if (e.key == GLFW_KEY_PAGE_UP && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { offset.x -= box.size.x * 0.5; e.consume(this); } - else if (e.key == GLFW_KEY_PAGE_DOWN && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_PAGE_DOWN && (e.mods & RACK_MOD_MASK) == 0) { offset.y += box.size.y * 0.5; e.consume(this); } - else if (e.key == GLFW_KEY_PAGE_DOWN && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + if (e.key == GLFW_KEY_PAGE_DOWN && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { offset.x += box.size.x * 0.5; e.consume(this); } - else if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) { math::Rect containerBox = container->getChildrenBoundingBox(); offset.y = containerBox.getTop(); e.consume(this); } - else if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { math::Rect containerBox = container->getChildrenBoundingBox(); offset.x = containerBox.getLeft(); e.consume(this); } - else if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) { math::Rect containerBox = container->getChildrenBoundingBox(); offset.y = containerBox.getBottom(); e.consume(this); } - else if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { math::Rect containerBox = container->getChildrenBoundingBox(); offset.x = containerBox.getRight(); e.consume(this); diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index f352efd5..d307b4c3 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -76,7 +76,7 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } - else if (e.key == GLFW_KEY_DELETE && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_DELETE && (e.mods & RACK_MOD_MASK) == 0) { if (cursor == selection) { text.erase(cursor, 1); event::Change eChange; @@ -91,7 +91,7 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } - else if (e.key == GLFW_KEY_LEFT) { + if (e.key == GLFW_KEY_LEFT) { if (e.mods & RACK_MOD_CTRL) { while (--cursor > 0) { if (text[cursor - 1] == ' ') @@ -106,7 +106,7 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } - else if (e.key == GLFW_KEY_RIGHT) { + if (e.key == GLFW_KEY_RIGHT) { if (e.mods & RACK_MOD_CTRL) { while (++cursor < (int) text.size()) { if (text[cursor] == ' ') @@ -121,29 +121,29 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } - else if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) { selection = cursor = 0; e.consume(this); } - else if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { cursor = 0; e.consume(this); } - else if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) { selection = cursor = text.size(); e.consume(this); } - else if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { + if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { cursor = text.size(); e.consume(this); } - else if (e.key == GLFW_KEY_V && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { const char* newText = glfwGetClipboardString(APP->window->win); if (newText) insertText(newText); e.consume(this); } - else if (e.key == GLFW_KEY_X && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { if (cursor != selection) { int begin = std::min(cursor, selection); std::string selectedText = text.substr(begin, std::abs(selection - cursor)); @@ -152,7 +152,7 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } - else if (e.key == GLFW_KEY_C && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { if (cursor != selection) { int begin = std::min(cursor, selection); std::string selectedText = text.substr(begin, std::abs(selection - cursor)); @@ -160,11 +160,11 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } - else if (e.key == GLFW_KEY_A && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { selectAll(); e.consume(this); } - else if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { + if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { if (multiline) { insertText("\n"); } @@ -175,10 +175,10 @@ void TextField::onSelectKey(const event::SelectKey& e) { e.consume(this); } // Consume all printable keys - else if ((GLFW_KEY_SPACE <= e.key && e.key <= GLFW_KEY_WORLD_2) || (GLFW_KEY_KP_0 <= e.key && e.key <= GLFW_KEY_KP_EQUAL)) { + if (e.keyName != "") { e.consume(this); } - else if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { + if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { APP->event->setSelected(NULL); e.consume(this); }