diff --git a/include/ui.hpp b/include/ui.hpp index a4c14c84..d70a41f8 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -193,9 +193,12 @@ struct TextField : OpaqueWidget { std::string text; std::string placeholder; bool multiline = false; - int begin = 0; - int end = 0; - int dragPos = 0; + /** The index of the text cursor */ + int cursor = 0; + /** The index of the other end of the selection. + If nothing is selected, this is equal to `cursor`. + */ + int selection = 0; TextField() { box.size.y = BND_WIDGET_HEIGHT; diff --git a/include/util/math.hpp b/include/util/math.hpp index b3a174f4..455d395e 100644 --- a/include/util/math.hpp +++ b/include/util/math.hpp @@ -14,7 +14,7 @@ using std::isnormal; namespace rack { //////////////////// -// basic integer functions (suffixed with "i") +// basic integer functions //////////////////// inline int min(int a, int b) { @@ -54,7 +54,7 @@ inline bool ispow2(int n) { } //////////////////// -// basic float functions (suffixed with "f") +// basic float functions //////////////////// /** Returns 1.f for positive numbers and -1.f for negative numbers (including positive/negative zero) */ diff --git a/src/app/LedDisplay.cpp b/src/app/LedDisplay.cpp index f3688389..2ffb8acd 100644 --- a/src/app/LedDisplay.cpp +++ b/src/app/LedDisplay.cpp @@ -84,10 +84,11 @@ void LedDisplayTextField::draw(NVGcontext *vg) { NVGcolor highlightColor = color; highlightColor.a = 0.5; - int cend = (this == gFocusedWidget) ? end : -1; + int begin = min(cursor, selection); + int end = (this == gFocusedWidget) ? max(cursor, selection) : -1; bndIconLabelCaret(vg, textOffset.x, textOffset.y, box.size.x - 2*textOffset.x, box.size.y - 2*textOffset.y, - -1, color, 12, text.c_str(), highlightColor, begin, cend); + -1, color, 12, text.c_str(), highlightColor, begin, end); bndSetFont(gGuiFont->handle); } diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index b97c6f91..2a57f448 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -19,6 +19,8 @@ void TextField::draw(NVGcontext *vg) { else state = BND_DEFAULT; + int begin = min(cursor, selection); + int end = max(cursor, selection); bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); // Draw placeholder text if (text.empty() && state != BND_ACTIVE) { @@ -29,25 +31,24 @@ void TextField::draw(NVGcontext *vg) { } void TextField::onMouseDown(EventMouseDown &e) { - dragPos = getTextPosition(e.pos); - begin = end = dragPos; + debug("%d", this == gFocusedWidget); + if (e.button == 0) { + cursor = selection = getTextPosition(e.pos); + } OpaqueWidget::onMouseDown(e); } void TextField::onMouseMove(EventMouseMove &e) { if (this == gDraggedWidget) { int pos = getTextPosition(e.pos); - if (pos != dragPos) { - begin = min(dragPos, pos); - end = max(dragPos, pos); + if (pos != selection) { + cursor = pos; } } OpaqueWidget::onMouseMove(e); } void TextField::onFocus(EventFocus &e) { - begin = 0; - end = text.size(); e.consumed = true; } @@ -61,85 +62,101 @@ void TextField::onText(EventText &e) { void TextField::onKey(EventKey &e) { switch (e.key) { - case GLFW_KEY_BACKSPACE: - if (begin < end) { - text.erase(begin, end - begin); - onTextChange(); - } - else { - begin--; - if (begin >= 0) { - text.erase(begin, 1); + case GLFW_KEY_BACKSPACE: { + if (cursor == selection) { + cursor--; + if (cursor >= 0) { + text.erase(cursor, 1); onTextChange(); } + selection = cursor; } - end = begin; - break; - case GLFW_KEY_DELETE: - if (begin < end) { - text.erase(begin, end - begin); + else { + int begin = min(cursor, selection); + text.erase(begin, std::abs(selection - cursor)); + onTextChange(); + cursor = selection = begin; + } + } break; + case GLFW_KEY_DELETE: { + if (cursor == selection) { + text.erase(cursor, 1); onTextChange(); } else { - text.erase(begin, 1); + int begin = min(cursor, selection); + text.erase(begin, std::abs(selection - cursor)); onTextChange(); + cursor = selection = begin; } - end = begin; - break; - case GLFW_KEY_LEFT: - if (begin < end) { + } break; + case GLFW_KEY_LEFT: { + if (windowIsModPressed()) { + while (--cursor > 0) { + if (text[cursor] == ' ') + break; + } } else { - begin--; + cursor--; + } + if (!windowIsShiftPressed()) { + selection = cursor; } - end = begin; - break; - case GLFW_KEY_RIGHT: - if (begin < end) { - begin = end; + } break; + case GLFW_KEY_RIGHT: { + if (windowIsModPressed()) { + while (++cursor < (int) text.size()) { + if (text[cursor] == ' ') + break; + } } else { - begin++; - } - end = begin; - break; - case GLFW_KEY_HOME: - end = begin = 0; - break; - case GLFW_KEY_END: - end = begin = text.size(); - break; - case GLFW_KEY_V: + cursor++; + } + if (!windowIsShiftPressed()) { + selection = cursor; + } + } break; + case GLFW_KEY_HOME: { + selection = cursor = 0; + } break; + case GLFW_KEY_END: { + selection = cursor = text.size(); + } break; + case GLFW_KEY_V: { if (windowIsModPressed()) { const char *newText = glfwGetClipboardString(gWindow); if (newText) insertText(newText); } - break; - case GLFW_KEY_X: + } break; + case GLFW_KEY_X: { if (windowIsModPressed()) { - if (begin < end) { - std::string selectedText = text.substr(begin, end - begin); + if (cursor != selection) { + int begin = min(cursor, selection); + std::string selectedText = text.substr(begin, std::abs(selection - cursor)); glfwSetClipboardString(gWindow, selectedText.c_str()); insertText(""); } } - break; - case GLFW_KEY_C: + } break; + case GLFW_KEY_C: { if (windowIsModPressed()) { - if (begin < end) { - std::string selectedText = text.substr(begin, end - begin); + if (cursor != selection) { + int begin = min(cursor, selection); + std::string selectedText = text.substr(begin, std::abs(selection - cursor)); glfwSetClipboardString(gWindow, selectedText.c_str()); } } - break; - case GLFW_KEY_A: + } break; + case GLFW_KEY_A: { if (windowIsModPressed()) { - begin = 0; - end = text.size(); + selection = 0; + cursor = text.size(); } - break; - case GLFW_KEY_ENTER: + } break; + case GLFW_KEY_ENTER: { if (multiline) { insertText("\n"); } @@ -147,27 +164,29 @@ void TextField::onKey(EventKey &e) { EventAction e; onAction(e); } - break; + } break; } - begin = clamp(begin, 0, text.size()); - end = clamp(end, 0, text.size()); + cursor = clamp(cursor, 0, text.size()); + selection = clamp(selection, 0, text.size()); e.consumed = true; } void TextField::insertText(std::string text) { - if (begin < end) - this->text.erase(begin, end - begin); - this->text.insert(begin, text); - begin += text.size(); - end = begin; + if (cursor != selection) { + int begin = min(cursor, selection); + this->text.erase(begin, std::abs(selection - cursor)); + cursor = selection = begin; + } + this->text.insert(cursor, text); + cursor += text.size(); + selection = cursor; onTextChange(); } void TextField::setText(std::string text) { this->text = text; - begin = clamp(begin, 0, text.size()); - end = clamp(end, 0, text.size()); + selection = cursor = text.size(); onTextChange(); }