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

View File

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

View File

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