mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-04 22:57:50 +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
|
||||
lib/EpdFont/fontsrc
|
||||
*.generated.h
|
||||
.vs
|
||||
build
|
||||
**/__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)
|
||||
- Left, Right, Back, Confirm
|
||||
- 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.
|
||||
- **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
|
||||
|
||||
@ -359,7 +359,7 @@ const std::string& Epub::getLanguage() 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";
|
||||
}
|
||||
|
||||
@ -382,7 +382,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
||||
|
||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||
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";
|
||||
|
||||
FsFile coverJpg;
|
||||
@ -401,7 +401,7 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
||||
coverJpg.close();
|
||||
return false;
|
||||
}
|
||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
|
||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp, cropped);
|
||||
coverJpg.close();
|
||||
coverBmp.close();
|
||||
SdMan.remove(coverJpgTempPath.c_str());
|
||||
|
||||
@ -125,6 +125,8 @@ bool isExplicitHyphen(const uint32_t cp) {
|
||||
case 0xFE58: // small em dash
|
||||
case 0xFE63: // small hyphen-minus
|
||||
case 0xFF0D: // fullwidth hyphen-minus
|
||||
case 0x005F: // Underscore
|
||||
case 0x2026: // Ellipsis
|
||||
return true;
|
||||
default:
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
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)
|
||||
if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) {
|
||||
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;
|
||||
|
||||
if (renderMode == BW && val < 3) {
|
||||
drawPixel(screenX, screenY);
|
||||
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
|
||||
drawPixel(screenX, screenY, false);
|
||||
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
|
||||
drawPixel(screenX, screenY, false);
|
||||
drawVal(screenX, screenY, val);
|
||||
|
||||
// draw extended pixels
|
||||
// amount of pixels taken from bitmap and repeated to extend.
|
||||
// Trade-off between risk of repeating "edgy" content like text and more quality
|
||||
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
|
||||
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 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 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,
|
||||
float cropY = 0) const;
|
||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, float cropY = 0,
|
||||
bool extend = false) 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;
|
||||
|
||||
|
||||
@ -200,7 +200,7 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un
|
||||
|
||||
// Internal implementation with configurable target size and bit depth
|
||||
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",
|
||||
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 scaleToFitHeight = static_cast<float>(targetHeight) / imageInfo.m_height;
|
||||
// We scale to the smaller dimension, so we can potentially crop later.
|
||||
// TODO: ideally, we already crop here.
|
||||
const float scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
||||
float scale = 1.0;
|
||||
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);
|
||||
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)
|
||||
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false);
|
||||
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut, bool crop) {
|
||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false, crop);
|
||||
}
|
||||
|
||||
// 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,
|
||||
unsigned char* pBytes_actually_read, void* pCallback_data);
|
||||
static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||
bool oneBit);
|
||||
bool oneBit, bool crop = true);
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
@ -17,7 +17,7 @@ class CrossPointSettings {
|
||||
|
||||
// Should match with SettingsActivity text
|
||||
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
|
||||
enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 };
|
||||
@ -32,7 +32,12 @@ class CrossPointSettings {
|
||||
// Front button layout options
|
||||
// Default: Back, Confirm, Left, Right
|
||||
// Swapped: Left, Right, Back, Confirm
|
||||
enum FRONT_BUTTON_LAYOUT { BACK_CONFIRM_LEFT_RIGHT = 0, LEFT_RIGHT_BACK_CONFIRM = 1, 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
|
||||
// Default: Previous, Next
|
||||
|
||||
@ -14,6 +14,9 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||
return InputManager::BTN_CONFIRM;
|
||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||
/* fall through */
|
||||
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||
/* fall through */
|
||||
default:
|
||||
return InputManager::BTN_BACK;
|
||||
}
|
||||
@ -24,15 +27,22 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||
return InputManager::BTN_LEFT;
|
||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||
/* fall through */
|
||||
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||
/* fall through */
|
||||
default:
|
||||
return InputManager::BTN_CONFIRM;
|
||||
}
|
||||
case Button::Left:
|
||||
switch (frontLayout) {
|
||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||
/* fall through */
|
||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||
return InputManager::BTN_BACK;
|
||||
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||
return InputManager::BTN_RIGHT;
|
||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||
/* fall through */
|
||||
default:
|
||||
return InputManager::BTN_LEFT;
|
||||
}
|
||||
@ -40,8 +50,12 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
||||
switch (frontLayout) {
|
||||
case CrossPointSettings::LEFT_RIGHT_BACK_CONFIRM:
|
||||
return InputManager::BTN_CONFIRM;
|
||||
case CrossPointSettings::BACK_CONFIRM_RIGHT_LEFT:
|
||||
return InputManager::BTN_LEFT;
|
||||
case CrossPointSettings::BACK_CONFIRM_LEFT_RIGHT:
|
||||
/* fall through */
|
||||
case CrossPointSettings::LEFT_BACK_CONFIRM_RIGHT:
|
||||
/* fall through */
|
||||
default:
|
||||
return InputManager::BTN_RIGHT;
|
||||
}
|
||||
@ -56,6 +70,7 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
||||
case CrossPointSettings::NEXT_PREV:
|
||||
return InputManager::BTN_DOWN;
|
||||
case CrossPointSettings::PREV_NEXT:
|
||||
/* fall through */
|
||||
default:
|
||||
return InputManager::BTN_UP;
|
||||
}
|
||||
@ -64,6 +79,7 @@ decltype(InputManager::BTN_BACK) MappedInputManager::mapButton(const Button butt
|
||||
case CrossPointSettings::NEXT_PREV:
|
||||
return InputManager::BTN_UP;
|
||||
case CrossPointSettings::PREV_NEXT:
|
||||
/* fall through */
|
||||
default:
|
||||
return InputManager::BTN_DOWN;
|
||||
}
|
||||
|
||||
@ -177,22 +177,23 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
|
||||
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);
|
||||
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);
|
||||
|
||||
if (bitmap.hasGreyscale()) {
|
||||
bitmap.rewindToData();
|
||||
renderer.clearScreen(0x00);
|
||||
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();
|
||||
|
||||
bitmap.rewindToData();
|
||||
renderer.clearScreen(0x00);
|
||||
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.displayGrayBuffer();
|
||||
@ -260,6 +261,7 @@ void SleepActivity::renderCoverSleepScreen() const {
|
||||
if (SdMan.openFileForRead("SLP", coverBmpPath, file)) {
|
||||
Bitmap bitmap(file);
|
||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||
Serial.printf("[SLP] Rendering sleep cover: %s\n", coverBmpPath);
|
||||
renderBitmapSleepScreen(bitmap);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -120,7 +120,8 @@ void MyLibraryActivity::loadFiles() {
|
||||
} else {
|
||||
auto filename = std::string(name);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,8 +354,8 @@ void WifiSelectionActivity::loop() {
|
||||
updateRequired = true;
|
||||
}
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
if (forgetPromptSelection == 0) {
|
||||
// User chose "Yes" - forget the network
|
||||
if (forgetPromptSelection == 1) {
|
||||
// User chose "Forget network" - forget the network
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
WIFI_STORE.removeCredential(selectedSSID);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
@ -366,7 +366,7 @@ void WifiSelectionActivity::loop() {
|
||||
network->hasSavedPassword = false;
|
||||
}
|
||||
}
|
||||
// Go back to network list
|
||||
// Go back to network list (whether Cancel or Forget network was selected)
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
updateRequired = true;
|
||||
} 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 (usedSavedPassword) {
|
||||
state = WifiSelectionState::FORGET_PROMPT;
|
||||
forgetPromptSelection = 0; // Default to "Yes"
|
||||
forgetPromptSelection = 0; // Default to "Cancel"
|
||||
} else {
|
||||
// Go back to network list on failure
|
||||
state = WifiSelectionState::NETWORK_LIST;
|
||||
@ -623,7 +623,9 @@ void WifiSelectionActivity::renderConnected() const {
|
||||
const std::string ipInfo = "IP Address: " + connectedIP;
|
||||
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 {
|
||||
@ -663,7 +665,9 @@ void WifiSelectionActivity::renderSavePrompt() const {
|
||||
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 {
|
||||
@ -673,7 +677,10 @@ void WifiSelectionActivity::renderConnectionFailed() const {
|
||||
|
||||
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(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 {
|
||||
@ -692,26 +699,28 @@ void WifiSelectionActivity::renderForgetPrompt() const {
|
||||
|
||||
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;
|
||||
constexpr int buttonWidth = 60;
|
||||
constexpr int buttonWidth = 120;
|
||||
constexpr int buttonSpacing = 30;
|
||||
constexpr int totalWidth = buttonWidth * 2 + buttonSpacing;
|
||||
const int startX = (pageWidth - totalWidth) / 2;
|
||||
|
||||
// Draw "Yes" button
|
||||
// Draw "Cancel" button
|
||||
if (forgetPromptSelection == 0) {
|
||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Yes]");
|
||||
renderer.drawText(UI_10_FONT_ID, startX, buttonY, "[Cancel]");
|
||||
} 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) {
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[No]");
|
||||
renderer.drawText(UI_10_FONT_ID, startX + buttonWidth + buttonSpacing, buttonY, "[Forget network]");
|
||||
} 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
|
||||
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);
|
||||
const std::string progress = progressStr;
|
||||
progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
||||
|
||||
@ -188,22 +188,23 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
|
||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
||||
|
||||
for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) {
|
||||
const int displayY = 60 + (itemIndex % pageItems) * 30;
|
||||
for (int i = 0; i < pageItems; i++) {
|
||||
int itemIndex = pageStartIndex + i;
|
||||
if (itemIndex >= totalItems) break;
|
||||
const int displayY = 60 + i * 30;
|
||||
const bool isSelected = (itemIndex == selectorIndex);
|
||||
|
||||
if (isSyncItem(itemIndex)) {
|
||||
// Draw sync option (at top or bottom)
|
||||
renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected);
|
||||
} else {
|
||||
// Draw TOC item (account for top sync offset)
|
||||
const int tocIndex = tocIndexFromItemIndex(itemIndex);
|
||||
auto item = epub->getTocItem(tocIndex);
|
||||
|
||||
const int indentSize = 20 + (item.level - 1) * 15;
|
||||
const std::string chapterName =
|
||||
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) {
|
||||
if (path.length() < 4) return false;
|
||||
std::string ext4 = path.substr(path.length() - 4);
|
||||
return ext4 == ".txt" || ext4 == ".TXT";
|
||||
return StringUtils::checkFileExtension(path, ".txt") ||
|
||||
StringUtils::checkFileExtension(path, ".md"); // Treat .md as txt files (until we have a markdown reader)
|
||||
}
|
||||
|
||||
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
||||
|
||||
@ -194,7 +194,7 @@ void KOReaderSettingsActivity::render() {
|
||||
} else if (i == 1) {
|
||||
status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]";
|
||||
} else if (i == 2) {
|
||||
status = KOREADER_STORE.getServerUrl().empty() ? "[Not Set]" : "[Set]";
|
||||
status = KOREADER_STORE.getServerUrl().empty() ? "[Default]" : "[Custom]";
|
||||
} else if (i == 3) {
|
||||
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]";
|
||||
} else if (i == 4) {
|
||||
|
||||
@ -15,7 +15,7 @@ constexpr int displaySettingsCount = 5;
|
||||
const SettingInfo displaySettings[displaySettingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
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("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
|
||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||
@ -37,8 +37,9 @@ const SettingInfo readerSettings[readerSettingsCount] = {
|
||||
|
||||
constexpr int controlsSettingsCount = 4;
|
||||
const SettingInfo controlsSettings[controlsSettingsCount] = {
|
||||
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
||||
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
|
||||
SettingInfo::Enum(
|
||||
"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,
|
||||
{"Prev, Next", "Next, Prev"}),
|
||||
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user