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.

382 lines
11KB

  1. #include "global_pre.hpp"
  2. #include "TSTextField.hpp"
  3. #include "widgets.hpp"
  4. #include "ui.hpp"
  5. // for gVg
  6. #include "window.hpp"
  7. // for key codes
  8. // // #include <GLFW/glfw3.h>
  9. #include "global_ui.hpp"
  10. using namespace rack;
  11. #include "trowaSoftComponents.hpp"
  12. TSTextField::TSTextField(TextType textType) : TextField() {
  13. setTextType(textType);
  14. font = Font::load(assetPlugin(plugin, TROWA_MONOSPACE_FONT));
  15. fontSize = 14.0f;
  16. backgroundColor = FORMS_DEFAULT_BG_COLOR;
  17. color = FORMS_DEFAULT_TEXT_COLOR;
  18. textOffset = Vec(0, 0);
  19. borderWidth = 1;
  20. borderColor = FORMS_DEFAULT_BORDER_COLOR;
  21. //caretColor = COLOR_TS_RED;// nvgRGBAf(1.0f - color.r, 1.0f - color.g, 1.0f - color.b, 0.70);
  22. caretColor = nvgRGBAf((color.r + backgroundColor.r) / 2.0, (color.g + backgroundColor.g) / 2.0, (color.b + backgroundColor.b) / 2.0, 0.70);
  23. return;
  24. }
  25. TSTextField::TSTextField(TextType textType, int maxLength) : TSTextField(textType) {
  26. this->maxLength = maxLength;
  27. return;
  28. }
  29. // Taken from Rack's LEDTextField
  30. int TSTextField::getTextPosition(Vec mousePos) {
  31. bndSetFont(font->handle);
  32. int textPos = bndIconLabelTextPosition(rack::global_ui->window.gVg, textOffset.x, textOffset.y,
  33. box.size.x - 2 * textOffset.x, box.size.y - 2 * textOffset.y,
  34. -1, fontSize, displayStr.c_str(), mousePos.x, mousePos.y);
  35. bndSetFont(rack::global_ui->window.gGuiFont->handle);
  36. return textPos;
  37. }
  38. // Draw if visible.
  39. void TSTextField::draw(NVGcontext *vg) {
  40. if (visible)
  41. {
  42. // Draw taken from Rack's LEDTextField and modified for scrolling (my quick & dirty ghetto text scrolling---ONLY truly effective for calculating the width with MONOSPACE font
  43. // since I don't want to do a bunch of calcs... [lazy]).
  44. nvgScissor(vg, 0, 0, box.size.x, box.size.y);
  45. // Background
  46. nvgBeginPath(vg);
  47. nvgRoundedRect(vg, 0, 0, box.size.x, box.size.y, 5.0);
  48. nvgFillColor(vg, backgroundColor);
  49. nvgFill(vg);
  50. // Border
  51. if (borderWidth > 0) {
  52. nvgStrokeWidth(vg, borderWidth);
  53. nvgStrokeColor(vg, borderColor);
  54. nvgStroke(vg);
  55. }
  56. // Text
  57. if (font->handle >= 0) {
  58. bndSetFont(font->handle);
  59. //NVGcolor highlightColor = color;
  60. //highlightColor.a = 0.5;
  61. int begin = min(cursor, selection);
  62. int end = (this == RACK_PLUGIN_UI_FOCUSED_WIDGET) ? max(cursor, selection) : -1;
  63. // Calculate overflow and the displayed text (based on bounding box)
  64. // Currently the scrolling should work for any font, **BUT** the width calculation is only really good for monospace.
  65. float txtBounds[4] = { 0,0,0,0 };
  66. nvgTextAlign(vg, NVG_ALIGN_LEFT);
  67. nvgFontSize(vg, fontSize);
  68. nvgFontFaceId(vg, font->handle);
  69. int maxTextWidth = box.size.x - textOffset.x * 2 - fontSize / 2.0; // There should be a caret
  70. float estLetterSize = nvgTextBounds(vg, 0, 0, "X", NULL, txtBounds); // Estimate size of a letter (accurate for monospace)
  71. float nextX = nvgTextBounds(vg, 0, 0, text.c_str(), NULL, txtBounds); // Calculate full string size
  72. displayStr = text;
  73. if (nextX > maxTextWidth) {
  74. int nChars = maxTextWidth / estLetterSize;
  75. if (nChars < 1)
  76. nChars = 1;
  77. if (this == RACK_PLUGIN_UI_FOCUSED_WIDGET) {
  78. int lastIx = (cursor > nChars) ? cursor : nChars;
  79. int startIx = clamp(lastIx - nChars, 0, lastIx);
  80. displayStr = text.substr(startIx, nChars);
  81. begin -= startIx;
  82. if (end > -1)
  83. end -= startIx;
  84. }
  85. else {
  86. displayStr = text.substr(0, nChars);
  87. }
  88. }
  89. // The caret color actually isn't the cursor color (that is hard-coded as nvgRGBf(0.337,0.502,0.761))
  90. //
  91. //void bndIconLabelCaret(NVGcontext *ctx, float x, float y, float w, float h,
  92. // int iconid, NVGcolor color, float fontsize, const char *label,
  93. // NVGcolor caretcolor, int cbegin, int cend
  94. bndIconLabelCaret(vg, /*x*/ textOffset.x, /*y*/ textOffset.y,
  95. /*w*/ box.size.x - 2 * textOffset.x, /*h*/ box.size.y - 2 * textOffset.y,
  96. /*iconid*/ -1, /*textColor*/ color, /*fontsize*/ fontSize,
  97. /*label*/ displayStr.c_str(),
  98. /*caretcolor*/ caretColor, /*cbegin*/ begin, /*cend*/ end);
  99. bndSetFont(rack::global_ui->window.gGuiFont->handle);
  100. }
  101. nvgResetScissor(vg);
  102. }
  103. } // end draw()
  104. // Request focus on this field.
  105. void TSTextField::requestFocus() {
  106. if (RACK_PLUGIN_UI_FOCUSED_WIDGET) {
  107. EventDefocus evt;
  108. RACK_PLUGIN_UI_FOCUSED_WIDGET->onDefocus(evt);
  109. RACK_PLUGIN_UI_FOCUSED_WIDGET_SET(NULL);
  110. }
  111. RACK_PLUGIN_UI_FOCUSED_WIDGET_SET(this);
  112. {
  113. EventFocus eFocus;
  114. onFocus(eFocus);
  115. cursor = 0;
  116. selection = text.length();
  117. }
  118. return;
  119. } // end requestFocus()
  120. // Remove invalid chars from input.
  121. std::string TSTextField::cleanseString(std::string newText)
  122. {
  123. if (allowedTextType == TextType::Any)
  124. {
  125. return newText.substr(0, maxLength);
  126. }
  127. else
  128. {
  129. // Remove invalid chars
  130. std::stringstream cleansedStr;
  131. // Issue: https://github.com/j4s0n-c/trowaSoft-VCV/issues/5. Changed from string constant (emtpy string "") to string object empty string ("") to older Linux compilers. Thx to @Chaircrusher.
  132. std::regex_replace(std::ostream_iterator<char>(cleansedStr), newText.begin(), newText.end(), regexInvalidChar, std::string(""));
  133. return cleansedStr.str().substr(0, maxLength);
  134. }
  135. } // end cleanseString()
  136. // Remove invalid chars
  137. /** Inserts text at the cursor, replacing the selection if necessary */
  138. void TSTextField::insertText(std::string newText) {
  139. if (cursor != selection) {
  140. int begin = min(cursor, selection);
  141. this->text.erase(begin, std::abs(selection - cursor));
  142. cursor = selection = begin;
  143. }
  144. std::string cleansedStr = cleanseString(newText);
  145. this->text.insert(cursor, cleansedStr);
  146. cursor += cleansedStr.size();
  147. selection = cursor;
  148. onTextChange();
  149. return;
  150. } // end insertText()
  151. // On Key
  152. void TSTextField::onText(EventText &e) {
  153. if (enabled)
  154. {
  155. if (e.codepoint < 128) {
  156. std::string newText(1, (char)e.codepoint);
  157. //insertText(newText);
  158. if ((allowedTextType == TextType::Any || regex_match(newText, regexChar)) && text.length() < maxLength)
  159. {
  160. insertText(newText);
  161. }
  162. }
  163. }
  164. e.consumed = true;
  165. return;
  166. } // end onText()
  167. void TSTextField::setText(std::string text) {
  168. this->text = cleanseString(text);
  169. selection = cursor = text.size();
  170. onTextChange();
  171. }
  172. // When the text changes.
  173. void TSTextField::onTextChange() {
  174. text = cleanseString(text);
  175. cursor = clamp(cursor, 0, text.size());
  176. selection = clamp(selection, 0, text.size());
  177. //debug("onTextChange() - New cursor: %d", cursor);
  178. return;
  179. } // end onTextChanged()
  180. // On key press.
  181. void TSTextField::onKey(EventKey &e) {
  182. if (!visible)
  183. {
  184. // Do not capture the keys.
  185. e.consumed = false;
  186. return;
  187. }
  188. if (!enabled)
  189. {
  190. e.consumed = false; // We are ingoring this.
  191. return;
  192. }
  193. // We can throw invalid chars away in onText(), so we don't have to check here anymore.
  194. //// Flag if we need to validate/cleanse this character (only if printable and if we are doing validation).
  195. //bool checkKey = (this->allowedTextType != TextType::Any) && isPrintableKey(e.key);
  196. switch (e.key) {
  197. case LGLW_VKEY_TAB/*GLFW_KEY_TAB*/:
  198. // If we have an event to fire, then do it
  199. if (windowIsShiftPressed())//(guiIsShiftPressed())
  200. {
  201. if (onShiftTabCallback != NULL)
  202. {
  203. onShiftTabCallback(id);
  204. }
  205. else if (prevField != NULL)
  206. {
  207. TSTextField* fField = prevField;
  208. if (!fField->visible)
  209. {
  210. switch (tabNextHiddenAction)
  211. {
  212. case TabFieldHiddenAction::MoveToNextVisibleTabField:
  213. fField = fField->prevField;
  214. while (fField != NULL && !fField->visible && fField != this && !fField->canTabToThisEnabled)
  215. fField = fField->prevField;
  216. if (fField == this || (fField != NULL && !fField->visible))
  217. fField = NULL;
  218. break;
  219. case TabFieldHiddenAction::ShowHiddenTabToField:
  220. while (fField != NULL && fField != this && !fField->canTabToThisEnabled)
  221. fField = fField->prevField;
  222. if (fField == this || (fField != NULL && !fField->canTabToThisEnabled))
  223. fField = NULL;
  224. if (fField != NULL)
  225. fField->visible = true;
  226. break;
  227. case TabFieldHiddenAction::DoNothing:
  228. default:
  229. fField = NULL;
  230. break;
  231. }
  232. }
  233. if (fField != NULL)
  234. {
  235. fField->requestFocus();
  236. }
  237. } // end if previous field
  238. }
  239. else if (onTabCallback != NULL)
  240. {
  241. onTabCallback(id);
  242. }
  243. else if (nextField != NULL)
  244. {
  245. TSTextField* fField = nextField;
  246. if (!fField->visible)
  247. {
  248. switch (tabNextHiddenAction)
  249. {
  250. case TabFieldHiddenAction::MoveToNextVisibleTabField:
  251. fField = fField->nextField;
  252. while (fField != NULL && !fField->visible && fField != this && !fField->canTabToThisEnabled)
  253. fField = fField->nextField;
  254. if (fField == this || (fField != NULL && !fField->visible))
  255. fField = NULL;
  256. break;
  257. case TabFieldHiddenAction::ShowHiddenTabToField:
  258. while (fField != NULL && fField != this && !fField->canTabToThisEnabled)
  259. fField = fField->nextField;
  260. if (fField == this || (fField != NULL && !fField->canTabToThisEnabled))
  261. fField = NULL;
  262. if (fField != NULL)
  263. fField->visible = true;
  264. break;
  265. case TabFieldHiddenAction::DoNothing:
  266. default:
  267. fField = NULL;
  268. break;
  269. }
  270. }
  271. if (fField != NULL)
  272. {
  273. fField->requestFocus();
  274. }
  275. } // end if next field
  276. break;
  277. // case GLFW_KEY_KP_ENTER:
  278. // {
  279. // // Key pad enter should also trigger event action
  280. // EventAction evt;
  281. // onAction(evt);
  282. // }
  283. // break;
  284. default:
  285. // Call base method
  286. TextField::onKey(e);
  287. break;
  288. }
  289. cursor = clamp(cursor, 0, text.size());
  290. selection = clamp(selection, 0, text.size());
  291. e.consumed = true;
  292. return;
  293. } // end onKey()
  294. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  295. // isPrintableKey()
  296. // @keyCode : (IN) The key that is pressed.
  297. // @returns: True if the key represents a printable character, false if not.
  298. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-
  299. bool isPrintableKey(int key)
  300. {
  301. bool isPrintable = false;
  302. switch (key)
  303. {
  304. case ' '/*GLFW_KEY_SPACE*/:
  305. case '\''/*GLFW_KEY_APOSTROPHE*/:
  306. case ','/*GLFW_KEY_COMMA*/:
  307. case '-'/*GLFW_KEY_MINUS*/:
  308. case '.'/*GLFW_KEY_PERIOD*/:
  309. case '/'/*GLFW_KEY_SLASH*/:
  310. case '0'/*GLFW_KEY_0*/:
  311. case '1'/*GLFW_KEY_1*/:
  312. case '2'/*GLFW_KEY_2*/:
  313. case '3'/*GLFW_KEY_3*/:
  314. case '4'/*GLFW_KEY_4*/:
  315. case '5'/*GLFW_KEY_5*/:
  316. case '6'/*GLFW_KEY_6*/:
  317. case '7'/*GLFW_KEY_7*/:
  318. case '8'/*GLFW_KEY_8*/:
  319. case '9'/*GLFW_KEY_9*/:
  320. case ';'/*GLFW_KEY_SEMICOLON*/:
  321. case '='/*GLFW_KEY_EQUAL*/:
  322. case 'a'/*GLFW_KEY_A*/:
  323. case 'b'/*GLFW_KEY_B*/:
  324. case 'c'/*GLFW_KEY_C*/:
  325. case 'd'/*GLFW_KEY_D*/:
  326. case 'e'/*GLFW_KEY_E*/:
  327. case 'f'/*GLFW_KEY_F*/:
  328. case 'g'/*GLFW_KEY_G*/:
  329. case 'h'/*GLFW_KEY_H*/:
  330. case 'i'/*GLFW_KEY_I*/:
  331. case 'j'/*GLFW_KEY_J*/:
  332. case 'k'/*GLFW_KEY_K*/:
  333. case 'l'/*GLFW_KEY_L*/:
  334. case 'm'/*GLFW_KEY_M*/:
  335. case 'n'/*GLFW_KEY_N*/:
  336. case 'o'/*GLFW_KEY_O*/:
  337. case 'p'/*GLFW_KEY_P*/:
  338. case 'q'/*GLFW_KEY_Q*/:
  339. case 'r'/*GLFW_KEY_R*/:
  340. case 's'/*GLFW_KEY_S*/:
  341. case 't'/*GLFW_KEY_T*/:
  342. case 'u'/*GLFW_KEY_U*/:
  343. case 'v'/*GLFW_KEY_V*/:
  344. case 'w'/*GLFW_KEY_W*/:
  345. case 'x'/*GLFW_KEY_X*/:
  346. case 'y'/*GLFW_KEY_Y*/:
  347. case 'z'/*GLFW_KEY_Z*/:
  348. case '['/*GLFW_KEY_LEFT_BRACKET*/:
  349. case '\\'/*GLFW_KEY_BACKSLASH*/:
  350. case ']'/*GLFW_KEY_RIGHT_BRACKET*/:
  351. case '^'/*GLFW_KEY_GRAVE_ACCENT*/:
  352. // case GLFW_KEY_WORLD_1:
  353. // case GLFW_KEY_WORLD_2:
  354. isPrintable = true;
  355. break;
  356. }
  357. return isPrintable;
  358. } // end isPrintableKey()