#include #include #include #include namespace rack { namespace ui { struct TextFieldCopyItem : ui::MenuItem { WeakPtr textField; void onAction(const ActionEvent& e) override { if (!textField) return; textField->copyClipboard(); APP->event->setSelectedWidget(textField); } }; struct TextFieldCutItem : ui::MenuItem { WeakPtr textField; void onAction(const ActionEvent& e) override { if (!textField) return; textField->cutClipboard(); APP->event->setSelectedWidget(textField); } }; struct TextFieldPasteItem : ui::MenuItem { WeakPtr textField; void onAction(const ActionEvent& e) override { if (!textField) return; textField->pasteClipboard(); APP->event->setSelectedWidget(textField); } }; struct TextFieldSelectAllItem : ui::MenuItem { WeakPtr textField; void onAction(const ActionEvent& e) override { if (!textField) return; textField->selectAll(); APP->event->setSelectedWidget(textField); } }; TextField::TextField() { box.size.y = BND_WIDGET_HEIGHT; } void TextField::draw(const DrawArgs& args) { nvgScissor(args.vg, RECT_ARGS(args.clipBox)); BNDwidgetState state; if (this == APP->event->selectedWidget) state = BND_ACTIVE; else if (this == APP->event->hoveredWidget) state = BND_HOVER; else state = BND_DEFAULT; int begin = std::min(cursor, selection); int end = std::max(cursor, selection); bndTextField(args.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()) { bndIconLabelCaret(args.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(args.vg); } void TextField::onDragHover(const DragHoverEvent& e) { OpaqueWidget::onDragHover(e); if (e.origin == this) { int pos = getTextPosition(e.pos); cursor = pos; } } void TextField::onButton(const ButtonEvent& e) { OpaqueWidget::onButton(e); if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) { cursor = selection = getTextPosition(e.pos); } if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { createContextMenu(); e.consume(this); } } void TextField::onSelectText(const SelectTextEvent& e) { if (e.codepoint < 128) { std::string newText(1, (char) e.codepoint); insertText(newText); } e.consume(this); } void TextField::onSelectKey(const SelectKeyEvent& e) { 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 = std::max(cursor - 1, 0); } 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) { cursor = std::min(cursor + 1, (int) text.size()); } 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_MASK) == RACK_MOD_CTRL) { cursorToPrevWord(); } else { 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_MASK) == RACK_MOD_CTRL) { cursorToNextWord(); } else { cursor = std::min(cursor + 1, (int) text.size()); } if (!(e.mods & GLFW_MOD_SHIFT)) { selection = cursor; } e.consume(this); } // Up (placeholder) if (e.key == GLFW_KEY_UP) { e.consume(this); } // Down (placeholder) if (e.key == GLFW_KEY_DOWN) { 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) { pasteClipboard(); e.consume(this); } // Ctrl+X if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { cutClipboard(); e.consume(this); } // Ctrl+C if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { copyClipboard(); 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"); } else { ActionEvent eAction; onAction(eAction); } e.consume(this); } // Tab if (e.key == GLFW_KEY_TAB && (e.mods & RACK_MOD_MASK) == 0) { if (nextField) APP->event->setSelectedWidget(nextField); e.consume(this); } // Shift-Tab if (e.key == GLFW_KEY_TAB && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) { if (prevField) APP->event->setSelectedWidget(prevField); e.consume(this); } // Consume all printable keys if (e.keyName != "") { e.consume(this); } assert(0 <= cursor); assert(cursor <= (int) text.size()); assert(0 <= selection); assert(selection <= (int) text.size()); } } int TextField::getTextPosition(math::Vec mousePos) { return bndTextFieldTextPosition(APP->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y); } std::string TextField::getText() { return text; } void TextField::setText(std::string text) { if (this->text != text) { this->text = text; // ChangeEvent ChangeEvent eChange; onChange(eChange); } selection = cursor = text.size(); } void TextField::selectAll() { cursor = text.size(); selection = 0; } std::string TextField::getSelectedText() { int begin = std::min(cursor, selection); int len = std::abs(selection - cursor); return text.substr(begin, len); } void TextField::insertText(std::string text) { bool changed = false; if (cursor != selection) { // Delete selected text int begin = std::min(cursor, selection); int len = std::abs(selection - cursor); this->text.erase(begin, len); cursor = selection = begin; changed = true; } if (!text.empty()) { this->text.insert(cursor, text); cursor += text.size(); selection = cursor; changed = true; } if (changed) { ChangeEvent eChange; onChange(eChange); } } void TextField::copyClipboard() { if (cursor == selection) return; glfwSetClipboardString(APP->window->win, getSelectedText().c_str()); } void TextField::cutClipboard() { copyClipboard(); insertText(""); } void TextField::pasteClipboard() { const char* newText = glfwGetClipboardString(APP->window->win); if (!newText) return; insertText(newText); } void TextField::cursorToPrevWord() { size_t pos = text.rfind(' ', std::max(cursor - 2, 0)); if (pos == std::string::npos) cursor = 0; else cursor = std::min((int) pos + 1, (int) text.size()); } void TextField::cursorToNextWord() { size_t pos = text.find(' ', std::min(cursor + 1, (int) text.size())); if (pos == std::string::npos) pos = text.size(); cursor = pos; } void TextField::createContextMenu() { ui::Menu* menu = createMenu(); TextFieldCutItem* cutItem = new TextFieldCutItem; cutItem->text = "Cut"; cutItem->rightText = RACK_MOD_CTRL_NAME "+X"; cutItem->textField = this; menu->addChild(cutItem); TextFieldCopyItem* copyItem = new TextFieldCopyItem; copyItem->text = "Copy"; copyItem->rightText = RACK_MOD_CTRL_NAME "+C"; copyItem->textField = this; menu->addChild(copyItem); TextFieldPasteItem* pasteItem = new TextFieldPasteItem; pasteItem->text = "Paste"; pasteItem->rightText = RACK_MOD_CTRL_NAME "+V"; pasteItem->textField = this; menu->addChild(pasteItem); TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem; selectAllItem->text = "Select all"; selectAllItem->rightText = RACK_MOD_CTRL_NAME "+A"; selectAllItem->textField = this; menu->addChild(selectAllItem); } } // namespace ui } // namespace rack