Compare commits

...

3 Commits

Author SHA1 Message Date
Maeve Andrews
01182ca57a
Merge 4de27a164e into b8ebcf5867 2026-01-27 21:25:21 +11:00
Maeve Andrews
4de27a164e feat: Apply orientation to UI too, move setting to Display 2026-01-26 15:20:05 -06:00
Maeve Andrews
92eddc6868 fix: rotate origin in drawImage 2026-01-26 15:19:26 -06:00
28 changed files with 300 additions and 265 deletions

View File

@ -145,10 +145,25 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int
}
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
// TODO: Rotate bits
int rotatedX = 0;
int rotatedY = 0;
rotateCoordinates(x, y, &rotatedX, &rotatedY);
// Rotate origin corner
switch (orientation) {
case Portrait:
rotatedY = rotatedY - height;
break;
case PortraitInverted:
rotatedX = rotatedX - width;
break;
case LandscapeClockwise:
rotatedY = rotatedY - height;
rotatedX = rotatedX - width;
break;
case LandscapeCounterClockwise:
break;
}
// TODO: Rotate bits
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
}
@ -474,11 +489,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++) {
@ -496,7 +511,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)
@ -545,6 +563,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,12 +7,13 @@
void BootActivity::onEnter() {
Activity::onEnter();
updateRendererOrientation();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING");
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION);

View File

@ -124,7 +124,7 @@ void SleepActivity::renderDefaultSleepScreen() const {
const auto pageHeight = renderer.getScreenHeight();
renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
@ -138,6 +138,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

@ -229,7 +229,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];
@ -247,7 +247,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

@ -216,14 +216,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 Calibre Library after My Library
menuItems.insert(menuItems.begin() + 1, "Calibre Library");
}
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)
@ -439,7 +454,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)
@ -449,7 +464,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);
}
@ -470,58 +486,41 @@ 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;
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)
const char* continueText = "Continue Reading";
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
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 =
bookY + (bookHeight - renderer.getLineHeight(UI_12_FONT_ID) - renderer.getLineHeight(UI_10_FONT_ID)) / 2;
// FIXME
renderer.drawCenteredText(UI_12_FONT_ID, y, "No open book");
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 Calibre Library after My Library
menuItems.insert(menuItems.begin() + 1, "Calibre Library");
}
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;
@ -549,7 +548,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,11 +13,7 @@
namespace {
// Layout constants
constexpr int TAB_BAR_Y = 15;
constexpr int CONTENT_START_Y = 60;
constexpr int LINE_HEIGHT = 30;
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;
@ -34,17 +30,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>(bookTitles.size());
@ -304,7 +289,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) {
@ -315,8 +300,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"
@ -335,20 +320,21 @@ void MyLibraryActivity::renderRecentTab() const {
const int bookCount = static_cast<int>(bookTitles.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) * 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 < bookCount && i < pageStartIndex + pageItems; i++) {
auto item = renderer.truncatedText(UI_10_FONT_ID, bookTitles[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, bookTitles[i].c_str(),
pageWidth - marginLeft - marginRight - scrollIndicatorWidth);
renderer.drawText(UI_10_FONT_ID, marginLeft, contentStartY + (i % pageItems) * LINE_HEIGHT, item.c_str(),
i != selectorIndex);
}
}
@ -359,20 +345,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

@ -399,12 +399,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);
@ -417,7 +418,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)
@ -431,10 +432,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) {
@ -456,7 +457,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

@ -97,10 +97,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)
@ -112,13 +112,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() > 16) {
displayName.replace(13, displayName.length() - 13, "...");
}
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

@ -32,24 +32,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();
@ -94,9 +76,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();
@ -183,27 +165,27 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
const std::string title =
renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, 15, title.c_str(), true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, title.c_str(), 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 itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) {
const int displayY = 60 + (itemIndex % pageItems) * 30;
const int displayY = contentStartY + (itemIndex % pageItems) * LINE_HEIGHT;
const bool isSelected = (itemIndex == selectorIndex);
if (isSyncItem(itemIndex)) {
// Draw sync option (at top or bottom)
renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected);
renderer.drawText(UI_10_FONT_ID, marginLeft, displayY, ">> Sync Progress", !isSelected);
} else {
// Draw TOC item (account for top sync offset)
const int tocIndex = tocIndexFromItemIndex(itemIndex);
auto item = epub->getTocItem(tocIndex);
const int indentSize = 20 + (item.level - 1) * 15;
const int indentSize = marginLeft + (item.level - 1) * 15;
const std::string chapterName =
renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize);
renderer.drawText(UI_10_FONT_ID, indentSize, 60 + (tocIndex % pageItems) * 30, chapterName.c_str(),
tocIndex != selectorIndex);
renderer.drawText(UI_10_FONT_ID, indentSize, contentStartY + (tocIndex % pageItems) * LINE_HEIGHT,
chapterName.c_str(), tocIndex != selectorIndex);
}
}

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

@ -33,24 +33,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();
@ -73,9 +55,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,21 +117,22 @@ 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);
}
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");

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

@ -141,23 +141,23 @@ void CalibreSettingsActivity::render() {
const auto pageWidth = renderer.getScreenWidth();
// Draw header
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Calibre", true, EpdFontFamily::BOLD);
renderer.drawCenteredText(UI_12_FONT_ID, marginTop, "Calibre", 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 URL setting
if (i == 0) {
const char* status = (strlen(SETTINGS.opdsServerUrl) > 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);
}
}

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

@ -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

@ -127,18 +127,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);
@ -147,33 +148,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.processedSize) + " / " + std::to_string(updater.totalSize)).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,17 +11,19 @@
const char* SettingsActivity::categoryNames[categoryCount] = {"Display", "Reader", "Controls", "System"};
namespace {
constexpr int displaySettingsCount = 5;
constexpr int displaySettingsCount = 6;
const SettingInfo displaySettings[displaySettingsCount] = {
// Should match with SLEEP_SCREEN_MODE
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
SettingInfo::Enum("Sleep Screen Cover Mode", &CrossPointSettings::sleepScreenCoverMode, {"Fit", "Crop"}),
SettingInfo::Enum("Orientation", &CrossPointSettings::orientation,
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
SettingInfo::Enum("Status Bar", &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}),
SettingInfo::Enum("Hide Battery %", &CrossPointSettings::hideBatteryPercentage, {"Never", "In Reader", "Always"}),
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
{"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"}),
@ -30,8 +32,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)};
@ -154,6 +154,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);
@ -175,25 +176,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", "", "");