Files
Crosspoint/src/activities/home/MyLibraryActivity.cpp
T
Zach Nelson 410c70ab89 perf: UITheme::getMetrics const and const-ref usage (#1094)
## Summary

**What is the goal of this PR?**

Small cleanup to make getTheme and getMetrics methods on UITheme const.
They return const refs, so updated call sites to use `const auto&`.

Realistically this won't make much performance difference, but it better
conveys the nature of theme metrics being shared const state.

---

### 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? _**NO**_
2026-02-23 13:29:18 +01:00

232 lines
7.0 KiB
C++

#include "MyLibraryActivity.h"
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include <algorithm>
#include "MappedInputManager.h"
#include "components/UITheme.h"
#include "fontIds.h"
#include "util/StringUtils.h"
namespace {
constexpr unsigned long GO_HOME_MS = 1000;
} // namespace
void sortFileList(std::vector<std::string>& strs) {
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
// Directories first
bool isDir1 = str1.back() == '/';
bool isDir2 = str2.back() == '/';
if (isDir1 != isDir2) return isDir1;
// Start naive natural sort
const char* s1 = str1.c_str();
const char* s2 = str2.c_str();
// Iterate while both strings have characters
while (*s1 && *s2) {
// Check if both are at the start of a number
if (isdigit(*s1) && isdigit(*s2)) {
// Skip leading zeros and track them
const char* start1 = s1;
const char* start2 = s2;
while (*s1 == '0') s1++;
while (*s2 == '0') s2++;
// Count digits to compare lengths first
int len1 = 0, len2 = 0;
while (isdigit(s1[len1])) len1++;
while (isdigit(s2[len2])) len2++;
// Different length so return smaller integer value
if (len1 != len2) return len1 < len2;
// Same length so compare digit by digit
for (int i = 0; i < len1; i++) {
if (s1[i] != s2[i]) return s1[i] < s2[i];
}
// Numbers equal so advance pointers
s1 += len1;
s2 += len2;
} else {
// Regular case-insensitive character comparison
char c1 = tolower(*s1);
char c2 = tolower(*s2);
if (c1 != c2) return c1 < c2;
s1++;
s2++;
}
}
// One string is prefix of other
return *s1 == '\0' && *s2 != '\0';
});
}
void MyLibraryActivity::loadFiles() {
files.clear();
auto root = Storage.open(basepath.c_str());
if (!root || !root.isDirectory()) {
if (root) root.close();
return;
}
root.rewindDirectory();
char name[500];
for (auto file = root.openNextFile(); file; file = root.openNextFile()) {
file.getName(name, sizeof(name));
if (name[0] == '.' || strcmp(name, "System Volume Information") == 0) {
file.close();
continue;
}
if (file.isDirectory()) {
files.emplace_back(std::string(name) + "/");
} else {
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, ".bmp")) {
files.emplace_back(filename);
}
}
file.close();
}
root.close();
sortFileList(files);
}
void MyLibraryActivity::onEnter() {
Activity::onEnter();
loadFiles();
selectorIndex = 0;
requestUpdate();
}
void MyLibraryActivity::onExit() {
Activity::onExit();
files.clear();
}
void MyLibraryActivity::loop() {
// Long press BACK (1s+) goes to root folder
if (mappedInput.isPressed(MappedInputManager::Button::Back) && mappedInput.getHeldTime() >= GO_HOME_MS &&
basepath != "/") {
basepath = "/";
loadFiles();
selectorIndex = 0;
return;
}
const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false);
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
if (files.empty()) {
return;
}
if (basepath.back() != '/') basepath += "/";
if (files[selectorIndex].back() == '/') {
basepath += files[selectorIndex].substr(0, files[selectorIndex].length() - 1);
loadFiles();
selectorIndex = 0;
requestUpdate();
} else {
onSelectBook(basepath + files[selectorIndex]);
return;
}
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
// Short press: go up one directory, or go home if at root
if (mappedInput.getHeldTime() < GO_HOME_MS) {
if (basepath != "/") {
const std::string oldPath = basepath;
basepath.replace(basepath.find_last_of('/'), std::string::npos, "");
if (basepath.empty()) basepath = "/";
loadFiles();
const auto pos = oldPath.find_last_of('/');
const std::string dirName = oldPath.substr(pos + 1) + "/";
selectorIndex = findEntry(dirName);
requestUpdate();
} else {
onGoHome();
}
}
}
int listSize = static_cast<int>(files.size());
buttonNavigator.onNextRelease([this, listSize] {
selectorIndex = ButtonNavigator::nextIndex(static_cast<int>(selectorIndex), listSize);
requestUpdate();
});
buttonNavigator.onPreviousRelease([this, listSize] {
selectorIndex = ButtonNavigator::previousIndex(static_cast<int>(selectorIndex), listSize);
requestUpdate();
});
buttonNavigator.onNextContinuous([this, listSize, pageItems] {
selectorIndex = ButtonNavigator::nextPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
requestUpdate();
});
buttonNavigator.onPreviousContinuous([this, listSize, pageItems] {
selectorIndex = ButtonNavigator::previousPageIndex(static_cast<int>(selectorIndex), listSize, pageItems);
requestUpdate();
});
}
std::string getFileName(std::string filename) {
if (filename.back() == '/') {
return filename.substr(0, filename.length() - 1);
}
const auto pos = filename.rfind('.');
return filename.substr(0, pos);
}
void MyLibraryActivity::render(Activity::RenderLock&&) {
renderer.clearScreen();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
const auto& metrics = UITheme::getInstance().getMetrics();
std::string folderName = (basepath == "/") ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1);
GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, folderName.c_str());
const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing;
const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing;
if (files.empty()) {
renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_BOOKS_FOUND));
} else {
GUI.drawList(
renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex,
[this](int index) { return getFileName(files[index]); }, nullptr,
[this](int index) { return UITheme::getFileIcon(files[index]); });
}
// Help text
const auto labels = mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), tr(STR_OPEN), tr(STR_DIR_UP),
tr(STR_DIR_DOWN));
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer();
}
size_t MyLibraryActivity::findEntry(const std::string& name) const {
for (size_t i = 0; i < files.size(); i++)
if (files[i] == name) return i;
return 0;
}