// // Created by MightyPork on 2023/04/09. // #include #include #include "screen_menu.h" #include "app_gui.h" #include "FreeRTOS.h" #include "task.h" #include "ufb/utf8.h" #include "ufb/framebuffer_config.h" #include "ufb/framebuffer.h" #include "ufb/fb_text.h" struct menu_state { int pos; int len; uint32_t change_time; uint32_t slide_end_time; uint16_t text_slide; void * last_opts; } s_menu; void screen_menu(GuiEvent event, const char **options, menu_callback_t cb) { screen_menu_offset(event, options, cb, 15); } void screen_menu_offset(GuiEvent event, const char **options, menu_callback_t cb, fbpos_t offset) { if (event != GUI_EVENT_SCREEN_INIT && s_menu.last_opts != (void*) options) { // ensure the menu is properly inited when the options list changes. screen_menu(GUI_EVENT_SCREEN_INIT, options, cb); } bool menu_changed = false; const uint32_t tickNow = xTaskGetTickCount(); switch (event) { case GUI_EVENT_SCREEN_INIT: memset(&s_menu, 0, sizeof(s_menu)); s_menu.last_opts = (void*) options; s_menu.change_time = tickNow; // count options const char **opt = options; while (*opt) { s_menu.len++; opt++; } break; case GUI_EVENT_SCREEN_TICK: // long text sliding animation if (tickNow - s_menu.change_time >= pdMS_TO_TICKS(500)) { const uint32_t textlen = utf8_strlen(options[s_menu.pos]) * 6; if (textlen >= FBW - 2) { if (textlen - s_menu.text_slide > FBW - 1) { s_menu.text_slide += 1; if (textlen - s_menu.text_slide >= FBW - 1) { s_menu.slide_end_time = tickNow; } } else if (tickNow - s_menu.slide_end_time >= pdMS_TO_TICKS(500)) { s_menu.change_time = tickNow; s_menu.slide_end_time = 0; s_menu.text_slide = 0; } request_paint(); } } break; case GUI_EVENT_PAINT: for (int i = 0; i < s_menu.len; i++) { // is the row currently rendered the selected row? const bool is_selected = s_menu.pos == i; const fbcolor_t color = !is_selected; // text color - black if selected, because it's inverted const fbpos_t y = 12 + offset + i * 10 ; fb_rect(0, y, FBW, 10, !color); fb_text(1 - (is_selected ? s_menu.text_slide : 0), y + 1, options[i], FONT_5X7, color); // ensure the text doesn't touch the edge (looks ugly) fb_vline(FBW - 1, y, 10, !color); fb_vline(0, y, 10, !color); } break; // the button is held! release is what activates the button case GUI_EVENT_KNOB_RELEASE: input_sound_effect(); cb(s_menu.pos); break; case GUI_EVENT_KNOB_PLUS: if (s_menu.pos < s_menu.len - 1) { s_menu.pos++; menu_changed = true; } break; case GUI_EVENT_KNOB_MINUS: if (s_menu.pos > 0) { s_menu.pos--; menu_changed = true; } break; } if (menu_changed) { s_menu.change_time = tickNow; s_menu.text_slide = 0; s_menu.slide_end_time = 0; input_sound_effect(); request_paint(); } }