home screen performance fix

This commit is contained in:
cottongin 2026-01-24 03:59:08 -05:00
parent 2f21f55512
commit 7fce5b347d
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
9 changed files with 155 additions and 41 deletions

View File

@ -5,7 +5,7 @@
#include <Serialization.h>
namespace {
constexpr uint8_t STATE_FILE_VERSION = 2;
constexpr uint8_t STATE_FILE_VERSION = 3;
constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
} // namespace
@ -20,6 +20,9 @@ bool CrossPointState::saveToFile() const {
serialization::writePod(outputFile, STATE_FILE_VERSION);
serialization::writeString(outputFile, openEpubPath);
serialization::writePod(outputFile, lastSleepImage);
// Version 3: add cached book title and author
serialization::writeString(outputFile, openBookTitle);
serialization::writeString(outputFile, openBookAuthor);
outputFile.close();
return true;
}
@ -45,6 +48,15 @@ bool CrossPointState::loadFromFile() {
lastSleepImage = 0;
}
// Version 3: read cached book title and author
if (version >= 3) {
serialization::readString(inputFile, openBookTitle);
serialization::readString(inputFile, openBookAuthor);
} else {
openBookTitle.clear();
openBookAuthor.clear();
}
inputFile.close();
return true;
}

View File

@ -8,6 +8,8 @@ class CrossPointState {
public:
std::string openEpubPath;
std::string openBookTitle; // Cached title for the current book
std::string openBookAuthor; // Cached author for the current book
uint8_t lastSleepImage;
~CrossPointState() = default;

View File

@ -42,6 +42,41 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left,
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
}
void ScreenComponents::drawBatteryLarge(const GfxRenderer& renderer, const int left, const int top,
const bool showPercentage) {
// Larger battery icon with UI_10 font for bottom button hint area
const uint16_t percentage = battery.readPercentage();
const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : "";
renderer.drawText(UI_10_FONT_ID, left + 28, top, percentageText.c_str());
// Scaled up battery dimensions (~33% larger)
constexpr int batteryWidth = 20;
constexpr int batteryHeight = 16;
const int x = left;
const int y = top + 6;
// Top line
renderer.drawLine(x + 1, y, x + batteryWidth - 4, y);
// Bottom line
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1);
// Left line
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2);
// Battery end (right side with nub)
renderer.drawLine(x + batteryWidth - 3, y + 1, x + batteryWidth - 3, y + batteryHeight - 2);
// Battery nub
renderer.drawPixel(x + batteryWidth - 2, y + 4);
renderer.drawPixel(x + batteryWidth - 2, y + batteryHeight - 5);
renderer.drawLine(x + batteryWidth - 1, y + 5, x + batteryWidth - 1, y + batteryHeight - 6);
// The +1 is to round up, so that we always fill at least one pixel
int filledWidth = percentage * (batteryWidth - 6) / 100 + 1;
if (filledWidth > batteryWidth - 6) {
filledWidth = batteryWidth - 6; // Ensure we don't overflow
}
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
}
int ScreenComponents::drawTabBar(const GfxRenderer& renderer, const int y, const std::vector<TabInfo>& tabs) {
constexpr int tabPadding = 20; // Horizontal padding between tabs
constexpr int leftMargin = 20; // Left margin for first tab

View File

@ -15,6 +15,9 @@ class ScreenComponents {
public:
static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true);
// Draw a larger battery icon suitable for bottom button hint area
static void drawBatteryLarge(const GfxRenderer& renderer, int left, int top, bool showPercentage = true);
// Draw a horizontal tab bar with underline indicator for selected tab
// Returns the height of the tab bar (for positioning content below)
static int drawTabBar(const GfxRenderer& renderer, int y, const std::vector<TabInfo>& tabs);

View File

@ -18,6 +18,12 @@
#include "fontIds.h"
#include "util/StringUtils.h"
// Static member definitions for persistent cover buffer
bool HomeActivity::coverRendered = false;
bool HomeActivity::coverBufferStored = false;
uint8_t* HomeActivity::coverBuffer = nullptr;
std::string HomeActivity::cachedCoverPath;
void HomeActivity::taskTrampoline(void* param) {
auto* self = static_cast<HomeActivity*>(param);
self->displayTaskLoop();
@ -41,36 +47,77 @@ void HomeActivity::onEnter() {
// Check if OPDS browser URL is configured
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
if (hasContinueReading) {
// Extract filename from path for display
lastBookTitle = APP_STATE.openEpubPath;
const size_t lastSlash = lastBookTitle.find_last_of('/');
if (!hasContinueReading) {
// No book to continue reading - clear any stale cover cache
if (coverBufferStored) {
freeCoverBuffer();
coverRendered = false;
cachedCoverPath.clear();
}
} else {
// Extract filename from path for display (fallback if no cached title)
std::string filenameFromPath = APP_STATE.openEpubPath;
const size_t lastSlash = filenameFromPath.find_last_of('/');
if (lastSlash != std::string::npos) {
lastBookTitle = lastBookTitle.substr(lastSlash + 1);
filenameFromPath = filenameFromPath.substr(lastSlash + 1);
}
// If epub, try to load the metadata for title/author and cover
if (StringUtils::checkFileExtension(lastBookTitle, ".epub")) {
// Check if we have cached title/author from CrossPointState
const bool hasCachedMetadata = !APP_STATE.openBookTitle.empty();
if (hasCachedMetadata) {
// Use cached title and author - no need to load the book file
lastBookTitle = APP_STATE.openBookTitle;
lastBookAuthor = APP_STATE.openBookAuthor;
} else {
// No cached metadata - use filename as fallback
lastBookTitle = filenameFromPath;
}
// If epub, try to get cover image (and populate cache if needed)
if (StringUtils::checkFileExtension(filenameFromPath, ".epub")) {
Epub epub(APP_STATE.openEpubPath, "/.crosspoint");
// Only load epub metadata if we don't have cached title/author
if (!hasCachedMetadata) {
epub.load(false);
if (!epub.getTitle().empty()) {
lastBookTitle = std::string(epub.getTitle());
APP_STATE.openBookTitle = lastBookTitle;
}
if (!epub.getAuthor().empty()) {
lastBookAuthor = std::string(epub.getAuthor());
APP_STATE.openBookAuthor = lastBookAuthor;
}
// Save updated cache to file
APP_STATE.saveToFile();
}
// Try to generate thumbnail image for Continue Reading card
if (epub.generateThumbBmp()) {
coverBmpPath = epub.getThumbBmpPath();
hasCoverImage = true;
}
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") ||
StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
} else if (StringUtils::checkFileExtension(filenameFromPath, ".xtch") ||
StringUtils::checkFileExtension(filenameFromPath, ".xtc")) {
// Handle XTC file
Xtc xtc(APP_STATE.openEpubPath, "/.crosspoint");
if (!hasCachedMetadata) {
if (xtc.load()) {
if (!xtc.getTitle().empty()) {
lastBookTitle = std::string(xtc.getTitle());
APP_STATE.openBookTitle = lastBookTitle;
APP_STATE.saveToFile();
}
}
// Remove extension from title if we don't have metadata
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
lastBookTitle.resize(lastBookTitle.length() - 5);
APP_STATE.openBookTitle = lastBookTitle;
APP_STATE.saveToFile();
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
lastBookTitle.resize(lastBookTitle.length() - 4);
APP_STATE.openBookTitle = lastBookTitle;
APP_STATE.saveToFile();
}
}
// Try to generate thumbnail image for Continue Reading card
if (xtc.generateThumbBmp()) {
@ -78,12 +125,15 @@ void HomeActivity::onEnter() {
hasCoverImage = true;
}
}
// Remove extension from title if we don't have metadata
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
lastBookTitle.resize(lastBookTitle.length() - 5);
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
lastBookTitle.resize(lastBookTitle.length() - 4);
}
// Check if cached cover buffer is still valid (same book)
if (hasCoverImage && coverBufferStored && cachedCoverPath == coverBmpPath) {
// Cover buffer is still valid, mark as rendered to skip reload
coverRendered = true;
} else if (hasCoverImage && coverBufferStored && cachedCoverPath != coverBmpPath) {
// Book changed, invalidate cached buffer
freeCoverBuffer();
coverRendered = false;
}
}
@ -112,8 +162,8 @@ void HomeActivity::onExit() {
vSemaphoreDelete(renderingMutex);
renderingMutex = nullptr;
// Free the stored cover buffer if any
freeCoverBuffer();
// NOTE: Do NOT free cover buffer here - keep it cached for fast re-entry
// The buffer will be freed/replaced when the book changes
}
bool HomeActivity::storeCoverBuffer() {
@ -291,6 +341,9 @@ void HomeActivity::render() {
// Store the buffer with cover image for fast navigation
coverBufferStored = storeCoverBuffer();
if (coverBufferStored) {
cachedCoverPath = coverBmpPath; // Remember which cover is cached
}
coverRendered = true;
// First render: if selected, draw selection indicators now
@ -552,13 +605,12 @@ void HomeActivity::render() {
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
// Draw battery in bottom-left where the back button hint would normally be
const bool showBatteryPercentage =
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
// get percentage so we can align text properly
const uint16_t percentage = battery.readPercentage();
const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : "";
const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
constexpr int batteryX = 25; // Align with first button hint position
const int batteryY = pageHeight - 34; // Vertically centered in button hint area
ScreenComponents::drawBatteryLarge(renderer, batteryX, batteryY, showBatteryPercentage);
renderer.displayBuffer();
}

View File

@ -15,9 +15,13 @@ class HomeActivity final : public Activity {
bool hasContinueReading = false;
bool hasOpdsUrl = false;
bool hasCoverImage = false;
bool coverRendered = false; // Track if cover has been rendered once
bool coverBufferStored = false; // Track if cover buffer is stored
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
// Static cover buffer - persists across activity changes to avoid reloading from SD
static bool coverRendered; // Track if cover has been rendered once
static bool coverBufferStored; // Track if cover buffer is stored
static uint8_t* coverBuffer; // HomeActivity's own buffer for cover image
static std::string cachedCoverPath; // Path of the cached cover (to detect book changes)
std::string lastBookTitle;
std::string lastBookAuthor;
std::string coverBmpPath;

View File

@ -120,8 +120,10 @@ void EpubReaderActivity::onEnter() {
}
}
// Save current epub as last opened epub and add to recent books
// Save current epub as last opened epub and cache title/author for home screen
APP_STATE.openEpubPath = epub->getPath();
APP_STATE.openBookTitle = epub->getTitle();
APP_STATE.openBookAuthor = epub->getAuthor();
APP_STATE.saveToFile();
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor());

View File

@ -97,8 +97,10 @@ void TxtReaderActivity::onEnter() {
});
}
// Save current txt as last opened file
// Save current txt as last opened file and cache title for home screen
APP_STATE.openEpubPath = txt->getPath();
APP_STATE.openBookTitle = txt->getTitle();
APP_STATE.openBookAuthor.clear(); // TXT files don't have author metadata
APP_STATE.saveToFile();
// Trigger first update

View File

@ -83,8 +83,10 @@ void XtcReaderActivity::onEnter() {
// Load saved progress
loadProgress();
// Save current XTC as last opened book and add to recent books
// Save current XTC as last opened book and cache title for home screen
APP_STATE.openEpubPath = xtc->getPath();
APP_STATE.openBookTitle = xtc->getTitle();
APP_STATE.openBookAuthor.clear(); // XTC files don't have author metadata
APP_STATE.saveToFile();
RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), "");