From 01e87fc27bc3df5482f09124fea8e8af3ceb06ae Mon Sep 17 00:00:00 2001 From: DiSlord Live Date: Wed, 30 Nov 2022 22:24:40 +0300 Subject: [PATCH] Add browser option --- nanovna.h | 11 +- ui.c | 139 +++++++++++--------- vna_browser.c | 346 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 436 insertions(+), 60 deletions(-) create mode 100644 vna_browser.c diff --git a/nanovna.h b/nanovna.h index 4633757..8b77579 100644 --- a/nanovna.h +++ b/nanovna.h @@ -92,6 +92,7 @@ #define __USE_SD_CARD__ // Enable SD card support #define __SD_CARD_LOAD__ // Allow run commands from SD card (config.ini in root) #define __SD_CARD_DUMP_FIRMWARE__ // Allow dump firmware to SD card +#define __SD_FILE_BROWSER__ #define __LCD_BRIGHTNESS__ // LCD or hardware allow change brightness, add menu item for this #define __HARMONIC__ #define __NOISE_FIGURE__ @@ -584,6 +585,14 @@ extern uint16_t graph_bottom; #define MENU_BUTTON_HEIGHT_N(n) (LCD_HEIGHT/(n)-1) +#define BROWSER_BUTTON_BORDER 1 +// Browser window settings +#define FILES_COLUMNS (LCD_WIDTH/160) // columns in browser +#define FILES_ROWS 10 // rows in browser +#define FILES_PER_PAGE (FILES_COLUMNS*FILES_ROWS) // FILES_ROWS * FILES_COLUMNS +#define FILE_BOTTOM_HEIGHT 20 // Height of bottom buttons (< > X) +#define FILE_BUTTON_HEIGHT ((LCD_HEIGHT - FILE_BOTTOM_HEIGHT)/FILES_ROWS) // Height of file buttons + // Define message box width #ifdef TINYSA4 #define MESSAGE_BOX_WIDTH 300 @@ -1448,7 +1457,7 @@ int invoke_quick_menu(int); bool ui_process_listen_lever(void); void refresh_sweep_menu(int i); void save_to_sd(int mask); -void drawMessageBox(char *header, char *text, uint32_t delay); +void drawMessageBox(const char *header, char *text, uint32_t delay); // Irq operation process set #define OP_NONE 0x00 diff --git a/ui.c b/ui.c index 8fbf2f0..aeb63ed 100644 --- a/ui.c +++ b/ui.c @@ -74,7 +74,11 @@ volatile uint8_t operation_requested = OP_NONE; int8_t previous_marker = MARKER_INVALID; enum { - UI_NORMAL, UI_MENU, UI_KEYPAD + UI_NORMAL, UI_MENU, UI_KEYPAD, +#ifdef __SD_FILE_BROWSER__ + UI_BROWSER, +#endif + UI_END }; #define NUMINPUT_LEN 12 @@ -111,7 +115,8 @@ static const uint8_t slider_bitmap[]= #define AUTO_ICON(S) (S>=2?BUTTON_ICON_CHECK_AUTO:S) // Depends on order of ICONs!!!!! #define BUTTON_BORDER_NONE 0x00 -#define BUTTON_BORDER_WIDTH_MASK 0x0F +#define BUTTON_BORDER_WIDTH_MASK 0x07 +#define BUTTON_BORDER_NO_FILL 0x08 // Define mask for draw border (if 1 use light color, if 0 dark) #define BUTTON_BORDER_TYPE_MASK 0xF0 @@ -147,6 +152,7 @@ static void erase_menu_buttons(void); static void ui_process_keypad(void); static void choose_active_marker(void); static void menu_move_back(bool leave_ui); +static void draw_button(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ui_button_t *b); //static const menuitem_t menu_marker_type[]; static int btn_check(void) @@ -3503,6 +3509,60 @@ static UI_FUNCTION_ADV_CALLBACK(menu_connection_acb) #endif #ifdef __USE_SD_CARD__ +//******************************************************************************************* +// Bitmap file header for LCD_WIDTH x LCD_HEIGHT image 16bpp (v4 format allow set RGB mask) +//******************************************************************************************* +#define BMP_UINT32(val) ((val)>>0)&0xFF, ((val)>>8)&0xFF, ((val)>>16)&0xFF, ((val)>>24)&0xFF +#define BMP_UINT16(val) ((val)>>0)&0xFF, ((val)>>8)&0xFF +#define BMP_H1_SIZE (14) // BMP header 14 bytes +#define BMP_V4_SIZE (108) // v4 header 108 bytes +#define BMP_HEAD_SIZE (BMP_H1_SIZE + BMP_V4_SIZE) // Size of all headers +#define BMP_SIZE (2*LCD_WIDTH*LCD_HEIGHT) // Bitmap size = 2*w*h +#define BMP_FILE_SIZE (BMP_SIZE + BMP_HEAD_SIZE) // File size = headers + bitmap +static const uint8_t bmp_header_v4[BMP_H1_SIZE + BMP_V4_SIZE] = { +// BITMAPFILEHEADER (14 byte size) + 0x42, 0x4D, // BM signature + BMP_UINT32(BMP_FILE_SIZE), // File size (h + v4 + bitmap) + BMP_UINT16(0), // reserved + BMP_UINT16(0), // reserved + BMP_UINT32(BMP_HEAD_SIZE), // Size of all headers (h + v4) +// BITMAPINFOv4 (108 byte size) + BMP_UINT32(BMP_V4_SIZE), // Data offset after this point (v4 size) + BMP_UINT32(LCD_WIDTH), // Width + BMP_UINT32(LCD_HEIGHT), // Height + BMP_UINT16(1), // Planes + BMP_UINT16(16), // 16bpp + BMP_UINT32(3), // Compression (BI_BITFIELDS) + BMP_UINT32(BMP_SIZE), // Bitmap size (w*h*2) + BMP_UINT32(0x0EC4), // x Resolution (96 DPI = 96 * 39.3701 inches per meter = 0x0EC4) + BMP_UINT32(0x0EC4), // y Resolution (96 DPI = 96 * 39.3701 inches per meter = 0x0EC4) + BMP_UINT32(0), // Palette size + BMP_UINT32(0), // Palette used +// Extend v4 header data (color mask for RGB565) + BMP_UINT32(0b1111100000000000),// R mask = 0b11111000 00000000 + BMP_UINT32(0b0000011111100000),// G mask = 0b00000111 11100000 + BMP_UINT32(0b0000000000011111),// B mask = 0b00000000 00011111 + BMP_UINT32(0b0000000000000000),// A mask = 0b00000000 00000000 + 'B','G','R','s', // CSType = 'sRGB' + BMP_UINT32(0), // ciexyzRed.ciexyzX Endpoints + BMP_UINT32(0), // ciexyzRed.ciexyzY + BMP_UINT32(0), // ciexyzRed.ciexyzZ + BMP_UINT32(0), // ciexyzGreen.ciexyzX + BMP_UINT32(0), // ciexyzGreen.ciexyzY + BMP_UINT32(0), // ciexyzGreen.ciexyzZ + BMP_UINT32(0), // ciexyzBlue.ciexyzX + BMP_UINT32(0), // ciexyzBlue.ciexyzY + BMP_UINT32(0), // ciexyzBlue.ciexyzZ + BMP_UINT32(0), // GammaRed + BMP_UINT32(0), // GammaGreen + BMP_UINT32(0), // GammaBlue +}; + +static void swap_bytes(uint16_t *buf, int size) { + for (int i = 0; i < size; i++) + buf[i] = __REVSH(buf[i]); // swap byte order (example 0x10FF to 0xFF10) +} + static uint16_t file_mask; // Save format enum @@ -3534,6 +3594,10 @@ static UI_FUNCTION_CALLBACK(menu_sdcard_cb) { (void)item; sa_save_file(0, data); } + +#ifdef __SD_FILE_BROWSER__ +#include "vna_browser.c" +#endif #endif // ===[MENU DEFINITION]========================================================= // Back button submenu list @@ -4025,6 +4089,9 @@ static const menuitem_t menu_settings[] = #ifdef __SD_CARD_DUMP_FIRMWARE__ { MT_CALLBACK, FMT_BIN_FILE, "DUMP\nFIRMWARE", menu_sdcard_cb}, #endif +#ifdef __SD_FILE_BROWSER__ + { MT_CALLBACK, FMT_BMP_FILE, "LOAD BMP", menu_sdcard_browse_cb }, +#endif #ifdef TINYSA4 { MT_ADV_CALLBACK, 0, "INTERNALS", menu_internals_acb}, #endif @@ -5469,7 +5536,7 @@ draw_button(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ui_button_t *b) { uint16_t bw = b->border&BUTTON_BORDER_WIDTH_MASK; ili9341_set_foreground(b->fg); - ili9341_set_background(b->bg);ili9341_fill(x + bw, y + bw, w - (bw * 2), h - (bw * 2)); + ili9341_set_background(b->bg); if (bw==0) return; uint16_t br = LCD_RISE_EDGE_COLOR; uint16_t bd = LCD_FALLEN_EDGE_COLOR; @@ -5480,9 +5547,11 @@ draw_button(uint16_t x, uint16_t y, uint16_t w, uint16_t h, ui_button_t *b) ili9341_set_background(type&BUTTON_BORDER_BOTTOM ? br : bd);ili9341_fill(x, y + h - bw, w, bw); // bottom // Set colors for button text after ili9341_set_background(b->bg); + if (type & BUTTON_BORDER_NO_FILL) return; + ili9341_fill(x + bw, y + bw, w - (bw * 2), h - (bw * 2)); } -void drawMessageBox(char *header, char *text, uint32_t delay){ +void drawMessageBox(const char *header, char *text, uint32_t delay){ ui_button_t b; b.bg = LCD_MENU_COLOR; b.fg = LCD_MENU_TEXT_COLOR; @@ -6478,6 +6547,9 @@ ui_process_lever(void) // case UI_KEYPAD: // ui_process_keypad(); // break; + case UI_BROWSER: + ui_process_browser_lever(); + break; } } @@ -6555,60 +6627,6 @@ static int touch_quick_menu(int touch_x, int touch_y) } #ifdef __USE_SD_CARD__ -//******************************************************************************************* -// Bitmap file header for LCD_WIDTH x LCD_HEIGHT image 16bpp (v4 format allow set RGB mask) -//******************************************************************************************* -#define BMP_UINT32(val) ((val)>>0)&0xFF, ((val)>>8)&0xFF, ((val)>>16)&0xFF, ((val)>>24)&0xFF -#define BMP_UINT16(val) ((val)>>0)&0xFF, ((val)>>8)&0xFF -#define BMP_H1_SIZE (14) // BMP header 14 bytes -#define BMP_V4_SIZE (108) // v4 header 108 bytes -#define BMP_HEAD_SIZE (BMP_H1_SIZE + BMP_V4_SIZE) // Size of all headers -#define BMP_SIZE (2*LCD_WIDTH*LCD_HEIGHT) // Bitmap size = 2*w*h -#define BMP_FILE_SIZE (BMP_SIZE + BMP_HEAD_SIZE) // File size = headers + bitmap -static const uint8_t bmp_header_v4[BMP_H1_SIZE + BMP_V4_SIZE] = { -// BITMAPFILEHEADER (14 byte size) - 0x42, 0x4D, // BM signature - BMP_UINT32(BMP_FILE_SIZE), // File size (h + v4 + bitmap) - BMP_UINT16(0), // reserved - BMP_UINT16(0), // reserved - BMP_UINT32(BMP_HEAD_SIZE), // Size of all headers (h + v4) -// BITMAPINFOv4 (108 byte size) - BMP_UINT32(BMP_V4_SIZE), // Data offset after this point (v4 size) - BMP_UINT32(LCD_WIDTH), // Width - BMP_UINT32(LCD_HEIGHT), // Height - BMP_UINT16(1), // Planes - BMP_UINT16(16), // 16bpp - BMP_UINT32(3), // Compression (BI_BITFIELDS) - BMP_UINT32(BMP_SIZE), // Bitmap size (w*h*2) - BMP_UINT32(0x0EC4), // x Resolution (96 DPI = 96 * 39.3701 inches per meter = 0x0EC4) - BMP_UINT32(0x0EC4), // y Resolution (96 DPI = 96 * 39.3701 inches per meter = 0x0EC4) - BMP_UINT32(0), // Palette size - BMP_UINT32(0), // Palette used -// Extend v4 header data (color mask for RGB565) - BMP_UINT32(0b1111100000000000),// R mask = 0b11111000 00000000 - BMP_UINT32(0b0000011111100000),// G mask = 0b00000111 11100000 - BMP_UINT32(0b0000000000011111),// B mask = 0b00000000 00011111 - BMP_UINT32(0b0000000000000000),// A mask = 0b00000000 00000000 - 'B','G','R','s', // CSType = 'sRGB' - BMP_UINT32(0), // ciexyzRed.ciexyzX Endpoints - BMP_UINT32(0), // ciexyzRed.ciexyzY - BMP_UINT32(0), // ciexyzRed.ciexyzZ - BMP_UINT32(0), // ciexyzGreen.ciexyzX - BMP_UINT32(0), // ciexyzGreen.ciexyzY - BMP_UINT32(0), // ciexyzGreen.ciexyzZ - BMP_UINT32(0), // ciexyzBlue.ciexyzX - BMP_UINT32(0), // ciexyzBlue.ciexyzY - BMP_UINT32(0), // ciexyzBlue.ciexyzZ - BMP_UINT32(0), // GammaRed - BMP_UINT32(0), // GammaGreen - BMP_UINT32(0), // GammaBlue -}; - -static void swap_bytes(uint16_t *buf, int size) { - for (int i = 0; i < size; i++) - buf[i] = __REVSH(buf[i]); // swap byte order (example 0x10FF to 0xFF10) -} - // Create file name from current time static FRESULT sa_create_file(char *fs_filename) { @@ -6848,6 +6866,9 @@ void ui_process_touch(void) case UI_MENU: menu_apply_touch(touch_x, touch_y); break; + case UI_BROWSER: + browser_apply_touch(touch_x, touch_y); + break; } } } @@ -6868,7 +6889,7 @@ ui_process(void) operation_requested = OP_NONE; } if (operation_requested&OP_TOUCH) { - ui_process_touch(); + ui_process_touch(); operation_requested = OP_NONE; } touch_start_watchdog(); diff --git a/vna_browser.c b/vna_browser.c new file mode 100644 index 0000000..cfe544a --- /dev/null +++ b/vna_browser.c @@ -0,0 +1,346 @@ +static uint16_t file_count; +static uint16_t page_count; +static uint16_t current_page; +static uint16_t sel_mode; + +// Buttons in browser +enum {FILE_BUTTON_LEFT = 0, FILE_BUTTON_RIGHT, FILE_BUTTON_EXIT, FILE_BUTTON_DEL, FILE_BUTTON_FILE}; +// Button position on screen +typedef struct { + uint16_t x; + uint16_t y; + uint16_t w; + uint8_t h; + uint8_t ofs; +} browser_btn_t; +static const browser_btn_t browser_btn[] = { + [FILE_BUTTON_LEFT] = { 0 + 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, LCD_WIDTH/2 - 80, FILE_BOTTOM_HEIGHT, (LCD_WIDTH/2 - 80 - FONT_WIDTH)/2}, // < previous + [FILE_BUTTON_RIGHT]= {LCD_WIDTH/2 + 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, LCD_WIDTH/2 - 80, FILE_BOTTOM_HEIGHT, (LCD_WIDTH/2 - 80 - FONT_WIDTH)/2}, // > next + [FILE_BUTTON_EXIT] = {LCD_WIDTH - 40, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, 40, FILE_BOTTOM_HEIGHT, ( 40 - FONT_WIDTH)/2}, // X exit + [FILE_BUTTON_DEL] = { 0 + 0, LCD_HEIGHT - FILE_BOTTOM_HEIGHT, 40, FILE_BOTTOM_HEIGHT, ( 40 - 3*FONT_WIDTH)/2}, // DEL + // File button, only size and start position, must be idx = FILE_BUTTON_FILE + [FILE_BUTTON_FILE] = { 0, 0, LCD_WIDTH/FILES_COLUMNS, FILE_BUTTON_HEIGHT, 5}, +}; + +static void browser_get_button_pos(int idx, browser_btn_t *b) { + int n = idx >= FILE_BUTTON_FILE ? FILE_BUTTON_FILE : idx; +#if 0 + memcpy(b, &browser_btn[n], sizeof(browser_btn_t)); +#else + b->x = browser_btn[n].x; + b->y = browser_btn[n].y; + b->w = browser_btn[n].w; + b->h = browser_btn[n].h; + b->ofs = browser_btn[n].ofs; +#endif + if (idx > FILE_BUTTON_FILE) { // for file buttons use multiplier from start offset + idx-= FILE_BUTTON_FILE; + b->x+= b->w * (idx / FILES_ROWS); + b->y+= b->h * (idx % FILES_ROWS); + } +} + +static void browser_draw_button(int idx, const char *txt) { + if (idx < 0) return; + ui_button_t b; + browser_btn_t btn; + browser_get_button_pos(idx, &btn); + // Mark DEL button in file delete mode + b.bg = (idx == FILE_BUTTON_DEL && sel_mode) ? LCD_LOW_BAT_COLOR : LCD_MENU_COLOR; + b.fg = LCD_MENU_TEXT_COLOR; + b.border = (idx == selection) ? BROWSER_BUTTON_BORDER|BUTTON_BORDER_FALLING : BROWSER_BUTTON_BORDER|BUTTON_BORDER_RISE; + if (txt == NULL) b.border|= BUTTON_BORDER_NO_FILL; + draw_button(btn.x, btn.y, btn.w, btn.h, &b); + if (txt) lcd_printf(btn.x + btn.ofs, btn.y + (btn.h - FONT_STR_HEIGHT) / 2, txt); +} + +static char to_lower(char c) {return (c >='A' && c <= 'Z') ? c - 'A' + 'a' : c;} + +static bool strcmpi(const char *t1, const char *t2) { + int i = 0; + while (1) { + char ch1 = to_lower(t1[i]), ch2 = to_lower(t2[i]); + if (ch1 != ch2) return false; + if (ch1 == 0) return true; + i++; + } +} + +static bool compare_ext(const char *name, const char *ext) { + int i = 0, j = 0; + while (name[i]) if (name[i++] == '.') j = i; // Get last '.' position + 1 + return j == 0 ? false : strcmpi(&name[j], ext); // Compare text after '.' and ext +} + +static FRESULT sd_findnext(DIR* dp, FILINFO* fno) { + while (f_readdir(dp, fno) == FR_OK && fno->fname[0]) { + if (fno->fattrib & AM_DIR) continue; + if (compare_ext(fno->fname, dp->pat)) return FR_OK; +//#if FF_USE_LFN && FF_USE_FIND == 2 +// if (compare_ext(fno->altname, dp->pat)) return FR_OK; +//#endif + } + return FR_NO_FILE; +} + +static FRESULT sd_open_dir(DIR* dp, const TCHAR* path, const TCHAR* pattern) { + dp->pat = pattern; + return f_opendir(dp, path); +} + +static void browser_open_file(int sel) { + FILINFO fno; + FRESULT res; + DIR dj; + int cnt; + if ((uint16_t)sel >= file_count) return; + if (f_mount(fs_volume, "", 1) != FR_OK) return; +repeat: + cnt = sel; + if (sd_open_dir(&dj, "", file_ext[keypad_mode]) != FR_OK) return; // open dir + while (sd_findnext(&dj, &fno) == FR_OK && cnt != 0) cnt--; // skip cnt files + f_closedir(&dj); + if (cnt != 0) return; + + // Delete file if in delete mode + if (sel_mode) {f_unlink(fno.fname); return;} + + const char *error = NULL; + bool leave_show = true; + UINT size; + if (f_open(fs_file, fno.fname, FA_READ) != FR_OK) return; + + ili9341_set_foreground(LCD_FG_COLOR); + ili9341_set_background(LCD_BG_COLOR); + switch (keypad_mode) { +//#ifdef __SD_CARD_LOAD__ +// case FMT_CMD_FILE: +// { +// const int buffer_size = 256; +// const int line_size = 128; +// char *buf_8 = (char *)spi_buffer; // must be greater then buffer_size + line_size +// char *line = buf_8 + buffer_size; +// uint16_t j = 0, i; +// while (f_read(fs_file, buf_8, buffer_size, &size) == FR_OK && size > 0) { +// for (i = 0; i < size; i++) { +// uint8_t c = buf_8[i]; +// if (c == '\r') { // New line (Enter) +// line[j] = 0; j = 0; +// VNAShell_executeCMDLine(line); +// } +// else if (c < 0x20) continue; // Others (skip) +// else if (j < line_size) line[j++] = (char)c; // Store +// } +// } +// break; +// } +//#endif + /* + * BMP file load procedure, load only device screenshots + */ + case FMT_BMP_FILE: + { + int y; + leave_show = false; // allow step up/down load bitmap + uint16_t *buf_16 = spi_buffer; // prepare buffer + res = f_read(fs_file, (void *)buf_16, sizeof(bmp_header_v4), &size); // read heaser + if (res != FR_OK || buf_16[9] != LCD_WIDTH || buf_16[11] != LCD_HEIGHT || buf_16[14] != 16) {error = "Format err"; break;} + for (y = LCD_HEIGHT-1; y >=0 && res == FR_OK; y--) { + res = f_read(fs_file, (void *)buf_16, LCD_WIDTH * sizeof(uint16_t), &size); + swap_bytes(buf_16, LCD_WIDTH); + ili9341_bulk(0, y, LCD_WIDTH, 1); + } + lcd_printf(0, LCD_HEIGHT - 3*FONT_STR_HEIGHT, fno.fname); + } + break; + /* + * Load calibration + */ +// case FMT_CAL_FILE: +// { +// uint32_t magic; +// char *src = (char*)¤t_props + sizeof(magic); +// uint32_t total = sizeof(current_props) - sizeof(magic); +// // Compare file size and try read magic header, if all OK load it +// if (fno.fsize == sizeof(current_props) && f_read(fs_file, &magic, sizeof(magic), &size) == FR_OK && +// magic == PROPS_MAGIC && f_read(fs_file, src, total, &size) == FR_OK) +// load_properties(NO_SAVE_SLOT); +// else error = "Format err"; +// } +// break; + default: break; + } + f_close(fs_file); + if (error) { + ili9341_clear_screen(); + drawMessageBox(error, fno.fname, leave_show ? 2000 : 0); + } + if (leave_show) return; + // Process input + while (1) { + uint16_t status = btn_check(); + int key = -1; + if (status & EVT_DOWN) key = 0; + if (status & EVT_UP ) key = 1; + if (status & EVT_BUTTON_SINGLE_CLICK) key = 2; + + status = touch_check(); + if (status == EVT_TOUCH_PRESSED || status == EVT_TOUCH_DOWN) { + int touch_x, touch_y; + touch_position(&touch_x, &touch_y); + if (touch_x < LCD_WIDTH *1/3) key = 0; + else if (touch_x < LCD_WIDTH *2/3) key = 2; + else key = 1; + touch_wait_release(); + } + chThdSleepMilliseconds(100); + int old_sel = sel; + if (key == 0) {if (--sel < 0) sel = file_count - 1;} + else if (key == 1) {if (++sel > file_count - 1) sel = 0;} + else if (key == 2) break; + if (old_sel != sel) goto repeat; + } +} + +static void browser_draw_buttons(void) { + browser_draw_button(FILE_BUTTON_DEL, "DEL"); + browser_draw_button(FILE_BUTTON_LEFT, "<"); + browser_draw_button(FILE_BUTTON_RIGHT, ">"); + browser_draw_button(FILE_BUTTON_EXIT, "X"); +} + +static void browser_draw_page(int page) { + FILINFO fno; + DIR dj; + // Mount SD card and open directory + if (f_mount(fs_volume, "", 1) != FR_OK || + sd_open_dir(&dj, "", file_ext[keypad_mode]) != FR_OK) { + drawMessageBox("ERROR", "NO CARD", 2000); + ui_mode_normal(); + return; + } + // Draw Browser UI + int cnt = 0; + uint16_t start_file = (page - 1) * FILES_PER_PAGE; + ili9341_set_background(LCD_MENU_COLOR); + ili9341_clear_screen(); + while (sd_findnext(&dj, &fno) == FR_OK) { + if (cnt >= start_file && cnt < (start_file + FILES_PER_PAGE)) { + //uint16_t sec = ((fno.ftime<<1) & 0x3F); + //uint16_t min = ((fno.ftime>>5) & 0x3F); + //uint16_t h = ((fno.ftime>>11) & 0x1F); + //uint16_t d = ((fno.fdate>>0) & 0x1F); + //uint16_t m = ((fno.fdate>>5) & 0x0F); + //uint16_t year= ((fno.fdate>>9) & 0x3F) + 1980; + //lcd_printf(x, y, "%2d %s %u - %u/%02u/%02u %02u:%02u:%02u", cnt, fno.fname, fno.fsize, year, m, d, h, min, sec); + browser_draw_button(cnt - start_file + FILE_BUTTON_FILE, fno.fname); + } + cnt++; + if (file_count && (start_file + FILES_PER_PAGE == cnt)) break; + } + f_closedir(&dj); + // Calculate page and file count on first run + if (file_count == 0) { + file_count = cnt; + page_count = cnt == 0 ? 1 : (file_count + FILES_PER_PAGE - 1) / FILES_PER_PAGE; + } + browser_draw_buttons(); + lcd_printf(LCD_WIDTH / 2 - 3 * FONT_WIDTH, LCD_HEIGHT - (FILE_BOTTOM_HEIGHT + FONT_STR_HEIGHT) / 2, "- %u | %u -", page, page_count); + return; +} + +static void browser_key_press(int key) { + int page; + switch (key) { + case FILE_BUTTON_LEFT: + case FILE_BUTTON_RIGHT: // Switch page on left / right change + page = current_page; + if (key == FILE_BUTTON_LEFT && --current_page < 1) current_page = page_count; + if (key == FILE_BUTTON_RIGHT && ++current_page > page_count) current_page = 1; + if (page != current_page) + browser_draw_page(current_page); + break; + case FILE_BUTTON_EXIT: //Exit + ui_mode_normal(); + break; + case FILE_BUTTON_DEL: // Toggle delete mode + sel_mode^= 1; + browser_draw_buttons(); + break; + case FILE_BUTTON_FILE: // Open or delete file + default: + browser_open_file(key - FILE_BUTTON_FILE + (current_page - 1) * FILES_PER_PAGE); + if (sel_mode) { + file_count = 0; // Reeset file count (recalculate on draw page) + selection = -1; // Reset delection + sel_mode = 0; // Exit file delete mode + browser_draw_page(current_page); + return; + } + ui_mode_normal(); // Exit + break; + } +} + +static int browser_get_max(void) { + // get max buttons depend from page and file count + int max = current_page == page_count ? (file_count % FILES_PER_PAGE) : FILES_PER_PAGE; + if (file_count > 0 && max == 0) max = FILES_PER_PAGE; + return max + FILE_BUTTON_FILE - 1; +} + +// Process UI input for browser +static void browser_apply_touch(int touch_x, int touch_y) { + browser_btn_t btn; + int old = selection; + int max = browser_get_max(); + for (int idx = 0; idx <= max; idx++) { + browser_get_button_pos(idx, &btn); + if (touch_x < btn.x || touch_x >= btn.x + btn.w || + touch_y < btn.y || touch_y >= btn.y + btn.h) continue; + // Found button under touch + browser_draw_button(selection = idx, NULL); // draw new selection + browser_draw_button(old, NULL); // clear old + touch_wait_release(); + selection = -1; + browser_draw_button(idx, NULL); // clear selection + browser_key_press(idx); + return; + } +} + +static void ui_process_browser_lever(void) { + uint16_t status = btn_check(); + if (status == 0) return; + if (status == EVT_BUTTON_SINGLE_CLICK) { + if (selection >= 0) browser_key_press(selection); // Process click + return; + } + int max = browser_get_max(); + do { + int old = selection; + if((status & EVT_DOWN) && --selection < 0) selection = max; + if((status & EVT_UP) && ++selection > max) selection = 0; + if (old != selection) { + browser_draw_button(old, NULL); // clear old selection + browser_draw_button(selection, NULL); // draw new selection + } + chThdSleepMilliseconds(100); + } while ((status = btn_wait_release()) != 0); +} + +static UI_FUNCTION_CALLBACK(menu_sdcard_browse_cb) { + (void)item; + if (ui_mode == UI_BROWSER) + return; + area_width = 0; + area_height = 0; + ui_mode = UI_BROWSER; + keypad_mode = data; + current_page = 1; + + file_count = 0; + selection = -1; + sel_mode = 0; + browser_draw_page(current_page); +}