code_input.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. #include "code_input.h"
  2. #include <gui/elements.h>
  3. #include <furi.h>
  4. #define MAX_CODE_LEN 10
  5. struct CodeInput {
  6. View* view;
  7. };
  8. typedef enum {
  9. CodeInputStateVerify,
  10. CodeInputStateUpdate,
  11. CodeInputStateTotal,
  12. } CodeInputStateEnum;
  13. typedef enum {
  14. CodeInputFirst,
  15. CodeInputSecond,
  16. CodeInputTotal,
  17. } CodeInputsEnum;
  18. typedef struct {
  19. uint8_t state;
  20. uint8_t current;
  21. bool ext_update;
  22. uint8_t input_length[CodeInputTotal];
  23. uint8_t local_buffer[CodeInputTotal][MAX_CODE_LEN];
  24. CodeInputOkCallback ok_callback;
  25. CodeInputFailCallback fail_callback;
  26. void* callback_context;
  27. const char* header;
  28. uint8_t* ext_buffer;
  29. uint8_t* ext_buffer_length;
  30. } CodeInputModel;
  31. static const Icon* keys_assets[] = {
  32. [InputKeyUp] = &I_ButtonUp_7x4,
  33. [InputKeyDown] = &I_ButtonDown_7x4,
  34. [InputKeyRight] = &I_ButtonRight_4x7,
  35. [InputKeyLeft] = &I_ButtonLeft_4x7,
  36. };
  37. /**
  38. * @brief Compare buffers
  39. *
  40. * @param in Input buffer pointer
  41. * @param len_in Input array length
  42. * @param src Source buffer pointer
  43. * @param len_src Source array length
  44. */
  45. bool code_input_compare(uint8_t* in, size_t len_in, uint8_t* src, size_t len_src) {
  46. bool result = false;
  47. do {
  48. result = (len_in && len_src);
  49. if(!result) {
  50. break;
  51. }
  52. result = (len_in == len_src);
  53. if(!result) {
  54. break;
  55. }
  56. for(size_t i = 0; i < len_in; i++) {
  57. result = (in[i] == src[i]);
  58. if(!result) {
  59. break;
  60. }
  61. }
  62. } while(false);
  63. return result;
  64. }
  65. /**
  66. * @brief Compare local buffers
  67. *
  68. * @param model
  69. */
  70. static bool code_input_compare_local(CodeInputModel* model) {
  71. uint8_t* source = model->local_buffer[CodeInputFirst];
  72. size_t source_length = model->input_length[CodeInputFirst];
  73. uint8_t* input = model->local_buffer[CodeInputSecond];
  74. size_t input_length = model->input_length[CodeInputSecond];
  75. return code_input_compare(input, input_length, source, source_length);
  76. }
  77. /**
  78. * @brief Compare ext with local
  79. *
  80. * @param model
  81. */
  82. static bool code_input_compare_ext(CodeInputModel* model) {
  83. uint8_t* input = model->local_buffer[CodeInputFirst];
  84. size_t input_length = model->input_length[CodeInputFirst];
  85. uint8_t* source = model->ext_buffer;
  86. size_t source_length = *model->ext_buffer_length;
  87. return code_input_compare(input, input_length, source, source_length);
  88. }
  89. /**
  90. * @brief Set ext buffer
  91. *
  92. * @param model
  93. */
  94. static void code_input_set_ext(CodeInputModel* model) {
  95. *model->ext_buffer_length = model->input_length[CodeInputFirst];
  96. for(size_t i = 0; i <= model->input_length[CodeInputFirst]; i++) {
  97. model->ext_buffer[i] = model->local_buffer[CodeInputFirst][i];
  98. }
  99. }
  100. /**
  101. * @brief Draw input sequence
  102. *
  103. * @param canvas
  104. * @param buffer
  105. * @param length
  106. * @param x
  107. * @param y
  108. * @param active
  109. */
  110. static void code_input_draw_sequence(
  111. Canvas* canvas,
  112. uint8_t* buffer,
  113. uint8_t length,
  114. uint8_t x,
  115. uint8_t y,
  116. bool active) {
  117. uint8_t pos_x = x + 6;
  118. uint8_t pos_y = y + 3;
  119. if(active) canvas_draw_icon(canvas, x - 4, y + 5, &I_ButtonRightSmall_3x5);
  120. elements_slightly_rounded_frame(canvas, x, y, 116, 15);
  121. for(size_t i = 0; i < length; i++) {
  122. // maybe symmetrical assets? :-/
  123. uint8_t offset_y = buffer[i] < 2 ? 2 + (buffer[i] * 2) : 1;
  124. canvas_draw_icon(canvas, pos_x, pos_y + offset_y, keys_assets[buffer[i]]);
  125. pos_x += buffer[i] > 1 ? 9 : 11;
  126. }
  127. }
  128. /**
  129. * @brief Reset input count
  130. *
  131. * @param model
  132. */
  133. static void code_input_reset_count(CodeInputModel* model) {
  134. model->input_length[model->current] = 0;
  135. }
  136. /**
  137. * @brief Call input callback
  138. *
  139. * @param model
  140. */
  141. static void code_input_call_ok_callback(CodeInputModel* model) {
  142. if(model->ok_callback != NULL) {
  143. model->ok_callback(model->callback_context);
  144. }
  145. }
  146. /**
  147. * @brief Call changed callback
  148. *
  149. * @param model
  150. */
  151. static void code_input_call_fail_callback(CodeInputModel* model) {
  152. if(model->fail_callback != NULL) {
  153. model->fail_callback(model->callback_context);
  154. }
  155. }
  156. /**
  157. * @brief Handle Back button
  158. *
  159. * @param model
  160. */
  161. static bool code_input_handle_back(CodeInputModel* model) {
  162. if(model->current && !model->input_length[model->current]) {
  163. --model->current;
  164. return true;
  165. }
  166. if(model->input_length[model->current]) {
  167. code_input_reset_count(model);
  168. return true;
  169. }
  170. code_input_call_fail_callback(model);
  171. return false;
  172. }
  173. /**
  174. * @brief Handle OK button
  175. *
  176. * @param model
  177. */
  178. static void code_input_handle_ok(CodeInputModel* model) {
  179. switch(model->state) {
  180. case CodeInputStateVerify:
  181. if(code_input_compare_ext(model)) {
  182. if(model->ext_update) {
  183. model->state = CodeInputStateUpdate;
  184. } else {
  185. code_input_call_ok_callback(model);
  186. }
  187. }
  188. code_input_reset_count(model);
  189. break;
  190. case CodeInputStateUpdate:
  191. if(!model->current && model->input_length[model->current]) {
  192. model->current++;
  193. } else {
  194. if(code_input_compare_local(model)) {
  195. if(model->ext_update) {
  196. code_input_set_ext(model);
  197. }
  198. code_input_call_ok_callback(model);
  199. } else {
  200. code_input_reset_count(model);
  201. }
  202. }
  203. break;
  204. default:
  205. break;
  206. }
  207. }
  208. /**
  209. * @brief Handle input
  210. *
  211. * @param model
  212. * @param key
  213. */
  214. size_t code_input_push(uint8_t* buffer, size_t length, InputKey key) {
  215. buffer[length] = key;
  216. length = CLAMP(length + 1, MAX_CODE_LEN, 0);
  217. return length;
  218. }
  219. /**
  220. * @brief Handle D-pad keys
  221. *
  222. * @param model
  223. * @param key
  224. */
  225. static void code_input_handle_dpad(CodeInputModel* model, InputKey key) {
  226. uint8_t at = model->current;
  227. size_t new_length = code_input_push(model->local_buffer[at], model->input_length[at], key);
  228. model->input_length[at] = new_length;
  229. }
  230. /**
  231. * @brief Draw callback
  232. *
  233. * @param canvas
  234. * @param _model
  235. */
  236. static void code_input_view_draw_callback(Canvas* canvas, void* _model) {
  237. CodeInputModel* model = _model;
  238. uint8_t y_offset = 0;
  239. canvas_clear(canvas);
  240. canvas_set_color(canvas, ColorBlack);
  241. if(model->header && strlen(model->header)) {
  242. canvas_draw_str(canvas, 2, 9, model->header);
  243. } else {
  244. y_offset = 4;
  245. }
  246. canvas_set_font(canvas, FontSecondary);
  247. switch(model->state) {
  248. case CodeInputStateVerify:
  249. code_input_draw_sequence(
  250. canvas,
  251. model->local_buffer[CodeInputFirst],
  252. model->input_length[CodeInputFirst],
  253. 6,
  254. 30 + y_offset,
  255. true);
  256. break;
  257. case CodeInputStateUpdate:
  258. code_input_draw_sequence(
  259. canvas,
  260. model->local_buffer[CodeInputFirst],
  261. model->input_length[CodeInputFirst],
  262. 6,
  263. 14 + y_offset,
  264. !model->current);
  265. code_input_draw_sequence(
  266. canvas,
  267. model->local_buffer[CodeInputSecond],
  268. model->input_length[CodeInputSecond],
  269. 6,
  270. 44 + y_offset,
  271. model->current);
  272. if(model->current) canvas_draw_str(canvas, 2, 39 + y_offset, "Repeat code");
  273. break;
  274. default:
  275. break;
  276. }
  277. }
  278. /**
  279. * @brief Input callback
  280. *
  281. * @param event
  282. * @param context
  283. * @return true
  284. * @return false
  285. */
  286. static bool code_input_view_input_callback(InputEvent* event, void* context) {
  287. CodeInput* code_input = context;
  288. furi_assert(code_input);
  289. bool consumed = false;
  290. if(event->type == InputTypeShort || event->type == InputTypeRepeat) {
  291. switch(event->key) {
  292. case InputKeyBack:
  293. with_view_model(
  294. code_input->view, (CodeInputModel * model) {
  295. consumed = code_input_handle_back(model);
  296. return true;
  297. });
  298. break;
  299. case InputKeyOk:
  300. with_view_model(
  301. code_input->view, (CodeInputModel * model) {
  302. code_input_handle_ok(model);
  303. return true;
  304. });
  305. consumed = true;
  306. break;
  307. default:
  308. with_view_model(
  309. code_input->view, (CodeInputModel * model) {
  310. code_input_handle_dpad(model, event->key);
  311. return true;
  312. });
  313. consumed = true;
  314. break;
  315. }
  316. }
  317. return consumed;
  318. }
  319. /**
  320. * @brief Reset all input-related data in model
  321. *
  322. * @param model CodeInputModel
  323. */
  324. static void code_input_reset_model_input_data(CodeInputModel* model) {
  325. model->current = 0;
  326. model->input_length[CodeInputFirst] = 0;
  327. model->input_length[CodeInputSecond] = 0;
  328. model->ext_buffer = NULL;
  329. model->ext_update = false;
  330. model->state = 0;
  331. }
  332. /**
  333. * @brief Allocate and initialize code input. This code input is used to enter codes.
  334. *
  335. * @return CodeInput instance pointer
  336. */
  337. CodeInput* code_input_alloc() {
  338. CodeInput* code_input = furi_alloc(sizeof(CodeInput));
  339. code_input->view = view_alloc();
  340. view_set_context(code_input->view, code_input);
  341. view_allocate_model(code_input->view, ViewModelTypeLocking, sizeof(CodeInputModel));
  342. view_set_draw_callback(code_input->view, code_input_view_draw_callback);
  343. view_set_input_callback(code_input->view, code_input_view_input_callback);
  344. with_view_model(
  345. code_input->view, (CodeInputModel * model) {
  346. model->header = "";
  347. model->ok_callback = NULL;
  348. model->fail_callback = NULL;
  349. model->callback_context = NULL;
  350. code_input_reset_model_input_data(model);
  351. return true;
  352. });
  353. return code_input;
  354. }
  355. /**
  356. * @brief Deinitialize and free code input
  357. *
  358. * @param code_input Code input instance
  359. */
  360. void code_input_free(CodeInput* code_input) {
  361. furi_assert(code_input);
  362. view_free(code_input->view);
  363. free(code_input);
  364. }
  365. /**
  366. * @brief Get code input view
  367. *
  368. * @param code_input code input instance
  369. * @return View instance that can be used for embedding
  370. */
  371. View* code_input_get_view(CodeInput* code_input) {
  372. furi_assert(code_input);
  373. return code_input->view;
  374. }
  375. /**
  376. * @brief Set code input callbacks
  377. *
  378. * @param code_input code input instance
  379. * @param ok_callback input callback fn
  380. * @param fail_callback code match callback fn
  381. * @param callback_context callback context
  382. * @param buffer buffer
  383. * @param buffer_length ptr to buffer length uint
  384. * @param ext_update true to update buffer
  385. */
  386. void code_input_set_result_callback(
  387. CodeInput* code_input,
  388. CodeInputOkCallback ok_callback,
  389. CodeInputFailCallback fail_callback,
  390. void* callback_context,
  391. uint8_t* buffer,
  392. uint8_t* buffer_length,
  393. bool ext_update) {
  394. with_view_model(
  395. code_input->view, (CodeInputModel * model) {
  396. code_input_reset_model_input_data(model);
  397. model->ok_callback = ok_callback;
  398. model->fail_callback = fail_callback;
  399. model->callback_context = callback_context;
  400. model->ext_buffer = buffer;
  401. model->ext_buffer_length = buffer_length;
  402. model->state = (*buffer_length == 0) ? 1 : 0;
  403. model->ext_update = ext_update;
  404. return true;
  405. });
  406. }
  407. /**
  408. * @brief Set code input header text
  409. *
  410. * @param code_input code input instance
  411. * @param text text to be shown
  412. */
  413. void code_input_set_header_text(CodeInput* code_input, const char* text) {
  414. with_view_model(
  415. code_input->view, (CodeInputModel * model) {
  416. model->header = text;
  417. return true;
  418. });
  419. }