mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-08 08:37:38 +03:00
Compare commits
2 Commits
b1763821b5
...
140d8749a6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
140d8749a6 | ||
|
|
534504cf7a |
29
README.md
29
README.md
@ -98,9 +98,9 @@ CrossPoint Reader is pretty aggressive about caching data down to the SD card to
|
|||||||
has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based
|
has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based
|
||||||
on this constraint.
|
on this constraint.
|
||||||
|
|
||||||
### EPUB caching
|
### Data caching
|
||||||
|
|
||||||
The first time chapters of an EPUB are loaded, they are cached to the SD card. Subsequent loads are served from the
|
The first time chapters of a book are loaded, they are cached to the SD card. Subsequent loads are served from the
|
||||||
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
|
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
|
||||||
|
|
||||||
|
|
||||||
@ -108,25 +108,22 @@ cache. This cache directory exists at `.crosspoint` on the SD card. The structur
|
|||||||
.crosspoint/
|
.crosspoint/
|
||||||
├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_<hash>`
|
├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_<hash>`
|
||||||
│ ├── progress.bin # Stores reading progress (chapter, page, etc.)
|
│ ├── progress.bin # Stores reading progress (chapter, page, etc.)
|
||||||
│ ├── 0/ # Each chapter is stored in a subdirectory named by its index (based on the spine order)
|
│ ├── cover.bmp # Book cover image (once generated)
|
||||||
│ │ ├── section.bin # Section metadata (page count)
|
│ ├── book.bin # Book metadata (title, author, spine, table of contents, etc.)
|
||||||
│ │ ├── page_0.bin # Each page is stored in a separate file, it
|
│ └── sections/ # All chapter data is stored in the sections subdirectory
|
||||||
│ │ ├── page_1.bin # contains the position (x, y) and text for each word
|
│ ├── 0.bin # Chapter data (screen count, all text layout info, etc.)
|
||||||
│ │ └── ...
|
│ ├── 1.bin # files are named by their index in the spine
|
||||||
│ ├── 1/
|
│ └── ...
|
||||||
│ │ ├── section.bin
|
|
||||||
│ │ ├── page_0.bin
|
|
||||||
│ │ ├── page_1.bin
|
|
||||||
│ │ └── ...
|
|
||||||
│ └── ...
|
|
||||||
│
|
│
|
||||||
└── epub_189013891/
|
└── epub_189013891/
|
||||||
```
|
```
|
||||||
|
|
||||||
Deleting the `.crosspoint` directory will clear the cache.
|
Deleting the `.crosspoint` directory will clear the entire cache.
|
||||||
|
|
||||||
Due the way it's currently implemented, the cache is not automatically cleared when the EPUB is deleted and moving an
|
Due the way it's currently implemented, the cache is not automatically cleared when a book is deleted and moving a book
|
||||||
EPUB file will reset the reading progress.
|
file will use a new cache directory, resetting the reading progress.
|
||||||
|
|
||||||
|
For more details on the internal file structures, see the [file formats document](./docs/file-formats.md).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@ -67,6 +67,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book, if disabled,
|
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book, if disabled,
|
||||||
paragraphs will not have vertical space between them, but will have first word indentation.
|
paragraphs will not have vertical space between them, but will have first word indentation.
|
||||||
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
|
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
|
||||||
|
- **Front Button Layout**: Swap the order of the bottom edge buttons from Back/Confirm/Left/Right to Left/Right/Back/Confirm.
|
||||||
|
|
||||||
### 3.6 Sleep Screen
|
### 3.6 Sleep Screen
|
||||||
|
|
||||||
|
|||||||
9
docs/file-formats.md
Normal file
9
docs/file-formats.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# File Formats
|
||||||
|
|
||||||
|
## `book.bin`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## `section.bin`
|
||||||
|
|
||||||
|

|
||||||
BIN
docs/images/file-formats/book-bin.png
Normal file
BIN
docs/images/file-formats/book-bin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 539 KiB |
BIN
docs/images/file-formats/section-bin.png
Normal file
BIN
docs/images/file-formats/section-bin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 296 KiB |
@ -3,20 +3,16 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr uint8_t PAGE_FILE_VERSION = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
||||||
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
|
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PageLine::serialize(File& file) {
|
bool PageLine::serialize(File& file) {
|
||||||
serialization::writePod(file, xPos);
|
serialization::writePod(file, xPos);
|
||||||
serialization::writePod(file, yPos);
|
serialization::writePod(file, yPos);
|
||||||
|
|
||||||
// serialize TextBlock pointed to by PageLine
|
// serialize TextBlock pointed to by PageLine
|
||||||
block->serialize(file);
|
return block->serialize(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PageLine> PageLine::deserialize(File& file) {
|
std::unique_ptr<PageLine> PageLine::deserialize(File& file) {
|
||||||
@ -35,27 +31,22 @@ void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::serialize(File& file) const {
|
bool Page::serialize(File& file) const {
|
||||||
serialization::writePod(file, PAGE_FILE_VERSION);
|
|
||||||
|
|
||||||
const uint32_t count = elements.size();
|
const uint32_t count = elements.size();
|
||||||
serialization::writePod(file, count);
|
serialization::writePod(file, count);
|
||||||
|
|
||||||
for (const auto& el : elements) {
|
for (const auto& el : elements) {
|
||||||
// Only PageLine exists currently
|
// Only PageLine exists currently
|
||||||
serialization::writePod(file, static_cast<uint8_t>(TAG_PageLine));
|
serialization::writePod(file, static_cast<uint8_t>(TAG_PageLine));
|
||||||
el->serialize(file);
|
if (!el->serialize(file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Page> Page::deserialize(File& file) {
|
std::unique_ptr<Page> Page::deserialize(File& file) {
|
||||||
uint8_t version;
|
|
||||||
serialization::readPod(file, version);
|
|
||||||
if (version != PAGE_FILE_VERSION) {
|
|
||||||
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown version %u\n", millis(), version);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto page = std::unique_ptr<Page>(new Page());
|
auto page = std::unique_ptr<Page>(new Page());
|
||||||
|
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
|
|||||||
@ -18,7 +18,7 @@ class PageElement {
|
|||||||
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
|
explicit PageElement(const int16_t xPos, const int16_t yPos) : xPos(xPos), yPos(yPos) {}
|
||||||
virtual ~PageElement() = default;
|
virtual ~PageElement() = default;
|
||||||
virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0;
|
virtual void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) = 0;
|
||||||
virtual void serialize(File& file) = 0;
|
virtual bool serialize(File& file) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// a line from a block element
|
// a line from a block element
|
||||||
@ -29,7 +29,7 @@ class PageLine final : public PageElement {
|
|||||||
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
|
PageLine(std::shared_ptr<TextBlock> block, const int16_t xPos, const int16_t yPos)
|
||||||
: PageElement(xPos, yPos), block(std::move(block)) {}
|
: PageElement(xPos, yPos), block(std::move(block)) {}
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
||||||
void serialize(File& file) override;
|
bool serialize(File& file) override;
|
||||||
static std::unique_ptr<PageLine> deserialize(File& file);
|
static std::unique_ptr<PageLine> deserialize(File& file);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,6 +38,6 @@ class Page {
|
|||||||
// the list of block index and line numbers on this page
|
// the list of block index and line numbers on this page
|
||||||
std::vector<std::shared_ptr<PageElement>> elements;
|
std::vector<std::shared_ptr<PageElement>> elements;
|
||||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) const;
|
||||||
void serialize(File& file) const;
|
bool serialize(File& file) const;
|
||||||
static std::unique_ptr<Page> deserialize(File& file);
|
static std::unique_ptr<Page> deserialize(File& file);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,54 +8,60 @@
|
|||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 6;
|
constexpr uint8_t SECTION_FILE_VERSION = 7;
|
||||||
|
constexpr size_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(int) +
|
||||||
|
sizeof(int) + sizeof(int) + sizeof(size_t);
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
size_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
if (!file) {
|
||||||
|
Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount);
|
||||||
File outputFile;
|
return 0;
|
||||||
if (!FsHelpers::openFileForWrite("SCT", filePath, outputFile)) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
page->serialize(outputFile);
|
|
||||||
outputFile.close();
|
|
||||||
|
|
||||||
|
const auto position = file.position();
|
||||||
|
if (!page->serialize(file)) {
|
||||||
|
Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
|
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
|
||||||
|
|
||||||
pageCount++;
|
pageCount++;
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const int viewportWidth, const int viewportHeight) const {
|
const int viewportWidth, const int viewportHeight) {
|
||||||
File outputFile;
|
if (!file) {
|
||||||
if (!FsHelpers::openFileForWrite("SCT", cachePath + "/section.bin", outputFile)) {
|
Serial.printf("[%lu] [SCT] File not open for writing header\n", millis());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
||||||
serialization::writePod(outputFile, fontId);
|
sizeof(extraParagraphSpacing) + sizeof(viewportWidth) + sizeof(viewportHeight) +
|
||||||
serialization::writePod(outputFile, lineCompression);
|
sizeof(pageCount) + sizeof(size_t),
|
||||||
serialization::writePod(outputFile, extraParagraphSpacing);
|
"Header size mismatch");
|
||||||
serialization::writePod(outputFile, viewportWidth);
|
serialization::writePod(file, SECTION_FILE_VERSION);
|
||||||
serialization::writePod(outputFile, viewportHeight);
|
serialization::writePod(file, fontId);
|
||||||
serialization::writePod(outputFile, pageCount);
|
serialization::writePod(file, lineCompression);
|
||||||
outputFile.close();
|
serialization::writePod(file, extraParagraphSpacing);
|
||||||
|
serialization::writePod(file, viewportWidth);
|
||||||
|
serialization::writePod(file, viewportHeight);
|
||||||
|
serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written)
|
||||||
|
serialization::writePod(file, static_cast<size_t>(0)); // Placeholder for LUT offset
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const int viewportWidth, const int viewportHeight) {
|
const int viewportWidth, const int viewportHeight) {
|
||||||
const auto sectionFilePath = cachePath + "/section.bin";
|
if (!FsHelpers::openFileForRead("SCT", filePath, file)) {
|
||||||
File inputFile;
|
|
||||||
if (!FsHelpers::openFileForRead("SCT", sectionFilePath, inputFile)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match parameters
|
// Match parameters
|
||||||
{
|
{
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(inputFile, version);
|
serialization::readPod(file, version);
|
||||||
if (version != SECTION_FILE_VERSION) {
|
if (version != SECTION_FILE_VERSION) {
|
||||||
inputFile.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version);
|
Serial.printf("[%lu] [SCT] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||||
clearCache();
|
clearCache();
|
||||||
return false;
|
return false;
|
||||||
@ -64,41 +70,36 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
|
|||||||
int fileFontId, fileViewportWidth, fileViewportHeight;
|
int fileFontId, fileViewportWidth, fileViewportHeight;
|
||||||
float fileLineCompression;
|
float fileLineCompression;
|
||||||
bool fileExtraParagraphSpacing;
|
bool fileExtraParagraphSpacing;
|
||||||
serialization::readPod(inputFile, fileFontId);
|
serialization::readPod(file, fileFontId);
|
||||||
serialization::readPod(inputFile, fileLineCompression);
|
serialization::readPod(file, fileLineCompression);
|
||||||
serialization::readPod(inputFile, fileExtraParagraphSpacing);
|
serialization::readPod(file, fileExtraParagraphSpacing);
|
||||||
serialization::readPod(inputFile, fileViewportWidth);
|
serialization::readPod(file, fileViewportWidth);
|
||||||
serialization::readPod(inputFile, fileViewportHeight);
|
serialization::readPod(file, fileViewportHeight);
|
||||||
|
|
||||||
if (fontId != fileFontId || lineCompression != fileLineCompression ||
|
if (fontId != fileFontId || lineCompression != fileLineCompression ||
|
||||||
extraParagraphSpacing != fileExtraParagraphSpacing || viewportWidth != fileViewportWidth ||
|
extraParagraphSpacing != fileExtraParagraphSpacing || viewportWidth != fileViewportWidth ||
|
||||||
viewportHeight != fileViewportHeight) {
|
viewportHeight != fileViewportHeight) {
|
||||||
inputFile.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
|
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
|
||||||
clearCache();
|
clearCache();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serialization::readPod(inputFile, pageCount);
|
serialization::readPod(file, pageCount);
|
||||||
inputFile.close();
|
file.close();
|
||||||
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
|
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Section::setupCacheDir() const {
|
|
||||||
epub->setupCacheDir();
|
|
||||||
SD.mkdir(cachePath.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
|
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
|
||||||
bool Section::clearCache() const {
|
bool Section::clearCache() const {
|
||||||
if (!SD.exists(cachePath.c_str())) {
|
if (!SD.exists(filePath.c_str())) {
|
||||||
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
|
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!FsHelpers::removeDir(cachePath.c_str())) {
|
if (!SD.remove(filePath.c_str())) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis());
|
Serial.printf("[%lu] [SCT] Failed to clear cache\n", millis());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -107,10 +108,10 @@ bool Section::clearCache() const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||||
const int viewportWidth, const int viewportHeight,
|
const int viewportWidth, const int viewportHeight,
|
||||||
const std::function<void()>& progressSetupFn,
|
const std::function<void()>& progressSetupFn,
|
||||||
const std::function<void(int)>& progressFn) {
|
const std::function<void(int)>& progressFn) {
|
||||||
constexpr size_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB
|
constexpr size_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB
|
||||||
const auto localPath = epub->getSpineItem(spineIndex).href;
|
const auto localPath = epub->getSpineItem(spineIndex).href;
|
||||||
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
|
||||||
@ -156,30 +157,66 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
|||||||
progressSetupFn();
|
progressSetupFn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!FsHelpers::openFileForWrite("SCT", filePath, file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight);
|
||||||
|
std::vector<size_t> lut = {};
|
||||||
|
|
||||||
ChapterHtmlSlimParser visitor(
|
ChapterHtmlSlimParser visitor(
|
||||||
tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight,
|
tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight,
|
||||||
[this](std::unique_ptr<Page> page) { this->onPageComplete(std::move(page)); }, progressFn);
|
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); },
|
||||||
|
progressFn);
|
||||||
success = visitor.parseAndBuildPages();
|
success = visitor.parseAndBuildPages();
|
||||||
|
|
||||||
SD.remove(tmpHtmlPath.c_str());
|
SD.remove(tmpHtmlPath.c_str());
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
|
Serial.printf("[%lu] [SCT] Failed to parse XML and build pages\n", millis());
|
||||||
|
file.close();
|
||||||
|
SD.remove(filePath.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCacheMetadata(fontId, lineCompression, extraParagraphSpacing, viewportWidth, viewportHeight);
|
const auto lutOffset = file.position();
|
||||||
|
bool hasFailedLutRecords = false;
|
||||||
|
// Write LUT
|
||||||
|
for (const auto& pos : lut) {
|
||||||
|
if (pos == 0) {
|
||||||
|
hasFailedLutRecords = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
serialization::writePod(file, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFailedLutRecords) {
|
||||||
|
Serial.printf("[%lu] [SCT] Failed to write LUT due to invalid page positions\n", millis());
|
||||||
|
file.close();
|
||||||
|
SD.remove(filePath.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Go back and write LUT offset
|
||||||
|
file.seek(HEADER_SIZE - sizeof(size_t) - sizeof(pageCount));
|
||||||
|
serialization::writePod(file, pageCount);
|
||||||
|
serialization::writePod(file, lutOffset);
|
||||||
|
file.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Page> Section::loadPageFromSD() const {
|
std::unique_ptr<Page> Section::loadPageFromSectionFile() {
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(currentPage) + ".bin";
|
if (!FsHelpers::openFileForRead("SCT", filePath, file)) {
|
||||||
|
|
||||||
File inputFile;
|
|
||||||
if (!FsHelpers::openFileForRead("SCT", filePath, inputFile)) {
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
auto page = Page::deserialize(inputFile);
|
|
||||||
inputFile.close();
|
file.seek(HEADER_SIZE - sizeof(size_t));
|
||||||
|
size_t lutOffset;
|
||||||
|
serialization::readPod(file, lutOffset);
|
||||||
|
file.seek(lutOffset + sizeof(size_t) * currentPage);
|
||||||
|
size_t pagePos;
|
||||||
|
serialization::readPod(file, pagePos);
|
||||||
|
file.seek(pagePos);
|
||||||
|
|
||||||
|
auto page = Page::deserialize(file);
|
||||||
|
file.close();
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,11 +11,12 @@ class Section {
|
|||||||
std::shared_ptr<Epub> epub;
|
std::shared_ptr<Epub> epub;
|
||||||
const int spineIndex;
|
const int spineIndex;
|
||||||
GfxRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
std::string cachePath;
|
std::string filePath;
|
||||||
|
File file;
|
||||||
|
|
||||||
void writeCacheMetadata(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
|
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
|
||||||
int viewportHeight) const;
|
int viewportHeight);
|
||||||
void onPageComplete(std::unique_ptr<Page> page);
|
size_t onPageComplete(std::unique_ptr<Page> page);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
@ -25,14 +26,13 @@ class Section {
|
|||||||
: epub(epub),
|
: epub(epub),
|
||||||
spineIndex(spineIndex),
|
spineIndex(spineIndex),
|
||||||
renderer(renderer),
|
renderer(renderer),
|
||||||
cachePath(epub->getCachePath() + "/" + std::to_string(spineIndex)) {}
|
filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {}
|
||||||
~Section() = default;
|
~Section() = default;
|
||||||
bool loadCacheMetadata(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
|
bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
|
||||||
int viewportHeight);
|
int viewportHeight);
|
||||||
void setupCacheDir() const;
|
|
||||||
bool clearCache() const;
|
bool clearCache() const;
|
||||||
bool persistPageDataToSD(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
|
bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, int viewportWidth,
|
||||||
int viewportHeight, const std::function<void()>& progressSetupFn = nullptr,
|
int viewportHeight, const std::function<void()>& progressSetupFn = nullptr,
|
||||||
const std::function<void(int)>& progressFn = nullptr);
|
const std::function<void(int)>& progressFn = nullptr);
|
||||||
std::unique_ptr<Page> loadPageFromSD() const;
|
std::unique_ptr<Page> loadPageFromSectionFile();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,34 +24,33 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBlock::serialize(File& file) const {
|
bool TextBlock::serialize(File& file) const {
|
||||||
// words
|
if (words.size() != wordXpos.size() || words.size() != wordStyles.size()) {
|
||||||
const uint32_t wc = words.size();
|
Serial.printf("[%lu] [TXB] Serialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(),
|
||||||
serialization::writePod(file, wc);
|
words.size(), wordXpos.size(), wordStyles.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word data
|
||||||
|
serialization::writePod(file, static_cast<uint32_t>(words.size()));
|
||||||
for (const auto& w : words) serialization::writeString(file, w);
|
for (const auto& w : words) serialization::writeString(file, w);
|
||||||
|
|
||||||
// wordXpos
|
|
||||||
const uint32_t xc = wordXpos.size();
|
|
||||||
serialization::writePod(file, xc);
|
|
||||||
for (auto x : wordXpos) serialization::writePod(file, x);
|
for (auto x : wordXpos) serialization::writePod(file, x);
|
||||||
|
|
||||||
// wordStyles
|
|
||||||
const uint32_t sc = wordStyles.size();
|
|
||||||
serialization::writePod(file, sc);
|
|
||||||
for (auto s : wordStyles) serialization::writePod(file, s);
|
for (auto s : wordStyles) serialization::writePod(file, s);
|
||||||
|
|
||||||
// style
|
// Block style
|
||||||
serialization::writePod(file, style);
|
serialization::writePod(file, style);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
||||||
uint32_t wc, xc, sc;
|
uint32_t wc;
|
||||||
std::list<std::string> words;
|
std::list<std::string> words;
|
||||||
std::list<uint16_t> wordXpos;
|
std::list<uint16_t> wordXpos;
|
||||||
std::list<EpdFontStyle> wordStyles;
|
std::list<EpdFontStyle> wordStyles;
|
||||||
BLOCK_STYLE style;
|
BLOCK_STYLE style;
|
||||||
|
|
||||||
// words
|
// Word count
|
||||||
serialization::readPod(file, wc);
|
serialization::readPod(file, wc);
|
||||||
|
|
||||||
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
|
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
|
||||||
@ -60,27 +59,15 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(File& file) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Word data
|
||||||
words.resize(wc);
|
words.resize(wc);
|
||||||
|
wordXpos.resize(wc);
|
||||||
|
wordStyles.resize(wc);
|
||||||
for (auto& w : words) serialization::readString(file, w);
|
for (auto& w : words) serialization::readString(file, w);
|
||||||
|
|
||||||
// wordXpos
|
|
||||||
serialization::readPod(file, xc);
|
|
||||||
wordXpos.resize(xc);
|
|
||||||
for (auto& x : wordXpos) serialization::readPod(file, x);
|
for (auto& x : wordXpos) serialization::readPod(file, x);
|
||||||
|
|
||||||
// wordStyles
|
|
||||||
serialization::readPod(file, sc);
|
|
||||||
wordStyles.resize(sc);
|
|
||||||
for (auto& s : wordStyles) serialization::readPod(file, s);
|
for (auto& s : wordStyles) serialization::readPod(file, s);
|
||||||
|
|
||||||
// Validate data consistency: all three lists must have the same size
|
// Block style
|
||||||
if (wc != xc || wc != sc) {
|
|
||||||
Serial.printf("[%lu] [TXB] Deserialization failed: size mismatch (words=%u, xpos=%u, styles=%u)\n", millis(), wc,
|
|
||||||
xc, sc);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// style
|
|
||||||
serialization::readPod(file, style);
|
serialization::readPod(file, style);
|
||||||
|
|
||||||
return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), style));
|
return std::unique_ptr<TextBlock>(new TextBlock(std::move(words), std::move(wordXpos), std::move(wordStyles), style));
|
||||||
|
|||||||
@ -36,6 +36,6 @@ class TextBlock final : public Block {
|
|||||||
// given a renderer works out where to break the words into lines
|
// given a renderer works out where to break the words into lines
|
||||||
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
BlockType getType() override { return TEXT_BLOCK; }
|
||||||
void serialize(File& file) const;
|
bool serialize(File& file) const;
|
||||||
static std::unique_ptr<TextBlock> deserialize(File& file);
|
static std::unique_ptr<TextBlock> deserialize(File& file);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -11,7 +11,7 @@ CrossPointSettings CrossPointSettings::instance;
|
|||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
// Increment this when adding new persisted settings fields
|
// Increment this when adding new persisted settings fields
|
||||||
constexpr uint8_t SETTINGS_COUNT = 5;
|
constexpr uint8_t SETTINGS_COUNT = 6;
|
||||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, shortPwrBtn);
|
serialization::writePod(outputFile, shortPwrBtn);
|
||||||
serialization::writePod(outputFile, statusBar);
|
serialization::writePod(outputFile, statusBar);
|
||||||
serialization::writePod(outputFile, orientation);
|
serialization::writePod(outputFile, orientation);
|
||||||
|
serialization::writePod(outputFile, frontButtonLayout);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||||
@ -67,6 +68,8 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, orientation);
|
serialization::readPod(inputFile, orientation);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
serialization::readPod(inputFile, frontButtonLayout);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
|
|||||||
@ -28,6 +28,11 @@ class CrossPointSettings {
|
|||||||
LANDSCAPE_CCW = 3 // 800x480 logical coordinates, native panel orientation
|
LANDSCAPE_CCW = 3 // 800x480 logical coordinates, native panel orientation
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Front button layout options
|
||||||
|
// Default: Back, Confirm, Left, Right
|
||||||
|
// Swapped: Left, Right, Back, Confirm
|
||||||
|
enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1 };
|
||||||
|
|
||||||
// Sleep screen settings
|
// Sleep screen settings
|
||||||
uint8_t sleepScreen = DARK;
|
uint8_t sleepScreen = DARK;
|
||||||
// Status bar settings
|
// Status bar settings
|
||||||
@ -39,6 +44,8 @@ class CrossPointSettings {
|
|||||||
// EPUB reading orientation settings
|
// EPUB reading orientation settings
|
||||||
// 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise
|
// 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise
|
||||||
uint8_t orientation = PORTRAIT;
|
uint8_t orientation = PORTRAIT;
|
||||||
|
// Front button layout
|
||||||
|
uint8_t frontButtonLayout = BACK_CONFIRM_LEFT_RIGHT;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
|
|||||||
77
src/MappedInputManager.cpp
Normal file
77
src/MappedInputManager.cpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
#include "MappedInputManager.h"
|
||||||
|
|
||||||
|
decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button button) const {
|
||||||
|
const auto layout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
|
||||||
|
|
||||||
|
switch (button) {
|
||||||
|
case Button::Back:
|
||||||
|
switch (layout) {
|
||||||
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
|
return InputManager::BTN_LEFT;
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
default:
|
||||||
|
return InputManager::BTN_BACK;
|
||||||
|
}
|
||||||
|
case Button::Confirm:
|
||||||
|
switch (layout) {
|
||||||
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
|
return InputManager::BTN_RIGHT;
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
default:
|
||||||
|
return InputManager::BTN_CONFIRM;
|
||||||
|
}
|
||||||
|
case Button::Left:
|
||||||
|
switch (layout) {
|
||||||
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
|
return InputManager::BTN_BACK;
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
default:
|
||||||
|
return InputManager::BTN_LEFT;
|
||||||
|
}
|
||||||
|
case Button::Right:
|
||||||
|
switch (layout) {
|
||||||
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
|
return InputManager::BTN_CONFIRM;
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
default:
|
||||||
|
return InputManager::BTN_RIGHT;
|
||||||
|
}
|
||||||
|
case Button::Up:
|
||||||
|
return InputManager::BTN_UP;
|
||||||
|
case Button::Down:
|
||||||
|
return InputManager::BTN_DOWN;
|
||||||
|
case Button::Power:
|
||||||
|
return InputManager::BTN_POWER;
|
||||||
|
case Button::PageBack:
|
||||||
|
return InputManager::BTN_UP;
|
||||||
|
case Button::PageForward:
|
||||||
|
return InputManager::BTN_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return InputManager::BTN_BACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MappedInputManager::wasPressed(const Button button) const { return inputManager.wasPressed(mapButton(button)); }
|
||||||
|
|
||||||
|
bool MappedInputManager::wasReleased(const Button button) const { return inputManager.wasReleased(mapButton(button)); }
|
||||||
|
|
||||||
|
bool MappedInputManager::isPressed(const Button button) const { return inputManager.isPressed(mapButton(button)); }
|
||||||
|
|
||||||
|
bool MappedInputManager::wasAnyPressed() const { return inputManager.wasAnyPressed(); }
|
||||||
|
|
||||||
|
bool MappedInputManager::wasAnyReleased() const { return inputManager.wasAnyReleased(); }
|
||||||
|
|
||||||
|
unsigned long MappedInputManager::getHeldTime() const { return inputManager.getHeldTime(); }
|
||||||
|
|
||||||
|
MappedInputManager::Labels MappedInputManager::mapLabels(const char* back, const char* confirm, const char* previous,
|
||||||
|
const char* next) const {
|
||||||
|
const auto layout = static_cast<CrossPointSettings::FRONT_BUTTON_LAYOUT>(SETTINGS.frontButtonLayout);
|
||||||
|
|
||||||
|
switch (layout) {
|
||||||
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
|
return {previous, next, back, confirm};
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
default:
|
||||||
|
return {back, confirm, previous, next};
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/MappedInputManager.h
Normal file
31
src/MappedInputManager.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <InputManager.h>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
|
||||||
|
class MappedInputManager {
|
||||||
|
public:
|
||||||
|
enum class Button { Back, Confirm, Left, Right, Up, Down, Power, PageBack, PageForward };
|
||||||
|
|
||||||
|
struct Labels {
|
||||||
|
const char* btn1;
|
||||||
|
const char* btn2;
|
||||||
|
const char* btn3;
|
||||||
|
const char* btn4;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit MappedInputManager(InputManager& inputManager) : inputManager(inputManager) {}
|
||||||
|
|
||||||
|
bool wasPressed(Button button) const;
|
||||||
|
bool wasReleased(Button button) const;
|
||||||
|
bool isPressed(Button button) const;
|
||||||
|
bool wasAnyPressed() const;
|
||||||
|
bool wasAnyReleased() const;
|
||||||
|
unsigned long getHeldTime() const;
|
||||||
|
Labels mapLabels(const char* back, const char* confirm, const char* previous, const char* next) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
InputManager& inputManager;
|
||||||
|
decltype(InputManager::BTN_BACK) mapButton(Button button) const;
|
||||||
|
};
|
||||||
@ -5,18 +5,19 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
class InputManager;
|
#include "../MappedInputManager.h"
|
||||||
|
|
||||||
class GfxRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
class Activity {
|
class Activity {
|
||||||
protected:
|
protected:
|
||||||
std::string name;
|
std::string name;
|
||||||
GfxRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
InputManager& inputManager;
|
MappedInputManager& mappedInput;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Activity(std::string name, GfxRenderer& renderer, InputManager& inputManager)
|
explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
: name(std::move(name)), renderer(renderer), inputManager(inputManager) {}
|
: name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {}
|
||||||
virtual ~Activity() = default;
|
virtual ~Activity() = default;
|
||||||
virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); }
|
virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); }
|
||||||
virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
|
virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
|
||||||
|
|||||||
@ -10,8 +10,8 @@ class ActivityWithSubactivity : public Activity {
|
|||||||
void enterNewActivity(Activity* activity);
|
void enterNewActivity(Activity* activity);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ActivityWithSubactivity(std::string name, GfxRenderer& renderer, InputManager& inputManager)
|
explicit ActivityWithSubactivity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
: Activity(std::move(name), renderer, inputManager) {}
|
: Activity(std::move(name), renderer, mappedInput) {}
|
||||||
void loop() override;
|
void loop() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
class BootActivity final : public Activity {
|
class BootActivity final : public Activity {
|
||||||
public:
|
public:
|
||||||
explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity("Boot", renderer, inputManager) {}
|
explicit BootActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
|
: Activity("Boot", renderer, mappedInput) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,8 +5,8 @@ class Bitmap;
|
|||||||
|
|
||||||
class SleepActivity final : public Activity {
|
class SleepActivity final : public Activity {
|
||||||
public:
|
public:
|
||||||
explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager)
|
explicit SleepActivity(GfxRenderer& renderer, MappedInputManager& mappedInput)
|
||||||
: Activity("Sleep", renderer, inputManager) {}
|
: Activity("Sleep", renderer, mappedInput) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@ -49,14 +49,14 @@ void HomeActivity::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void HomeActivity::loop() {
|
void HomeActivity::loop() {
|
||||||
const bool prevPressed =
|
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
||||||
const bool nextPressed =
|
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT);
|
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
const int menuCount = getMenuItemCount();
|
const int menuCount = getMenuItemCount();
|
||||||
|
|
||||||
if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
if (hasContinueReading) {
|
if (hasContinueReading) {
|
||||||
// Menu: Continue Reading, Browse, File transfer, Settings
|
// Menu: Continue Reading, Browse, File transfer, Settings
|
||||||
if (selectorIndex == 0) {
|
if (selectorIndex == 0) {
|
||||||
@ -143,7 +143,8 @@ void HomeActivity::render() const {
|
|||||||
|
|
||||||
renderer.drawText(UI_FONT_ID, 20, menuY, "Settings", selectorIndex != menuIndex);
|
renderer.drawText(UI_FONT_ID, 20, menuY, "Settings", selectorIndex != menuIndex);
|
||||||
|
|
||||||
renderer.drawButtonHints(UI_FONT_ID, "Back", "Confirm", "Left", "Right");
|
const auto labels = mappedInput.mapLabels("Back", "Confirm", "Left", "Right");
|
||||||
|
renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,10 +24,10 @@ class HomeActivity final : public Activity {
|
|||||||
int getMenuItemCount() const;
|
int getMenuItemCount() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void()>& onContinueReading, const std::function<void()>& onReaderOpen,
|
const std::function<void()>& onContinueReading, const std::function<void()>& onReaderOpen,
|
||||||
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen)
|
const std::function<void()>& onSettingsOpen, const std::function<void()>& onFileTransferOpen)
|
||||||
: Activity("Home", renderer, inputManager),
|
: Activity("Home", renderer, mappedInput),
|
||||||
onContinueReading(onContinueReading),
|
onContinueReading(onContinueReading),
|
||||||
onReaderOpen(onReaderOpen),
|
onReaderOpen(onReaderOpen),
|
||||||
onSettingsOpen(onSettingsOpen),
|
onSettingsOpen(onSettingsOpen),
|
||||||
|
|||||||
@ -57,7 +57,7 @@ void CrossPointWebServerActivity::onEnter() {
|
|||||||
// Launch network mode selection subactivity
|
// Launch network mode selection subactivity
|
||||||
Serial.printf("[%lu] [WEBACT] Launching NetworkModeSelectionActivity...\n", millis());
|
Serial.printf("[%lu] [WEBACT] Launching NetworkModeSelectionActivity...\n", millis());
|
||||||
enterNewActivity(new NetworkModeSelectionActivity(
|
enterNewActivity(new NetworkModeSelectionActivity(
|
||||||
renderer, inputManager, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
||||||
[this]() { onGoBack(); } // Cancel goes back to home
|
[this]() { onGoBack(); } // Cancel goes back to home
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ void CrossPointWebServerActivity::onNetworkModeSelected(const NetworkMode mode)
|
|||||||
|
|
||||||
state = WebServerActivityState::WIFI_SELECTION;
|
state = WebServerActivityState::WIFI_SELECTION;
|
||||||
Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis());
|
Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis());
|
||||||
enterNewActivity(new WifiSelectionActivity(renderer, inputManager,
|
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||||
} else {
|
} else {
|
||||||
// AP mode - start access point
|
// AP mode - start access point
|
||||||
@ -174,7 +174,7 @@ void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected)
|
|||||||
exitActivity();
|
exitActivity();
|
||||||
state = WebServerActivityState::MODE_SELECTION;
|
state = WebServerActivityState::MODE_SELECTION;
|
||||||
enterNewActivity(new NetworkModeSelectionActivity(
|
enterNewActivity(new NetworkModeSelectionActivity(
|
||||||
renderer, inputManager, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
renderer, mappedInput, [this](const NetworkMode mode) { onNetworkModeSelected(mode); },
|
||||||
[this]() { onGoBack(); }));
|
[this]() { onGoBack(); }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -305,7 +305,7 @@ void CrossPointWebServerActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle exit on Back button
|
// Handle exit on Back button
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -428,5 +428,6 @@ void CrossPointWebServerActivity::renderServerRunning() const {
|
|||||||
REGULAR);
|
REGULAR);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawButtonHints(UI_FONT_ID, "« Exit", "", "", "");
|
const auto labels = mappedInput.mapLabels("« Exit", "", "", "");
|
||||||
|
renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,9 +63,9 @@ class CrossPointWebServerActivity final : public ActivityWithSubactivity {
|
|||||||
void stopWebServer();
|
void stopWebServer();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit CrossPointWebServerActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit CrossPointWebServerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void()>& onGoBack)
|
const std::function<void()>& onGoBack)
|
||||||
: ActivityWithSubactivity("CrossPointWebServer", renderer, inputManager), onGoBack(onGoBack) {}
|
: ActivityWithSubactivity("CrossPointWebServer", renderer, mappedInput), onGoBack(onGoBack) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -51,23 +51,23 @@ void NetworkModeSelectionActivity::onExit() {
|
|||||||
|
|
||||||
void NetworkModeSelectionActivity::loop() {
|
void NetworkModeSelectionActivity::loop() {
|
||||||
// Handle back button - cancel
|
// Handle back button - cancel
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
onCancel();
|
onCancel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle confirm button - select current option
|
// Handle confirm button - select current option
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
const NetworkMode mode = (selectedIndex == 0) ? NetworkMode::JOIN_NETWORK : NetworkMode::CREATE_HOTSPOT;
|
const NetworkMode mode = (selectedIndex == 0) ? NetworkMode::JOIN_NETWORK : NetworkMode::CREATE_HOTSPOT;
|
||||||
onModeSelected(mode);
|
onModeSelected(mode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle navigation
|
// Handle navigation
|
||||||
const bool prevPressed =
|
const bool prevPressed = mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
mappedInput.wasPressed(MappedInputManager::Button::Left);
|
||||||
const bool nextPressed =
|
const bool nextPressed = mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT);
|
mappedInput.wasPressed(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
if (prevPressed) {
|
if (prevPressed) {
|
||||||
selectedIndex = (selectedIndex + MENU_ITEM_COUNT - 1) % MENU_ITEM_COUNT;
|
selectedIndex = (selectedIndex + MENU_ITEM_COUNT - 1) % MENU_ITEM_COUNT;
|
||||||
@ -122,7 +122,8 @@ void NetworkModeSelectionActivity::render() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw help text at bottom
|
// Draw help text at bottom
|
||||||
renderer.drawButtonHints(UI_FONT_ID, "« Back", "Select", "", "");
|
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
||||||
|
renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,10 +31,10 @@ class NetworkModeSelectionActivity final : public Activity {
|
|||||||
void render() const;
|
void render() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit NetworkModeSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit NetworkModeSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void(NetworkMode)>& onModeSelected,
|
const std::function<void(NetworkMode)>& onModeSelected,
|
||||||
const std::function<void()>& onCancel)
|
const std::function<void()>& onCancel)
|
||||||
: Activity("NetworkModeSelection", renderer, inputManager), onModeSelected(onModeSelected), onCancel(onCancel) {}
|
: Activity("NetworkModeSelection", renderer, mappedInput), onModeSelected(onModeSelected), onCancel(onCancel) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -190,7 +190,7 @@ void WifiSelectionActivity::selectNetwork(const int index) {
|
|||||||
// Don't allow screen updates while changing activity
|
// Don't allow screen updates while changing activity
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
enterNewActivity(new KeyboardEntryActivity(
|
enterNewActivity(new KeyboardEntryActivity(
|
||||||
renderer, inputManager, "Enter WiFi Password",
|
renderer, mappedInput, "Enter WiFi Password",
|
||||||
"", // No initial text
|
"", // No initial text
|
||||||
50, // Y position
|
50, // Y position
|
||||||
64, // Max password length
|
64, // Max password length
|
||||||
@ -302,17 +302,19 @@ void WifiSelectionActivity::loop() {
|
|||||||
|
|
||||||
// Handle save prompt state
|
// Handle save prompt state
|
||||||
if (state == WifiSelectionState::SAVE_PROMPT) {
|
if (state == WifiSelectionState::SAVE_PROMPT) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
if (savePromptSelection > 0) {
|
if (savePromptSelection > 0) {
|
||||||
savePromptSelection--;
|
savePromptSelection--;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_RIGHT) || inputManager.wasPressed(InputManager::BTN_DOWN)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
if (savePromptSelection < 1) {
|
if (savePromptSelection < 1) {
|
||||||
savePromptSelection++;
|
savePromptSelection++;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
if (savePromptSelection == 0) {
|
if (savePromptSelection == 0) {
|
||||||
// User chose "Yes" - save the password
|
// User chose "Yes" - save the password
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
@ -321,7 +323,7 @@ void WifiSelectionActivity::loop() {
|
|||||||
}
|
}
|
||||||
// Complete - parent will start web server
|
// Complete - parent will start web server
|
||||||
onComplete(true);
|
onComplete(true);
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
// Skip saving, complete anyway
|
// Skip saving, complete anyway
|
||||||
onComplete(true);
|
onComplete(true);
|
||||||
}
|
}
|
||||||
@ -330,17 +332,19 @@ void WifiSelectionActivity::loop() {
|
|||||||
|
|
||||||
// Handle forget prompt state (connection failed with saved credentials)
|
// Handle forget prompt state (connection failed with saved credentials)
|
||||||
if (state == WifiSelectionState::FORGET_PROMPT) {
|
if (state == WifiSelectionState::FORGET_PROMPT) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_LEFT) || inputManager.wasPressed(InputManager::BTN_UP)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
if (forgetPromptSelection > 0) {
|
if (forgetPromptSelection > 0) {
|
||||||
forgetPromptSelection--;
|
forgetPromptSelection--;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_RIGHT) || inputManager.wasPressed(InputManager::BTN_DOWN)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
if (forgetPromptSelection < 1) {
|
if (forgetPromptSelection < 1) {
|
||||||
forgetPromptSelection++;
|
forgetPromptSelection++;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
if (forgetPromptSelection == 0) {
|
if (forgetPromptSelection == 0) {
|
||||||
// User chose "Yes" - forget the network
|
// User chose "Yes" - forget the network
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
@ -356,7 +360,7 @@ void WifiSelectionActivity::loop() {
|
|||||||
// Go back to network list
|
// Go back to network list
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
// Skip forgetting, go back to network list
|
// Skip forgetting, go back to network list
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -373,7 +377,8 @@ void WifiSelectionActivity::loop() {
|
|||||||
|
|
||||||
// Handle connection failed state
|
// Handle connection failed state
|
||||||
if (state == WifiSelectionState::CONNECTION_FAILED) {
|
if (state == WifiSelectionState::CONNECTION_FAILED) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK) || inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
// If we used saved credentials, offer to forget the network
|
// If we used saved credentials, offer to forget the network
|
||||||
if (usedSavedPassword) {
|
if (usedSavedPassword) {
|
||||||
state = WifiSelectionState::FORGET_PROMPT;
|
state = WifiSelectionState::FORGET_PROMPT;
|
||||||
@ -390,13 +395,13 @@ void WifiSelectionActivity::loop() {
|
|||||||
// Handle network list state
|
// Handle network list state
|
||||||
if (state == WifiSelectionState::NETWORK_LIST) {
|
if (state == WifiSelectionState::NETWORK_LIST) {
|
||||||
// Check for Back button to exit (cancel)
|
// Check for Back button to exit (cancel)
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
onComplete(false);
|
onComplete(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Confirm button to select network or rescan
|
// Check for Confirm button to select network or rescan
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
if (!networks.empty()) {
|
if (!networks.empty()) {
|
||||||
selectNetwork(selectedNetworkIndex);
|
selectNetwork(selectedNetworkIndex);
|
||||||
} else {
|
} else {
|
||||||
@ -406,12 +411,14 @@ void WifiSelectionActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle UP/DOWN navigation
|
// Handle UP/DOWN navigation
|
||||||
if (inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
if (selectedNetworkIndex > 0) {
|
if (selectedNetworkIndex > 0) {
|
||||||
selectedNetworkIndex--;
|
selectedNetworkIndex--;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
if (!networks.empty() && selectedNetworkIndex < static_cast<int>(networks.size()) - 1) {
|
if (!networks.empty() && selectedNetworkIndex < static_cast<int>(networks.size()) - 1) {
|
||||||
selectedNetworkIndex++;
|
selectedNetworkIndex++;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -557,7 +564,8 @@ void WifiSelectionActivity::renderNetworkList() const {
|
|||||||
|
|
||||||
// Draw help text
|
// Draw help text
|
||||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
||||||
renderer.drawButtonHints(UI_FONT_ID, "« Back", "Connect", "", "");
|
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
|
||||||
|
renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderConnecting() const {
|
void WifiSelectionActivity::renderConnecting() const {
|
||||||
|
|||||||
@ -92,9 +92,9 @@ class WifiSelectionActivity final : public ActivityWithSubactivity {
|
|||||||
std::string getSignalStrengthIndicator(int32_t rssi) const;
|
std::string getSignalStrengthIndicator(int32_t rssi) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WifiSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit WifiSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void(bool connected)>& onComplete)
|
const std::function<void(bool connected)>& onComplete)
|
||||||
: ActivityWithSubactivity("WifiSelection", renderer, inputManager), onComplete(onComplete) {}
|
: ActivityWithSubactivity("WifiSelection", renderer, mappedInput), onComplete(onComplete) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -106,12 +106,12 @@ void EpubReaderActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enter chapter selection activity
|
// Enter chapter selection activity
|
||||||
if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
// Don't start activity transition while rendering
|
// Don't start activity transition while rendering
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new EpubReaderChapterSelectionActivity(
|
enterNewActivity(new EpubReaderChapterSelectionActivity(
|
||||||
this->renderer, this->inputManager, epub, currentSpineIndex,
|
this->renderer, this->mappedInput, epub, currentSpineIndex,
|
||||||
[this] {
|
[this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
@ -129,21 +129,21 @@ void EpubReaderActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Long press BACK (1s+) goes directly to home
|
// Long press BACK (1s+) goes directly to home
|
||||||
if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= goHomeMs) {
|
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) {
|
||||||
onGoHome();
|
onGoHome();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short press BACK goes to file selection
|
// Short press BACK goes to file selection
|
||||||
if (inputManager.wasReleased(InputManager::BTN_BACK) && inputManager.getHeldTime() < goHomeMs) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back) && mappedInput.getHeldTime() < goHomeMs) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool prevReleased =
|
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
const bool nextReleased =
|
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
if (!prevReleased && !nextReleased) {
|
if (!prevReleased && !nextReleased) {
|
||||||
return;
|
return;
|
||||||
@ -157,7 +157,7 @@ void EpubReaderActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skipChapter = inputManager.getHeldTime() > skipChapterMs;
|
const bool skipChapter = mappedInput.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
|
||||||
@ -254,8 +254,8 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
const auto viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
|
const auto viewportWidth = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
|
||||||
const auto viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom;
|
const auto viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom;
|
||||||
|
|
||||||
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, SETTINGS.extraParagraphSpacing, viewportWidth,
|
if (!section->loadSectionFile(READER_FONT_ID, lineCompression, SETTINGS.extraParagraphSpacing, viewportWidth,
|
||||||
viewportHeight)) {
|
viewportHeight)) {
|
||||||
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
||||||
|
|
||||||
// Progress bar dimensions
|
// Progress bar dimensions
|
||||||
@ -282,8 +282,6 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
pagesUntilFullRefresh = 0;
|
pagesUntilFullRefresh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
section->setupCacheDir();
|
|
||||||
|
|
||||||
// Setup callback - only called for chapters >= 50KB, redraws with progress bar
|
// Setup callback - only called for chapters >= 50KB, redraws with progress bar
|
||||||
auto progressSetup = [this, boxXWithBar, boxWidthWithBar, boxHeightWithBar, barX, barY] {
|
auto progressSetup = [this, boxXWithBar, boxWidthWithBar, boxHeightWithBar, barX, barY] {
|
||||||
renderer.fillRect(boxXWithBar, boxY, boxWidthWithBar, boxHeightWithBar, false);
|
renderer.fillRect(boxXWithBar, boxY, boxWidthWithBar, boxHeightWithBar, false);
|
||||||
@ -300,8 +298,8 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
|
renderer.displayBuffer(EInkDisplay::FAST_REFRESH);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, SETTINGS.extraParagraphSpacing, viewportWidth,
|
if (!section->createSectionFile(READER_FONT_ID, lineCompression, SETTINGS.extraParagraphSpacing, viewportWidth,
|
||||||
viewportHeight, progressSetup, progressCallback)) {
|
viewportHeight, progressSetup, progressCallback)) {
|
||||||
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
||||||
section.reset();
|
section.reset();
|
||||||
return;
|
return;
|
||||||
@ -336,7 +334,7 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto p = section->loadPageFromSD();
|
auto p = section->loadPageFromSectionFile();
|
||||||
if (!p) {
|
if (!p) {
|
||||||
Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis());
|
Serial.printf("[%lu] [ERS] Failed to load page from SD - clearing section cache\n", millis());
|
||||||
section->clearCache();
|
section->clearCache();
|
||||||
|
|||||||
@ -27,9 +27,9 @@ class EpubReaderActivity final : public ActivityWithSubactivity {
|
|||||||
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
|
void renderStatusBar(int orientedMarginRight, int orientedMarginBottom, int orientedMarginLeft) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub,
|
explicit EpubReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Epub> epub,
|
||||||
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome)
|
||||||
: ActivityWithSubactivity("EpubReader", renderer, inputManager),
|
: ActivityWithSubactivity("EpubReader", renderer, mappedInput),
|
||||||
epub(std::move(epub)),
|
epub(std::move(epub)),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
onGoHome(onGoHome) {}
|
onGoHome(onGoHome) {}
|
||||||
|
|||||||
@ -66,17 +66,17 @@ void EpubReaderChapterSelectionActivity::onExit() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionActivity::loop() {
|
void EpubReaderChapterSelectionActivity::loop() {
|
||||||
const bool prevReleased =
|
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
const bool nextReleased =
|
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS;
|
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
|
|
||||||
if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
onSelectSpineIndex(selectorIndex);
|
onSelectSpineIndex(selectorIndex);
|
||||||
} else if (inputManager.wasReleased(InputManager::BTN_BACK)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
} else if (prevReleased) {
|
} else if (prevReleased) {
|
||||||
if (skipPage) {
|
if (skipPage) {
|
||||||
|
|||||||
@ -27,11 +27,11 @@ class EpubReaderChapterSelectionActivity final : public Activity {
|
|||||||
void renderScreen();
|
void renderScreen();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
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)
|
||||||
: Activity("EpubReaderChapterSelection", renderer, inputManager),
|
: Activity("EpubReaderChapterSelection", renderer, mappedInput),
|
||||||
epub(epub),
|
epub(epub),
|
||||||
currentSpineIndex(currentSpineIndex),
|
currentSpineIndex(currentSpineIndex),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
|
|||||||
@ -89,7 +89,7 @@ void FileSelectionActivity::onExit() {
|
|||||||
|
|
||||||
void FileSelectionActivity::loop() {
|
void FileSelectionActivity::loop() {
|
||||||
// Long press BACK (1s+) goes to root folder
|
// Long press BACK (1s+) goes to root folder
|
||||||
if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= GO_HOME_MS) {
|
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS) {
|
||||||
if (basepath != "/") {
|
if (basepath != "/") {
|
||||||
basepath = "/";
|
basepath = "/";
|
||||||
loadFiles();
|
loadFiles();
|
||||||
@ -98,14 +98,14 @@ void FileSelectionActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool prevReleased =
|
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
const bool nextReleased =
|
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS;
|
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
||||||
|
|
||||||
if (inputManager.wasReleased(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -118,9 +118,9 @@ void FileSelectionActivity::loop() {
|
|||||||
} else {
|
} else {
|
||||||
onSelect(basepath + files[selectorIndex]);
|
onSelect(basepath + files[selectorIndex]);
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasReleased(InputManager::BTN_BACK)) {
|
} else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
|
||||||
// Short press: go up one directory, or go home if at root
|
// Short press: go up one directory, or go home if at root
|
||||||
if (inputManager.getHeldTime() < GO_HOME_MS) {
|
if (mappedInput.getHeldTime() < GO_HOME_MS) {
|
||||||
if (basepath != "/") {
|
if (basepath != "/") {
|
||||||
basepath.replace(basepath.find_last_of('/'), std::string::npos, "");
|
basepath.replace(basepath.find_last_of('/'), std::string::npos, "");
|
||||||
if (basepath.empty()) basepath = "/";
|
if (basepath.empty()) basepath = "/";
|
||||||
@ -166,7 +166,8 @@ void FileSelectionActivity::render() const {
|
|||||||
renderer.drawCenteredText(READER_FONT_ID, 10, "Books", true, BOLD);
|
renderer.drawCenteredText(READER_FONT_ID, 10, "Books", true, BOLD);
|
||||||
|
|
||||||
// Help text
|
// Help text
|
||||||
renderer.drawButtonHints(UI_FONT_ID, "« Home", "Open", "", "");
|
const auto labels = mappedInput.mapLabels("« Home", "Open", "", "");
|
||||||
|
renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
renderer.drawText(UI_FONT_ID, 20, 60, "No books found");
|
renderer.drawText(UI_FONT_ID, 20, 60, "No books found");
|
||||||
|
|||||||
@ -25,10 +25,10 @@ class FileSelectionActivity final : public Activity {
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit FileSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
const std::function<void(const std::string&)>& onSelect,
|
const std::function<void(const std::string&)>& onSelect,
|
||||||
const std::function<void()>& onGoHome, std::string initialPath = "/")
|
const std::function<void()>& onGoHome, std::string initialPath = "/")
|
||||||
: Activity("FileSelection", renderer, inputManager),
|
: Activity("FileSelection", renderer, mappedInput),
|
||||||
basepath(initialPath.empty() ? "/" : std::move(initialPath)),
|
basepath(initialPath.empty() ? "/" : std::move(initialPath)),
|
||||||
onSelect(onSelect),
|
onSelect(onSelect),
|
||||||
onGoHome(onGoHome) {}
|
onGoHome(onGoHome) {}
|
||||||
|
|||||||
@ -61,7 +61,7 @@ std::unique_ptr<Xtc> ReaderActivity::loadXtc(const std::string& path) {
|
|||||||
void ReaderActivity::onSelectBookFile(const std::string& path) {
|
void ReaderActivity::onSelectBookFile(const std::string& path) {
|
||||||
currentBookPath = path; // Track current book path
|
currentBookPath = path; // Track current book path
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading..."));
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInput, "Loading..."));
|
||||||
|
|
||||||
if (isXtcFile(path)) {
|
if (isXtcFile(path)) {
|
||||||
// Load XTC file
|
// Load XTC file
|
||||||
@ -70,7 +70,7 @@ void ReaderActivity::onSelectBookFile(const std::string& path) {
|
|||||||
onGoToXtcReader(std::move(xtc));
|
onGoToXtcReader(std::move(xtc));
|
||||||
} else {
|
} else {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load XTC", REGULAR,
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInput, "Failed to load XTC", REGULAR,
|
||||||
EInkDisplay::HALF_REFRESH));
|
EInkDisplay::HALF_REFRESH));
|
||||||
delay(2000);
|
delay(2000);
|
||||||
onGoToFileSelection();
|
onGoToFileSelection();
|
||||||
@ -82,7 +82,7 @@ void ReaderActivity::onSelectBookFile(const std::string& path) {
|
|||||||
onGoToEpubReader(std::move(epub));
|
onGoToEpubReader(std::move(epub));
|
||||||
} else {
|
} else {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load epub", REGULAR,
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInput, "Failed to load epub", REGULAR,
|
||||||
EInkDisplay::HALF_REFRESH));
|
EInkDisplay::HALF_REFRESH));
|
||||||
delay(2000);
|
delay(2000);
|
||||||
onGoToFileSelection();
|
onGoToFileSelection();
|
||||||
@ -95,7 +95,7 @@ void ReaderActivity::onGoToFileSelection(const std::string& fromBookPath) {
|
|||||||
// If coming from a book, start in that book's folder; otherwise start from root
|
// If coming from a book, start in that book's folder; otherwise start from root
|
||||||
const auto initialPath = fromBookPath.empty() ? "/" : extractFolderPath(fromBookPath);
|
const auto initialPath = fromBookPath.empty() ? "/" : extractFolderPath(fromBookPath);
|
||||||
enterNewActivity(new FileSelectionActivity(
|
enterNewActivity(new FileSelectionActivity(
|
||||||
renderer, inputManager, [this](const std::string& path) { onSelectBookFile(path); }, onGoBack, initialPath));
|
renderer, mappedInput, [this](const std::string& path) { onSelectBookFile(path); }, onGoBack, initialPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
||||||
@ -103,7 +103,7 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
|||||||
currentBookPath = epubPath;
|
currentBookPath = epubPath;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new EpubReaderActivity(
|
enterNewActivity(new EpubReaderActivity(
|
||||||
renderer, inputManager, std::move(epub), [this, epubPath] { onGoToFileSelection(epubPath); },
|
renderer, mappedInput, std::move(epub), [this, epubPath] { onGoToFileSelection(epubPath); },
|
||||||
[this] { onGoBack(); }));
|
[this] { onGoBack(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ void ReaderActivity::onGoToXtcReader(std::unique_ptr<Xtc> xtc) {
|
|||||||
currentBookPath = xtcPath;
|
currentBookPath = xtcPath;
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new XtcReaderActivity(
|
enterNewActivity(new XtcReaderActivity(
|
||||||
renderer, inputManager, std::move(xtc), [this, xtcPath] { onGoToFileSelection(xtcPath); },
|
renderer, mappedInput, std::move(xtc), [this, xtcPath] { onGoToFileSelection(xtcPath); },
|
||||||
[this] { onGoBack(); }));
|
[this] { onGoBack(); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,9 +21,9 @@ class ReaderActivity final : public ActivityWithSubactivity {
|
|||||||
void onGoToXtcReader(std::unique_ptr<Xtc> xtc);
|
void onGoToXtcReader(std::unique_ptr<Xtc> xtc);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::string initialBookPath,
|
explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath,
|
||||||
const std::function<void()>& onGoBack)
|
const std::function<void()>& onGoBack)
|
||||||
: ActivityWithSubactivity("Reader", renderer, inputManager),
|
: ActivityWithSubactivity("Reader", renderer, mappedInput),
|
||||||
initialBookPath(std::move(initialBookPath)),
|
initialBookPath(std::move(initialBookPath)),
|
||||||
onGoBack(onGoBack) {}
|
onGoBack(onGoBack) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|||||||
@ -71,21 +71,21 @@ void XtcReaderActivity::onExit() {
|
|||||||
|
|
||||||
void XtcReaderActivity::loop() {
|
void XtcReaderActivity::loop() {
|
||||||
// Long press BACK (1s+) goes directly to home
|
// Long press BACK (1s+) goes directly to home
|
||||||
if (inputManager.isPressed(InputManager::BTN_BACK) && inputManager.getHeldTime() >= goHomeMs) {
|
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= goHomeMs) {
|
||||||
onGoHome();
|
onGoHome();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Short press BACK goes to file selection
|
// Short press BACK goes to file selection
|
||||||
if (inputManager.wasReleased(InputManager::BTN_BACK) && inputManager.getHeldTime() < goHomeMs) {
|
if (mappedInput.wasReleased(MappedInputManager::Button::Back) && mappedInput.getHeldTime() < goHomeMs) {
|
||||||
onGoBack();
|
onGoBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool prevReleased =
|
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::PageBack) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||||
const bool nextReleased =
|
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::PageForward) ||
|
||||||
inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
mappedInput.wasReleased(MappedInputManager::Button::Right);
|
||||||
|
|
||||||
if (!prevReleased && !nextReleased) {
|
if (!prevReleased && !nextReleased) {
|
||||||
return;
|
return;
|
||||||
@ -98,7 +98,7 @@ void XtcReaderActivity::loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skipPages = inputManager.getHeldTime() > skipPageMs;
|
const bool skipPages = mappedInput.getHeldTime() > skipPageMs;
|
||||||
const int skipAmount = skipPages ? 10 : 1;
|
const int skipAmount = skipPages ? 10 : 1;
|
||||||
|
|
||||||
if (prevReleased) {
|
if (prevReleased) {
|
||||||
|
|||||||
@ -32,9 +32,9 @@ class XtcReaderActivity final : public Activity {
|
|||||||
void loadProgress();
|
void loadProgress();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit XtcReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Xtc> xtc,
|
explicit XtcReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::unique_ptr<Xtc> xtc,
|
||||||
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoBack, const std::function<void()>& onGoHome)
|
||||||
: Activity("XtcReader", renderer, inputManager), xtc(std::move(xtc)), onGoBack(onGoBack), onGoHome(onGoHome) {}
|
: Activity("XtcReader", renderer, mappedInput), xtc(std::move(xtc)), onGoBack(onGoBack), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -72,7 +72,7 @@ void OtaUpdateActivity::onEnter() {
|
|||||||
|
|
||||||
// Launch WiFi selection subactivity
|
// Launch WiFi selection subactivity
|
||||||
Serial.printf("[%lu] [OTA] Launching WifiSelectionActivity...\n", millis());
|
Serial.printf("[%lu] [OTA] Launching WifiSelectionActivity...\n", millis());
|
||||||
enterNewActivity(new WifiSelectionActivity(renderer, inputManager,
|
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +191,7 @@ void OtaUpdateActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == WAITING_CONFIRMATION) {
|
if (state == WAITING_CONFIRMATION) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
Serial.printf("[%lu] [OTA] New update available, starting download...\n", millis());
|
Serial.printf("[%lu] [OTA] New update available, starting download...\n", millis());
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
state = UPDATE_IN_PROGRESS;
|
state = UPDATE_IN_PROGRESS;
|
||||||
@ -215,7 +215,7 @@ void OtaUpdateActivity::loop() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,14 +223,14 @@ void OtaUpdateActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (state == FAILED) {
|
if (state == FAILED) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == NO_UPDATE) {
|
if (state == NO_UPDATE) {
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
goBack();
|
goBack();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -35,8 +35,9 @@ class OtaUpdateActivity : public ActivityWithSubactivity {
|
|||||||
void render();
|
void render();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit OtaUpdateActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& goBack)
|
explicit OtaUpdateActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
: ActivityWithSubactivity("OtaUpdate", renderer, inputManager), goBack(goBack), updater() {}
|
const std::function<void()>& goBack)
|
||||||
|
: ActivityWithSubactivity("OtaUpdate", renderer, mappedInput), goBack(goBack), updater() {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
// Define the static settings list
|
// Define the static settings list
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int settingsCount = 6;
|
constexpr int settingsCount = 7;
|
||||||
const SettingInfo settingsList[settingsCount] = {
|
const SettingInfo settingsList[settingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
{"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
|
{"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
|
||||||
@ -20,6 +20,10 @@ const SettingInfo settingsList[settingsCount] = {
|
|||||||
SettingType::ENUM,
|
SettingType::ENUM,
|
||||||
&CrossPointSettings::orientation,
|
&CrossPointSettings::orientation,
|
||||||
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}},
|
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}},
|
||||||
|
{"Front Button Layout",
|
||||||
|
SettingType::ENUM,
|
||||||
|
&CrossPointSettings::frontButtonLayout,
|
||||||
|
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm"}},
|
||||||
{"Check for updates", SettingType::ACTION, nullptr, {}},
|
{"Check for updates", SettingType::ACTION, nullptr, {}},
|
||||||
};
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
@ -68,24 +72,26 @@ void SettingsActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle actions with early return
|
// Handle actions with early return
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
toggleCurrentSetting();
|
toggleCurrentSetting();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
onGoHome();
|
onGoHome();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle navigation
|
// Handle navigation
|
||||||
if (inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
// Move selection up (with wrap-around)
|
// Move selection up (with wrap-around)
|
||||||
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1);
|
selectedSettingIndex = (selectedSettingIndex > 0) ? (selectedSettingIndex - 1) : (settingsCount - 1);
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||||
|
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
// Move selection down
|
// Move selection down
|
||||||
if (selectedSettingIndex < settingsCount - 1) {
|
if (selectedSettingIndex < settingsCount - 1) {
|
||||||
selectedSettingIndex++;
|
selectedSettingIndex++;
|
||||||
@ -113,7 +119,7 @@ void SettingsActivity::toggleCurrentSetting() {
|
|||||||
if (std::string(setting.name) == "Check for updates") {
|
if (std::string(setting.name) == "Check for updates") {
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new OtaUpdateActivity(renderer, inputManager, [this] {
|
enterNewActivity(new OtaUpdateActivity(renderer, mappedInput, [this] {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
@ -173,10 +179,13 @@ void SettingsActivity::render() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw help text
|
// Draw version text above button hints
|
||||||
renderer.drawButtonHints(UI_FONT_ID, "« Save", "Toggle", "", "");
|
|
||||||
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||||
pageHeight - 30, CROSSPOINT_VERSION);
|
pageHeight - 60, CROSSPOINT_VERSION);
|
||||||
|
|
||||||
|
// Draw help text
|
||||||
|
const auto labels = mappedInput.mapLabels("« Save", "Toggle", "", "");
|
||||||
|
renderer.drawButtonHints(UI_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
|
|
||||||
// Always use standard refresh for settings screen
|
// Always use standard refresh for settings screen
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
|
|||||||
@ -34,8 +34,9 @@ class SettingsActivity final : public ActivityWithSubactivity {
|
|||||||
void toggleCurrentSetting();
|
void toggleCurrentSetting();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome)
|
explicit SettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
: ActivityWithSubactivity("Settings", renderer, inputManager), onGoHome(onGoHome) {}
|
const std::function<void()>& onGoHome)
|
||||||
|
: ActivityWithSubactivity("Settings", renderer, mappedInput), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@ -13,10 +13,10 @@ class FullScreenMessageActivity final : public Activity {
|
|||||||
EInkDisplay::RefreshMode refreshMode;
|
EInkDisplay::RefreshMode refreshMode;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FullScreenMessageActivity(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
explicit FullScreenMessageActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string text,
|
||||||
const EpdFontStyle style = REGULAR,
|
const EpdFontStyle style = REGULAR,
|
||||||
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
||||||
: Activity("FullScreenMessage", renderer, inputManager),
|
: Activity("FullScreenMessage", renderer, mappedInput),
|
||||||
text(std::move(text)),
|
text(std::move(text)),
|
||||||
style(style),
|
style(style),
|
||||||
refreshMode(refreshMode) {}
|
refreshMode(refreshMode) {}
|
||||||
|
|||||||
@ -138,7 +138,7 @@ void KeyboardEntryActivity::handleKeyPress() {
|
|||||||
|
|
||||||
void KeyboardEntryActivity::loop() {
|
void KeyboardEntryActivity::loop() {
|
||||||
// Navigation
|
// Navigation
|
||||||
if (inputManager.wasPressed(InputManager::BTN_UP)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Up)) {
|
||||||
if (selectedRow > 0) {
|
if (selectedRow > 0) {
|
||||||
selectedRow--;
|
selectedRow--;
|
||||||
// Clamp column to valid range for new row
|
// Clamp column to valid range for new row
|
||||||
@ -148,7 +148,7 @@ void KeyboardEntryActivity::loop() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_DOWN)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Down)) {
|
||||||
if (selectedRow < NUM_ROWS - 1) {
|
if (selectedRow < NUM_ROWS - 1) {
|
||||||
selectedRow++;
|
selectedRow++;
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
@ -157,7 +157,7 @@ void KeyboardEntryActivity::loop() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_LEFT)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||||
// Special bottom row case
|
// Special bottom row case
|
||||||
if (selectedRow == SPECIAL_ROW) {
|
if (selectedRow == SPECIAL_ROW) {
|
||||||
// Bottom row has special key widths
|
// Bottom row has special key widths
|
||||||
@ -187,7 +187,7 @@ void KeyboardEntryActivity::loop() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_RIGHT)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||||
const int maxCol = getRowLength(selectedRow) - 1;
|
const int maxCol = getRowLength(selectedRow) - 1;
|
||||||
|
|
||||||
// Special bottom row case
|
// Special bottom row case
|
||||||
@ -220,13 +220,13 @@ void KeyboardEntryActivity::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Selection
|
// Selection
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
handleKeyPress();
|
handleKeyPress();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel
|
// Cancel
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
if (onCancel) {
|
if (onCancel) {
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <InputManager.h>
|
|
||||||
#include <freertos/FreeRTOS.h>
|
#include <freertos/FreeRTOS.h>
|
||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
@ -31,7 +30,7 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param renderer Reference to the GfxRenderer for drawing
|
* @param renderer Reference to the GfxRenderer for drawing
|
||||||
* @param inputManager Reference to InputManager for handling input
|
* @param mappedInput Reference to MappedInputManager for handling input
|
||||||
* @param title Title to display above the keyboard
|
* @param title Title to display above the keyboard
|
||||||
* @param initialText Initial text to show in the input field
|
* @param initialText Initial text to show in the input field
|
||||||
* @param startY Y position to start rendering the keyboard
|
* @param startY Y position to start rendering the keyboard
|
||||||
@ -40,11 +39,11 @@ class KeyboardEntryActivity : public Activity {
|
|||||||
* @param onComplete Callback invoked when input is complete
|
* @param onComplete Callback invoked when input is complete
|
||||||
* @param onCancel Callback invoked when input is cancelled
|
* @param onCancel Callback invoked when input is cancelled
|
||||||
*/
|
*/
|
||||||
explicit KeyboardEntryActivity(GfxRenderer& renderer, InputManager& inputManager, std::string title = "Enter Text",
|
explicit KeyboardEntryActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||||
std::string initialText = "", const int startY = 10, const size_t maxLength = 0,
|
std::string title = "Enter Text", std::string initialText = "", const int startY = 10,
|
||||||
const bool isPassword = false, OnCompleteCallback onComplete = nullptr,
|
const size_t maxLength = 0, const bool isPassword = false,
|
||||||
OnCancelCallback onCancel = nullptr)
|
OnCompleteCallback onComplete = nullptr, OnCancelCallback onCancel = nullptr)
|
||||||
: Activity("KeyboardEntry", renderer, inputManager),
|
: Activity("KeyboardEntry", renderer, mappedInput),
|
||||||
title(std::move(title)),
|
title(std::move(title)),
|
||||||
text(std::move(initialText)),
|
text(std::move(initialText)),
|
||||||
startY(startY),
|
startY(startY),
|
||||||
|
|||||||
16
src/main.cpp
16
src/main.cpp
@ -16,6 +16,7 @@
|
|||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
#include "MappedInputManager.h"
|
||||||
#include "activities/boot_sleep/BootActivity.h"
|
#include "activities/boot_sleep/BootActivity.h"
|
||||||
#include "activities/boot_sleep/SleepActivity.h"
|
#include "activities/boot_sleep/SleepActivity.h"
|
||||||
#include "activities/home/HomeActivity.h"
|
#include "activities/home/HomeActivity.h"
|
||||||
@ -41,6 +42,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;
|
||||||
|
MappedInputManager mappedInputManager(inputManager);
|
||||||
GfxRenderer renderer(einkDisplay);
|
GfxRenderer renderer(einkDisplay);
|
||||||
Activity* currentActivity;
|
Activity* currentActivity;
|
||||||
|
|
||||||
@ -124,7 +126,7 @@ void waitForPowerRelease() {
|
|||||||
// Enter deep sleep mode
|
// Enter deep sleep mode
|
||||||
void enterDeepSleep() {
|
void enterDeepSleep() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new SleepActivity(renderer, inputManager));
|
enterNewActivity(new SleepActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
einkDisplay.deepSleep();
|
einkDisplay.deepSleep();
|
||||||
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
Serial.printf("[%lu] [ ] Power button press calibration value: %lu ms\n", millis(), t2 - t1);
|
||||||
@ -139,24 +141,24 @@ void enterDeepSleep() {
|
|||||||
void onGoHome();
|
void onGoHome();
|
||||||
void onGoToReader(const std::string& initialEpubPath) {
|
void onGoToReader(const std::string& initialEpubPath) {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new ReaderActivity(renderer, inputManager, initialEpubPath, onGoHome));
|
enterNewActivity(new ReaderActivity(renderer, mappedInputManager, initialEpubPath, onGoHome));
|
||||||
}
|
}
|
||||||
void onGoToReaderHome() { onGoToReader(std::string()); }
|
void onGoToReaderHome() { onGoToReader(std::string()); }
|
||||||
void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); }
|
void onContinueReading() { onGoToReader(APP_STATE.openEpubPath); }
|
||||||
|
|
||||||
void onGoToFileTransfer() {
|
void onGoToFileTransfer() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new CrossPointWebServerActivity(renderer, inputManager, onGoHome));
|
enterNewActivity(new CrossPointWebServerActivity(renderer, mappedInputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoToSettings() {
|
void onGoToSettings() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome));
|
enterNewActivity(new SettingsActivity(renderer, mappedInputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new HomeActivity(renderer, inputManager, onContinueReading, onGoToReaderHome, onGoToSettings,
|
enterNewActivity(new HomeActivity(renderer, mappedInputManager, onContinueReading, onGoToReaderHome, onGoToSettings,
|
||||||
onGoToFileTransfer));
|
onGoToFileTransfer));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,7 +195,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(renderer, inputManager, "SD card error", BOLD));
|
enterNewActivity(new FullScreenMessageActivity(renderer, mappedInputManager, "SD card error", BOLD));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,7 +207,7 @@ void setup() {
|
|||||||
setupDisplayAndFonts();
|
setupDisplayAndFonts();
|
||||||
|
|
||||||
exitActivity();
|
exitActivity();
|
||||||
enterNewActivity(new BootActivity(renderer, inputManager));
|
enterNewActivity(new BootActivity(renderer, mappedInputManager));
|
||||||
|
|
||||||
APP_STATE.loadFromFile();
|
APP_STATE.loadFromFile();
|
||||||
if (APP_STATE.openEpubPath.empty()) {
|
if (APP_STATE.openEpubPath.empty()) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user