| @@ -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) { | |||