#include "Lyra3CoversTheme.h" #include #include #include #include #include #include "RecentBooksStore.h" #include "components/UITheme.h" #include "components/icons/cover.h" #include "fontIds.h" // Internal constants namespace { constexpr int hPaddingInSelection = 8; constexpr int cornerRadius = 6; } // namespace void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector& recentBooks, const int selectorIndex, bool& coverRendered, bool& coverBufferStored, bool& bufferRestored, std::function storeCoverBuffer) const { const int tileWidth = (rect.width - 2 * Lyra3CoversMetrics::values.contentSidePadding) / 3; const int tileY = rect.y; const int titleAreaHeight = renderer.getLineHeight(SMALL_FONT_ID) * 3 + hPaddingInSelection + 5; const int coverHeight = std::max(120, rect.height - titleAreaHeight); const bool hasContinueReading = !recentBooks.empty(); // Draw book card regardless, fill with message based on `hasContinueReading` // Draw cover image as background if available (inside the box) // Only load from SD on first render, then use stored buffer if (hasContinueReading) { if (!coverRendered) { for (int i = 0; i < std::min(static_cast(recentBooks.size()), Lyra3CoversMetrics::values.homeRecentBooksCount); i++) { std::string coverPath = recentBooks[i].coverBmpPath; bool hasCover = true; int tileX = Lyra3CoversMetrics::values.contentSidePadding + tileWidth * i; if (coverPath.empty()) { hasCover = false; } else { const std::string coverBmpPath = UITheme::getCoverThumbPath(coverPath, coverHeight); // First time: load cover from SD and render FsFile file; if (Storage.openFileForRead("HOME", coverBmpPath, file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { const float bitmapHeight = static_cast(bitmap.getHeight()); const float bitmapWidth = static_cast(bitmap.getWidth()); const float ratio = bitmapWidth / bitmapHeight; const float tileRatio = static_cast(tileWidth - 2 * hPaddingInSelection) / static_cast(coverHeight); const float cropX = 1.0f - (tileRatio / ratio); renderer.drawBitmap(bitmap, tileX + hPaddingInSelection, tileY + hPaddingInSelection, tileWidth - 2 * hPaddingInSelection, coverHeight, cropX); } else { hasCover = false; } file.close(); } else { hasCover = false; } } // Draw either way renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, tileWidth - 2 * hPaddingInSelection, coverHeight, true); if (!hasCover) { // Render empty cover renderer.fillRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection + (coverHeight / 3), tileWidth - 2 * hPaddingInSelection, 2 * coverHeight / 3, true); renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); } } coverBufferStored = storeCoverBuffer(); coverRendered = coverBufferStored; // Only consider it rendered if we successfully stored the buffer } for (int i = 0; i < std::min(static_cast(recentBooks.size()), Lyra3CoversMetrics::values.homeRecentBooksCount); i++) { bool bookSelected = (selectorIndex == i); int tileX = Lyra3CoversMetrics::values.contentSidePadding + tileWidth * i; const int maxLineWidth = tileWidth - 2 * hPaddingInSelection; auto titleLines = renderer.wrappedText(SMALL_FONT_ID, recentBooks[i].title.c_str(), maxLineWidth, 3); const int titleLineHeight = renderer.getLineHeight(SMALL_FONT_ID); const int dynamicBlockHeight = static_cast(titleLines.size()) * titleLineHeight; // Add a little padding below the text inside the selection box just like the top padding (5 + hPaddingSelection) const int dynamicTitleBoxHeight = dynamicBlockHeight + hPaddingInSelection + 5; if (bookSelected) { // Draw selection box renderer.fillRoundedRect(tileX, tileY, tileWidth, hPaddingInSelection, cornerRadius, true, true, false, false, Color::LightGray); renderer.fillRectDither(tileX, tileY + hPaddingInSelection, hPaddingInSelection, coverHeight, Color::LightGray); renderer.fillRectDither(tileX + tileWidth - hPaddingInSelection, tileY + hPaddingInSelection, hPaddingInSelection, coverHeight, Color::LightGray); renderer.fillRoundedRect(tileX, tileY + coverHeight + hPaddingInSelection, tileWidth, dynamicTitleBoxHeight, cornerRadius, false, false, true, true, Color::LightGray); } int currentY = tileY + coverHeight + hPaddingInSelection + 5; for (const auto& line : titleLines) { renderer.drawText(SMALL_FONT_ID, tileX + hPaddingInSelection, currentY, line.c_str(), true); currentY += titleLineHeight; } } } else { drawEmptyRecents(renderer, rect); } }