feat: Added BmpViewer activity for viewing .bmp images in file browser (#887)
## Summary * **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.) Implements new feature for viewing .bmp files directly from the "Browse Files" menu. * **What changes are included?** You can now view .bmp files when browsing. You can click the select button to open the file, and then click back to close it and continue browsing in the same location. Once open a file will display on the screen with no additional options to interact outside of exiting with the back button. The attached video shows this feature in action: https://github.com/user-attachments/assets/9659b6da-abf7-4458-b158-e11c248c8bef ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). The changes implemented in #884 are also present here as this feature is actually what led to me noticing this issue. I figured I would add that PR as a separate request in case that one could be more easily merged given this feature is significantly more complicated and will likely be subject to more intense review. --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? **YES** --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Epub> 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> 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> 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();
|
||||
|
||||
@@ -18,12 +18,14 @@ class ReaderActivity final : public ActivityWithSubactivity {
|
||||
static std::unique_ptr<Txt> 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> epub);
|
||||
void onGoToXtcReader(std::unique_ptr<Xtc> xtc);
|
||||
void onGoToTxtReader(std::unique_ptr<Txt> txt);
|
||||
void onGoToBmpViewer(const std::string& path);
|
||||
|
||||
public:
|
||||
explicit ReaderActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string initialBookPath,
|
||||
|
||||
101
src/activities/util/BmpViewerActivity.cpp
Normal file
101
src/activities/util/BmpViewerActivity.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "BmpViewerActivity.h"
|
||||
|
||||
#include <Bitmap.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <HalStorage.h>
|
||||
#include <I18n.h>
|
||||
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
BmpViewerActivity::BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path,
|
||||
std::function<void()> 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<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
||||
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
|
||||
|
||||
if (ratio > screenRatio) {
|
||||
// Wider than screen
|
||||
x = 0;
|
||||
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
|
||||
} else {
|
||||
// Taller than screen
|
||||
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(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;
|
||||
}
|
||||
}
|
||||
21
src/activities/util/BmpViewerActivity.h
Normal file
21
src/activities/util/BmpViewerActivity.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include "../Activity.h"
|
||||
#include "MappedInputManager.h"
|
||||
|
||||
class BmpViewerActivity final : public Activity {
|
||||
public:
|
||||
BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string filePath,
|
||||
std::function<void()> onGoBack);
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
|
||||
private:
|
||||
std::string filePath;
|
||||
std::function<void()> onGoBack;
|
||||
};
|
||||
Reference in New Issue
Block a user