diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index 8b44d7d1..1793e1b8 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -92,7 +92,7 @@ void MyLibraryActivity::loadFiles() { auto filename = std::string(name); if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") || StringUtils::checkFileExtension(filename, ".xtc") || StringUtils::checkFileExtension(filename, ".txt") || - StringUtils::checkFileExtension(filename, ".md")) { + StringUtils::checkFileExtension(filename, ".md") || StringUtils::checkFileExtension(filename, ".bmp")) { files.emplace_back(filename); } } diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp index f2f9199a..9c191f15 100644 --- a/src/activities/reader/ReaderActivity.cpp +++ b/src/activities/reader/ReaderActivity.cpp @@ -9,6 +9,7 @@ #include "TxtReaderActivity.h" #include "Xtc.h" #include "XtcReaderActivity.h" +#include "activities/util/BmpViewerActivity.h" #include "activities/util/FullScreenMessageActivity.h" #include "util/StringUtils.h" @@ -29,6 +30,8 @@ bool ReaderActivity::isTxtFile(const std::string& path) { StringUtils::checkFileExtension(path, ".md"); // Treat .md as txt files (until we have a markdown reader) } +bool ReaderActivity::isBmpFile(const std::string& path) { return StringUtils::checkFileExtension(path, ".bmp"); } + std::unique_ptr ReaderActivity::loadEpub(const std::string& path) { if (!Storage.exists(path.c_str())) { LOG_ERR("READER", "File does not exist: %s", path.c_str()); @@ -88,6 +91,11 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) { renderer, mappedInput, std::move(epub), [this, epubPath] { goToLibrary(epubPath); }, [this] { onGoBack(); })); } +void ReaderActivity::onGoToBmpViewer(const std::string& path) { + exitActivity(); + enterNewActivity(new BmpViewerActivity(renderer, mappedInput, path, [this, path] { goToLibrary(path); })); +} + void ReaderActivity::onGoToXtcReader(std::unique_ptr xtc) { const auto xtcPath = xtc->getPath(); currentBookPath = xtcPath; @@ -113,8 +121,9 @@ void ReaderActivity::onEnter() { } currentBookPath = initialBookPath; - - if (isXtcFile(initialBookPath)) { + if (isBmpFile(initialBookPath)) { + onGoToBmpViewer(initialBookPath); + } else if (isXtcFile(initialBookPath)) { auto xtc = loadXtc(initialBookPath); if (!xtc) { onGoBack(); diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h index 5a2c1012..52a3e1d1 100644 --- a/src/activities/reader/ReaderActivity.h +++ b/src/activities/reader/ReaderActivity.h @@ -18,12 +18,14 @@ class ReaderActivity final : public ActivityWithSubactivity { static std::unique_ptr loadTxt(const std::string& path); static bool isXtcFile(const std::string& path); static bool isTxtFile(const std::string& path); + static bool isBmpFile(const std::string& path); static std::string extractFolderPath(const std::string& filePath); void goToLibrary(const std::string& fromBookPath = ""); void onGoToEpubReader(std::unique_ptr epub); void onGoToXtcReader(std::unique_ptr xtc); void onGoToTxtReader(std::unique_ptr txt); + void onGoToBmpViewer(const std::string& path); public: explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath, diff --git a/src/activities/util/BmpViewerActivity.cpp b/src/activities/util/BmpViewerActivity.cpp new file mode 100644 index 00000000..46dec3c7 --- /dev/null +++ b/src/activities/util/BmpViewerActivity.cpp @@ -0,0 +1,101 @@ +#include "BmpViewerActivity.h" + +#include +#include +#include +#include + +#include "components/UITheme.h" +#include "fontIds.h" + +BmpViewerActivity::BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path, + std::function onGoBack) + : Activity("BmpViewer", renderer, mappedInput), filePath(std::move(path)), onGoBack(std::move(onGoBack)) {} + +void BmpViewerActivity::onEnter() { + Activity::onEnter(); + // Removed the redundant initial renderer.clearScreen() + + FsFile file; + + const auto pageWidth = renderer.getScreenWidth(); + const auto pageHeight = renderer.getScreenHeight(); + Rect popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP)); + GUI.fillPopupProgress(renderer, popupRect, 20); // Initial 20% progress + // 1. Open the file + if (Storage.openFileForRead("BMP", filePath, file)) { + Bitmap bitmap(file, true); + + // 2. Parse headers to get dimensions + if (bitmap.parseHeaders() == BmpReaderError::Ok) { + int x, y; + + if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) { + float ratio = static_cast(bitmap.getWidth()) / static_cast(bitmap.getHeight()); + const float screenRatio = static_cast(pageWidth) / static_cast(pageHeight); + + if (ratio > screenRatio) { + // Wider than screen + x = 0; + y = std::round((static_cast(pageHeight) - static_cast(pageWidth) / ratio) / 2); + } else { + // Taller than screen + x = std::round((static_cast(pageWidth) - static_cast(pageHeight) * ratio) / 2); + y = 0; + } + } else { + // Center small images + x = (pageWidth - bitmap.getWidth()) / 2; + y = (pageHeight - bitmap.getHeight()) / 2; + } + + // 4. Prepare Rendering + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.fillPopupProgress(renderer, popupRect, 50); + + renderer.clearScreen(); + // Assuming drawBitmap defaults to 0,0 crop if omitted, or pass explicitly: drawBitmap(bitmap, x, y, pageWidth, + // pageHeight, 0, 0) + renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, 0, 0); + + // Draw UI hints on the base layer + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + // Single pass for non-grayscale images + + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + + } else { + // Handle file parsing error + renderer.clearScreen(); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Invalid BMP File"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(HalDisplay::FAST_REFRESH); + } + + file.close(); + } else { + // Handle file open error + renderer.clearScreen(); + renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Could not open file"); + const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); + GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); + renderer.displayBuffer(HalDisplay::FULL_REFRESH); + } +} + +void BmpViewerActivity::onExit() { + Activity::onExit(); + renderer.clearScreen(); + renderer.displayBuffer(HalDisplay::FAST_REFRESH); +} + +void BmpViewerActivity::loop() { + // Keep CPU awake/polling so 1st click works + Activity::loop(); + + if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { + if (onGoBack) onGoBack(); + return; + } +} \ No newline at end of file diff --git a/src/activities/util/BmpViewerActivity.h b/src/activities/util/BmpViewerActivity.h new file mode 100644 index 00000000..c3fba379 --- /dev/null +++ b/src/activities/util/BmpViewerActivity.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +#include "../Activity.h" +#include "MappedInputManager.h" + +class BmpViewerActivity final : public Activity { + public: + BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string filePath, + std::function onGoBack); + + void onEnter() override; + void onExit() override; + void loop() override; + + private: + std::string filePath; + std::function onGoBack; +}; \ No newline at end of file diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index a291b7c4..3f0898c4 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -355,7 +355,7 @@ void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c const int x = buttonPositions[i]; if (labels[i] != nullptr && labels[i][0] != '\0') { // Draw the filled background and border for a FULL-sized button - renderer.fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false); + renderer.fillRoundedRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, cornerRadius, Color::White); renderer.drawRoundedRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, 1, cornerRadius, true, true, false, false, true); const int textWidth = renderer.getTextWidth(SMALL_FONT_ID, labels[i]); @@ -363,7 +363,8 @@ void LyraTheme::drawButtonHints(GfxRenderer& renderer, const char* btn1, const c renderer.drawText(SMALL_FONT_ID, textX, pageHeight - buttonY + textYOffset, labels[i]); } else { // Draw the filled background and border for a SMALL-sized button - renderer.fillRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, false); + renderer.fillRoundedRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, cornerRadius, + Color::White); renderer.drawRoundedRect(x, pageHeight - smallButtonHeight, buttonWidth, smallButtonHeight, 1, cornerRadius, true, true, false, false, true); }