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.

356 lines
8.3KB

  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 event::Action& e) override {
  10. if (!textField)
  11. return;
  12. textField->copyClipboard();
  13. APP->event->setSelected(textField);
  14. }
  15. };
  16. struct TextFieldCutItem : ui::MenuItem {
  17. WeakPtr<TextField> textField;
  18. void onAction(const event::Action& e) override {
  19. if (!textField)
  20. return;
  21. textField->cutClipboard();
  22. APP->event->setSelected(textField);
  23. }
  24. };
  25. struct TextFieldPasteItem : ui::MenuItem {
  26. WeakPtr<TextField> textField;
  27. void onAction(const event::Action& e) override {
  28. if (!textField)
  29. return;
  30. textField->pasteClipboard();
  31. APP->event->setSelected(textField);
  32. }
  33. };
  34. struct TextFieldSelectAllItem : ui::MenuItem {
  35. WeakPtr<TextField> textField;
  36. void onAction(const event::Action& e) override {
  37. if (!textField)
  38. return;
  39. textField->selectAll();
  40. APP->event->setSelected(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 event::DragHover& 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 event::Button& 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 event::SelectText& 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 event::SelectKey& 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. // Home
  149. if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == 0) {
  150. selection = cursor = 0;
  151. e.consume(this);
  152. }
  153. // Shift+Home
  154. if (e.key == GLFW_KEY_HOME && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  155. cursor = 0;
  156. e.consume(this);
  157. }
  158. // End
  159. if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == 0) {
  160. selection = cursor = text.size();
  161. e.consume(this);
  162. }
  163. // Shift+End
  164. if (e.key == GLFW_KEY_END && (e.mods & RACK_MOD_MASK) == GLFW_MOD_SHIFT) {
  165. cursor = text.size();
  166. e.consume(this);
  167. }
  168. // Ctrl+V
  169. if (e.keyName == "v" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  170. pasteClipboard();
  171. e.consume(this);
  172. }
  173. // Ctrl+X
  174. if (e.keyName == "x" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  175. cutClipboard();
  176. e.consume(this);
  177. }
  178. // Ctrl+C
  179. if (e.keyName == "c" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  180. copyClipboard();
  181. e.consume(this);
  182. }
  183. // Ctrl+A
  184. if (e.keyName == "a" && (e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) {
  185. selectAll();
  186. e.consume(this);
  187. }
  188. // Enter
  189. if ((e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER) && (e.mods & RACK_MOD_MASK) == 0) {
  190. if (multiline) {
  191. insertText("\n");
  192. }
  193. else {
  194. event::Action eAction;
  195. onAction(eAction);
  196. }
  197. e.consume(this);
  198. }
  199. // Consume all printable keys
  200. if (e.keyName != "") {
  201. e.consume(this);
  202. }
  203. // Esc
  204. if (e.key == GLFW_KEY_ESCAPE && (e.mods & RACK_MOD_MASK) == 0) {
  205. APP->event->setSelected(NULL);
  206. e.consume(this);
  207. }
  208. assert(0 <= cursor);
  209. assert(cursor <= (int) text.size());
  210. assert(0 <= selection);
  211. assert(selection <= (int) text.size());
  212. }
  213. // Consume ALL keys with ALL actions
  214. e.consume(this);
  215. }
  216. int TextField::getTextPosition(math::Vec mousePos) {
  217. return bndTextFieldTextPosition(APP->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
  218. }
  219. std::string TextField::getText() {
  220. return text;
  221. }
  222. void TextField::setText(std::string text) {
  223. if (this->text != text) {
  224. this->text = text;
  225. // event::Change
  226. event::Change eChange;
  227. onChange(eChange);
  228. }
  229. selection = cursor = text.size();
  230. }
  231. void TextField::selectAll() {
  232. cursor = text.size();
  233. selection = 0;
  234. }
  235. std::string TextField::getSelectedText() {
  236. int begin = std::min(cursor, selection);
  237. int len = std::abs(selection - cursor);
  238. return text.substr(begin, len);
  239. }
  240. void TextField::insertText(std::string text) {
  241. bool changed = false;
  242. if (cursor != selection) {
  243. // Delete selected text
  244. int begin = std::min(cursor, selection);
  245. int len = std::abs(selection - cursor);
  246. this->text.erase(begin, len);
  247. cursor = selection = begin;
  248. changed = true;
  249. }
  250. if (!text.empty()) {
  251. this->text.insert(cursor, text);
  252. cursor += text.size();
  253. selection = cursor;
  254. changed = true;
  255. }
  256. if (changed) {
  257. event::Change eChange;
  258. onChange(eChange);
  259. }
  260. }
  261. void TextField::copyClipboard() {
  262. if (cursor == selection)
  263. return;
  264. glfwSetClipboardString(APP->window->win, getSelectedText().c_str());
  265. }
  266. void TextField::cutClipboard() {
  267. copyClipboard();
  268. insertText("");
  269. }
  270. void TextField::pasteClipboard() {
  271. const char* newText = glfwGetClipboardString(APP->window->win);
  272. if (!newText)
  273. return;
  274. insertText(newText);
  275. }
  276. void TextField::cursorToPrevWord() {
  277. size_t pos = text.rfind(' ', std::max(cursor - 2, 0));
  278. if (pos == std::string::npos)
  279. cursor = 0;
  280. else
  281. cursor = std::min((int) pos + 1, (int) text.size());
  282. }
  283. void TextField::cursorToNextWord() {
  284. size_t pos = text.find(' ', std::min(cursor + 1, (int) text.size()));
  285. if (pos == std::string::npos)
  286. pos = text.size();
  287. cursor = pos;
  288. }
  289. void TextField::createContextMenu() {
  290. ui::Menu* menu = createMenu();
  291. TextFieldCutItem* cutItem = new TextFieldCutItem;
  292. cutItem->text = "Cut";
  293. cutItem->rightText = RACK_MOD_CTRL_NAME "+X";
  294. cutItem->textField = this;
  295. menu->addChild(cutItem);
  296. TextFieldCopyItem* copyItem = new TextFieldCopyItem;
  297. copyItem->text = "Copy";
  298. copyItem->rightText = RACK_MOD_CTRL_NAME "+C";
  299. copyItem->textField = this;
  300. menu->addChild(copyItem);
  301. TextFieldPasteItem* pasteItem = new TextFieldPasteItem;
  302. pasteItem->text = "Paste";
  303. pasteItem->rightText = RACK_MOD_CTRL_NAME "+V";
  304. pasteItem->textField = this;
  305. menu->addChild(pasteItem);
  306. TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem;
  307. selectAllItem->text = "Select all";
  308. selectAllItem->rightText = RACK_MOD_CTRL_NAME "+A";
  309. selectAllItem->textField = this;
  310. menu->addChild(selectAllItem);
  311. }
  312. } // namespace ui
  313. } // namespace rack