Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
021f77eab3 | ||
|
|
6d3d25a288 | ||
|
|
9a33030623 | ||
|
|
6414f85257 |
@@ -146,8 +146,8 @@ void EpubHtmlParser::makePages() {
|
|||||||
currentPage = new Page();
|
currentPage = new Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
const int lineHeight = renderer->getLineHeight();
|
const int lineHeight = renderer.getLineHeight();
|
||||||
const int pageHeight = renderer->getPageHeight();
|
const int pageHeight = renderer.getPageHeight();
|
||||||
|
|
||||||
// Long running task, make sure to let other things happen
|
// Long running task, make sure to let other things happen
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class EpdRenderer;
|
|||||||
|
|
||||||
class EpubHtmlParser final : public tinyxml2::XMLVisitor {
|
class EpubHtmlParser final : public tinyxml2::XMLVisitor {
|
||||||
const char* filepath;
|
const char* filepath;
|
||||||
EpdRenderer* renderer;
|
EpdRenderer& renderer;
|
||||||
std::function<void(Page*)> completePageFn;
|
std::function<void(Page*)> completePageFn;
|
||||||
|
|
||||||
bool insideBoldTag = false;
|
bool insideBoldTag = false;
|
||||||
@@ -27,7 +27,7 @@ class EpubHtmlParser final : public tinyxml2::XMLVisitor {
|
|||||||
bool VisitExit(const tinyxml2::XMLElement& element) override;
|
bool VisitExit(const tinyxml2::XMLElement& element) override;
|
||||||
// xml parser callbacks
|
// xml parser callbacks
|
||||||
public:
|
public:
|
||||||
explicit EpubHtmlParser(const char* filepath, EpdRenderer* renderer, const std::function<void(Page*)>& completePageFn)
|
explicit EpubHtmlParser(const char* filepath, EpdRenderer& renderer, const std::function<void(Page*)>& completePageFn)
|
||||||
: filepath(filepath), renderer(renderer), completePageFn(completePageFn) {}
|
: filepath(filepath), renderer(renderer), completePageFn(completePageFn) {}
|
||||||
~EpubHtmlParser() override = default;
|
~EpubHtmlParser() override = default;
|
||||||
bool parseAndBuildPages();
|
bool parseAndBuildPages();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void PageLine::render(EpdRenderer* renderer) { block->render(renderer, 0, yPos); }
|
void PageLine::render(EpdRenderer& renderer) { block->render(renderer, 0, yPos); }
|
||||||
|
|
||||||
void PageLine::serialize(std::ostream& os) {
|
void PageLine::serialize(std::ostream& os) {
|
||||||
serialization::writePod(os, yPos);
|
serialization::writePod(os, yPos);
|
||||||
@@ -20,7 +20,7 @@ PageLine* PageLine::deserialize(std::istream& is) {
|
|||||||
return new PageLine(tb, yPos);
|
return new PageLine(tb, yPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::render(EpdRenderer* renderer) const {
|
void Page::render(EpdRenderer& renderer) const {
|
||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
element->render(renderer);
|
element->render(renderer);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class PageElement {
|
|||||||
int yPos;
|
int yPos;
|
||||||
explicit PageElement(const int yPos) : yPos(yPos) {}
|
explicit PageElement(const int yPos) : yPos(yPos) {}
|
||||||
virtual ~PageElement() = default;
|
virtual ~PageElement() = default;
|
||||||
virtual void render(EpdRenderer* renderer) = 0;
|
virtual void render(EpdRenderer& renderer) = 0;
|
||||||
virtual void serialize(std::ostream& os) = 0;
|
virtual void serialize(std::ostream& os) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ class PageLine final : public PageElement {
|
|||||||
public:
|
public:
|
||||||
PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {}
|
PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {}
|
||||||
~PageLine() override { delete block; }
|
~PageLine() override { delete block; }
|
||||||
void render(EpdRenderer* renderer) override;
|
void render(EpdRenderer& renderer) override;
|
||||||
void serialize(std::ostream& os) override;
|
void serialize(std::ostream& os) override;
|
||||||
static PageLine* deserialize(std::istream& is);
|
static PageLine* deserialize(std::istream& is);
|
||||||
};
|
};
|
||||||
@@ -32,7 +32,7 @@ class Page {
|
|||||||
int nextY = 0;
|
int nextY = 0;
|
||||||
// the list of block index and line numbers on this page
|
// the list of block index and line numbers on this page
|
||||||
std::vector<PageElement*> elements;
|
std::vector<PageElement*> elements;
|
||||||
void render(EpdRenderer* renderer) const;
|
void render(EpdRenderer& renderer) const;
|
||||||
~Page() {
|
~Page() {
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
delete element;
|
delete element;
|
||||||
|
|||||||
@@ -107,11 +107,11 @@ void Section::renderPage() {
|
|||||||
delete p;
|
delete p;
|
||||||
} else if (pageCount == 0) {
|
} else if (pageCount == 0) {
|
||||||
Serial.println("No pages to render");
|
Serial.println("No pages to render");
|
||||||
const int width = renderer->getTextWidth("Empty chapter", BOLD);
|
const int width = renderer.getTextWidth("Empty chapter", BOLD);
|
||||||
renderer->drawText((renderer->getPageWidth() - width) / 2, 300, "Empty chapter", 1, BOLD);
|
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", 1, BOLD);
|
||||||
} else {
|
} else {
|
||||||
Serial.printf("Page out of bounds: %d (max %d)\n", currentPage, pageCount);
|
Serial.printf("Page out of bounds: %d (max %d)\n", currentPage, pageCount);
|
||||||
const int width = renderer->getTextWidth("Out of bounds", BOLD);
|
const int width = renderer.getTextWidth("Out of bounds", BOLD);
|
||||||
renderer->drawText((renderer->getPageWidth() - width) / 2, 300, "Out of bounds", 1, BOLD);
|
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", 1, BOLD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class EpdRenderer;
|
|||||||
class Section {
|
class Section {
|
||||||
Epub* epub;
|
Epub* epub;
|
||||||
const int spineIndex;
|
const int spineIndex;
|
||||||
EpdRenderer* renderer;
|
EpdRenderer& renderer;
|
||||||
std::string cachePath;
|
std::string cachePath;
|
||||||
|
|
||||||
void onPageComplete(const Page* page);
|
void onPageComplete(const Page* page);
|
||||||
@@ -16,7 +16,7 @@ class Section {
|
|||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
|
|
||||||
explicit Section(Epub* epub, const int spineIndex, EpdRenderer* renderer)
|
explicit Section(Epub* epub, const int spineIndex, EpdRenderer& renderer)
|
||||||
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
||||||
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
|||||||
class Block {
|
class Block {
|
||||||
public:
|
public:
|
||||||
virtual ~Block() = default;
|
virtual ~Block() = default;
|
||||||
virtual void layout(EpdRenderer* renderer) = 0;
|
virtual void layout(EpdRenderer& renderer) = 0;
|
||||||
virtual BlockType getType() = 0;
|
virtual BlockType getType() = 0;
|
||||||
virtual bool isEmpty() = 0;
|
virtual bool isEmpty() = 0;
|
||||||
virtual void finish() {}
|
virtual void finish() {}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ void TextBlock::addSpan(const std::string& span, const bool is_bold, const bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer* renderer) {
|
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
||||||
const int totalWordCount = words.size();
|
const int totalWordCount = words.size();
|
||||||
const int pageWidth = renderer->getPageWidth();
|
const int pageWidth = renderer.getPageWidth();
|
||||||
const int spaceWidth = renderer->getSpaceWidth();
|
const int spaceWidth = renderer.getSpaceWidth();
|
||||||
|
|
||||||
words.shrink_to_fit();
|
words.shrink_to_fit();
|
||||||
wordStyles.shrink_to_fit();
|
wordStyles.shrink_to_fit();
|
||||||
@@ -66,7 +66,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer* renderer) {
|
|||||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||||
fontStyle = ITALIC;
|
fontStyle = ITALIC;
|
||||||
}
|
}
|
||||||
const int width = renderer->getTextWidth(words[i].c_str(), fontStyle);
|
const int width = renderer.getTextWidth(words[i].c_str(), fontStyle);
|
||||||
wordWidths[i] = width;
|
wordWidths[i] = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer* renderer) {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBlock::render(const EpdRenderer* renderer, const int x, const int y) const {
|
void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) const {
|
||||||
for (int i = 0; i < words.size(); i++) {
|
for (int i = 0; i < words.size(); i++) {
|
||||||
// get the style
|
// get the style
|
||||||
const uint8_t wordStyle = wordStyles[i];
|
const uint8_t wordStyle = wordStyles[i];
|
||||||
@@ -203,7 +203,7 @@ void TextBlock::render(const EpdRenderer* renderer, const int x, const int y) co
|
|||||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||||
fontStyle = ITALIC;
|
fontStyle = ITALIC;
|
||||||
}
|
}
|
||||||
renderer->drawText(x + wordXpos[i], y, words[i].c_str(), 1, fontStyle);
|
renderer.drawText(x + wordXpos[i], y, words[i].c_str(), 1, fontStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ class TextBlock final : public Block {
|
|||||||
void set_style(const BLOCK_STYLE style) { this->style = style; }
|
void set_style(const BLOCK_STYLE style) { this->style = style; }
|
||||||
BLOCK_STYLE get_style() const { return style; }
|
BLOCK_STYLE get_style() const { return style; }
|
||||||
bool isEmpty() override { return words.empty(); }
|
bool isEmpty() override { return words.empty(); }
|
||||||
void layout(EpdRenderer* renderer) override {};
|
void layout(EpdRenderer& renderer) override {};
|
||||||
// given a renderer works out where to break the words into lines
|
// given a renderer works out where to break the words into lines
|
||||||
std::list<TextBlock*> splitIntoLines(const EpdRenderer* renderer);
|
std::list<TextBlock*> splitIntoLines(const EpdRenderer& renderer);
|
||||||
void render(const EpdRenderer* renderer, int x, int y) const;
|
void render(const EpdRenderer& renderer, int x, int y) const;
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
BlockType getType() override { return TEXT_BLOCK; }
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(std::ostream& os) const;
|
||||||
static TextBlock* deserialize(std::istream& is);
|
static TextBlock* deserialize(std::istream& is);
|
||||||
|
|||||||
Submodule open-x4-sdk updated: 90a19bb8a7...8224d278c5
@@ -34,3 +34,4 @@ lib_deps =
|
|||||||
zinggjm/GxEPD2@^1.6.5
|
zinggjm/GxEPD2@^1.6.5
|
||||||
https://github.com/leethomason/tinyxml2.git#11.0.0
|
https://github.com/leethomason/tinyxml2.git#11.0.0
|
||||||
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
|
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
|
||||||
|
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
||||||
|
|||||||
@@ -9,38 +9,27 @@
|
|||||||
constexpr uint8_t STATE_VERSION = 1;
|
constexpr uint8_t STATE_VERSION = 1;
|
||||||
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
||||||
|
|
||||||
void CrossPointState::serialize(std::ostream& os) const {
|
bool CrossPointState::saveToFile() const {
|
||||||
serialization::writePod(os, STATE_VERSION);
|
std::ofstream outputFile(STATE_FILE);
|
||||||
serialization::writeString(os, openEpubPath);
|
serialization::writePod(outputFile, STATE_VERSION);
|
||||||
|
serialization::writeString(outputFile, openEpubPath);
|
||||||
|
outputFile.close();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CrossPointState* CrossPointState::deserialize(std::istream& is) {
|
bool CrossPointState::loadFromFile() {
|
||||||
const auto state = new CrossPointState();
|
std::ifstream inputFile(STATE_FILE);
|
||||||
|
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
serialization::readPod(is, version);
|
serialization::readPod(inputFile, version);
|
||||||
if (version != STATE_VERSION) {
|
if (version != STATE_VERSION) {
|
||||||
Serial.printf("CrossPointState: Unknown version %u\n", version);
|
Serial.printf("CrossPointState: Unknown version %u\n", version);
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
serialization::readString(is, state->openEpubPath);
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CrossPointState::saveToFile() const {
|
|
||||||
std::ofstream outputFile(STATE_FILE);
|
|
||||||
serialize(outputFile);
|
|
||||||
outputFile.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
CrossPointState* CrossPointState::loadFromFile() {
|
|
||||||
if (!SD.exists(&STATE_FILE[3])) {
|
|
||||||
return new CrossPointState();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream inputFile(STATE_FILE);
|
|
||||||
CrossPointState* state = deserialize(inputFile);
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
return state;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
serialization::readString(inputFile, openEpubPath);
|
||||||
|
|
||||||
|
inputFile.close();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class CrossPointState {
|
class CrossPointState {
|
||||||
void serialize(std::ostream& os) const;
|
|
||||||
static CrossPointState* deserialize(std::istream& is);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string openEpubPath;
|
std::string openEpubPath;
|
||||||
~CrossPointState() = default;
|
~CrossPointState() = default;
|
||||||
void saveToFile() const;
|
|
||||||
static CrossPointState* loadFromFile();
|
bool saveToFile() const;
|
||||||
|
|
||||||
|
bool loadFromFile();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
#include "Input.h"
|
|
||||||
|
|
||||||
#include <esp32-hal-adc.h>
|
|
||||||
|
|
||||||
void setupInputPinModes() {
|
|
||||||
pinMode(BTN_GPIO1, INPUT);
|
|
||||||
pinMode(BTN_GPIO2, INPUT);
|
|
||||||
pinMode(BTN_GPIO3, INPUT_PULLUP); // Power button
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get currently pressed button by reading ADC values (and digital for power
|
|
||||||
// button)
|
|
||||||
Button getPressedButton() {
|
|
||||||
// Check BTN_GPIO3 (Power button) - digital read
|
|
||||||
if (digitalRead(BTN_GPIO3) == LOW) return POWER;
|
|
||||||
|
|
||||||
// Check BTN_GPIO1 (4 buttons on resistor ladder)
|
|
||||||
const int btn1 = analogRead(BTN_GPIO1);
|
|
||||||
if (btn1 < BTN_RIGHT_VAL + BTN_THRESHOLD) return RIGHT;
|
|
||||||
if (btn1 < BTN_LEFT_VAL + BTN_THRESHOLD) return LEFT;
|
|
||||||
if (btn1 < BTN_CONFIRM_VAL + BTN_THRESHOLD) return CONFIRM;
|
|
||||||
if (btn1 < BTN_BACK_VAL + BTN_THRESHOLD) return BACK;
|
|
||||||
|
|
||||||
// Check BTN_GPIO2 (2 buttons on resistor ladder)
|
|
||||||
const int btn2 = analogRead(BTN_GPIO2);
|
|
||||||
if (btn2 < BTN_VOLUME_DOWN_VAL + BTN_THRESHOLD) return VOLUME_DOWN;
|
|
||||||
if (btn2 < BTN_VOLUME_UP_VAL + BTN_THRESHOLD) return VOLUME_UP;
|
|
||||||
|
|
||||||
return NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
Input getInput(const long maxHoldMs) {
|
|
||||||
const Button button = getPressedButton();
|
|
||||||
if (button == NONE) return {NONE, 0};
|
|
||||||
|
|
||||||
const auto start = millis();
|
|
||||||
unsigned long held = 0;
|
|
||||||
while (getPressedButton() == button && (maxHoldMs < 0 || held < maxHoldMs)) {
|
|
||||||
delay(50);
|
|
||||||
held = millis() - start;
|
|
||||||
}
|
|
||||||
return {button, held};
|
|
||||||
}
|
|
||||||
28
src/Input.h
28
src/Input.h
@@ -1,28 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
// 4 buttons on ADC resistor ladder: Back, Confirm, Left, Right
|
|
||||||
#define BTN_GPIO1 1
|
|
||||||
// 2 buttons on ADC resistor ladder: Volume Up, Volume Down
|
|
||||||
#define BTN_GPIO2 2
|
|
||||||
// Power button (digital)
|
|
||||||
#define BTN_GPIO3 3
|
|
||||||
|
|
||||||
// Button ADC thresholds
|
|
||||||
#define BTN_THRESHOLD 100 // Threshold tolerance
|
|
||||||
#define BTN_RIGHT_VAL 3
|
|
||||||
#define BTN_LEFT_VAL 1500
|
|
||||||
#define BTN_CONFIRM_VAL 2700
|
|
||||||
#define BTN_BACK_VAL 3550
|
|
||||||
#define BTN_VOLUME_DOWN_VAL 3
|
|
||||||
#define BTN_VOLUME_UP_VAL 2305
|
|
||||||
|
|
||||||
enum Button { NONE = 0, RIGHT, LEFT, CONFIRM, BACK, VOLUME_UP, VOLUME_DOWN, POWER };
|
|
||||||
|
|
||||||
struct Input {
|
|
||||||
Button button;
|
|
||||||
unsigned long pressTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
void setupInputPinModes();
|
|
||||||
Button getPressedButton();
|
|
||||||
Input getInput(long maxHoldMs = -1);
|
|
||||||
93
src/main.cpp
93
src/main.cpp
@@ -2,12 +2,12 @@
|
|||||||
#include <EpdRenderer.h>
|
#include <EpdRenderer.h>
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
#include <GxEPD2_BW.h>
|
#include <GxEPD2_BW.h>
|
||||||
|
#include <InputManager.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
#include "Input.h"
|
|
||||||
#include "screens/BootLogoScreen.h"
|
#include "screens/BootLogoScreen.h"
|
||||||
#include "screens/EpubReaderScreen.h"
|
#include "screens/EpubReaderScreen.h"
|
||||||
#include "screens/FileSelectionScreen.h"
|
#include "screens/FileSelectionScreen.h"
|
||||||
@@ -30,9 +30,10 @@
|
|||||||
|
|
||||||
GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC,
|
GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC,
|
||||||
EPD_RST, EPD_BUSY));
|
EPD_RST, EPD_BUSY));
|
||||||
auto renderer = new EpdRenderer(&display);
|
InputManager inputManager;
|
||||||
|
EpdRenderer renderer(&display);
|
||||||
Screen* currentScreen;
|
Screen* currentScreen;
|
||||||
CrossPointState* appState;
|
CrossPointState appState;
|
||||||
|
|
||||||
// Power button timing
|
// Power button timing
|
||||||
// Time required to confirm boot from sleep
|
// Time required to confirm boot from sleep
|
||||||
@@ -42,7 +43,7 @@ constexpr unsigned long POWER_BUTTON_SLEEP_MS = 1000;
|
|||||||
|
|
||||||
Epub* loadEpub(const std::string& path) {
|
Epub* loadEpub(const std::string& path) {
|
||||||
if (!SD.exists(path.c_str())) {
|
if (!SD.exists(path.c_str())) {
|
||||||
Serial.println("File does not exist");
|
Serial.printf("File does not exist: %s\n", path.c_str());
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,36 +73,55 @@ void enterNewScreen(Screen* screen) {
|
|||||||
void verifyWakeupLongPress() {
|
void verifyWakeupLongPress() {
|
||||||
// Give the user up to 1000ms to start holding the power button, and must hold for POWER_BUTTON_WAKEUP_MS
|
// Give the user up to 1000ms to start holding the power button, and must hold for POWER_BUTTON_WAKEUP_MS
|
||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
auto input = getInput(POWER_BUTTON_WAKEUP_MS);
|
bool abort = false;
|
||||||
while (input.button != POWER && millis() - start < 1000) {
|
|
||||||
|
Serial.println("Verifying power button press");
|
||||||
|
inputManager.update();
|
||||||
|
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
||||||
delay(50);
|
delay(50);
|
||||||
input = getInput(POWER_BUTTON_WAKEUP_MS);
|
inputManager.update();
|
||||||
|
Serial.println("Waiting...");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.button != POWER || input.pressTime < POWER_BUTTON_WAKEUP_MS) {
|
Serial.printf("Made it? %s\n", inputManager.isPressed(InputManager::BTN_POWER) ? "yes" : "no");
|
||||||
|
if (inputManager.isPressed(InputManager::BTN_POWER)) {
|
||||||
|
do {
|
||||||
|
delay(50);
|
||||||
|
inputManager.update();
|
||||||
|
} while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < POWER_BUTTON_WAKEUP_MS);
|
||||||
|
abort = inputManager.getHeldTime() < POWER_BUTTON_WAKEUP_MS;
|
||||||
|
} else {
|
||||||
|
abort = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("held for %lu\n", inputManager.getHeldTime());
|
||||||
|
|
||||||
|
if (abort) {
|
||||||
// Button released too early. Returning to sleep.
|
// Button released too early. Returning to sleep.
|
||||||
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
// IMPORTANT: Re-arm the wakeup trigger before sleeping again
|
||||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_GPIO3, ESP_GPIO_WAKEUP_GPIO_LOW);
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||||
esp_deep_sleep_start();
|
esp_deep_sleep_start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void waitForNoButton() {
|
void waitForPowerRelease() {
|
||||||
while (getInput().button != NONE) {
|
inputManager.update();
|
||||||
|
while (inputManager.isPressed(InputManager::BTN_POWER)) {
|
||||||
delay(50);
|
delay(50);
|
||||||
|
inputManager.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter deep sleep mode
|
// Enter deep sleep mode
|
||||||
void enterDeepSleep() {
|
void enterDeepSleep() {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new SleepScreen(renderer));
|
enterNewScreen(new SleepScreen(renderer, inputManager));
|
||||||
|
|
||||||
Serial.println("Power button released after a long press. Entering deep sleep.");
|
Serial.println("Power button released after a long press. Entering deep sleep.");
|
||||||
delay(1000); // Allow Serial buffer to empty and display to update
|
delay(1000); // Allow Serial buffer to empty and display to update
|
||||||
|
|
||||||
// Enable Wakeup on LOW (button press)
|
// Enable Wakeup on LOW (button press)
|
||||||
esp_deep_sleep_enable_gpio_wakeup(1ULL << BTN_GPIO3, ESP_GPIO_WAKEUP_GPIO_LOW);
|
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
|
||||||
|
|
||||||
display.hibernate();
|
display.hibernate();
|
||||||
|
|
||||||
@@ -112,17 +132,17 @@ void enterDeepSleep() {
|
|||||||
void onGoHome();
|
void onGoHome();
|
||||||
void onSelectEpubFile(const std::string& path) {
|
void onSelectEpubFile(const std::string& path) {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new FullScreenMessageScreen(renderer, "Loading..."));
|
enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading..."));
|
||||||
|
|
||||||
Epub* epub = loadEpub(path);
|
Epub* epub = loadEpub(path);
|
||||||
if (epub) {
|
if (epub) {
|
||||||
appState->openEpubPath = path;
|
appState.openEpubPath = path;
|
||||||
appState->saveToFile();
|
appState.saveToFile();
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new EpubReaderScreen(renderer, epub, onGoHome));
|
enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome));
|
||||||
} else {
|
} else {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new FullScreenMessageScreen(renderer, "Failed to load epub", REGULAR, false, false));
|
enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, false, false));
|
||||||
delay(2000);
|
delay(2000);
|
||||||
onGoHome();
|
onGoHome();
|
||||||
}
|
}
|
||||||
@@ -130,11 +150,11 @@ void onSelectEpubFile(const std::string& path) {
|
|||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new FileSelectionScreen(renderer, onSelectEpubFile));
|
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
setupInputPinModes();
|
inputManager.begin();
|
||||||
verifyWakeupLongPress();
|
verifyWakeupLongPress();
|
||||||
|
|
||||||
// Begin serial only if USB connected
|
// Begin serial only if USB connected
|
||||||
@@ -157,47 +177,48 @@ void setup() {
|
|||||||
Serial.println("Display initialized");
|
Serial.println("Display initialized");
|
||||||
|
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new BootLogoScreen(renderer));
|
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
||||||
|
|
||||||
// SD Card Initialization
|
// SD Card Initialization
|
||||||
SD.begin(SD_SPI_CS, SPI, SPI_FQ);
|
SD.begin(SD_SPI_CS, SPI, SPI_FQ);
|
||||||
|
|
||||||
appState = CrossPointState::loadFromFile();
|
appState.loadFromFile();
|
||||||
if (!appState->openEpubPath.empty()) {
|
if (!appState.openEpubPath.empty()) {
|
||||||
Epub* epub = loadEpub(appState->openEpubPath);
|
Epub* epub = loadEpub(appState.openEpubPath);
|
||||||
if (epub) {
|
if (epub) {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new EpubReaderScreen(renderer, epub, onGoHome));
|
enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome));
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
// Ensure we're not still holding the power button before leaving setup
|
||||||
waitForNoButton();
|
waitForPowerRelease();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new FileSelectionScreen(renderer, onSelectEpubFile));
|
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile));
|
||||||
|
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
// Ensure we're not still holding the power button before leaving setup
|
||||||
waitForNoButton();
|
waitForPowerRelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
delay(50);
|
delay(10);
|
||||||
|
|
||||||
const Input input = getInput();
|
static unsigned long lastMemPrint = 0;
|
||||||
|
if (Serial && millis() - lastMemPrint >= 2000) {
|
||||||
if (input.button == NONE) {
|
Serial.printf("[%lu] Memory - Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
|
||||||
return;
|
ESP.getHeapSize(), ESP.getMinFreeHeap());
|
||||||
|
lastMemPrint = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (input.button == POWER && input.pressTime > POWER_BUTTON_SLEEP_MS) {
|
inputManager.update();
|
||||||
|
if (inputManager.wasReleased(InputManager::BTN_POWER) && inputManager.getHeldTime() > POWER_BUTTON_WAKEUP_MS) {
|
||||||
enterDeepSleep();
|
enterDeepSleep();
|
||||||
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
||||||
delay(1000);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentScreen) {
|
if (currentScreen) {
|
||||||
currentScreen->handleInput(input);
|
currentScreen->handleInput();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void BootLogoScreen::onEnter() {
|
void BootLogoScreen::onEnter() {
|
||||||
const auto pageWidth = renderer->getPageWidth();
|
const auto pageWidth = renderer.getPageWidth();
|
||||||
const auto pageHeight = renderer->getPageHeight();
|
const auto pageHeight = renderer.getPageHeight();
|
||||||
|
|
||||||
renderer->clearScreen();
|
renderer.clearScreen();
|
||||||
// Location for images is from top right in landscape orientation
|
// Location for images is from top right in landscape orientation
|
||||||
renderer->drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
class BootLogoScreen final : public Screen {
|
class BootLogoScreen final : public Screen {
|
||||||
public:
|
public:
|
||||||
explicit BootLogoScreen(EpdRenderer* renderer) : Screen(renderer) {}
|
explicit BootLogoScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -59,9 +59,36 @@ void EpubReaderScreen::onExit() {
|
|||||||
epub = nullptr;
|
epub = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::handleInput(const Input input) {
|
void EpubReaderScreen::handleInput() {
|
||||||
if (input.button == VOLUME_UP || input.button == VOLUME_DOWN || input.button == LEFT || input.button == RIGHT) {
|
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
const bool skipChapter = input.pressTime > SKIP_CHAPTER_MS;
|
onGoHome();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool prevReleased =
|
||||||
|
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
||||||
|
const bool nextReleased =
|
||||||
|
inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
||||||
|
|
||||||
|
if (!prevReleased && !nextReleased) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("Prev released: %d, Next released: %d\n", prevReleased, nextReleased);
|
||||||
|
|
||||||
|
const bool skipChapter = inputManager.getHeldTime() > SKIP_CHAPTER_MS;
|
||||||
|
|
||||||
|
if (skipChapter) {
|
||||||
|
// We don't want to delete the section mid-render, so grab the semaphore
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
nextPageNumber = 0;
|
||||||
|
currentSpineIndex = nextReleased ? currentSpineIndex + 1 : currentSpineIndex - 1;
|
||||||
|
delete section;
|
||||||
|
section = nullptr;
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
updateRequired = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// No current section, attempt to rerender the book
|
// No current section, attempt to rerender the book
|
||||||
if (!section) {
|
if (!section) {
|
||||||
@@ -69,17 +96,7 @@ void EpubReaderScreen::handleInput(const Input input) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((input.button == VOLUME_UP || input.button == LEFT) && skipChapter) {
|
if (prevReleased) {
|
||||||
nextPageNumber = 0;
|
|
||||||
currentSpineIndex--;
|
|
||||||
delete section;
|
|
||||||
section = nullptr;
|
|
||||||
} else if ((input.button == VOLUME_DOWN || input.button == RIGHT) && skipChapter) {
|
|
||||||
nextPageNumber = 0;
|
|
||||||
currentSpineIndex++;
|
|
||||||
delete section;
|
|
||||||
section = nullptr;
|
|
||||||
} else if (input.button == VOLUME_UP || input.button == LEFT) {
|
|
||||||
if (section->currentPage > 0) {
|
if (section->currentPage > 0) {
|
||||||
section->currentPage--;
|
section->currentPage--;
|
||||||
} else {
|
} else {
|
||||||
@@ -91,7 +108,8 @@ void EpubReaderScreen::handleInput(const Input input) {
|
|||||||
section = nullptr;
|
section = nullptr;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
} else if (input.button == VOLUME_DOWN || input.button == RIGHT) {
|
updateRequired = true;
|
||||||
|
} else {
|
||||||
if (section->currentPage < section->pageCount - 1) {
|
if (section->currentPage < section->pageCount - 1) {
|
||||||
section->currentPage++;
|
section->currentPage++;
|
||||||
} else {
|
} else {
|
||||||
@@ -103,11 +121,7 @@ void EpubReaderScreen::handleInput(const Input input) {
|
|||||||
section = nullptr;
|
section = nullptr;
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (input.button == BACK) {
|
|
||||||
onGoHome();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,16 +154,16 @@ void EpubReaderScreen::renderPage() {
|
|||||||
Serial.println("Cache not found, building...");
|
Serial.println("Cache not found, building...");
|
||||||
|
|
||||||
{
|
{
|
||||||
const int textWidth = renderer->getTextWidth("Indexing...");
|
const int textWidth = renderer.getTextWidth("Indexing...");
|
||||||
constexpr int margin = 20;
|
constexpr int margin = 20;
|
||||||
const int x = (renderer->getPageWidth() - textWidth - margin * 2) / 2;
|
const int x = (renderer.getPageWidth() - textWidth - margin * 2) / 2;
|
||||||
constexpr int y = 50;
|
constexpr int y = 50;
|
||||||
const int w = textWidth + margin * 2;
|
const int w = textWidth + margin * 2;
|
||||||
const int h = renderer->getLineHeight() + margin * 2;
|
const int h = renderer.getLineHeight() + margin * 2;
|
||||||
renderer->fillRect(x, y, w, h, 0);
|
renderer.fillRect(x, y, w, h, 0);
|
||||||
renderer->drawText(x + margin, y + margin, "Indexing...");
|
renderer.drawText(x + margin, y + margin, "Indexing...");
|
||||||
renderer->drawRect(x + 5, y + 5, w - 10, h - 10);
|
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
||||||
renderer->flushArea(x, y, w, h);
|
renderer.flushArea(x, y, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
section->setupCacheDir();
|
section->setupCacheDir();
|
||||||
@@ -170,14 +184,14 @@ void EpubReaderScreen::renderPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->clearScreen();
|
renderer.clearScreen();
|
||||||
section->renderPage();
|
section->renderPage();
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
renderer->flushDisplay(false);
|
renderer.flushDisplay(false);
|
||||||
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
||||||
} else {
|
} else {
|
||||||
renderer->flushDisplay();
|
renderer.flushDisplay();
|
||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,16 +206,16 @@ void EpubReaderScreen::renderPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderStatusBar() const {
|
void EpubReaderScreen::renderStatusBar() const {
|
||||||
const auto pageWidth = renderer->getPageWidth();
|
const auto pageWidth = renderer.getPageWidth();
|
||||||
|
|
||||||
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
||||||
const auto progressTextWidth = renderer->getSmallTextWidth(progress.c_str());
|
const auto progressTextWidth = renderer.getSmallTextWidth(progress.c_str());
|
||||||
renderer->drawSmallText(pageWidth - progressTextWidth, 765, progress.c_str());
|
renderer.drawSmallText(pageWidth - progressTextWidth, 765, progress.c_str());
|
||||||
|
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery.readPercentage();
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
const auto percentageTextWidth = renderer->getSmallTextWidth(percentageText.c_str());
|
const auto percentageTextWidth = renderer.getSmallTextWidth(percentageText.c_str());
|
||||||
renderer->drawSmallText(20, 765, percentageText.c_str());
|
renderer.drawSmallText(20, 765, percentageText.c_str());
|
||||||
|
|
||||||
// 1 column on left, 2 columns on right, 5 columns of battery body
|
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||||
constexpr int batteryWidth = 15;
|
constexpr int batteryWidth = 15;
|
||||||
@@ -210,23 +224,23 @@ void EpubReaderScreen::renderStatusBar() const {
|
|||||||
constexpr int y = 772;
|
constexpr int y = 772;
|
||||||
|
|
||||||
// Top line
|
// Top line
|
||||||
renderer->drawLine(x, y, x + batteryWidth - 4, y);
|
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
||||||
// Bottom line
|
// Bottom line
|
||||||
renderer->drawLine(x, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1);
|
renderer.drawLine(x, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1);
|
||||||
// Left line
|
// Left line
|
||||||
renderer->drawLine(x, y, x, y + batteryHeight - 1);
|
renderer.drawLine(x, y, x, y + batteryHeight - 1);
|
||||||
// Battery end
|
// Battery end
|
||||||
renderer->drawLine(x + batteryWidth - 4, y, x + batteryWidth - 4, y + batteryHeight - 1);
|
renderer.drawLine(x + batteryWidth - 4, y, x + batteryWidth - 4, y + batteryHeight - 1);
|
||||||
renderer->drawLine(x + batteryWidth - 3, y + 2, x + batteryWidth - 3, y + batteryHeight - 3);
|
renderer.drawLine(x + batteryWidth - 3, y + 2, x + batteryWidth - 3, y + batteryHeight - 3);
|
||||||
renderer->drawLine(x + batteryWidth - 2, y + 2, x + batteryWidth - 2, y + batteryHeight - 3);
|
renderer.drawLine(x + batteryWidth - 2, y + 2, x + batteryWidth - 2, y + batteryHeight - 3);
|
||||||
renderer->drawLine(x + batteryWidth - 1, y + 2, x + batteryWidth - 1, y + batteryHeight - 3);
|
renderer.drawLine(x + batteryWidth - 1, y + 2, x + batteryWidth - 1, y + batteryHeight - 3);
|
||||||
|
|
||||||
// The +1 is to round up, so that we always fill at least one pixel
|
// The +1 is to round up, so that we always fill at least one pixel
|
||||||
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
||||||
if (filledWidth > batteryWidth - 5) {
|
if (filledWidth > batteryWidth - 5) {
|
||||||
filledWidth = batteryWidth - 5; // Ensure we don't overflow
|
filledWidth = batteryWidth - 5; // Ensure we don't overflow
|
||||||
}
|
}
|
||||||
renderer->fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2);
|
renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2);
|
||||||
|
|
||||||
// Page width minus existing content with 30px padding on each side
|
// Page width minus existing content with 30px padding on each side
|
||||||
const int leftMargin = 20 + percentageTextWidth + 30;
|
const int leftMargin = 20 + percentageTextWidth + 30;
|
||||||
@@ -234,11 +248,11 @@ void EpubReaderScreen::renderStatusBar() const {
|
|||||||
const int availableTextWidth = pageWidth - leftMargin - rightMargin;
|
const int availableTextWidth = pageWidth - leftMargin - rightMargin;
|
||||||
const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex));
|
const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex));
|
||||||
auto title = tocItem.title;
|
auto title = tocItem.title;
|
||||||
int titleWidth = renderer->getSmallTextWidth(title.c_str());
|
int titleWidth = renderer.getSmallTextWidth(title.c_str());
|
||||||
while (titleWidth > availableTextWidth) {
|
while (titleWidth > availableTextWidth) {
|
||||||
title = title.substr(0, title.length() - 8) + "...";
|
title = title.substr(0, title.length() - 8) + "...";
|
||||||
titleWidth = renderer->getSmallTextWidth(title.c_str());
|
titleWidth = renderer.getSmallTextWidth(title.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->drawSmallText(leftMargin + (availableTextWidth - titleWidth) / 2, 765, title.c_str());
|
renderer.drawSmallText(leftMargin + (availableTextWidth - titleWidth) / 2, 765, title.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ class EpubReaderScreen final : public Screen {
|
|||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderScreen(EpdRenderer* renderer, Epub* epub, const std::function<void()>& onGoHome)
|
explicit EpubReaderScreen(EpdRenderer& renderer, InputManager& inputManager, Epub* epub,
|
||||||
: Screen(renderer), epub(epub), onGoHome(onGoHome) {}
|
const std::function<void()>& onGoHome)
|
||||||
|
: Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput(Input input) override;
|
void handleInput() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,14 @@
|
|||||||
#include <EpdRenderer.h>
|
#include <EpdRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
|
void caseInsensitiveSort(std::vector<std::string>& strs) {
|
||||||
|
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
||||||
|
return lexicographical_compare(
|
||||||
|
begin(str1), end(str1), begin(str2), end(str2),
|
||||||
|
[](const char& char1, const char& char2) { return tolower(char1) < tolower(char2); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::taskTrampoline(void* param) {
|
void FileSelectionScreen::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<FileSelectionScreen*>(param);
|
auto* self = static_cast<FileSelectionScreen*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
@@ -27,6 +35,7 @@ void FileSelectionScreen::loadFiles() {
|
|||||||
file.close();
|
file.close();
|
||||||
}
|
}
|
||||||
root.close();
|
root.close();
|
||||||
|
caseInsensitiveSort(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::onEnter() {
|
void FileSelectionScreen::onEnter() {
|
||||||
@@ -59,31 +68,36 @@ void FileSelectionScreen::onExit() {
|
|||||||
files.clear();
|
files.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::handleInput(const Input input) {
|
void FileSelectionScreen::handleInput() {
|
||||||
if (input.button == VOLUME_DOWN || input.button == RIGHT) {
|
const bool prevPressed =
|
||||||
selectorIndex = (selectorIndex + 1) % files.size();
|
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
||||||
updateRequired = true;
|
const bool nextPressed =
|
||||||
} else if (input.button == VOLUME_UP || input.button == LEFT) {
|
inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT);
|
||||||
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
|
|
||||||
updateRequired = true;
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
} else if (input.button == CONFIRM) {
|
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files[selectorIndex].back() == '/') {
|
|
||||||
if (basepath.back() != '/') basepath += "/";
|
if (basepath.back() != '/') basepath += "/";
|
||||||
|
if (files[selectorIndex].back() == '/') {
|
||||||
basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1);
|
basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1);
|
||||||
loadFiles();
|
loadFiles();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else {
|
} else {
|
||||||
onSelect(basepath + files[selectorIndex]);
|
onSelect(basepath + files[selectorIndex]);
|
||||||
}
|
}
|
||||||
} else if (input.button == BACK && basepath != "/") {
|
} else if (inputManager.wasPressed(InputManager::BTN_BACK) && basepath != "/") {
|
||||||
basepath = basepath.substr(0, basepath.rfind('/'));
|
basepath = basepath.substr(0, basepath.rfind('/'));
|
||||||
if (basepath.empty()) basepath = "/";
|
if (basepath.empty()) basepath = "/";
|
||||||
loadFiles();
|
loadFiles();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
} else if (prevPressed) {
|
||||||
|
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
|
||||||
|
updateRequired = true;
|
||||||
|
} else if (nextPressed) {
|
||||||
|
selectorIndex = (selectorIndex + 1) % files.size();
|
||||||
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,23 +114,23 @@ void FileSelectionScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::render() const {
|
void FileSelectionScreen::render() const {
|
||||||
renderer->clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = renderer->getPageWidth();
|
const auto pageWidth = renderer.getPageWidth();
|
||||||
const auto titleWidth = renderer->getTextWidth("CrossPoint Reader", BOLD);
|
const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD);
|
||||||
renderer->drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", 1, BOLD);
|
renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", 1, BOLD);
|
||||||
|
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
renderer->drawUiText(10, 50, "No EPUBs found");
|
renderer.drawUiText(10, 50, "No EPUBs found");
|
||||||
} else {
|
} else {
|
||||||
// Draw selection
|
// Draw selection
|
||||||
renderer->fillRect(0, 50 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 50 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
for (size_t i = 0; i < files.size(); i++) {
|
for (size_t i = 0; i < files.size(); i++) {
|
||||||
const auto file = files[i];
|
const auto file = files[i];
|
||||||
renderer->drawUiText(10, 50 + i * 30, file.c_str(), i == selectorIndex ? 0 : 1);
|
renderer.drawUiText(10, 50 + i * 30, file.c_str(), i == selectorIndex ? 0 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer->flushDisplay();
|
renderer.flushDisplay();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ class FileSelectionScreen final : public Screen {
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSelectionScreen(EpdRenderer* renderer, const std::function<void(const std::string&)>& onSelect)
|
explicit FileSelectionScreen(EpdRenderer& renderer, InputManager& inputManager,
|
||||||
: Screen(renderer), onSelect(onSelect) {}
|
const std::function<void(const std::string&)>& onSelect)
|
||||||
|
: Screen(renderer, inputManager), onSelect(onSelect) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput(Input input) override;
|
void handleInput() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
#include <EpdRenderer.h>
|
#include <EpdRenderer.h>
|
||||||
|
|
||||||
void FullScreenMessageScreen::onEnter() {
|
void FullScreenMessageScreen::onEnter() {
|
||||||
const auto width = renderer->getUiTextWidth(text.c_str(), style);
|
const auto width = renderer.getUiTextWidth(text.c_str(), style);
|
||||||
const auto height = renderer->getLineHeight();
|
const auto height = renderer.getLineHeight();
|
||||||
const auto left = (renderer->getPageWidth() - width) / 2;
|
const auto left = (renderer.getPageWidth() - width) / 2;
|
||||||
const auto top = (renderer->getPageHeight() - height) / 2;
|
const auto top = (renderer.getPageHeight() - height) / 2;
|
||||||
|
|
||||||
renderer->clearScreen(invert);
|
renderer.clearScreen(invert);
|
||||||
renderer->drawUiText(left, top, text.c_str(), invert ? 0 : 1, style);
|
renderer.drawUiText(left, top, text.c_str(), invert ? 0 : 1, style);
|
||||||
renderer->flushDisplay(partialUpdate);
|
renderer.flushDisplay(partialUpdate);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,13 @@ class FullScreenMessageScreen final : public Screen {
|
|||||||
bool partialUpdate;
|
bool partialUpdate;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FullScreenMessageScreen(EpdRenderer* renderer, std::string text, const EpdFontStyle style = REGULAR,
|
explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text,
|
||||||
const bool invert = false, const bool partialUpdate = true)
|
const EpdFontStyle style = REGULAR, const bool invert = false,
|
||||||
: Screen(renderer), text(std::move(text)), style(style), invert(invert), partialUpdate(partialUpdate) {}
|
const bool partialUpdate = true)
|
||||||
|
: Screen(renderer, inputManager),
|
||||||
|
text(std::move(text)),
|
||||||
|
style(style),
|
||||||
|
invert(invert),
|
||||||
|
partialUpdate(partialUpdate) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "Input.h"
|
#include <InputManager.h>
|
||||||
|
|
||||||
class EpdRenderer;
|
class EpdRenderer;
|
||||||
|
|
||||||
class Screen {
|
class Screen {
|
||||||
protected:
|
protected:
|
||||||
EpdRenderer* renderer;
|
EpdRenderer& renderer;
|
||||||
|
InputManager& inputManager;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Screen(EpdRenderer* renderer) : renderer(renderer) {}
|
explicit Screen(EpdRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
||||||
virtual ~Screen() = default;
|
virtual ~Screen() = default;
|
||||||
virtual void onEnter() {}
|
virtual void onEnter() {}
|
||||||
virtual void onExit() {}
|
virtual void onExit() {}
|
||||||
virtual void handleInput(Input input) {}
|
virtual void handleInput() {}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
|
|
||||||
#include "images/SleepScreenImg.h"
|
#include "images/SleepScreenImg.h"
|
||||||
|
|
||||||
void SleepScreen::onEnter() { renderer->drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480, false, true); }
|
void SleepScreen::onEnter() { renderer.drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480, false, true); }
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
class SleepScreen final : public Screen {
|
class SleepScreen final : public Screen {
|
||||||
public:
|
public:
|
||||||
explicit SleepScreen(EpdRenderer* renderer) : Screen(renderer) {}
|
explicit SleepScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user