diff --git a/lib/EpdFont/SdCardFont.cpp b/lib/EpdFont/SdCardFont.cpp index 1609ab3a28..efb79cce4d 100644 --- a/lib/EpdFont/SdCardFont.cpp +++ b/lib/EpdFont/SdCardFont.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -131,6 +132,7 @@ void SdCardFont::freeAll() { freeStyleAll(styles_[i]); } styleCount_ = 0; + cacheOwner_ = CacheOwner::None; contentHash_ = 0; loaded_ = false; } @@ -657,11 +659,47 @@ int32_t SdCardFont::findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) // --- Prewarm --- +void SdCardFont::appendLigatureOutputs(uint8_t styleIdx, uint32_t* codepoints, uint32_t& cpCount, uint32_t maxCount) { + if (styleIdx >= MAX_STYLES || !styles_[styleIdx].present || cpCount == 0 || cpCount >= maxCount) return; + + auto& s = styles_[styleIdx]; + loadStyleKernLigatureData(s); + if (!s.ligaturePairs) return; + + for (uint8_t li = 0; li < s.header.ligaturePairCount && cpCount < maxCount; li++) { + const uint32_t leftCp = s.ligaturePairs[li].pair >> 16; + const uint32_t rightCp = s.ligaturePairs[li].pair & 0xFFFF; + const uint32_t outCp = s.ligaturePairs[li].ligatureCp; + + bool hasLeft = false; + bool hasRight = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == leftCp) hasLeft = true; + if (codepoints[i] == rightCp) hasRight = true; + if (hasLeft && hasRight) break; + } + if (!hasLeft || !hasRight) continue; + + bool hasOut = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == outCp) { + hasOut = true; + break; + } + } + if (!hasOut) codepoints[cpCount++] = outCp; + } +} + int SdCardFont::prewarm(const char* utf8Text, uint8_t styleMask, bool metadataOnly) { if (!loaded_) return -1; + if (!utf8Text) return 0; styleMask = resolveStyleMask(styleMask); if (styleMask == 0) return 0; + if (cacheOwner_ == CacheOwner::Ui) clearCache(); + cacheOwner_ = CacheOwner::ReaderPage; + unsigned long startMs = millis(); // Step 1: Extract unique codepoints from UTF-8 text (shared across all styles). @@ -715,35 +753,7 @@ int SdCardFont::prewarm(const char* utf8Text, uint8_t styleMask, bool metadataOn if (!metadataOnly) { for (uint8_t si = 0; si < MAX_STYLES; si++) { if (!(styleMask & (1 << si)) || !styles_[si].present) continue; - auto& s = styles_[si]; - - loadStyleKernLigatureData(s); - if (s.ligaturePairs && s.header.ligaturePairCount > 0) { - for (uint8_t li = 0; li < s.header.ligaturePairCount && cpCount < MAX_PAGE_GLYPHS; li++) { - uint32_t leftCp = s.ligaturePairs[li].pair >> 16; - uint32_t rightCp = s.ligaturePairs[li].pair & 0xFFFF; - uint32_t outCp = s.ligaturePairs[li].ligatureCp; - - bool hasLeft = false, hasRight = false; - for (uint32_t i = 0; i < cpCount; i++) { - if (codepoints[i] == leftCp) hasLeft = true; - if (codepoints[i] == rightCp) hasRight = true; - if (hasLeft && hasRight) break; - } - if (!hasLeft || !hasRight) continue; - - bool hasOut = false; - for (uint32_t i = 0; i < cpCount; i++) { - if (codepoints[i] == outCp) { - hasOut = true; - break; - } - } - if (!hasOut) { - codepoints[cpCount++] = outCp; - } - } - } + appendLigatureOutputs(si, codepoints.get(), cpCount, MAX_PAGE_GLYPHS); } } @@ -761,6 +771,107 @@ int SdCardFont::prewarm(const char* utf8Text, uint8_t styleMask, bool metadataOn return totalMissed; } +int SdCardFont::prewarmUi(const char* utf8Text, uint8_t styleMask) { + if (!loaded_) return -1; + if (!utf8Text) return 0; + styleMask = resolveStyleMask(styleMask); + if (styleMask == 0) return 0; + + // Cache mutation is not internally synchronized; callers must serialize UI + // measurement and rendering with RenderLock. + if (cacheOwner_ == CacheOwner::Ui) { + bool allCached = true; + for (uint8_t si = 0; si < MAX_STYLES && allCached; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + const auto& s = styles_[si]; + + const char* cursor = utf8Text; + uint32_t cp; + while ((cp = utf8NextCodepoint(reinterpret_cast(&cursor)))) { + cp = s.epdFont.applyLigatures(cp, cursor); + if (!s.epdFont.hasCodepoint(cp)) { + allCached = false; + break; + } + } + } + if (allCached) return 0; + } + + if (cacheOwner_ != CacheOwner::Ui) { + clearCache(); + cacheOwner_ = CacheOwner::Ui; + } + + auto codepoints = makeUniqueNoThrow(MAX_UI_GLYPHS); + if (!codepoints) { + LOG_ERR("SDCF", "Failed to allocate UI codepoint buffer (%u bytes)", MAX_UI_GLYPHS * 4); + return -1; + } + uint32_t cpCount = 0; + + // Put the current string first. If the bounded cache fills, the visible UI + // gets priority over glyphs retained from an older screen. + collectUniqueCodepoints(utf8Text, codepoints.get(), cpCount, MAX_UI_GLYPHS); + + bool hasReplacement = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == REPLACEMENT_GLYPH) { + hasReplacement = true; + break; + } + } + if (!hasReplacement && cpCount < MAX_UI_GLYPHS) codepoints[cpCount++] = REPLACEMENT_GLYPH; + + // Retain already cached glyphs while there is room. The next rebuild keeps + // the union, so a repeated UI string does not touch the SD card again. + for (uint8_t si = 0; si < MAX_STYLES && cpCount < MAX_UI_GLYPHS; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + const auto& s = styles_[si]; + for (uint32_t ii = 0; ii < s.miniData.intervalCount && cpCount < MAX_UI_GLYPHS; ii++) { + const auto& interval = s.miniData.intervals[ii]; + for (uint32_t cp = interval.first;; cp++) { + bool found = false; + for (uint32_t i = 0; i < cpCount; i++) { + if (codepoints[i] == cp) { + found = true; + break; + } + } + if (!found) codepoints[cpCount++] = cp; + if (cp == interval.last || cpCount >= MAX_UI_GLYPHS) break; + } + } + } + + for (uint8_t si = 0; si < MAX_STYLES; si++) { + if (styleMask & (1 << si)) appendLigatureOutputs(si, codepoints.get(), cpCount, MAX_UI_GLYPHS); + } + std::sort(codepoints.get(), codepoints.get() + cpCount); + + bool needsRebuild = false; + for (uint8_t si = 0; si < MAX_STYLES && !needsRebuild; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + const auto& s = styles_[si]; + for (uint32_t i = 0; i < cpCount; i++) { + if (findGlobalGlyphIndex(s, codepoints[i]) >= 0 && !s.epdFont.hasCodepoint(codepoints[i])) { + needsRebuild = true; + break; + } + } + } + if (!needsRebuild) return 0; + + unsigned long startMs = millis(); + int totalMissed = 0; + for (uint8_t si = 0; si < MAX_STYLES; si++) { + if (!(styleMask & (1 << si)) || !styles_[si].present) continue; + totalMissed += prewarmStyle(si, codepoints.get(), cpCount, false); + } + stats_.prewarmTotalMs = millis() - startMs; + return totalMissed; +} + int SdCardFont::prewarmStyle(uint8_t styleIdx, const uint32_t* codepoints, uint32_t cpCount, bool metadataOnly) { auto& s = styles_[styleIdx]; @@ -996,6 +1107,16 @@ void SdCardFont::clearCache() { freeStyleMiniData(styles_[i]); applyGlyphMissCallback(i); } + cacheOwner_ = CacheOwner::None; +} + +// Activity transitions deliberately clear all active bitmap caches, +// regardless of cacheOwner_. +void SdCardFont::clearUiCache() { clearCache(); } + +void SdCardFont::clearReaderCache() { + clearCache(); + clearPersistentCache(); } // --- Advance table --- diff --git a/lib/EpdFont/SdCardFont.h b/lib/EpdFont/SdCardFont.h index 443fee684a..7b5a6e8023 100644 --- a/lib/EpdFont/SdCardFont.h +++ b/lib/EpdFont/SdCardFont.h @@ -21,6 +21,7 @@ class SdCardFont { public: static constexpr uint16_t MAX_PAGE_GLYPHS = 512; + static constexpr uint16_t MAX_UI_GLYPHS = 256; static constexpr uint8_t MAX_STYLES = 4; SdCardFont() = default; @@ -44,6 +45,10 @@ class SdCardFont { // Returns number of glyphs that couldn't be loaded (0 on full success). int prewarm(const char* utf8Text, uint8_t styleMask = 0x0F, bool metadataOnly = false); + // Batch-load UI glyphs and keep the bounded bitmap cache until the next + // reader/UI transition or SD-font invalidation. + int prewarmUi(const char* utf8Text, uint8_t styleMask = 0x0F); + // Build a compact advance-only table for layout measurement. // Extracts ALL unique codepoints from words (no MAX_PAGE_GLYPHS cap), // batch-reads advanceX from SD, stores in a sorted per-style table. @@ -63,6 +68,12 @@ class SdCardFont { // previously fetched metrics. void clearCache(); + // Clear the active UI bitmap cache while retaining font metadata and advances. + void clearUiCache(); + + // Clear reader bitmap and persistent advance caches. + void clearReaderCache(); + // Drop the persistent advance cache. Call when unloading the SD font or // when font/size/family/glyph-table state changes. void clearPersistentCache(); @@ -191,8 +202,11 @@ class SdCardFont { bool present = false; }; + enum class CacheOwner : uint8_t { None, ReaderPage, Ui }; + PerStyle styles_[MAX_STYLES] = {}; uint8_t styleCount_ = 0; + CacheOwner cacheOwner_ = CacheOwner::None; char filePath_[128] = {}; @@ -244,6 +258,7 @@ class SdCardFont { void freeStyleMiniKern(PerStyle& s); bool loadStyleKernLigatureData(PerStyle& s); bool buildMiniKernMatrix(PerStyle& s, const uint32_t* codepoints, uint32_t cpCount); + void appendLigatureOutputs(uint8_t styleIdx, uint32_t* codepoints, uint32_t& cpCount, uint32_t maxCount); void applyKernLigaturePointers(PerStyle& s, EpdFontData& data) const; void applyGlyphMissCallback(uint8_t styleIdx); int32_t findGlobalGlyphIndex(const PerStyle& s, uint32_t codepoint) const; diff --git a/lib/GfxRenderer/FontCacheManager.cpp b/lib/GfxRenderer/FontCacheManager.cpp index 19b1a5c13d..a9b1171a58 100644 --- a/lib/GfxRenderer/FontCacheManager.cpp +++ b/lib/GfxRenderer/FontCacheManager.cpp @@ -19,6 +19,32 @@ void FontCacheManager::clearCache() { } } +void FontCacheManager::clearUiCache() { + if (fontDecompressor_) fontDecompressor_->clearCache(); + for (auto& [id, font] : sdCardFonts_) { + font->clearUiCache(); + } +} + +void FontCacheManager::clearReaderCache() { + if (fontDecompressor_) fontDecompressor_->clearCache(); + for (auto& [id, font] : sdCardFonts_) { + font->clearReaderCache(); + } +} + +void FontCacheManager::prewarmUi(int fontId, const char* utf8Text, EpdFontFamily::Style style) { + auto it = sdCardFonts_.find(fontId); + if (it == sdCardFonts_.end()) return; + + const uint8_t styleMask = static_cast(1u << (static_cast(style) & 0x03)); + const int missed = it->second->prewarmUi(utf8Text, styleMask); + if (missed > 0) { + LOG_DBG("FCM", "prewarmUi: %d glyph(s) not found (fontId=%d, style=%u)", missed, fontId, + static_cast(style) & 0x03); + } +} + void FontCacheManager::prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask) { // SD card font prewarm path: prewarm all requested styles in one call auto it = sdCardFonts_.find(fontId); diff --git a/lib/GfxRenderer/FontCacheManager.h b/lib/GfxRenderer/FontCacheManager.h index 27dde4a00e..1eca85ab37 100644 --- a/lib/GfxRenderer/FontCacheManager.h +++ b/lib/GfxRenderer/FontCacheManager.h @@ -16,7 +16,10 @@ class FontCacheManager { void setFontDecompressor(FontDecompressor* d); void clearCache(); + void clearUiCache(); + void clearReaderCache(); void prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask = 0x0F); + void prewarmUi(int fontId, const char* utf8Text, EpdFontFamily::Style style); void logStats(const char* label = "render"); void resetStats(); diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index 1d1b7bfda0..b117521b2c 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -93,6 +93,30 @@ void GfxRenderer::begin() { bool GfxRenderer::isFontCacheScanning() const { return fontCacheManager_ && fontCacheManager_->isScanning(); } +void GfxRenderer::clearUiFontCache() const { + if (fontCacheManager_) { + fontCacheManager_->clearUiCache(); + return; + } + for (const auto& [id, font] : sdCardFonts_) { + font->clearUiCache(); + } +} + +void GfxRenderer::clearReaderFontCache() const { + if (fontCacheManager_) { + fontCacheManager_->clearReaderCache(); + return; + } + for (const auto& [id, font] : sdCardFonts_) { + font->clearReaderCache(); + } +} + +void GfxRenderer::prewarmUiText(const int fontId, const char* text, const EpdFontFamily::Style style) const { + resolveTextFontId(fontId, text, style); +} + void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { auto result = fontMap.insert({fontId, font}); if (!result.second) { @@ -120,6 +144,12 @@ int GfxRenderer::resolveTextFontId(const int fontId, const char* text, const Epd // Only redirect for CJK the primary font cannot draw. Latin/symbol strings // the built-in UI fonts already cover are left untouched. if (utf8IsCjkBreakable(cp) && !primary.hasCodepoint(cp, style)) { + if (fontCacheManager_) { + fontCacheManager_->prewarmUi(fallbackFontId, text, style); + } else { + const auto sdIt = sdCardFonts_.find(fallbackFontId); + if (sdIt != sdCardFonts_.end()) sdIt->second->prewarmUi(text, 1u << (static_cast(style) & 0x03)); + } return fallbackFontId; } } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index 4fe718f3b2..83ab6af319 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -124,6 +124,11 @@ class GfxRenderer { } void setFontCacheManager(FontCacheManager* m) { fontCacheManager_ = m; } FontCacheManager* getFontCacheManager() const { return fontCacheManager_; } + void clearUiFontCache() const; + void clearReaderFontCache() const; + // Trigger one batch prewarm for a UI string. The normal resolver keeps + // Latin-only strings on the built-in font and redirects CJK strings to SD. + void prewarmUiText(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; bool isFontCacheScanning() const; const std::map& getFontMap() const { return fontMap; } void registerSdCardFont(int fontId, SdCardFont* font) { sdCardFonts_[fontId] = font; } diff --git a/src/activities/ActivityManager.cpp b/src/activities/ActivityManager.cpp index 7757d050b8..e3c33c745b 100644 --- a/src/activities/ActivityManager.cpp +++ b/src/activities/ActivityManager.cpp @@ -1,6 +1,5 @@ #include "ActivityManager.h" -#include #include #include @@ -37,12 +36,25 @@ void ActivityManager::renderTaskTrampoline(void* param) { self->renderTaskLoop(); } +void ActivityManager::syncFontCacheMode() { + const bool inReader = isReaderActivity(); + if (inReader == fontCacheReaderMode_) return; + + if (inReader) { + renderer.clearUiFontCache(); + } else { + renderer.clearReaderFontCache(); + } + fontCacheReaderMode_ = inReader; +} + void ActivityManager::renderTaskLoop() { while (true) { ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // Acquire the lock before reading currentActivity to avoid a TOCTOU race // where the main task deletes the activity between the null-check and render(). RenderLock lock; + syncFontCacheMode(); if (currentActivity) { HalPowerManager::Lock powerLock; // Ensure we don't go into low-power mode while rendering currentActivity->render(std::move(lock)); @@ -91,6 +103,7 @@ void ActivityManager::loop() { } else { currentActivity = std::move(stackActivities.back()); stackActivities.pop_back(); + syncFontCacheMode(); LOG_DBG("ACT", "Popped from activity stack, new size = %zu", stackActivities.size()); // Handle result if necessary if (currentActivity->resultHandler) { @@ -131,6 +144,7 @@ void ActivityManager::loop() { } pendingAction = PendingAction::None; currentActivity = std::move(pendingActivity); + syncFontCacheMode(); lock.unlock(); // onEnter may acquire its own lock currentActivity->onEnter(); @@ -158,7 +172,7 @@ void ActivityManager::exitActivity(const RenderLock& lock) { } void ActivityManager::replaceActivity(std::unique_ptr&& newActivity) { - // Note: no lock here, this is usually called by loop() and we may run into deadlock + // Deferred replacements are locked in loop(); only the immediate path locks here. if (currentActivity) { // Defer launch if we're currently in an activity, to avoid deleting the current activity // leading to the "delete this" problem @@ -166,7 +180,10 @@ void ActivityManager::replaceActivity(std::unique_ptr&& newActivity) { pendingAction = PendingAction::Replace; } else { // No current activity, safe to launch immediately + RenderLock lock; currentActivity = std::move(newActivity); + syncFontCacheMode(); + lock.unlock(); currentActivity->onEnter(); } } diff --git a/src/activities/ActivityManager.h b/src/activities/ActivityManager.h index 5712ec7122..d3db8a2cc6 100644 --- a/src/activities/ActivityManager.h +++ b/src/activities/ActivityManager.h @@ -53,6 +53,8 @@ class ActivityManager { TaskHandle_t renderTaskHandle = nullptr; static void renderTaskTrampoline(void* param); [[noreturn]] virtual void renderTaskLoop(); + void syncFontCacheMode(); + bool fontCacheReaderMode_ = false; // Set by requestUpdateAndWait(); read and cleared by the render task after render completes. // Note: only one waiting task is supported at a time diff --git a/src/activities/settings/FontSelectionActivity.cpp b/src/activities/settings/FontSelectionActivity.cpp index ca53f3a8f4..c13766f4c9 100644 --- a/src/activities/settings/FontSelectionActivity.cpp +++ b/src/activities/settings/FontSelectionActivity.cpp @@ -171,7 +171,7 @@ void FontSelectionActivity::renderPreviewPane(int top, int height, int fontId, c if (auto* fcm = renderer.getFontCacheManager()) { char prewarmBuf[256]; snprintf(prewarmBuf, sizeof(prewarmBuf), "%s %s", previewText, ELLIPSIS_UTF8); - fcm->prewarmCache(fontId, prewarmBuf, 0x01); + fcm->prewarmUi(fontId, prewarmBuf, EpdFontFamily::REGULAR); } const auto lines = renderer.wrappedText(fontId, previewText, width, maxLines); diff --git a/src/activities/util/ConfirmationActivity.cpp b/src/activities/util/ConfirmationActivity.cpp index f37cfde8e7..b9c54b32fc 100644 --- a/src/activities/util/ConfirmationActivity.cpp +++ b/src/activities/util/ConfirmationActivity.cpp @@ -11,6 +11,7 @@ ConfirmationActivity::ConfirmationActivity(GfxRenderer& renderer, MappedInputMan void ConfirmationActivity::onEnter() { Activity::onEnter(); + RenderLock lock(*this); lineHeight = renderer.getLineHeight(fontId); const int maxWidth = renderer.getScreenWidth() - (margin * 2); @@ -71,4 +72,4 @@ void ConfirmationActivity::loop() { finish(); return; } -} \ No newline at end of file +} diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index b2bbc21c16..b3aae20d50 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -255,6 +255,38 @@ int BaseTheme::getListPageItems(int contentHeight, bool hasSubtitle) const { return contentHeight / rowHeight; } +void BaseTheme::prewarmListUi(const GfxRenderer& renderer, const int firstIndex, const int endIndex, + const std::function& rowTitle, + const std::function& rowSubtitle, + const std::function& rowValue, const int titleFontId, + const EpdFontFamily::Style titleStyle, const int subtitleFontId, + const EpdFontFamily::Style subtitleStyle, const int valueFontId, + const EpdFontFamily::Style valueStyle) const { + std::string titleBatch; + std::string subtitleBatch; + std::string valueBatch; + const int rowCount = std::max(0, endIndex - firstIndex); + titleBatch.reserve(static_cast(rowCount) * 16); + subtitleBatch.reserve(static_cast(rowCount) * 16); + valueBatch.reserve(static_cast(rowCount) * 8); + + const auto append = [](std::string& batch, const std::string& text) { + if (text.empty()) return; + if (!batch.empty()) batch.push_back('\n'); + batch += text; + }; + + for (int i = firstIndex; i < endIndex; i++) { + append(titleBatch, rowTitle(i)); + if (rowSubtitle) append(subtitleBatch, rowSubtitle(i)); + if (rowValue) append(valueBatch, rowValue(i)); + } + + if (!titleBatch.empty()) renderer.prewarmUiText(titleFontId, titleBatch.c_str(), titleStyle); + if (!subtitleBatch.empty()) renderer.prewarmUiText(subtitleFontId, subtitleBatch.c_str(), subtitleStyle); + if (!valueBatch.empty()) renderer.prewarmUiText(valueFontId, valueBatch.c_str(), valueStyle); +} + void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, const std::function& rowTitle, const std::function& rowSubtitle, @@ -300,6 +332,9 @@ void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, // Draw all items const auto pageStartIndex = selectedIndex / pageItems * pageItems; + prewarmListUi(renderer, pageStartIndex, std::min(itemCount, pageStartIndex + pageItems), rowTitle, rowSubtitle, + rowValue, UI_10_FONT_ID, EpdFontFamily::REGULAR, SMALL_FONT_ID, EpdFontFamily::REGULAR, UI_10_FONT_ID, + EpdFontFamily::REGULAR); for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) { const int itemY = rect.y + (i % pageItems) * rowHeight; diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index 46d0bdaf8f..7c39601e4d 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -219,6 +221,14 @@ class BaseTheme { const std::function& rowIcon = nullptr, const std::function& rowValue = nullptr, bool highlightValue = false, const std::function& rowDimmed = nullptr) const; + // Prewarm the visible list rows as one UI batch so each new row does not + // rebuild and reread the entire existing mini cache. + void prewarmListUi(const GfxRenderer& renderer, int firstIndex, int endIndex, + const std::function& rowTitle, + const std::function& rowSubtitle, + const std::function& rowValue, int titleFontId, + EpdFontFamily::Style titleStyle, int subtitleFontId, EpdFontFamily::Style subtitleStyle, + int valueFontId, EpdFontFamily::Style valueStyle) const; virtual void drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, const char* subtitle = nullptr) const; virtual void drawSubHeader(const GfxRenderer& renderer, Rect rect, const char* label, diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index c5af0a395c..71bc28a4f9 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -255,6 +255,9 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, // Draw all items const auto pageStartIndex = selectedIndex / pageItems * pageItems; + prewarmListUi(renderer, pageStartIndex, std::min(itemCount, pageStartIndex + pageItems), rowTitle, rowSubtitle, + rowValue, UI_10_FONT_ID, EpdFontFamily::REGULAR, SMALL_FONT_ID, EpdFontFamily::REGULAR, UI_10_FONT_ID, + EpdFontFamily::REGULAR); int iconY = (rowSubtitle != nullptr) ? 16 : 10; for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) { const int itemY = rect.y + (i % pageItems) * rowHeight; diff --git a/src/components/themes/roundedraff/RoundedRaffTheme.cpp b/src/components/themes/roundedraff/RoundedRaffTheme.cpp index 0e609ac993..fc3275a78e 100644 --- a/src/components/themes/roundedraff/RoundedRaffTheme.cpp +++ b/src/components/themes/roundedraff/RoundedRaffTheme.cpp @@ -327,6 +327,9 @@ void RoundedRaffTheme::drawList(const GfxRenderer& renderer, Rect rect, int item const int rowX = rect.x + sidePadding; const int rowWidth = rect.width - sidePadding * 2; + prewarmListUi(renderer, pageStartIndex, std::min(itemCount, pageStartIndex + pageItems), rowTitle, rowSubtitle, + rowValue, kTitleFontId, EpdFontFamily::BOLD, kSubtitleFontId, EpdFontFamily::REGULAR, kTitleFontId, + EpdFontFamily::REGULAR); for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) { const int rowY = rect.y + (i % pageItems) * rowStep; const bool isSelected = i == selectedIndex;