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.

375 lines
8.7KB

  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. // Escape
  208. if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) {
  209. APP->event->setSelectedWidget(NULL);
  210. e.consume(this);
  211. }
  212. // Tab
  213. if (e.key == GLFW_KEY_TAB && (e.mods & RACK_MOD_MASK) == 0) {
  214. if (nextField)
  215. APP->event->setSelectedWidget(nextField);
  216. e.consume(this);
  217. }
  218. // Shift-Tab
  219. if (e.key == GLFW_KEY_TAB && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  220. if (prevField)
  221. APP->event->setSelectedWidget(prevField);
  222. e.consume(this);
  223. }
  224. // Consume all printable keys
  225. if (e.keyName != "") {
  226. e.consume(this);
  227. }
  228. assert(0 <= cursor);
  229. assert(cursor <= (int) text.size());
  230. assert(0 <= selection);
  231. assert(selection <= (int) text.size());
  232. }
  233. }
  234. int TextField::getTextPosition(math::Vec mousePos) {
  235. return bndTextFieldTextPosition(APP->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
  236. }
  237. std::string TextField::getText() {
  238. return text;
  239. }
  240. void TextField::setText(std::string text) {
  241. if (this->text != text) {
  242. this->text = text;
  243. // ChangeEvent
  244. ChangeEvent eChange;
  245. onChange(eChange);
  246. }
  247. selection = cursor = text.size();
  248. }
  249. void TextField::selectAll() {
  250. cursor = text.size();
  251. selection = 0;
  252. }
  253. std::string TextField::getSelectedText() {
  254. int begin = std::min(cursor, selection);
  255. int len = std::abs(selection - cursor);
  256. return text.substr(begin, len);
  257. }
  258. void TextField::insertText(std::string text) {
  259. bool changed = false;
  260. if (cursor != selection) {
  261. // Delete selected text
  262. int begin = std::min(cursor, selection);
  263. int len = std::abs(selection - cursor);
  264. this->text.erase(begin, len);
  265. cursor = selection = begin;
  266. changed = true;
  267. }
  268. if (!text.empty()) {
  269. this->text.insert(cursor, text);
  270. cursor += text.size();
  271. selection = cursor;
  272. changed = true;
  273. }
  274. if (changed) {
  275. ChangeEvent eChange;
  276. onChange(eChange);
  277. }
  278. }
  279. void TextField::copyClipboard() {
  280. if (cursor == selection)
  281. return;
  282. glfwSetClipboardString(APP->window->win, getSelectedText().c_str());
  283. }
  284. void TextField::cutClipboard() {
  285. copyClipboard();
  286. insertText("");
  287. }
  288. void TextField::pasteClipboard() {
  289. const char* newText = glfwGetClipboardString(APP->window->win);
  290. if (!newText)
  291. return;
  292. insertText(newText);
  293. }
  294. void TextField::cursorToPrevWord() {
  295. size_t pos = text.rfind(' ', std::max(cursor - 2, 0));
  296. if (pos == std::string::npos)
  297. cursor = 0;
  298. else
  299. cursor = std::min((int) pos + 1, (int) text.size());
  300. }
  301. void TextField::cursorToNextWord() {
  302. size_t pos = text.find(' ', std::min(cursor + 1, (int) text.size()));
  303. if (pos == std::string::npos)
  304. pos = text.size();
  305. cursor = pos;
  306. }
  307. void TextField::createContextMenu() {
  308. ui::Menu* menu = createMenu();
  309. TextFieldCutItem* cutItem = new TextFieldCutItem;
  310. cutItem->text = "Cut";
  311. cutItem->rightText = RACK_MOD_CTRL_NAME "+X";
  312. cutItem->textField = this;
  313. menu->addChild(cutItem);
  314. TextFieldCopyItem* copyItem = new TextFieldCopyItem;
  315. copyItem->text = "Copy";
  316. copyItem->rightText = RACK_MOD_CTRL_NAME "+C";
  317. copyItem->textField = this;
  318. menu->addChild(copyItem);
  319. TextFieldPasteItem* pasteItem = new TextFieldPasteItem;
  320. pasteItem->text = "Paste";
  321. pasteItem->rightText = RACK_MOD_CTRL_NAME "+V";
  322. pasteItem->textField = this;
  323. menu->addChild(pasteItem);
  324. TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem;
  325. selectAllItem->text = "Select all";
  326. selectAllItem->rightText = RACK_MOD_CTRL_NAME "+A";
  327. selectAllItem->textField = this;
  328. menu->addChild(selectAllItem);
  329. }
  330. } // namespace ui
  331. } // namespace rack