mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 23:57:39 +03:00
Add view modes
This commit is contained in:
parent
ac1ffd3d2c
commit
df99807bb0
@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance;
|
|||||||
namespace {
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
// Increment this when adding new persisted settings fields
|
// Increment this when adding new persisted settings fields
|
||||||
constexpr uint8_t SETTINGS_COUNT = 21;
|
constexpr uint8_t SETTINGS_COUNT = 22;
|
||||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@ -50,6 +50,7 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, longPressChapterSkip);
|
serialization::writePod(outputFile, longPressChapterSkip);
|
||||||
serialization::writePod(outputFile, hyphenationEnabled);
|
serialization::writePod(outputFile, hyphenationEnabled);
|
||||||
serialization::writePod(outputFile, displayFileExtensions);
|
serialization::writePod(outputFile, displayFileExtensions);
|
||||||
|
serialization::writePod(outputFile, recentsViewMode);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||||
@ -123,6 +124,8 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, displayFileExtensions);
|
serialization::readPod(inputFile, displayFileExtensions);
|
||||||
if (++settingsRead >= fileSettingsCount) break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
serialization::readPod(inputFile, recentsViewMode);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
} while (false);
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
|
|||||||
@ -97,6 +97,9 @@ class CrossPointSettings {
|
|||||||
// Display file extensions in the file browser
|
// Display file extensions in the file browser
|
||||||
uint8_t displayFileExtensions = 0;
|
uint8_t displayFileExtensions = 0;
|
||||||
|
|
||||||
|
enum RECENTS_VIEW_MODE { FILE_LIST = 0, BOOK_DATA = 1, BOOK_COVER_LIST = 2, BOOK_COVER_GRID = 3 };
|
||||||
|
uint8_t recentsViewMode = BOOK_DATA;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
// Get singleton instance
|
// Get singleton instance
|
||||||
|
|||||||
@ -2,15 +2,17 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SDCardManager.h>
|
#include <SDCardManager.h>
|
||||||
|
#include <Bitmap.h>
|
||||||
|
#include <Epub.h>
|
||||||
|
#include <Xtc.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
#include "MappedInputManager.h"
|
#include "MappedInputManager.h"
|
||||||
#include "RecentBooksStore.h"
|
#include "RecentBooksStore.h"
|
||||||
#include "ScreenComponents.h"
|
#include "ScreenComponents.h"
|
||||||
#include "fontIds.h"
|
#include "fontIds.h"
|
||||||
#include "util/StringUtils.h"
|
#include "util/StringUtils.h"
|
||||||
#include "CrossPointSettings.h"
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
// Layout constants
|
// Layout constants
|
||||||
@ -40,7 +42,28 @@ int MyLibraryActivity::getPageItems() const {
|
|||||||
const int screenHeight = renderer.getScreenHeight();
|
const int screenHeight = renderer.getScreenHeight();
|
||||||
const int bottomBarHeight = 60; // Space for button hints
|
const int bottomBarHeight = 60; // Space for button hints
|
||||||
const int availableHeight = screenHeight - CONTENT_START_Y - bottomBarHeight;
|
const int availableHeight = screenHeight - CONTENT_START_Y - bottomBarHeight;
|
||||||
int items = availableHeight / LINE_HEIGHT;
|
|
||||||
|
int items = 1; // Default to at least 1
|
||||||
|
if (currentTab == Tab::Recent) {
|
||||||
|
switch (SETTINGS.recentsViewMode) {
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::FILE_LIST:
|
||||||
|
items = availableHeight / LINE_HEIGHT;
|
||||||
|
break;
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::BOOK_DATA:
|
||||||
|
items = availableHeight / RECENTS_LINE_HEIGHT;
|
||||||
|
break;
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::BOOK_COVER_LIST:
|
||||||
|
items = availableHeight / 140;
|
||||||
|
break;
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::BOOK_COVER_GRID:
|
||||||
|
// 3x3 grid, so 9 items per page
|
||||||
|
items = 9;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
items = availableHeight / LINE_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
if (items < 1) {
|
if (items < 1) {
|
||||||
items = 1;
|
items = 1;
|
||||||
}
|
}
|
||||||
@ -312,6 +335,60 @@ void MyLibraryActivity::render() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyLibraryActivity::renderRecentTab() const {
|
void MyLibraryActivity::renderRecentTab() const {
|
||||||
|
switch (SETTINGS.recentsViewMode) {
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::FILE_LIST:
|
||||||
|
renderRecentAsFileList();
|
||||||
|
break;
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::BOOK_DATA:
|
||||||
|
renderRecentAsBookData();
|
||||||
|
break;
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::BOOK_COVER_LIST:
|
||||||
|
renderRecentAsBookCoverList();
|
||||||
|
break;
|
||||||
|
case CrossPointSettings::RECENTS_VIEW_MODE::BOOK_COVER_GRID:
|
||||||
|
renderRecentAsBookCoverGrid();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyLibraryActivity::renderRecentAsFileList() const {
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
const int pageItems = getPageItems();
|
||||||
|
const int bookCount = static_cast<int>(recentBooks.size());
|
||||||
|
|
||||||
|
if (bookCount == 0) {
|
||||||
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||||
|
|
||||||
|
// Draw selection highlight
|
||||||
|
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * LINE_HEIGHT - 2, pageWidth - RIGHT_MARGIN,
|
||||||
|
LINE_HEIGHT);
|
||||||
|
|
||||||
|
// Draw items
|
||||||
|
for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) {
|
||||||
|
const auto& book = recentBooks[i];
|
||||||
|
std::string title = book.title;
|
||||||
|
if (title.empty()) {
|
||||||
|
// Fallback for older entries or files without metadata
|
||||||
|
title = book.path;
|
||||||
|
const size_t lastSlash = title.find_last_of('/');
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
title = title.substr(lastSlash + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (SETTINGS.displayFileExtensions == 0) {
|
||||||
|
title = StringUtils::stripFileExtension(title);
|
||||||
|
}
|
||||||
|
auto item = renderer.truncatedText(UI_10_FONT_ID, title.c_str(), pageWidth - LEFT_MARGIN - RIGHT_MARGIN);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y + (i % pageItems) * LINE_HEIGHT, item.c_str(),
|
||||||
|
i != selectorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyLibraryActivity::renderRecentAsBookData() const {
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
const int bookCount = static_cast<int>(recentBooks.size());
|
const int bookCount = static_cast<int>(recentBooks.size());
|
||||||
@ -358,6 +435,160 @@ void MyLibraryActivity::renderRecentTab() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MyLibraryActivity::renderRecentAsBookCoverList() const {
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
const int pageItems = getPageItems();
|
||||||
|
const int bookCount = static_cast<int>(recentBooks.size());
|
||||||
|
|
||||||
|
if (bookCount == 0) {
|
||||||
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||||
|
constexpr int itemHeight = 140;
|
||||||
|
constexpr int coverWidth = 100;
|
||||||
|
constexpr int textX = LEFT_MARGIN + coverWidth + 10;
|
||||||
|
const int textWidth = pageWidth - textX - RIGHT_MARGIN;
|
||||||
|
|
||||||
|
// Draw selection highlight
|
||||||
|
renderer.fillRect(0, CONTENT_START_Y + (selectorIndex % pageItems) * itemHeight - 2, pageWidth - RIGHT_MARGIN,
|
||||||
|
itemHeight);
|
||||||
|
|
||||||
|
// Draw items
|
||||||
|
for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) {
|
||||||
|
const auto& book = recentBooks[i];
|
||||||
|
const int y = CONTENT_START_Y + (i % pageItems) * itemHeight;
|
||||||
|
|
||||||
|
// --- Draw cover image ---
|
||||||
|
std::string coverBmpPath;
|
||||||
|
bool hasCoverImage = false;
|
||||||
|
|
||||||
|
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||||
|
Epub epub(book.path, "/.crosspoint");
|
||||||
|
if (epub.load(false) && epub.generateThumbBmp()) {
|
||||||
|
coverBmpPath = epub.getThumbBmpPath();
|
||||||
|
hasCoverImage = true;
|
||||||
|
}
|
||||||
|
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||||
|
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||||
|
Xtc xtc(book.path, "/.crosspoint");
|
||||||
|
if (xtc.load() && xtc.generateThumbBmp()) {
|
||||||
|
coverBmpPath = xtc.getThumbBmpPath();
|
||||||
|
hasCoverImage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCoverImage && !coverBmpPath.empty()) {
|
||||||
|
FsFile file;
|
||||||
|
if (SdMan.openFileForRead("MYLIB", coverBmpPath, file)) {
|
||||||
|
Bitmap bitmap(file);
|
||||||
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
|
renderer.drawBitmap(bitmap, LEFT_MARGIN, y, coverWidth, itemHeight - 10);
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Draw a placeholder if no cover
|
||||||
|
renderer.drawRect(LEFT_MARGIN, y, coverWidth, itemHeight - 10);
|
||||||
|
renderer.drawCenteredText(UI_10_FONT_ID, y + (itemHeight - 10) / 2 - 10, "No cover", false,
|
||||||
|
LEFT_MARGIN, coverWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Draw text ---
|
||||||
|
// Line 1: Title
|
||||||
|
std::string title = book.title;
|
||||||
|
if (title.empty()) {
|
||||||
|
title = book.path;
|
||||||
|
const size_t lastSlash = title.find_last_of('/');
|
||||||
|
if (lastSlash != std::string::npos) {
|
||||||
|
title = title.substr(lastSlash + 1);
|
||||||
|
}
|
||||||
|
const size_t dot = title.find_last_of('.');
|
||||||
|
if (dot != std::string::npos) {
|
||||||
|
title.resize(dot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto truncatedTitle = renderer.truncatedText(UI_12_FONT_ID, title.c_str(), textWidth);
|
||||||
|
renderer.drawText(UI_12_FONT_ID, textX, y + 20, truncatedTitle.c_str(), i != selectorIndex);
|
||||||
|
|
||||||
|
// Line 2: Author
|
||||||
|
if (!book.author.empty()) {
|
||||||
|
auto truncatedAuthor = renderer.truncatedText(UI_10_FONT_ID, book.author.c_str(), textWidth);
|
||||||
|
renderer.drawText(UI_10_FONT_ID, textX, y + 60, truncatedAuthor.c_str(), i != selectorIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyLibraryActivity::renderRecentAsBookCoverGrid() const {
|
||||||
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
|
const int pageItems = getPageItems();
|
||||||
|
const int bookCount = static_cast<int>(recentBooks.size());
|
||||||
|
|
||||||
|
if (bookCount == 0) {
|
||||||
|
renderer.drawText(UI_10_FONT_ID, LEFT_MARGIN, CONTENT_START_Y, "No recent books");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||||
|
|
||||||
|
constexpr int cols = 3;
|
||||||
|
const int gridMargin = 10;
|
||||||
|
const int itemWidth = (pageWidth - (cols + 1) * gridMargin) / cols;
|
||||||
|
const int itemHeight = (renderer.getScreenHeight() - CONTENT_START_Y - 60 - 2 * gridMargin) / 3;
|
||||||
|
|
||||||
|
// Draw items
|
||||||
|
for (int i = pageStartIndex; i < bookCount && i < pageStartIndex + pageItems; i++) {
|
||||||
|
const auto& book = recentBooks[i];
|
||||||
|
const int row = (i % pageItems) / cols;
|
||||||
|
const int col = (i % pageItems) % cols;
|
||||||
|
|
||||||
|
const int x = gridMargin + col * (itemWidth + gridMargin);
|
||||||
|
const int y = CONTENT_START_Y + row * (itemHeight + gridMargin);
|
||||||
|
|
||||||
|
// --- Draw cover image ---
|
||||||
|
std::string coverBmpPath;
|
||||||
|
bool hasCoverImage = false;
|
||||||
|
|
||||||
|
if (StringUtils::checkFileExtension(book.path, ".epub")) {
|
||||||
|
Epub epub(book.path, "/.crosspoint");
|
||||||
|
if (epub.load(false) && epub.generateThumbBmp()) {
|
||||||
|
coverBmpPath = epub.getThumbBmpPath();
|
||||||
|
hasCoverImage = true;
|
||||||
|
}
|
||||||
|
} else if (StringUtils::checkFileExtension(book.path, ".xtch") ||
|
||||||
|
StringUtils::checkFileExtension(book.path, ".xtc")) {
|
||||||
|
Xtc xtc(book.path, "/.crosspoint");
|
||||||
|
if (xtc.load() && xtc.generateThumbBmp()) {
|
||||||
|
coverBmpPath = xtc.getThumbBmpPath();
|
||||||
|
hasCoverImage = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasCoverImage && !coverBmpPath.empty()) {
|
||||||
|
FsFile file;
|
||||||
|
if (SdMan.openFileForRead("MYLIB", coverBmpPath, file)) {
|
||||||
|
Bitmap bitmap(file);
|
||||||
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
|
renderer.drawBitmap(bitmap, x, y, itemWidth, itemHeight);
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Draw a placeholder if no cover
|
||||||
|
renderer.drawRect(x, y, itemWidth, itemHeight);
|
||||||
|
renderer.drawCenteredText(UI_10_FONT_ID, y + itemHeight / 2 - 10, "No cover", false, x, itemWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Draw selection highlight ---
|
||||||
|
if (i == selectorIndex) {
|
||||||
|
renderer.drawRect(x - 2, y - 2, itemWidth + 4, itemHeight + 4);
|
||||||
|
renderer.drawRect(x - 3, y - 3, itemWidth + 6, itemHeight + 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void MyLibraryActivity::renderFilesTab() const {
|
void MyLibraryActivity::renderFilesTab() const {
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
const int pageItems = getPageItems();
|
const int pageItems = getPageItems();
|
||||||
|
|||||||
@ -49,6 +49,10 @@ class MyLibraryActivity final : public Activity {
|
|||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void render() const;
|
void render() const;
|
||||||
void renderRecentTab() const;
|
void renderRecentTab() const;
|
||||||
|
void renderRecentAsFileList() const;
|
||||||
|
void renderRecentAsBookData() const;
|
||||||
|
void renderRecentAsBookCoverList() const;
|
||||||
|
void renderRecentAsBookCoverGrid() const;
|
||||||
void renderFilesTab() const;
|
void renderFilesTab() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|||||||
@ -51,9 +51,11 @@ const SettingInfo systemSettings[systemSettingsCount] = {
|
|||||||
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
SettingInfo::Action("KOReader Sync"), SettingInfo::Action("Calibre Settings"), SettingInfo::Action("Clear Cache"),
|
||||||
SettingInfo::Action("Check for updates")};
|
SettingInfo::Action("Check for updates")};
|
||||||
|
|
||||||
constexpr int filesSettingsCount = 1;
|
constexpr int filesSettingsCount = 2;
|
||||||
const SettingInfo filesSettings[filesSettingsCount] = {
|
const SettingInfo filesSettings[filesSettingsCount] = {
|
||||||
SettingInfo::Toggle("Display File Extensions", &CrossPointSettings::displayFileExtensions)};
|
SettingInfo::Toggle("Display File Extensions", &CrossPointSettings::displayFileExtensions),
|
||||||
|
SettingInfo::Enum("Recents View Mode", &CrossPointSettings::recentsViewMode,
|
||||||
|
{"File List", "Book Data", "Book Cover List", "Book Cover Grid"})};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void SettingsActivity::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user