diff --git a/src/core/display.cpp b/src/core/display.cpp index 0e1238ec5..4e6d6ee3b 100644 --- a/src/core/display.cpp +++ b/src/core/display.cpp @@ -32,28 +32,34 @@ bool __attribute__((weak)) isCharging() { return false; } ***************************************************************************************/ void displayScrollingText(const String &text, Opt_Coord &coord) { int len = text.length(); - String displayText = text + " "; // Add spaces for smooth looping - int scrollLen = len + 8; // Full text plus space buffer + static String _lastText = ""; static int i = 0; static long _lastmillis = 0; + + // Reset scroll position when the selected item changes + if (text != _lastText) { + i = 0; + _lastText = text; + _lastmillis = 0; + } + tft.setTextColor(coord.fgcolor, coord.bgcolor); if (len < coord.size) { // Text fits within limit, no scrolling needed return; } else if (millis() > _lastmillis + 200) { - String scrollingPart = - displayText.substring(i, i + (coord.size - 1)); // Display charLimit characters at a time + String displayText = text + " "; + int scrollLen = len + 8; + String scrollingPart = displayText.substring(i, i + (coord.size - 1)); tft.fillRect( - coord.x, - coord.y, + coord.x, coord.y, (coord.size - 1) * LW * tft.getTextSize(), LH * tft.getTextSize(), bruceConfig.bgColor - ); // Clear display area - tft.setCursor(coord.x, coord.y); + ); tft.setCursor(coord.x, coord.y); tft.print(scrollingPart); - if (i >= scrollLen - coord.size) i = -1; // Loop back + if (i >= scrollLen - coord.size) i = -1; _lastmillis = millis(); i++; if (i == 1) _lastmillis = millis() + 1000; @@ -928,47 +934,81 @@ void drawWireguardStatus(int x, int y) { ** Description: Função para desenhar e mostrar o menu principal ***************************************************************************************/ #define MAX_ITEMS (int)(tftHeight - 20) / (LH * FM) + +static bool _listFilesForceReset = true; +void resetListFilesCache() { _listFilesForceReset = true; } + +// Draw one line with text+bg atomically — no flicker, no separate fillRect +static void _drawLine(int visPos, bool selected, const FileList &item, int nchars) { + uint16_t fg; + if (item.folder) fg = getColorVariation(bruceConfig.priColor); + else if (item.operation) fg = ALCOLOR; + else fg = bruceConfig.priColor; + + tft.setTextColor(fg, bruceConfig.bgColor); + tft.setCursor(10, 10 + visPos * LH * FM); + String txt = selected ? ">" : " "; + txt += item.filename; + while ((int)txt.length() < nchars) txt += ' '; + tft.print(txt.substring(0, nchars)); +} + Opt_Coord listFiles(int index, std::vector fileList) { Opt_Coord coord; - tft.drawPixel(0, 0, bruceConfig.bgColor); - if (index == 0) { - tft.fillScreen(bruceConfig.bgColor); - tft.drawRoundRect(5, 5, tftWidth - 10, tftHeight - 10, 5, bruceConfig.priColor); - } - tft.setCursor(10, 10); tft.setTextSize(FM); - int i = 0; + int arraySize = fileList.size(); + int nchars = (tftWidth - 20) / (6 * FM); + int start = 0; if (index >= MAX_ITEMS) { start = index - MAX_ITEMS + 1; if (start < 0) start = 0; } - int nchars = (tftWidth - 20) / (6 * tft.getTextSize()); - String txt = ">"; - while (i < arraySize) { - if (i >= start) { - tft.setCursor(10, tft.getCursorY()); - if (fileList[i].folder == true) - tft.setTextColor(getColorVariation(bruceConfig.priColor), bruceConfig.bgColor); - else if (fileList[i].operation == true) tft.setTextColor(ALCOLOR, bruceConfig.bgColor); - else { tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); } - - if (index == i) { - txt = ">"; - coord.x = 10 + FM * LW; - coord.y = tft.getCursorY(); - coord.size = nchars; - coord.fgcolor = - fileList[i].folder ? getColorVariation(bruceConfig.priColor) : bruceConfig.priColor; - coord.bgcolor = bruceConfig.bgColor; - } else txt = " "; - txt += fileList[i].filename + " "; - tft.println(txt.substring(0, nchars)); + + static int _lastStart = -1; + static int _lastIndex = -1; + + bool pageChanged = (_listFilesForceReset || start != _lastStart); + + if (pageChanged) { + // Full redraw only when page changes or forced (folder change) + if (_listFilesForceReset) { + tft.fillScreen(bruceConfig.bgColor); + tft.drawRoundRect(5, 5, tftWidth - 10, tftHeight - 10, 5, bruceConfig.priColor); } - i++; - if (i == (start + MAX_ITEMS) || i == arraySize) break; + for (int visPos = 0; visPos < MAX_ITEMS; visPos++) { + int i = start + visPos; + if (i >= arraySize) { + // Clear empty slot + tft.setTextColor(bruceConfig.priColor, bruceConfig.bgColor); + tft.setCursor(10, 10 + visPos * LH * FM); + String blank(nchars, ' '); + tft.print(blank); + } else { + _drawLine(visPos, i == index, fileList[i], nchars); + } + } + _listFilesForceReset = false; + } else if (index != _lastIndex) { + // Partial redraw: only the 2 lines that changed + int oldVis = _lastIndex - start; + int newVis = index - start; + if (oldVis >= 0 && oldVis < MAX_ITEMS && _lastIndex < arraySize) + _drawLine(oldVis, false, fileList[_lastIndex], nchars); + if (newVis >= 0 && newVis < MAX_ITEMS) + _drawLine(newVis, true, fileList[index], nchars); } + + _lastStart = start; + _lastIndex = index; + + coord.x = 10 + FM * LW; + coord.y = 10 + (index - start) * LH * FM; + coord.size = nchars; + coord.fgcolor = fileList[index].folder ? getColorVariation(bruceConfig.priColor) : bruceConfig.priColor; + coord.bgcolor = bruceConfig.bgColor; + return coord; } @@ -1228,25 +1268,6 @@ bool showJpeg(FS &fs, String filename, int x, int y, bool center) { return true; } -bool showJpeg(const uint8_t *data_array, size_t data_size, int x, int y, bool center) { - bool decoded = false; - if (data_array) { - decoded = JpegDec.decodeArray(data_array, data_size); - } else { - return false; - } - - if (decoded) { - if (center) { - x = x + (tftWidth - JpegDec.width) / 2; - y = y + (tftHeight - JpegDec.height) / 2; - } - jpegRender(x, y); - } - - return true; -} - #if !defined(LITE_VERSION) // #################################################################################################### // Draw a GIF on the TFT diff --git a/src/core/sd_functions.cpp b/src/core/sd_functions.cpp index aba8ab5fe..77bc6ed6d 100644 --- a/src/core/sd_functions.cpp +++ b/src/core/sd_functions.cpp @@ -1,5 +1,6 @@ #include "sd_functions.h" -#include "display.h" // using displayRedStripe as error msg +#include "display.h" +extern void resetListFilesCache(); // using displayRedStripe as error msg #include "modules/badusb_ble/ducky_typer.h" #include "modules/bjs_interpreter/interpreter.h" #include "modules/gps/wigle.h" @@ -564,7 +565,7 @@ String loopSD(FS &fs, bool filePicker, String allowed_ext, String rootPath) { String Folder = rootPath; String PreFolder = rootPath; tft.drawPixel(0, 0, 0); - tft.fillScreen(bruceConfig.bgColor); // TODO: Does only the T-Embed CC1101 need this? + tft.fillScreen(bruceConfig.bgColor); tft.drawRoundRect(5, 5, tftWidth - 10, tftHeight - 10, 5, bruceConfig.priColor); if (&fs == &SD) { if (!setupSdCard()) { @@ -573,95 +574,91 @@ String loopSD(FS &fs, bool filePicker, String allowed_ext, String rootPath) { } } bool exit = false; - // returnToMenu=true; // make sure menu is redrawn when quitting in any point readFs(fs, Folder, allowed_ext); - maxFiles = fileList.size() - 1; // discount the >back operator + maxFiles = fileList.size() - 1; LongPress = false; unsigned long LongPressTmp = millis(); + while (1) { - delay(10); - // if(returnToMenu) break; // stop this loop and retur to the previous loop - if (exit) break; // stop this loop and retur to the previous loop + yield(); - if (redraw) { - if (strcmp(PreFolder.c_str(), Folder.c_str()) != 0 || reload) { - index = 0; - tft.fillScreen(bruceConfig.bgColor); - tft.drawRoundRect(5, 5, tftWidth - 10, tftHeight - 10, 5, bruceConfig.priColor); - Serial.println("reload to read: " + Folder); - readFs(fs, Folder, allowed_ext); - PreFolder = Folder; - maxFiles = fileList.size() - 1; - if (strcmp(PreFolder.c_str(), Folder.c_str()) != 0 || index > maxFiles) index = 0; - reload = false; - } - if (fileList.size() < 2) readFs(fs, Folder, allowed_ext); + if (exit) break; - coord = listFiles(index, fileList); -#if defined(HAS_TOUCH) - TouchFooter(); -#endif - redraw = false; - } - displayScrollingText(fileList[index].filename, coord); - - // !PrevPress enables EscPress on 3Btn devices to be used in Serial Navigation - // This condition is important for StickCPlus, Core and other 3 Btn devices if (EscPress && PrevPress) EscPress = false; - char pressed_letter; if (check(EscPress)) goto BACK_FOLDER; #ifdef HAS_KEYBOARD - pressed_letter = checkLetterShortcutPress(); - - // check letter shortcuts - if (pressed_letter > 0) { - // Serial.println(pressed_letter); - if (tolower(fileList[index].filename.c_str()[0]) == pressed_letter) { - // already selected, go to the next - index += 1; - // check if index is still valid - if (index <= maxFiles && tolower(fileList[index].filename.c_str()[0]) == pressed_letter) { - redraw = true; - continue; + { + char pressed_letter = checkLetterShortcutPress(); + if (pressed_letter > 0) { + if (tolower(fileList[index].filename.c_str()[0]) == pressed_letter) { + index += 1; + if (index <= maxFiles && tolower(fileList[index].filename.c_str()[0]) == pressed_letter) { + redraw = true; + } } - } - // else look again from the start - for (int i = 0; i < maxFiles; i++) { - if (tolower(fileList[i].filename.c_str()[0]) == pressed_letter) { // check if 1st char matches - index = i; - redraw = true; - break; // quit on 1st match + for (int i = 0; i < maxFiles; i++) { + if (tolower(fileList[i].filename.c_str()[0]) == pressed_letter) { + index = i; + redraw = true; + break; + } } } } #endif if (check(PrevPress) || check(UpPress)) { - if (index == 0) index = maxFiles; - else if (index > 0) index--; + for (int s = 0; s < 3; s++) { + if (index == 0) index = maxFiles; + else index--; + if (s < 2 && !(check(PrevPress) || check(UpPress))) break; + } redraw = true; } - /* DW Btn to next item */ if (check(NextPress) || check(DownPress)) { - if (index == maxFiles) index = 0; - else index++; + for (int s = 0; s < 3; s++) { + if (index == maxFiles) index = 0; + else index++; + if (s < 2 && !(check(NextPress) || check(DownPress))) break; + } redraw = true; } if (check(NextPagePress)) { index += PAGE_JUMP_SIZE; - if (index > maxFiles) index = maxFiles - 1; // check bounds + if (index > maxFiles) index = maxFiles - 1; redraw = true; - continue; } if (check(PrevPagePress)) { index -= PAGE_JUMP_SIZE; - if (index < 0) index = 0; // check bounds + if (index < 0) index = 0; redraw = true; - continue; } + + if (redraw) { + bool folderChanged = (strcmp(PreFolder.c_str(), Folder.c_str()) != 0); + bool forceFullRedraw = folderChanged || reload; + + if (forceFullRedraw) { + index = 0; + readFs(fs, Folder, allowed_ext); + PreFolder = Folder; + maxFiles = fileList.size() - 1; + reload = false; + resetListFilesCache(); + } + if (fileList.size() < 2) readFs(fs, Folder, allowed_ext); + + coord = listFiles(index, fileList); +#if defined(HAS_TOUCH) + TouchFooter(); +#endif + redraw = false; + } + + displayScrollingText(fileList[index].filename, coord); /* Select to install */ if (LongPress || SelPress) { if (!LongPress) { @@ -754,9 +751,9 @@ String loopSD(FS &fs, bool filePicker, String allowed_ext, String rootPath) { options.insert(options.begin(), {"Subghz Tx", [&]() { delay(200); RfCodes data{}; - if (readSubFile(&fs, filepath, data)) txSubFile(data); + txSubFile(&fs, filepath); }}); if (filepath.endsWith(".csv")) { options.insert(options.begin(), {"Wigle Upload", [&]() { @@ -872,7 +869,7 @@ String loopSD(FS &fs, bool filePicker, String allowed_ext, String rootPath) { redraw = true; } WAITING: - delay(10); + yield(); } } fileList.clear();