mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-07 16:17:38 +03:00
Compare commits
16 Commits
a7fb41a3ac
...
2eec8e5e68
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eec8e5e68 | ||
|
|
13f0ebed96 | ||
|
|
0bc0baa966 | ||
|
|
5d369df6be | ||
|
|
b8ebcf5867 | ||
|
|
e858ebbe88 | ||
|
|
9224bc3f8c | ||
|
|
67a679ab41 | ||
|
|
7a53342f9d | ||
|
|
077526a126 | ||
|
|
b9cb412c1c | ||
|
|
757753df72 | ||
|
|
3ae73098d8 | ||
|
|
1f2d5c64b1 | ||
|
|
075324948e | ||
|
|
9dc839c244 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,5 +4,6 @@
|
|||||||
.vscode
|
.vscode
|
||||||
lib/EpdFont/fontsrc
|
lib/EpdFont/fontsrc
|
||||||
*.generated.h
|
*.generated.h
|
||||||
|
.vs
|
||||||
build
|
build
|
||||||
**/__pycache__/
|
**/__pycache__/
|
||||||
@ -116,6 +116,7 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
|||||||
- Back, Confirm, Left, Right (default)
|
- Back, Confirm, Left, Right (default)
|
||||||
- Left, Right, Back, Confirm
|
- Left, Right, Back, Confirm
|
||||||
- Left, Back, Confirm, Right
|
- Left, Back, Confirm, Right
|
||||||
|
- Back, Confirm, Right, Left
|
||||||
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||||
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
||||||
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
||||||
|
|||||||
@ -359,7 +359,7 @@ const std::string& Epub::getLanguage() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string Epub::getCoverBmpPath(bool cropped) const {
|
std::string Epub::getCoverBmpPath(bool cropped) const {
|
||||||
const auto coverFileName = "cover" + cropped ? "_crop" : "";
|
const auto coverFileName = std::string("cover") + (cropped ? "_crop" : "");
|
||||||
return cachePath + "/" + coverFileName + ".bmp";
|
return cachePath + "/" + coverFileName + ".bmp";
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,7 +382,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
|
|
||||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||||
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
|
Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image (%s mode)\n", millis(), cropped ? "cropped" : "fit");
|
||||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||||
|
|
||||||
FsFile coverJpg;
|
FsFile coverJpg;
|
||||||
@ -401,7 +401,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
|||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
|
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp, cropped);
|
||||||
coverJpg.close();
|
coverJpg.close();
|
||||||
coverBmp.close();
|
coverBmp.close();
|
||||||
SdMan.remove(coverJpgTempPath.c_str());
|
SdMan.remove(coverJpgTempPath.c_str());
|
||||||
|
|||||||
@ -125,6 +125,8 @@ bool isExplicitHyphen(const uint32_t cp) {
|
|||||||
case 0xFE58: // small em dash
|
case 0xFE58: // small em dash
|
||||||
case 0xFE63: // small hyphen-minus
|
case 0xFE63: // small hyphen-minus
|
||||||
case 0xFF0D: // fullwidth hyphen-minus
|
case 0xFF0D: // fullwidth hyphen-minus
|
||||||
|
case 0x005F: // Underscore
|
||||||
|
case 0x2026: // Ellipsis
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -152,8 +152,17 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co
|
|||||||
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
|
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawVal(const int x, const int y, const uint8_t val) const {
|
||||||
|
if (renderMode == BW && val < 3) {
|
||||||
|
drawPixel(x, y);
|
||||||
|
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
|
||||||
|
drawPixel(x, y, false);
|
||||||
|
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
|
||||||
|
drawPixel(x, y, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
||||||
const float cropX, const float cropY) const {
|
const float cropX, const float cropY, bool extend) const {
|
||||||
// For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit)
|
// For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit)
|
||||||
if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) {
|
if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) {
|
||||||
drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight);
|
drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight);
|
||||||
@ -233,12 +242,85 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
|||||||
|
|
||||||
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
|
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
|
||||||
|
|
||||||
if (renderMode == BW && val < 3) {
|
drawVal(screenX, screenY, val);
|
||||||
drawPixel(screenX, screenY);
|
|
||||||
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
|
// draw extended pixels
|
||||||
drawPixel(screenX, screenY, false);
|
// amount of pixels taken from bitmap and repeated to extend.
|
||||||
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
|
// Trade-off between risk of repeating "edgy" content like text and more quality
|
||||||
drawPixel(screenX, screenY, false);
|
int extendY = 20;
|
||||||
|
int extendX = 20;
|
||||||
|
int drawExtY = 0;
|
||||||
|
if (extend) {
|
||||||
|
int imgHeight = std::floor(scale * (bitmap.getHeight() - cropPixY));
|
||||||
|
int imgWidth = std::floor(scale * (bitmap.getWidth() - cropPixX));
|
||||||
|
// 1. TOP EXTENSION
|
||||||
|
// Check if the current pixel is within the strip to be mirrored
|
||||||
|
if (screenY >= y && screenY < y + extendY) {
|
||||||
|
// How many times do we need to mirror to fill the gap 'y'?
|
||||||
|
// Using +1 to ensure we cover fractional blocks at the screen edge
|
||||||
|
int numIterations = (y / extendY) + 1;
|
||||||
|
|
||||||
|
for (int ny = 0; ny < numIterations; ny++) {
|
||||||
|
// Compute 2 target rows t1, t2 for "accordeon" effect.
|
||||||
|
// Mirror Fold (e.g., pixel 0 goes to y-1, pixel 1 to y-2)
|
||||||
|
int t1 = y - 1 - (2 * ny * extendY + (screenY - y));
|
||||||
|
// Reverse Fold (creates the 'accordion' continuity)
|
||||||
|
int t2 = y - 1 - (2 * ny * extendY + (2 * extendY - 1 - (screenY - y)));
|
||||||
|
|
||||||
|
if (t1 >= 0 && t1 < y) drawVal(screenX, t1, val);
|
||||||
|
if (t2 >= 0 && t2 < y) drawVal(screenX, t2, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. BOTTOM EXTENSION
|
||||||
|
int imgBottom = y + imgHeight;
|
||||||
|
int gapBottom = getScreenHeight() - imgBottom;
|
||||||
|
|
||||||
|
if (screenY >= imgBottom - extendY && screenY < imgBottom) {
|
||||||
|
int numIterations = (gapBottom / extendY) + 1;
|
||||||
|
|
||||||
|
for (int ny = 0; ny < numIterations; ny++) {
|
||||||
|
// Mirror Fold (pixel at imgBottom-1 goes to imgBottom)
|
||||||
|
int t1 = imgBottom + (2 * ny * extendY + (imgBottom - 1 - screenY));
|
||||||
|
// Reverse Fold
|
||||||
|
int t2 = imgBottom + (2 * ny * extendY + (2 * extendY - 1 - (imgBottom - 1 - screenY)));
|
||||||
|
|
||||||
|
if (t1 >= imgBottom && t1 < getScreenHeight()) drawVal(screenX, t1, val);
|
||||||
|
if (t2 >= imgBottom && t2 < getScreenHeight()) drawVal(screenX, t2, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 2. LEFT EXTENSION ---
|
||||||
|
int imgRight = x + imgWidth; // x is the left margin/offset
|
||||||
|
// If the current pixel is within the leftmost 'extendX' pixels of the image
|
||||||
|
if (screenX >= x && screenX < x + extendX) {
|
||||||
|
int numIterations = (x / extendX) + 1;
|
||||||
|
for (int nx = 0; nx < numIterations; nx++) {
|
||||||
|
// Mirror Fold (pixel at 'x' maps to 'x-1')
|
||||||
|
int t1 = x - 1 - (2 * nx * extendX + (screenX - x));
|
||||||
|
// Reverse Fold
|
||||||
|
int t2 = x - 1 - (2 * nx * extendX + (2 * extendX - 1 - (screenX - x)));
|
||||||
|
|
||||||
|
if (t1 >= 0 && t1 < x) drawVal(t1, screenY, val);
|
||||||
|
if (t2 >= 0 && t2 < x) drawVal(t2, screenY, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 3. RIGHT EXTENSION ---
|
||||||
|
int gapRight = getScreenWidth() - imgRight;
|
||||||
|
// If the current pixel is within the rightmost 'extendX' pixels of the image
|
||||||
|
if (screenX >= imgRight - extendX && screenX < imgRight) {
|
||||||
|
int numIterations = (gapRight / extendX) + 1;
|
||||||
|
for (int nx = 0; nx < numIterations; nx++) {
|
||||||
|
// Mirror Fold (pixel at 'imgRight-1' maps to 'imgRight')
|
||||||
|
int t1 = imgRight + (2 * nx * extendX + (imgRight - 1 - screenX));
|
||||||
|
// Reverse Fold
|
||||||
|
int t2 = imgRight + (2 * nx * extendX + (2 * extendX - 1 - (imgRight - 1 - screenX)));
|
||||||
|
|
||||||
|
if (t1 >= imgRight && t1 < getScreenWidth()) drawVal(t1, screenY, val);
|
||||||
|
if (t2 >= imgRight && t2 < getScreenWidth()) drawVal(t2, screenY, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -62,12 +62,13 @@ class GfxRenderer {
|
|||||||
|
|
||||||
// Drawing
|
// Drawing
|
||||||
void drawPixel(int x, int y, bool state = true) const;
|
void drawPixel(int x, int y, bool state = true) const;
|
||||||
|
void drawVal(const int x, const int y, const uint8_t val) const;
|
||||||
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
|
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, float cropY = 0,
|
||||||
float cropY = 0) const;
|
bool extend = false) const;
|
||||||
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
||||||
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
|
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
|
||||||
|
|
||||||
|
|||||||
@ -200,7 +200,7 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un
|
|||||||
|
|
||||||
// Internal implementation with configurable target size and bit depth
|
// Internal implementation with configurable target size and bit depth
|
||||||
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||||
bool oneBit) {
|
bool oneBit, bool crop) {
|
||||||
Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit",
|
Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit",
|
||||||
targetWidth, targetHeight);
|
targetWidth, targetHeight);
|
||||||
|
|
||||||
@ -242,8 +242,12 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
const float scaleToFitWidth = static_cast<float>(targetWidth) / imageInfo.m_width;
|
const float scaleToFitWidth = static_cast<float>(targetWidth) / imageInfo.m_width;
|
||||||
const float scaleToFitHeight = static_cast<float>(targetHeight) / imageInfo.m_height;
|
const float scaleToFitHeight = static_cast<float>(targetHeight) / imageInfo.m_height;
|
||||||
// We scale to the smaller dimension, so we can potentially crop later.
|
// We scale to the smaller dimension, so we can potentially crop later.
|
||||||
// TODO: ideally, we already crop here.
|
float scale = 1.0;
|
||||||
const float scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
if (crop) { // if we will crop, scale to the smaller dimension
|
||||||
|
scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
||||||
|
} else { // else, scale to the larger dimension to fit
|
||||||
|
scale = (scaleToFitWidth < scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
||||||
|
}
|
||||||
|
|
||||||
outWidth = static_cast<int>(imageInfo.m_width * scale);
|
outWidth = static_cast<int>(imageInfo.m_width * scale);
|
||||||
outHeight = static_cast<int>(imageInfo.m_height * scale);
|
outHeight = static_cast<int>(imageInfo.m_height * scale);
|
||||||
@ -550,8 +554,8 @@ bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bm
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Core function: Convert JPEG file to 2-bit BMP (uses default target size)
|
// Core function: Convert JPEG file to 2-bit BMP (uses default target size)
|
||||||
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop) {
|
||||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false);
|
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false, crop);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert with custom target size (for thumbnails, 2-bit)
|
// Convert with custom target size (for thumbnails, 2-bit)
|
||||||
|
|||||||
@ -8,10 +8,10 @@ class JpegToBmpConverter {
|
|||||||
static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
||||||
unsigned char* pBytes_actually_read, void* pCallback_data);
|
unsigned char* pBytes_actually_read, void* pCallback_data);
|
||||||
static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||||
bool oneBit);
|
bool oneBit, bool crop = true);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut);
|
static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop = true);
|
||||||
// Convert with custom target size (for thumbnails)
|
// Convert with custom target size (for thumbnails)
|
||||||
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
||||||
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
||||||
|
|||||||
@ -17,7 +17,7 @@ class CrossPointSettings {
|
|||||||
|
|
||||||
// Should match with SettingsActivity text
|
// Should match with SettingsActivity text
|
||||||
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
|
enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3, BLANK = 4 };
|
||||||
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1 };
|
enum SLEEP_SCREEN_COVER_MODE { FIT = 0, CROP = 1, EXTEND = 2 };
|
||||||
|
|
||||||
// Status bar display type enum
|
// Status bar display type enum
|
||||||
enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 };
|
enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 };
|
||||||
@ -32,7 +32,12 @@ class CrossPointSettings {
|
|||||||
// Front button layout options
|
// Front button layout options
|
||||||
// Default: Back, Confirm, Left, Right
|
// Default: Back, Confirm, Left, Right
|
||||||
// Swapped: Left, Right, Back, Confirm
|
// Swapped: Left, Right, Back, Confirm
|
||||||
enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1, LEFT_BACK_CONFIRM_RIGHT = 2 };
|
enum FRONT_BUTTON_LAYOUT {
|
||||||
|
BACK_CONFIRM_LEFT_RIGHT = 0,
|
||||||
|
LEFT_RIGHT_BACK_CONFIRM = 1,
|
||||||
|
LEFT_BACK_CONFIRM_RIGHT = 2,
|
||||||
|
BACK_CONFIRM_RIGHT_LEFT = 3
|
||||||
|
};
|
||||||
|
|
||||||
// Side button layout options
|
// Side button layout options
|
||||||
// Default: Previous, Next
|
// Default: Previous, Next
|
||||||
|
|||||||
@ -14,6 +14,9 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
|||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||||
return InputManager::BTN_CONFIRM;
|
return InputManager::BTN_CONFIRM;
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
/* fall through */
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return InputManager::BTN_BACK;
|
return InputManager::BTN_BACK;
|
||||||
}
|
}
|
||||||
@ -24,15 +27,22 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
|||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||||
return InputManager::BTN_LEFT;
|
return InputManager::BTN_LEFT;
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
/* fall through */
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return InputManager::BTN_CONFIRM;
|
return InputManager::BTN_CONFIRM;
|
||||||
}
|
}
|
||||||
case Button::Left:
|
case Button::Left:
|
||||||
switch (frontLayout) {
|
switch (frontLayout) {
|
||||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
|
/* fall through */
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||||
return InputManager::BTN_BACK;
|
return InputManager::BTN_BACK;
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||||
|
return InputManager::BTN_RIGHT;
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return InputManager::BTN_LEFT;
|
return InputManager::BTN_LEFT;
|
||||||
}
|
}
|
||||||
@ -40,8 +50,12 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
|||||||
switch (frontLayout) {
|
switch (frontLayout) {
|
||||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||||
return InputManager::BTN_CONFIRM;
|
return InputManager::BTN_CONFIRM;
|
||||||
|
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||||
|
return InputManager::BTN_LEFT;
|
||||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||||
|
/* fall through */
|
||||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return InputManager::BTN_RIGHT;
|
return InputManager::BTN_RIGHT;
|
||||||
}
|
}
|
||||||
@ -56,6 +70,7 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
|||||||
case CrossPointSettings::NEXT_PREV:
|
case CrossPointSettings::NEXT_PREV:
|
||||||
return InputManager::BTN_DOWN;
|
return InputManager::BTN_DOWN;
|
||||||
case CrossPointSettings::PREV_NEXT:
|
case CrossPointSettings::PREV_NEXT:
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return InputManager::BTN_UP;
|
return InputManager::BTN_UP;
|
||||||
}
|
}
|
||||||
@ -64,6 +79,7 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
|||||||
case CrossPointSettings::NEXT_PREV:
|
case CrossPointSettings::NEXT_PREV:
|
||||||
return InputManager::BTN_UP;
|
return InputManager::BTN_UP;
|
||||||
case CrossPointSettings::PREV_NEXT:
|
case CrossPointSettings::PREV_NEXT:
|
||||||
|
/* fall through */
|
||||||
default:
|
default:
|
||||||
return InputManager::BTN_DOWN;
|
return InputManager::BTN_DOWN;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,22 +177,23 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
|
|||||||
y = (pageHeight - bitmap.getHeight()) / 2;
|
y = (pageHeight - bitmap.getHeight()) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool extended = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::EXTEND;
|
||||||
Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y);
|
Serial.printf("[%lu] [SLP] drawing to %d x %d\n", millis(), x, y);
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, extended);
|
||||||
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
|
|
||||||
if (bitmap.hasGreyscale()) {
|
if (bitmap.hasGreyscale()) {
|
||||||
bitmap.rewindToData();
|
bitmap.rewindToData();
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, extended);
|
||||||
renderer.copyGrayscaleLsbBuffers();
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
|
|
||||||
bitmap.rewindToData();
|
bitmap.rewindToData();
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
||||||
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY, extended);
|
||||||
renderer.copyGrayscaleMsbBuffers();
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
|
|
||||||
renderer.displayGrayBuffer();
|
renderer.displayGrayBuffer();
|
||||||
@ -260,6 +261,7 @@ void SleepActivity::renderCoverSleepScreen() const {
|
|||||||
if (SdMan.openFileForRead("SLP", coverBmpPath, file)) {
|
if (SdMan.openFileForRead("SLP", coverBmpPath, file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
|
Serial.printf("[SLP] Rendering sleep cover: %s\n", coverBmpPath);
|
||||||
renderBitmapSleepScreen(bitmap);
|
renderBitmapSleepScreen(bitmap);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -120,7 +120,8 @@ void MyLibraryActivity::loadFiles() {
|
|||||||
} else {
|
} else {
|
||||||
auto filename = std::string(name);
|
auto filename = std::string(name);
|
||||||
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
|
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
|
||||||
StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt")) {
|
StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt") ||
|
||||||
|
StringUtils::checkFileExtension(filename, ".md")) {
|
||||||
files.emplace_back(filename);
|
files.emplace_back(filename);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -354,8 +354,8 @@ void WifiSelectionActivity::loop() {
|
|||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||||
if (forgetPromptSelection == 0) {
|
if (forgetPromptSelection == 1) {
|
||||||
// User chose "Yes" - forget the network
|
// User chose "Forget network" - forget the network
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
WIFI_STORE.removeCredential(selectedSSID);
|
WIFI_STORE.removeCredential(selectedSSID);
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
@ -366,7 +366,7 @@ void WifiSelectionActivity::loop() {
|
|||||||
network->hasSavedPassword = false;
|
network->hasSavedPassword = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Go back to network list
|
// Go back to network list (whether Cancel or Forget network was selected)
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
} else if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||||
@ -391,7 +391,7 @@ void WifiSelectionActivity::loop() {
|
|||||||
// 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;
|
||||||
forgetPromptSelection = 0; // Default to "Yes"
|
forgetPromptSelection = 0; // Default to "Cancel"
|
||||||
} else {
|
} else {
|
||||||
// Go back to network list on failure
|
// Go back to network list on failure
|
||||||
state = WifiSelectionState::NETWORK_LIST;
|
state = WifiSelectionState::NETWORK_LIST;
|
||||||
@ -623,7 +623,9 @@ void WifiSelectionActivity::renderConnected() const {
|
|||||||
const std::string ipInfo = "IP Address: " + connectedIP;
|
const std::string ipInfo = "IP Address: " + connectedIP;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, ipInfo.c_str());
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue");
|
// Use centralized button hints
|
||||||
|
const auto labels = mappedInput.mapLabels("", "Continue", "", "");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderSavePrompt() const {
|
void WifiSelectionActivity::renderSavePrompt() const {
|
||||||
@ -663,7 +665,9 @@ void WifiSelectionActivity::renderSavePrompt() const {
|
|||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "LEFT/RIGHT: Select | OK: Confirm");
|
// Use centralized button hints
|
||||||
|
const auto labels = mappedInput.mapLabels("« Skip", "Select", "Left", "Right");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderConnectionFailed() const {
|
void WifiSelectionActivity::renderConnectionFailed() const {
|
||||||
@ -673,7 +677,10 @@ void WifiSelectionActivity::renderConnectionFailed() const {
|
|||||||
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, top - 20, "Connection Failed", true, EpdFontFamily::BOLD);
|
renderer.drawCenteredText(UI_12_FONT_ID, top - 20, "Connection Failed", true, EpdFontFamily::BOLD);
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str());
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 20, connectionError.c_str());
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "Press any button to continue");
|
|
||||||
|
// Use centralized button hints
|
||||||
|
const auto labels = mappedInput.mapLabels("« Back", "Continue", "", "");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiSelectionActivity::renderForgetPrompt() const {
|
void WifiSelectionActivity::renderForgetPrompt() const {
|
||||||
@ -692,26 +699,28 @@ void WifiSelectionActivity::renderForgetPrompt() const {
|
|||||||
|
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Remove saved password?");
|
renderer.drawCenteredText(UI_10_FONT_ID, top + 40, "Remove saved password?");
|
||||||
|
|
||||||
// Draw Yes/No buttons
|
// Draw Cancel/Forget network buttons
|
||||||
const int buttonY = top + 80;
|
const int buttonY = top + 80;
|
||||||
constexpr int buttonWidth = 60;
|
constexpr int buttonWidth = 120;
|
||||||
constexpr int buttonSpacing = 30;
|
constexpr int buttonSpacing = 30;
|
||||||
constexpr int totalWidth = buttonWidth * 2 + buttonSpacing;
|
constexpr int totalWidth = buttonWidth * 2 + buttonSpacing;
|
||||||
const int startX = (pageWidth - totalWidth) / 2;
|
const int startX = (pageWidth - totalWidth) / 2;
|
||||||
|
|
||||||
// Draw "Yes" button
|
// Draw "Cancel" button
|
||||||
if (forgetPromptSelection == 0) {
|
if (forgetPromptSelection == 0) {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Yes]");
|
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Cancel]");
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Yes");
|
renderer.drawText(UI_10_FONT_ID, startX + 4, buttonY, "Cancel");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw "No" button
|
// Draw "Forget network" button
|
||||||
if (forgetPromptSelection == 1) {
|
if (forgetPromptSelection == 1) {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[No]");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[Forget network]");
|
||||||
} else {
|
} else {
|
||||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "No");
|
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing + 4, buttonY, "Forget network");
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, "LEFT/RIGHT: Select | OK: Confirm");
|
// Use centralized button hints
|
||||||
|
const auto labels = mappedInput.mapLabels("« Back", "Select", "Left", "Right");
|
||||||
|
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -448,7 +448,7 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
|
|||||||
|
|
||||||
// Right aligned text for progress counter
|
// Right aligned text for progress counter
|
||||||
char progressStr[32];
|
char progressStr[32];
|
||||||
snprintf(progressStr, sizeof(progressStr), "%d/%d %.1f%%", section->currentPage + 1, section->pageCount,
|
snprintf(progressStr, sizeof(progressStr), "%d/%d %.0f%%", section->currentPage + 1, section->pageCount,
|
||||||
bookProgress);
|
bookProgress);
|
||||||
const std::string progress = progressStr;
|
const std::string progress = progressStr;
|
||||||
progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
||||||
|
|||||||
@ -188,22 +188,23 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
|
|||||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||||
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) {
|
for (int i = 0; i < pageItems; i++) {
|
||||||
const int displayY = 60 + (itemIndex % pageItems) * 30;
|
int itemIndex = pageStartIndex + i;
|
||||||
|
if (itemIndex >= totalItems) break;
|
||||||
|
const int displayY = 60 + i * 30;
|
||||||
const bool isSelected = (itemIndex == selectorIndex);
|
const bool isSelected = (itemIndex == selectorIndex);
|
||||||
|
|
||||||
if (isSyncItem(itemIndex)) {
|
if (isSyncItem(itemIndex)) {
|
||||||
// Draw sync option (at top or bottom)
|
|
||||||
renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected);
|
renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected);
|
||||||
} else {
|
} else {
|
||||||
// Draw TOC item (account for top sync offset)
|
|
||||||
const int tocIndex = tocIndexFromItemIndex(itemIndex);
|
const int tocIndex = tocIndexFromItemIndex(itemIndex);
|
||||||
auto item = epub->getTocItem(tocIndex);
|
auto item = epub->getTocItem(tocIndex);
|
||||||
|
|
||||||
const int indentSize = 20 + (item.level - 1) * 15;
|
const int indentSize = 20 + (item.level - 1) * 15;
|
||||||
const std::string chapterName =
|
const std::string chapterName =
|
||||||
renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize);
|
renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize);
|
||||||
renderer.drawText(UI_10_FONT_ID, indentSize, 60 + (tocIndex % pageItems) * 30, chapterName.c_str(),
|
|
||||||
tocIndex != selectorIndex);
|
renderer.drawText(UI_10_FONT_ID, indentSize, displayY, chapterName.c_str(), !isSelected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,9 +22,8 @@ bool ReaderActivity::isXtcFile(const std::string& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool ReaderActivity::isTxtFile(const std::string& path) {
|
bool ReaderActivity::isTxtFile(const std::string& path) {
|
||||||
if (path.length() < 4) return false;
|
return StringUtils::checkFileExtension(path, ".txt") ||
|
||||||
std::string ext4 = path.substr(path.length() - 4);
|
StringUtils::checkFileExtension(path, ".md"); // Treat .md as txt files (until we have a markdown reader)
|
||||||
return ext4 == ".txt" || ext4 == ".TXT";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
||||||
|
|||||||
@ -194,7 +194,7 @@ void KOReaderSettingsActivity::render() {
|
|||||||
} else if (i == 1) {
|
} else if (i == 1) {
|
||||||
status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]";
|
status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]";
|
||||||
} else if (i == 2) {
|
} else if (i == 2) {
|
||||||
status = KOREADER_STORE.getServerUrl().empty() ? "[Not Set]" : "[Set]";
|
status = KOREADER_STORE.getServerUrl().empty() ? "[Default]" : "[Custom]";
|
||||||
} else if (i == 3) {
|
} else if (i == 3) {
|
||||||
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]";
|
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]";
|
||||||
} else if (i == 4) {
|
} else if (i == 4) {
|
||||||
|
|||||||
@ -15,7 +15,7 @@ constexpr int displaySettingsCount = 5;
|
|||||||
const SettingInfo displaySettings[displaySettingsCount] = {
|
const SettingInfo displaySettings[displaySettingsCount] = {
|
||||||
// Should match with SLEEP_SCREEN_MODE
|
// Should match with SLEEP_SCREEN_MODE
|
||||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||||
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
|
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop", "Extend"}),
|
||||||
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
|
||||||
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
||||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||||
@ -37,8 +37,9 @@ const SettingInfo readerSettings[readerSettingsCount] = {
|
|||||||
|
|
||||||
constexpr int controlsSettingsCount = 4;
|
constexpr int controlsSettingsCount = 4;
|
||||||
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
||||||
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
SettingInfo::Enum(
|
||||||
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
|
"Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
||||||
|
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght", "Bck, Cnfrm, Rght, Lft"}),
|
||||||
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
||||||
{"Prev, Next", "Next, Prev"}),
|
{"Prev, Next", "Next, Prev"}),
|
||||||
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user