yep it works
This commit is contained in:
parent
5c3828efe8
commit
1e20d30875
@ -6,6 +6,7 @@
|
|||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
#include <Xtc.h>
|
#include <Xtc.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -218,12 +219,32 @@ void HomeActivity::render() {
|
|||||||
|
|
||||||
constexpr int margin = 20;
|
constexpr int margin = 20;
|
||||||
constexpr int bottomMargin = 60;
|
constexpr int bottomMargin = 60;
|
||||||
|
|
||||||
// --- Top "book" card for the current title (selectorIndex == 0) ---
|
|
||||||
const int bookWidth = pageWidth / 2;
|
|
||||||
const int bookHeight = pageHeight / 2;
|
|
||||||
const int bookX = (pageWidth - bookWidth) / 2;
|
|
||||||
constexpr int bookY = 30;
|
constexpr int bookY = 30;
|
||||||
|
constexpr int elementSpacing = 15;
|
||||||
|
|
||||||
|
// --- Calculate layout from bottom up ---
|
||||||
|
|
||||||
|
// Build menu items dynamically (need count for layout calculation)
|
||||||
|
std::vector<const char*> menuItems = {"My Library", "File Transfer", "Settings"};
|
||||||
|
if (hasOpdsUrl) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Anchor menu to bottom of screen
|
||||||
|
const int menuStartY = pageHeight - bottomMargin - totalMenuHeight - margin;
|
||||||
|
|
||||||
|
// Calculate book card dimensions - larger, filling available space
|
||||||
|
const int bookWidth = pageWidth - 2 * margin;
|
||||||
|
// Card extends to just above menu
|
||||||
|
const int bookCardBottomY = menuStartY - elementSpacing;
|
||||||
|
const int bookHeight = bookCardBottomY - bookY;
|
||||||
|
const int bookX = margin;
|
||||||
const bool bookSelected = hasContinueReading && selectorIndex == 0;
|
const bool bookSelected = hasContinueReading && selectorIndex == 0;
|
||||||
|
|
||||||
// Bookmark dimensions (used in multiple places)
|
// Bookmark dimensions (used in multiple places)
|
||||||
@ -242,27 +263,26 @@ void HomeActivity::render() {
|
|||||||
if (SdMan.openFileForRead("HOME", coverBmpPath, file)) {
|
if (SdMan.openFileForRead("HOME", coverBmpPath, file)) {
|
||||||
Bitmap bitmap(file);
|
Bitmap bitmap(file);
|
||||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
// Calculate position to center image within the book card
|
// Add padding around the cover image so it doesn't touch the frame
|
||||||
int coverX, coverY;
|
constexpr int coverPadding = 10;
|
||||||
|
const int availableWidth = bookWidth - 2 * coverPadding;
|
||||||
|
const int availableHeight = bookHeight - 2 * coverPadding;
|
||||||
|
|
||||||
if (bitmap.getWidth() > bookWidth || bitmap.getHeight() > bookHeight) {
|
// Calculate scale to fit image within padded area while maintaining aspect ratio
|
||||||
const float imgRatio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
const float scaleX = static_cast<float>(availableWidth) / static_cast<float>(bitmap.getWidth());
|
||||||
const float boxRatio = static_cast<float>(bookWidth) / static_cast<float>(bookHeight);
|
const float scaleY = static_cast<float>(availableHeight) / static_cast<float>(bitmap.getHeight());
|
||||||
|
const float scale = std::min(scaleX, scaleY);
|
||||||
|
|
||||||
if (imgRatio > boxRatio) {
|
// Calculate actual scaled dimensions
|
||||||
coverX = bookX;
|
const int scaledWidth = static_cast<int>(bitmap.getWidth() * scale);
|
||||||
coverY = bookY + (bookHeight - static_cast<int>(bookWidth / imgRatio)) / 2;
|
const int scaledHeight = static_cast<int>(bitmap.getHeight() * scale);
|
||||||
} else {
|
|
||||||
coverX = bookX + (bookWidth - static_cast<int>(bookHeight * imgRatio)) / 2;
|
// Center the scaled image within the book card (accounting for padding)
|
||||||
coverY = bookY;
|
const int coverX = bookX + (bookWidth - scaledWidth) / 2;
|
||||||
}
|
const int coverY = bookY + (bookHeight - scaledHeight) / 2;
|
||||||
} else {
|
|
||||||
coverX = bookX + (bookWidth - bitmap.getWidth()) / 2;
|
|
||||||
coverY = bookY + (bookHeight - bitmap.getHeight()) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw the cover image centered within the book card
|
// Draw the cover image centered within the book card
|
||||||
renderer.drawBitmap(bitmap, coverX, coverY, bookWidth, bookHeight);
|
renderer.drawBitmap(bitmap, coverX, coverY, scaledWidth, scaledHeight);
|
||||||
|
|
||||||
// Draw border around the card
|
// Draw border around the card
|
||||||
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
||||||
@ -325,169 +345,177 @@ void HomeActivity::render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasContinueReading) {
|
if (hasContinueReading) {
|
||||||
// Invert text colors based on selection state:
|
|
||||||
// - With cover: selected = white text on black box, unselected = black text on white box
|
|
||||||
// - Without cover: selected = white text on black card, unselected = black text on white card
|
|
||||||
|
|
||||||
// Split into words (avoid stringstream to keep this light on the MCU)
|
|
||||||
std::vector<std::string> words;
|
|
||||||
words.reserve(8);
|
|
||||||
size_t pos = 0;
|
|
||||||
while (pos < lastBookTitle.size()) {
|
|
||||||
while (pos < lastBookTitle.size() && lastBookTitle[pos] == ' ') {
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
if (pos >= lastBookTitle.size()) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const size_t start = pos;
|
|
||||||
while (pos < lastBookTitle.size() && lastBookTitle[pos] != ' ') {
|
|
||||||
++pos;
|
|
||||||
}
|
|
||||||
words.emplace_back(lastBookTitle.substr(start, pos - start));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::string> lines;
|
|
||||||
std::string currentLine;
|
|
||||||
// Extra padding inside the card so text doesn't hug the border
|
|
||||||
const int maxLineWidth = bookWidth - 40;
|
|
||||||
const int spaceWidth = renderer.getSpaceWidth(UI_12_FONT_ID);
|
|
||||||
|
|
||||||
for (auto& i : words) {
|
|
||||||
// If we just hit the line limit (3), stop processing words
|
|
||||||
if (lines.size() >= 3) {
|
|
||||||
// Limit to 3 lines
|
|
||||||
// Still have words left, so add ellipsis to last line
|
|
||||||
lines.back().append("...");
|
|
||||||
|
|
||||||
while (!lines.back().empty() && renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) {
|
|
||||||
// Remove "..." first, then remove one UTF-8 char, then add "..." back
|
|
||||||
lines.back().resize(lines.back().size() - 3); // Remove "..."
|
|
||||||
StringUtils::utf8RemoveLastChar(lines.back());
|
|
||||||
lines.back().append("...");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
int wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str());
|
|
||||||
while (wordWidth > maxLineWidth && !i.empty()) {
|
|
||||||
// Word itself is too long, trim it (UTF-8 safe)
|
|
||||||
StringUtils::utf8RemoveLastChar(i);
|
|
||||||
// Check if we have room for ellipsis
|
|
||||||
std::string withEllipsis = i + "...";
|
|
||||||
wordWidth = renderer.getTextWidth(UI_12_FONT_ID, withEllipsis.c_str());
|
|
||||||
if (wordWidth <= maxLineWidth) {
|
|
||||||
i = withEllipsis;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int newLineWidth = renderer.getTextWidth(UI_12_FONT_ID, currentLine.c_str());
|
|
||||||
if (newLineWidth > 0) {
|
|
||||||
newLineWidth += spaceWidth;
|
|
||||||
}
|
|
||||||
newLineWidth += wordWidth;
|
|
||||||
|
|
||||||
if (newLineWidth > maxLineWidth && !currentLine.empty()) {
|
|
||||||
// New line too long, push old line
|
|
||||||
lines.push_back(currentLine);
|
|
||||||
currentLine = i;
|
|
||||||
} else {
|
|
||||||
currentLine.append(" ").append(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If lower than the line limit, push remaining words
|
|
||||||
if (!currentLine.empty() && lines.size() < 3) {
|
|
||||||
lines.push_back(currentLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Book title text
|
|
||||||
int totalTextHeight = renderer.getLineHeight(UI_12_FONT_ID) * static_cast<int>(lines.size());
|
|
||||||
if (!lastBookAuthor.empty()) {
|
|
||||||
totalTextHeight += renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertically center the title block within the card
|
|
||||||
int titleYStart = bookY + (bookHeight - totalTextHeight) / 2;
|
|
||||||
|
|
||||||
// If cover image was rendered, draw box behind title and author
|
|
||||||
if (coverRendered) {
|
if (coverRendered) {
|
||||||
|
// --- Cover image present: draw combined label at the bottom of the card ---
|
||||||
|
// Box contains: "Continue Reading" (larger) and "Title - Author" (smaller)
|
||||||
|
|
||||||
|
const char* continueText = "Continue Reading";
|
||||||
constexpr int boxPadding = 8;
|
constexpr int boxPadding = 8;
|
||||||
// Calculate the max text width for the box
|
constexpr int lineSpacing = 2;
|
||||||
int maxTextWidth = 0;
|
|
||||||
for (const auto& line : lines) {
|
// Build subtitle: "Title - Author" or just "Title"
|
||||||
const int lineWidth = renderer.getTextWidth(UI_12_FONT_ID, line.c_str());
|
std::string subtitle = lastBookTitle;
|
||||||
if (lineWidth > maxTextWidth) {
|
|
||||||
maxTextWidth = lineWidth;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!lastBookAuthor.empty()) {
|
if (!lastBookAuthor.empty()) {
|
||||||
std::string trimmedAuthor = lastBookAuthor;
|
subtitle += " - " + lastBookAuthor;
|
||||||
while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) {
|
|
||||||
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
|
||||||
}
|
|
||||||
if (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) <
|
|
||||||
renderer.getTextWidth(UI_10_FONT_ID, lastBookAuthor.c_str())) {
|
|
||||||
trimmedAuthor.append("...");
|
|
||||||
}
|
|
||||||
const int authorWidth = renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str());
|
|
||||||
if (authorWidth > maxTextWidth) {
|
|
||||||
maxTextWidth = authorWidth;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const int boxWidth = maxTextWidth + boxPadding * 2;
|
// Calculate box dimensions based on both lines
|
||||||
const int boxHeight = totalTextHeight + boxPadding * 2;
|
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
|
||||||
const int boxX = (pageWidth - boxWidth) / 2;
|
const int continueLineHeight = renderer.getLineHeight(UI_10_FONT_ID);
|
||||||
const int boxY = titleYStart - boxPadding;
|
const int subtitleLineHeight = renderer.getLineHeight(SMALL_FONT_ID);
|
||||||
|
|
||||||
// Draw box (inverted when selected: black box instead of white)
|
// Truncate subtitle to fit within card (with padding)
|
||||||
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, bookSelected);
|
const int maxSubtitleWidth = bookWidth - 2 * boxPadding - 20; // Extra margin for aesthetics
|
||||||
// Draw border around the box (inverted when selected: white border instead of black)
|
|
||||||
renderer.drawRect(boxX, boxY, boxWidth, boxHeight, !bookSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& line : lines) {
|
|
||||||
renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected);
|
|
||||||
titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lastBookAuthor.empty()) {
|
|
||||||
titleYStart += renderer.getLineHeight(UI_10_FONT_ID) / 2;
|
|
||||||
std::string trimmedAuthor = lastBookAuthor;
|
|
||||||
// Trim author if too long (UTF-8 safe)
|
|
||||||
bool wasTrimmed = false;
|
bool wasTrimmed = false;
|
||||||
while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) {
|
while (renderer.getTextWidth(SMALL_FONT_ID, subtitle.c_str()) > maxSubtitleWidth && !subtitle.empty()) {
|
||||||
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
StringUtils::utf8RemoveLastChar(subtitle);
|
||||||
wasTrimmed = true;
|
wasTrimmed = true;
|
||||||
}
|
}
|
||||||
if (wasTrimmed && !trimmedAuthor.empty()) {
|
if (wasTrimmed && !subtitle.empty()) {
|
||||||
// Make room for ellipsis
|
// Make room for ellipsis
|
||||||
while (renderer.getTextWidth(UI_10_FONT_ID, (trimmedAuthor + "...").c_str()) > maxLineWidth &&
|
while (renderer.getTextWidth(SMALL_FONT_ID, (subtitle + "...").c_str()) > maxSubtitleWidth &&
|
||||||
!trimmedAuthor.empty()) {
|
!subtitle.empty()) {
|
||||||
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
StringUtils::utf8RemoveLastChar(subtitle);
|
||||||
}
|
}
|
||||||
trimmedAuthor.append("...");
|
subtitle.append("...");
|
||||||
}
|
}
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected);
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Continue Reading" label at the bottom
|
const int subtitleTextWidth = renderer.getTextWidth(SMALL_FONT_ID, subtitle.c_str());
|
||||||
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
|
||||||
if (coverRendered) {
|
// Box width is the wider of the two lines plus padding
|
||||||
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
|
const int boxContentWidth = std::max(continueTextWidth, subtitleTextWidth);
|
||||||
const char* continueText = "Continue Reading";
|
const int boxWidth = boxContentWidth + boxPadding * 2;
|
||||||
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
|
const int boxHeight = continueLineHeight + lineSpacing + subtitleLineHeight + boxPadding * 2;
|
||||||
constexpr int continuePadding = 6;
|
|
||||||
const int continueBoxWidth = continueTextWidth + continuePadding * 2;
|
// Position box at the bottom of the card, centered
|
||||||
const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding;
|
const int boxX = (pageWidth - boxWidth) / 2;
|
||||||
const int continueBoxX = (pageWidth - continueBoxWidth) / 2;
|
const int boxY = bookY + bookHeight - boxHeight - boxPadding;
|
||||||
const int continueBoxY = continueY - continuePadding / 2;
|
|
||||||
renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected);
|
// Draw box background and border
|
||||||
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);
|
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, bookSelected);
|
||||||
|
renderer.drawRect(boxX, boxY, boxWidth, boxHeight, !bookSelected);
|
||||||
|
|
||||||
|
// Draw "Continue Reading" line
|
||||||
|
const int continueY = boxY + boxPadding;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
|
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
|
||||||
|
|
||||||
|
// Draw "Title - Author" line below
|
||||||
|
const int subtitleY = continueY + continueLineHeight + lineSpacing;
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, subtitleY, subtitle.c_str(), !bookSelected);
|
||||||
} else {
|
} else {
|
||||||
|
// --- No cover image: draw title/author inside the card (existing behavior) ---
|
||||||
|
// Invert text colors based on selection state
|
||||||
|
|
||||||
|
// Split into words (avoid stringstream to keep this light on the MCU)
|
||||||
|
std::vector<std::string> words;
|
||||||
|
words.reserve(8);
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < lastBookTitle.size()) {
|
||||||
|
while (pos < lastBookTitle.size() && lastBookTitle[pos] == ' ') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
if (pos >= lastBookTitle.size()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const size_t start = pos;
|
||||||
|
while (pos < lastBookTitle.size() && lastBookTitle[pos] != ' ') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
words.emplace_back(lastBookTitle.substr(start, pos - start));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> lines;
|
||||||
|
std::string currentLine;
|
||||||
|
// Extra padding inside the card so text doesn't hug the border
|
||||||
|
const int maxLineWidth = bookWidth - 40;
|
||||||
|
const int spaceWidth = renderer.getSpaceWidth(UI_12_FONT_ID);
|
||||||
|
|
||||||
|
for (auto& i : words) {
|
||||||
|
// If we just hit the line limit (3), stop processing words
|
||||||
|
if (lines.size() >= 3) {
|
||||||
|
// Limit to 3 lines
|
||||||
|
// Still have words left, so add ellipsis to last line
|
||||||
|
lines.back().append("...");
|
||||||
|
|
||||||
|
while (!lines.back().empty() &&
|
||||||
|
renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) {
|
||||||
|
// Remove "..." first, then remove one UTF-8 char, then add "..." back
|
||||||
|
lines.back().resize(lines.back().size() - 3); // Remove "..."
|
||||||
|
StringUtils::utf8RemoveLastChar(lines.back());
|
||||||
|
lines.back().append("...");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str());
|
||||||
|
while (wordWidth > maxLineWidth && !i.empty()) {
|
||||||
|
// Word itself is too long, trim it (UTF-8 safe)
|
||||||
|
StringUtils::utf8RemoveLastChar(i);
|
||||||
|
// Check if we have room for ellipsis
|
||||||
|
std::string withEllipsis = i + "...";
|
||||||
|
wordWidth = renderer.getTextWidth(UI_12_FONT_ID, withEllipsis.c_str());
|
||||||
|
if (wordWidth <= maxLineWidth) {
|
||||||
|
i = withEllipsis;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int newLineWidth = renderer.getTextWidth(UI_12_FONT_ID, currentLine.c_str());
|
||||||
|
if (newLineWidth > 0) {
|
||||||
|
newLineWidth += spaceWidth;
|
||||||
|
}
|
||||||
|
newLineWidth += wordWidth;
|
||||||
|
|
||||||
|
if (newLineWidth > maxLineWidth && !currentLine.empty()) {
|
||||||
|
// New line too long, push old line
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
currentLine = i;
|
||||||
|
} else {
|
||||||
|
currentLine.append(" ").append(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If lower than the line limit, push remaining words
|
||||||
|
if (!currentLine.empty() && lines.size() < 3) {
|
||||||
|
lines.push_back(currentLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Book title text
|
||||||
|
int totalTextHeight = renderer.getLineHeight(UI_12_FONT_ID) * static_cast<int>(lines.size());
|
||||||
|
if (!lastBookAuthor.empty()) {
|
||||||
|
totalTextHeight += renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertically center the title block within the card
|
||||||
|
int titleYStart = bookY + (bookHeight - totalTextHeight) / 2;
|
||||||
|
|
||||||
|
for (const auto& line : lines) {
|
||||||
|
renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected);
|
||||||
|
titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lastBookAuthor.empty()) {
|
||||||
|
titleYStart += renderer.getLineHeight(UI_10_FONT_ID) / 2;
|
||||||
|
std::string trimmedAuthor = lastBookAuthor;
|
||||||
|
// Trim author if too long (UTF-8 safe)
|
||||||
|
bool wasTrimmed = false;
|
||||||
|
while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) {
|
||||||
|
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
||||||
|
wasTrimmed = true;
|
||||||
|
}
|
||||||
|
if (wasTrimmed && !trimmedAuthor.empty()) {
|
||||||
|
// Make room for ellipsis
|
||||||
|
while (renderer.getTextWidth(UI_10_FONT_ID, (trimmedAuthor + "...").c_str()) > maxLineWidth &&
|
||||||
|
!trimmedAuthor.empty()) {
|
||||||
|
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
||||||
|
}
|
||||||
|
trimmedAuthor.append("...");
|
||||||
|
}
|
||||||
|
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Continue Reading" label at the bottom of card (only when no cover)
|
||||||
|
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
||||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected);
|
renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -498,27 +526,7 @@ 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 ---
|
// --- Bottom menu tiles (anchored to bottom) ---
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
constexpr int tileX = margin;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user