Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
7.4 kB · 205 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206#include "UITheme.h"
#include <FsHelpers.h>#include <GfxRenderer.h>#include <HalGPIO.h>#include <HalMemory.h>#include <Logging.h>#include <Memory.h>
#include <algorithm>#include <memory>
#include "MappedInputManager.h"#include "RecentBooksStore.h"#include "components/CoverGridHomeUi.h"#include "components/themes/BaseTheme.h"#include "components/themes/lyra/Lyra3CoversTheme.h"#include "components/themes/lyra/LyraTheme.h"#include "components/themes/roundedraff/RoundedRaffTheme.h"
UITheme UITheme::instance;
UITheme::UITheme() { auto themeType = static_cast<CrossPointSettings::UI_THEME>(SETTINGS.uiTheme); setTheme(themeType);}
void UITheme::reload() { auto themeType = static_cast<CrossPointSettings::UI_THEME>(SETTINGS.uiTheme); setTheme(themeType);}
bool UITheme::supportsCoverGrid() { return HalMemory::getPsramHeap().totalBytes > 0; }
bool UITheme::hasCoverGridHome() { return SETTINGS.uiTheme == CrossPointSettings::COVER_GRID && supportsCoverGrid(); }
void UITheme::drawCoverGridHome(CoverGridHomeUi& home) { home.renderUi(); }
void UITheme::setTheme(CrossPointSettings::UI_THEME type) { if (type == CrossPointSettings::COVER_GRID && !supportsCoverGrid()) type = CrossPointSettings::LYRA;
switch (type) { case CrossPointSettings::UI_THEME::CLASSIC: LOG_DBG("UI", "Using Classic theme"); currentTheme = std::make_unique<BaseTheme>(); currentMetrics = &BaseMetrics::values; break; case CrossPointSettings::UI_THEME::COVER_GRID: case CrossPointSettings::UI_THEME::LYRA: { // The cover home owns its screen-lifetime UI state; other screens retain Lyra styling. auto theme = makeUniqueNoThrow<LyraTheme>(); if (!theme) { LOG_ERR("UI", "OOM: Lyra theme"); return; } currentTheme = std::move(theme); currentMetrics = &LyraMetrics::values; LOG_DBG("UI", "Using Lyra theme"); break; } case CrossPointSettings::UI_THEME::ROUNDEDRAFF: LOG_DBG("UI", "Using RoundedRaff theme"); currentTheme = std::make_unique<RoundedRaffTheme>(); currentMetrics = &RoundedRaffMetrics::values; break; case CrossPointSettings::UI_THEME::LYRA_3_COVERS: LOG_DBG("UI", "Using Lyra 3 Covers theme"); currentTheme = std::make_unique<Lyra3CoversTheme>(); currentMetrics = &Lyra3CoversMetrics::values; break; } metricsValid = false;}
const ThemeMetrics& UITheme::getMetrics() const { // hasTouch() can flip once touch init completes after static construction, so the // cached copy is refreshed when the flag differs instead of copying the struct per call. const bool touch = gpio.hasTouch(); if (!metricsValid || touch != metricsForTouch) { adjustedMetrics = *currentMetrics; if (touch) { adjustedMetrics.buttonHintsHeight = 0; } metricsForTouch = touch; metricsValid = true; } return adjustedMetrics;}
// Screen area excluding the button hintsRect UITheme::getScreenSafeArea(const GfxRenderer& renderer, bool hasFrontButtonHints, bool hasSideButtonHints) { auto orientation = renderer.getOrientation(); const int screenWidth = renderer.getScreenWidth(); const int screenHeight = renderer.getScreenHeight(); Rect safeArea = Rect{0, 0, screenWidth, screenHeight}; const ThemeMetrics metrics = getMetrics(); switch (orientation) { case GfxRenderer::Orientation::Portrait: if (hasFrontButtonHints) { safeArea.height -= metrics.buttonHintsHeight; } break; case GfxRenderer::Orientation::LandscapeClockwise: if (hasFrontButtonHints) { safeArea.x += metrics.buttonHintsHeight; safeArea.width -= metrics.buttonHintsHeight; } break; case GfxRenderer::Orientation::PortraitInverted: if (hasFrontButtonHints) { safeArea.y += metrics.buttonHintsHeight; safeArea.height -= metrics.buttonHintsHeight; } break; case GfxRenderer::Orientation::LandscapeCounterClockwise: if (hasFrontButtonHints) { safeArea.width -= metrics.buttonHintsHeight; } break; } return safeArea;}
std::string UITheme::getCoverThumbPath(std::string coverBmpPath, int coverHeight) { size_t pos = coverBmpPath.find("[HEIGHT]", 0); if (pos != std::string::npos) { coverBmpPath.replace(pos, 8, std::to_string(coverHeight)); } return coverBmpPath;}
UIIcon UITheme::getFileIcon(const std::string& filename) { if (filename.back() == '/') { return Folder; } if (FsHelpers::hasEpubExtension(filename) || FsHelpers::hasXtcExtension(filename)) { return Book; } if (FsHelpers::hasTxtExtension(filename) || FsHelpers::hasMarkdownExtension(filename)) { return Text; } if (FsHelpers::hasBmpExtension(filename) || FsHelpers::hasPngExtension(filename)) { return Image; } return File;}
int UITheme::getStatusBarHeight() { const ThemeMetrics metrics = UITheme::getInstance().getMetrics(); const auto sb = SETTINGS.statusBarSpec();
// Layout reservation is hardware-agnostic: pass clockAvailable=true so the // reserved height does not depend on whether an RTC is present. return (sb.textLaneVisible(true) ? (metrics.statusBarVerticalMargin) : 0) + (sb.showsProgressBar() ? (sb.progressBarHeightPx + metrics.progressBarMarginTop) : 0);}
int UITheme::getProgressBarHeight() { const ThemeMetrics metrics = UITheme::getInstance().getMetrics(); const auto sb = SETTINGS.statusBarSpec(); return sb.showsProgressBar() ? (sb.progressBarHeightPx + metrics.progressBarMarginTop) : 0;}
// Centered text implementation that takes the safe area into accountvoid UITheme::drawCenteredText(const GfxRenderer& renderer, Rect screen, int fontId, int y, const char* text, bool black, EpdFontFamily::Style style) { const int x = screen.x + (screen.width - renderer.getTextWidth(fontId, text, style)) / 2; renderer.drawText(fontId, x, y, text, black, style);}
void UITheme::drawCenteredWrappedText(const GfxRenderer& renderer, Rect bounds, int fontId, const char* text, int maxLines, bool black, EpdFontFamily::Style style, TextVerticalAlignment verticalAlignment) { if (!text || *text == '\0' || bounds.width <= 0 || bounds.height <= 0 || maxLines <= 0) return;
const int lineHeight = renderer.getLineHeight(fontId); if (lineHeight <= 0) return;
const int lineLimit = std::min(maxLines, bounds.height / lineHeight); if (lineLimit <= 0) return;
const auto alignedTop = [&](const int textHeight) { switch (verticalAlignment) { case TextVerticalAlignment::CENTER: return bounds.y + (bounds.height - textHeight) / 2; case TextVerticalAlignment::BOTTOM: return bounds.y + bounds.height - textHeight; case TextVerticalAlignment::TOP: default: return bounds.y; } };
if (renderer.getTextWidth(fontId, text, style) <= bounds.width) { drawCenteredText(renderer, bounds, fontId, alignedTop(lineHeight), text, black, style); return; }
const auto lines = renderer.wrappedText(fontId, text, bounds.width, lineLimit, style); int y = alignedTop(static_cast<int>(lines.size()) * lineHeight); for (const auto& line : lines) { drawCenteredText(renderer, bounds, fontId, y, line.c_str(), black, style); y += lineHeight; }}