Compare commits

...

4 Commits

Author SHA1 Message Date
Maeve Andrews
342c62991b
Merge ba700eef84 into 78d6e5931c 2026-02-03 18:11:08 -05:00
Jake Kenneally
78d6e5931c
fix: Correct debugging_monitor.py script instructions (#676)
Some checks failed
CI / build (push) Has been cancelled
## Summary

**What is the goal of this PR?**
- Minor correction to the `debugging_monitor.py` script instructions

**What changes are included?**
- `pyserial` should be installed, NOT `serial`, which is a [different
lib](https://pypi.org/project/serial/)
- Added macOS serial port

## Additional Context

- Just a minor docs update. I can confirm the debugging script is
working great on macOS

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-02-04 00:33:20 +03:00
Luke Stein
dac11c3fdd
fix: Correct instruction text to match actual button text (#672)
## Summary

* Instruction text says "Press OK to scan again" but button label is
actually "Connect" (not OK)
* Corrects instruction text

---

### AI Usage

Did you use AI tools to help write this code? **No**
2026-02-04 00:32:52 +03:00
Maeve Andrews
ba700eef84 feat: Apply orientation to UI too, move setting to Display 2026-02-01 12:29:32 -06:00
29 changed files with 301 additions and 283 deletions

View File

@ -102,13 +102,18 @@ After flashing the new features, its recommended to capture detailed logs fro
First, make sure all required Python packages are installed: First, make sure all required Python packages are installed:
```python ```python
python3 -m pip install serial colorama matplotlib python3 -m pip install pyserial colorama matplotlib
``` ```
after that run the script: after that run the script:
```sh ```sh
# For Linux
# This was tested on Debian and should work on most Linux systems.
python3 scripts/debugging_monitor.py python3 scripts/debugging_monitor.py
# For macOS
python3 scripts/debugging_monitor.py /dev/cu.usbmodem2101
``` ```
This was tested on Debian and should work on most Linux systems. Minor adjustments may be required for Windows or macOS. Minor adjustments may be required for Windows.
## Internals ## Internals

View File

@ -494,11 +494,11 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
setOrientation(Orientation::Portrait); setOrientation(Orientation::Portrait);
const int pageHeight = getScreenHeight(); const int pageHeight = getScreenHeight();
constexpr int buttonWidth = 106; constexpr int buttonWidth = 94;
constexpr int buttonHeight = 40; constexpr int buttonHeight = 40;
constexpr int buttonY = 40; // Distance from bottom constexpr int buttonY = 40; // 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 buttonPositions[] = {50, 143, 247, 340};
const char* labels[] = {btn1, btn2, btn3, btn4}; const char* labels[] = {btn1, btn2, btn3, btn4};
for (int i = 0; i < 4; i++) { 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); 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(); 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)
@ -565,6 +568,8 @@ void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, cons
drawTextRotated90CW(fontId, textX, textY, labels[i]); drawTextRotated90CW(fontId, textX, textY, labels[i]);
} }
} }
setOrientation(orig_orientation);
} }
int GfxRenderer::getTextHeight(const int fontId) const { int GfxRenderer::getTextHeight(const int fontId) const {

View File

@ -85,7 +85,7 @@ class GfxRenderer {
// UI Components // UI Components
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4); 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: private:
// Helper for drawing rotated text (90 degrees clockwise, for side buttons) // 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 MappedInputManager;
class GfxRenderer; class GfxRenderer;
constexpr int LINE_HEIGHT = 30;
class Activity { class Activity {
protected: protected:
std::string name; std::string name;
GfxRenderer& renderer; GfxRenderer& renderer;
MappedInputManager& mappedInput; MappedInputManager& mappedInput;
int marginTop = 15;
int marginBottom = 50;
int marginLeft = 20;
int marginRight = 20;
int contentStartY = 60;
public: public:
explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput) explicit Activity(std::string name, GfxRenderer& renderer, MappedInputManager& mappedInput)
: name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {} : name(std::move(name)), renderer(renderer), mappedInput(mappedInput) {}
virtual ~Activity() = default; virtual ~Activity() = default;
virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); } virtual void onEnter();
virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); } virtual void onExit();
virtual void loop() {} virtual void loop() {}
virtual bool skipLoopDelay() { return false; } virtual bool skipLoopDelay() { return false; }
virtual bool preventAutoSleep() { return false; } virtual bool preventAutoSleep() { return false; }
void updateMargins();
void updateRendererOrientation();
int getPageItems() const;
}; };

View File

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

View File

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

View File

@ -228,7 +228,7 @@ void OpdsBookBrowserActivity::render() const {
} }
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(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++) { 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,7 +246,7 @@ 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);
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)); i != static_cast<size_t>(selectorIndex));
} }

View File

@ -220,14 +220,29 @@ 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; // --- Bottom menu tiles ---
constexpr int bottomMargin = 60; // 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) --- // --- 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 bookWidth = pageWidth / 2;
const int bookHeight = pageHeight / 2; const int maxBookHeight = pageHeight - marginTop - marginBottom - totalMenuHeight - batteryHeight;
const int bookX = (pageWidth - bookWidth) / 2; const int bookHeight = std::min(pageHeight / 2, maxBookHeight);
constexpr int bookY = 30; const int bookX = marginLeft + (pageWidth - marginLeft - marginRight - bookWidth) / 2;
const int bookY = marginTop + batteryHeight;
const bool bookSelected = hasContinueReading && selectorIndex == 0; const bool bookSelected = hasContinueReading && selectorIndex == 0;
// Bookmark dimensions (used in multiple places) // Bookmark dimensions (used in multiple places)
@ -443,7 +458,7 @@ void HomeActivity::render() {
const int boxWidth = maxTextWidth + boxPadding * 2; const int boxWidth = maxTextWidth + boxPadding * 2;
const int boxHeight = totalTextHeight + 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; const int boxY = titleYStart - boxPadding;
// Draw box (inverted when selected: black box instead of white) // Draw box (inverted when selected: black box instead of white)
@ -453,7 +468,8 @@ void HomeActivity::render() {
} }
for (const auto& line : lines) { 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); titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
} }
@ -474,26 +490,27 @@ void HomeActivity::render() {
} }
trimmedAuthor.append("..."); 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 // "Continue Reading" label at the bottom
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2; const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
const char* continueText = "Continue Reading";
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
if (coverRendered) { if (coverRendered) {
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white) // 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);
constexpr int continuePadding = 6; constexpr int continuePadding = 6;
const int continueBoxWidth = continueTextWidth + continuePadding * 2; const int continueBoxWidth = continueTextWidth + continuePadding * 2;
const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding; 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; const int continueBoxY = continueY - continuePadding / 2;
renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected); renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected);
renderer.drawRect(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 { } else {
// No book to continue reading // No book to continue reading
const int y = 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"); renderer.drawCenteredText(UI_10_FONT_ID, y + renderer.getLineHeight(UI_12_FONT_ID), "Start reading below");
} }
// --- Bottom menu tiles --- int menuStartY = bookY + bookHeight + menuSpacing;
// 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;
}
for (size_t i = 0; i < menuItems.size(); ++i) { for (size_t i = 0; i < menuItems.size(); ++i) {
const int overallIndex = static_cast<int>(i) + (hasContinueReading ? 1 : 0); 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 int tileY = menuStartY + static_cast<int>(i) * (menuTileHeight + menuSpacing);
const bool selected = selectorIndex == overallIndex; const bool selected = selectorIndex == overallIndex;
@ -553,7 +551,7 @@ void HomeActivity::render() {
// get percentage so we can align text properly // get percentage so we can align text properly
const uint16_t percentage = battery.readPercentage(); const uint16_t percentage = battery.readPercentage();
const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : ""; 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); ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
renderer.displayBuffer(); renderer.displayBuffer();

View File

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

View File

@ -34,7 +34,6 @@ class MyLibraryActivity final : public Activity {
const std::function<void(const std::string& path, Tab fromTab)> onSelectBook; const std::function<void(const std::string& path, Tab fromTab)> onSelectBook;
// Number of items that fit on a page // Number of items that fit on a page
int getPageItems() const;
int getCurrentItemCount() const; int getCurrentItemCount() const;
int getTotalPages() const; int getTotalPages() const;
int getCurrentPage() 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 { void CrossPointWebServerActivity::renderServerRunning() const {
// Use consistent line spacing // Use consistent line spacing
constexpr int LINE_SPACING = 28; // Space between lines 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) { if (isApMode) {
// AP mode display - center the content block // 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); 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."); "or scan QR code with your phone to connect to Wifi.");
// Show QR code for URL // Show QR code for URL
const std::string wifiConfig = std::string("WIFI:S:") + connectedSSID + ";;"; 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; startY += 6 * 29 + 3 * LINE_SPACING;
// Show primary URL (hostname) // Show primary URL (hostname)
@ -449,10 +450,10 @@ void CrossPointWebServerActivity::renderServerRunning() const {
// Show QR code for URL // Show QR code for URL
renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 6, "or scan QR code with your phone:"); 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 { } else {
// STA mode display (original behavior) // STA mode display (original behavior)
const int startY = 65; const int startY = marginTop + 50;
std::string ssidInfo = "Network: " + connectedSSID; std::string ssidInfo = "Network: " + connectedSSID;
if (ssidInfo.length() > 28) { 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"); renderer.drawCenteredText(SMALL_FONT_ID, startY + LINE_SPACING * 4, "Open this URL in your browser");
// Show QR code for URL // 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:"); 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(); const auto pageHeight = renderer.getScreenHeight();
// Draw header // 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 // 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 // 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)
@ -120,13 +120,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(marginLeft, itemY - 2, pageWidth - marginLeft - marginRight, 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, marginLeft + 10, itemY, MENU_ITEMS[i], /*black=*/!isSelected);
renderer.drawText(SMALL_FONT_ID, 30, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected); renderer.drawText(SMALL_FONT_ID, marginLeft + 10, itemY + 22, MENU_DESCRIPTIONS[i], /*black=*/!isSelected);
} }
// Draw help text at bottom // Draw help text at bottom

View File

@ -513,18 +513,18 @@ void WifiSelectionActivity::renderNetworkList() const {
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
// Draw header // 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()) { if (networks.empty()) {
// No networks found or scan failed // No networks found or scan failed
const auto height = renderer.getLineHeight(UI_10_FONT_ID); const auto height = renderer.getLineHeight(UI_10_FONT_ID);
const auto top = (pageHeight - height) / 2; const auto top = (pageHeight - height) / 2;
renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found"); renderer.drawCenteredText(UI_10_FONT_ID, top, "No networks found");
renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press OK to scan again"); renderer.drawCenteredText(SMALL_FONT_ID, top + height + 10, "Press Connect 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 = contentStartY;
constexpr int lineHeight = 25; constexpr int lineHeight = 25; // tighter spacing than normal menus
const int maxVisibleNetworks = (pageHeight - startY - 40) / lineHeight; const int maxVisibleNetworks = (pageHeight - startY - 40) / lineHeight;
// Calculate scroll offset to keep selected item visible // Calculate scroll offset to keep selected item visible
@ -541,7 +541,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, marginLeft - 15, networkY, ">");
} }
// Draw network name (truncate if too long) // Draw network name (truncate if too long)
@ -549,42 +549,47 @@ void WifiSelectionActivity::renderNetworkList() const {
if (displayName.length() > 33) { if (displayName.length() > 33) {
displayName.replace(30, displayName.length() - 30, "..."); 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 // 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 - marginRight - 90, 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 - marginRight - 50, 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 - marginRight - 30, 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 - marginRight - 15, 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 - marginRight - 15, 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, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, countStr) - 95,
pageHeight - 90, 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,
pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, cachedMacAddress.c_str()) - 95,
pageHeight - 105, cachedMacAddress.c_str());
// Draw help text // 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", "", ""); 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

@ -34,24 +34,6 @@ void EpubReaderActivity::onEnter() {
return; 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(); renderingMutex = xSemaphoreCreateMutex();
epub->setupCacheDir(); epub->setupCacheDir();
@ -101,9 +83,6 @@ void EpubReaderActivity::onEnter() {
void EpubReaderActivity::onExit() { void EpubReaderActivity::onExit() {
ActivityWithSubactivity::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 // Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) { if (displayTaskHandle) {

View File

@ -32,24 +32,6 @@ int EpubReaderChapterSelectionActivity::tocIndexFromItemIndex(int itemIndex) con
return itemIndex - offset; 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) { void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
auto* self = static_cast<EpubReaderChapterSelectionActivity*>(param); auto* self = static_cast<EpubReaderChapterSelectionActivity*>(param);
self->displayTaskLoop(); self->displayTaskLoop();
@ -184,21 +166,21 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Chapter", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_12_FONT_ID, 15, "Go to Chapter", 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(0, contentStartY + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
for (int i = 0; i < pageItems; i++) { for (int i = 0; i < pageItems; i++) {
int itemIndex = pageStartIndex + i; int itemIndex = pageStartIndex + i;
if (itemIndex >= totalItems) break; if (itemIndex >= totalItems) break;
const int displayY = 60 + i * 30; const int displayY = contentStartY + (itemIndex % pageItems) * LINE_HEIGHT;
const bool isSelected = (itemIndex == selectorIndex); const bool isSelected = (itemIndex == selectorIndex);
if (isSyncItem(itemIndex)) { 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 { } else {
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 = marginLeft + (item.level - 1) * 15;
const std::string chapterName = const std::string chapterName =
renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize); renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize);
@ -206,11 +188,8 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
} }
} }
// Skip button hints in landscape CW mode (they overlap content) const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
if (renderer.getOrientation() != GfxRenderer::LandscapeClockwise) { renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
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(); 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)> onSelectSpineIndex;
const std::function<void(int newSpineIndex, int newPage)> onSyncPosition; 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) // Total items including sync options (top and bottom)
int getTotalItems() const; int getTotalItems() const;

View File

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

View File

@ -35,24 +35,6 @@ void TxtReaderActivity::onEnter() {
return; 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(); renderingMutex = xSemaphoreCreateMutex();
txt->setupCacheDir(); txt->setupCacheDir();
@ -76,9 +58,6 @@ void TxtReaderActivity::onEnter() {
void TxtReaderActivity::onExit() { void TxtReaderActivity::onExit() {
ActivityWithSubactivity::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 // Wait until not rendering to delete task
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) { if (displayTaskHandle) {

View File

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

View File

@ -9,21 +9,6 @@ namespace {
constexpr int SKIP_PAGE_MS = 700; constexpr int SKIP_PAGE_MS = 700;
} // namespace } // 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 { int XtcReaderChapterSelectionActivity::findChapterIndexForPage(uint32_t page) const {
if (!xtc) { if (!xtc) {
return 0; return 0;
@ -132,28 +117,26 @@ void XtcReaderChapterSelectionActivity::renderScreen() {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const int pageItems = getPageItems(); 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(); const auto& chapters = xtc->getChapters();
if (chapters.empty()) { if (chapters.empty()) {
renderer.drawCenteredText(UI_10_FONT_ID, 120, "No chapters"); renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 105, "No chapters");
renderer.displayBuffer(); renderer.displayBuffer();
return; return;
} }
const auto pageStartIndex = selectorIndex / pageItems * pageItems; const auto pageStartIndex = selectorIndex / pageItems * pageItems;
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); renderer.fillRect(0, contentStartY + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - 1, LINE_HEIGHT);
for (int i = pageStartIndex; i < static_cast<int>(chapters.size()) && i < pageStartIndex + pageItems; i++) { for (int i = pageStartIndex; i < static_cast<int>(chapters.size()) && i < pageStartIndex + pageItems; i++) {
const auto& chapter = chapters[i]; const auto& chapter = chapters[i];
const char* title = chapter.name.empty() ? "Unnamed" : chapter.name.c_str(); 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) const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
if (renderer.getOrientation() != GfxRenderer::LandscapeClockwise) { renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
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(); renderer.displayBuffer();
} }

View File

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

View File

@ -80,7 +80,7 @@ void CalibreSettingsActivity::handleSelection() {
// OPDS Server URL // OPDS Server URL
exitActivity(); exitActivity();
enterNewActivity(new KeyboardEntryActivity( enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "OPDS Server URL", SETTINGS.opdsServerUrl, 10, renderer, mappedInput, "OPDS Server URL", SETTINGS.opdsServerUrl, 50,
127, // maxLength 127, // maxLength
false, // not password false, // not password
[this](const std::string& url) { [this](const std::string& url) {
@ -98,7 +98,7 @@ void CalibreSettingsActivity::handleSelection() {
// Username // Username
exitActivity(); exitActivity();
enterNewActivity(new KeyboardEntryActivity( enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "Username", SETTINGS.opdsUsername, 10, renderer, mappedInput, "Username", SETTINGS.opdsUsername, 50,
63, // maxLength 63, // maxLength
false, // not password false, // not password
[this](const std::string& username) { [this](const std::string& username) {
@ -116,7 +116,7 @@ void CalibreSettingsActivity::handleSelection() {
// Password // Password
exitActivity(); exitActivity();
enterNewActivity(new KeyboardEntryActivity( enterNewActivity(new KeyboardEntryActivity(
renderer, mappedInput, "Password", SETTINGS.opdsPassword, 10, renderer, mappedInput, "Password", SETTINGS.opdsPassword, 50,
63, // maxLength 63, // maxLength
false, // not password mode false, // not password mode
[this](const std::string& password) { [this](const std::string& password) {
@ -153,20 +153,20 @@ void CalibreSettingsActivity::render() {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
// Draw header // 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 // 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 // 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 // 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 = contentStartY + 10 + i * LINE_HEIGHT;
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, marginLeft, settingY, menuNames[i], !isSelected);
// Draw status for each setting // Draw status for each setting
const char* status = "[Not Set]"; const char* status = "[Not Set]";
@ -178,7 +178,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 - marginRight - width, settingY, status, !isSelected);
} }
// Draw button hints // Draw button hints

View File

@ -87,6 +87,9 @@ 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());
if (strcmp(setting.name, "Orientation") == 0) {
updateRendererOrientation();
}
} 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) {
@ -151,20 +154,22 @@ void CategorySettingsActivity::render() const {
renderer.clearScreen(); renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth(); 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 // 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 // Draw all settings
for (int i = 0; i < settingsCount; i++) { const auto pageStartIndex = selectedSettingIndex / pageItems * pageItems;
const int settingY = 60 + i * 30; // 30 pixels between settings for (int i = pageStartIndex; i < settingsCount && i < pageStartIndex + pageItems; i++) {
const int settingY = contentStartY + (i % pageItems) * LINE_HEIGHT;
const bool isSelected = (i == selectedSettingIndex); const bool isSelected = (i == 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, marginLeft, settingY, settingsList[i].name, !isSelected);
// Draw value based on setting type // Draw value based on setting type
std::string valueText; std::string valueText;
@ -179,12 +184,12 @@ 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 - marginRight - width, settingY, valueText.c_str(), !isSelected);
} }
} }
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION); marginTop + 5, 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

@ -56,7 +56,7 @@ void ClearCacheActivity::render() {
const auto pageHeight = renderer.getScreenHeight(); const auto pageHeight = renderer.getScreenHeight();
renderer.clearScreen(); 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) { 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, pageHeight / 2 - 60, "This will clear all cached book data.", true);

View File

@ -123,17 +123,17 @@ void KOReaderAuthActivity::render() {
} }
renderer.clearScreen(); 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) { 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(); renderer.displayBuffer();
return; return;
} }
if (state == SUCCESS) { if (state == SUCCESS) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Success!", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "Success!", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, "KOReader sync is ready to use"); renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, "KOReader sync is ready to use");
const auto labels = mappedInput.mapLabels("Done", "", "", ""); const auto labels = mappedInput.mapLabels("Done", "", "", "");
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);
@ -142,8 +142,8 @@ void KOReaderAuthActivity::render() {
} }
if (state == FAILED) { if (state == FAILED) {
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Authentication Failed", true, EpdFontFamily::BOLD); renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 265, "Authentication Failed", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str()); renderer.drawCenteredText(UI_10_FONT_ID, marginTop + 305, errorMessage.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);

View File

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

View File

@ -128,18 +128,19 @@ void OtaUpdateActivity::render() {
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
renderer.clearScreen(); 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) { 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(); 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, marginTop + 185, "New update available!", true, EpdFontFamily::BOLD);
renderer.drawText(UI_10_FONT_ID, 20, 250, "Current Version: " CROSSPOINT_VERSION); renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 235, "Current Version: " CROSSPOINT_VERSION);
renderer.drawText(UI_10_FONT_ID, 20, 270, ("New Version: " + updater.getLatestVersion()).c_str()); renderer.drawText(UI_10_FONT_ID, marginLeft, marginTop + 255,
("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 +149,34 @@ 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, marginTop + 295, "Updating...", true, EpdFontFamily::BOLD);
renderer.drawRect(20, 350, pageWidth - 40, 50); renderer.drawRect(marginLeft, marginTop + 335, pageWidth - 40, 50);
renderer.fillRect(24, 354, static_cast<int>(updaterProgress * static_cast<float>(pageWidth - 44)), 42); renderer.fillRect(marginLeft + 4, marginTop + 339,
renderer.drawCenteredText(UI_10_FONT_ID, 420, 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()); (std::to_string(static_cast<int>(updaterProgress * 100)) + "%").c_str());
renderer.drawCenteredText( 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()); (std::to_string(updater.getProcessedSize()) + " / " + std::to_string(updater.getTotalSize())).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, marginTop + 285, "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, marginTop + 285, "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, marginTop + 285, "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 + 335, "Press and hold power button to turn back on");
renderer.displayBuffer(); renderer.displayBuffer();
state = SHUTTING_DOWN; state = SHUTTING_DOWN;
return; return;

View File

@ -11,9 +11,11 @@
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"}; const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
namespace { namespace {
constexpr int displaySettingsCount = 6; constexpr int displaySettingsCount = 7;
const SettingInfo displaySettings[displaySettingsCount] = { const SettingInfo displaySettings[displaySettingsCount] = {
// Should match with SLEEP_SCREEN_MODE // 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", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}), SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter, SettingInfo::Enum("Sleep Screen Cover Filter", &CrossPointSettings::sleepScreenCoverFilter,
@ -24,7 +26,7 @@ const SettingInfo displaySettings[displaySettingsCount] = {
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"})};
constexpr int readerSettingsCount = 9; constexpr int readerSettingsCount = 8;
const SettingInfo readerSettings[readerSettingsCount] = { const SettingInfo readerSettings[readerSettingsCount] = {
SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}), SettingInfo::Enum("Font Family", &CrossPointSettings::fontFamily, {"Bookerly", "Noto Sans", "Open Dyslexic"}),
SettingInfo::Enum("Font Size", &CrossPointSettings::fontSize, {"Small", "Medium", "Large", "X Large"}), 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, SettingInfo::Enum("Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
{"Justify", "Left", "Center", "Right"}), {"Justify", "Left", "Center", "Right"}),
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled), 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("Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing),
SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)}; SettingInfo::Toggle("Text Anti-Aliasing", &CrossPointSettings::textAntiAliasing)};
@ -157,6 +157,7 @@ void SettingsActivity::enterCategory(int categoryIndex) {
enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList, enterNewActivity(new CategorySettingsActivity(renderer, mappedInput, categoryNames[categoryIndex], settingsList,
settingsCount, [this] { settingsCount, [this] {
exitActivity(); exitActivity();
updateMargins(); // we may have changed orientation
updateRequired = true; updateRequired = true;
})); }));
xSemaphoreGive(renderingMutex); xSemaphoreGive(renderingMutex);
@ -178,25 +179,24 @@ void SettingsActivity::render() const {
renderer.clearScreen(); renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth(); const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
// Draw header // 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 // 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 // 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 = contentStartY + i * LINE_HEIGHT;
// Draw category name // 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 // Draw version text in corner of header
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION), renderer.drawText(SMALL_FONT_ID, pageWidth - marginRight - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
pageHeight - 60, CROSSPOINT_VERSION); marginTop + 5, CROSSPOINT_VERSION);
// Draw help text // Draw help text
const auto labels = mappedInput.mapLabels("« Back", "Select", "", ""); const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");