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.

222 lines
5.4KB

  1. #pragma once
  2. #include "widgets/OpaqueWidget.hpp"
  3. #include "ui/common.hpp"
  4. #include "event.hpp"
  5. #include "context.hpp"
  6. namespace rack {
  7. struct TextField : OpaqueWidget {
  8. std::string text;
  9. std::string placeholder;
  10. bool multiline = false;
  11. /** The index of the text cursor */
  12. int cursor = 0;
  13. /** The index of the other end of the selection.
  14. If nothing is selected, this is equal to `cursor`.
  15. */
  16. int selection = 0;
  17. TextField() {
  18. box.size.y = BND_WIDGET_HEIGHT;
  19. }
  20. void draw(NVGcontext *vg) override {
  21. nvgScissor(vg, 0, 0, box.size.x, box.size.y);
  22. BNDwidgetState state;
  23. if (this == context()->event->selectedWidget)
  24. state = BND_ACTIVE;
  25. else if (this == context()->event->hoveredWidget)
  26. state = BND_HOVER;
  27. else
  28. state = BND_DEFAULT;
  29. int begin = std::min(cursor, selection);
  30. int end = std::max(cursor, selection);
  31. bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end);
  32. // Draw placeholder text
  33. if (text.empty() && state != BND_ACTIVE) {
  34. bndIconLabelCaret(vg, 0.0, 0.0, box.size.x, box.size.y, -1, bndGetTheme()->textFieldTheme.itemColor, 13, placeholder.c_str(), bndGetTheme()->textFieldTheme.itemColor, 0, -1);
  35. }
  36. nvgResetScissor(vg);
  37. }
  38. void onButton(event::Button &e) override {
  39. if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_LEFT) {
  40. cursor = selection = getTextPosition(e.pos);
  41. }
  42. OpaqueWidget::onButton(e);
  43. }
  44. void onHover(event::Hover &e) override {
  45. if (this == context()->event->draggedWidget) {
  46. int pos = getTextPosition(e.pos);
  47. if (pos != selection) {
  48. cursor = pos;
  49. }
  50. }
  51. OpaqueWidget::onHover(e);
  52. }
  53. void onEnter(event::Enter &e) override {
  54. e.target = this;
  55. }
  56. void onSelectText(event::SelectText &e) override {
  57. if (e.codepoint < 128) {
  58. std::string newText(1, (char) e.codepoint);
  59. insertText(newText);
  60. }
  61. e.target = this;
  62. }
  63. void onSelectKey(event::SelectKey &e) override {
  64. switch (e.key) {
  65. case GLFW_KEY_BACKSPACE: {
  66. if (cursor == selection) {
  67. cursor--;
  68. if (cursor >= 0) {
  69. text.erase(cursor, 1);
  70. event::Change eChange;
  71. onChange(eChange);
  72. }
  73. selection = cursor;
  74. }
  75. else {
  76. int begin = std::min(cursor, selection);
  77. text.erase(begin, std::abs(selection - cursor));
  78. event::Change eChange;
  79. onChange(eChange);
  80. cursor = selection = begin;
  81. }
  82. } break;
  83. case GLFW_KEY_DELETE: {
  84. if (cursor == selection) {
  85. text.erase(cursor, 1);
  86. event::Change eChange;
  87. onChange(eChange);
  88. }
  89. else {
  90. int begin = std::min(cursor, selection);
  91. text.erase(begin, std::abs(selection - cursor));
  92. event::Change eChange;
  93. onChange(eChange);
  94. cursor = selection = begin;
  95. }
  96. } break;
  97. case GLFW_KEY_LEFT: {
  98. if (context()->window->isModPressed()) {
  99. while (--cursor > 0) {
  100. if (text[cursor] == ' ')
  101. break;
  102. }
  103. }
  104. else {
  105. cursor--;
  106. }
  107. if (!context()->window->isShiftPressed()) {
  108. selection = cursor;
  109. }
  110. } break;
  111. case GLFW_KEY_RIGHT: {
  112. if (context()->window->isModPressed()) {
  113. while (++cursor < (int) text.size()) {
  114. if (text[cursor] == ' ')
  115. break;
  116. }
  117. }
  118. else {
  119. cursor++;
  120. }
  121. if (!context()->window->isShiftPressed()) {
  122. selection = cursor;
  123. }
  124. } break;
  125. case GLFW_KEY_HOME: {
  126. selection = cursor = 0;
  127. } break;
  128. case GLFW_KEY_END: {
  129. selection = cursor = text.size();
  130. } break;
  131. case GLFW_KEY_V: {
  132. if (context()->window->isModPressed()) {
  133. const char *newText = glfwGetClipboardString(context()->window->win);
  134. if (newText)
  135. insertText(newText);
  136. }
  137. } break;
  138. case GLFW_KEY_X: {
  139. if (context()->window->isModPressed()) {
  140. if (cursor != selection) {
  141. int begin = std::min(cursor, selection);
  142. std::string selectedText = text.substr(begin, std::abs(selection - cursor));
  143. glfwSetClipboardString(context()->window->win, selectedText.c_str());
  144. insertText("");
  145. }
  146. }
  147. } break;
  148. case GLFW_KEY_C: {
  149. if (context()->window->isModPressed()) {
  150. if (cursor != selection) {
  151. int begin = std::min(cursor, selection);
  152. std::string selectedText = text.substr(begin, std::abs(selection - cursor));
  153. glfwSetClipboardString(context()->window->win, selectedText.c_str());
  154. }
  155. }
  156. } break;
  157. case GLFW_KEY_A: {
  158. if (context()->window->isModPressed()) {
  159. selection = 0;
  160. cursor = text.size();
  161. }
  162. } break;
  163. case GLFW_KEY_ENTER: {
  164. if (multiline) {
  165. insertText("\n");
  166. }
  167. else {
  168. event::Action eAction;
  169. onAction(eAction);
  170. }
  171. } break;
  172. }
  173. cursor = clamp(cursor, 0, (int) text.size());
  174. selection = clamp(selection, 0, (int) text.size());
  175. e.target = this;
  176. }
  177. /** Inserts text at the cursor, replacing the selection if necessary */
  178. void insertText(std::string text) {
  179. if (cursor != selection) {
  180. int begin = std::min(cursor, selection);
  181. this->text.erase(begin, std::abs(selection - cursor));
  182. cursor = selection = begin;
  183. }
  184. this->text.insert(cursor, text);
  185. cursor += text.size();
  186. selection = cursor;
  187. event::Change eChange;
  188. onChange(eChange);
  189. }
  190. /** Replaces the entire text */
  191. void setText(std::string text) {
  192. this->text = text;
  193. selection = cursor = text.size();
  194. event::Change eChange;
  195. onChange(eChange);
  196. }
  197. virtual int getTextPosition(Vec mousePos) {
  198. return bndTextFieldTextPosition(context()->window->vg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), mousePos.x, mousePos.y);
  199. }
  200. };
  201. } // namespace rack