submenu.c 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. #include "submenu.h"
  2. #include <m-array.h>
  3. #include <gui/elements.h>
  4. #include <furi.h>
  5. struct Submenu {
  6. View* view;
  7. };
  8. typedef struct {
  9. FuriString* label;
  10. uint32_t index;
  11. SubmenuItemCallback callback;
  12. void* callback_context;
  13. } SubmenuItem;
  14. static void SubmenuItem_init(SubmenuItem* item) {
  15. item->label = furi_string_alloc();
  16. item->index = 0;
  17. item->callback = NULL;
  18. item->callback_context = NULL;
  19. }
  20. static void SubmenuItem_init_set(SubmenuItem* item, const SubmenuItem* src) {
  21. item->label = furi_string_alloc_set(src->label);
  22. item->index = src->index;
  23. item->callback = src->callback;
  24. item->callback_context = src->callback_context;
  25. }
  26. static void SubmenuItem_set(SubmenuItem* item, const SubmenuItem* src) {
  27. furi_string_set(item->label, src->label);
  28. item->index = src->index;
  29. item->callback = src->callback;
  30. item->callback_context = src->callback_context;
  31. }
  32. static void SubmenuItem_clear(SubmenuItem* item) {
  33. furi_string_free(item->label);
  34. }
  35. ARRAY_DEF(
  36. SubmenuItemArray,
  37. SubmenuItem,
  38. (INIT(API_2(SubmenuItem_init)),
  39. SET(API_6(SubmenuItem_set)),
  40. INIT_SET(API_6(SubmenuItem_init_set)),
  41. CLEAR(API_2(SubmenuItem_clear))))
  42. typedef struct {
  43. SubmenuItemArray_t items;
  44. FuriString* header;
  45. size_t position;
  46. size_t window_position;
  47. } SubmenuModel;
  48. static void submenu_process_up(Submenu* submenu);
  49. static void submenu_process_down(Submenu* submenu);
  50. static void submenu_process_ok(Submenu* submenu);
  51. static void submenu_view_draw_callback(Canvas* canvas, void* _model) {
  52. SubmenuModel* model = _model;
  53. const uint8_t item_height = 16;
  54. const uint8_t item_width = 123;
  55. canvas_clear(canvas);
  56. if(!furi_string_empty(model->header)) {
  57. canvas_set_font(canvas, FontPrimary);
  58. canvas_draw_str(canvas, 4, 11, furi_string_get_cstr(model->header));
  59. }
  60. canvas_set_font(canvas, FontSecondary);
  61. size_t position = 0;
  62. SubmenuItemArray_it_t it;
  63. for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
  64. SubmenuItemArray_next(it)) {
  65. const size_t item_position = position - model->window_position;
  66. const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
  67. uint8_t y_offset = furi_string_empty(model->header) ? 0 : 16;
  68. if(item_position < items_on_screen) {
  69. if(position == model->position) {
  70. canvas_set_color(canvas, ColorBlack);
  71. elements_slightly_rounded_box(
  72. canvas,
  73. 0,
  74. y_offset + (item_position * item_height) + 1,
  75. item_width,
  76. item_height - 2);
  77. canvas_set_color(canvas, ColorWhite);
  78. } else {
  79. canvas_set_color(canvas, ColorBlack);
  80. }
  81. FuriString* disp_str;
  82. disp_str = furi_string_alloc_set(SubmenuItemArray_cref(it)->label);
  83. elements_string_fit_width(canvas, disp_str, item_width - 20);
  84. canvas_draw_str(
  85. canvas,
  86. 6,
  87. y_offset + (item_position * item_height) + item_height - 4,
  88. furi_string_get_cstr(disp_str));
  89. furi_string_free(disp_str);
  90. }
  91. position++;
  92. }
  93. elements_scrollbar(canvas, model->position, SubmenuItemArray_size(model->items));
  94. }
  95. static bool submenu_view_input_callback(InputEvent* event, void* context) {
  96. Submenu* submenu = context;
  97. furi_assert(submenu);
  98. bool consumed = false;
  99. if(event->type == InputTypeShort) {
  100. switch(event->key) {
  101. case InputKeyUp:
  102. consumed = true;
  103. submenu_process_up(submenu);
  104. break;
  105. case InputKeyDown:
  106. consumed = true;
  107. submenu_process_down(submenu);
  108. break;
  109. case InputKeyOk:
  110. consumed = true;
  111. submenu_process_ok(submenu);
  112. break;
  113. default:
  114. break;
  115. }
  116. } else if(event->type == InputTypeRepeat) {
  117. if(event->key == InputKeyUp) {
  118. consumed = true;
  119. submenu_process_up(submenu);
  120. } else if(event->key == InputKeyDown) {
  121. consumed = true;
  122. submenu_process_down(submenu);
  123. }
  124. }
  125. return consumed;
  126. }
  127. Submenu* submenu_alloc() {
  128. Submenu* submenu = malloc(sizeof(Submenu));
  129. submenu->view = view_alloc();
  130. view_set_context(submenu->view, submenu);
  131. view_allocate_model(submenu->view, ViewModelTypeLocking, sizeof(SubmenuModel));
  132. view_set_draw_callback(submenu->view, submenu_view_draw_callback);
  133. view_set_input_callback(submenu->view, submenu_view_input_callback);
  134. with_view_model(
  135. submenu->view,
  136. SubmenuModel * model,
  137. {
  138. SubmenuItemArray_init(model->items);
  139. model->position = 0;
  140. model->window_position = 0;
  141. model->header = furi_string_alloc();
  142. },
  143. true);
  144. return submenu;
  145. }
  146. void submenu_free(Submenu* submenu) {
  147. furi_assert(submenu);
  148. with_view_model(
  149. submenu->view,
  150. SubmenuModel * model,
  151. {
  152. furi_string_free(model->header);
  153. SubmenuItemArray_clear(model->items);
  154. },
  155. true);
  156. view_free(submenu->view);
  157. free(submenu);
  158. }
  159. View* submenu_get_view(Submenu* submenu) {
  160. furi_assert(submenu);
  161. return submenu->view;
  162. }
  163. void submenu_add_item(
  164. Submenu* submenu,
  165. const char* label,
  166. uint32_t index,
  167. SubmenuItemCallback callback,
  168. void* callback_context) {
  169. SubmenuItem* item = NULL;
  170. furi_assert(label);
  171. furi_assert(submenu);
  172. with_view_model(
  173. submenu->view,
  174. SubmenuModel * model,
  175. {
  176. item = SubmenuItemArray_push_new(model->items);
  177. furi_string_set_str(item->label, label);
  178. item->index = index;
  179. item->callback = callback;
  180. item->callback_context = callback_context;
  181. },
  182. true);
  183. }
  184. void submenu_reset(Submenu* submenu) {
  185. furi_assert(submenu);
  186. with_view_model(
  187. submenu->view,
  188. SubmenuModel * model,
  189. {
  190. SubmenuItemArray_reset(model->items);
  191. model->position = 0;
  192. model->window_position = 0;
  193. furi_string_reset(model->header);
  194. },
  195. true);
  196. }
  197. void submenu_set_selected_item(Submenu* submenu, uint32_t index) {
  198. with_view_model(
  199. submenu->view,
  200. SubmenuModel * model,
  201. {
  202. size_t position = 0;
  203. SubmenuItemArray_it_t it;
  204. for(SubmenuItemArray_it(it, model->items); !SubmenuItemArray_end_p(it);
  205. SubmenuItemArray_next(it)) {
  206. if(index == SubmenuItemArray_cref(it)->index) {
  207. break;
  208. }
  209. position++;
  210. }
  211. const size_t items_size = SubmenuItemArray_size(model->items);
  212. if(position >= items_size) {
  213. position = 0;
  214. }
  215. model->position = position;
  216. model->window_position = position;
  217. if(model->window_position > 0) {
  218. model->window_position -= 1;
  219. }
  220. const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
  221. if(items_size <= items_on_screen) {
  222. model->window_position = 0;
  223. } else {
  224. const size_t pos = items_size - items_on_screen;
  225. if(model->window_position > pos) {
  226. model->window_position = pos;
  227. }
  228. }
  229. },
  230. true);
  231. }
  232. void submenu_process_up(Submenu* submenu) {
  233. with_view_model(
  234. submenu->view,
  235. SubmenuModel * model,
  236. {
  237. const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
  238. const size_t items_size = SubmenuItemArray_size(model->items);
  239. if(model->position > 0) {
  240. model->position--;
  241. if((model->position == model->window_position) && (model->window_position > 0)) {
  242. model->window_position--;
  243. }
  244. } else {
  245. model->position = items_size - 1;
  246. if(model->position > items_on_screen - 1) {
  247. model->window_position = model->position - (items_on_screen - 1);
  248. }
  249. }
  250. },
  251. true);
  252. }
  253. void submenu_process_down(Submenu* submenu) {
  254. with_view_model(
  255. submenu->view,
  256. SubmenuModel * model,
  257. {
  258. const size_t items_on_screen = furi_string_empty(model->header) ? 4 : 3;
  259. const size_t items_size = SubmenuItemArray_size(model->items);
  260. if(model->position < items_size - 1) {
  261. model->position++;
  262. if((model->position - model->window_position > items_on_screen - 2) &&
  263. (model->window_position < items_size - items_on_screen)) {
  264. model->window_position++;
  265. }
  266. } else {
  267. model->position = 0;
  268. model->window_position = 0;
  269. }
  270. },
  271. true);
  272. }
  273. void submenu_process_ok(Submenu* submenu) {
  274. SubmenuItem* item = NULL;
  275. with_view_model(
  276. submenu->view,
  277. SubmenuModel * model,
  278. {
  279. const size_t items_size = SubmenuItemArray_size(model->items);
  280. if(model->position < items_size) {
  281. item = SubmenuItemArray_get(model->items, model->position);
  282. }
  283. },
  284. true);
  285. if(item && item->callback) {
  286. item->callback(item->callback_context, item->index);
  287. }
  288. }
  289. void submenu_set_header(Submenu* submenu, const char* header) {
  290. furi_assert(submenu);
  291. with_view_model(
  292. submenu->view,
  293. SubmenuModel * model,
  294. {
  295. if(header == NULL) {
  296. furi_string_reset(model->header);
  297. } else {
  298. furi_string_set_str(model->header, header);
  299. }
  300. },
  301. true);
  302. }