This commit is contained in:
cottongin
2026-01-24 02:01:53 -05:00
parent 2952d7554c
commit 5c3828efe8
14 changed files with 908 additions and 24 deletions

View File

@@ -1,5 +1,6 @@
#include "MyLibraryActivity.h"
#include <Bitmap.h>
#include <GfxRenderer.h>
#include <SDCardManager.h>
@@ -20,6 +21,24 @@ constexpr int LINE_HEIGHT = 30;
constexpr int RECENTS_LINE_HEIGHT = 65; // Increased for two-line items
constexpr int LEFT_MARGIN = 20;
constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator
constexpr int MICRO_THUMB_WIDTH = 45;
constexpr int MICRO_THUMB_HEIGHT = 60;
constexpr int THUMB_RIGHT_MARGIN = 50; // Space from right edge for thumbnail
// Helper function to get the micro-thumb path for a book based on its file path
std::string getMicroThumbPathForBook(const std::string& bookPath) {
// Calculate cache path using same hash method as Epub/Xtc/Txt classes
const size_t hash = std::hash<std::string>{}(bookPath);
if (StringUtils::checkFileExtension(bookPath, ".epub")) {
return "/.crosspoint/epub_" + std::to_string(hash) + "/micro_thumb.bmp";
} else if (StringUtils::checkFileExtension(bookPath, ".xtc") || StringUtils::checkFileExtension(bookPath, ".xtch")) {
return "/.crosspoint/xtc_" + std::to_string(hash) + "/micro_thumb.bmp";
} else if (StringUtils::checkFileExtension(bookPath, ".txt") || StringUtils::checkFileExtension(bookPath, ".TXT")) {
return "/.crosspoint/txt_" + std::to_string(hash) + "/micro_thumb.bmp";
}
return "";
}
// Timing thresholds
constexpr int SKIP_PAGE_MS = 700;
@@ -481,10 +500,49 @@ void MyLibraryActivity::renderRecentTab() const {
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2,
pageWidth - RIGHT_MARGIN, RECENTS_LINE_HEIGHT);
// Calculate available text width (leaving space for thumbnail on the right)
const int textMaxWidth = pageWidth - LEFT_MARGIN - RIGHT_MARGIN - MICRO_THUMB_WIDTH - 10;
const int thumbX = pageWidth - THUMB_RIGHT_MARGIN - MICRO_THUMB_WIDTH;
// Draw items
for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) {
const auto& book = recentBooks[i];
const int y = CONTENT_START_Y + (i % pageItems) * RECENTS_LINE_HEIGHT;
const bool isSelected = (i == selectorIndex);
// Try to load and draw micro-thumbnail
const std::string microThumbPath = getMicroThumbPathForBook(book.path);
bool hasThumb = false;
if (!microThumbPath.empty() && SdMan.exists(microThumbPath.c_str())) {
FsFile thumbFile;
if (SdMan.openFileForRead("MYL", microThumbPath, thumbFile)) {
Bitmap bitmap(thumbFile);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
// Calculate actual drawn size (scaled to fit within max dimensions, preserving aspect ratio)
const int bmpW = bitmap.getWidth();
const int bmpH = bitmap.getHeight();
const float scaleX = static_cast<float>(MICRO_THUMB_WIDTH) / static_cast<float>(bmpW);
const float scaleY = static_cast<float>(MICRO_THUMB_HEIGHT) / static_cast<float>(bmpH);
const float scale = std::min(scaleX, scaleY);
const int drawnW = static_cast<int>(bmpW * scale);
const int drawnH = static_cast<int>(bmpH * scale);
// Center thumbnail vertically within the row using actual drawn height
const int thumbY = y + (RECENTS_LINE_HEIGHT - drawnH) / 2;
// When selected, clear only the actual drawn area to white first
// (drawBitmap1Bit only draws pixels, it doesn't clear, so we need the white background)
if (isSelected) {
renderer.fillRect(thumbX, thumbY, drawnW, drawnH, false);
}
renderer.drawBitmap(bitmap, thumbX, thumbY, MICRO_THUMB_WIDTH, MICRO_THUMB_HEIGHT, 0, 0, isSelected);
hasThumb = true;
}
thumbFile.close();
}
}
// Use full width if no thumbnail, otherwise use reduced width
const int availableWidth = hasThumb ? textMaxWidth : (pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
// Line 1: Title
std::string title = book.title;
@@ -500,14 +558,13 @@ void MyLibraryActivity::renderRecentTab() const {
title.resize(dot);
}
}
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, y + 2, truncatedTitle.c_str(), i != selectorIndex);
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(), availableWidth);
renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, y + 2, truncatedTitle.c_str(), !isSelected);
// Line 2: Author
if (!book.author.empty()) {
auto truncatedAuthor =
renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, y + 32, truncatedAuthor.c_str(), i != selectorIndex);
auto truncatedAuthor = renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), availableWidth);
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, y + 32, truncatedAuthor.c_str(), !isSelected);
}
}
}

View File

@@ -58,6 +58,47 @@ void EpubReaderActivity::onEnter() {
epub->setupCacheDir();
// Check if cover generation is needed and do it NOW (blocking)
const bool needsThumb = !SdMan.exists(epub->getThumbBmpPath().c_str());
const bool needsMicroThumb = !SdMan.exists(epub->getMicroThumbBmpPath().c_str());
const bool needsCoverFit = !SdMan.exists(epub->getCoverBmpPath(false).c_str());
const bool needsCoverCrop = !SdMan.exists(epub->getCoverBmpPath(true).c_str());
if (needsThumb || needsMicroThumb || needsCoverFit || needsCoverCrop) {
// Show "Preparing book... [X%]" popup, updating every 3 seconds
constexpr int boxMargin = 20;
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, "Preparing book... [100%]");
const int boxWidth = textWidth + boxMargin * 2;
const int boxHeight = renderer.getLineHeight(UI_12_FONT_ID) + boxMargin * 2;
const int boxX = (renderer.getScreenWidth() - boxWidth) / 2;
constexpr int boxY = 50;
unsigned long lastUpdate = 0;
// Draw initial popup
renderer.clearScreen();
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, "Preparing book... [0%]");
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
// Generate covers with progress callback
epub->generateAllCovers([&](int percent) {
const unsigned long now = millis();
if ((now - lastUpdate) >= 3000) {
lastUpdate = now;
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
char progressStr[32];
snprintf(progressStr, sizeof(progressStr), "Preparing book... [%d%%]", percent);
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, progressStr);
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
}
});
}
FsFile f;
if (SdMan.openFileForRead("ERS", epub->getCachePath() + "/progress.bin", f)) {
uint8_t data[4];
@@ -486,6 +527,7 @@ void EpubReaderActivity::renderScreen() {
Serial.printf("[%lu] [ERS] Rendered page in %dms\n", millis(), millis() - start);
}
// Save progress
FsFile f;
if (SdMan.openFileForWrite("ERS", epub->getCachePath() + "/progress.bin", f)) {
uint8_t data[4];

View File

@@ -56,6 +56,47 @@ void TxtReaderActivity::onEnter() {
txt->setupCacheDir();
// Check if cover generation is needed and do it NOW (blocking)
const bool needsCover = !SdMan.exists(txt->getCoverBmpPath().c_str());
const bool needsThumb = !SdMan.exists(txt->getThumbBmpPath().c_str());
const bool needsMicroThumb = !SdMan.exists(txt->getMicroThumbBmpPath().c_str());
const bool hasCoverImage = !txt->findCoverImage().empty();
if (hasCoverImage && (needsCover || needsThumb || needsMicroThumb)) {
// Show "Preparing book... [X%]" popup, updating every 3 seconds
constexpr int boxMargin = 20;
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, "Preparing book... [100%]");
const int boxWidth = textWidth + boxMargin * 2;
const int boxHeight = renderer.getLineHeight(UI_12_FONT_ID) + boxMargin * 2;
const int boxX = (renderer.getScreenWidth() - boxWidth) / 2;
constexpr int boxY = 50;
unsigned long lastUpdate = 0;
// Draw initial popup
renderer.clearScreen();
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, "Preparing book... [0%]");
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
// Generate covers with progress callback
txt->generateAllCovers([&](int percent) {
const unsigned long now = millis();
if ((now - lastUpdate) >= 3000) {
lastUpdate = now;
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
char progressStr[32];
snprintf(progressStr, sizeof(progressStr), "Preparing book... [%d%%]", percent);
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, progressStr);
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
}
});
}
// Save current txt as last opened file
APP_STATE.openEpubPath = txt->getPath();
APP_STATE.saveToFile();
@@ -445,8 +486,6 @@ void TxtReaderActivity::renderScreen() {
renderer.clearScreen();
renderPage();
// Save progress
saveProgress();
}

View File

@@ -40,6 +40,46 @@ void XtcReaderActivity::onEnter() {
xtc->setupCacheDir();
// Check if cover generation is needed and do it NOW (blocking)
const bool needsCover = !SdMan.exists(xtc->getCoverBmpPath().c_str());
const bool needsThumb = !SdMan.exists(xtc->getThumbBmpPath().c_str());
const bool needsMicroThumb = !SdMan.exists(xtc->getMicroThumbBmpPath().c_str());
if (needsCover || needsThumb || needsMicroThumb) {
// Show "Preparing book... [X%]" popup, updating every 3 seconds
constexpr int boxMargin = 20;
const int textWidth = renderer.getTextWidth(UI_12_FONT_ID, "Preparing book... [100%]");
const int boxWidth = textWidth + boxMargin * 2;
const int boxHeight = renderer.getLineHeight(UI_12_FONT_ID) + boxMargin * 2;
const int boxX = (renderer.getScreenWidth() - boxWidth) / 2;
constexpr int boxY = 50;
unsigned long lastUpdate = 0;
// Draw initial popup
renderer.clearScreen();
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, "Preparing book... [0%]");
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
// Generate covers with progress callback
xtc->generateAllCovers([&](int percent) {
const unsigned long now = millis();
if ((now - lastUpdate) >= 3000) {
lastUpdate = now;
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
renderer.drawRect(boxX + 5, boxY + 5, boxWidth - 10, boxHeight - 10);
char progressStr[32];
snprintf(progressStr, sizeof(progressStr), "Preparing book... [%d%%]", percent);
renderer.drawText(UI_12_FONT_ID, boxX + boxMargin, boxY + boxMargin, progressStr);
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
}
});
}
// Load saved progress
loadProgress();