Rotation Support
This commit is contained in:
parent
cfe838e03b
commit
32d747c6da
@ -10,8 +10,8 @@
|
|||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 5;
|
constexpr uint8_t SECTION_FILE_VERSION = 6;
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
||||||
@ -27,7 +27,8 @@ void Section::onPageComplete(std::unique_ptr<Page> page) {
|
|||||||
|
|
||||||
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
const int marginRight, const int marginBottom, const int marginLeft,
|
const int marginRight, const int marginBottom, const int marginLeft,
|
||||||
const bool extraParagraphSpacing) const {
|
const bool extraParagraphSpacing, const int screenWidth,
|
||||||
|
const int screenHeight) const {
|
||||||
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
||||||
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
||||||
serialization::writePod(outputFile, fontId);
|
serialization::writePod(outputFile, fontId);
|
||||||
@ -37,13 +38,15 @@ void Section::writeCacheMetadata(const int fontId, const float lineCompression,
|
|||||||
serialization::writePod(outputFile, marginBottom);
|
serialization::writePod(outputFile, marginBottom);
|
||||||
serialization::writePod(outputFile, marginLeft);
|
serialization::writePod(outputFile, marginLeft);
|
||||||
serialization::writePod(outputFile, extraParagraphSpacing);
|
serialization::writePod(outputFile, extraParagraphSpacing);
|
||||||
|
serialization::writePod(outputFile, screenWidth);
|
||||||
|
serialization::writePod(outputFile, screenHeight);
|
||||||
serialization::writePod(outputFile, pageCount);
|
serialization::writePod(outputFile, pageCount);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
const int marginRight, const int marginBottom, const int marginLeft,
|
const int marginRight, const int marginBottom, const int marginLeft,
|
||||||
const bool extraParagraphSpacing) {
|
const bool extraParagraphSpacing, const int screenWidth, const int screenHeight) {
|
||||||
if (!SD.exists(cachePath.c_str())) {
|
if (!SD.exists(cachePath.c_str())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -69,6 +72,7 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
|
|||||||
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
|
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
|
||||||
float fileLineCompression;
|
float fileLineCompression;
|
||||||
bool fileExtraParagraphSpacing;
|
bool fileExtraParagraphSpacing;
|
||||||
|
int fileScreenWidth, fileScreenHeight;
|
||||||
serialization::readPod(inputFile, fileFontId);
|
serialization::readPod(inputFile, fileFontId);
|
||||||
serialization::readPod(inputFile, fileLineCompression);
|
serialization::readPod(inputFile, fileLineCompression);
|
||||||
serialization::readPod(inputFile, fileMarginTop);
|
serialization::readPod(inputFile, fileMarginTop);
|
||||||
@ -76,10 +80,13 @@ bool Section::loadCacheMetadata(const int fontId, const float lineCompression, c
|
|||||||
serialization::readPod(inputFile, fileMarginBottom);
|
serialization::readPod(inputFile, fileMarginBottom);
|
||||||
serialization::readPod(inputFile, fileMarginLeft);
|
serialization::readPod(inputFile, fileMarginLeft);
|
||||||
serialization::readPod(inputFile, fileExtraParagraphSpacing);
|
serialization::readPod(inputFile, fileExtraParagraphSpacing);
|
||||||
|
serialization::readPod(inputFile, fileScreenWidth);
|
||||||
|
serialization::readPod(inputFile, fileScreenHeight);
|
||||||
|
|
||||||
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
|
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
|
||||||
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft ||
|
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft ||
|
||||||
extraParagraphSpacing != fileExtraParagraphSpacing) {
|
extraParagraphSpacing != fileExtraParagraphSpacing || screenWidth != fileScreenWidth ||
|
||||||
|
screenHeight != fileScreenHeight) {
|
||||||
inputFile.close();
|
inputFile.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();
|
||||||
@ -116,7 +123,7 @@ bool Section::clearCache() const {
|
|||||||
|
|
||||||
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
||||||
const int marginRight, const int marginBottom, const int marginLeft,
|
const int marginRight, const int marginBottom, const int marginLeft,
|
||||||
const bool extraParagraphSpacing) {
|
const bool extraParagraphSpacing, const int screenWidth, const int screenHeight) {
|
||||||
const auto localPath = epub->getSpineItem(spineIndex);
|
const auto localPath = epub->getSpineItem(spineIndex);
|
||||||
|
|
||||||
// TODO: Should we get rid of this file all together?
|
// TODO: Should we get rid of this file all together?
|
||||||
@ -147,7 +154,8 @@ bool Section::persistPageDataToSD(const int fontId, const float lineCompression,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing);
|
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft, extraParagraphSpacing,
|
||||||
|
screenWidth, screenHeight);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ class Section {
|
|||||||
std::string cachePath;
|
std::string cachePath;
|
||||||
|
|
||||||
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
int marginLeft, bool extraParagraphSpacing) const;
|
int marginLeft, bool extraParagraphSpacing, int screenWidth, int screenHeight) const;
|
||||||
void onPageComplete(std::unique_ptr<Page> page);
|
void onPageComplete(std::unique_ptr<Page> page);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -26,10 +26,10 @@ class Section {
|
|||||||
}
|
}
|
||||||
~Section() = default;
|
~Section() = default;
|
||||||
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
int marginLeft, bool extraParagraphSpacing);
|
int marginLeft, bool extraParagraphSpacing, int screenWidth, int screenHeight);
|
||||||
void setupCacheDir() const;
|
void setupCacheDir() const;
|
||||||
bool clearCache() const;
|
bool clearCache() const;
|
||||||
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
int marginLeft, bool extraParagraphSpacing);
|
int marginLeft, bool extraParagraphSpacing, int screenWidth, int screenHeight);
|
||||||
std::unique_ptr<Page> loadPageFromSD() const;
|
std::unique_ptr<Page> loadPageFromSD() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
|
|
||||||
|
// Default to portrait orientation for all callers
|
||||||
|
GfxRenderer::Orientation GfxRenderer::orientation = GfxRenderer::Orientation::Portrait;
|
||||||
|
|
||||||
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||||
|
|
||||||
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||||
@ -13,15 +16,35 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
int rotatedX = 0;
|
||||||
// Rotation: 90 degrees clockwise
|
int rotatedY = 0;
|
||||||
const int rotatedX = y;
|
|
||||||
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
|
||||||
|
|
||||||
// Bounds checking (portrait: 480x800)
|
switch (orientation) {
|
||||||
|
case Orientation::Portrait: {
|
||||||
|
// Logical portrait (480x800) → panel (800x480)
|
||||||
|
// Rotation: 90 degrees clockwise
|
||||||
|
rotatedX = y;
|
||||||
|
rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Orientation::LandscapeNormal: {
|
||||||
|
// Logical landscape (800x480) aligned with panel orientation
|
||||||
|
rotatedX = x;
|
||||||
|
rotatedY = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Orientation::LandscapeFlipped: {
|
||||||
|
// Logical landscape (800x480) rotated 180° (swap top/bottom and left/right)
|
||||||
|
rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x;
|
||||||
|
rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds checking against physical panel dimensions
|
||||||
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
|
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
|
||||||
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
|
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
|
||||||
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d)\n", millis(), x, y);
|
Serial.printf("[%lu] [GFX] !! Outside range (%d, %d) -> (%d, %d)\n", millis(), x, y, rotatedX, rotatedY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,8 +138,17 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||||
// Flip X and Y for portrait mode
|
switch (orientation) {
|
||||||
einkDisplay.drawImage(bitmap, y, x, height, width);
|
case Orientation::Portrait:
|
||||||
|
// Flip X and Y for portrait mode
|
||||||
|
einkDisplay.drawImage(bitmap, y, x, height, width);
|
||||||
|
break;
|
||||||
|
case Orientation::LandscapeNormal:
|
||||||
|
case Orientation::LandscapeFlipped:
|
||||||
|
// Native landscape coordinates
|
||||||
|
einkDisplay.drawImage(bitmap, x, y, width, height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
|
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
|
||||||
@ -193,22 +225,55 @@ void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::displayWindow(const int x, const int y, const int width, const int height) const {
|
void GfxRenderer::displayWindow(const int x, const int y, const int width, const int height) const {
|
||||||
// Rotate coordinates from portrait (480x800) to landscape (800x480)
|
switch (orientation) {
|
||||||
// Rotation: 90 degrees clockwise
|
case Orientation::Portrait: {
|
||||||
// Portrait coordinates: (x, y) with dimensions (width, height)
|
// Rotate coordinates from portrait (480x800) to landscape (800x480)
|
||||||
// Landscape coordinates: (rotatedX, rotatedY) with dimensions (rotatedWidth, rotatedHeight)
|
// Rotation: 90 degrees clockwise
|
||||||
|
// Portrait coordinates: (x, y) with dimensions (width, height)
|
||||||
|
// Landscape coordinates: (rotatedX, rotatedY) with dimensions (rotatedWidth, rotatedHeight)
|
||||||
|
|
||||||
const int rotatedX = y;
|
const int rotatedX = y;
|
||||||
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x - width + 1;
|
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x - width + 1;
|
||||||
const int rotatedWidth = height;
|
const int rotatedWidth = height;
|
||||||
const int rotatedHeight = width;
|
const int rotatedHeight = width;
|
||||||
|
|
||||||
einkDisplay.displayWindow(rotatedX, rotatedY, rotatedWidth, rotatedHeight);
|
einkDisplay.displayWindow(rotatedX, rotatedY, rotatedWidth, rotatedHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Orientation::LandscapeNormal:
|
||||||
|
case Orientation::LandscapeFlipped:
|
||||||
|
// Native landscape coordinates
|
||||||
|
einkDisplay.displayWindow(x, y, width, height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: Internal driver treats screen in command orientation, this library treats in portrait orientation
|
// Note: Internal driver treats screen in command orientation; this library exposes a logical orientation
|
||||||
int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; }
|
int GfxRenderer::getScreenWidth() {
|
||||||
int GfxRenderer::getScreenHeight() { return EInkDisplay::DISPLAY_WIDTH; }
|
switch (orientation) {
|
||||||
|
case Orientation::Portrait:
|
||||||
|
// 480px wide in portrait logical coordinates
|
||||||
|
return EInkDisplay::DISPLAY_HEIGHT;
|
||||||
|
case Orientation::LandscapeNormal:
|
||||||
|
case Orientation::LandscapeFlipped:
|
||||||
|
// 800px wide in landscape logical coordinates
|
||||||
|
return EInkDisplay::DISPLAY_WIDTH;
|
||||||
|
}
|
||||||
|
return EInkDisplay::DISPLAY_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::getScreenHeight() {
|
||||||
|
switch (orientation) {
|
||||||
|
case Orientation::Portrait:
|
||||||
|
// 800px tall in portrait logical coordinates
|
||||||
|
return EInkDisplay::DISPLAY_WIDTH;
|
||||||
|
case Orientation::LandscapeNormal:
|
||||||
|
case Orientation::LandscapeFlipped:
|
||||||
|
// 480px tall in landscape logical coordinates
|
||||||
|
return EInkDisplay::DISPLAY_HEIGHT;
|
||||||
|
}
|
||||||
|
return EInkDisplay::DISPLAY_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||||
if (fontMap.count(fontId) == 0) {
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
|||||||
@ -12,12 +12,22 @@ class GfxRenderer {
|
|||||||
public:
|
public:
|
||||||
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||||
|
|
||||||
|
// Logical screen orientation from the perspective of callers
|
||||||
|
enum class Orientation {
|
||||||
|
Portrait, // 480x800 logical coordinates (current default)
|
||||||
|
LandscapeNormal, // 800x480 logical coordinates, native panel orientation
|
||||||
|
LandscapeFlipped // 800x480 logical coordinates, rotated 180° (swap top/bottom)
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||||
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
||||||
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
||||||
"BW buffer chunking does not line up with display buffer size");
|
"BW buffer chunking does not line up with display buffer size");
|
||||||
|
|
||||||
|
// Global orientation used for all rendering operations
|
||||||
|
static Orientation orientation;
|
||||||
|
|
||||||
EInkDisplay& einkDisplay;
|
EInkDisplay& einkDisplay;
|
||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||||
@ -33,11 +43,15 @@ class GfxRenderer {
|
|||||||
// Setup
|
// Setup
|
||||||
void insertFont(int fontId, EpdFontFamily font);
|
void insertFont(int fontId, EpdFontFamily font);
|
||||||
|
|
||||||
|
// Orientation control (affects logical width/height and coordinate transforms)
|
||||||
|
static void setOrientation(Orientation o) { orientation = o; }
|
||||||
|
static Orientation getOrientation() { return orientation; }
|
||||||
|
|
||||||
// Screen ops
|
// Screen ops
|
||||||
static int getScreenWidth();
|
static int getScreenWidth();
|
||||||
static int getScreenHeight();
|
static int getScreenHeight();
|
||||||
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||||
// EXPERIMENTAL: Windowed update - display only a rectangular region (portrait coordinates)
|
// EXPERIMENTAL: Windowed update - display only a rectangular region
|
||||||
void displayWindow(int x, int y, int width, int height) const;
|
void displayWindow(int x, int y, int width, int height) const;
|
||||||
void invertScreen() const;
|
void invertScreen() const;
|
||||||
void clearScreen(uint8_t color = 0xFF) const;
|
void clearScreen(uint8_t color = 0xFF) const;
|
||||||
|
|||||||
@ -12,7 +12,8 @@ CrossPointSettings CrossPointSettings::instance;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
constexpr uint8_t SETTINGS_COUNT = 3;
|
// Increment this when adding new persisted settings fields
|
||||||
|
constexpr uint8_t SETTINGS_COUNT = 5;
|
||||||
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -26,6 +27,8 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, whiteSleepScreen);
|
serialization::writePod(outputFile, whiteSleepScreen);
|
||||||
serialization::writePod(outputFile, extraParagraphSpacing);
|
serialization::writePod(outputFile, extraParagraphSpacing);
|
||||||
serialization::writePod(outputFile, shortPwrBtn);
|
serialization::writePod(outputFile, shortPwrBtn);
|
||||||
|
serialization::writePod(outputFile, landscapeReading);
|
||||||
|
serialization::writePod(outputFile, landscapeFlipped);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||||
@ -51,7 +54,7 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
uint8_t fileSettingsCount = 0;
|
uint8_t fileSettingsCount = 0;
|
||||||
serialization::readPod(inputFile, fileSettingsCount);
|
serialization::readPod(inputFile, fileSettingsCount);
|
||||||
|
|
||||||
// load settings that exist
|
// load settings that exist (support older files with fewer fields)
|
||||||
uint8_t settingsRead = 0;
|
uint8_t settingsRead = 0;
|
||||||
do {
|
do {
|
||||||
serialization::readPod(inputFile, whiteSleepScreen);
|
serialization::readPod(inputFile, whiteSleepScreen);
|
||||||
@ -60,6 +63,10 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, shortPwrBtn);
|
serialization::readPod(inputFile, shortPwrBtn);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
serialization::readPod(inputFile, landscapeReading);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
serialization::readPod(inputFile, landscapeFlipped);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
|
|||||||
@ -21,6 +21,11 @@ class CrossPointSettings {
|
|||||||
uint8_t extraParagraphSpacing = 1;
|
uint8_t extraParagraphSpacing = 1;
|
||||||
// Duration of the power button press
|
// Duration of the power button press
|
||||||
uint8_t shortPwrBtn = 0;
|
uint8_t shortPwrBtn = 0;
|
||||||
|
// EPUB reading orientation settings
|
||||||
|
// 0 = portrait (default), 1 = landscape
|
||||||
|
uint8_t landscapeReading = 0;
|
||||||
|
// When in landscape mode: 0 = normal, 1 = flipped (swap top/bottom)
|
||||||
|
uint8_t landscapeFlipped = 0;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,17 @@ void EpubReaderActivity::onEnter() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure screen orientation based on settings
|
||||||
|
if (SETTINGS.landscapeReading) {
|
||||||
|
if (SETTINGS.landscapeFlipped) {
|
||||||
|
GfxRenderer::setOrientation(GfxRenderer::Orientation::LandscapeFlipped);
|
||||||
|
} else {
|
||||||
|
GfxRenderer::setOrientation(GfxRenderer::Orientation::LandscapeNormal);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GfxRenderer::setOrientation(GfxRenderer::Orientation::Portrait);
|
||||||
|
}
|
||||||
|
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
epub->setupCacheDir();
|
epub->setupCacheDir();
|
||||||
@ -56,6 +67,9 @@ void EpubReaderActivity::onEnter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::onExit() {
|
void EpubReaderActivity::onExit() {
|
||||||
|
// Reset orientation back to portrait for the rest of the UI
|
||||||
|
GfxRenderer::setOrientation(GfxRenderer::Orientation::Portrait);
|
||||||
|
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -208,7 +222,8 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
|
Serial.printf("[%lu] [ERS] Loading file: %s, index: %d\n", millis(), filepath.c_str(), currentSpineIndex);
|
||||||
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
|
section = std::unique_ptr<Section>(new Section(epub, currentSpineIndex, renderer));
|
||||||
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, marginLeft,
|
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, marginLeft,
|
||||||
SETTINGS.extraParagraphSpacing)) {
|
SETTINGS.extraParagraphSpacing, GfxRenderer::getScreenWidth(),
|
||||||
|
GfxRenderer::getScreenHeight())) {
|
||||||
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -227,7 +242,8 @@ void EpubReaderActivity::renderScreen() {
|
|||||||
|
|
||||||
section->setupCacheDir();
|
section->setupCacheDir();
|
||||||
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
||||||
marginLeft, SETTINGS.extraParagraphSpacing)) {
|
marginLeft, SETTINGS.extraParagraphSpacing, GfxRenderer::getScreenWidth(),
|
||||||
|
GfxRenderer::getScreenHeight())) {
|
||||||
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;
|
||||||
@ -322,7 +338,9 @@ void EpubReaderActivity::renderContents(std::unique_ptr<Page> page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderActivity::renderStatusBar() const {
|
void EpubReaderActivity::renderStatusBar() const {
|
||||||
constexpr auto textY = 776;
|
// Position status bar near the bottom of the logical screen, regardless of orientation
|
||||||
|
const auto screenHeight = GfxRenderer::getScreenHeight();
|
||||||
|
const auto textY = screenHeight - 24;
|
||||||
|
|
||||||
// Calculate progress in book
|
// Calculate progress in book
|
||||||
float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount;
|
float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount;
|
||||||
@ -345,7 +363,7 @@ void EpubReaderActivity::renderStatusBar() const {
|
|||||||
constexpr int batteryWidth = 15;
|
constexpr int batteryWidth = 15;
|
||||||
constexpr int batteryHeight = 10;
|
constexpr int batteryHeight = 10;
|
||||||
constexpr int x = marginLeft;
|
constexpr int x = marginLeft;
|
||||||
constexpr int y = 783;
|
const int y = screenHeight - 17;
|
||||||
|
|
||||||
// Top line
|
// Top line
|
||||||
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
||||||
|
|||||||
@ -10,7 +10,9 @@
|
|||||||
const SettingInfo SettingsActivity::settingsList[settingsCount] = {
|
const SettingInfo SettingsActivity::settingsList[settingsCount] = {
|
||||||
{"White Sleep Screen", SettingType::TOGGLE, &CrossPointSettings::whiteSleepScreen},
|
{"White Sleep Screen", SettingType::TOGGLE, &CrossPointSettings::whiteSleepScreen},
|
||||||
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing},
|
{"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing},
|
||||||
{"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn}};
|
{"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn},
|
||||||
|
{"Landscape Reading", SettingType::TOGGLE, &CrossPointSettings::landscapeReading},
|
||||||
|
{"Flip Landscape (swap top/bottom)", SettingType::TOGGLE, &CrossPointSettings::landscapeFlipped}};
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<SettingsActivity*>(param);
|
auto* self = static_cast<SettingsActivity*>(param);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ class SettingsActivity final : public Activity {
|
|||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
|
||||||
// Static settings list
|
// Static settings list
|
||||||
static constexpr int settingsCount = 3; // Number of settings
|
static constexpr int settingsCount = 5; // Number of settings
|
||||||
static const SettingInfo settingsList[settingsCount];
|
static const SettingInfo settingsList[settingsCount];
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user