This commit is contained in:
Maeve Andrews 2026-02-03 18:34:48 +03:00 committed by GitHub
commit 2717767aa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 293 additions and 280 deletions

View File

@ -494,11 +494,11 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
setOrientation(Orientation::Portrait);
const int pageHeight = getScreenHeight();
constexpr int buttonWidth = 106;
constexpr int buttonWidth = 94;
constexpr int buttonHeight = 40;
constexpr int buttonY = 40; // Distance from bottom
constexpr int textYOffset = 7; // Distance from top of button to text baseline
constexpr int buttonPositions[] = {25, 130, 245, 350};
constexpr int buttonPositions[] = {50, 143, 247, 340};
const char* labels[] = {btn1, btn2, btn3, btn4};
for (int i = 0; i < 4; i++) {
@ -516,7 +516,10 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
setOrientation(orig_orientation);
}
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const {
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) {
const Orientation orig_orientation = getOrientation();
setOrientation(Orientation::Portrait);
const int screenWidth = getScreenWidth();
constexpr int buttonWidth = 40; // Width on screen (height when rotated)
constexpr int buttonHeight = 80; // Height on screen (width when rotated)
@ -565,6 +568,8 @@ void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, cons
drawTextRotated90CW(fontId, textX, textY, labels[i]);
}
}
setOrientation(orig_orientation);
}
int GfxRenderer::getTextHeight(const int fontId) const {

View File

@ -85,7 +85,7 @@ class GfxRenderer {
// UI Components
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn);
private:
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)

View File

@ -0,0 +1,78 @@
#include "Activity.h"
#include <GfxRenderer.h>
#include "CrossPointSettings.h"
void Activity::onEnter() {
Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str());
updateMargins();
}
void Activity::onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
void Activity::updateMargins() {
switch (SETTINGS.orientation) {
case CrossPointSettings::ORIENTATION::PORTRAIT:
marginTop = 15;
marginBottom = 50;
marginLeft = marginRight = 20;
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CW:
marginTop = 15;
marginLeft = 50;
marginRight = marginBottom = 20;
break;
case CrossPointSettings::ORIENTATION::INVERTED:
marginTop = 50;
marginBottom = 15;
marginLeft = marginRight = 20;
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW:
marginTop = 15;
marginRight = 50;
marginLeft = marginBottom = 20;
break;
default:
break;
}
contentStartY = marginTop + 45;
}
void Activity::updateRendererOrientation() {
switch (SETTINGS.orientation) {
case CrossPointSettings::ORIENTATION::PORTRAIT:
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeClockwise);
break;
case CrossPointSettings::ORIENTATION::INVERTED:
renderer.setOrientation(GfxRenderer::Orientation::PortraitInverted);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise);
break;
}
updateMargins();
}
// Number of items that fit on a page, derived from logical screen height.
// This adapts automatically when switching between portrait and landscape.
int Activity::getPageItems() const {
// Layout constants used in renderScreen
const int startY = contentStartY;
constexpr int lineHeight = LINE_HEIGHT;
const int screenHeight = renderer.getScreenHeight();
const int endY = screenHeight - lineHeight;
const int availableHeight = endY - startY;
int items = availableHeight / lineHeight;
// Ensure we always have at least one item per page to avoid division by zero
if (items < 1) {
items = 1;
}
return items;
}

View File

@ -8,19 +8,31 @@
class MappedInputManager;
class GfxRenderer;
constexpr int LINE_HEIGHT = 30;
class Activity {
protected:
std::string name;
GfxRenderer& renderer;
MappedInputManager& mappedInput;
int marginTop = 15;
int marginBottom = 50;
int marginLeft = 20;
int marginRight = 20;
int contentStartY = 60;
public:
explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput)
: name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {}
virtual ~Activity() = default;
virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); }
virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
virtual void onEnter();
virtual void onExit();
virtual void loop() {}
virtual bool skipLoopDelay() { return false; }
virtual bool preventAutoSleep() { return false; }
void updateMargins();
void updateRendererOrientation();
int getPageItems() const;
};

View File

@ -7,6 +7,7 @@
void BootActivity::onEnter() {
Activity::onEnter();
updateRendererOrientation();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();

View File

@ -126,6 +126,7 @@ void SleepActivity::renderDefaultSleepScreen() const {
void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
int x, y;
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
float cropX = 0, cropY = 0;

View File

@ -228,7 +228,7 @@ void OpdsBookBrowserActivity::render() const {
}
const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS;
renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + (selectorIndex % PAGE_ITEMS) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
for (size_t i = pageStartIndex; i < entries.size() && i < static_cast<size_t>(pageStartIndex + PAGE_ITEMS); i++) {
const auto& entry = entries[i];
@ -246,7 +246,7 @@ void OpdsBookBrowserActivity::render() const {
}
auto item = renderer.truncatedText(UI_10_FONT_ID, displayText.c_str(), renderer.getScreenWidth() - 40);
renderer.drawText(UI_10_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, item.c_str(),
renderer.drawText(UI_10_FONT_ID, 20, contentStartY + (i % PAGE_ITEMS) * LINE_HEIGHT, item.c_str(),
i != static_cast<size_t>(selectorIndex));
}

View File

@ -220,14 +220,29 @@ void HomeActivity::render() {
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
constexpr int margin = 20;
constexpr int bottomMargin = 60;
// --- Bottom menu tiles ---
// Build menu items dynamically
std::vector<const char*> menuItems = {"My Library", "File Transfer", "Settings"};
if (hasOpdsUrl) {
// Insert OPDS Browser after My Library
menuItems.insert(menuItems.begin() + 1, "OPDS Browser");
}
const int menuTileWidth = pageWidth - marginLeft - marginRight;
constexpr int menuTileHeight = 45;
constexpr int menuSpacing = 8;
const int totalMenuHeight =
static_cast<int>(menuItems.size()) * menuTileHeight + (static_cast<int>(menuItems.size()) - 1) * menuSpacing;
// --- Top "book" card for the current title (selectorIndex == 0) ---
// if we're in inverted portrait, battery and hints can overlap
GfxRenderer::Orientation orientation = renderer.getOrientation();
const int batteryHeight = (orientation == GfxRenderer::Orientation::PortraitInverted ? 0 : 20);
const int bookWidth = pageWidth / 2;
const int bookHeight = pageHeight / 2;
const int bookX = (pageWidth - bookWidth) / 2;
constexpr int bookY = 30;
const int maxBookHeight = pageHeight - marginTop - marginBottom - totalMenuHeight - batteryHeight;
const int bookHeight = std::min(pageHeight / 2, maxBookHeight);
const int bookX = marginLeft + (pageWidth - marginLeft - marginRight - bookWidth) / 2;
const int bookY = marginTop + batteryHeight;
const bool bookSelected = hasContinueReading && selectorIndex == 0;
// Bookmark dimensions (used in multiple places)
@ -443,7 +458,7 @@ void HomeActivity::render() {
const int boxWidth = maxTextWidth + boxPadding * 2;
const int boxHeight = totalTextHeight + boxPadding * 2;
const int boxX = (pageWidth - boxWidth) / 2;
const int boxX = bookX + (bookWidth - boxWidth) / 2;
const int boxY = titleYStart - boxPadding;
// Draw box (inverted when selected: black box instead of white)
@ -453,7 +468,8 @@ void HomeActivity::render() {
}
for (const auto& line : lines) {
renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected);
renderer.drawText(UI_12_FONT_ID, bookX + (bookWidth - renderer.getTextWidth(UI_12_FONT_ID, line.c_str())) / 2,
titleYStart, line.c_str(), !bookSelected);
titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
}
@ -474,26 +490,27 @@ void HomeActivity::render() {
}
trimmedAuthor.append("...");
}
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected);
renderer.drawText(UI_10_FONT_ID,
bookX + (bookWidth - renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str())) / 2,
titleYStart, trimmedAuthor.c_str(), !bookSelected);
}
// "Continue Reading" label at the bottom
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
if (coverRendered) {
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
const char* continueText = "Continue Reading";
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
if (coverRendered) {
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
constexpr int continuePadding = 6;
const int continueBoxWidth = continueTextWidth + continuePadding * 2;
const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding;
const int continueBoxX = (pageWidth - continueBoxWidth) / 2;
const int continueBoxX = bookX + (bookWidth - continueBoxWidth) / 2;
const int continueBoxY = continueY - continuePadding / 2;
renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected);
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
} else {
renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected);
}
renderer.drawText(UI_10_FONT_ID, bookX + (bookWidth - continueTextWidth) / 2, continueY, continueText,
!bookSelected);
} else {
// No book to continue reading
const int y =
@ -502,30 +519,11 @@ void HomeActivity::render() {
renderer.drawCenteredText(UI_10_FONT_ID, y + renderer.getLineHeight(UI_12_FONT_ID), "Start reading below");
}
// --- Bottom menu tiles ---
// Build menu items dynamically
std::vector<const char*> menuItems = {"My Library", "File Transfer", "Settings"};
if (hasOpdsUrl) {
// Insert OPDS Browser after My Library
menuItems.insert(menuItems.begin() + 1, "OPDS Browser");
}
const int menuTileWidth = pageWidth - 2 * margin;
constexpr int menuTileHeight = 45;
constexpr int menuSpacing = 8;
const int totalMenuHeight =
static_cast<int>(menuItems.size()) * menuTileHeight + (static_cast<int>(menuItems.size()) - 1) * menuSpacing;
int menuStartY = bookY + bookHeight + 15;
// Ensure we don't collide with the bottom button legend
const int maxMenuStartY = pageHeight - bottomMargin - totalMenuHeight - margin;
if (menuStartY > maxMenuStartY) {
menuStartY = maxMenuStartY;
}
int menuStartY = bookY + bookHeight + menuSpacing;
for (size_t i = 0; i < menuItems.size(); ++i) {
const int overallIndex = static_cast<int>(i) + (hasContinueReading ? 1 : 0);
constexpr int tileX = margin;
const int tileX = marginLeft;
const int tileY = menuStartY + static_cast<int>(i) * (menuTileHeight + menuSpacing);
const bool selected = selectorIndex == overallIndex;
@ -553,7 +551,7 @@ void HomeActivity::render() {
// get percentage so we can align text properly
const uint16_t percentage = battery.readPercentage();
const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : "";
const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
const auto batteryX = pageWidth - 30 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
renderer.displayBuffer();

View File

@ -13,12 +13,8 @@
namespace {
// Layout constants
constexpr int TAB_BAR_Y = 15;
constexpr int CONTENT_START_Y = 60;
constexpr int LINE_HEIGHT = 30;
constexpr int RECENTS_LINE_HEIGHT = 65; // Increased for two-line items
constexpr int LEFT_MARGIN = 20;
constexpr int RIGHT_MARGIN = 40; // Extra space for scroll indicator
constexpr int scrollIndicatorWidth = 20;
// Timing thresholds
constexpr int SKIP_PAGE_MS = 700;
@ -35,17 +31,6 @@ void sortFileList(std::vector<std::string>& strs) {
}
} // namespace
int MyLibraryActivity::getPageItems() const {
const int screenHeight = renderer.getScreenHeight();
const int bottomBarHeight = 60; // Space for button hints
const int availableHeight = screenHeight - CONTENT_START_Y - bottomBarHeight;
int items = availableHeight / LINE_HEIGHT;
if (items < 1) {
items = 1;
}
return items;
}
int MyLibraryActivity::getCurrentItemCount() const {
if (currentTab == Tab::Recent) {
return static_cast<int>(recentBooks.size());
@ -286,7 +271,7 @@ void MyLibraryActivity::render() const {
// Draw tab bar
std::vector<TabInfo> tabs = {{"Recent", currentTab == Tab::Recent}, {"Files", currentTab == Tab::Files}};
ScreenComponents::drawTabBar(renderer, TAB_BAR_Y, tabs);
ScreenComponents::drawTabBar(renderer, marginTop, tabs);
// Draw content based on current tab
if (currentTab == Tab::Recent) {
@ -297,8 +282,8 @@ void MyLibraryActivity::render() const {
// Draw scroll indicator
const int screenHeight = renderer.getScreenHeight();
const int contentHeight = screenHeight - CONTENT_START_Y - 60; // 60 for bottom bar
ScreenComponents::drawScrollIndicator(renderer, getCurrentPage(), getTotalPages(), CONTENT_START_Y, contentHeight);
const int contentHeight = screenHeight - contentStartY - marginBottom;
ScreenComponents::drawScrollIndicator(renderer, getCurrentPage(), getTotalPages(), contentStartY, contentHeight);
// Draw side button hints (up/down navigation on right side)
// Note: text is rotated 90° CW, so ">" appears as "^" and "<" appears as "v"
@ -317,20 +302,20 @@ void MyLibraryActivity::renderRecentTab() const {
const int bookCount = static_cast<int>(recentBooks.size());
if (bookCount == 0) {
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
renderer.drawText(UI_10_FONT_ID, marginLeft, contentStartY, "No recent books");
return;
}
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Draw selection highlight
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2,
pageWidth - RIGHT_MARGIN, RECENTS_LINE_HEIGHT);
renderer.fillRect(0, contentStartY + (selectorIndex % pageItems) * RECENTS_LINE_HEIGHT - 2,
pageWidth - marginRight - scrollIndicatorWidth, RECENTS_LINE_HEIGHT);
// Draw items
for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) {
const auto& book = recentBooks[i];
const int y = CONTENT_START_Y + (i % pageItems) * RECENTS_LINE_HEIGHT;
const int y = contentStartY + (i % pageItems) * RECENTS_LINE_HEIGHT;
// Line 1: Title
std::string title = book.title;
@ -346,14 +331,15 @@ void MyLibraryActivity::renderRecentTab() const {
title.resize(dot);
}
}
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
renderer.drawText(UI_12_FONT_ID, LEFT_MARGIN, y + 2, truncatedTitle.c_str(), i != selectorIndex);
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(),
pageWidth - marginLeft - marginRight - scrollIndicatorWidth);
renderer.drawText(UI_12_FONT_ID, marginLeft, y + 2, truncatedTitle.c_str(), i != selectorIndex);
// Line 2: Author
if (!book.author.empty()) {
auto truncatedAuthor =
renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, y + 32, truncatedAuthor.c_str(), i != selectorIndex);
auto truncatedAuthor = renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(),
pageWidth - marginLeft - marginRight - scrollIndicatorWidth);
renderer.drawText(UI_10_FONT_ID, marginLeft, y + 32, truncatedAuthor.c_str(), i != selectorIndex);
}
}
}
@ -364,20 +350,21 @@ void MyLibraryActivity::renderFilesTab() const {
const int fileCount = static_cast<int>(files.size());
if (fileCount == 0) {
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No books found");
renderer.drawText(UI_10_FONT_ID, marginLeft, contentStartY, "No books found");
return;
}
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
// Draw selection highlight
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN,
LINE_HEIGHT);
renderer.fillRect(0, contentStartY + (selectorIndex % pageItems) * LINE_HEIGHT - 2,
pageWidth - marginRight - scrollIndicatorWidth, LINE_HEIGHT);
// Draw items
for (int i = pageStartIndex; i < fileCount && i < pageStartIndex + pageItems; i++) {
auto item = renderer.truncatedText(UI_10_FONT_ID, files[i].c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y + (i % pageItems) * LINE_HEIGHT, item.c_str(),
auto item = renderer.truncatedText(UI_10_FONT_ID, files[i].c_str(),
pageWidth - marginLeft - marginRight - scrollIndicatorWidth);
renderer.drawText(UI_10_FONT_ID, marginLeft, contentStartY + (i % pageItems) * LINE_HEIGHT, item.c_str(),
i != selectorIndex);
}
}

View File

@ -34,7 +34,6 @@ class MyLibraryActivity final : public Activity {
const std::function<void(const std::string& path, Tab fromTab)> onSelectBook;
// Number of items that fit on a page
int getPageItems() const;
int getCurrentItemCount() const;
int getTotalPages() const;
int getCurrentPage() const;

View File

@ -417,12 +417,13 @@ void drawQRCode(const GfxRenderer& renderer, const int x, const int y, const std
void CrossPointWebServerActivity::renderServerRunning() const {
// Use consistent line spacing
constexpr int LINE_SPACING = 28; // Space between lines
const int screenWidth = renderer.getScreenWidth();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "File Transfer", true, EpdFontFamily::BOLD);
if (isApMode) {
// AP mode display - center the content block
int startY = 55;
int startY = marginTop + 35;
renderer.drawCenteredText(UI_10_FONT_ID, startY, "Hotspot Mode", true, EpdFontFamily::BOLD);
@ -435,7 +436,7 @@ void CrossPointWebServerActivity::renderServerRunning() const {
"or scan QR code with your phone to connect to Wifi.");
// Show QR code for URL
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;";
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
drawQRCode(renderer, (screenWidth - 6 * 33) / 2, startY + LINE_SPACING * 4, wifiConfig);
startY += 6 * 29 + 3 * LINE_SPACING;
// Show primary URL (hostname)
@ -449,10 +450,10 @@ void CrossPointWebServerActivity::renderServerRunning() const {
// Show QR code for URL
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:");
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
drawQRCode(renderer, (screenWidth - 6 * 33) / 2, startY + LINE_SPACING * 7, hostnameUrl);
} else {
// STA mode display (original behavior)
const int startY = 65;
const int startY = marginTop + 50;
std::string ssidInfo = "Network: " + connectedSSID;
if (ssidInfo.length() > 28) {
@ -474,7 +475,7 @@ void CrossPointWebServerActivity::renderServerRunning() const {
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser");
// Show QR code for URL
drawQRCode(renderer, (480 - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
drawQRCode(renderer, (screenWidth - 6 * 33) / 2, startY + LINE_SPACING * 6, webInfo);
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 5, "or scan QR code with your phone:");
}

View File

@ -105,10 +105,10 @@ void NetworkModeSelectionActivity::render() const {
const auto pageHeight = renderer.getScreenHeight();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "File Transfer", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "File Transfer", true, EpdFontFamily::BOLD);
// Draw subtitle
renderer.drawCenteredText(UI_10_FONT_ID, 50, "How would you like to connect?");
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 35, "How would you like to connect?");
// Draw menu items centered on screen
constexpr int itemHeight = 50; // Height for each menu item (including description)
@ -120,13 +120,13 @@ void NetworkModeSelectionActivity::render() const {
// Draw selection highlight (black fill) for selected item
if (isSelected) {
renderer.fillRect(20, itemY - 2, pageWidth - 40, itemHeight - 6);
renderer.fillRect(marginLeft, itemY - 2, pageWidth - marginLeft - marginRight, itemHeight - 6);
}
// Draw text: black=false (white text) when selected (on black 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(SMALL_FONT_ID, 30, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected);
renderer.drawText(UI_10_FONT_ID, marginLeft + 10, itemY, MENU_ITEMS[i], /*black=*/!isSelected);
renderer.drawText(SMALL_FONT_ID, marginLeft + 10, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected);
}
// Draw help text at bottom

View File

@ -513,7 +513,7 @@ void WifiSelectionActivity::renderNetworkList() const {
const auto pageHeight = renderer.getScreenHeight();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "WiFi Networks", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "WiFi Networks", true, EpdFontFamily::BOLD);
if (networks.empty()) {
// No networks found or scan failed
@ -523,8 +523,8 @@ void WifiSelectionActivity::renderNetworkList() const {
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again");
} else {
// Calculate how many networks we can display
constexpr int startY = 60;
constexpr int lineHeight = 25;
const int startY = contentStartY;
constexpr int lineHeight = 25; // tighter spacing than normal menus
const int maxVisibleNetworks = (pageHeight - startY - 40) / lineHeight;
// Calculate scroll offset to keep selected item visible
@ -541,7 +541,7 @@ void WifiSelectionActivity::renderNetworkList() const {
// Draw selection indicator
if (static_cast<int>(i) == selectedNetworkIndex) {
renderer.drawText(UI_10_FONT_ID, 5, networkY, ">");
renderer.drawText(UI_10_FONT_ID, marginLeft - 15, networkY, ">");
}
// Draw network name (truncate if too long)
@ -549,42 +549,47 @@ void WifiSelectionActivity::renderNetworkList() const {
if (displayName.length() > 33) {
displayName.replace(30, displayName.length() - 30, "...");
}
renderer.drawText(UI_10_FONT_ID, 20, networkY, displayName.c_str());
renderer.drawText(UI_10_FONT_ID, marginLeft, networkY, displayName.c_str());
// Draw signal strength indicator
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 - marginRight - 90, networkY, signalStr.c_str());
// Draw saved indicator (checkmark) for networks with saved passwords
if (network.hasSavedPassword) {
renderer.drawText(UI_10_FONT_ID, pageWidth - 50, networkY, "+");
renderer.drawText(UI_10_FONT_ID, pageWidth - marginRight - 50, networkY, "+");
}
// Draw lock icon for encrypted networks
if (network.isEncrypted) {
renderer.drawText(UI_10_FONT_ID, pageWidth - 30, networkY, "*");
renderer.drawText(UI_10_FONT_ID, pageWidth - marginRight - 30, networkY, "*");
}
}
// Draw scroll indicators if needed
if (scrollOffset > 0) {
renderer.drawText(SMALL_FONT_ID, pageWidth - 15, startY - 10, "^");
renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - 15, startY - 10, "^");
}
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 - marginRight - 15, startY + maxVisibleNetworks * lineHeight, "v");
}
// Show network count
char countStr[32];
snprintf(countStr, sizeof(countStr), "%zu networks found", networks.size());
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr);
renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, countStr) - 95,
pageHeight - 90, countStr);
}
// 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,
pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, cachedMacAddress.c_str()) - 95,
pageHeight - 105, cachedMacAddress.c_str());
// Draw help text
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
const char helpLabel[] = "* = Encrypted | + = Saved";
renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, helpLabel) - 95,
pageHeight - 75, helpLabel);
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
}

View File

@ -34,24 +34,6 @@ void EpubReaderActivity::onEnter() {
return;
}
// Configure screen orientation based on settings
switch (SETTINGS.orientation) {
case CrossPointSettings::ORIENTATION::PORTRAIT:
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeClockwise);
break;
case CrossPointSettings::ORIENTATION::INVERTED:
renderer.setOrientation(GfxRenderer::Orientation::PortraitInverted);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise);
break;
default:
break;
}
renderingMutex = xSemaphoreCreateMutex();
epub->setupCacheDir();
@ -101,9 +83,6 @@ void EpubReaderActivity::onEnter() {
void EpubReaderActivity::onExit() {
ActivityWithSubactivity::onExit();
// Reset orientation back to portrait for the rest of the UI
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {

View File

@ -32,24 +32,6 @@ int EpubReaderChapterSelectionActivity::tocIndexFromItemIndex(int itemIndex) con
return itemIndex - offset;
}
int EpubReaderChapterSelectionActivity::getPageItems() const {
// Layout constants used in renderScreen
constexpr int startY = 60;
constexpr int lineHeight = 30;
const int screenHeight = renderer.getScreenHeight();
const int endY = screenHeight - lineHeight;
const int availableHeight = endY - startY;
int items = availableHeight / lineHeight;
// Ensure we always have at least one item per page to avoid division by zero
if (items < 1) {
items = 1;
}
return items;
}
void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
auto* self = static_cast<EpubReaderChapterSelectionActivity*>(param);
self->displayTaskLoop();
@ -184,21 +166,21 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Chapter", true, EpdFontFamily::BOLD);
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
for (int i = 0; i < pageItems; i++) {
int itemIndex = pageStartIndex + i;
if (itemIndex >= totalItems) break;
const int displayY = 60 + i * 30;
const int displayY = contentStartY + (itemIndex % pageItems) * LINE_HEIGHT;
const bool isSelected = (itemIndex == selectorIndex);
if (isSyncItem(itemIndex)) {
renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected);
renderer.drawText(UI_10_FONT_ID, marginLeft, displayY, ">> Sync Progress", !isSelected);
} else {
const int tocIndex = tocIndexFromItemIndex(itemIndex);
auto item = epub->getTocItem(tocIndex);
const int indentSize = 20 + (item.level - 1) * 15;
const int indentSize = marginLeft + (item.level - 1) * 15;
const std::string chapterName =
renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize);
@ -206,11 +188,8 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
}
}
// Skip button hints in landscape CW mode (they overlap content)
if (renderer.getOrientation() != GfxRenderer::LandscapeClockwise) {
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
}
renderer.displayBuffer();
}

View File

@ -22,10 +22,6 @@ class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity
const std::function<void(int newSpineIndex)> onSelectSpineIndex;
const std::function<void(int newSpineIndex, int newPage)> onSyncPosition;
// Number of items that fit on a page, derived from logical screen height.
// This adapts automatically when switching between portrait and landscape.
int getPageItems() const;
// Total items including sync options (top and bottom)
int getTotalItems() const;

View File

@ -259,11 +259,11 @@ void KOReaderSyncActivity::render() {
const auto pageWidth = renderer.getScreenWidth();
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "KOReader Sync", true, EpdFontFamily::BOLD);
if (state == NO_CREDENTIALS) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "No credentials configured", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, "Set up KOReader account in Settings");
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "No credentials configured", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, "Set up KOReader account in Settings");
const auto labels = mappedInput.mapLabels("Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -272,14 +272,14 @@ void KOReaderSyncActivity::render() {
}
if (state == SYNCING || state == UPLOADING) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 285, statusMessage.c_str(), true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
if (state == SHOWING_RESULT) {
// Show comparison
renderer.drawCenteredText(UI_10_FONT_ID, 120, "Progress found!", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 105, "Progress found!", true, EpdFontFamily::BOLD);
// Get chapter names from TOC
const int remoteTocIndex = epub->getTocIndexForSpineIndex(remotePosition.spineIndex);
@ -291,52 +291,52 @@ void KOReaderSyncActivity::render() {
: ("Section " + std::to_string(currentSpineIndex + 1));
// Remote progress - chapter and page
renderer.drawText(UI_10_FONT_ID, 20, 160, "Remote:", true);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 145, "Remote:", true);
char remoteChapterStr[128];
snprintf(remoteChapterStr, sizeof(remoteChapterStr), " %s", remoteChapter.c_str());
renderer.drawText(UI_10_FONT_ID, 20, 185, remoteChapterStr);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 170, remoteChapterStr);
char remotePageStr[64];
snprintf(remotePageStr, sizeof(remotePageStr), " Page %d, %.2f%% overall", remotePosition.pageNumber + 1,
remoteProgress.percentage * 100);
renderer.drawText(UI_10_FONT_ID, 20, 210, remotePageStr);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 195, remotePageStr);
if (!remoteProgress.device.empty()) {
char deviceStr[64];
snprintf(deviceStr, sizeof(deviceStr), " From: %s", remoteProgress.device.c_str());
renderer.drawText(UI_10_FONT_ID, 20, 235, deviceStr);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 220, deviceStr);
}
// Local progress - chapter and page
renderer.drawText(UI_10_FONT_ID, 20, 270, "Local:", true);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 255, "Local:", true);
char localChapterStr[128];
snprintf(localChapterStr, sizeof(localChapterStr), " %s", localChapter.c_str());
renderer.drawText(UI_10_FONT_ID, 20, 295, localChapterStr);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 280, localChapterStr);
char localPageStr[64];
snprintf(localPageStr, sizeof(localPageStr), " Page %d/%d, %.2f%% overall", currentPage + 1, totalPagesInSpine,
localProgress.percentage * 100);
renderer.drawText(UI_10_FONT_ID, 20, 320, localPageStr);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 305, localPageStr);
// Options
const int optionY = 350;
const int optionY = marginTop + 335;
const int optionHeight = 30;
// Apply option
if (selectedOption == 0) {
renderer.fillRect(0, optionY - 2, pageWidth - 1, optionHeight);
}
renderer.drawText(UI_10_FONT_ID, 20, optionY, "Apply remote progress", selectedOption != 0);
renderer.drawText(UI_10_FONT_ID, marginLeft, optionY, "Apply remote progress", selectedOption != 0);
// Upload option
if (selectedOption == 1) {
renderer.fillRect(0, optionY + optionHeight - 2, pageWidth - 1, optionHeight);
}
renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, "Upload local progress", selectedOption != 1);
renderer.drawText(UI_10_FONT_ID, marginLeft, optionY + optionHeight, "Upload local progress", selectedOption != 1);
// Cancel option
if (selectedOption == 2) {
renderer.fillRect(0, optionY + optionHeight * 2 - 2, pageWidth - 1, optionHeight);
}
renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight * 2, "Cancel", selectedOption != 2);
renderer.drawText(UI_10_FONT_ID, marginLeft, optionY + optionHeight * 2, "Cancel", selectedOption != 2);
const auto labels = mappedInput.mapLabels("", "Select", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -345,8 +345,8 @@ void KOReaderSyncActivity::render() {
}
if (state == NO_REMOTE_PROGRESS) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "No remote progress found", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, "Upload current position?");
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "No remote progress found", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, "Upload current position?");
const auto labels = mappedInput.mapLabels("Cancel", "Upload", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -355,7 +355,7 @@ void KOReaderSyncActivity::render() {
}
if (state == UPLOAD_COMPLETE) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Progress uploaded!", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 285, "Progress uploaded!", true, EpdFontFamily::BOLD);
const auto labels = mappedInput.mapLabels("Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -364,8 +364,8 @@ void KOReaderSyncActivity::render() {
}
if (state == SYNC_FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Sync failed", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, statusMessage.c_str());
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "Sync failed", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, statusMessage.c_str());
const auto labels = mappedInput.mapLabels("Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -35,24 +35,6 @@ void TxtReaderActivity::onEnter() {
return;
}
// Configure screen orientation based on settings
switch (SETTINGS.orientation) {
case CrossPointSettings::ORIENTATION::PORTRAIT:
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeClockwise);
break;
case CrossPointSettings::ORIENTATION::INVERTED:
renderer.setOrientation(GfxRenderer::Orientation::PortraitInverted);
break;
case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW:
renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise);
break;
default:
break;
}
renderingMutex = xSemaphoreCreateMutex();
txt->setupCacheDir();
@ -76,9 +58,6 @@ void TxtReaderActivity::onEnter() {
void TxtReaderActivity::onExit() {
ActivityWithSubactivity::onExit();
// Reset orientation back to portrait for the rest of the UI
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
// Wait until not rendering to delete task
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {

View File

@ -30,6 +30,7 @@ void XtcReaderActivity::taskTrampoline(void* param) {
void XtcReaderActivity::onEnter() {
ActivityWithSubactivity::onEnter();
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
if (!xtc) {
return;
@ -60,6 +61,7 @@ void XtcReaderActivity::onEnter() {
void XtcReaderActivity::onExit() {
ActivityWithSubactivity::onExit();
updateRendererOrientation();
// Wait until not rendering to delete task
xSemaphoreTake(renderingMutex, portMAX_DELAY);
@ -84,6 +86,7 @@ void XtcReaderActivity::loop() {
if (xtc && xtc->hasChapters() && !xtc->getChapters().empty()) {
xSemaphoreTake(renderingMutex, portMAX_DELAY);
exitActivity();
updateRendererOrientation();
enterNewActivity(new XtcReaderChapterSelectionActivity(
this->renderer, this->mappedInput, xtc, currentPage,
[this] {
@ -93,6 +96,7 @@ void XtcReaderActivity::loop() {
[this](const uint32_t newPage) {
currentPage = newPage;
exitActivity();
renderer.setOrientation(GfxRenderer::Orientation::Portrait);
updateRequired = true;
}));
xSemaphoreGive(renderingMutex);

View File

@ -9,21 +9,6 @@ namespace {
constexpr int SKIP_PAGE_MS = 700;
} // namespace
int XtcReaderChapterSelectionActivity::getPageItems() const {
constexpr int startY = 60;
constexpr int lineHeight = 30;
const int screenHeight = renderer.getScreenHeight();
const int endY = screenHeight - lineHeight;
const int availableHeight = endY - startY;
int items = availableHeight / lineHeight;
if (items < 1) {
items = 1;
}
return items;
}
int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) const {
if (!xtc) {
return 0;
@ -132,28 +117,26 @@ void XtcReaderChapterSelectionActivity::renderScreen() {
const auto pageWidth = renderer.getScreenWidth();
const int pageItems = getPageItems();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Select Chapter", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Select Chapter", true, EpdFontFamily::BOLD);
const auto& chapters = xtc->getChapters();
if (chapters.empty()) {
renderer.drawCenteredText(UI_10_FONT_ID, 120, "No chapters");
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 105, "No chapters");
renderer.displayBuffer();
return;
}
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
for (int i = pageStartIndex; i < static_cast<int>(chapters.size()) && i < pageStartIndex + pageItems; i++) {
const auto& chapter = chapters[i];
const char* title = chapter.name.empty() ? "Unnamed" : chapter.name.c_str();
renderer.drawText(UI_10_FONT_ID, 20, 60 + (i % pageItems) * 30, title, i != selectorIndex);
renderer.drawText(UI_10_FONT_ID, marginLeft, contentStartY + (i % pageItems) * LINE_HEIGHT, title,
i != selectorIndex);
}
// Skip button hints in landscape CW mode (they overlap content)
if (renderer.getOrientation() != GfxRenderer::LandscapeClockwise) {
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
}
renderer.displayBuffer();
}

View File

@ -18,7 +18,6 @@ class XtcReaderChapterSelectionActivity final : public Activity {
const std::function<void()> onGoBack;
const std::function<void(uint32_t newPage)> onSelectPage;
int getPageItems() const;
int findChapterIndexForPage(uint32_t page) const;
static void taskTrampoline(void* param);

View File

@ -80,7 +80,7 @@ void CalibreSettingsActivity::handleSelection() {
// OPDS Server URL
exitActivity();
enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "OPDS Server URL", SETTINGS.opdsServerUrl, 10,
renderer, mappedInput, "OPDS Server URL", SETTINGS.opdsServerUrl, 50,
127, // maxLength
false, // not password
[this](const std::string& url) {
@ -98,7 +98,7 @@ void CalibreSettingsActivity::handleSelection() {
// Username
exitActivity();
enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "Username", SETTINGS.opdsUsername, 10,
renderer, mappedInput, "Username", SETTINGS.opdsUsername, 50,
63, // maxLength
false, // not password
[this](const std::string& username) {
@ -116,7 +116,7 @@ void CalibreSettingsActivity::handleSelection() {
// Password
exitActivity();
enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "Password", SETTINGS.opdsPassword, 10,
renderer, mappedInput, "Password", SETTINGS.opdsPassword, 50,
63, // maxLength
false, // not password mode
[this](const std::string& password) {
@ -153,20 +153,20 @@ void CalibreSettingsActivity::render() {
const auto pageWidth = renderer.getScreenWidth();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "OPDS Browser", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "OPDS Browser", true, EpdFontFamily::BOLD);
// 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, marginTop + 25, "For Calibre, add /opds to your URL");
// Draw selection highlight
renderer.fillRect(0, 70 + selectedIndex * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + 10 + selectedIndex * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
// Draw menu items
for (int i = 0; i < MENU_ITEMS; i++) {
const int settingY = 70 + i * 30;
const int settingY = contentStartY + 10 + i * LINE_HEIGHT;
const bool isSelected = (i == selectedIndex);
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected);
renderer.drawText(UI_10_FONT_ID, marginLeft, settingY, menuNames[i], !isSelected);
// Draw status for each setting
const char* status = "[Not Set]";
@ -178,7 +178,7 @@ void CalibreSettingsActivity::render() {
status = (strlen(SETTINGS.opdsPassword) > 0) ? "[Set]" : "[Not Set]";
}
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 - marginRight - width, settingY, status, !isSelected);
}
// Draw button hints

View File

@ -87,6 +87,9 @@ void CategorySettingsActivity::toggleCurrentSetting() {
} else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast<uint8_t>(setting.enumValues.size());
if (strcmp(setting.name, "Orientation") == 0) {
updateRendererOrientation();
}
} else if (setting.type == SettingType::VALUE && setting.valuePtr != nullptr) {
const int8_t currentValue = SETTINGS.*(setting.valuePtr);
if (currentValue + setting.valueRange.step > setting.valueRange.max) {
@ -151,20 +154,22 @@ void CategorySettingsActivity::render() const {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
const int pageItems = getPageItems();
renderer.drawCenteredText(UI_12_FONT_ID, 15, categoryName, true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, categoryName, true, EpdFontFamily::BOLD);
// Draw selection highlight
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + (selectedSettingIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1,
LINE_HEIGHT);
// Draw all settings
for (int i = 0; i < settingsCount; i++) {
const int settingY = 60 + i * 30; // 30 pixels between settings
const auto pageStartIndex = selectedSettingIndex / pageItems * pageItems;
for (int i = pageStartIndex; i < settingsCount && i < pageStartIndex + pageItems; i++) {
const int settingY = contentStartY + (i % pageItems) * LINE_HEIGHT;
const bool isSelected = (i == selectedSettingIndex);
// Draw setting name
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
renderer.drawText(UI_10_FONT_ID, marginLeft, settingY, settingsList[i].name, !isSelected);
// Draw value based on setting type
std::string valueText;
@ -179,12 +184,12 @@ void CategorySettingsActivity::render() const {
}
if (!valueText.empty()) {
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 - marginRight - width, settingY, valueText.c_str(), !isSelected);
}
}
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION);
renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
marginTop + 5, CROSSPOINT_VERSION);
const auto labels = mappedInput.mapLabels("« Back", "Toggle", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -56,7 +56,7 @@ void ClearCacheActivity::render() {
const auto pageHeight = renderer.getScreenHeight();
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Clear Cache", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Clear Cache", true, EpdFontFamily::BOLD);
if (state == WARNING) {
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 - 60, "This will clear all cached book data.", true);

View File

@ -123,17 +123,17 @@ void KOReaderAuthActivity::render() {
}
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Auth", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "KOReader Auth", true, EpdFontFamily::BOLD);
if (state == AUTHENTICATING) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 285, statusMessage.c_str(), true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
if (state == SUCCESS) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Success!", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, "KOReader sync is ready to use");
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "Success!", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, "KOReader sync is ready to use");
const auto labels = mappedInput.mapLabels("Done", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -142,8 +142,8 @@ void KOReaderAuthActivity::render() {
}
if (state == FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Authentication Failed", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str());
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "Authentication Failed", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, errorMessage.c_str());
const auto labels = mappedInput.mapLabels("Back", "", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);

View File

@ -81,7 +81,7 @@ void KOReaderSettingsActivity::handleSelection() {
// Username
exitActivity();
enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "KOReader Username", KOREADER_STORE.getUsername(), 10,
renderer, mappedInput, "KOReader Username", KOREADER_STORE.getUsername(), 50,
64, // maxLength
false, // not password
[this](const std::string& username) {
@ -98,7 +98,7 @@ void KOReaderSettingsActivity::handleSelection() {
// Password
exitActivity();
enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "KOReader Password", KOREADER_STORE.getPassword(), 10,
renderer, mappedInput, "KOReader Password", KOREADER_STORE.getPassword(), 50,
64, // maxLength
false, // show characters
[this](const std::string& password) {
@ -117,7 +117,7 @@ void KOReaderSettingsActivity::handleSelection() {
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
exitActivity();
enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "Sync Server URL", prefillUrl, 10,
renderer, mappedInput, "Sync Server URL", prefillUrl, 50,
128, // maxLength - URLs can be long
false, // not password
[this](const std::string& url) {
@ -175,17 +175,17 @@ void KOReaderSettingsActivity::render() {
const auto pageWidth = renderer.getScreenWidth();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "KOReader Sync", true, EpdFontFamily::BOLD);
// Draw selection highlight
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + selectedIndex * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
// Draw menu items
for (int i = 0; i < MENU_ITEMS; i++) {
const int settingY = 60 + i * 30;
const int settingY = contentStartY + i * LINE_HEIGHT;
const bool isSelected = (i == selectedIndex);
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected);
renderer.drawText(UI_10_FONT_ID, marginLeft, settingY, menuNames[i], !isSelected);
// Draw status for each item
const char* status = "";
@ -202,7 +202,7 @@ void KOReaderSettingsActivity::render() {
}
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 - marginRight - width, settingY, status, !isSelected);
}
// Draw button hints

View File

@ -128,18 +128,19 @@ void OtaUpdateActivity::render() {
const auto pageWidth = renderer.getScreenWidth();
renderer.clearScreen();
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Update", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Update", true, EpdFontFamily::BOLD);
if (state == CHECKING_FOR_UPDATE) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Checking for update...", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 285, "Checking for update...", true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
if (state == WAITING_CONFIRMATION) {
renderer.drawCenteredText(UI_10_FONT_ID, 200, "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, 270, ("New Version: " + updater.getLatestVersion()).c_str());
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 185, "New update available!", true, EpdFontFamily::BOLD);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 235, "Current Version: " CROSSPOINT_VERSION);
renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 255,
("New Version: " + updater.getLatestVersion()).c_str());
const auto labels = mappedInput.mapLabels("Cancel", "Update", "", "");
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
@ -148,33 +149,34 @@ void OtaUpdateActivity::render() {
}
if (state == UPDATE_IN_PROGRESS) {
renderer.drawCenteredText(UI_10_FONT_ID, 310, "Updating...", true, EpdFontFamily::BOLD);
renderer.drawRect(20, 350, pageWidth - 40, 50);
renderer.fillRect(24, 354, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42);
renderer.drawCenteredText(UI_10_FONT_ID, 420,
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 295, "Updating...", true, EpdFontFamily::BOLD);
renderer.drawRect(marginLeft, marginTop + 335, pageWidth - 40, 50);
renderer.fillRect(marginLeft + 4, marginTop + 339,
static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 405,
(std::to_string(static_cast<int>(updaterProgress * 100)) + "%").c_str());
renderer.drawCenteredText(
UI_10_FONT_ID, 440,
UI_10_FONT_ID, marginTop + 425,
(std::to_string(updater.getProcessedSize()) + " / " + std::to_string(updater.getTotalSize())).c_str());
renderer.displayBuffer();
return;
}
if (state == NO_UPDATE) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "No update available", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 285, "No update available", true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
if (state == FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Update failed", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 285, "Update failed", true, EpdFontFamily::BOLD);
renderer.displayBuffer();
return;
}
if (state == FINISHED) {
renderer.drawCenteredText(UI_10_FONT_ID, 300, "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, marginTop + 285, "Update complete", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 335, "Press and hold power button to turn back on");
renderer.displayBuffer();
state = SHUTTING_DOWN;
return;

View File

@ -11,9 +11,11 @@
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
namespace {
constexpr int displaySettingsCount = 6;
constexpr int displaySettingsCount = 7;
const SettingInfo displaySettings[displaySettingsCount] = {
// Should match with SLEEP_SCREEN_MODE
SettingInfo::Enum("Orientation", &CrossPointSettings::orientation,
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
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 Filter", &CrossPointSettings::sleepScreenCoverFilter,
@ -24,7 +26,7 @@ const SettingInfo displaySettings[displaySettingsCount] = {
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"})};
constexpr int readerSettingsCount = 9;
constexpr int readerSettingsCount = 8;
const SettingInfo readerSettings[readerSettingsCount] = {
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}),
@ -33,8 +35,6 @@ const SettingInfo readerSettings[readerSettingsCount] = {
SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
{"Justify", "Left", "Center", "Right"}),
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
SettingInfo::Toggle("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
@ -157,6 +157,7 @@ void SettingsActivity::enterCategory(int categoryIndex) {
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
settingsCount, [this] {
exitActivity();
updateMargins(); // we may have changed orientation
updateRequired = true;
}));
xSemaphoreGive(renderingMutex);
@ -178,25 +179,24 @@ void SettingsActivity::render() const {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Settings", true, EpdFontFamily::BOLD);
// Draw selection
renderer.fillRect(0, 60 + selectedCategoryIndex * 30 - 2, pageWidth - 1, 30);
renderer.fillRect(0, contentStartY + selectedCategoryIndex * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
// Draw all categories
for (int i = 0; i < categoryCount; i++) {
const int categoryY = 60 + i * 30; // 30 pixels between categories
const int categoryY = contentStartY + i * LINE_HEIGHT;
// Draw category name
renderer.drawText(UI_10_FONT_ID, 20, categoryY, categoryNames[i], i != selectedCategoryIndex);
renderer.drawText(UI_10_FONT_ID, marginLeft, categoryY, categoryNames[i], i != selectedCategoryIndex);
}
// Draw version text above button hints
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION);
// Draw version text in corner of header
renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
marginTop + 5, CROSSPOINT_VERSION);
// Draw help text
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");