Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
11 kB · 298 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299#include "SleepActivity.h"
#include <Epub.h>#include <FsHelpers.h>#include <GfxRenderer.h>#include <HalStorage.h>#include <I18n.h>#include <Txt.h>#include <Xtc.h>
#include "CrossPointSettings.h"#include "CrossPointState.h"#include "activities/reader/ReaderUtils.h"#include "components/UITheme.h"#include "fontIds.h"#include "images/Logo120.h"
void SleepActivity::onEnter() { Activity::onEnter();
// Show popup with reader orientation only when going to sleep from reader if (APP_STATE.lastSleepFromReader) { ReaderUtils::applyOrientation(renderer, SETTINGS.orientation); GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); renderer.setOrientation(GfxRenderer::Orientation::Portrait); } else { GUI.drawPopup(renderer, tr(STR_ENTERING_SLEEP)); }
switch (SETTINGS.sleepScreen) { case (CrossPointSettings::SLEEP_SCREEN_MODE::BLANK): return renderBlankSleepScreen(); case (CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM): return renderCustomSleepScreen(); case (CrossPointSettings::SLEEP_SCREEN_MODE::COVER): return renderCoverSleepScreen(); case (CrossPointSettings::SLEEP_SCREEN_MODE::COVER_CUSTOM): if (APP_STATE.lastSleepFromReader) { return renderCoverSleepScreen(); } else { return renderCustomSleepScreen(); } default: return renderDefaultSleepScreen(); }}
void SleepActivity::renderCustomSleepScreen() const { // Check if we have a /.sleep (preferred) or /sleep directory const char* sleepDir = nullptr; auto dir = Storage.open("/.sleep"); if (dir && dir.isDirectory()) { sleepDir = "/.sleep"; } else { dir = Storage.open("/sleep"); if (dir && dir.isDirectory()) { sleepDir = "/sleep"; } }
if (sleepDir) { std::vector<std::string> files; char name[500]; // collect all valid BMP files for (auto file = dir.openNextFile(); file; file = dir.openNextFile()) { if (file.isDirectory()) { continue; } file.getName(name, sizeof(name)); auto filename = std::string(name); if (filename[0] == '.') { continue; }
if (!FsHelpers::hasBmpExtension(filename)) { LOG_DBG("SLP", "Skipping non-.bmp file name: %s", name); continue; } Bitmap bitmap(file); if (bitmap.parseHeaders() != BmpReaderError::Ok) { LOG_DBG("SLP", "Skipping invalid BMP file: %s", name); continue; } files.emplace_back(filename); } const auto numFiles = files.size(); if (numFiles > 0) { // Pick a random wallpaper, excluding recently shown ones. // Window: up to SLEEP_RECENT_COUNT entries, capped at numFiles-1. const uint16_t fileCount = static_cast<uint16_t>(std::min(numFiles, static_cast<size_t>(UINT16_MAX))); const uint8_t window = static_cast<uint8_t>(std::min(static_cast<size_t>(APP_STATE.recentSleepFill), numFiles - 1)); auto randomFileIndex = static_cast<uint16_t>(random(fileCount)); for (uint8_t attempt = 0; attempt < 20 && APP_STATE.isRecentSleep(randomFileIndex, window); attempt++) { randomFileIndex = static_cast<uint16_t>(random(fileCount)); } APP_STATE.pushRecentSleep(randomFileIndex); APP_STATE.saveToFile(); const auto filename = std::string(sleepDir) + "/" + files[randomFileIndex]; FsFile file; if (Storage.openFileForRead("SLP", filename, file)) { LOG_DBG("SLP", "Randomly loading: %s/%s", sleepDir, files[randomFileIndex].c_str()); delay(100); Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { renderBitmapSleepScreen(bitmap); return; } } } } // Look for sleep.bmp on the root of the sd card to determine if we should // render a custom sleep screen instead of the default. FsFile file; if (Storage.openFileForRead("SLP", "/sleep.bmp", file)) { Bitmap bitmap(file, true); if (bitmap.parseHeaders() == BmpReaderError::Ok) { LOG_DBG("SLP", "Loading: /sleep.bmp"); renderBitmapSleepScreen(bitmap); return; } }
renderDefaultSleepScreen();}
void SleepActivity::renderDefaultSleepScreen() const { const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight();
renderer.clearScreen(); renderer.drawImage(Logo120, (pageWidth - 120) / 2, (pageHeight - 120) / 2, 120, 120); renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2 + 70, tr(STR_CROSSPOINT), true, EpdFontFamily::BOLD); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, tr(STR_SLEEPING));
// Make sleep screen dark unless light is selected in settings if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) { renderer.invertScreen(); }
renderer.displayBuffer(HalDisplay::HALF_REFRESH);}
void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const { int x, y; const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); float cropX = 0, cropY = 0;
LOG_DBG("SLP", "bitmap %d x %d, screen %d x %d", bitmap.getWidth(), bitmap.getHeight(), pageWidth, pageHeight); if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) { // image will scale, make sure placement is right float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight()); const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
LOG_DBG("SLP", "bitmap ratio: %f, screen ratio: %f", ratio, screenRatio); if (ratio > screenRatio) { // image wider than viewport ratio, scaled down image needs to be centered vertically if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) { cropX = 1.0f - (screenRatio / ratio); LOG_DBG("SLP", "Cropping bitmap x: %f", cropX); ratio = (1.0f - cropX) * static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight()); } x = 0; y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2); LOG_DBG("SLP", "Centering with ratio %f to y=%d", ratio, y); } else { // image taller than viewport ratio, scaled down image needs to be centered horizontally if (SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP) { cropY = 1.0f - (ratio / screenRatio); LOG_DBG("SLP", "Cropping bitmap y: %f", cropY); ratio = static_cast<float>(bitmap.getWidth()) / ((1.0f - cropY) * static_cast<float>(bitmap.getHeight())); } x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2); y = 0; LOG_DBG("SLP", "Centering with ratio %f to x=%d", ratio, x); } } else { // center the image x = (pageWidth - bitmap.getWidth()) / 2; y = (pageHeight - bitmap.getHeight()) / 2; }
LOG_DBG("SLP", "drawing to %d x %d", x, y); renderer.clearScreen();
const bool hasGreyscale = bitmap.hasGreyscale() && SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::NO_FILTER;
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY);
if (SETTINGS.sleepScreenCoverFilter == CrossPointSettings::SLEEP_SCREEN_COVER_FILTER::INVERTED_BLACK_AND_WHITE) { renderer.invertScreen(); }
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
if (hasGreyscale) { bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); renderer.copyGrayscaleLsbBuffers();
bitmap.rewindToData(); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, cropX, cropY); renderer.copyGrayscaleMsbBuffers();
renderer.displayGrayBuffer(); renderer.setRenderMode(GfxRenderer::BW); }}
void SleepActivity::renderCoverSleepScreen() const { void (SleepActivity::*renderNoCoverSleepScreen)() const; switch (SETTINGS.sleepScreen) { case (CrossPointSettings::SLEEP_SCREEN_MODE::COVER_CUSTOM): renderNoCoverSleepScreen = &SleepActivity::renderCustomSleepScreen; break; default: renderNoCoverSleepScreen = &SleepActivity::renderDefaultSleepScreen; break; }
if (APP_STATE.openEpubPath.empty()) { return (this->*renderNoCoverSleepScreen)(); }
std::string coverBmpPath; bool cropped = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP;
// Check if the current book is XTC, TXT, or EPUB if (FsHelpers::hasXtcExtension(APP_STATE.openEpubPath)) { // Handle XTC file Xtc lastXtc(APP_STATE.openEpubPath, "/.crosspoint"); if (!lastXtc.load()) { LOG_ERR("SLP", "Failed to load last XTC"); return (this->*renderNoCoverSleepScreen)(); }
if (!lastXtc.generateCoverBmp()) { LOG_ERR("SLP", "Failed to generate XTC cover bmp"); return (this->*renderNoCoverSleepScreen)(); }
coverBmpPath = lastXtc.getCoverBmpPath(); } else if (FsHelpers::hasTxtExtension(APP_STATE.openEpubPath)) { // Handle TXT file - looks for cover image in the same folder Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint"); if (!lastTxt.load()) { LOG_ERR("SLP", "Failed to load last TXT"); return (this->*renderNoCoverSleepScreen)(); }
if (!lastTxt.generateCoverBmp()) { LOG_ERR("SLP", "No cover image found for TXT file"); return (this->*renderNoCoverSleepScreen)(); }
coverBmpPath = lastTxt.getCoverBmpPath(); } else if (FsHelpers::hasEpubExtension(APP_STATE.openEpubPath)) { // Handle EPUB file Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint"); // Skip loading css since we only need metadata here if (!lastEpub.load(true, true)) { LOG_ERR("SLP", "Failed to load last epub"); return (this->*renderNoCoverSleepScreen)(); }
if (!lastEpub.generateCoverBmp(cropped)) { LOG_ERR("SLP", "Failed to generate cover bmp"); return (this->*renderNoCoverSleepScreen)(); }
coverBmpPath = lastEpub.getCoverBmpPath(cropped); } else { return (this->*renderNoCoverSleepScreen)(); }
FsFile file; if (Storage.openFileForRead("SLP", coverBmpPath, file)) { Bitmap bitmap(file); if (bitmap.parseHeaders() == BmpReaderError::Ok) { LOG_DBG("SLP", "Rendering sleep cover: %s", coverBmpPath.c_str()); renderBitmapSleepScreen(bitmap); return; } }
return (this->*renderNoCoverSleepScreen)();}
void SleepActivity::renderBlankSleepScreen() const { renderer.clearScreen(); renderer.displayBuffer(HalDisplay::HALF_REFRESH);}