mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-18 23:27:44 +03:00
Rename Screens to Activities and restructure files (#44)
## Summary
* This PR drastically reshapes the structure of the codebase, moving
from the concept of "Screens" to "Activities", restructing the files and
setting up the concept of subactivities.
* This should help with keep the main file clean and containing all
functional logic in the relevant activity.
* CrossPointState is now also a global singleton which should help with
accessing it from within activities.
## Additional Context
* This is probably going to be a bit disruptive for people with open
PRs, sorry 😞
This commit is contained in:
parent
c78f2a9840
commit
d429966dd4
@ -3,7 +3,9 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t PAGE_FILE_VERSION = 3;
|
constexpr uint8_t PAGE_FILE_VERSION = 3;
|
||||||
|
}
|
||||||
|
|
||||||
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,9 @@
|
|||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 5;
|
constexpr uint8_t SECTION_FILE_VERSION = 5;
|
||||||
|
}
|
||||||
|
|
||||||
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
||||||
|
|||||||
@ -10,9 +10,11 @@
|
|||||||
// Initialize the static instance
|
// Initialize the static instance
|
||||||
CrossPointSettings CrossPointSettings::instance;
|
CrossPointSettings CrossPointSettings::instance;
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
constexpr uint8_t SETTINGS_COUNT = 2;
|
constexpr uint8_t SETTINGS_COUNT = 2;
|
||||||
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool CrossPointSettings::saveToFile() const {
|
bool CrossPointSettings::saveToFile() const {
|
||||||
// Make sure the directory exists
|
// Make sure the directory exists
|
||||||
|
|||||||
@ -6,8 +6,12 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t STATE_FILE_VERSION = 1;
|
constexpr uint8_t STATE_FILE_VERSION = 1;
|
||||||
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CrossPointState CrossPointState::instance;
|
||||||
|
|
||||||
bool CrossPointState::saveToFile() const {
|
bool CrossPointState::saveToFile() const {
|
||||||
std::ofstream outputFile(STATE_FILE);
|
std::ofstream outputFile(STATE_FILE);
|
||||||
|
|||||||
@ -3,11 +3,20 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class CrossPointState {
|
class CrossPointState {
|
||||||
|
// Static instance
|
||||||
|
static CrossPointState instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string openEpubPath;
|
std::string openEpubPath;
|
||||||
~CrossPointState() = default;
|
~CrossPointState() = default;
|
||||||
|
|
||||||
|
// Get singleton instance
|
||||||
|
static CrossPointState& getInstance() { return instance; }
|
||||||
|
|
||||||
bool saveToFile() const;
|
bool saveToFile() const;
|
||||||
|
|
||||||
bool loadFromFile();
|
bool loadFromFile();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper macro to access settings
|
||||||
|
#define APP_STATE CrossPointState::getInstance()
|
||||||
|
|||||||
18
src/activities/Activity.h
Normal file
18
src/activities/Activity.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <InputManager.h>
|
||||||
|
|
||||||
|
class GfxRenderer;
|
||||||
|
|
||||||
|
class Activity {
|
||||||
|
protected:
|
||||||
|
GfxRenderer& renderer;
|
||||||
|
InputManager& inputManager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Activity(GfxRenderer& renderer, InputManager& inputManager)
|
||||||
|
: renderer(renderer), inputManager(inputManager) {}
|
||||||
|
virtual ~Activity() = default;
|
||||||
|
virtual void onEnter() {}
|
||||||
|
virtual void onExit() {}
|
||||||
|
virtual void loop() {}
|
||||||
|
};
|
||||||
21
src/activities/ActivityWithSubactivity.cpp
Normal file
21
src/activities/ActivityWithSubactivity.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::exitActivity() {
|
||||||
|
if (subActivity) {
|
||||||
|
subActivity->onExit();
|
||||||
|
subActivity.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::enterNewActivity(Activity* activity) {
|
||||||
|
subActivity.reset(activity);
|
||||||
|
subActivity->onEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::loop() {
|
||||||
|
if (subActivity) {
|
||||||
|
subActivity->loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::onExit() { exitActivity(); }
|
||||||
17
src/activities/ActivityWithSubactivity.h
Normal file
17
src/activities/ActivityWithSubactivity.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Activity.h"
|
||||||
|
|
||||||
|
class ActivityWithSubactivity : public Activity {
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Activity> subActivity = nullptr;
|
||||||
|
void exitActivity();
|
||||||
|
void enterNewActivity(Activity* activity);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ActivityWithSubactivity(GfxRenderer& renderer, InputManager& inputManager)
|
||||||
|
: Activity(renderer, inputManager) {}
|
||||||
|
void loop() override;
|
||||||
|
void onExit() override;
|
||||||
|
};
|
||||||
@ -1,11 +1,11 @@
|
|||||||
#include "BootLogoScreen.h"
|
#include "BootActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void BootLogoScreen::onEnter() {
|
void BootActivity::onEnter() {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
8
src/activities/boot_sleep/BootActivity.h
Normal file
8
src/activities/boot_sleep/BootActivity.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class BootActivity final : public Activity {
|
||||||
|
public:
|
||||||
|
explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "SleepScreen.h"
|
#include "SleepActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
@ -6,7 +6,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void SleepScreen::onEnter() {
|
void SleepActivity::onEnter() {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
8
src/activities/boot_sleep/SleepActivity.h
Normal file
8
src/activities/boot_sleep/SleepActivity.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class SleepActivity final : public Activity {
|
||||||
|
public:
|
||||||
|
explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
@ -1,16 +1,20 @@
|
|||||||
#include "HomeScreen.h"
|
#include "HomeActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
void HomeScreen::taskTrampoline(void* param) {
|
namespace {
|
||||||
auto* self = static_cast<HomeScreen*>(param);
|
constexpr int menuItemCount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::taskTrampoline(void* param) {
|
||||||
|
auto* self = static_cast<HomeActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeScreen::onEnter() {
|
void HomeActivity::onEnter() {
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
@ -18,7 +22,7 @@ void HomeScreen::onEnter() {
|
|||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&HomeScreen::taskTrampoline, "HomeScreenTask",
|
xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -26,7 +30,7 @@ void HomeScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeScreen::onExit() {
|
void HomeActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -37,7 +41,7 @@ void HomeScreen::onExit() {
|
|||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeScreen::handleInput() {
|
void HomeActivity::loop() {
|
||||||
const bool prevPressed =
|
const bool prevPressed =
|
||||||
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
||||||
const bool nextPressed =
|
const bool nextPressed =
|
||||||
@ -45,7 +49,7 @@ void HomeScreen::handleInput() {
|
|||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
if (selectorIndex == 0) {
|
if (selectorIndex == 0) {
|
||||||
onFileSelectionOpen();
|
onReaderOpen();
|
||||||
} else if (selectorIndex == 1) {
|
} else if (selectorIndex == 1) {
|
||||||
onSettingsOpen();
|
onSettingsOpen();
|
||||||
}
|
}
|
||||||
@ -58,7 +62,7 @@ void HomeScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeScreen::displayTaskLoop() {
|
void HomeActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -70,7 +74,7 @@ void HomeScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeScreen::render() const {
|
void HomeActivity::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
29
src/activities/home/HomeActivity.h
Normal file
29
src/activities/home/HomeActivity.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class HomeActivity final : public Activity {
|
||||||
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
int selectorIndex = 0;
|
||||||
|
bool updateRequired = false;
|
||||||
|
const std::function<void()> onReaderOpen;
|
||||||
|
const std::function<void()> onSettingsOpen;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* param);
|
||||||
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
void render() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onReaderOpen,
|
||||||
|
const std::function<void()>& onSettingsOpen)
|
||||||
|
: Activity(renderer, inputManager), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen) {}
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "EpubReaderScreen.h"
|
#include "EpubReaderActivity.h"
|
||||||
|
|
||||||
#include <Epub/Page.h>
|
#include <Epub/Page.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
@ -6,23 +6,25 @@
|
|||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "EpubReaderChapterSelectionScreen.h"
|
#include "EpubReaderChapterSelectionActivity.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
constexpr int PAGES_PER_REFRESH = 15;
|
namespace {
|
||||||
constexpr unsigned long SKIP_CHAPTER_MS = 700;
|
constexpr int pagesPerRefresh = 15;
|
||||||
|
constexpr unsigned long skipChapterMs = 700;
|
||||||
constexpr float lineCompression = 0.95f;
|
constexpr float lineCompression = 0.95f;
|
||||||
constexpr int marginTop = 8;
|
constexpr int marginTop = 8;
|
||||||
constexpr int marginRight = 10;
|
constexpr int marginRight = 10;
|
||||||
constexpr int marginBottom = 22;
|
constexpr int marginBottom = 22;
|
||||||
constexpr int marginLeft = 10;
|
constexpr int marginLeft = 10;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void EpubReaderScreen::taskTrampoline(void* param) {
|
void EpubReaderActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<EpubReaderScreen*>(param);
|
auto* self = static_cast<EpubReaderActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::onEnter() {
|
void EpubReaderActivity::onEnter() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -44,7 +46,7 @@ void EpubReaderScreen::onEnter() {
|
|||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&EpubReaderScreen::taskTrampoline, "EpubReaderScreenTask",
|
xTaskCreate(&EpubReaderActivity::taskTrampoline, "EpubReaderActivityTask",
|
||||||
8192, // Stack size
|
8192, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -52,7 +54,7 @@ void EpubReaderScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::onExit() {
|
void EpubReaderActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -65,22 +67,22 @@ void EpubReaderScreen::onExit() {
|
|||||||
epub.reset();
|
epub.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::handleInput() {
|
void EpubReaderActivity::loop() {
|
||||||
// Pass input responsibility to sub screen if exists
|
// Pass input responsibility to sub activity if exists
|
||||||
if (subScreen) {
|
if (subAcitivity) {
|
||||||
subScreen->handleInput();
|
subAcitivity->loop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter chapter selection screen
|
// Enter chapter selection activity
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
// Don't start screen transition while rendering
|
// Don't start activity transition while rendering
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
subScreen.reset(new EpubReaderChapterSelectionScreen(
|
subAcitivity.reset(new EpubReaderChapterSelectionActivity(
|
||||||
this->renderer, this->inputManager, epub, currentSpineIndex,
|
this->renderer, this->inputManager, epub, currentSpineIndex,
|
||||||
[this] {
|
[this] {
|
||||||
subScreen->onExit();
|
subAcitivity->onExit();
|
||||||
subScreen.reset();
|
subAcitivity.reset();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
},
|
},
|
||||||
[this](const int newSpineIndex) {
|
[this](const int newSpineIndex) {
|
||||||
@ -89,11 +91,11 @@ void EpubReaderScreen::handleInput() {
|
|||||||
nextPageNumber = 0;
|
nextPageNumber = 0;
|
||||||
section.reset();
|
section.reset();
|
||||||
}
|
}
|
||||||
subScreen->onExit();
|
subAcitivity->onExit();
|
||||||
subScreen.reset();
|
subAcitivity.reset();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
subScreen->onEnter();
|
subAcitivity->onEnter();
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,7 +121,7 @@ void EpubReaderScreen::handleInput() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skipChapter = inputManager.getHeldTime() > SKIP_CHAPTER_MS;
|
const bool skipChapter = inputManager.getHeldTime() > skipChapterMs;
|
||||||
|
|
||||||
if (skipChapter) {
|
if (skipChapter) {
|
||||||
// We don't want to delete the section mid-render, so grab the semaphore
|
// We don't want to delete the section mid-render, so grab the semaphore
|
||||||
@ -165,7 +167,7 @@ void EpubReaderScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::displayTaskLoop() {
|
void EpubReaderActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -178,7 +180,7 @@ void EpubReaderScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Failure handling
|
// TODO: Failure handling
|
||||||
void EpubReaderScreen::renderScreen() {
|
void EpubReaderActivity::renderScreen() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -285,12 +287,12 @@ void EpubReaderScreen::renderScreen() {
|
|||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderContents(std::unique_ptr<Page> page) {
|
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page) {
|
||||||
page->render(renderer, READER_FONT_ID);
|
page->render(renderer, READER_FONT_ID);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
pagesUntilFullRefresh = pagesPerRefresh;
|
||||||
} else {
|
} else {
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
@ -322,7 +324,7 @@ void EpubReaderScreen::renderContents(std::unique_ptr<Page> page) {
|
|||||||
renderer.restoreBwBuffer();
|
renderer.restoreBwBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderStatusBar() const {
|
void EpubReaderActivity::renderStatusBar() const {
|
||||||
constexpr auto textY = 776;
|
constexpr auto textY = 776;
|
||||||
|
|
||||||
// Calculate progress in book
|
// Calculate progress in book
|
||||||
@ -5,14 +5,14 @@
|
|||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class EpubReaderScreen final : public Screen {
|
class EpubReaderActivity final : public Activity {
|
||||||
std::shared_ptr<Epub> epub;
|
std::shared_ptr<Epub> epub;
|
||||||
std::unique_ptr<Section> section = nullptr;
|
std::unique_ptr<Section> section = nullptr;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
std::unique_ptr<Screen> subScreen = nullptr;
|
std::unique_ptr<Activity> subAcitivity = nullptr;
|
||||||
int currentSpineIndex = 0;
|
int currentSpineIndex = 0;
|
||||||
int nextPageNumber = 0;
|
int nextPageNumber = 0;
|
||||||
int pagesUntilFullRefresh = 0;
|
int pagesUntilFullRefresh = 0;
|
||||||
@ -26,10 +26,10 @@ class EpubReaderScreen final : public Screen {
|
|||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub,
|
explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub,
|
||||||
const std::function<void()>& onGoBack)
|
const std::function<void()>& onGoBack)
|
||||||
: Screen(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
|
: Activity(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "EpubReaderChapterSelectionScreen.h"
|
#include "EpubReaderChapterSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
@ -8,12 +8,12 @@
|
|||||||
constexpr int PAGE_ITEMS = 24;
|
constexpr int PAGE_ITEMS = 24;
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
constexpr int SKIP_PAGE_MS = 700;
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::taskTrampoline(void* param) {
|
void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<EpubReaderChapterSelectionScreen*>(param);
|
auto* self = static_cast<EpubReaderChapterSelectionActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::onEnter() {
|
void EpubReaderChapterSelectionActivity::onEnter() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ void EpubReaderChapterSelectionScreen::onEnter() {
|
|||||||
|
|
||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
xTaskCreate(&EpubReaderChapterSelectionScreen::taskTrampoline, "EpubReaderChapterSelectionScreenTask",
|
xTaskCreate(&EpubReaderChapterSelectionActivity::taskTrampoline, "EpubReaderChapterSelectionActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -31,7 +31,7 @@ void EpubReaderChapterSelectionScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::onExit() {
|
void EpubReaderChapterSelectionActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -42,7 +42,7 @@ void EpubReaderChapterSelectionScreen::onExit() {
|
|||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::handleInput() {
|
void EpubReaderChapterSelectionActivity::loop() {
|
||||||
const bool prevReleased =
|
const bool prevReleased =
|
||||||
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
||||||
const bool nextReleased =
|
const bool nextReleased =
|
||||||
@ -72,7 +72,7 @@ void EpubReaderChapterSelectionScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::displayTaskLoop() {
|
void EpubReaderChapterSelectionActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -84,7 +84,7 @@ void EpubReaderChapterSelectionScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::renderScreen() {
|
void EpubReaderChapterSelectionActivity::renderScreen() {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class EpubReaderChapterSelectionScreen final : public Screen {
|
class EpubReaderChapterSelectionActivity final : public Activity {
|
||||||
std::shared_ptr<Epub> epub;
|
std::shared_ptr<Epub> epub;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
@ -23,16 +23,16 @@ class EpubReaderChapterSelectionScreen final : public Screen {
|
|||||||
void renderScreen();
|
void renderScreen();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderChapterSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
|
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
const std::shared_ptr<Epub>& epub, const int currentSpineIndex,
|
const std::shared_ptr<Epub>& epub, const int currentSpineIndex,
|
||||||
const std::function<void()>& onGoBack,
|
const std::function<void()>& onGoBack,
|
||||||
const std::function<void(int newSpineIndex)>& onSelectSpineIndex)
|
const std::function<void(int newSpineIndex)>& onSelectSpineIndex)
|
||||||
: Screen(renderer, inputManager),
|
: Activity(renderer, inputManager),
|
||||||
epub(epub),
|
epub(epub),
|
||||||
currentSpineIndex(currentSpineIndex),
|
currentSpineIndex(currentSpineIndex),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
onSelectSpineIndex(onSelectSpineIndex) {}
|
onSelectSpineIndex(onSelectSpineIndex) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "FileSelectionScreen.h"
|
#include "FileSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
@ -15,12 +15,12 @@ void sortFileList(std::vector<std::string>& strs) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::taskTrampoline(void* param) {
|
void FileSelectionActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<FileSelectionScreen*>(param);
|
auto* self = static_cast<FileSelectionActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::loadFiles() {
|
void FileSelectionActivity::loadFiles() {
|
||||||
files.clear();
|
files.clear();
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
auto root = SD.open(basepath.c_str());
|
auto root = SD.open(basepath.c_str());
|
||||||
@ -42,7 +42,7 @@ void FileSelectionScreen::loadFiles() {
|
|||||||
sortFileList(files);
|
sortFileList(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::onEnter() {
|
void FileSelectionActivity::onEnter() {
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
basepath = "/";
|
basepath = "/";
|
||||||
@ -52,7 +52,7 @@ void FileSelectionScreen::onEnter() {
|
|||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask",
|
xTaskCreate(&FileSelectionActivity::taskTrampoline, "FileSelectionActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -60,7 +60,7 @@ void FileSelectionScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::onExit() {
|
void FileSelectionActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -72,7 +72,7 @@ void FileSelectionScreen::onExit() {
|
|||||||
files.clear();
|
files.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::handleInput() {
|
void FileSelectionActivity::loop() {
|
||||||
const bool prevPressed =
|
const bool prevPressed =
|
||||||
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
||||||
const bool nextPressed =
|
const bool nextPressed =
|
||||||
@ -110,7 +110,7 @@ void FileSelectionScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::displayTaskLoop() {
|
void FileSelectionActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -122,7 +122,7 @@ void FileSelectionScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::render() const {
|
void FileSelectionActivity::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
@ -7,9 +7,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class FileSelectionScreen final : public Screen {
|
class FileSelectionActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
std::string basepath = "/";
|
std::string basepath = "/";
|
||||||
@ -25,11 +25,11 @@ class FileSelectionScreen final : public Screen {
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
|
explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
const std::function<void(const std::string&)>& onSelect,
|
const std::function<void(const std::string&)>& onSelect,
|
||||||
const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoHome)
|
||||||
: Screen(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
|
: Activity(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
68
src/activities/reader/ReaderActivity.cpp
Normal file
68
src/activities/reader/ReaderActivity.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "ReaderActivity.h"
|
||||||
|
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
#include "CrossPointState.h"
|
||||||
|
#include "Epub.h"
|
||||||
|
#include "EpubReaderActivity.h"
|
||||||
|
#include "FileSelectionActivity.h"
|
||||||
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
|
|
||||||
|
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
||||||
|
if (!SD.exists(path.c_str())) {
|
||||||
|
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint"));
|
||||||
|
if (epub->load()) {
|
||||||
|
return epub;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onSelectEpubFile(const std::string& path) {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading..."));
|
||||||
|
|
||||||
|
auto epub = loadEpub(path);
|
||||||
|
if (epub) {
|
||||||
|
APP_STATE.openEpubPath = path;
|
||||||
|
APP_STATE.saveToFile();
|
||||||
|
onGoToEpubReader(std::move(epub));
|
||||||
|
} else {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load epub", REGULAR,
|
||||||
|
EInkDisplay::HALF_REFRESH));
|
||||||
|
delay(2000);
|
||||||
|
onGoToFileSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onGoToFileSelection() {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FileSelectionActivity(
|
||||||
|
renderer, inputManager, [this](const std::string& path) { onSelectEpubFile(path); }, onGoBack));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new EpubReaderActivity(renderer, inputManager, std::move(epub), [this] { onGoToFileSelection(); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onEnter() {
|
||||||
|
if (initialEpubPath.empty()) {
|
||||||
|
onGoToFileSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto epub = loadEpub(initialEpubPath);
|
||||||
|
if (!epub) {
|
||||||
|
onGoBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onGoToEpubReader(std::move(epub));
|
||||||
|
}
|
||||||
24
src/activities/reader/ReaderActivity.h
Normal file
24
src/activities/reader/ReaderActivity.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "../ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
class Epub;
|
||||||
|
|
||||||
|
class ReaderActivity final : public ActivityWithSubactivity {
|
||||||
|
std::string initialEpubPath;
|
||||||
|
const std::function<void()> onGoBack;
|
||||||
|
static std::unique_ptr<Epub> loadEpub(const std::string& path);
|
||||||
|
|
||||||
|
void onSelectEpubFile(const std::string& path);
|
||||||
|
void onGoToFileSelection();
|
||||||
|
void onGoToEpubReader(std::unique_ptr<Epub> epub);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::string initialEpubPath,
|
||||||
|
const std::function<void()>& onGoBack)
|
||||||
|
: ActivityWithSubactivity(renderer, inputManager),
|
||||||
|
initialEpubPath(std::move(initialEpubPath)),
|
||||||
|
onGoBack(onGoBack) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "SettingsScreen.h"
|
#include "SettingsActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
@ -7,16 +7,16 @@
|
|||||||
|
|
||||||
// Define the static settings list
|
// Define the static settings list
|
||||||
|
|
||||||
const SettingInfo SettingsScreen::settingsList[SettingsScreen::settingsCount] = {
|
const SettingInfo SettingsActivity::settingsList[settingsCount] = {
|
||||||
{"White Sleep Screen", &CrossPointSettings::whiteSleepScreen},
|
{"White Sleep Screen", &CrossPointSettings::whiteSleepScreen},
|
||||||
{"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing}};
|
{"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing}};
|
||||||
|
|
||||||
void SettingsScreen::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<SettingsScreen*>(param);
|
auto* self = static_cast<SettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::onEnter() {
|
void SettingsActivity::onEnter() {
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
// Reset selection to first item
|
// Reset selection to first item
|
||||||
@ -25,7 +25,7 @@ void SettingsScreen::onEnter() {
|
|||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&SettingsScreen::taskTrampoline, "SettingsScreenTask",
|
xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -33,7 +33,7 @@ void SettingsScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::onExit() {
|
void SettingsActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -44,7 +44,7 @@ void SettingsScreen::onExit() {
|
|||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::handleInput() {
|
void SettingsActivity::loop() {
|
||||||
// Handle actions with early return
|
// Handle actions with early return
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
toggleCurrentSetting();
|
toggleCurrentSetting();
|
||||||
@ -70,7 +70,7 @@ void SettingsScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::toggleCurrentSetting() {
|
void SettingsActivity::toggleCurrentSetting() {
|
||||||
// Validate index
|
// Validate index
|
||||||
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
||||||
return;
|
return;
|
||||||
@ -84,7 +84,7 @@ void SettingsScreen::toggleCurrentSetting() {
|
|||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::displayTaskLoop() {
|
void SettingsActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -96,7 +96,7 @@ void SettingsScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::render() const {
|
void SettingsActivity::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
@ -7,7 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class CrossPointSettings;
|
class CrossPointSettings;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ struct SettingInfo {
|
|||||||
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings
|
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings
|
||||||
};
|
};
|
||||||
|
|
||||||
class SettingsScreen final : public Screen {
|
class SettingsActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
@ -34,9 +34,9 @@ class SettingsScreen final : public Screen {
|
|||||||
void toggleCurrentSetting();
|
void toggleCurrentSetting();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SettingsScreen(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome)
|
explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome)
|
||||||
: Screen(renderer, inputManager), onGoHome(onGoHome) {}
|
: Activity(renderer, inputManager), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#include "FullScreenMessageScreen.h"
|
#include "FullScreenMessageActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
void FullScreenMessageScreen::onEnter() {
|
void FullScreenMessageActivity::onEnter() {
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
|
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
|
||||||
|
|
||||||
21
src/activities/util/FullScreenMessageActivity.h
Normal file
21
src/activities/util/FullScreenMessageActivity.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
#include <EpdFontFamily.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class FullScreenMessageActivity final : public Activity {
|
||||||
|
std::string text;
|
||||||
|
EpdFontStyle style;
|
||||||
|
EInkDisplay::RefreshMode refreshMode;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FullScreenMessageActivity(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
||||||
|
const EpdFontStyle style = REGULAR,
|
||||||
|
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
||||||
|
: Activity(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
109
src/main.cpp
109
src/main.cpp
@ -16,14 +16,13 @@
|
|||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
#include "activities/boot_sleep/BootActivity.h"
|
||||||
|
#include "activities/boot_sleep/SleepActivity.h"
|
||||||
|
#include "activities/home/HomeActivity.h"
|
||||||
|
#include "activities/reader/ReaderActivity.h"
|
||||||
|
#include "activities/settings/SettingsActivity.h"
|
||||||
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "screens/BootLogoScreen.h"
|
|
||||||
#include "screens/EpubReaderScreen.h"
|
|
||||||
#include "screens/FileSelectionScreen.h"
|
|
||||||
#include "screens/FullScreenMessageScreen.h"
|
|
||||||
#include "screens/HomeScreen.h"
|
|
||||||
#include "screens/SettingsScreen.h"
|
|
||||||
#include "screens/SleepScreen.h"
|
|
||||||
|
|
||||||
#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)
|
||||||
@ -42,8 +41,7 @@
|
|||||||
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
||||||
InputManager inputManager;
|
InputManager inputManager;
|
||||||
GfxRenderer renderer(einkDisplay);
|
GfxRenderer renderer(einkDisplay);
|
||||||
Screen* currentScreen;
|
Activity* currentActivity;
|
||||||
CrossPointState appState;
|
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
EpdFont bookerlyFont(&bookerly_2b);
|
EpdFont bookerlyFont(&bookerly_2b);
|
||||||
@ -67,31 +65,16 @@ constexpr unsigned long POWER_BUTTON_SLEEP_MS = 500;
|
|||||||
// Auto-sleep timeout (10 minutes of inactivity)
|
// Auto-sleep timeout (10 minutes of inactivity)
|
||||||
constexpr unsigned long AUTO_SLEEP_TIMEOUT_MS = 10 * 60 * 1000;
|
constexpr unsigned long AUTO_SLEEP_TIMEOUT_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
std::unique_ptr<Epub> loadEpub(const std::string& path) {
|
void exitActivity() {
|
||||||
if (!SD.exists(path.c_str())) {
|
if (currentActivity) {
|
||||||
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
currentActivity->onExit();
|
||||||
return nullptr;
|
delete currentActivity;
|
||||||
}
|
|
||||||
|
|
||||||
auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint"));
|
|
||||||
if (epub->load()) {
|
|
||||||
return epub;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void exitScreen() {
|
|
||||||
if (currentScreen) {
|
|
||||||
currentScreen->onExit();
|
|
||||||
delete currentScreen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void enterNewScreen(Screen* screen) {
|
void enterNewActivity(Activity* activity) {
|
||||||
currentScreen = screen;
|
currentActivity = activity;
|
||||||
currentScreen->onEnter();
|
currentActivity->onEnter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify long press on wake-up from deep sleep
|
// Verify long press on wake-up from deep sleep
|
||||||
@ -135,8 +118,8 @@ void waitForPowerRelease() {
|
|||||||
|
|
||||||
// Enter deep sleep mode
|
// Enter deep sleep mode
|
||||||
void enterDeepSleep() {
|
void enterDeepSleep() {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new SleepScreen(renderer, inputManager));
|
enterNewActivity(new SleepActivity(renderer, inputManager));
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Power button released after a long press. Entering deep sleep.\n", millis());
|
Serial.printf("[%lu] [ ] Power button released after a long press. Entering deep sleep.\n", millis());
|
||||||
delay(1000); // Allow Serial buffer to empty and display to update
|
delay(1000); // Allow Serial buffer to empty and display to update
|
||||||
@ -151,39 +134,20 @@ void enterDeepSleep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome();
|
void onGoHome();
|
||||||
void onGoToFileSelection();
|
void onGoToReader(const std::string& initialEpubPath) {
|
||||||
void onSelectEpubFile(const std::string& path) {
|
exitActivity();
|
||||||
exitScreen();
|
enterNewActivity(new ReaderActivity(renderer, inputManager, initialEpubPath, onGoHome));
|
||||||
enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading..."));
|
|
||||||
|
|
||||||
auto epub = loadEpub(path);
|
|
||||||
if (epub) {
|
|
||||||
appState.openEpubPath = path;
|
|
||||||
appState.saveToFile();
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoToFileSelection));
|
|
||||||
} else {
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(
|
|
||||||
new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH));
|
|
||||||
delay(2000);
|
|
||||||
onGoToFileSelection();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onGoToFileSelection() {
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoHome));
|
|
||||||
}
|
}
|
||||||
|
void onGoToReaderHome() { onGoToReader(std::string()); }
|
||||||
|
|
||||||
void onGoToSettings() {
|
void onGoToSettings() {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new SettingsScreen(renderer, inputManager, onGoHome));
|
enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new HomeScreen(renderer, inputManager, onGoToFileSelection, onGoToSettings));
|
enterNewActivity(new HomeActivity(renderer, inputManager, onGoToReaderHome, onGoToSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
@ -209,27 +173,20 @@ void setup() {
|
|||||||
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
||||||
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
||||||
|
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
enterNewActivity(new BootActivity(renderer, inputManager));
|
||||||
|
|
||||||
// SD Card Initialization
|
// SD Card Initialization
|
||||||
SD.begin(SD_SPI_CS, SPI, SPI_FQ);
|
SD.begin(SD_SPI_CS, SPI, SPI_FQ);
|
||||||
|
|
||||||
SETTINGS.loadFromFile();
|
SETTINGS.loadFromFile();
|
||||||
appState.loadFromFile();
|
APP_STATE.loadFromFile();
|
||||||
if (!appState.openEpubPath.empty()) {
|
if (APP_STATE.openEpubPath.empty()) {
|
||||||
auto epub = loadEpub(appState.openEpubPath);
|
onGoHome();
|
||||||
if (epub) {
|
} else {
|
||||||
exitScreen();
|
onGoToReader(APP_STATE.openEpubPath);
|
||||||
enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoHome));
|
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
|
||||||
waitForPowerRelease();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onGoHome();
|
|
||||||
|
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
// Ensure we're not still holding the power button before leaving setup
|
||||||
waitForPowerRelease();
|
waitForPowerRelease();
|
||||||
}
|
}
|
||||||
@ -265,7 +222,7 @@ void loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentScreen) {
|
if (currentActivity) {
|
||||||
currentScreen->handleInput();
|
currentActivity->loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class BootLogoScreen final : public Screen {
|
|
||||||
public:
|
|
||||||
explicit BootLogoScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
|
||||||
void onEnter() override;
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <EInkDisplay.h>
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class FullScreenMessageScreen final : public Screen {
|
|
||||||
std::string text;
|
|
||||||
EpdFontStyle style;
|
|
||||||
EInkDisplay::RefreshMode refreshMode;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FullScreenMessageScreen(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
|
||||||
const EpdFontStyle style = REGULAR,
|
|
||||||
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
|
||||||
: Screen(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {}
|
|
||||||
void onEnter() override;
|
|
||||||
};
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <freertos/FreeRTOS.h>
|
|
||||||
#include <freertos/semphr.h>
|
|
||||||
#include <freertos/task.h>
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class HomeScreen final : public Screen {
|
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
|
||||||
int selectorIndex = 0;
|
|
||||||
bool updateRequired = false;
|
|
||||||
const std::function<void()> onFileSelectionOpen;
|
|
||||||
const std::function<void()> onSettingsOpen;
|
|
||||||
|
|
||||||
static constexpr int menuItemCount = 2;
|
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
|
||||||
[[noreturn]] void displayTaskLoop();
|
|
||||||
void render() const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit HomeScreen(GfxRenderer& renderer, InputManager& inputManager,
|
|
||||||
const std::function<void()>& onFileSelectionOpen, const std::function<void()>& onSettingsOpen)
|
|
||||||
: Screen(renderer, inputManager), onFileSelectionOpen(onFileSelectionOpen), onSettingsOpen(onSettingsOpen) {}
|
|
||||||
void onEnter() override;
|
|
||||||
void onExit() override;
|
|
||||||
void handleInput() override;
|
|
||||||
};
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <InputManager.h>
|
|
||||||
|
|
||||||
class GfxRenderer;
|
|
||||||
|
|
||||||
class Screen {
|
|
||||||
protected:
|
|
||||||
GfxRenderer& renderer;
|
|
||||||
InputManager& inputManager;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Screen(GfxRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
|
||||||
virtual ~Screen() = default;
|
|
||||||
virtual void onEnter() {}
|
|
||||||
virtual void onExit() {}
|
|
||||||
virtual void handleInput() {}
|
|
||||||
};
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class SleepScreen final : public Screen {
|
|
||||||
public:
|
|
||||||
explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
|
||||||
void onEnter() override;
|
|
||||||
};
|
|
||||||
Loading…
Reference in New Issue
Block a user