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.

390 lines
9.1KB

  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. std::string drawText;
  58. if (password) {
  59. drawText = std::string(text.size(), '*');
  60. }
  61. else {
  62. drawText = text;
  63. }
  64. bndTextField(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, drawText.c_str(), begin, end);
  65. // Draw placeholder text
  66. if (text.empty()) {
  67. 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);
  68. }
  69. nvgResetScissor(args.vg);
  70. }
  71. void TextField::onDragHover(const DragHoverEvent& e) {
  72. OpaqueWidget::onDragHover(e);
  73. if (e.origin == this) {
  74. int pos = getTextPosition(e.pos);
  75. cursor = pos;
  76. }
  77. }
  78. void TextField::onButton(const ButtonEvent& e) {
  79. OpaqueWidget::onButton(e);
  80. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  81. cursor = selection = getTextPosition(e.pos);
  82. }
  83. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
  84. createContextMenu();
  85. e.consume(this);
  86. }
  87. }
  88. void TextField::onSelectText(const SelectTextEvent& e) {
  89. if (e.codepoint < 128) {
  90. std::string newText(1, (char) e.codepoint);
  91. insertText(newText);
  92. }
  93. e.consume(this);
  94. }
  95. void TextField::onSelectKey(const SelectKeyEvent& e) {
  96. if (e.action == GLFW_PRESS || e.action == GLFW_REPEAT) {
  97. // Backspace
  98. if (e.isKeyCommand(GLFW_KEY_BACKSPACE)) {
  99. if (cursor == selection) {
  100. cursor = std::max(cursor - 1, 0);
  101. }
  102. insertText("");
  103. e.consume(this);
  104. }
  105. // Ctrl+Backspace
  106. if (e.isKeyCommand(GLFW_KEY_BACKSPACE, RACK_MOD_CTRL)) {
  107. if (cursor == selection) {
  108. cursorToPrevWord();
  109. }
  110. insertText("");
  111. e.consume(this);
  112. }
  113. // Delete
  114. if (e.isKeyCommand(GLFW_KEY_DELETE)) {
  115. if (cursor == selection) {
  116. cursor = std::min(cursor + 1, (int) text.size());
  117. }
  118. insertText("");
  119. e.consume(this);
  120. }
  121. // Ctrl+Delete
  122. if (e.isKeyCommand(GLFW_KEY_DELETE, RACK_MOD_CTRL)) {
  123. if (cursor == selection) {
  124. cursorToNextWord();
  125. }
  126. insertText("");
  127. e.consume(this);
  128. }
  129. // Left
  130. if (e.isKeyCommand(GLFW_KEY_LEFT)) {
  131. cursor = std::max(cursor - 1, 0);
  132. selection = cursor;
  133. e.consume(this);
  134. }
  135. if (e.isKeyCommand(GLFW_KEY_LEFT, RACK_MOD_CTRL)) {
  136. cursorToPrevWord();
  137. selection = cursor;
  138. e.consume(this);
  139. }
  140. if (e.isKeyCommand(GLFW_KEY_LEFT, GLFW_MOD_SHIFT)) {
  141. cursor = std::max(cursor - 1, 0);
  142. e.consume(this);
  143. }
  144. if (e.isKeyCommand(GLFW_KEY_LEFT, RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  145. cursorToPrevWord();
  146. e.consume(this);
  147. }
  148. // Right
  149. if (e.isKeyCommand(GLFW_KEY_RIGHT)) {
  150. cursor = std::min(cursor + 1, (int) text.size());
  151. selection = cursor;
  152. e.consume(this);
  153. }
  154. if (e.isKeyCommand(GLFW_KEY_RIGHT, RACK_MOD_CTRL)) {
  155. cursorToNextWord();
  156. selection = cursor;
  157. e.consume(this);
  158. }
  159. if (e.isKeyCommand(GLFW_KEY_RIGHT, GLFW_MOD_SHIFT)) {
  160. cursor = std::min(cursor + 1, (int) text.size());
  161. e.consume(this);
  162. }
  163. if (e.isKeyCommand(GLFW_KEY_RIGHT, RACK_MOD_CTRL | GLFW_MOD_SHIFT)) {
  164. cursorToNextWord();
  165. e.consume(this);
  166. }
  167. // Up (placeholder)
  168. if (e.isKeyCommand(GLFW_KEY_UP)) {
  169. e.consume(this);
  170. }
  171. // Down (placeholder)
  172. if (e.isKeyCommand(GLFW_KEY_DOWN)) {
  173. e.consume(this);
  174. }
  175. // Home
  176. if (e.isKeyCommand(GLFW_KEY_HOME)) {
  177. selection = cursor = 0;
  178. e.consume(this);
  179. }
  180. // Shift+Home
  181. if (e.isKeyCommand(GLFW_KEY_HOME, GLFW_MOD_SHIFT)) {
  182. cursor = 0;
  183. e.consume(this);
  184. }
  185. // End
  186. if (e.isKeyCommand(GLFW_KEY_END)) {
  187. selection = cursor = text.size();
  188. e.consume(this);
  189. }
  190. // Shift+End
  191. if (e.isKeyCommand(GLFW_KEY_END, GLFW_MOD_SHIFT)) {
  192. cursor = text.size();
  193. e.consume(this);
  194. }
  195. // Ctrl+V
  196. if (e.isKeyCommand(GLFW_KEY_V, RACK_MOD_CTRL)) {
  197. pasteClipboard();
  198. e.consume(this);
  199. }
  200. // Ctrl+X
  201. if (e.isKeyCommand(GLFW_KEY_X, RACK_MOD_CTRL)) {
  202. cutClipboard();
  203. e.consume(this);
  204. }
  205. // Ctrl+C
  206. if (e.isKeyCommand(GLFW_KEY_C, RACK_MOD_CTRL)) {
  207. copyClipboard();
  208. e.consume(this);
  209. }
  210. // Ctrl+A
  211. if (e.isKeyCommand(GLFW_KEY_A, RACK_MOD_CTRL)) {
  212. selectAll();
  213. e.consume(this);
  214. }
  215. // Enter
  216. if (e.isKeyCommand(GLFW_KEY_ENTER) || e.isKeyCommand(GLFW_KEY_KP_ENTER)) {
  217. if (multiline) {
  218. insertText("\n");
  219. }
  220. else {
  221. ActionEvent eAction;
  222. onAction(eAction);
  223. }
  224. e.consume(this);
  225. }
  226. // Tab
  227. if (e.isKeyCommand(GLFW_KEY_TAB)) {
  228. if (nextField)
  229. APP->event->setSelectedWidget(nextField);
  230. e.consume(this);
  231. }
  232. // Shift+Tab
  233. if (e.isKeyCommand(GLFW_KEY_TAB, GLFW_MOD_SHIFT)) {
  234. if (prevField)
  235. APP->event->setSelectedWidget(prevField);
  236. e.consume(this);
  237. }
  238. // Consume all printable keys unless Ctrl is held
  239. if (GLFW_KEY_SPACE <= e.key && e.key < 128 && (e.mods & RACK_MOD_CTRL) == 0) {
  240. e.consume(this);
  241. }
  242. assert(0 <= cursor);
  243. assert(cursor <= (int) text.size());
  244. assert(0 <= selection);
  245. assert(selection <= (int) text.size());
  246. }
  247. }
  248. int TextField::getTextPosition(math::Vec mousePos) {
  249. return bndTextFieldTextPosition(APP->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
  250. }
  251. std::string TextField::getText() {
  252. return text;
  253. }
  254. void TextField::setText(std::string text) {
  255. if (this->text != text) {
  256. this->text = text;
  257. // ChangeEvent
  258. ChangeEvent eChange;
  259. onChange(eChange);
  260. }
  261. selection = cursor = text.size();
  262. }
  263. void TextField::selectAll() {
  264. cursor = text.size();
  265. selection = 0;
  266. }
  267. std::string TextField::getSelectedText() {
  268. int begin = std::min(cursor, selection);
  269. int len = std::abs(selection - cursor);
  270. return text.substr(begin, len);
  271. }
  272. void TextField::insertText(std::string text) {
  273. bool changed = false;
  274. if (cursor != selection) {
  275. // Delete selected text
  276. int begin = std::min(cursor, selection);
  277. int len = std::abs(selection - cursor);
  278. this->text.erase(begin, len);
  279. cursor = selection = begin;
  280. changed = true;
  281. }
  282. if (!text.empty()) {
  283. this->text.insert(cursor, text);
  284. cursor += text.size();
  285. selection = cursor;
  286. changed = true;
  287. }
  288. if (changed) {
  289. ChangeEvent eChange;
  290. onChange(eChange);
  291. }
  292. }
  293. void TextField::copyClipboard() {
  294. if (cursor == selection)
  295. return;
  296. glfwSetClipboardString(APP->window->win, getSelectedText().c_str());
  297. }
  298. void TextField::cutClipboard() {
  299. copyClipboard();
  300. insertText("");
  301. }
  302. void TextField::pasteClipboard() {
  303. const char* newText = glfwGetClipboardString(APP->window->win);
  304. if (!newText)
  305. return;
  306. insertText(newText);
  307. }
  308. void TextField::cursorToPrevWord() {
  309. size_t pos = text.rfind(' ', std::max(cursor - 2, 0));
  310. if (pos == std::string::npos)
  311. cursor = 0;
  312. else
  313. cursor = std::min((int) pos + 1, (int) text.size());
  314. }
  315. void TextField::cursorToNextWord() {
  316. size_t pos = text.find(' ', std::min(cursor + 1, (int) text.size()));
  317. if (pos == std::string::npos)
  318. pos = text.size();
  319. cursor = pos;
  320. }
  321. void TextField::createContextMenu() {
  322. ui::Menu* menu = createMenu();
  323. TextFieldCutItem* cutItem = new TextFieldCutItem;
  324. cutItem->text = string::translate("TextField.cut");
  325. cutItem->rightText = widget::getKeyCommandName(GLFW_KEY_X, RACK_MOD_CTRL);
  326. cutItem->textField = this;
  327. menu->addChild(cutItem);
  328. TextFieldCopyItem* copyItem = new TextFieldCopyItem;
  329. copyItem->text = string::translate("TextField.copy");
  330. copyItem->rightText = widget::getKeyCommandName(GLFW_KEY_C, RACK_MOD_CTRL);
  331. copyItem->textField = this;
  332. menu->addChild(copyItem);
  333. TextFieldPasteItem* pasteItem = new TextFieldPasteItem;
  334. pasteItem->text = string::translate("TextField.paste");
  335. pasteItem->rightText = widget::getKeyCommandName(GLFW_KEY_V, RACK_MOD_CTRL);
  336. pasteItem->textField = this;
  337. menu->addChild(pasteItem);
  338. TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem;
  339. selectAllItem->text = string::translate("TextField.selectAll");
  340. selectAllItem->rightText = widget::getKeyCommandName(GLFW_KEY_A, RACK_MOD_CTRL);
  341. selectAllItem->textField = this;
  342. menu->addChild(selectAllItem);
  343. }
  344. } // namespace ui
  345. } // namespace rack