| @@ -56,93 +56,107 @@ void TextField::onSelectText(const event::SelectText& e) { | |||||
| } | } | ||||
| void TextField::onSelectKey(const event::SelectKey& 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) { | if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) { | ||||
| // Backspace | |||||
| if (e.key == GLFW_KEY_BACKSPACE && (e.mods & RACK_MOD_MASK) == 0) { | if (e.key == GLFW_KEY_BACKSPACE && (e.mods & RACK_MOD_MASK) == 0) { | ||||
| if (cursor == selection) { | 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); | e.consume(this); | ||||
| } | } | ||||
| // Delete | |||||
| 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) { | 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); | e.consume(this); | ||||
| } | } | ||||
| // Left | |||||
| 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] == ' ') | |||||
| break; | |||||
| } | |||||
| if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||||
| cursorToPrevWord(); | |||||
| } | } | ||||
| else { | else { | ||||
| cursor--; | |||||
| cursor = std::max(cursor - 1, 0); | |||||
| } | } | ||||
| if (!(e.mods & GLFW_MOD_SHIFT)) { | if (!(e.mods & GLFW_MOD_SHIFT)) { | ||||
| selection = cursor; | selection = cursor; | ||||
| } | } | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Right | |||||
| 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] == ' ') | |||||
| break; | |||||
| } | |||||
| if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | |||||
| cursorToNextWord(); | |||||
| } | } | ||||
| else { | else { | ||||
| cursor++; | |||||
| cursor = std::min(cursor + 1, (int) text.size()); | |||||
| } | } | ||||
| if (!(e.mods & GLFW_MOD_SHIFT)) { | if (!(e.mods & GLFW_MOD_SHIFT)) { | ||||
| selection = cursor; | selection = cursor; | ||||
| } | } | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Home | |||||
| 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; | selection = cursor = 0; | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Shift+Home | |||||
| 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; | cursor = 0; | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // End | |||||
| 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(); | selection = cursor = text.size(); | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Shift+End | |||||
| 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(); | cursor = text.size(); | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Ctrl+V | |||||
| if (e.keyName == "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); | const char* newText = glfwGetClipboardString(APP->window->win); | ||||
| if (newText) | if (newText) | ||||
| insertText(newText); | insertText(newText); | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Ctrl+C | |||||
| if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| if (cursor != selection) { | if (cursor != selection) { | ||||
| int begin = std::min(cursor, selection); | int begin = std::min(cursor, selection); | ||||
| @@ -152,6 +166,7 @@ void TextField::onSelectKey(const event::SelectKey& e) { | |||||
| } | } | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Ctrl+C | |||||
| if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| if (cursor != selection) { | if (cursor != selection) { | ||||
| int begin = std::min(cursor, selection); | int begin = std::min(cursor, selection); | ||||
| @@ -160,10 +175,12 @@ void TextField::onSelectKey(const event::SelectKey& e) { | |||||
| } | } | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Ctrl+A | |||||
| if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { | ||||
| selectAll(); | selectAll(); | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Enter | |||||
| 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) { | if (multiline) { | ||||
| insertText("\n"); | insertText("\n"); | ||||
| @@ -178,30 +195,41 @@ void TextField::onSelectKey(const event::SelectKey& e) { | |||||
| if (e.keyName != "") { | if (e.keyName != "") { | ||||
| e.consume(this); | e.consume(this); | ||||
| } | } | ||||
| // Esc | |||||
| 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); | APP->event->setSelected(NULL); | ||||
| e.consume(this); | 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); | e.consume(this); | ||||
| } | } | ||||
| void TextField::insertText(std::string text) { | void TextField::insertText(std::string text) { | ||||
| bool changed = false; | |||||
| if (cursor != selection) { | if (cursor != selection) { | ||||
| // Delete selected text | |||||
| int begin = std::min(cursor, selection); | int begin = std::min(cursor, selection); | ||||
| this->text.erase(begin, std::abs(selection - cursor)); | this->text.erase(begin, std::abs(selection - cursor)); | ||||
| cursor = selection = begin; | 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) { | void TextField::setText(std::string text) { | ||||