nice
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user