text_input.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. #include "text_input.h"
  2. #include <gui/elements.h>
  3. #include <furi.h>
  4. struct TextInput {
  5. View* view;
  6. };
  7. typedef struct {
  8. const char text;
  9. const uint8_t x;
  10. const uint8_t y;
  11. } TextInputKey;
  12. typedef struct {
  13. const char* header;
  14. char* text_buffer;
  15. size_t text_buffer_size;
  16. bool clear_default_text;
  17. TextInputCallback callback;
  18. void* callback_context;
  19. uint8_t selected_row;
  20. uint8_t selected_column;
  21. } TextInputModel;
  22. static const uint8_t keyboard_origin_x = 1;
  23. static const uint8_t keyboard_origin_y = 29;
  24. static const uint8_t keyboard_row_count = 3;
  25. #define ENTER_KEY '\r'
  26. #define BACKSPACE_KEY '\b'
  27. static const TextInputKey keyboard_keys_row_1[] = {
  28. {'q', 1, 8},
  29. {'w', 10, 8},
  30. {'e', 19, 8},
  31. {'r', 28, 8},
  32. {'t', 37, 8},
  33. {'y', 46, 8},
  34. {'u', 55, 8},
  35. {'i', 64, 8},
  36. {'o', 73, 8},
  37. {'p', 82, 8},
  38. {'0', 91, 8},
  39. {'1', 100, 8},
  40. {'2', 110, 8},
  41. {'3', 120, 8},
  42. };
  43. static const TextInputKey keyboard_keys_row_2[] = {
  44. {'a', 1, 20},
  45. {'s', 10, 20},
  46. {'d', 19, 20},
  47. {'f', 28, 20},
  48. {'g', 37, 20},
  49. {'h', 46, 20},
  50. {'j', 55, 20},
  51. {'k', 64, 20},
  52. {'l', 73, 20},
  53. {BACKSPACE_KEY, 82, 12},
  54. {'4', 100, 20},
  55. {'5', 110, 20},
  56. {'6', 120, 20},
  57. };
  58. static const TextInputKey keyboard_keys_row_3[] = {
  59. {'z', 1, 32},
  60. {'x', 10, 32},
  61. {'c', 19, 32},
  62. {'v', 28, 32},
  63. {'b', 37, 32},
  64. {'n', 46, 32},
  65. {'m', 55, 32},
  66. {'_', 64, 32},
  67. {ENTER_KEY, 74, 23},
  68. {'7', 100, 32},
  69. {'8', 110, 32},
  70. {'9', 120, 32},
  71. };
  72. static uint8_t get_row_size(uint8_t row_index) {
  73. uint8_t row_size = 0;
  74. switch(row_index + 1) {
  75. case 1:
  76. row_size = sizeof(keyboard_keys_row_1) / sizeof(TextInputKey);
  77. break;
  78. case 2:
  79. row_size = sizeof(keyboard_keys_row_2) / sizeof(TextInputKey);
  80. break;
  81. case 3:
  82. row_size = sizeof(keyboard_keys_row_3) / sizeof(TextInputKey);
  83. break;
  84. }
  85. return row_size;
  86. }
  87. static const TextInputKey* get_row(uint8_t row_index) {
  88. const TextInputKey* row = NULL;
  89. switch(row_index + 1) {
  90. case 1:
  91. row = keyboard_keys_row_1;
  92. break;
  93. case 2:
  94. row = keyboard_keys_row_2;
  95. break;
  96. case 3:
  97. row = keyboard_keys_row_3;
  98. break;
  99. }
  100. return row;
  101. }
  102. static const char get_selected_char(TextInputModel* model) {
  103. return get_row(model->selected_row)[model->selected_column].text;
  104. }
  105. static const bool char_is_lowercase(char letter) {
  106. return (letter >= 0x61 && letter <= 0x7A);
  107. }
  108. static const char char_to_uppercase(const char letter) {
  109. return (letter - 0x20);
  110. }
  111. static void text_input_backspace_cb(TextInputModel* model) {
  112. uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
  113. if(text_length > 0) {
  114. model->text_buffer[text_length - 1] = 0;
  115. }
  116. }
  117. static void text_input_view_draw_callback(Canvas* canvas, void* _model) {
  118. TextInputModel* model = _model;
  119. uint8_t text_length = strlen(model->text_buffer);
  120. uint8_t needed_string_width = canvas_width(canvas) - 8;
  121. uint8_t start_pos = 4;
  122. const char* text = model->text_buffer;
  123. canvas_clear(canvas);
  124. canvas_set_color(canvas, ColorBlack);
  125. canvas_draw_str(canvas, 2, 8, model->header);
  126. elements_slightly_rounded_frame(canvas, 1, 12, 126, 15);
  127. if(canvas_string_width(canvas, text) > needed_string_width) {
  128. canvas_draw_str(canvas, start_pos, 22, "...");
  129. start_pos += 6;
  130. needed_string_width -= 8;
  131. }
  132. while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
  133. text++;
  134. }
  135. if(model->clear_default_text) {
  136. elements_slightly_rounded_box(
  137. canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
  138. canvas_set_color(canvas, ColorWhite);
  139. } else {
  140. canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|");
  141. canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|");
  142. }
  143. canvas_draw_str(canvas, start_pos, 22, text);
  144. canvas_set_font(canvas, FontKeyboard);
  145. for(uint8_t row = 0; row <= keyboard_row_count; row++) {
  146. const uint8_t column_count = get_row_size(row);
  147. const TextInputKey* keys = get_row(row);
  148. for(size_t column = 0; column < column_count; column++) {
  149. if(keys[column].text == ENTER_KEY) {
  150. canvas_set_color(canvas, ColorBlack);
  151. if(model->selected_row == row && model->selected_column == column) {
  152. canvas_draw_icon(
  153. canvas,
  154. keyboard_origin_x + keys[column].x,
  155. keyboard_origin_y + keys[column].y,
  156. &I_KeySaveSelected_24x11);
  157. } else {
  158. canvas_draw_icon(
  159. canvas,
  160. keyboard_origin_x + keys[column].x,
  161. keyboard_origin_y + keys[column].y,
  162. &I_KeySave_24x11);
  163. }
  164. } else if(keys[column].text == BACKSPACE_KEY) {
  165. canvas_set_color(canvas, ColorBlack);
  166. if(model->selected_row == row && model->selected_column == column) {
  167. canvas_draw_icon(
  168. canvas,
  169. keyboard_origin_x + keys[column].x,
  170. keyboard_origin_y + keys[column].y,
  171. &I_KeyBackspaceSelected_16x9);
  172. } else {
  173. canvas_draw_icon(
  174. canvas,
  175. keyboard_origin_x + keys[column].x,
  176. keyboard_origin_y + keys[column].y,
  177. &I_KeyBackspace_16x9);
  178. }
  179. } else {
  180. if(model->selected_row == row && model->selected_column == column) {
  181. canvas_set_color(canvas, ColorBlack);
  182. canvas_draw_box(
  183. canvas,
  184. keyboard_origin_x + keys[column].x - 1,
  185. keyboard_origin_y + keys[column].y - 8,
  186. 7,
  187. 10);
  188. canvas_set_color(canvas, ColorWhite);
  189. } else {
  190. canvas_set_color(canvas, ColorBlack);
  191. }
  192. if(text_length == 0 && char_is_lowercase(keys[column].text)) {
  193. canvas_draw_glyph(
  194. canvas,
  195. keyboard_origin_x + keys[column].x,
  196. keyboard_origin_y + keys[column].y,
  197. char_to_uppercase(keys[column].text));
  198. } else {
  199. canvas_draw_glyph(
  200. canvas,
  201. keyboard_origin_x + keys[column].x,
  202. keyboard_origin_y + keys[column].y,
  203. keys[column].text);
  204. }
  205. }
  206. }
  207. }
  208. }
  209. static void text_input_handle_up(TextInput* text_input) {
  210. with_view_model(
  211. text_input->view, (TextInputModel * model) {
  212. if(model->selected_row > 0) {
  213. model->selected_row--;
  214. if(model->selected_column > get_row_size(model->selected_row) - 6) {
  215. model->selected_column = model->selected_column + 1;
  216. }
  217. }
  218. return true;
  219. });
  220. }
  221. static void text_input_handle_down(TextInput* text_input) {
  222. with_view_model(
  223. text_input->view, (TextInputModel * model) {
  224. if(model->selected_row < keyboard_row_count - 1) {
  225. model->selected_row++;
  226. if(model->selected_column > get_row_size(model->selected_row) - 4) {
  227. model->selected_column = model->selected_column - 1;
  228. }
  229. }
  230. return true;
  231. });
  232. }
  233. static void text_input_handle_left(TextInput* text_input) {
  234. with_view_model(
  235. text_input->view, (TextInputModel * model) {
  236. if(model->selected_column > 0) {
  237. model->selected_column--;
  238. } else {
  239. model->selected_column = get_row_size(model->selected_row) - 1;
  240. }
  241. return true;
  242. });
  243. }
  244. static void text_input_handle_right(TextInput* text_input) {
  245. with_view_model(
  246. text_input->view, (TextInputModel * model) {
  247. if(model->selected_column < get_row_size(model->selected_row) - 1) {
  248. model->selected_column++;
  249. } else {
  250. model->selected_column = 0;
  251. }
  252. return true;
  253. });
  254. }
  255. static void text_input_handle_ok(TextInput* text_input) {
  256. with_view_model(
  257. text_input->view, (TextInputModel * model) {
  258. char selected = get_selected_char(model);
  259. uint8_t text_length = strlen(model->text_buffer);
  260. if(selected == ENTER_KEY) {
  261. if(model->callback != 0 && text_length > 0) {
  262. model->callback(model->callback_context);
  263. }
  264. } else if(selected == BACKSPACE_KEY) {
  265. text_input_backspace_cb(model);
  266. } else if(text_length < (model->text_buffer_size - 1)) {
  267. if(model->clear_default_text) {
  268. text_length = 0;
  269. }
  270. if(text_length == 0 && char_is_lowercase(selected)) {
  271. selected = char_to_uppercase(selected);
  272. }
  273. model->text_buffer[text_length] = selected;
  274. model->text_buffer[text_length + 1] = 0;
  275. }
  276. model->clear_default_text = false;
  277. return true;
  278. });
  279. }
  280. static bool text_input_view_input_callback(InputEvent* event, void* context) {
  281. TextInput* text_input = context;
  282. furi_assert(text_input);
  283. bool consumed = false;
  284. if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
  285. switch(event->key) {
  286. case InputKeyUp:
  287. text_input_handle_up(text_input);
  288. consumed = true;
  289. break;
  290. case InputKeyDown:
  291. text_input_handle_down(text_input);
  292. consumed = true;
  293. break;
  294. case InputKeyLeft:
  295. text_input_handle_left(text_input);
  296. consumed = true;
  297. break;
  298. case InputKeyRight:
  299. text_input_handle_right(text_input);
  300. consumed = true;
  301. break;
  302. case InputKeyOk:
  303. text_input_handle_ok(text_input);
  304. consumed = true;
  305. break;
  306. default:
  307. break;
  308. }
  309. }
  310. if((event->type == InputTypeLong || event->type == InputTypeRepeat) &&
  311. event->key == InputKeyBack) {
  312. with_view_model(
  313. text_input->view, (TextInputModel * model) {
  314. text_input_backspace_cb(model);
  315. return true;
  316. });
  317. consumed = true;
  318. }
  319. return consumed;
  320. }
  321. TextInput* text_input_alloc() {
  322. TextInput* text_input = furi_alloc(sizeof(TextInput));
  323. text_input->view = view_alloc();
  324. view_set_context(text_input->view, text_input);
  325. view_allocate_model(text_input->view, ViewModelTypeLocking, sizeof(TextInputModel));
  326. view_set_draw_callback(text_input->view, text_input_view_draw_callback);
  327. view_set_input_callback(text_input->view, text_input_view_input_callback);
  328. with_view_model(
  329. text_input->view, (TextInputModel * model) {
  330. model->text_buffer_size = 0;
  331. model->header = "";
  332. model->selected_row = 0;
  333. model->selected_column = 0;
  334. model->clear_default_text = false;
  335. return true;
  336. });
  337. return text_input;
  338. }
  339. void text_input_free(TextInput* text_input) {
  340. furi_assert(text_input);
  341. view_free(text_input->view);
  342. free(text_input);
  343. }
  344. View* text_input_get_view(TextInput* text_input) {
  345. furi_assert(text_input);
  346. return text_input->view;
  347. }
  348. void text_input_set_result_callback(
  349. TextInput* text_input,
  350. TextInputCallback callback,
  351. void* callback_context,
  352. char* text_buffer,
  353. size_t text_buffer_size,
  354. bool clear_default_text) {
  355. with_view_model(
  356. text_input->view, (TextInputModel * model) {
  357. model->callback = callback;
  358. model->callback_context = callback_context;
  359. model->text_buffer = text_buffer;
  360. model->text_buffer_size = text_buffer_size;
  361. model->clear_default_text = clear_default_text;
  362. return true;
  363. });
  364. }
  365. void text_input_set_header_text(TextInput* text_input, const char* text) {
  366. with_view_model(
  367. text_input->view, (TextInputModel * model) {
  368. model->header = text;
  369. return true;
  370. });
  371. }