You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

370 lines
8.6KB

  1. #include <ui/TextField.hpp>
  2. #include <ui/MenuItem.hpp>
  3. #include <helpers.hpp>
  4. #include <context.hpp>
  5. namespace rack {
  6. namespace ui {
  7. struct TextFieldCopyItem : ui::MenuItem {
  8. WeakPtr<TextField> textField;
  9. void onAction(const ActionEvent& e) override {
  10. if (!textField)
  11. return;
  12. textField->copyClipboard();
  13. APP->event->setSelectedWidget(textField);
  14. }
  15. };
  16. struct TextFieldCutItem : ui::MenuItem {
  17. WeakPtr<TextField> textField;
  18. void onAction(const ActionEvent& e) override {
  19. if (!textField)
  20. return;
  21. textField->cutClipboard();
  22. APP->event->setSelectedWidget(textField);
  23. }
  24. };
  25. struct TextFieldPasteItem : ui::MenuItem {
  26. WeakPtr<TextField> textField;
  27. void onAction(const ActionEvent& e) override {
  28. if (!textField)
  29. return;
  30. textField->pasteClipboard();
  31. APP->event->setSelectedWidget(textField);
  32. }
  33. };
  34. struct TextFieldSelectAllItem : ui::MenuItem {
  35. WeakPtr<TextField> textField;
  36. void onAction(const ActionEvent& e) override {
  37. if (!textField)
  38. return;
  39. textField->selectAll();
  40. APP->event->setSelectedWidget(textField);
  41. }
  42. };
  43. TextField::TextField() {
  44. box.size.y = BND_WIDGET_HEIGHT;
  45. }
  46. void TextField::draw(const DrawArgs& args) {
  47. nvgScissor(args.vg, RECT_ARGS(args.clipBox));
  48. BNDwidgetState state;
  49. if (this == APP->event->selectedWidget)
  50. state = BND_ACTIVE;
  51. else if (this == APP->event->hoveredWidget)
  52. state = BND_HOVER;
  53. else
  54. state = BND_DEFAULT;
  55. int begin = std::min(cursor, selection);
  56. int end = std::max(cursor, selection);
  57. bndTextField(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end);
  58. // Draw placeholder text
  59. if (text.empty()) {
  60. 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);
  61. }
  62. nvgResetScissor(args.vg);
  63. }
  64. void TextField::onDragHover(const DragHoverEvent& e) {
  65. OpaqueWidget::onDragHover(e);
  66. if (e.origin == this) {
  67. int pos = getTextPosition(e.pos);
  68. cursor = pos;
  69. }
  70. }
  71. void TextField::onButton(const ButtonEvent& e) {
  72. OpaqueWidget::onButton(e);
  73. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  74. cursor = selection = getTextPosition(e.pos);
  75. }
  76. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  77. createContextMenu();
  78. e.consume(this);
  79. }
  80. }
  81. void TextField::onSelectText(const SelectTextEvent& e) {
  82. if (e.codepoint < 128) {
  83. std::string newText(1, (char) e.codepoint);
  84. insertText(newText);
  85. }
  86. e.consume(this);
  87. }
  88. void TextField::onSelectKey(const SelectKeyEvent& e) {
  89. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  90. // Backspace
  91. if (e.key == GLFW_KEY_BACKSPACE && (e.mods & RACK_MOD_MASK) == 0) {
  92. if (cursor == selection) {
  93. cursor = std::max(cursor - 1, 0);
  94. }
  95. insertText("");
  96. e.consume(this);
  97. }
  98. // Ctrl+Backspace
  99. if (e.key == GLFW_KEY_BACKSPACE && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  100. if (cursor == selection) {
  101. cursorToPrevWord();
  102. }
  103. insertText("");
  104. e.consume(this);
  105. }
  106. // Delete
  107. if (e.key == GLFW_KEY_DELETE && (e.mods & RACK_MOD_MASK) == 0) {
  108. if (cursor == selection) {
  109. cursor = std::min(cursor + 1, (int) text.size());
  110. }
  111. insertText("");
  112. e.consume(this);
  113. }
  114. // Ctrl+Delete
  115. if (e.key == GLFW_KEY_DELETE && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  116. if (cursor == selection) {
  117. cursorToNextWord();
  118. }
  119. insertText("");
  120. e.consume(this);
  121. }
  122. // Left
  123. if (e.key == GLFW_KEY_LEFT) {
  124. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  125. cursorToPrevWord();
  126. }
  127. else {
  128. cursor = std::max(cursor - 1, 0);
  129. }
  130. if (!(e.mods & GLFW_MOD_SHIFT)) {
  131. selection = cursor;
  132. }
  133. e.consume(this);
  134. }
  135. // Right
  136. if (e.key == GLFW_KEY_RIGHT) {
  137. if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  138. cursorToNextWord();
  139. }
  140. else {
  141. cursor = std::min(cursor + 1, (int) text.size());
  142. }
  143. if (!(e.mods & GLFW_MOD_SHIFT)) {
  144. selection = cursor;
  145. }
  146. e.consume(this);
  147. }
  148. // Up (placeholder)
  149. if (e.key == GLFW_KEY_UP) {
  150. e.consume(this);
  151. }
  152. // Down (placeholder)
  153. if (e.key == GLFW_KEY_DOWN) {
  154. e.consume(this);
  155. }
  156. // Home
  157. if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) {
  158. selection = cursor = 0;
  159. e.consume(this);
  160. }
  161. // Shift+Home
  162. if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  163. cursor = 0;
  164. e.consume(this);
  165. }
  166. // End
  167. if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) {
  168. selection = cursor = text.size();
  169. e.consume(this);
  170. }
  171. // Shift+End
  172. if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  173. cursor = text.size();
  174. e.consume(this);
  175. }
  176. // Ctrl+V
  177. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  178. pasteClipboard();
  179. e.consume(this);
  180. }
  181. // Ctrl+X
  182. if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  183. cutClipboard();
  184. e.consume(this);
  185. }
  186. // Ctrl+C
  187. if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  188. copyClipboard();
  189. e.consume(this);
  190. }
  191. // Ctrl+A
  192. if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  193. selectAll();
  194. e.consume(this);
  195. }
  196. // Enter
  197. if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) {
  198. if (multiline) {
  199. insertText("\n");
  200. }
  201. else {
  202. ActionEvent eAction;
  203. onAction(eAction);
  204. }
  205. e.consume(this);
  206. }
  207. // Tab
  208. if (e.key == GLFW_KEY_TAB && (e.mods & RACK_MOD_MASK) == 0) {
  209. if (nextField)
  210. APP->event->setSelectedWidget(nextField);
  211. e.consume(this);
  212. }
  213. // Shift-Tab
  214. if (e.key == GLFW_KEY_TAB && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  215. if (prevField)
  216. APP->event->setSelectedWidget(prevField);
  217. e.consume(this);
  218. }
  219. // Consume all printable keys
  220. if (e.keyName != "") {
  221. e.consume(this);
  222. }
  223. assert(0 <= cursor);
  224. assert(cursor <= (int) text.size());
  225. assert(0 <= selection);
  226. assert(selection <= (int) text.size());
  227. }
  228. }
  229. int TextField::getTextPosition(math::Vec mousePos) {
  230. return bndTextFieldTextPosition(APP->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
  231. }
  232. std::string TextField::getText() {
  233. return text;
  234. }
  235. void TextField::setText(std::string text) {
  236. if (this->text != text) {
  237. this->text = text;
  238. // ChangeEvent
  239. ChangeEvent eChange;
  240. onChange(eChange);
  241. }
  242. selection = cursor = text.size();
  243. }
  244. void TextField::selectAll() {
  245. cursor = text.size();
  246. selection = 0;
  247. }
  248. std::string TextField::getSelectedText() {
  249. int begin = std::min(cursor, selection);
  250. int len = std::abs(selection - cursor);
  251. return text.substr(begin, len);
  252. }
  253. void TextField::insertText(std::string text) {
  254. bool changed = false;
  255. if (cursor != selection) {
  256. // Delete selected text
  257. int begin = std::min(cursor, selection);
  258. int len = std::abs(selection - cursor);
  259. this->text.erase(begin, len);
  260. cursor = selection = begin;
  261. changed = true;
  262. }
  263. if (!text.empty()) {
  264. this->text.insert(cursor, text);
  265. cursor += text.size();
  266. selection = cursor;
  267. changed = true;
  268. }
  269. if (changed) {
  270. ChangeEvent eChange;
  271. onChange(eChange);
  272. }
  273. }
  274. void TextField::copyClipboard() {
  275. if (cursor == selection)
  276. return;
  277. glfwSetClipboardString(APP->window->win, getSelectedText().c_str());
  278. }
  279. void TextField::cutClipboard() {
  280. copyClipboard();
  281. insertText("");
  282. }
  283. void TextField::pasteClipboard() {
  284. const char* newText = glfwGetClipboardString(APP->window->win);
  285. if (!newText)
  286. return;
  287. insertText(newText);
  288. }
  289. void TextField::cursorToPrevWord() {
  290. size_t pos = text.rfind(' ', std::max(cursor - 2, 0));
  291. if (pos == std::string::npos)
  292. cursor = 0;
  293. else
  294. cursor = std::min((int) pos + 1, (int) text.size());
  295. }
  296. void TextField::cursorToNextWord() {
  297. size_t pos = text.find(' ', std::min(cursor + 1, (int) text.size()));
  298. if (pos == std::string::npos)
  299. pos = text.size();
  300. cursor = pos;
  301. }
  302. void TextField::createContextMenu() {
  303. ui::Menu* menu = createMenu();
  304. TextFieldCutItem* cutItem = new TextFieldCutItem;
  305. cutItem->text = "Cut";
  306. cutItem->rightText = RACK_MOD_CTRL_NAME "+X";
  307. cutItem->textField = this;
  308. menu->addChild(cutItem);
  309. TextFieldCopyItem* copyItem = new TextFieldCopyItem;
  310. copyItem->text = "Copy";
  311. copyItem->rightText = RACK_MOD_CTRL_NAME "+C";
  312. copyItem->textField = this;
  313. menu->addChild(copyItem);
  314. TextFieldPasteItem* pasteItem = new TextFieldPasteItem;
  315. pasteItem->text = "Paste";
  316. pasteItem->rightText = RACK_MOD_CTRL_NAME "+V";
  317. pasteItem->textField = this;
  318. menu->addChild(pasteItem);
  319. TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem;
  320. selectAllItem->text = "Select all";
  321. selectAllItem->rightText = RACK_MOD_CTRL_NAME "+A";
  322. selectAllItem->textField = this;
  323. menu->addChild(selectAllItem);
  324. }
  325. } // namespace ui
  326. } // namespace rack