This commit is contained in:
Kenneth 2026-01-13 14:13:37 -05:00
parent 5d81d7cac3
commit 5d04440407
3 changed files with 107 additions and 183 deletions

View File

@ -15,17 +15,15 @@
#include "fontIds.h" #include "fontIds.h"
#include "util/StringUtils.h" #include "util/StringUtils.h"
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();
} }
int HomeActivity::getMenuItemCount() const { int HomeActivity::getMenuItemCount() const {
int count = 3; // Base: My Library, File transfer, Settings int count = 3; // My Library, File transfer, Settings
if (hasContinueReading) if (hasContinueReading) count++;
count++; if (hasOpdsUrl) count++;
if (hasOpdsUrl)
count++;
return count; return count;
} }
@ -35,8 +33,7 @@ void HomeActivity::onEnter() {
renderingMutex = xSemaphoreCreateMutex(); renderingMutex = xSemaphoreCreateMutex();
// Check if we have a book to continue reading // Check if we have a book to continue reading
hasContinueReading = !APP_STATE.openEpubPath.empty() && hasContinueReading = !APP_STATE.openEpubPath.empty() && SdMan.exists(APP_STATE.openEpubPath.c_str());
SdMan.exists(APP_STATE.openEpubPath.c_str());
// Check if OPDS browser URL is configured // Check if OPDS browser URL is configured
hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0; hasOpdsUrl = strlen(SETTINGS.opdsServerUrl) > 0;
@ -72,18 +69,17 @@ void HomeActivity::onEnter() {
updateRequired = true; updateRequired = true;
xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask", xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask",
4096, // Stack size 4096, // Stack size
this, // Parameters this, // Parameters
1, // Priority 1, // Priority
&displayTaskHandle // Task handle &displayTaskHandle // Task handle
); );
} }
void HomeActivity::onExit() { void HomeActivity::onExit() {
Activity::onExit(); Activity::onExit();
// Wait until not rendering to delete task to avoid killing mid-instruction to // Wait until not rendering to delete task to avoid killing mid-instruction to EPD
// EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) { if (displayTaskHandle) {
vTaskDelete(displayTaskHandle); vTaskDelete(displayTaskHandle);
@ -94,12 +90,10 @@ void HomeActivity::onExit() {
} }
void HomeActivity::loop() { void HomeActivity::loop() {
const bool prevPressed = const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
mappedInput.wasPressed(MappedInputManager::Button::Up) || mappedInput.wasPressed(MappedInputManager::Button::Left);
mappedInput.wasPressed(MappedInputManager::Button::Left); const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Right);
mappedInput.wasPressed(MappedInputManager::Button::Down) ||
mappedInput.wasPressed(MappedInputManager::Button::Right);
const int menuCount = getMenuItemCount(); const int menuCount = getMenuItemCount();
@ -175,12 +169,10 @@ void HomeActivity::render() const {
constexpr int bookmarkY = bookY + 1; constexpr int bookmarkY = bookY + 1;
// Main bookmark body (solid) // Main bookmark body (solid)
renderer.fillRect(bookmarkX, bookmarkY, bookmarkWidth, bookmarkHeight, renderer.fillRect(bookmarkX, bookmarkY, bookmarkWidth, bookmarkHeight, !bookSelected);
!bookSelected);
// Carve out an inverted triangle notch at the bottom center to create // Carve out an inverted triangle notch at the bottom center to create angled points
// angled points const int notchHeight = bookmarkHeight / 2; // depth of the notch
const int notchHeight = bookmarkHeight / 2; // depth of the notch
for (int i = 0; i < notchHeight; ++i) { for (int i = 0; i < notchHeight; ++i) {
const int y = bookmarkY + bookmarkHeight - 1 - i; const int y = bookmarkY + bookmarkHeight - 1 - i;
const int xStart = bookmarkX + i; const int xStart = bookmarkX + i;
@ -218,16 +210,14 @@ void HomeActivity::render() const {
const int maxLineWidth = bookWidth - 40; const int maxLineWidth = bookWidth - 40;
const int spaceWidth = renderer.getSpaceWidth(UI_12_FONT_ID); const int spaceWidth = renderer.getSpaceWidth(UI_12_FONT_ID);
for (auto &i : words) { for (auto& i : words) {
// If we just hit the line limit (3), stop processing words // If we just hit the line limit (3), stop processing words
if (lines.size() >= 3) { if (lines.size() >= 3) {
// Limit to 3 lines // Limit to 3 lines
// Still have words left, so add ellipsis to last line // Still have words left, so add ellipsis to last line
lines.back().append("..."); lines.back().append("...");
while (!lines.back().empty() && while (!lines.back().empty() && renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) {
renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) >
maxLineWidth) {
lines.back().resize(lines.back().size() - 5); lines.back().resize(lines.back().size() - 5);
lines.back().append("..."); lines.back().append("...");
} }
@ -242,8 +232,7 @@ void HomeActivity::render() const {
wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str()); wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str());
} }
int newLineWidth = int newLineWidth = renderer.getTextWidth(UI_12_FONT_ID, currentLine.c_str());
renderer.getTextWidth(UI_12_FONT_ID, currentLine.c_str());
if (newLineWidth > 0) { if (newLineWidth > 0) {
newLineWidth += spaceWidth; newLineWidth += spaceWidth;
} }
@ -264,8 +253,7 @@ void HomeActivity::render() const {
} }
// Book title text // Book title text
int totalTextHeight = int totalTextHeight = renderer.getLineHeight(UI_12_FONT_ID) * static_cast<int>(lines.size());
renderer.getLineHeight(UI_12_FONT_ID) * static_cast<int>(lines.size());
if (!lastBookAuthor.empty()) { if (!lastBookAuthor.empty()) {
totalTextHeight += renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2; totalTextHeight += renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
} }
@ -273,9 +261,8 @@ void HomeActivity::render() const {
// Vertically center the title block within the card // Vertically center the title block within the card
int titleYStart = bookY + (bookHeight - totalTextHeight) / 2; int titleYStart = bookY + (bookHeight - totalTextHeight) / 2;
for (const auto &line : lines) { for (const auto& line : lines) {
renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected);
!bookSelected);
titleYStart += renderer.getLineHeight(UI_12_FONT_ID); titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
} }
@ -283,35 +270,26 @@ void HomeActivity::render() const {
titleYStart += renderer.getLineHeight(UI_10_FONT_ID) / 2; titleYStart += renderer.getLineHeight(UI_10_FONT_ID) / 2;
std::string trimmedAuthor = lastBookAuthor; std::string trimmedAuthor = lastBookAuthor;
// Trim author if too long // Trim author if too long
while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) {
maxLineWidth &&
!trimmedAuthor.empty()) {
trimmedAuthor.resize(trimmedAuthor.size() - 5); trimmedAuthor.resize(trimmedAuthor.size() - 5);
trimmedAuthor.append("..."); trimmedAuthor.append("...");
} }
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected);
trimmedAuthor.c_str(), !bookSelected);
} }
renderer.drawCenteredText(UI_10_FONT_ID, renderer.drawCenteredText(UI_10_FONT_ID, bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2,
bookY + bookHeight -
renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2,
"Continue Reading", !bookSelected); "Continue Reading", !bookSelected);
} else { } else {
// No book to continue reading // No book to continue reading
const int y = bookY + (bookHeight - renderer.getLineHeight(UI_12_FONT_ID) - const int y =
renderer.getLineHeight(UI_10_FONT_ID)) / bookY + (bookHeight - renderer.getLineHeight(UI_12_FONT_ID) - renderer.getLineHeight(UI_10_FONT_ID)) / 2;
2;
renderer.drawCenteredText(UI_12_FONT_ID, y, "No open book"); renderer.drawCenteredText(UI_12_FONT_ID, y, "No open book");
renderer.drawCenteredText(UI_10_FONT_ID, renderer.drawCenteredText(UI_10_FONT_ID, y + renderer.getLineHeight(UI_12_FONT_ID), "Start reading below");
y + renderer.getLineHeight(UI_12_FONT_ID),
"Start reading below");
} }
// --- Bottom menu tiles --- // --- Bottom menu tiles ---
// Build menu items dynamically // Build menu items dynamically
std::vector<const char *> menuItems = {"My Library", "File Transfer", std::vector<const char*> menuItems = {"My Library", "File Transfer", "Settings"};
"Settings"};
if (hasOpdsUrl) { if (hasOpdsUrl) {
// Insert Calibre Library after My Library // Insert Calibre Library after My Library
menuItems.insert(menuItems.begin() + 1, "Calibre Library"); menuItems.insert(menuItems.begin() + 1, "Calibre Library");
@ -321,13 +299,11 @@ void HomeActivity::render() const {
constexpr int menuTileHeight = 45; constexpr int menuTileHeight = 45;
constexpr int menuSpacing = 8; constexpr int menuSpacing = 8;
const int totalMenuHeight = const int totalMenuHeight =
static_cast<int>(menuItems.size()) * menuTileHeight + static_cast<int>(menuItems.size()) * menuTileHeight + (static_cast<int>(menuItems.size()) - 1) * menuSpacing;
(static_cast<int>(menuItems.size()) - 1) * menuSpacing;
int menuStartY = bookY + bookHeight + 15; int menuStartY = bookY + bookHeight + 15;
// Ensure we don't collide with the bottom button legend // Ensure we don't collide with the bottom button legend
const int maxMenuStartY = const int maxMenuStartY = pageHeight - bottomMargin - totalMenuHeight - margin;
pageHeight - bottomMargin - totalMenuHeight - margin;
if (menuStartY > maxMenuStartY) { if (menuStartY > maxMenuStartY) {
menuStartY = maxMenuStartY; menuStartY = maxMenuStartY;
} }
@ -335,8 +311,7 @@ void HomeActivity::render() const {
for (size_t i = 0; i < menuItems.size(); ++i) { for (size_t i = 0; i < menuItems.size(); ++i) {
const int overallIndex = static_cast<int>(i) + (hasContinueReading ? 1 : 0); const int overallIndex = static_cast<int>(i) + (hasContinueReading ? 1 : 0);
constexpr int tileX = margin; constexpr int tileX = margin;
const int tileY = const int tileY = menuStartY + static_cast<int>(i) * (menuTileHeight + menuSpacing);
menuStartY + static_cast<int>(i) * (menuTileHeight + menuSpacing);
const bool selected = selectorIndex == overallIndex; const bool selected = selectorIndex == overallIndex;
if (selected) { if (selected) {
@ -345,33 +320,25 @@ void HomeActivity::render() const {
renderer.drawRect(tileX, tileY, menuTileWidth, menuTileHeight); renderer.drawRect(tileX, tileY, menuTileWidth, menuTileHeight);
} }
const char *label = menuItems[i]; const char* label = menuItems[i];
const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, label); const int textWidth = renderer.getTextWidth(UI_10_FONT_ID, label);
const int textX = tileX + (menuTileWidth - textWidth) / 2; const int textX = tileX + (menuTileWidth - textWidth) / 2;
const int lineHeight = renderer.getLineHeight(UI_10_FONT_ID); const int lineHeight = renderer.getLineHeight(UI_10_FONT_ID);
const int textY = const int textY = tileY + (menuTileHeight - lineHeight) / 2; // vertically centered assuming y is top of text
tileY + (menuTileHeight - lineHeight) /
2; // vertically centered assuming y is top of text
// Invert text when the tile is selected, to contrast with the filled // Invert text when the tile is selected, to contrast with the filled background
// background
renderer.drawText(UI_10_FONT_ID, textX, textY, label, !selected); renderer.drawText(UI_10_FONT_ID, textX, textY, label, !selected);
} }
const auto labels = mappedInput.mapLabels("", "Confirm", "Up", "Down"); const auto labels = mappedInput.mapLabels("", "Confirm", "Up", "Down");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
labels.btn4);
const bool showBatteryPercentage = const bool showBatteryPercentage =
SETTINGS.hideBatteryPercentage != SETTINGS.hideBatteryPercentage != CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
CrossPointSettings::HIDE_BATTERY_PERCENTAGE::HIDE_ALWAYS;
// get percentage so we can align text properly // get percentage so we can align text properly
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = battery.readPercentage();
const auto percentageText = const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : "";
showBatteryPercentage ? std::to_string(percentage) + "%" : ""; const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
const auto batteryX =
pageWidth - 25 -
renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage); ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
renderer.displayBuffer(); renderer.displayBuffer();

View File

@ -22,21 +22,21 @@ class HomeActivity final : public Activity {
const std::function<void()> onFileTransferOpen; const std::function<void()> onFileTransferOpen;
const std::function<void()> onOpdsBrowserOpen; const std::function<void()> onOpdsBrowserOpen;
static void taskTrampoline(void *param); static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop(); [[noreturn]] void displayTaskLoop();
void render() const; void render() const;
int getMenuItemCount() const; int getMenuItemCount() const;
public: public:
explicit HomeActivity(GfxRenderer &renderer, MappedInputManager &mappedInput, explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
const std::function<void()> &onContinueReading, const std::function<void()>& onContinueReading, const std::function<void()>& onMyLibraryOpen,
const std::function<void()> &onMyLibraryOpen, const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen,
const std::function<void()> &onSettingsOpen, const std::function<void()>& onOpdsBrowserOpen)
const std::function<void()> &onFileTransferOpen,
const std::function<void()> &onOpdsBrowserOpen)
: Activity("Home", renderer, mappedInput), : Activity("Home", renderer, mappedInput),
onContinueReading(onContinueReading), onMyLibraryOpen(onMyLibraryOpen), onContinueReading(onContinueReading),
onSettingsOpen(onSettingsOpen), onFileTransferOpen(onFileTransferOpen), onMyLibraryOpen(onMyLibraryOpen),
onSettingsOpen(onSettingsOpen),
onFileTransferOpen(onFileTransferOpen),
onOpdsBrowserOpen(onOpdsBrowserOpen) {} onOpdsBrowserOpen(onOpdsBrowserOpen) {}
void onEnter() override; void onEnter() override;
void onExit() override; void onExit() override;

View File

@ -27,14 +27,14 @@
#define SPI_FQ 40000000 #define SPI_FQ 40000000
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults) // Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
#define EPD_SCLK 8 // SPI Clock #define EPD_SCLK 8 // SPI Clock
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In) #define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
#define EPD_CS 21 // Chip Select #define EPD_CS 21 // Chip Select
#define EPD_DC 4 // Data/Command #define EPD_DC 4 // Data/Command
#define EPD_RST 5 // Reset #define EPD_RST 5 // Reset
#define EPD_BUSY 6 // Busy #define EPD_BUSY 6 // Busy
#define UART0_RXD 20 // Used for USB connection detection #define UART0_RXD 20 // Used for USB connection detection
#define SD_SPI_MISO 7 #define SD_SPI_MISO 7
@ -42,98 +42,82 @@ EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
InputManager inputManager; InputManager inputManager;
MappedInputManager mappedInputManager(inputManager); MappedInputManager mappedInputManager(inputManager);
GfxRenderer renderer(einkDisplay); GfxRenderer renderer(einkDisplay);
Activity *currentActivity; Activity* currentActivity;
// Fonts // Fonts
EpdFont bookerly12RegularFont(&bookerly_12_regular); EpdFont bookerly12RegularFont(&bookerly_12_regular);
EpdFont bookerly12BoldFont(&bookerly_12_bold); EpdFont bookerly12BoldFont(&bookerly_12_bold);
EpdFont bookerly12ItalicFont(&bookerly_12_italic); EpdFont bookerly12ItalicFont(&bookerly_12_italic);
EpdFont bookerly12BoldItalicFont(&bookerly_12_bolditalic); EpdFont bookerly12BoldItalicFont(&bookerly_12_bolditalic);
EpdFontFamily bookerly12FontFamily(&bookerly12RegularFont, &bookerly12BoldFont, EpdFontFamily bookerly12FontFamily(&bookerly12RegularFont, &bookerly12BoldFont, &bookerly12ItalicFont,
&bookerly12ItalicFont,
&bookerly12BoldItalicFont); &bookerly12BoldItalicFont);
EpdFont bookerly14RegularFont(&bookerly_14_regular); EpdFont bookerly14RegularFont(&bookerly_14_regular);
EpdFont bookerly14BoldFont(&bookerly_14_bold); EpdFont bookerly14BoldFont(&bookerly_14_bold);
EpdFont bookerly14ItalicFont(&bookerly_14_italic); EpdFont bookerly14ItalicFont(&bookerly_14_italic);
EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic); EpdFont bookerly14BoldItalicFont(&bookerly_14_bolditalic);
EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont, EpdFontFamily bookerly14FontFamily(&bookerly14RegularFont, &bookerly14BoldFont, &bookerly14ItalicFont,
&bookerly14ItalicFont,
&bookerly14BoldItalicFont); &bookerly14BoldItalicFont);
EpdFont bookerly16RegularFont(&bookerly_16_regular); EpdFont bookerly16RegularFont(&bookerly_16_regular);
EpdFont bookerly16BoldFont(&bookerly_16_bold); EpdFont bookerly16BoldFont(&bookerly_16_bold);
EpdFont bookerly16ItalicFont(&bookerly_16_italic); EpdFont bookerly16ItalicFont(&bookerly_16_italic);
EpdFont bookerly16BoldItalicFont(&bookerly_16_bolditalic); EpdFont bookerly16BoldItalicFont(&bookerly_16_bolditalic);
EpdFontFamily bookerly16FontFamily(&bookerly16RegularFont, &bookerly16BoldFont, EpdFontFamily bookerly16FontFamily(&bookerly16RegularFont, &bookerly16BoldFont, &bookerly16ItalicFont,
&bookerly16ItalicFont,
&bookerly16BoldItalicFont); &bookerly16BoldItalicFont);
EpdFont bookerly18RegularFont(&bookerly_18_regular); EpdFont bookerly18RegularFont(&bookerly_18_regular);
EpdFont bookerly18BoldFont(&bookerly_18_bold); EpdFont bookerly18BoldFont(&bookerly_18_bold);
EpdFont bookerly18ItalicFont(&bookerly_18_italic); EpdFont bookerly18ItalicFont(&bookerly_18_italic);
EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic); EpdFont bookerly18BoldItalicFont(&bookerly_18_bolditalic);
EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont, EpdFontFamily bookerly18FontFamily(&bookerly18RegularFont, &bookerly18BoldFont, &bookerly18ItalicFont,
&bookerly18ItalicFont,
&bookerly18BoldItalicFont); &bookerly18BoldItalicFont);
EpdFont notosans12RegularFont(&notosans_12_regular); EpdFont notosans12RegularFont(&notosans_12_regular);
EpdFont notosans12BoldFont(&notosans_12_bold); EpdFont notosans12BoldFont(&notosans_12_bold);
EpdFont notosans12ItalicFont(&notosans_12_italic); EpdFont notosans12ItalicFont(&notosans_12_italic);
EpdFont notosans12BoldItalicFont(&notosans_12_bolditalic); EpdFont notosans12BoldItalicFont(&notosans_12_bolditalic);
EpdFontFamily notosans12FontFamily(&notosans12RegularFont, &notosans12BoldFont, EpdFontFamily notosans12FontFamily(&notosans12RegularFont, &notosans12BoldFont, &notosans12ItalicFont,
&notosans12ItalicFont,
&notosans12BoldItalicFont); &notosans12BoldItalicFont);
EpdFont notosans14RegularFont(&notosans_14_regular); EpdFont notosans14RegularFont(&notosans_14_regular);
EpdFont notosans14BoldFont(&notosans_14_bold); EpdFont notosans14BoldFont(&notosans_14_bold);
EpdFont notosans14ItalicFont(&notosans_14_italic); EpdFont notosans14ItalicFont(&notosans_14_italic);
EpdFont notosans14BoldItalicFont(&notosans_14_bolditalic); EpdFont notosans14BoldItalicFont(&notosans_14_bolditalic);
EpdFontFamily notosans14FontFamily(&notosans14RegularFont, &notosans14BoldFont, EpdFontFamily notosans14FontFamily(&notosans14RegularFont, &notosans14BoldFont, &notosans14ItalicFont,
&notosans14ItalicFont,
&notosans14BoldItalicFont); &notosans14BoldItalicFont);
EpdFont notosans16RegularFont(&notosans_16_regular); EpdFont notosans16RegularFont(&notosans_16_regular);
EpdFont notosans16BoldFont(&notosans_16_bold); EpdFont notosans16BoldFont(&notosans_16_bold);
EpdFont notosans16ItalicFont(&notosans_16_italic); EpdFont notosans16ItalicFont(&notosans_16_italic);
EpdFont notosans16BoldItalicFont(&notosans_16_bolditalic); EpdFont notosans16BoldItalicFont(&notosans_16_bolditalic);
EpdFontFamily notosans16FontFamily(&notosans16RegularFont, &notosans16BoldFont, EpdFontFamily notosans16FontFamily(&notosans16RegularFont, &notosans16BoldFont, &notosans16ItalicFont,
&notosans16ItalicFont,
&notosans16BoldItalicFont); &notosans16BoldItalicFont);
EpdFont notosans18RegularFont(&notosans_18_regular); EpdFont notosans18RegularFont(&notosans_18_regular);
EpdFont notosans18BoldFont(&notosans_18_bold); EpdFont notosans18BoldFont(&notosans_18_bold);
EpdFont notosans18ItalicFont(&notosans_18_italic); EpdFont notosans18ItalicFont(&notosans_18_italic);
EpdFont notosans18BoldItalicFont(&notosans_18_bolditalic); EpdFont notosans18BoldItalicFont(&notosans_18_bolditalic);
EpdFontFamily notosans18FontFamily(&notosans18RegularFont, &notosans18BoldFont, EpdFontFamily notosans18FontFamily(&notosans18RegularFont, &notosans18BoldFont, &notosans18ItalicFont,
&notosans18ItalicFont,
&notosans18BoldItalicFont); &notosans18BoldItalicFont);
EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular); EpdFont opendyslexic8RegularFont(&opendyslexic_8_regular);
EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold); EpdFont opendyslexic8BoldFont(&opendyslexic_8_bold);
EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic); EpdFont opendyslexic8ItalicFont(&opendyslexic_8_italic);
EpdFont opendyslexic8BoldItalicFont(&opendyslexic_8_bolditalic); EpdFont opendyslexic8BoldItalicFont(&opendyslexic_8_bolditalic);
EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont, EpdFontFamily opendyslexic8FontFamily(&opendyslexic8RegularFont, &opendyslexic8BoldFont, &opendyslexic8ItalicFont,
&opendyslexic8BoldFont,
&opendyslexic8ItalicFont,
&opendyslexic8BoldItalicFont); &opendyslexic8BoldItalicFont);
EpdFont opendyslexic10RegularFont(&opendyslexic_10_regular); EpdFont opendyslexic10RegularFont(&opendyslexic_10_regular);
EpdFont opendyslexic10BoldFont(&opendyslexic_10_bold); EpdFont opendyslexic10BoldFont(&opendyslexic_10_bold);
EpdFont opendyslexic10ItalicFont(&opendyslexic_10_italic); EpdFont opendyslexic10ItalicFont(&opendyslexic_10_italic);
EpdFont opendyslexic10BoldItalicFont(&opendyslexic_10_bolditalic); EpdFont opendyslexic10BoldItalicFont(&opendyslexic_10_bolditalic);
EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont, EpdFontFamily opendyslexic10FontFamily(&opendyslexic10RegularFont, &opendyslexic10BoldFont, &opendyslexic10ItalicFont,
&opendyslexic10BoldFont,
&opendyslexic10ItalicFont,
&opendyslexic10BoldItalicFont); &opendyslexic10BoldItalicFont);
EpdFont opendyslexic12RegularFont(&opendyslexic_12_regular); EpdFont opendyslexic12RegularFont(&opendyslexic_12_regular);
EpdFont opendyslexic12BoldFont(&opendyslexic_12_bold); EpdFont opendyslexic12BoldFont(&opendyslexic_12_bold);
EpdFont opendyslexic12ItalicFont(&opendyslexic_12_italic); EpdFont opendyslexic12ItalicFont(&opendyslexic_12_italic);
EpdFont opendyslexic12BoldItalicFont(&opendyslexic_12_bolditalic); EpdFont opendyslexic12BoldItalicFont(&opendyslexic_12_bolditalic);
EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont, EpdFontFamily opendyslexic12FontFamily(&opendyslexic12RegularFont, &opendyslexic12BoldFont, &opendyslexic12ItalicFont,
&opendyslexic12BoldFont,
&opendyslexic12ItalicFont,
&opendyslexic12BoldItalicFont); &opendyslexic12BoldItalicFont);
EpdFont opendyslexic14RegularFont(&opendyslexic_14_regular); EpdFont opendyslexic14RegularFont(&opendyslexic_14_regular);
EpdFont opendyslexic14BoldFont(&opendyslexic_14_bold); EpdFont opendyslexic14BoldFont(&opendyslexic_14_bold);
EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic); EpdFont opendyslexic14ItalicFont(&opendyslexic_14_italic);
EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic); EpdFont opendyslexic14BoldItalicFont(&opendyslexic_14_bolditalic);
EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont, EpdFontFamily opendyslexic14FontFamily(&opendyslexic14RegularFont, &opendyslexic14BoldFont, &opendyslexic14ItalicFont,
&opendyslexic14BoldFont,
&opendyslexic14ItalicFont,
&opendyslexic14BoldItalicFont); &opendyslexic14BoldItalicFont);
EpdFont smallFont(&notosans_8_regular); EpdFont smallFont(&notosans_8_regular);
@ -159,33 +143,27 @@ void exitActivity() {
} }
} }
void enterNewActivity(Activity *activity) { void enterNewActivity(Activity* activity) {
currentActivity = activity; currentActivity = activity;
currentActivity->onEnter(); currentActivity->onEnter();
} }
// Verify long press on wake-up from deep sleep // Verify long press on wake-up from deep sleep
void verifyWakeupLongPress() { void verifyWakeupLongPress() {
// Give the user up to 1000ms to start holding the power button, and must hold // Give the user up to 1000ms to start holding the power button, and must hold for SETTINGS.getPowerButtonDuration()
// for SETTINGS.getPowerButtonDuration()
const auto start = millis(); const auto start = millis();
bool abort = false; bool abort = false;
// Subtract the current time, because inputManager only starts counting the // Subtract the current time, because inputManager only starts counting the HeldTime from the first update()
// HeldTime from the first update() This way, we remove the time we already // This way, we remove the time we already took to reach here from the duration,
// took to reach here from the duration, assuming the button was held until // assuming the button was held until now from millis()==0 (i.e. device start time).
// now from millis()==0 (i.e. device start time).
const uint16_t calibration = start; const uint16_t calibration = start;
const uint16_t calibratedPressDuration = const uint16_t calibratedPressDuration =
(calibration < SETTINGS.getPowerButtonDuration()) (calibration < SETTINGS.getPowerButtonDuration()) ? SETTINGS.getPowerButtonDuration() - calibration : 1;
? SETTINGS.getPowerButtonDuration() - calibration
: 1;
inputManager.update(); inputManager.update();
// Verify the user has actually pressed // Verify the user has actually pressed
while (!inputManager.isPressed(InputManager::BTN_POWER) && while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
millis() - start < 1000) { delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration.
delay(10); // only wait 10ms each iteration to not delay too much in case of
// short configured duration.
inputManager.update(); inputManager.update();
} }
@ -194,8 +172,7 @@ void verifyWakeupLongPress() {
do { do {
delay(10); delay(10);
inputManager.update(); inputManager.update();
} while (inputManager.isPressed(InputManager::BTN_POWER) && } while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < calibratedPressDuration);
inputManager.getHeldTime() < calibratedPressDuration);
abort = inputManager.getHeldTime() < calibratedPressDuration; abort = inputManager.getHeldTime() < calibratedPressDuration;
} else { } else {
abort = true; abort = true;
@ -204,8 +181,7 @@ void verifyWakeupLongPress() {
if (abort) { if (abort) {
// Button released too early. Returning to sleep. // Button released too early. Returning to sleep.
// IMPORTANT: Re-arm the wakeup trigger before sleeping again // IMPORTANT: Re-arm the wakeup trigger before sleeping again
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
ESP_GPIO_WAKEUP_GPIO_LOW);
esp_deep_sleep_start(); esp_deep_sleep_start();
} }
} }
@ -224,55 +200,46 @@ void enterDeepSleep() {
enterNewActivity(new SleepActivity(renderer, mappedInputManager)); enterNewActivity(new SleepActivity(renderer, mappedInputManager));
einkDisplay.deepSleep(); einkDisplay.deepSleep();
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
millis(), t2 - t1);
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis()); Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
ESP_GPIO_WAKEUP_GPIO_LOW); // Ensure that the power button has been released to avoid immediately turning back on if you're holding it
// Ensure that the power button has been released to avoid immediately turning
// back on if you're holding it
waitForPowerRelease(); waitForPowerRelease();
// Enter Deep Sleep // Enter Deep Sleep
esp_deep_sleep_start(); esp_deep_sleep_start();
} }
void onGoHome(); void onGoHome();
void onGoToReader(const std::string &initialEpubPath) { void onGoToReader(const std::string& initialEpubPath) {
exitActivity(); exitActivity();
enterNewActivity(new ReaderActivity(renderer, mappedInputManager, enterNewActivity(new ReaderActivity(renderer, mappedInputManager, initialEpubPath, onGoHome));
initialEpubPath, onGoHome));
} }
void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); } void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); }
void onGoToFileTransfer() { void onGoToFileTransfer() {
exitActivity(); exitActivity();
enterNewActivity( enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
} }
void onGoToSettings() { void onGoToSettings() {
exitActivity(); exitActivity();
enterNewActivity( enterNewActivity(new SettingsActivity(renderer, mappedInputManager, onGoHome));
new SettingsActivity(renderer, mappedInputManager, onGoHome));
} }
void onGoToMyLibrary() { void onGoToMyLibrary() {
exitActivity(); exitActivity();
enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, enterNewActivity(new MyLibraryActivity(renderer, mappedInputManager, onGoHome, onGoToReader));
onGoToReader));
} }
void onGoToBrowser() { void onGoToBrowser() {
exitActivity(); exitActivity();
enterNewActivity( enterNewActivity(new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome));
new OpdsBookBrowserActivity(renderer, mappedInputManager, onGoHome));
} }
void onGoHome() { void onGoHome() {
exitActivity(); exitActivity();
enterNewActivity(new HomeActivity( enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToMyLibrary, onGoToSettings,
renderer, mappedInputManager, onContinueReading, onGoToMyLibrary, onGoToFileTransfer, onGoToBrowser));
onGoToSettings, onGoToFileTransfer, onGoToBrowser));
} }
void setupDisplayAndFonts() { void setupDisplayAndFonts() {
@ -318,8 +285,7 @@ void setup() {
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis()); Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
setupDisplayAndFonts(); setupDisplayAndFonts();
exitActivity(); exitActivity();
enterNewActivity(new FullScreenMessageActivity( enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
renderer, mappedInputManager, "SD card error", EpdFontFamily::BOLD));
return; return;
} }
@ -328,11 +294,8 @@ void setup() {
// verify power button press duration after we've read settings. // verify power button press duration after we've read settings.
verifyWakeupLongPress(); verifyWakeupLongPress();
// First serial output only here to avoid timing inconsistencies for power // First serial output only here to avoid timing inconsistencies for power button press duration verification
// button press duration verification Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION
"\n",
millis());
setupDisplayAndFonts(); setupDisplayAndFonts();
@ -345,8 +308,7 @@ void setup() {
if (APP_STATE.openEpubPath.empty()) { if (APP_STATE.openEpubPath.empty()) {
onGoHome(); onGoHome();
} else { } else {
// Clear app state to avoid getting into a boot loop if the epub doesn't // Clear app state to avoid getting into a boot loop if the epub doesn't load
// load
const auto path = APP_STATE.openEpubPath; const auto path = APP_STATE.openEpubPath;
APP_STATE.openEpubPath = ""; APP_STATE.openEpubPath = "";
APP_STATE.saveToFile(); APP_STATE.saveToFile();
@ -365,25 +327,21 @@ void loop() {
inputManager.update(); inputManager.update();
if (Serial && millis() - lastMemPrint >= 10000) { if (Serial && millis() - lastMemPrint >= 10000) {
Serial.printf( Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
"[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", ESP.getHeapSize(), ESP.getMinFreeHeap());
millis(), ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getMinFreeHeap());
lastMemPrint = millis(); lastMemPrint = millis();
} }
// Check for any user activity (button press or release) or active background // Check for any user activity (button press or release) or active background work
// work
static unsigned long lastActivityTime = millis(); static unsigned long lastActivityTime = millis();
if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased() || if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased() ||
(currentActivity && currentActivity->preventAutoSleep())) { (currentActivity && currentActivity->preventAutoSleep())) {
lastActivityTime = millis(); // Reset inactivity timer lastActivityTime = millis(); // Reset inactivity timer
} }
const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs(); const unsigned long sleepTimeoutMs = SETTINGS.getSleepTimeoutMs();
if (millis() - lastActivityTime >= sleepTimeoutMs) { if (millis() - lastActivityTime >= sleepTimeoutMs) {
Serial.printf( Serial.printf("[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n", millis(), sleepTimeoutMs);
"[%lu] [SLP] Auto-sleep triggered after %lu ms of inactivity\n",
millis(), sleepTimeoutMs);
enterDeepSleep(); enterDeepSleep();
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start // This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
return; return;
@ -406,18 +364,17 @@ void loop() {
if (loopDuration > maxLoopDuration) { if (loopDuration > maxLoopDuration) {
maxLoopDuration = loopDuration; maxLoopDuration = loopDuration;
if (maxLoopDuration > 50) { if (maxLoopDuration > 50) {
Serial.printf( Serial.printf("[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", millis(), maxLoopDuration,
"[%lu] [LOOP] New max loop duration: %lu ms (activity: %lu ms)\n", activityDuration);
millis(), maxLoopDuration, activityDuration);
} }
} }
// Add delay at the end of the loop to prevent tight spinning // Add delay at the end of the loop to prevent tight spinning
// When an activity requests skip loop delay (e.g., webserver running), use // When an activity requests skip loop delay (e.g., webserver running), use yield() for faster response
// yield() for faster response Otherwise, use longer delay to save power // Otherwise, use longer delay to save power
if (currentActivity && currentActivity->skipLoopDelay()) { if (currentActivity && currentActivity->skipLoopDelay()) {
yield(); // Give FreeRTOS a chance to run tasks, but return immediately yield(); // Give FreeRTOS a chance to run tasks, but return immediately
} else { } else {
delay(10); // Normal delay when no activity requires fast response delay(10); // Normal delay when no activity requires fast response
} }
} }