#pragma once #include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" #include "event.hpp" namespace rack { struct TextField : OpaqueWidget { std::string text; std::string placeholder; bool multiline = false; /** 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; } void draw(NVGcontext *vg) override { nvgScissor(vg, 0, 0, box.size.x, box.size.y); BNDwidgetState state; if (this == event::gContext->selectedWidget) state = BND_ACTIVE; else if (this == event::gContext->hoveredWidget) state = BND_HOVER; else state = BND_DEFAULT; int begin = std::min(cursor, selection); int end = std::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) { bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1); } nvgResetScissor(vg); } void onButton(event::Button &e) override { if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { cursor = selection = getTextPosition(e.pos); } OpaqueWidget::onButton(e); } void onHover(event::Hover &e) override { if (this == event::gContext->draggedWidget) { int pos = getTextPosition(e.pos); if (pos != selection) { cursor = pos; } } OpaqueWidget::onHover(e); } void onEnter(event::Enter &e) override { e.target = this; } void onSelectText(event::SelectText &e) override { if (e.codepoint < 128) { std::string newText(1, (char) e.codepoint); insertText(newText); } e.target = this; } void onSelectKey(event::SelectKey &e) override { switch (e.key) { case GLFW_KEY_BACKSPACE: { if (cursor == selection) { cursor--; if (cursor >= 0) { text.erase(cursor, 1); event::Change eChange; onChange(eChange); } selection = cursor; } else { int begin = std::min(cursor, selection); text.erase(begin, std::abs(selection - cursor)); event::Change eChange; onChange(eChange); cursor = selection = begin; } } break; case GLFW_KEY_DELETE: { if (cursor == selection) { text.erase(cursor, 1); event::Change eChange; onChange(eChange); } else { int begin = std::min(cursor, selection); text.erase(begin, std::abs(selection - cursor)); event::Change eChange; onChange(eChange); cursor = selection = begin; } } break; case GLFW_KEY_LEFT: { if (windowIsModPressed()) { while (--cursor > 0) { if (text[cursor] == ' ') break; } } else { cursor--; } if (!windowIsShiftPressed()) { selection = cursor; } } break; case GLFW_KEY_RIGHT: { if (windowIsModPressed()) { while (++cursor < (int) text.size()) { if (text[cursor] == ' ') break; } } else { 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: { if (windowIsModPressed()) { if (cursor != selection) { int begin = std::min(cursor, selection); std::string selectedText = text.substr(begin, std::abs(selection - cursor)); glfwSetClipboardString(gWindow, selectedText.c_str()); insertText(""); } } } break; case GLFW_KEY_C: { if (windowIsModPressed()) { if (cursor != selection) { int begin = std::min(cursor, selection); std::string selectedText = text.substr(begin, std::abs(selection - cursor)); glfwSetClipboardString(gWindow, selectedText.c_str()); } } } break; case GLFW_KEY_A: { if (windowIsModPressed()) { selection = 0; cursor = text.size(); } } break; case GLFW_KEY_ENTER: { if (multiline) { insertText("\n"); } else { event::Action eAction; onAction(eAction); } } break; } cursor = clamp(cursor, 0, (int) text.size()); selection = clamp(selection, 0, (int) text.size()); e.target = this; } /** Inserts text at the cursor, replacing the selection if necessary */ void insertText(std::string text) { if (cursor != selection) { int begin = std::min(cursor, selection); this->text.erase(begin, std::abs(selection - cursor)); cursor = selection = begin; } this->text.insert(cursor, text); cursor += text.size(); selection = cursor; event::Change eChange; onChange(eChange); } /** Replaces the entire text */ void setText(std::string text) { this->text = text; selection = cursor = text.size(); event::Change eChange; onChange(eChange); } virtual int getTextPosition(Vec mousePos) { return bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); } }; } // namespace rack