mag_text_input.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. #include "mag_text_input.h"
  2. #include <gui/elements.h>
  3. #include <furi.h>
  4. struct Mag_TextInput {
  5. View* view;
  6. FuriTimer* timer;
  7. };
  8. typedef struct {
  9. const char text;
  10. const uint8_t x;
  11. const uint8_t y;
  12. } Mag_TextInputKey;
  13. typedef struct {
  14. const char* header;
  15. char* text_buffer;
  16. size_t text_buffer_size;
  17. bool clear_default_text;
  18. Mag_TextInputCallback callback;
  19. void* callback_context;
  20. uint8_t selected_row;
  21. uint8_t selected_column;
  22. // Mag_TextInputValidatorCallback validator_callback;
  23. // void* validator_callback_context;
  24. // FuriString* validator_text;
  25. // bool validator_message_visible;
  26. } Mag_TextInputModel;
  27. static const uint8_t keyboard_origin_x = 1;
  28. static const uint8_t keyboard_origin_y = 29;
  29. static const uint8_t keyboard_row_count = 3;
  30. #define ENTER_KEY '\r'
  31. #define BACKSPACE_KEY '\b'
  32. static const Mag_TextInputKey keyboard_keys_row_1[] = {
  33. {'q', 1, 8},
  34. {'w', 9, 8},
  35. {'e', 17, 8},
  36. {'r', 25, 8},
  37. {'t', 33, 8},
  38. {'y', 41, 8},
  39. {'u', 49, 8},
  40. {'i', 57, 8},
  41. {'o', 65, 8},
  42. {'p', 73, 8},
  43. {'0', 81, 8},
  44. {'1', 89, 8},
  45. {'2', 97, 8},
  46. {'3', 105, 8},
  47. {'%', 113, 8},
  48. {'^', 120, 8},
  49. };
  50. static const Mag_TextInputKey keyboard_keys_row_2[] = {
  51. {'a', 1, 20},
  52. {'s', 9, 20},
  53. {'d', 18, 20},
  54. {'f', 25, 20},
  55. {'g', 33, 20},
  56. {'h', 41, 20},
  57. {'j', 49, 20},
  58. {'k', 57, 20},
  59. {'l', 65, 20},
  60. {BACKSPACE_KEY, 72, 12},
  61. {'4', 89, 20},
  62. {'5', 97, 20},
  63. {'6', 105, 20},
  64. {'/', 113, 20},
  65. {'?', 120, 20},
  66. };
  67. static const Mag_TextInputKey keyboard_keys_row_3[] = {
  68. {'z', 1, 32},
  69. {'x', 9, 32},
  70. {'c', 18, 32},
  71. {'v', 25, 32},
  72. {'b', 33, 32},
  73. {'n', 41, 32},
  74. {'m', 49, 32},
  75. {'_', 57, 32},
  76. {ENTER_KEY, 64, 23},
  77. {'7', 89, 32},
  78. {'8', 97, 32},
  79. {'9', 105, 32},
  80. {';', 113, 32},
  81. {'=', 120, 32},
  82. };
  83. static uint8_t get_row_size(uint8_t row_index) {
  84. uint8_t row_size = 0;
  85. switch(row_index + 1) {
  86. case 1:
  87. row_size = sizeof(keyboard_keys_row_1) / sizeof(Mag_TextInputKey);
  88. break;
  89. case 2:
  90. row_size = sizeof(keyboard_keys_row_2) / sizeof(Mag_TextInputKey);
  91. break;
  92. case 3:
  93. row_size = sizeof(keyboard_keys_row_3) / sizeof(Mag_TextInputKey);
  94. break;
  95. }
  96. return row_size;
  97. }
  98. static const Mag_TextInputKey* get_row(uint8_t row_index) {
  99. const Mag_TextInputKey* row = NULL;
  100. switch(row_index + 1) {
  101. case 1:
  102. row = keyboard_keys_row_1;
  103. break;
  104. case 2:
  105. row = keyboard_keys_row_2;
  106. break;
  107. case 3:
  108. row = keyboard_keys_row_3;
  109. break;
  110. }
  111. return row;
  112. }
  113. static char get_selected_char(Mag_TextInputModel* model) {
  114. return get_row(model->selected_row)[model->selected_column].text;
  115. }
  116. static bool char_is_lowercase(char letter) {
  117. return (letter >= 0x61 && letter <= 0x7A);
  118. }
  119. static char char_to_uppercase(const char letter) {
  120. if(letter == '_') {
  121. return 0x20;
  122. } else if(isalpha(letter)) {
  123. return (letter - 0x20);
  124. } else {
  125. return letter;
  126. }
  127. }
  128. static void mag_text_input_backspace_cb(Mag_TextInputModel* model) {
  129. uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
  130. if(text_length > 0) {
  131. model->text_buffer[text_length - 1] = 0;
  132. }
  133. }
  134. static void mag_text_input_view_draw_callback(Canvas* canvas, void* _model) {
  135. Mag_TextInputModel* model = _model;
  136. // uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
  137. uint8_t needed_string_width = canvas_width(canvas) - 8;
  138. uint8_t start_pos = 4;
  139. const char* text = model->text_buffer;
  140. canvas_clear(canvas);
  141. canvas_set_color(canvas, ColorBlack);
  142. canvas_draw_str(canvas, 2, 8, model->header);
  143. elements_slightly_rounded_frame(canvas, 1, 12, 126, 15);
  144. if(canvas_string_width(canvas, text) > needed_string_width) {
  145. canvas_draw_str(canvas, start_pos, 22, "...");
  146. start_pos += 6;
  147. needed_string_width -= 8;
  148. }
  149. while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
  150. text++;
  151. }
  152. if(model->clear_default_text) {
  153. elements_slightly_rounded_box(
  154. canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
  155. canvas_set_color(canvas, ColorWhite);
  156. } else {
  157. canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|");
  158. canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|");
  159. }
  160. canvas_draw_str(canvas, start_pos, 22, text);
  161. canvas_set_font(canvas, FontKeyboard);
  162. for(uint8_t row = 0; row <= keyboard_row_count; row++) {
  163. const uint8_t column_count = get_row_size(row);
  164. const Mag_TextInputKey* keys = get_row(row);
  165. for(size_t column = 0; column < column_count; column++) {
  166. if(keys[column].text == ENTER_KEY) {
  167. canvas_set_color(canvas, ColorBlack);
  168. if(model->selected_row == row && model->selected_column == column) {
  169. canvas_draw_icon(
  170. canvas,
  171. keyboard_origin_x + keys[column].x,
  172. keyboard_origin_y + keys[column].y,
  173. &I_KeySaveSelected_24x11);
  174. } else {
  175. canvas_draw_icon(
  176. canvas,
  177. keyboard_origin_x + keys[column].x,
  178. keyboard_origin_y + keys[column].y,
  179. &I_KeySave_24x11);
  180. }
  181. } else if(keys[column].text == BACKSPACE_KEY) {
  182. canvas_set_color(canvas, ColorBlack);
  183. if(model->selected_row == row && model->selected_column == column) {
  184. canvas_draw_icon(
  185. canvas,
  186. keyboard_origin_x + keys[column].x,
  187. keyboard_origin_y + keys[column].y,
  188. &I_KeyBackspaceSelected_16x9);
  189. } else {
  190. canvas_draw_icon(
  191. canvas,
  192. keyboard_origin_x + keys[column].x,
  193. keyboard_origin_y + keys[column].y,
  194. &I_KeyBackspace_16x9);
  195. }
  196. } else {
  197. if(model->selected_row == row && model->selected_column == column) {
  198. canvas_set_color(canvas, ColorBlack);
  199. canvas_draw_box(
  200. canvas,
  201. keyboard_origin_x + keys[column].x - 1,
  202. keyboard_origin_y + keys[column].y - 8,
  203. 7,
  204. 10);
  205. canvas_set_color(canvas, ColorWhite);
  206. } else {
  207. canvas_set_color(canvas, ColorBlack);
  208. }
  209. if(model->clear_default_text || (char_is_lowercase(keys[column].text))) {
  210. canvas_draw_glyph(
  211. canvas,
  212. keyboard_origin_x + keys[column].x,
  213. keyboard_origin_y + keys[column].y,
  214. char_to_uppercase(keys[column].text));
  215. //keys[column].text);
  216. } else {
  217. canvas_draw_glyph(
  218. canvas,
  219. keyboard_origin_x + keys[column].x,
  220. keyboard_origin_y + keys[column].y,
  221. keys[column].text);
  222. }
  223. }
  224. }
  225. }
  226. /*if(model->validator_message_visible) {
  227. canvas_set_font(canvas, FontSecondary);
  228. canvas_set_color(canvas, ColorWhite);
  229. canvas_draw_box(canvas, 8, 10, 110, 48);
  230. canvas_set_color(canvas, ColorBlack);
  231. canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42);
  232. canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
  233. canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
  234. elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
  235. canvas_set_font(canvas, FontKeyboard);
  236. }*/
  237. }
  238. static void mag_text_input_handle_up(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
  239. UNUSED(mag_text_input);
  240. if(model->selected_row > 0) {
  241. model->selected_row--;
  242. if(model->selected_column > get_row_size(model->selected_row) - 6) {
  243. model->selected_column = model->selected_column + 1;
  244. }
  245. }
  246. }
  247. static void mag_text_input_handle_down(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
  248. UNUSED(mag_text_input);
  249. if(model->selected_row < keyboard_row_count - 1) {
  250. model->selected_row++;
  251. if(model->selected_column > get_row_size(model->selected_row) - 4) {
  252. model->selected_column = model->selected_column - 1;
  253. }
  254. }
  255. }
  256. static void mag_text_input_handle_left(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
  257. UNUSED(mag_text_input);
  258. if(model->selected_column > 0) {
  259. model->selected_column--;
  260. } else {
  261. model->selected_column = get_row_size(model->selected_row) - 1;
  262. }
  263. }
  264. static void mag_text_input_handle_right(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
  265. UNUSED(mag_text_input);
  266. if(model->selected_column < get_row_size(model->selected_row) - 1) {
  267. model->selected_column++;
  268. } else {
  269. model->selected_column = 0;
  270. }
  271. }
  272. static void
  273. mag_text_input_handle_ok(Mag_TextInput* mag_text_input, Mag_TextInputModel* model, bool shift) {
  274. UNUSED(mag_text_input);
  275. char selected = get_selected_char(model);
  276. uint8_t text_length = strlen(model->text_buffer);
  277. if(shift) {
  278. selected = char_to_uppercase(selected);
  279. }
  280. if(selected == ENTER_KEY) {
  281. /*if(model->validator_callback &&
  282. (!model->validator_callback(
  283. model->text_buffer, model->validator_text, model->validator_callback_context))) {
  284. model->validator_message_visible = true;
  285. furi_timer_start(mag_text_input->timer, furi_kernel_get_tick_frequency() * 4);
  286. } else*/
  287. if(model->callback != 0 && text_length > 0) {
  288. model->callback(model->callback_context);
  289. }
  290. } else if(selected == BACKSPACE_KEY) {
  291. mag_text_input_backspace_cb(model);
  292. } else {
  293. if(model->clear_default_text) {
  294. text_length = 0;
  295. }
  296. if(text_length < (model->text_buffer_size - 1)) {
  297. if(char_is_lowercase(selected)) {
  298. selected = char_to_uppercase(selected);
  299. }
  300. model->text_buffer[text_length] = selected;
  301. model->text_buffer[text_length + 1] = 0;
  302. }
  303. }
  304. model->clear_default_text = false;
  305. }
  306. static bool mag_text_input_view_input_callback(InputEvent* event, void* context) {
  307. Mag_TextInput* mag_text_input = context;
  308. furi_assert(mag_text_input);
  309. bool consumed = false;
  310. // Acquire model
  311. Mag_TextInputModel* model = view_get_model(mag_text_input->view);
  312. /* if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
  313. model->validator_message_visible) {
  314. model->validator_message_visible = false;
  315. consumed = true;
  316. } else*/
  317. if(event->type == InputTypeShort) {
  318. consumed = true;
  319. switch(event->key) {
  320. case InputKeyUp:
  321. mag_text_input_handle_up(mag_text_input, model);
  322. break;
  323. case InputKeyDown:
  324. mag_text_input_handle_down(mag_text_input, model);
  325. break;
  326. case InputKeyLeft:
  327. mag_text_input_handle_left(mag_text_input, model);
  328. break;
  329. case InputKeyRight:
  330. mag_text_input_handle_right(mag_text_input, model);
  331. break;
  332. case InputKeyOk:
  333. mag_text_input_handle_ok(mag_text_input, model, false);
  334. break;
  335. default:
  336. consumed = false;
  337. break;
  338. }
  339. } else if(event->type == InputTypeLong) {
  340. consumed = true;
  341. switch(event->key) {
  342. case InputKeyUp:
  343. mag_text_input_handle_up(mag_text_input, model);
  344. break;
  345. case InputKeyDown:
  346. mag_text_input_handle_down(mag_text_input, model);
  347. break;
  348. case InputKeyLeft:
  349. mag_text_input_handle_left(mag_text_input, model);
  350. break;
  351. case InputKeyRight:
  352. mag_text_input_handle_right(mag_text_input, model);
  353. break;
  354. case InputKeyOk:
  355. mag_text_input_handle_ok(mag_text_input, model, true);
  356. break;
  357. case InputKeyBack:
  358. mag_text_input_backspace_cb(model);
  359. break;
  360. default:
  361. consumed = false;
  362. break;
  363. }
  364. } else if(event->type == InputTypeRepeat) {
  365. consumed = true;
  366. switch(event->key) {
  367. case InputKeyUp:
  368. mag_text_input_handle_up(mag_text_input, model);
  369. break;
  370. case InputKeyDown:
  371. mag_text_input_handle_down(mag_text_input, model);
  372. break;
  373. case InputKeyLeft:
  374. mag_text_input_handle_left(mag_text_input, model);
  375. break;
  376. case InputKeyRight:
  377. mag_text_input_handle_right(mag_text_input, model);
  378. break;
  379. case InputKeyBack:
  380. mag_text_input_backspace_cb(model);
  381. break;
  382. default:
  383. consumed = false;
  384. break;
  385. }
  386. }
  387. // Commit model
  388. view_commit_model(mag_text_input->view, consumed);
  389. return consumed;
  390. }
  391. void mag_text_input_timer_callback(void* context) {
  392. furi_assert(context);
  393. Mag_TextInput* mag_text_input = context;
  394. UNUSED(mag_text_input);
  395. /*with_view_model(
  396. mag_text_input->view,
  397. Mag_TextInputModel * model,
  398. { model->validator_message_visible = false; },
  399. true);*/
  400. }
  401. Mag_TextInput* mag_text_input_alloc() {
  402. Mag_TextInput* mag_text_input = malloc(sizeof(Mag_TextInput));
  403. mag_text_input->view = view_alloc();
  404. view_set_context(mag_text_input->view, mag_text_input);
  405. view_allocate_model(mag_text_input->view, ViewModelTypeLocking, sizeof(Mag_TextInputModel));
  406. view_set_draw_callback(mag_text_input->view, mag_text_input_view_draw_callback);
  407. view_set_input_callback(mag_text_input->view, mag_text_input_view_input_callback);
  408. mag_text_input->timer =
  409. furi_timer_alloc(mag_text_input_timer_callback, FuriTimerTypeOnce, mag_text_input);
  410. /*with_view_model(
  411. mag_text_input->view,
  412. Mag_TextInputModel * model,
  413. { model->validator_text = furi_string_alloc(); },
  414. false);*/
  415. mag_text_input_reset(mag_text_input);
  416. return mag_text_input;
  417. }
  418. void mag_text_input_free(Mag_TextInput* mag_text_input) {
  419. furi_assert(mag_text_input);
  420. /*with_view_model(
  421. mag_text_input->view,
  422. Mag_TextInputModel * model,
  423. { furi_string_free(model->validator_text); },
  424. false);*/
  425. // Send stop command
  426. furi_timer_stop(mag_text_input->timer);
  427. // Release allocated memory
  428. furi_timer_free(mag_text_input->timer);
  429. view_free(mag_text_input->view);
  430. free(mag_text_input);
  431. }
  432. void mag_text_input_reset(Mag_TextInput* mag_text_input) {
  433. furi_assert(mag_text_input);
  434. with_view_model(
  435. mag_text_input->view,
  436. Mag_TextInputModel * model,
  437. {
  438. model->text_buffer_size = 0;
  439. model->header = "";
  440. model->selected_row = 0;
  441. model->selected_column = 0;
  442. model->clear_default_text = false;
  443. model->text_buffer = NULL;
  444. model->text_buffer_size = 0;
  445. model->callback = NULL;
  446. model->callback_context = NULL;
  447. /*model->validator_callback = NULL;
  448. model->validator_callback_context = NULL;
  449. furi_string_reset(model->validator_text);
  450. model->validator_message_visible = false;*/
  451. },
  452. true);
  453. }
  454. View* mag_text_input_get_view(Mag_TextInput* mag_text_input) {
  455. furi_assert(mag_text_input);
  456. return mag_text_input->view;
  457. }
  458. void mag_text_input_set_result_callback(
  459. Mag_TextInput* mag_text_input,
  460. Mag_TextInputCallback callback,
  461. void* callback_context,
  462. char* text_buffer,
  463. size_t text_buffer_size,
  464. bool clear_default_text) {
  465. with_view_model(
  466. mag_text_input->view,
  467. Mag_TextInputModel * model,
  468. {
  469. model->callback = callback;
  470. model->callback_context = callback_context;
  471. model->text_buffer = text_buffer;
  472. model->text_buffer_size = text_buffer_size;
  473. model->clear_default_text = clear_default_text;
  474. if(text_buffer && text_buffer[0] != '\0') {
  475. // Set focus on Save
  476. model->selected_row = 2;
  477. model->selected_column = 8;
  478. }
  479. },
  480. true);
  481. }
  482. /* void mag_text_input_set_validator(
  483. Mag_TextInput* mag_text_input,
  484. Mag_TextInputValidatorCallback callback,
  485. void* callback_context) {
  486. with_view_model(
  487. mag_text_input->view,
  488. Mag_TextInputModel * model,
  489. {
  490. model->validator_callback = callback;
  491. model->validator_callback_context = callback_context;
  492. },
  493. true);
  494. }
  495. Mag_TextInputValidatorCallback
  496. mag_text_input_get_validator_callback(Mag_TextInput* mag_text_input) {
  497. Mag_TextInputValidatorCallback validator_callback = NULL;
  498. with_view_model(
  499. mag_text_input->view,
  500. Mag_TextInputModel * model,
  501. { validator_callback = model->validator_callback; },
  502. false);
  503. return validator_callback;
  504. }
  505. void* mag_text_input_get_validator_callback_context(Mag_TextInput* mag_text_input) {
  506. void* validator_callback_context = NULL;
  507. with_view_model(
  508. mag_text_input->view,
  509. Mag_TextInputModel * model,
  510. { validator_callback_context = model->validator_callback_context; },
  511. false);
  512. return validator_callback_context;
  513. }*/
  514. void mag_text_input_set_header_text(Mag_TextInput* mag_text_input, const char* text) {
  515. with_view_model(
  516. mag_text_input->view, Mag_TextInputModel * model, { model->header = text; }, true);
  517. }