Thumbnail background loading

This commit is contained in:
CaptainFrito 2025-12-27 16:21:55 -08:00
parent 666d3a9179
commit f40ab574b2
3 changed files with 83 additions and 44 deletions

View File

@ -18,8 +18,8 @@ constexpr int TILE_PADDING = 5;
constexpr int THUMB_W = 90;
constexpr int THUMB_H = 120;
constexpr int TILE_TEXT_H = 60;
constexpr int gridLeftOffset = 37;
constexpr int gridTopOffset = 125;
constexpr int GRID_OFFSET_LEFT = 37;
constexpr int GRID_OFFSET_TOP = 125;
} // namespace
inline int min(const int a, const int b) { return a < b ? a : b; }
@ -39,10 +39,40 @@ void GridBrowserActivity::displayTaskTrampoline(void* param) {
self->displayTaskLoop();
}
// void GridBrowserActivity::loadThumbsTaskTrampoline(void* param) {
// auto* self = static_cast<GridBrowserActivity*>(param);
// self->displayTaskLoop();
// }
void GridBrowserActivity::loadThumbsTaskTrampoline(void* param) {
auto* self = static_cast<GridBrowserActivity*>(param);
self->loadThumbsTaskLoop();
}
void GridBrowserActivity::loadThumbsTaskLoop() {
while (true) {
if (thumbsLoadingRequired) {
xSemaphoreTake(loadThumbsMutex, portMAX_DELAY);
loadThumbs();
xSemaphoreGive(loadThumbsMutex);
thumbsLoadingRequired = false;
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
void GridBrowserActivity::loadThumbs() {
int thumbsCount = min(PAGE_ITEMS, files.size() - page * PAGE_ITEMS);
for (int i = 0; i < thumbsCount; i++) {
const auto file = files[i + page * PAGE_ITEMS];
if (file.type == F_EPUB) {
if (file.thumbPath.empty()) {
Serial.printf("[%lu] Loading thumb for epub: %s\n", millis(), file.name.c_str());
std::string thumbPath = loadEpubThumb(basepath + "/" + file.name);
if (!thumbPath.empty()) {
files[i + page * PAGE_ITEMS].thumbPath = thumbPath;
}
renderRequired = true;
taskYIELD();
}
}
}
}
std::string GridBrowserActivity::loadEpubThumb(std::string path) {
File file;
@ -87,12 +117,6 @@ void GridBrowserActivity::loadFiles() {
std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); });
if (ext == ".epub") {
type = F_EPUB;
// xTaskCreate(&GridBrowserActivity::taskTrampoline, "GridFileBrowserTask",
// 2048, // Stack size
// this, // Parameters
// 1, // Priority
// &displayTaskHandle // Task handle
// );
} else if (ext == ".bmp") {
type = F_BMP;
}
@ -105,27 +129,29 @@ void GridBrowserActivity::loadFiles() {
count++;
}
root.close();
Serial.printf("Files loaded\n");
GridBrowserActivity::sortFileList(files);
Serial.printf("Files sorted\n");
}
void GridBrowserActivity::onEnter() {
renderingMutex = xSemaphoreCreateMutex();
loadThumbsMutex = xSemaphoreCreateMutex();
loadFiles();
selectorIndex = 0;
page = 0;
// Trigger first render
renderRequired = true;
loadFiles();
onPageChanged();
xTaskCreate(&GridBrowserActivity::displayTaskTrampoline, "GridFileBrowserTask",
8192, // Stack size
this, // Parameters
1, // Priority
2, // Priority
&displayTaskHandle // Task handle
);
xTaskCreate(&GridBrowserActivity::loadThumbsTaskTrampoline, "LoadThumbsTask",
8192, // Stack size
this, // Parameters
1, // Priority
&loadThumbsTaskHandle // Task handle
);
}
void GridBrowserActivity::onExit() {
@ -139,9 +165,24 @@ void GridBrowserActivity::onExit() {
}
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
if (loadThumbsTaskHandle) {
vTaskDelete(loadThumbsTaskHandle);
loadThumbsTaskHandle = nullptr;
}
vSemaphoreDelete(loadThumbsMutex);
loadThumbsMutex = nullptr;
files.clear();
}
void GridBrowserActivity::onPageChanged() {
selectorIndex = 0;
previousSelectorIndex = -1;
renderRequired = true;
thumbsLoadingRequired = true;
}
void GridBrowserActivity::loop() {
const bool prevReleased = inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
const bool nextReleased = inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
@ -160,7 +201,7 @@ void GridBrowserActivity::loop() {
// open subfolder
basepath += files[selected].name;
loadFiles();
renderRequired = true;
onPageChanged();
} else {
onSelect(basepath + files[selected].name);
}
@ -169,7 +210,7 @@ void GridBrowserActivity::loop() {
basepath.resize(basepath.rfind('/'));
if (basepath.empty()) basepath = "/";
loadFiles();
renderRequired = true;
onPageChanged();
} else {
// At root level, go back home
onGoHome();
@ -179,9 +220,7 @@ void GridBrowserActivity::loop() {
if (selectorIndex == 0 || skipPage) {
if (page > 0) {
page--;
selectorIndex = 0;
previousSelectorIndex = -1;
renderRequired = true;
onPageChanged();
}
} else {
selectorIndex--;
@ -192,9 +231,7 @@ void GridBrowserActivity::loop() {
if (selectorIndex == min(PAGE_ITEMS, files.size() - page * PAGE_ITEMS) - 1 || skipPage) {
if (page < files.size() / PAGE_ITEMS) {
page++;
selectorIndex = 0;
previousSelectorIndex = -1;
renderRequired = true;
onPageChanged();
}
} else {
selectorIndex++;
@ -205,16 +242,12 @@ void GridBrowserActivity::loop() {
void GridBrowserActivity::displayTaskLoop() {
while (true) {
if (renderRequired) {
if (renderRequired || updateRequired) {
bool didRequireRender = renderRequired;
renderRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
render(true);
xSemaphoreGive(renderingMutex);
} else if (updateRequired) {
updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY);
// update(true);
render(false);
render(didRequireRender);
xSemaphoreGive(renderingMutex);
}
vTaskDelay(10 / portTICK_PERIOD_MS);
@ -232,8 +265,8 @@ void GridBrowserActivity::render(bool clear) const {
for (size_t i = 0; i < min(PAGE_ITEMS, files.size() - page * PAGE_ITEMS); i++) {
const auto file = files[i + page * PAGE_ITEMS];
const int16_t tileX = gridLeftOffset + i % 3 * TILE_W;
const int16_t tileY = gridTopOffset + i / 3 * TILE_H;
const int16_t tileX = GRID_OFFSET_LEFT + i % 3 * TILE_W;
const int16_t tileY = GRID_OFFSET_TOP + i / 3 * TILE_H;
if (file.type == F_DIRECTORY) {
constexpr int iconOffsetX = (TILE_W - FOLDERICON_WIDTH) / 2;
@ -263,7 +296,7 @@ void GridBrowserActivity::render(bool clear) const {
}
void GridBrowserActivity::drawSelectionRectangle(int tileIndex, bool black) const {
renderer.drawRoundedRect(gridLeftOffset + tileIndex % 3 * TILE_W, gridTopOffset + tileIndex / 3 * TILE_H, TILE_W, TILE_H, 2, 5, black);
renderer.drawRoundedRect(GRID_OFFSET_LEFT + tileIndex % 3 * TILE_W, GRID_OFFSET_TOP + tileIndex / 3 * TILE_H, TILE_W, TILE_H, 2, 5, black);
}
void GridBrowserActivity::update(bool render) const {

View File

@ -27,6 +27,8 @@ struct FileInfo {
class GridBrowserActivity final : public Activity {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
TaskHandle_t loadThumbsTaskHandle = nullptr;
SemaphoreHandle_t loadThumbsMutex = nullptr;
std::string basepath = "/";
std::vector<FileInfo> files;
int selectorIndex = 0;
@ -34,6 +36,7 @@ class GridBrowserActivity final : public Activity {
int page = 0;
bool updateRequired = false;
bool renderRequired = false;
bool thumbsLoadingRequired = false;
const std::function<void(const std::string&)> onSelect;
const std::function<void()> onGoHome;
@ -45,6 +48,9 @@ class GridBrowserActivity final : public Activity {
void loadFiles();
void drawSelectionRectangle(int tileIndex, bool black) const;
std::string loadEpubThumb(std::string path);
void loadThumbsTaskLoop();
void loadThumbs();
void onPageChanged();
public:
explicit GridBrowserActivity(GfxRenderer& renderer, InputManager& inputManager,

View File

@ -9,7 +9,7 @@
// Define the static settings list
namespace {
constexpr int settingsCount = 5;
constexpr int settingsCount = 6;
const SettingInfo settingsList[settingsCount] = {
// Should match with SLEEP_SCREEN_MODE
{"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
@ -17,7 +17,7 @@ const SettingInfo settingsList[settingsCount] = {
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}},
{"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}},
{"UI Theme", SettingType::ENUM, &CrossPointSettings::uiTheme, {"List", "Grid"}},
{"Check for updates", SettingType::ACTION, nullptr, {}},
{"Check for updates", SettingType::ACTION, nullptr, {}}
};
} // namespace