diff --git a/include/helpers.hpp b/include/helpers.hpp index fa6d8457..9886d4ab 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -146,14 +146,14 @@ TParamWidget* createLightParamCentered(math::Vec pos, engine::Module* module, in } template -TMenuLabel * createMenuLabel(std::string text) { +TMenuLabel* createMenuLabel(std::string text) { TMenuLabel* o = new TMenuLabel; o->text = text; return o; } template -TMenuItem * createMenuItem(std::string text, std::string rightText = "") { +TMenuItem* createMenuItem(std::string text, std::string rightText = "") { TMenuItem* o = new TMenuItem; o->text = text; o->rightText = rightText; @@ -161,7 +161,7 @@ TMenuItem * createMenuItem(std::string text, std::string rightText = "") { } template -TMenu * createMenu() { +TMenu* createMenu() { TMenu* o = new TMenu; o->box.pos = APP->scene->mousePos; diff --git a/include/ui/TextField.hpp b/include/ui/TextField.hpp index ebf9e69e..26fb15b0 100644 --- a/include/ui/TextField.hpp +++ b/include/ui/TextField.hpp @@ -25,14 +25,21 @@ struct TextField : widget::OpaqueWidget { void onButton(const event::Button& e) override; void onSelectText(const event::SelectText& e) override; void onSelectKey(const event::SelectKey& e) override; + virtual int getTextPosition(math::Vec mousePos); - /** Inserts text at the cursor, replacing the selection if necessary */ - void insertText(std::string text); - + std::string getText(); /** Replaces the entire text */ void setText(std::string text); void selectAll(); - virtual int getTextPosition(math::Vec mousePos); + std::string getSelectedText(); + /** Inserts text at the cursor, replacing the selection if necessary */ + void insertText(std::string text); + void copyClipboard(); + void cutClipboard(); + void pasteClipboard(); + void cursorToPrevWord(); + void cursorToNextWord(); + void createContextMenu(); }; diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index d86b5da3..a9ceb914 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -1,9 +1,56 @@ #include +#include +#include +#include namespace rack { namespace ui { +struct TextFieldCopyItem : ui::MenuItem { + WeakPtr textField; + void onAction(const event::Action& e) override { + if (!textField) + return; + textField->copyClipboard(); + APP->event->setSelected(textField); + } +}; + + +struct TextFieldCutItem : ui::MenuItem { + WeakPtr textField; + void onAction(const event::Action& e) override { + if (!textField) + return; + textField->cutClipboard(); + APP->event->setSelected(textField); + } +}; + + +struct TextFieldPasteItem : ui::MenuItem { + WeakPtr textField; + void onAction(const event::Action& e) override { + if (!textField) + return; + textField->pasteClipboard(); + APP->event->setSelected(textField); + } +}; + + +struct TextFieldSelectAllItem : ui::MenuItem { + WeakPtr textField; + void onAction(const event::Action& e) override { + if (!textField) + return; + textField->selectAll(); + APP->event->setSelected(textField); + } +}; + + TextField::TextField() { box.size.y = BND_WIDGET_HEIGHT; } @@ -45,6 +92,11 @@ void TextField::onButton(const event::Button& 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 event::SelectText& e) { @@ -56,20 +108,6 @@ 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 = std::min((int) pos + 1, (int) text.size()); - }; - 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) { @@ -151,28 +189,17 @@ void TextField::onSelectKey(const event::SelectKey& e) { } // 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); + pasteClipboard(); e.consume(this); } - // Ctrl+C + // Ctrl+X if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - if (cursor != selection) { - int begin = std::min(cursor, selection); - std::string selectedText = text.substr(begin, std::abs(selection - cursor)); - glfwSetClipboardString(APP->window->win, selectedText.c_str()); - insertText(""); - } + cutClipboard(); 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); - std::string selectedText = text.substr(begin, std::abs(selection - cursor)); - glfwSetClipboardString(APP->window->win, selectedText.c_str()); - } + copyClipboard(); e.consume(this); } // Ctrl+A @@ -211,12 +238,42 @@ void TextField::onSelectKey(const event::SelectKey& e) { e.consume(this); } +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; + // event::Change + event::Change 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); - this->text.erase(begin, std::abs(selection - cursor)); + int len = std::abs(selection - cursor); + this->text.erase(begin, len); cursor = selection = begin; changed = true; } @@ -232,23 +289,65 @@ void TextField::insertText(std::string text) { } } -void TextField::setText(std::string text) { - if (this->text != text) { - this->text = text; - // event::Change - event::Change eChange; - onChange(eChange); - } - selection = cursor = text.size(); +void TextField::copyClipboard() { + if (cursor == selection) + return; + glfwSetClipboardString(APP->window->win, getSelectedText().c_str()); } -void TextField::selectAll() { - cursor = text.size(); - selection = 0; +void TextField::cutClipboard() { + copyClipboard(); + insertText(""); } -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); +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); }