diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index d307b4c3..069a56b5 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -56,93 +56,107 @@ void TextField::onSelectText(const event::SelectText& e) { } void TextField::onSelectKey(const event::SelectKey& e) { + auto cursorToPrevWord = [&]() { + size_t pos = text.rfind(' ', std::max(cursor - 2, 0)); + if (pos == std::string::npos) + cursor = 0; + else + cursor = pos; + }; + auto cursorToNextWord = [&]() { + size_t pos = text.find(' ', std::min(cursor + 1, (int) text.size())); + if (pos == std::string::npos) + pos = text.size(); + cursor = pos; + }; + if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { + // Backspace if (e.key == GLFW_KEY_BACKSPACE && (e.mods & RACK_MOD_MASK) == 0) { if (cursor == selection) { - cursor--; - if (cursor >= 0) { - text.erase(cursor, 1); - event::Change eChange; - onChange(eChange); - } - selection = cursor; + cursor = std::max(cursor - 1, 0); } - else { - int begin = std::min(cursor, selection); - text.erase(begin, std::abs(selection - cursor)); - event::Change eChange; - onChange(eChange); - cursor = selection = begin; + insertText(""); + e.consume(this); + } + // Ctrl+Backspace + if (e.key == GLFW_KEY_BACKSPACE && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (cursor == selection) { + cursorToPrevWord(); } + insertText(""); e.consume(this); } + // Delete if (e.key == GLFW_KEY_DELETE && (e.mods & RACK_MOD_MASK) == 0) { if (cursor == selection) { - text.erase(cursor, 1); - event::Change eChange; - onChange(eChange); + cursor = std::min(cursor + 1, (int) text.size()); } - else { - int begin = std::min(cursor, selection); - text.erase(begin, std::abs(selection - cursor)); - event::Change eChange; - onChange(eChange); - cursor = selection = begin; + insertText(""); + e.consume(this); + } + // Ctrl+Delete + if (e.key == GLFW_KEY_DELETE && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + if (cursor == selection) { + cursorToNextWord(); } + insertText(""); e.consume(this); } + // Left if (e.key == GLFW_KEY_LEFT) { - if (e.mods & RACK_MOD_CTRL) { - while (--cursor > 0) { - if (text[cursor - 1] == ' ') - break; - } + if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + cursorToPrevWord(); } else { - cursor--; + cursor = std::max(cursor - 1, 0); } if (!(e.mods & GLFW_MOD_SHIFT)) { selection = cursor; } e.consume(this); } + // Right if (e.key == GLFW_KEY_RIGHT) { - if (e.mods & RACK_MOD_CTRL) { - while (++cursor < (int) text.size()) { - if (text[cursor] == ' ') - break; - } + if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { + cursorToNextWord(); } else { - cursor++; + cursor = std::min(cursor + 1, (int) text.size()); } if (!(e.mods & GLFW_MOD_SHIFT)) { selection = cursor; } e.consume(this); } + // Home if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) { selection = cursor = 0; e.consume(this); } + // Shift+Home if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { cursor = 0; e.consume(this); } + // End if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) { selection = cursor = text.size(); e.consume(this); } + // Shift+End if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { cursor = text.size(); e.consume(this); } + // Ctrl+V 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); } + // Ctrl+C if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { if (cursor != selection) { int begin = std::min(cursor, selection); @@ -152,6 +166,7 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } + // Ctrl+C if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { if (cursor != selection) { int begin = std::min(cursor, selection); @@ -160,10 +175,12 @@ void TextField::onSelectKey(const event::SelectKey& e) { } e.consume(this); } + // Ctrl+A if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { selectAll(); e.consume(this); } + // Enter if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) { if (multiline) { insertText("\n"); @@ -178,30 +195,41 @@ void TextField::onSelectKey(const event::SelectKey& e) { if (e.keyName != "") { e.consume(this); } + // Esc if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) { APP->event->setSelected(NULL); e.consume(this); } - cursor = math::clamp(cursor, 0, (int) text.size()); - selection = math::clamp(selection, 0, (int) text.size()); + assert(0 <= cursor); + assert(cursor <= (int) text.size()); + assert(0 <= selection); + assert(selection <= (int) text.size()); } - // Consume ALL keys with ALL actions. Is this bad? I don't know, need more testing. + // Consume ALL keys with ALL actions e.consume(this); } void TextField::insertText(std::string text) { + bool changed = false; if (cursor != selection) { + // Delete selected text int begin = std::min(cursor, selection); this->text.erase(begin, std::abs(selection - cursor)); cursor = selection = begin; + changed = true; + } + if (!text.empty()) { + this->text.insert(cursor, text); + cursor += text.size(); + selection = cursor; + changed = true; + } + if (changed) { + event::Change eChange; + onChange(eChange); } - this->text.insert(cursor, text); - cursor += text.size(); - selection = cursor; - event::Change eChange; - onChange(eChange); } void TextField::setText(std::string text) {