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.

221 lines
5.2KB

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