#include "FileSelectionScreen.h" #include #include void FileSelectionScreen::taskTrampoline(void* param) { auto* self = static_cast(param); self->displayTaskLoop(); } void FileSelectionScreen::loadFiles() { files.clear(); selectorIndex = 0; auto root = SD.open(basepath.c_str()); for (File file = root.openNextFile(); file; file = root.openNextFile()) { auto filename = std::string(file.name()); if (filename[0] == '.') { file.close(); continue; } if (file.isDirectory()) { files.emplace_back(filename + "/"); } else if (filename.substr(filename.length() - 5) == ".epub") { files.emplace_back(filename); } file.close(); } root.close(); } void FileSelectionScreen::onEnter() { renderingMutex = xSemaphoreCreateMutex(); basepath = "/"; loadFiles(); selectorIndex = 0; // Trigger first update updateRequired = true; xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask", 1024, // Stack size this, // Parameters 1, // Priority &displayTaskHandle // Task handle ); } void FileSelectionScreen::onExit() { // Wait until not rendering to delete task to avoid killing mid-instruction to EPD xSemaphoreTake(renderingMutex, portMAX_DELAY); if (displayTaskHandle) { vTaskDelete(displayTaskHandle); displayTaskHandle = nullptr; } vSemaphoreDelete(renderingMutex); renderingMutex = nullptr; files.clear(); } void FileSelectionScreen::handleInput() { const bool prevPressed = inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT); const bool nextPressed = inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT); if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) { if (files.empty()) { return; } if (files[selectorIndex].back() == '/') { if (basepath.back() != '/') basepath += "/"; basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1); loadFiles(); updateRequired = true; } else { onSelect(basepath + files[selectorIndex]); } } else if (inputManager.wasPressed(InputManager::BTN_BACK) && basepath != "/") { basepath = basepath.substr(0, basepath.rfind('/')); if (basepath.empty()) basepath = "/"; loadFiles(); updateRequired = true; } else if (prevPressed) { selectorIndex = (selectorIndex + files.size() - 1) % files.size(); updateRequired = true; } else if (nextPressed) { selectorIndex = (selectorIndex + 1) % files.size(); updateRequired = true; } } void FileSelectionScreen::displayTaskLoop() { while (true) { if (updateRequired) { updateRequired = false; xSemaphoreTake(renderingMutex, portMAX_DELAY); render(); xSemaphoreGive(renderingMutex); } vTaskDelay(10 / portTICK_PERIOD_MS); } } void FileSelectionScreen::render() const { renderer->clearScreen(); const auto pageWidth = renderer->getPageWidth(); const auto titleWidth = renderer->getTextWidth("CrossPoint Reader", BOLD); renderer->drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", 1, BOLD); if (files.empty()) { renderer->drawUiText(10, 50, "No EPUBs found"); } else { // Draw selection renderer->fillRect(0, 50 + selectorIndex * 30 + 2, pageWidth - 1, 30); for (size_t i = 0; i < files.size(); i++) { const auto file = files[i]; renderer->drawUiText(10, 50 + i * 30, file.c_str(), i == selectorIndex ? 0 : 1); } } renderer->flushDisplay(); }