adds bezel compensation settings

This commit is contained in:
cottongin 2026-01-27 21:40:52 -05:00
parent c2a966a6ea
commit 80c9e7a1d6
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
24 changed files with 428 additions and 155 deletions

View File

@ -2,6 +2,7 @@
1) Ability to clear all books and clear individual books from Recents. 1) Ability to clear all books and clear individual books from Recents.
2) Bookmarks 2) Bookmarks
2a) crosspoint logo on firmware flashing screen
3) ability to add/remove books from lists on device. 3) ability to add/remove books from lists on device.
4) quick menu 4) quick menu
5) hide "system folders" from files view 5) hide "system folders" from files view

View File

@ -621,15 +621,20 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
const int pageHeight = getScreenHeight(); const int pageHeight = getScreenHeight();
constexpr int buttonWidth = 106; constexpr int buttonWidth = 106;
constexpr int buttonHeight = 40; constexpr int buttonHeight = 40;
constexpr int buttonY = 40; // Distance from bottom constexpr int baseButtonY = 40; // Base distance from bottom
constexpr int textYOffset = 7; // Distance from top of button to text baseline constexpr int textYOffset = 7; // Distance from top of button to text baseline
constexpr int buttonPositions[] = {25, 130, 245, 350}; constexpr int baseButtonPositions[] = {25, 130, 245, 350};
const char* labels[] = {btn1, btn2, btn3, btn4}; const char* labels[] = {btn1, btn2, btn3, btn4};
// Apply bezel compensation (in portrait mode, bottom bezel affects Y position)
const int bezelBottom = getBezelOffsetBottom();
const int bezelLeft = getBezelOffsetLeft();
const int buttonY = baseButtonY + bezelBottom;
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
// Only draw if the label is non-empty // Only draw if the label is non-empty
if (labels[i] != nullptr && labels[i][0] != '\0') { if (labels[i] != nullptr && labels[i][0] != '\0') {
const int x = buttonPositions[i]; const int x = baseButtonPositions[i] + bezelLeft;
fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false); fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false);
drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight); drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight);
const int textWidth = getTextWidth(fontId, labels[i]); const int textWidth = getTextWidth(fontId, labels[i]);
@ -648,9 +653,14 @@ void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, cons
const int screenWidth = getScreenWidth(); const int screenWidth = getScreenWidth();
constexpr int buttonWidth = 40; // Width on screen (height when rotated) constexpr int buttonWidth = 40; // Width on screen (height when rotated)
constexpr int buttonHeight = 80; // Height on screen (width when rotated) constexpr int buttonHeight = 80; // Height on screen (width when rotated)
constexpr int buttonX = 5; // Distance from right edge constexpr int baseButtonX = 5; // Base distance from right edge
// Position for the button group - buttons share a border so they're adjacent // Position for the button group - buttons share a border so they're adjacent
constexpr int topButtonY = 345; // Top button position constexpr int baseTopButtonY = 345; // Base top button position
// Apply bezel compensation (in portrait mode)
const int bezelRight = getBezelOffsetRight();
const int buttonX = baseButtonX + bezelRight;
const int topButtonY = baseTopButtonY; // Y position doesn't need adjustment for side buttons
const char* labels[] = {topBtn, bottomBtn}; const char* labels[] = {topBtn, bottomBtn};
@ -979,31 +989,107 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp,
*x += glyph->advanceX; *x += glyph->advanceX;
} }
// Helper to map physical bezel edge to logical edge based on orientation
// bezelEdge: 0=physical bottom, 1=physical top, 2=physical left, 3=physical right (in portrait)
// Returns: 0=logical bottom, 1=logical top, 2=logical left, 3=logical right
int mapPhysicalToLogicalEdge(int bezelEdge, GfxRenderer::Orientation orientation) {
switch (orientation) {
case GfxRenderer::Portrait:
return bezelEdge;
case GfxRenderer::LandscapeClockwise:
switch (bezelEdge) {
case 0: return 2; // Physical bottom -> logical left
case 1: return 3; // Physical top -> logical right
case 2: return 1; // Physical left -> logical top
case 3: return 0; // Physical right -> logical bottom
}
break;
case GfxRenderer::PortraitInverted:
switch (bezelEdge) {
case 0: return 1; // Physical bottom -> logical top
case 1: return 0; // Physical top -> logical bottom
case 2: return 3; // Physical left -> logical right
case 3: return 2; // Physical right -> logical left
}
break;
case GfxRenderer::LandscapeCounterClockwise:
switch (bezelEdge) {
case 0: return 3; // Physical bottom -> logical right
case 1: return 2; // Physical top -> logical left
case 2: return 0; // Physical left -> logical bottom
case 3: return 1; // Physical right -> logical top
}
break;
}
return bezelEdge;
}
int GfxRenderer::getViewableMarginTop() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return BASE_VIEWABLE_MARGIN_TOP + (logicalEdge == 1 ? bezelCompensation : 0);
}
int GfxRenderer::getViewableMarginRight() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return BASE_VIEWABLE_MARGIN_RIGHT + (logicalEdge == 3 ? bezelCompensation : 0);
}
int GfxRenderer::getViewableMarginBottom() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return BASE_VIEWABLE_MARGIN_BOTTOM + (logicalEdge == 0 ? bezelCompensation : 0);
}
int GfxRenderer::getViewableMarginLeft() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return BASE_VIEWABLE_MARGIN_LEFT + (logicalEdge == 2 ? bezelCompensation : 0);
}
int GfxRenderer::getBezelOffsetTop() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return (logicalEdge == 1) ? bezelCompensation : 0;
}
int GfxRenderer::getBezelOffsetRight() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return (logicalEdge == 3) ? bezelCompensation : 0;
}
int GfxRenderer::getBezelOffsetBottom() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return (logicalEdge == 0) ? bezelCompensation : 0;
}
int GfxRenderer::getBezelOffsetLeft() const {
int logicalEdge = mapPhysicalToLogicalEdge(bezelEdge, orientation);
return (logicalEdge == 2) ? bezelCompensation : 0;
}
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const { void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {
// Get base margins rotated for current orientation, with bezel compensation applied
switch (orientation) { switch (orientation) {
case Portrait: case Portrait:
*outTop = VIEWABLE_MARGIN_TOP; *outTop = getViewableMarginTop();
*outRight = VIEWABLE_MARGIN_RIGHT; *outRight = getViewableMarginRight();
*outBottom = VIEWABLE_MARGIN_BOTTOM; *outBottom = getViewableMarginBottom();
*outLeft = VIEWABLE_MARGIN_LEFT; *outLeft = getViewableMarginLeft();
break; break;
case LandscapeClockwise: case LandscapeClockwise:
*outTop = VIEWABLE_MARGIN_LEFT; *outTop = BASE_VIEWABLE_MARGIN_LEFT + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 1 ? bezelCompensation : 0);
*outRight = VIEWABLE_MARGIN_TOP; *outRight = BASE_VIEWABLE_MARGIN_TOP + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 3 ? bezelCompensation : 0);
*outBottom = VIEWABLE_MARGIN_RIGHT; *outBottom = BASE_VIEWABLE_MARGIN_RIGHT + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 0 ? bezelCompensation : 0);
*outLeft = VIEWABLE_MARGIN_BOTTOM; *outLeft = BASE_VIEWABLE_MARGIN_BOTTOM + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 2 ? bezelCompensation : 0);
break; break;
case PortraitInverted: case PortraitInverted:
*outTop = VIEWABLE_MARGIN_BOTTOM; *outTop = BASE_VIEWABLE_MARGIN_BOTTOM + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 1 ? bezelCompensation : 0);
*outRight = VIEWABLE_MARGIN_LEFT; *outRight = BASE_VIEWABLE_MARGIN_LEFT + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 3 ? bezelCompensation : 0);
*outBottom = VIEWABLE_MARGIN_TOP; *outBottom = BASE_VIEWABLE_MARGIN_TOP + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 0 ? bezelCompensation : 0);
*outLeft = VIEWABLE_MARGIN_RIGHT; *outLeft = BASE_VIEWABLE_MARGIN_RIGHT + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 2 ? bezelCompensation : 0);
break; break;
case LandscapeCounterClockwise: case LandscapeCounterClockwise:
*outTop = VIEWABLE_MARGIN_RIGHT; *outTop = BASE_VIEWABLE_MARGIN_RIGHT + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 1 ? bezelCompensation : 0);
*outRight = VIEWABLE_MARGIN_BOTTOM; *outRight = BASE_VIEWABLE_MARGIN_BOTTOM + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 3 ? bezelCompensation : 0);
*outBottom = VIEWABLE_MARGIN_LEFT; *outBottom = BASE_VIEWABLE_MARGIN_LEFT + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 0 ? bezelCompensation : 0);
*outLeft = VIEWABLE_MARGIN_TOP; *outLeft = BASE_VIEWABLE_MARGIN_TOP + (mapPhysicalToLogicalEdge(bezelEdge, orientation) == 2 ? bezelCompensation : 0);
break; break;
} }
} }

View File

@ -28,9 +28,17 @@ class GfxRenderer {
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");
// Base viewable margins (hardware-specific, before bezel compensation)
static constexpr int BASE_VIEWABLE_MARGIN_TOP = 9;
static constexpr int BASE_VIEWABLE_MARGIN_RIGHT = 3;
static constexpr int BASE_VIEWABLE_MARGIN_BOTTOM = 3;
static constexpr int BASE_VIEWABLE_MARGIN_LEFT = 3;
EInkDisplay& einkDisplay; EInkDisplay& einkDisplay;
RenderMode renderMode; RenderMode renderMode;
Orientation orientation; Orientation orientation;
int bezelCompensation = 0; // Pixels to add for bezel defect compensation
int bezelEdge = 0; // Which physical edge (0=bottom, 1=top, 2=left, 3=right in portrait)
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
std::map<int, EpdFontFamily> fontMap; std::map<int, EpdFontFamily> fontMap;
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState, void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
@ -42,10 +50,24 @@ class GfxRenderer {
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {} explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW), orientation(Portrait) {}
~GfxRenderer() { freeBwBufferChunks(); } ~GfxRenderer() { freeBwBufferChunks(); }
static constexpr int VIEWABLE_MARGIN_TOP = 9; // Viewable margins (includes bezel compensation applied to the configured edge)
static constexpr int VIEWABLE_MARGIN_RIGHT = 3; int getViewableMarginTop() const;
static constexpr int VIEWABLE_MARGIN_BOTTOM = 3; int getViewableMarginRight() const;
static constexpr int VIEWABLE_MARGIN_LEFT = 3; int getViewableMarginBottom() const;
int getViewableMarginLeft() const;
// Bezel compensation configuration
void setBezelCompensation(int amount, int edge) {
bezelCompensation = amount;
bezelEdge = edge;
}
// Get bezel offset for each logical edge (0 if not the compensated edge)
// Use these to add bezel compensation to hardcoded margins in UI screens
int getBezelOffsetTop() const;
int getBezelOffsetRight() const;
int getBezelOffsetBottom() const;
int getBezelOffsetLeft() const;
// Setup // Setup
void insertFont(int fontId, EpdFontFamily font); void insertFont(int fontId, EpdFontFamily font);

View File

@ -23,7 +23,7 @@ void readAndValidate(FsFile& file, uint8_t& member, const uint8_t maxValue) {
namespace { namespace {
constexpr uint8_t SETTINGS_FILE_VERSION = 1; constexpr uint8_t SETTINGS_FILE_VERSION = 1;
// Increment this when adding new persisted settings fields // Increment this when adding new persisted settings fields
constexpr uint8_t SETTINGS_COUNT = 27; // 26 + displayContrast constexpr uint8_t SETTINGS_COUNT = 29; // 28 + bezelCompensationEdge
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
} // namespace } // namespace
@ -68,6 +68,10 @@ bool CrossPointSettings::saveToFile() const {
serialization::writePod(outputFile, sleepScreenCoverFilter); serialization::writePod(outputFile, sleepScreenCoverFilter);
// System-wide display contrast // System-wide display contrast
serialization::writePod(outputFile, displayContrast); serialization::writePod(outputFile, displayContrast);
// Bezel compensation for physical screen edge defects
serialization::writePod(outputFile, bezelCompensation);
// Which physical edge needs bezel compensation
serialization::writePod(outputFile, bezelCompensationEdge);
// New fields added at end for backward compatibility // New fields added at end for backward compatibility
outputFile.close(); outputFile.close();
@ -172,6 +176,12 @@ bool CrossPointSettings::loadFromFile() {
// System-wide display contrast (0 = normal, 1 = high) // System-wide display contrast (0 = normal, 1 = high)
serialization::readPod(inputFile, displayContrast); serialization::readPod(inputFile, displayContrast);
if (++settingsRead >= fileSettingsCount) break; if (++settingsRead >= fileSettingsCount) break;
// Bezel compensation for physical screen edge defects (0-10px)
serialization::readPod(inputFile, bezelCompensation);
if (++settingsRead >= fileSettingsCount) break;
// Which physical edge needs bezel compensation
readAndValidate(inputFile, bezelCompensationEdge, BEZEL_EDGE_COUNT);
if (++settingsRead >= fileSettingsCount) break;
// New fields added at end for backward compatibility // New fields added at end for backward compatibility
} while (false); } while (false);

View File

@ -91,6 +91,10 @@ class CrossPointSettings {
// Hide battery percentage // Hide battery percentage
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT }; enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2, HIDE_BATTERY_PERCENTAGE_COUNT };
// Bezel compensation edge (which physical edge has the defect)
// These refer to physical edges in portrait orientation
enum BEZEL_EDGE { BEZEL_BOTTOM = 0, BEZEL_TOP = 1, BEZEL_LEFT = 2, BEZEL_RIGHT = 3, BEZEL_EDGE_COUNT };
// Sleep screen settings // Sleep screen settings
uint8_t sleepScreen = DARK; uint8_t sleepScreen = DARK;
// Sleep screen cover mode settings // Sleep screen cover mode settings
@ -135,6 +139,11 @@ class CrossPointSettings {
uint8_t longPressChapterSkip = 1; uint8_t longPressChapterSkip = 1;
// System-wide display contrast (0 = normal, 1 = high) // System-wide display contrast (0 = normal, 1 = high)
uint8_t displayContrast = 0; uint8_t displayContrast = 0;
// Bezel compensation - extra margin for physical screen edge defects (0-10px)
// Applied to the physical edge specified by bezelCompensationEdge, rotates with orientation
uint8_t bezelCompensation = 0;
// Which physical edge needs compensation (in portrait orientation)
uint8_t bezelCompensationEdge = BEZEL_BOTTOM;
// Pinned list name (empty = none pinned) // Pinned list name (empty = none pinned)
char pinnedListName[64] = ""; char pinnedListName[64] = "";

View File

@ -7,6 +7,7 @@
#include <string> #include <string>
#include "Battery.h" #include "Battery.h"
#include "CrossPointSettings.h"
#include "fontIds.h" #include "fontIds.h"
void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top, void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top,

View File

@ -11,10 +11,15 @@ void BootActivity::onEnter() {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
renderer.clearScreen(); renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, centerY - 64, 128, 128);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 70, "CrossPoint", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING"); renderer.drawCenteredText(SMALL_FONT_ID, centerY + 95, "BOOTING");
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30 - bezelBottom, CROSSPOINT_VERSION);
renderer.displayBuffer(); renderer.displayBuffer();
} }

View File

@ -139,10 +139,15 @@ void SleepActivity::renderDefaultSleepScreen() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
renderer.clearScreen(); renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, centerY - 64, 128, 128);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 70, "CrossPoint", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING"); renderer.drawCenteredText(SMALL_FONT_ID, centerY + 95, "SLEEPING");
// Make sleep screen dark unless light is selected in settings // Make sleep screen dark unless light is selected in settings
if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) { if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {

View File

@ -172,10 +172,17 @@ void OpdsBookBrowserActivity::render() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD); // Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "OPDS Browser", true, EpdFontFamily::BOLD);
if (state == BrowserState::CHECK_WIFI) { if (state == BrowserState::CHECK_WIFI) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, centerY, statusMessage.c_str());
const auto labels = mappedInput.mapLabels("« Back", "", "", ""); const auto labels = mappedInput.mapLabels("« Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(); renderer.displayBuffer();
@ -183,7 +190,7 @@ void OpdsBookBrowserActivity::render() const {
} }
if (state == BrowserState::LOADING) { if (state == BrowserState::LOADING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, statusMessage.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, centerY, statusMessage.c_str());
const auto labels = mappedInput.mapLabels("« Back", "", "", ""); const auto labels = mappedInput.mapLabels("« Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(); renderer.displayBuffer();
@ -191,8 +198,8 @@ void OpdsBookBrowserActivity::render() const {
} }
if (state == BrowserState::ERROR) { if (state == BrowserState::ERROR) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Error:"); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 20, "Error:");
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, errorMessage.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 10, errorMessage.c_str());
const auto labels = mappedInput.mapLabels("« Back", "Retry", "", ""); const auto labels = mappedInput.mapLabels("« Back", "Retry", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(); renderer.displayBuffer();
@ -200,13 +207,13 @@ void OpdsBookBrowserActivity::render() const {
} }
if (state == BrowserState::DOWNLOADING) { if (state == BrowserState::DOWNLOADING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 40, "Downloading..."); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 40, "Downloading...");
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 10, statusMessage.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 10, statusMessage.c_str());
if (downloadTotal > 0) { if (downloadTotal > 0) {
const int barWidth = pageWidth - 100; const int barWidth = pageWidth - 100 - bezelLeft - bezelRight;
constexpr int barHeight = 20; constexpr int barHeight = 20;
constexpr int barX = 50; const int barX = 50 + bezelLeft;
const int barY = pageHeight / 2 + 20; const int barY = centerY + 20;
ScreenComponents::drawProgressBar(renderer, barX, barY, barWidth, barHeight, downloadProgress, downloadTotal); ScreenComponents::drawProgressBar(renderer, barX, barY, barWidth, barHeight, downloadProgress, downloadTotal);
} }
renderer.displayBuffer(); renderer.displayBuffer();
@ -223,13 +230,13 @@ void OpdsBookBrowserActivity::render() const {
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
if (entries.empty()) { if (entries.empty()) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "No entries found"); renderer.drawCenteredText(UI_10_FONT_ID, centerY, "No entries found");
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS; const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS;
renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 - 2, pageWidth - 1, 30); renderer.fillRect(bezelLeft, 60 + bezelTop + (selectorIndex % PAGE_ITEMS) * 30 - 2, pageWidth - 1 - bezelLeft - bezelRight, 30);
for (size_t i = pageStartIndex; i < entries.size() && i < static_cast<size_t>(pageStartIndex + PAGE_ITEMS); i++) { for (size_t i = pageStartIndex; i < entries.size() && i < static_cast<size_t>(pageStartIndex + PAGE_ITEMS); i++) {
const auto& entry = entries[i]; const auto& entry = entries[i];
@ -246,8 +253,8 @@ void OpdsBookBrowserActivity::render() const {
} }
} }
auto item = renderer.truncatedText(UI_10_FONT_ID, displayText.c_str(), renderer.getScreenWidth() - 40); auto item = renderer.truncatedText(UI_10_FONT_ID, displayText.c_str(), renderer.getScreenWidth() - 40 - bezelLeft - bezelRight);
renderer.drawText(UI_10_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, item.c_str(), renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, 60 + bezelTop + (i % PAGE_ITEMS) * 30, item.c_str(),
i != static_cast<size_t>(selectorIndex)); i != static_cast<size_t>(selectorIndex));
} }

View File

@ -343,9 +343,12 @@ void HomeActivity::render() {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
constexpr int margin = 20; // Base margins plus bezel compensation
constexpr int bottomMargin = 60; const int margin = 20 + renderer.getBezelOffsetLeft();
constexpr int bookY = 30; const int rightMargin = 20 + renderer.getBezelOffsetRight();
const int bottomMargin = 60 + renderer.getBezelOffsetBottom();
const int topMargin = renderer.getBezelOffsetTop();
const int bookY = 30 + topMargin;
constexpr int elementSpacing = 15; constexpr int elementSpacing = 15;
// --- Calculate layout from bottom up --- // --- Calculate layout from bottom up ---
@ -366,7 +369,7 @@ void HomeActivity::render() {
fullWidthItems.insert(fullWidthItems.begin(), "OPDS Browser"); fullWidthItems.insert(fullWidthItems.begin(), "OPDS Browser");
} }
const int menuTileWidth = pageWidth - 2 * margin; const int menuTileWidth = pageWidth - margin - rightMargin;
constexpr int menuTileHeight = 45; constexpr int menuTileHeight = 45;
constexpr int menuSpacing = 8; constexpr int menuSpacing = 8;
const int halfTileWidth = (menuTileWidth - menuSpacing) / 2; // Account for spacing between halves const int halfTileWidth = (menuTileWidth - menuSpacing) / 2; // Account for spacing between halves
@ -375,10 +378,10 @@ void HomeActivity::render() {
menuTileHeight + static_cast<int>(fullWidthItems.size()) * (menuTileHeight + menuSpacing); menuTileHeight + static_cast<int>(fullWidthItems.size()) * (menuTileHeight + menuSpacing);
// Anchor menu to bottom of screen // Anchor menu to bottom of screen
const int menuStartY = pageHeight - bottomMargin - totalMenuHeight - margin; const int menuStartY = pageHeight - bottomMargin - totalMenuHeight;
// Calculate book card dimensions - larger, filling available space // Calculate book card dimensions - larger, filling available space
const int bookWidth = pageWidth - 2 * margin; const int bookWidth = pageWidth - margin - rightMargin;
// Card extends to just above menu // Card extends to just above menu
const int bookCardBottomY = menuStartY - elementSpacing; const int bookCardBottomY = menuStartY - elementSpacing;
const int bookHeight = bookCardBottomY - bookY; const int bookHeight = bookCardBottomY - bookY;
@ -721,7 +724,7 @@ void HomeActivity::render() {
for (size_t i = 0; i < fullWidthItems.size(); ++i) { for (size_t i = 0; i < fullWidthItems.size(); ++i) {
// Index offset: base + 2 (for Lists and My Library) + i // Index offset: base + 2 (for Lists and My Library) + i
const int overallIndex = baseMenuIndex + 2 + static_cast<int>(i); const int overallIndex = baseMenuIndex + 2 + static_cast<int>(i);
constexpr int tileX = margin; const int tileX = margin;
const int tileY = firstRowY + menuTileHeight + menuSpacing + static_cast<int>(i) * (menuTileHeight + menuSpacing); const int tileY = firstRowY + menuTileHeight + menuSpacing + static_cast<int>(i) * (menuTileHeight + menuSpacing);
const bool selected = selectorIndex == overallIndex; const bool selected = selectorIndex == overallIndex;

View File

@ -12,15 +12,15 @@
#include "util/StringUtils.h" #include "util/StringUtils.h"
namespace { namespace {
// Layout constants (matching MyLibraryActivity's Recent tab) // Base layout constants (bezel offsets added at render time)
constexpr int HEADER_Y = 15; constexpr int BASE_HEADER_Y = 15;
constexpr int CONTENT_START_Y = 60; constexpr int BASE_CONTENT_START_Y = 60;
constexpr int LINE_HEIGHT = 65; // Two-line items (title + author) constexpr int LINE_HEIGHT = 65; // Two-line items (title + author)
constexpr int LEFT_MARGIN = 20; constexpr int BASE_LEFT_MARGIN = 20;
constexpr int RIGHT_MARGIN = 40; constexpr int BASE_RIGHT_MARGIN = 40;
constexpr int MICRO_THUMB_WIDTH = 45; constexpr int MICRO_THUMB_WIDTH = 45;
constexpr int MICRO_THUMB_HEIGHT = 60; constexpr int MICRO_THUMB_HEIGHT = 60;
constexpr int THUMB_RIGHT_MARGIN = 50; constexpr int BASE_THUMB_RIGHT_MARGIN = 50;
// Timing thresholds // Timing thresholds
constexpr int SKIP_PAGE_MS = 700; constexpr int SKIP_PAGE_MS = 700;
@ -41,7 +41,9 @@ std::string getMicroThumbPathForBook(const std::string& bookPath) {
int ListViewActivity::getPageItems() const { int ListViewActivity::getPageItems() const {
const int screenHeight = renderer.getScreenHeight(); const int screenHeight = renderer.getScreenHeight();
const int bottomBarHeight = 60; const int bottomBarHeight = 60;
const int availableHeight = screenHeight - CONTENT_START_Y - bottomBarHeight; const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int availableHeight = screenHeight - (BASE_CONTENT_START_Y + bezelTop) - bottomBarHeight - bezelBottom;
int items = availableHeight / LINE_HEIGHT; int items = availableHeight / LINE_HEIGHT;
if (items < 1) { if (items < 1) {
items = 1; items = 1;
@ -172,6 +174,16 @@ void ListViewActivity::render() const {
const int pageItems = getPageItems(); const int pageItems = getPageItems();
const int bookCount = static_cast<int>(bookList.books.size()); const int bookCount = static_cast<int>(bookList.books.size());
// Calculate bezel-adjusted margins
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int HEADER_Y = BASE_HEADER_Y + bezelTop;
const int CONTENT_START_Y = BASE_CONTENT_START_Y + bezelTop;
const int LEFT_MARGIN = BASE_LEFT_MARGIN + bezelLeft;
const int RIGHT_MARGIN = BASE_RIGHT_MARGIN + bezelRight;
const int THUMB_RIGHT_MARGIN = BASE_THUMB_RIGHT_MARGIN + bezelRight;
// Draw header with list name // Draw header with list name
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, listName.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN); auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, listName.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, HEADER_Y, truncatedTitle.c_str(), true, EpdFontFamily::BOLD); renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, HEADER_Y, truncatedTitle.c_str(), true, EpdFontFamily::BOLD);
@ -190,7 +202,7 @@ void ListViewActivity::render() const {
const auto pageStartIndex = selectorIndex / pageItems * pageItems; const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN, renderer.fillRect(bezelLeft, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN - bezelLeft,
LINE_HEIGHT); LINE_HEIGHT);
// Calculate available text width // Calculate available text width

View File

@ -31,16 +31,16 @@ void MyLibraryActivity::clearThumbExistsCache() {
} }
namespace { namespace {
// Layout constants // Base layout constants (bezel offsets added at render time)
constexpr int TAB_BAR_Y = 15; constexpr int BASE_TAB_BAR_Y = 15;
constexpr int CONTENT_START_Y = 60; constexpr int BASE_CONTENT_START_Y = 60;
constexpr int LINE_HEIGHT = 30; constexpr int LINE_HEIGHT = 30;
constexpr int RECENTS_LINE_HEIGHT = 65; // Increased for two-line items constexpr int RECENTS_LINE_HEIGHT = 65; // Increased for two-line items
constexpr int LEFT_MARGIN = 20; constexpr int BASE_LEFT_MARGIN = 20;
constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator constexpr int BASE_RIGHT_MARGIN = 40; // Extra space for scroll indicator
constexpr int MICRO_THUMB_WIDTH = 45; constexpr int MICRO_THUMB_WIDTH = 45;
constexpr int MICRO_THUMB_HEIGHT = 60; constexpr int MICRO_THUMB_HEIGHT = 60;
constexpr int THUMB_RIGHT_MARGIN = 50; // Space from right edge for thumbnail constexpr int BASE_THUMB_RIGHT_MARGIN = 50; // Space from right edge for thumbnail
// Helper function to get the micro-thumb path for a book based on its file path // Helper function to get the micro-thumb path for a book based on its file path
std::string getMicroThumbPathForBook(const std::string& bookPath) { std::string getMicroThumbPathForBook(const std::string& bookPath) {
@ -74,7 +74,9 @@ void sortFileList(std::vector<std::string>& strs) {
int MyLibraryActivity::getPageItems() const { int MyLibraryActivity::getPageItems() const {
const int screenHeight = renderer.getScreenHeight(); const int screenHeight = renderer.getScreenHeight();
const int bottomBarHeight = 60; // Space for button hints const int bottomBarHeight = 60; // Space for button hints
const int availableHeight = screenHeight - CONTENT_START_Y - bottomBarHeight; const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int availableHeight = screenHeight - (BASE_CONTENT_START_Y + bezelTop) - bottomBarHeight - bezelBottom;
// Recent tab uses taller items (title + author), Lists and Files use single-line items // Recent tab uses taller items (title + author), Lists and Files use single-line items
const int lineHeight = (currentTab == Tab::Recent) ? RECENTS_LINE_HEIGHT : LINE_HEIGHT; const int lineHeight = (currentTab == Tab::Recent) ? RECENTS_LINE_HEIGHT : LINE_HEIGHT;
int items = availableHeight / lineHeight; int items = availableHeight / lineHeight;
@ -700,6 +702,12 @@ void MyLibraryActivity::render() const {
return; return;
} }
// Calculate bezel-adjusted margins
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int TAB_BAR_Y = BASE_TAB_BAR_Y + bezelTop;
const int CONTENT_START_Y = BASE_CONTENT_START_Y + bezelTop;
// Normal state - draw library view // Normal state - draw library view
// Draw tab bar // Draw tab bar
std::vector<TabInfo> tabs = {{"Recent", currentTab == Tab::Recent}, std::vector<TabInfo> tabs = {{"Recent", currentTab == Tab::Recent},
@ -718,7 +726,7 @@ void MyLibraryActivity::render() const {
// Draw scroll indicator // Draw scroll indicator
const int screenHeight = renderer.getScreenHeight(); const int screenHeight = renderer.getScreenHeight();
const int contentHeight = screenHeight - CONTENT_START_Y - 60; // 60 for bottom bar const int contentHeight = screenHeight - CONTENT_START_Y - 60 - bezelBottom; // 60 for bottom bar
ScreenComponents::drawScrollIndicator(renderer, getCurrentPage(), getTotalPages(), CONTENT_START_Y, contentHeight); ScreenComponents::drawScrollIndicator(renderer, getCurrentPage(), getTotalPages(), CONTENT_START_Y, contentHeight);
// Draw side button hints (up/down navigation on right side) // Draw side button hints (up/down navigation on right side)
@ -737,6 +745,15 @@ void MyLibraryActivity::renderRecentTab() const {
const int pageItems = getPageItems(); const int pageItems = getPageItems();
const int bookCount = static_cast<int>(recentBooks.size()); const int bookCount = static_cast<int>(recentBooks.size());
// Calculate bezel-adjusted margins
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int CONTENT_START_Y = BASE_CONTENT_START_Y + bezelTop;
const int LEFT_MARGIN = BASE_LEFT_MARGIN + bezelLeft;
const int RIGHT_MARGIN = BASE_RIGHT_MARGIN + bezelRight;
const int THUMB_RIGHT_MARGIN = BASE_THUMB_RIGHT_MARGIN + bezelRight;
if (bookCount == 0) { if (bookCount == 0) {
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books"); renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
return; return;
@ -745,8 +762,8 @@ void MyLibraryActivity::renderRecentTab() const {
const auto pageStartIndex = selectorIndex / pageItems * pageItems; const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2, renderer.fillRect(bezelLeft, CONTENT_START_Y + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2,
pageWidth - RIGHT_MARGIN, RECENTS_LINE_HEIGHT); pageWidth - RIGHT_MARGIN - bezelLeft, RECENTS_LINE_HEIGHT);
// Calculate available text width (leaving space for thumbnail on the right) // Calculate available text width (leaving space for thumbnail on the right)
const int textMaxWidth = pageWidth - LEFT_MARGIN - RIGHT_MARGIN - MICRO_THUMB_WIDTH - 10; const int textMaxWidth = pageWidth - LEFT_MARGIN - RIGHT_MARGIN - MICRO_THUMB_WIDTH - 10;
@ -897,6 +914,14 @@ void MyLibraryActivity::renderListsTab() const {
const int pageItems = getPageItems(); const int pageItems = getPageItems();
const int listCount = static_cast<int>(lists.size()); const int listCount = static_cast<int>(lists.size());
// Calculate bezel-adjusted margins
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int CONTENT_START_Y = BASE_CONTENT_START_Y + bezelTop;
const int LEFT_MARGIN = BASE_LEFT_MARGIN + bezelLeft;
const int RIGHT_MARGIN = BASE_RIGHT_MARGIN + bezelRight;
if (listCount == 0) { if (listCount == 0) {
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No lists found"); renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No lists found");
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y + LINE_HEIGHT, "Create lists in Companion App"); renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y + LINE_HEIGHT, "Create lists in Companion App");
@ -906,7 +931,7 @@ void MyLibraryActivity::renderListsTab() const {
const auto pageStartIndex = selectorIndex / pageItems * pageItems; const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN, renderer.fillRect(bezelLeft, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN - bezelLeft,
LINE_HEIGHT); LINE_HEIGHT);
// Draw items // Draw items
@ -927,6 +952,14 @@ void MyLibraryActivity::renderFilesTab() const {
const int pageItems = getPageItems(); const int pageItems = getPageItems();
const int fileCount = static_cast<int>(files.size()); const int fileCount = static_cast<int>(files.size());
// Calculate bezel-adjusted margins
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int CONTENT_START_Y = BASE_CONTENT_START_Y + bezelTop;
const int LEFT_MARGIN = BASE_LEFT_MARGIN + bezelLeft;
const int RIGHT_MARGIN = BASE_RIGHT_MARGIN + bezelRight;
if (fileCount == 0) { if (fileCount == 0) {
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No books found"); renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No books found");
return; return;
@ -935,7 +968,7 @@ void MyLibraryActivity::renderFilesTab() const {
const auto pageStartIndex = selectorIndex / pageItems * pageItems; const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN, renderer.fillRect(bezelLeft, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN - bezelLeft,
LINE_HEIGHT); LINE_HEIGHT);
// Draw items // Draw items
@ -950,12 +983,17 @@ void MyLibraryActivity::renderActionMenu() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
// Title // Title
renderer.drawCenteredText(UI_12_FONT_ID, 20, "Book Actions", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 20 + bezelTop, "Book Actions", true, EpdFontFamily::BOLD);
// Show filename // Show filename
const int filenameY = 70; const int filenameY = 70 + bezelTop;
auto truncatedName = renderer.truncatedText(UI_10_FONT_ID, actionTargetName.c_str(), pageWidth - 40); auto truncatedName = renderer.truncatedText(UI_10_FONT_ID, actionTargetName.c_str(), pageWidth - 40 - bezelLeft - bezelRight);
renderer.drawCenteredText(UI_10_FONT_ID, filenameY, truncatedName.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, filenameY, truncatedName.c_str());
// Menu options - 4 for Recent tab, 2 for Files tab // Menu options - 4 for Recent tab, 2 for Files tab

View File

@ -211,10 +211,13 @@ void CalibreConnectActivity::render() const {
renderer.clearScreen(); renderer.clearScreen();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
if (state == CalibreConnectState::SERVER_STARTING) { if (state == CalibreConnectState::SERVER_STARTING) {
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Calibre...", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, centerY - 20, "Starting Calibre...", true, EpdFontFamily::BOLD);
} else if (state == CalibreConnectState::ERROR) { } else if (state == CalibreConnectState::ERROR) {
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Calibre setup failed", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, centerY - 20, "Calibre setup failed", true, EpdFontFamily::BOLD);
} }
renderer.displayBuffer(); renderer.displayBuffer();
} }
@ -224,7 +227,8 @@ void CalibreConnectActivity::renderServerRunning() const {
constexpr int SMALL_SPACING = 20; constexpr int SMALL_SPACING = 20;
constexpr int SECTION_SPACING = 40; constexpr int SECTION_SPACING = 40;
constexpr int TOP_PADDING = 14; constexpr int TOP_PADDING = 14;
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Connect to Calibre", true, EpdFontFamily::BOLD); const int bezelTop = renderer.getBezelOffsetTop();
renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "Connect to Calibre", true, EpdFontFamily::BOLD);
int y = 55 + TOP_PADDING; int y = 55 + TOP_PADDING;
renderer.drawCenteredText(UI_10_FONT_ID, y, "Network", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, y, "Network", true, EpdFontFamily::BOLD);

View File

@ -460,7 +460,10 @@ void CrossPointWebServerActivity::render() const {
} else if (state == WebServerActivityState::AP_STARTING) { } else if (state == WebServerActivityState::AP_STARTING) {
renderer.clearScreen(); renderer.clearScreen();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
renderer.drawCenteredText(UI_12_FONT_ID, pageHeight / 2 - 20, "Starting Hotspot...", true, EpdFontFamily::BOLD); const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
renderer.drawCenteredText(UI_12_FONT_ID, centerY - 20, "Starting Hotspot...", true, EpdFontFamily::BOLD);
renderer.displayBuffer(); renderer.displayBuffer();
} }
} }

View File

@ -105,15 +105,21 @@ void NetworkModeSelectionActivity::render() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
// Draw header // Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "File Transfer", true, EpdFontFamily::BOLD);
// Draw subtitle // Draw subtitle
renderer.drawCenteredText(UI_10_FONT_ID, 50, "How would you like to connect?"); renderer.drawCenteredText(UI_10_FONT_ID, 50 + bezelTop, "How would you like to connect?");
// Draw menu items centered on screen // Draw menu items centered on screen
constexpr int itemHeight = 50; // Height for each menu item (including description) constexpr int itemHeight = 50; // Height for each menu item (including description)
const int startY = (pageHeight - (MENU_ITEM_COUNT * itemHeight)) / 2 + 10; const int startY = (pageHeight - bezelTop - bezelBottom - (MENU_ITEM_COUNT * itemHeight)) / 2 + bezelTop + 10;
for (int i = 0; i < MENU_ITEM_COUNT; i++) { for (int i = 0; i < MENU_ITEM_COUNT; i++) {
const int itemY = startY + i * itemHeight; const int itemY = startY + i * itemHeight;
@ -121,13 +127,13 @@ void NetworkModeSelectionActivity::render() const {
// Draw selection highlight (black fill) for selected item // Draw selection highlight (black fill) for selected item
if (isSelected) { if (isSelected) {
renderer.fillRect(20, itemY - 2, pageWidth - 40, itemHeight - 6); renderer.fillRect(20 + bezelLeft, itemY - 2, pageWidth - 40 - bezelLeft - bezelRight, itemHeight - 6);
} }
// Draw text: black=false (white text) when selected (on black background) // Draw text: black=false (white text) when selected (on black background)
// black=true (black text) when not selected (on white background) // black=true (black text) when not selected (on white background)
renderer.drawText(UI_10_FONT_ID, 30, itemY, MENU_ITEMS[i], /*black=*/!isSelected); renderer.drawText(UI_10_FONT_ID, 30 + bezelLeft, itemY, MENU_ITEMS[i], /*black=*/!isSelected);
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected); renderer.drawText(SMALL_FONT_ID, 30 + bezelLeft, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected);
} }
// Draw help text at bottom // Draw help text at bottom

View File

@ -513,8 +513,14 @@ void WifiSelectionActivity::renderNetworkList() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
// Draw header // Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "WiFi Networks", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "WiFi Networks", true, EpdFontFamily::BOLD);
if (networks.empty()) { if (networks.empty()) {
// No networks found or scan failed // No networks found or scan failed
@ -524,9 +530,9 @@ void WifiSelectionActivity::renderNetworkList() const {
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again"); renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again");
} else { } else {
// Calculate how many networks we can display // Calculate how many networks we can display
constexpr int startY = 60; const int startY = 60 + bezelTop;
constexpr int lineHeight = 25; constexpr int lineHeight = 25;
const int maxVisibleNetworks = (pageHeight - startY - 40) / lineHeight; const int maxVisibleNetworks = (pageHeight - startY - 40 - bezelBottom) / lineHeight;
// Calculate scroll offset to keep selected item visible // Calculate scroll offset to keep selected item visible
int scrollOffset = 0; int scrollOffset = 0;
@ -542,7 +548,7 @@ void WifiSelectionActivity::renderNetworkList() const {
// Draw selection indicator // Draw selection indicator
if (static_cast<int>(i) == selectedNetworkIndex) { if (static_cast<int>(i) == selectedNetworkIndex) {
renderer.drawText(UI_10_FONT_ID, 5, networkY, ">"); renderer.drawText(UI_10_FONT_ID, 5 + bezelLeft, networkY, ">");
} }
// Draw network name (truncate if too long) // Draw network name (truncate if too long)
@ -550,42 +556,42 @@ void WifiSelectionActivity::renderNetworkList() const {
if (displayName.length() > 16) { if (displayName.length() > 16) {
displayName.replace(13, displayName.length() - 13, "..."); displayName.replace(13, displayName.length() - 13, "...");
} }
renderer.drawText(UI_10_FONT_ID, 20, networkY, displayName.c_str()); renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, networkY, displayName.c_str());
// Draw signal strength indicator // Draw signal strength indicator
std::string signalStr = getSignalStrengthIndicator(network.rssi); std::string signalStr = getSignalStrengthIndicator(network.rssi);
renderer.drawText(UI_10_FONT_ID, pageWidth - 90, networkY, signalStr.c_str()); renderer.drawText(UI_10_FONT_ID, pageWidth - 90 - bezelRight, networkY, signalStr.c_str());
// Draw saved indicator (checkmark) for networks with saved passwords // Draw saved indicator (checkmark) for networks with saved passwords
if (network.hasSavedPassword) { if (network.hasSavedPassword) {
renderer.drawText(UI_10_FONT_ID, pageWidth - 50, networkY, "+"); renderer.drawText(UI_10_FONT_ID, pageWidth - 50 - bezelRight, networkY, "+");
} }
// Draw lock icon for encrypted networks // Draw lock icon for encrypted networks
if (network.isEncrypted) { if (network.isEncrypted) {
renderer.drawText(UI_10_FONT_ID, pageWidth - 30, networkY, "*"); renderer.drawText(UI_10_FONT_ID, pageWidth - 30 - bezelRight, networkY, "*");
} }
} }
// Draw scroll indicators if needed // Draw scroll indicators if needed
if (scrollOffset > 0) { if (scrollOffset > 0) {
renderer.drawText(SMALL_FONT_ID, pageWidth - 15, startY - 10, "^"); renderer.drawText(SMALL_FONT_ID, pageWidth - 15 - bezelRight, startY - 10, "^");
} }
if (scrollOffset + maxVisibleNetworks < static_cast<int>(networks.size())) { if (scrollOffset + maxVisibleNetworks < static_cast<int>(networks.size())) {
renderer.drawText(SMALL_FONT_ID, pageWidth - 15, startY + maxVisibleNetworks * lineHeight, "v"); renderer.drawText(SMALL_FONT_ID, pageWidth - 15 - bezelRight, startY + maxVisibleNetworks * lineHeight, "v");
} }
// Show network count // Show network count
char countStr[32]; char countStr[32];
snprintf(countStr, sizeof(countStr), "%zu networks found", networks.size()); snprintf(countStr, sizeof(countStr), "%zu networks found", networks.size());
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr); renderer.drawText(SMALL_FONT_ID, 20 + bezelLeft, pageHeight - 90 - bezelBottom, countStr);
} }
// Show MAC address above the network count and legend // Show MAC address above the network count and legend
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str()); renderer.drawText(SMALL_FONT_ID, 20 + bezelLeft, pageHeight - 105 - bezelBottom, cachedMacAddress.c_str());
// Draw help text // Draw help text
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved"); renderer.drawText(SMALL_FONT_ID, 20 + bezelLeft, pageHeight - 75 - bezelBottom, "* = Encrypted | + = Saved");
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", ""); const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
} }

View File

@ -166,28 +166,33 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
const int pageItems = getPageItems(); const int pageItems = getPageItems();
const int totalItems = getTotalItems(); const int totalItems = getTotalItems();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const std::string title = const std::string title =
renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40, EpdFontFamily::BOLD); renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40 - bezelLeft - bezelRight, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, title.c_str(), true, EpdFontFamily::BOLD);
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(bezelLeft, 60 + bezelTop + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1 - bezelLeft - bezelRight, 30);
for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) { for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) {
const int displayY = 60 + (itemIndex % pageItems) * 30; const int displayY = 60 + bezelTop + (itemIndex % pageItems) * 30;
const bool isSelected = (itemIndex == selectorIndex); const bool isSelected = (itemIndex == selectorIndex);
if (isSyncItem(itemIndex)) { if (isSyncItem(itemIndex)) {
// Draw sync option (at top or bottom) // 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 + bezelLeft, displayY, ">> Sync Progress", !isSelected);
} else { } else {
// Draw TOC item (account for top sync offset) // 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 + bezelLeft + (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 - bezelLeft - bezelRight - indentSize + 20 + bezelLeft);
renderer.drawText(UI_10_FONT_ID, indentSize, 60 + (tocIndex % pageItems) * 30, chapterName.c_str(), renderer.drawText(UI_10_FONT_ID, indentSize, 60 + bezelTop + (tocIndex % pageItems) * 30, chapterName.c_str(),
tocIndex != selectorIndex); tocIndex != selectorIndex);
} }
} }

View File

@ -153,21 +153,26 @@ void CalibreSettingsActivity::render() {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
// Draw header // Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "OPDS Browser", true, EpdFontFamily::BOLD);
// Draw info text about Calibre // Draw info text about Calibre
renderer.drawCenteredText(UI_10_FONT_ID, 40, "For Calibre, add /opds to your URL"); renderer.drawCenteredText(UI_10_FONT_ID, 40 + bezelTop, "For Calibre, add /opds to your URL");
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, 70 + selectedIndex * 30 - 2, pageWidth - 1, 30); renderer.fillRect(bezelLeft, 70 + bezelTop + selectedIndex * 30 - 2, pageWidth - 1 - bezelLeft - bezelRight, 30);
// Draw menu items // Draw menu items
for (int i = 0; i < MENU_ITEMS; i++) { for (int i = 0; i < MENU_ITEMS; i++) {
const int settingY = 70 + i * 30; const int settingY = 70 + bezelTop + i * 30;
const bool isSelected = (i == selectedIndex); const bool isSelected = (i == selectedIndex);
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected); renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, settingY, menuNames[i], !isSelected);
// Draw status for each setting // Draw status for each setting
const char* status = "[Not Set]"; const char* status = "[Not Set]";
@ -179,7 +184,7 @@ void CalibreSettingsActivity::render() {
status = (strlen(SETTINGS.opdsPassword) > 0) ? "[Set]" : "[Not Set]"; status = (strlen(SETTINGS.opdsPassword) > 0) ? "[Set]" : "[Not Set]";
} }
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status); const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected); renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - bezelRight - width, settingY, status, !isSelected);
} }
// Draw button hints // Draw button hints

View File

@ -124,6 +124,11 @@ void CategorySettingsActivity::toggleCurrentSetting() {
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) { } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
const uint8_t currentValue = SETTINGS.*(setting.valuePtr); const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size()); SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
// Handle side effects for specific settings
if (setting.valuePtr == &CrossPointSettings::bezelCompensationEdge) {
renderer.setBezelCompensation(SETTINGS.bezelCompensation, SETTINGS.bezelCompensationEdge);
}
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) { } else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
const int8_t currentValue = SETTINGS.*(setting.valuePtr); const int8_t currentValue = SETTINGS.*(setting.valuePtr);
if (currentValue + setting.valueRange.step > setting.valueRange.max) { if (currentValue + setting.valueRange.step > setting.valueRange.max) {
@ -131,6 +136,11 @@ void CategorySettingsActivity::toggleCurrentSetting() {
} else { } else {
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step; SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
} }
// Handle side effects for specific settings
if (setting.valuePtr == &CrossPointSettings::bezelCompensation) {
renderer.setBezelCompensation(SETTINGS.bezelCompensation, SETTINGS.bezelCompensationEdge);
}
} else if (setting.type == SettingType::ACTION) { } else if (setting.type == SettingType::ACTION) {
if (strcmp(setting.name, "Calibre Settings") == 0) { if (strcmp(setting.name, "Calibre Settings") == 0) {
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
@ -182,10 +192,16 @@ void CategorySettingsActivity::render() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD); // Bezel compensation offsets
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int bezelBottom = renderer.getBezelOffsetBottom();
renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, categoryName, true, EpdFontFamily::BOLD);
// Draw selection highlight // Draw selection highlight
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30); renderer.fillRect(bezelLeft, 60 + bezelTop + selectedSettingIndex * 30 - 2, pageWidth - 1 - bezelLeft - bezelRight, 30);
// Draw only visible settings // Draw only visible settings
int visibleIndex = 0; int visibleIndex = 0;
@ -194,11 +210,11 @@ void CategorySettingsActivity::render() const {
continue; // Skip hidden settings continue; // Skip hidden settings
} }
const int settingY = 60 + visibleIndex * 30; // 30 pixels between settings const int settingY = 60 + bezelTop + visibleIndex * 30; // 30 pixels between settings
const bool isSelected = (visibleIndex == selectedSettingIndex); const bool isSelected = (visibleIndex == selectedSettingIndex);
// Draw setting name // Draw setting name
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected); renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, settingY, settingsList[i].name, !isSelected);
// Draw value based on setting type // Draw value based on setting type
std::string valueText; std::string valueText;
@ -215,14 +231,14 @@ void CategorySettingsActivity::render() const {
} }
if (!valueText.empty()) { if (!valueText.empty()) {
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()); const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected); renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - bezelRight - width, settingY, valueText.c_str(), !isSelected);
} }
visibleIndex++; visibleIndex++;
} }
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - bezelRight - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION); pageHeight - 60 - bezelBottom, CROSSPOINT_VERSION);
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", ""); const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -58,15 +58,20 @@ void ClearCacheActivity::displayTaskLoop() {
void ClearCacheActivity::render() { void ClearCacheActivity::render() {
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
renderer.clearScreen(); renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Clear Cache", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "Clear Cache", true, EpdFontFamily::BOLD);
if (state == WARNING) { if (state == WARNING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, "This will clear all cached book data.", true); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 60, "This will clear all cached book data.", true);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 30, "All reading progress will be lost!", true, renderer.drawCenteredText(UI_10_FONT_ID, centerY - 30, "All reading progress will be lost!", true,
EpdFontFamily::BOLD); EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Books will need to be re-indexed", true); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 10, "Books will need to be re-indexed", true);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 30, "when opened again.", true); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 30, "when opened again.", true);
const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", ""); const auto labels = mappedInput.mapLabels("« Cancel", "Clear", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -75,18 +80,18 @@ void ClearCacheActivity::render() {
} }
if (state == CLEARING) { if (state == CLEARING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Clearing cache...", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY, "Clearing cache...", true, EpdFontFamily::BOLD);
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
if (state == SUCCESS) { if (state == SUCCESS) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Cache Cleared", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 20, "Cache Cleared", true, EpdFontFamily::BOLD);
String resultText = String(clearedCount) + " items removed"; String resultText = String(clearedCount) + " items removed";
if (failedCount > 0) { if (failedCount > 0) {
resultText += ", " + String(failedCount) + " failed"; resultText += ", " + String(failedCount) + " failed";
} }
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, resultText.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 10, resultText.c_str());
const auto labels = mappedInput.mapLabels("« Back", "", "", ""); const auto labels = mappedInput.mapLabels("« Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -95,8 +100,8 @@ void ClearCacheActivity::render() {
} }
if (state == FAILED) { if (state == FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 20, "Failed to clear cache", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 20, "Failed to clear cache", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 10, "Check serial output for details"); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 10, "Check serial output for details");
const auto labels = mappedInput.mapLabels("« Back", "", "", ""); const auto labels = mappedInput.mapLabels("« Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -126,20 +126,28 @@ void OtaUpdateActivity::render() {
} }
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int centerY = (pageHeight - bezelTop - bezelBottom) / 2 + bezelTop;
renderer.clearScreen(); renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Update", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "Update", true, EpdFontFamily::BOLD);
if (state == CHECKING_FOR_UPDATE) { if (state == CHECKING_FOR_UPDATE) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Checking for update...", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY, "Checking for update...", true, EpdFontFamily::BOLD);
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
if (state == WAITING_CONFIRMATION) { if (state == WAITING_CONFIRMATION) {
renderer.drawCenteredText(UI_10_FONT_ID, 200, "New update available!", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 100, "New update available!", true, EpdFontFamily::BOLD);
renderer.drawText(UI_10_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION); renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, centerY - 50, "Current Version: " CROSSPOINT_VERSION);
renderer.drawText(UI_10_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str()); renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, centerY - 30, ("New Version: " + updater.getLatestVersion()).c_str());
const auto labels = mappedInput.mapLabels("Cancel", "Update", "", ""); const auto labels = mappedInput.mapLabels("Cancel", "Update", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -148,33 +156,33 @@ void OtaUpdateActivity::render() {
} }
if (state == UPDATE_IN_PROGRESS) { if (state == UPDATE_IN_PROGRESS) {
renderer.drawCenteredText(UI_10_FONT_ID, 310, "Updating...", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY - 40, "Updating...", true, EpdFontFamily::BOLD);
renderer.drawRect(20, 350, pageWidth - 40, 50); renderer.drawRect(20 + bezelLeft, centerY, pageWidth - 40 - bezelLeft - bezelRight, 50);
renderer.fillRect(24, 354, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42); renderer.fillRect(24 + bezelLeft, centerY + 4, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44 - bezelLeft - bezelRight)), 42);
renderer.drawCenteredText(UI_10_FONT_ID, 420, renderer.drawCenteredText(UI_10_FONT_ID, centerY + 70,
(std::to_string(static_cast<int>(updaterProgress * 100)) + "%").c_str()); (std::to_string(static_cast<int>(updaterProgress * 100)) + "%").c_str());
renderer.drawCenteredText( renderer.drawCenteredText(
UI_10_FONT_ID, 440, UI_10_FONT_ID, centerY + 90,
(std::to_string(updater.processedSize) + " / " + std::to_string(updater.totalSize)).c_str()); (std::to_string(updater.processedSize) + " / " + std::to_string(updater.totalSize)).c_str());
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
if (state == NO_UPDATE) { if (state == NO_UPDATE) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "No update available", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY, "No update available", true, EpdFontFamily::BOLD);
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
if (state == FAILED) { if (state == FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update failed", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY, "Update failed", true, EpdFontFamily::BOLD);
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
if (state == FINISHED) { if (state == FINISHED) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update complete", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, centerY, "Update complete", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 350, "Press and hold power button to turn back on"); renderer.drawCenteredText(UI_10_FONT_ID, centerY + 50, "Press and hold power button to turn back on");
renderer.displayBuffer(); renderer.displayBuffer();
state = SHUTTING_DOWN; state = SHUTTING_DOWN;
return; return;

View File

@ -12,7 +12,10 @@
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"}; const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
namespace { namespace {
constexpr int displaySettingsCount = 7; // Visibility condition for bezel edge setting (only show when compensation > 0)
bool isBezelCompensationEnabled() { return SETTINGS.bezelCompensation > 0; }
constexpr int displaySettingsCount = 9;
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"}),
@ -24,7 +27,10 @@ const SettingInfo displaySettings[displaySettingsCount] = {
{"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}), {"None", "No Progress", "Full w/ Percentage", "Full w/ Progress Bar", "Progress Bar"}),
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,
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})}; {"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
SettingInfo::Value("Bezel Compensation", &CrossPointSettings::bezelCompensation, {0, 10, 1}),
SettingInfo::Enum("Bezel Edge", &CrossPointSettings::bezelCompensationEdge,
{"Bottom", "Top", "Left", "Right"}, isBezelCompensationEnabled)};
// Helper to get custom font names as a vector // Helper to get custom font names as a vector
std::vector<std::string> getCustomFontNamesVector() { std::vector<std::string> getCustomFontNamesVector() {
@ -213,23 +219,29 @@ void SettingsActivity::render() const {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Bezel compensation offsets
const int bezelTop = renderer.getBezelOffsetTop();
const int bezelLeft = renderer.getBezelOffsetLeft();
const int bezelRight = renderer.getBezelOffsetRight();
const int bezelBottom = renderer.getBezelOffsetBottom();
// Draw header // Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15 + bezelTop, "Settings", true, EpdFontFamily::BOLD);
// Draw selection // Draw selection
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30); renderer.fillRect(bezelLeft, 60 + bezelTop + selectedCategoryIndex * 30 - 2, pageWidth - 1 - bezelLeft - bezelRight, 30);
// Draw all categories // Draw all categories
for (int i = 0; i < categoryCount; i++) { for (int i = 0; i < categoryCount; i++) {
const int categoryY = 60 + i * 30; // 30 pixels between categories const int categoryY = 60 + bezelTop + i * 30; // 30 pixels between categories
// Draw category name // Draw category name
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex); renderer.drawText(UI_10_FONT_ID, 20 + bezelLeft, categoryY, categoryNames[i], i != selectedCategoryIndex);
} }
// Draw version text above button hints // Draw version text above button hints
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - bezelRight - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION); pageHeight - 60 - bezelBottom, CROSSPOINT_VERSION);
// Draw help text // Draw help text
const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");

View File

@ -8,7 +8,9 @@ void FullScreenMessageActivity::onEnter() {
Activity::onEnter(); Activity::onEnter();
const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto height = renderer.getLineHeight(UI_10_FONT_ID);
const auto top = (renderer.getScreenHeight() - height) / 2; const int bezelTop = renderer.getBezelOffsetTop();
const int bezelBottom = renderer.getBezelOffsetBottom();
const auto top = (renderer.getScreenHeight() - bezelTop - bezelBottom - height) / 2 + bezelTop;
renderer.clearScreen(); renderer.clearScreen();
renderer.drawCenteredText(UI_10_FONT_ID, top, text.c_str(), true, style); renderer.drawCenteredText(UI_10_FONT_ID, top, text.c_str(), true, style);

View File

@ -475,6 +475,8 @@ void setup() {
SETTINGS.loadFromFile(); SETTINGS.loadFromFile();
// Apply high contrast mode from settings // Apply high contrast mode from settings
setHighContrastMode(SETTINGS.displayContrast == 1); setHighContrastMode(SETTINGS.displayContrast == 1);
// Apply bezel compensation from settings
renderer.setBezelCompensation(SETTINGS.bezelCompensation, SETTINGS.bezelCompensationEdge);
if (isWakeupByPowerButton()) { if (isWakeupByPowerButton()) {
// For normal wakeups, verify power button press duration // For normal wakeups, verify power button press duration