home screen performance fix
This commit is contained in:
parent
2f21f55512
commit
7fce5b347d
@ -5,7 +5,7 @@
|
|||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t STATE_FILE_VERSION = 2;
|
constexpr uint8_t STATE_FILE_VERSION = 3;
|
||||||
constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
|
constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -20,6 +20,9 @@ bool CrossPointState::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
||||||
serialization::writeString(outputFile, openEpubPath);
|
serialization::writeString(outputFile, openEpubPath);
|
||||||
serialization::writePod(outputFile, lastSleepImage);
|
serialization::writePod(outputFile, lastSleepImage);
|
||||||
|
// Version 3: add cached book title and author
|
||||||
|
serialization::writeString(outputFile, openBookTitle);
|
||||||
|
serialization::writeString(outputFile, openBookAuthor);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -45,6 +48,15 @@ bool CrossPointState::loadFromFile() {
|
|||||||
lastSleepImage = 0;
|
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();
|
inputFile.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,8 @@ class CrossPointState {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
std::string openEpubPath;
|
std::string openEpubPath;
|
||||||
|
std::string openBookTitle; // Cached title for the current book
|
||||||
|
std::string openBookAuthor; // Cached author for the current book
|
||||||
uint8_t lastSleepImage;
|
uint8_t lastSleepImage;
|
||||||
~CrossPointState() = default;
|
~CrossPointState() = default;
|
||||||
|
|
||||||
|
|||||||
@ -42,6 +42,41 @@ void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left,
|
|||||||
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
|
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) {
|
int ScreenComponents::drawTabBar(const GfxRenderer& renderer, const int y, const std::vector<TabInfo>& tabs) {
|
||||||
constexpr int tabPadding = 20; // Horizontal padding between tabs
|
constexpr int tabPadding = 20; // Horizontal padding between tabs
|
||||||
constexpr int leftMargin = 20; // Left margin for first tab
|
constexpr int leftMargin = 20; // Left margin for first tab
|
||||||
|
|||||||
@ -15,6 +15,9 @@ class ScreenComponents {
|
|||||||
public:
|
public:
|
||||||
static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true);
|
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
|
// Draw a horizontal tab bar with underline indicator for selected tab
|
||||||
// Returns the height of the tab bar (for positioning content below)
|
// Returns the height of the tab bar (for positioning content below)
|
||||||
static int drawTabBar(const GfxRenderer& renderer, int y, const std::vector<TabInfo>& tabs);
|
static int drawTabBar(const GfxRenderer& renderer, int y, const std::vector<TabInfo>& tabs);
|
||||||
|
|||||||
@ -18,6 +18,12 @@
|
|||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
#include "util/StringUtils.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) {
|
void HomeActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<HomeActivity*>(param);
|
auto* self = static_cast<HomeActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
@ -41,50 +47,94 @@ void HomeActivity::onEnter() {
|
|||||||
// Check if OPDS browser URL is configured
|
// Check if OPDS browser URL is configured
|
||||||
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
|
||||||
|
|
||||||
if (hasContinueReading) {
|
if (!hasContinueReading) {
|
||||||
// Extract filename from path for display
|
// No book to continue reading - clear any stale cover cache
|
||||||
lastBookTitle = APP_STATE.openEpubPath;
|
if (coverBufferStored) {
|
||||||
const size_t lastSlash = lastBookTitle.find_last_of('/');
|
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) {
|
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
|
// Check if we have cached title/author from CrossPointState
|
||||||
if (StringUtils::checkFileExtension(lastBookTitle, ".epub")) {
|
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");
|
Epub epub(APP_STATE.openEpubPath, "/.crosspoint");
|
||||||
epub.load(false);
|
// Only load epub metadata if we don't have cached title/author
|
||||||
if (!epub.getTitle().empty()) {
|
if (!hasCachedMetadata) {
|
||||||
lastBookTitle = std::string(epub.getTitle());
|
epub.load(false);
|
||||||
}
|
if (!epub.getTitle().empty()) {
|
||||||
if (!epub.getAuthor().empty()) {
|
lastBookTitle = std::string(epub.getTitle());
|
||||||
lastBookAuthor = std::string(epub.getAuthor());
|
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
|
// Try to generate thumbnail image for Continue Reading card
|
||||||
if (epub.generateThumbBmp()) {
|
if (epub.generateThumbBmp()) {
|
||||||
coverBmpPath = epub.getThumbBmpPath();
|
coverBmpPath = epub.getThumbBmpPath();
|
||||||
hasCoverImage = true;
|
hasCoverImage = true;
|
||||||
}
|
}
|
||||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") ||
|
} else if (StringUtils::checkFileExtension(filenameFromPath, ".xtch") ||
|
||||||
StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
StringUtils::checkFileExtension(filenameFromPath, ".xtc")) {
|
||||||
// Handle XTC file
|
// Handle XTC file
|
||||||
Xtc xtc(APP_STATE.openEpubPath, "/.crosspoint");
|
Xtc xtc(APP_STATE.openEpubPath, "/.crosspoint");
|
||||||
if (xtc.load()) {
|
if (!hasCachedMetadata) {
|
||||||
if (!xtc.getTitle().empty()) {
|
if (xtc.load()) {
|
||||||
lastBookTitle = std::string(xtc.getTitle());
|
if (!xtc.getTitle().empty()) {
|
||||||
|
lastBookTitle = std::string(xtc.getTitle());
|
||||||
|
APP_STATE.openBookTitle = lastBookTitle;
|
||||||
|
APP_STATE.saveToFile();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Try to generate thumbnail image for Continue Reading card
|
// Remove extension from title if we don't have metadata
|
||||||
if (xtc.generateThumbBmp()) {
|
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
|
||||||
coverBmpPath = xtc.getThumbBmpPath();
|
lastBookTitle.resize(lastBookTitle.length() - 5);
|
||||||
hasCoverImage = true;
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove extension from title if we don't have metadata
|
// Try to generate thumbnail image for Continue Reading card
|
||||||
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
|
if (xtc.generateThumbBmp()) {
|
||||||
lastBookTitle.resize(lastBookTitle.length() - 5);
|
coverBmpPath = xtc.getThumbBmpPath();
|
||||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
hasCoverImage = true;
|
||||||
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
@ -112,8 +162,8 @@ void HomeActivity::onExit() {
|
|||||||
vSemaphoreDelete(renderingMutex);
|
vSemaphoreDelete(renderingMutex);
|
||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
|
|
||||||
// Free the stored cover buffer if any
|
// NOTE: Do NOT free cover buffer here - keep it cached for fast re-entry
|
||||||
freeCoverBuffer();
|
// The buffer will be freed/replaced when the book changes
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HomeActivity::storeCoverBuffer() {
|
bool HomeActivity::storeCoverBuffer() {
|
||||||
@ -291,6 +341,9 @@ void HomeActivity::render() {
|
|||||||
|
|
||||||
// Store the buffer with cover image for fast navigation
|
// Store the buffer with cover image for fast navigation
|
||||||
coverBufferStored = storeCoverBuffer();
|
coverBufferStored = storeCoverBuffer();
|
||||||
|
if (coverBufferStored) {
|
||||||
|
cachedCoverPath = coverBmpPath; // Remember which cover is cached
|
||||||
|
}
|
||||||
coverRendered = true;
|
coverRendered = true;
|
||||||
|
|
||||||
// First render: if selected, draw selection indicators now
|
// First render: if selected, draw selection indicators now
|
||||||
@ -552,13 +605,12 @@ void HomeActivity::render() {
|
|||||||
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
|
const auto labels = mappedInput.mapLabels("", "Select", "Up", "Down");
|
||||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
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 =
|
const bool showBatteryPercentage =
|
||||||
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
|
||||||
// get percentage so we can align text properly
|
constexpr int batteryX = 25; // Align with first button hint position
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const int batteryY = pageHeight - 34; // Vertically centered in button hint area
|
||||||
const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : "";
|
ScreenComponents::drawBatteryLarge(renderer, batteryX, batteryY, showBatteryPercentage);
|
||||||
const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
|
||||||
ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
|
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,9 +15,13 @@ class HomeActivity final : public Activity {
|
|||||||
bool hasContinueReading = false;
|
bool hasContinueReading = false;
|
||||||
bool hasOpdsUrl = false;
|
bool hasOpdsUrl = false;
|
||||||
bool hasCoverImage = false;
|
bool hasCoverImage = false;
|
||||||
bool coverRendered = false; // Track if cover has been rendered once
|
|
||||||
bool coverBufferStored = false; // Track if cover buffer is stored
|
// Static cover buffer - persists across activity changes to avoid reloading from SD
|
||||||
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
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 lastBookTitle;
|
||||||
std::string lastBookAuthor;
|
std::string lastBookAuthor;
|
||||||
std::string coverBmpPath;
|
std::string coverBmpPath;
|
||||||
|
|||||||
@ -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.openEpubPath = epub->getPath();
|
||||||
|
APP_STATE.openBookTitle = epub->getTitle();
|
||||||
|
APP_STATE.openBookAuthor = epub->getAuthor();
|
||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor());
|
RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor());
|
||||||
|
|
||||||
|
|||||||
@ -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.openEpubPath = txt->getPath();
|
||||||
|
APP_STATE.openBookTitle = txt->getTitle();
|
||||||
|
APP_STATE.openBookAuthor.clear(); // TXT files don't have author metadata
|
||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
|
|
||||||
// Trigger first update
|
// Trigger first update
|
||||||
|
|||||||
@ -83,8 +83,10 @@ void XtcReaderActivity::onEnter() {
|
|||||||
// Load saved progress
|
// Load saved progress
|
||||||
loadProgress();
|
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.openEpubPath = xtc->getPath();
|
||||||
|
APP_STATE.openBookTitle = xtc->getTitle();
|
||||||
|
APP_STATE.openBookAuthor.clear(); // XTC files don't have author metadata
|
||||||
APP_STATE.saveToFile();
|
APP_STATE.saveToFile();
|
||||||
RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), "");
|
RECENT_BOOKS.addBook(xtc->getPath(), xtc->getTitle(), "");
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user