Files
crosspoint-reader-mod/src/components/UITheme.cpp
jpirnay 3c1bd77813 fix: prevent UITheme memory leak on theme reload (#975)
## Summary

- `UITheme::currentTheme` was a raw owning pointer with no destructor,
  causing a heap leak every time `setTheme()` was called (e.g. on
  theme change via settings reload)

## Additional Context

- Replaced `const BaseTheme*` with `std::unique_ptr<BaseTheme>` so the
  previous theme object is automatically deleted on reassignment
- Added `<memory>` include to `UITheme.h`; allocations updated to
  `std::make_unique<>` in `UITheme.cpp`

---

### 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**_ (identified by
claude though)

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-19 22:13:12 +11:00

93 lines
2.9 KiB
C++

#include "UITheme.h"
#include <GfxRenderer.h>
#include <Logging.h>
#include <memory>
#include "MappedInputManager.h"
#include "RecentBooksStore.h"
#include "components/themes/BaseTheme.h"
#include "components/themes/lyra/Lyra3CoversTheme.h"
#include "components/themes/lyra/LyraTheme.h"
#include "util/StringUtils.h"
namespace {
constexpr int SKIP_PAGE_MS = 700;
} // namespace
UITheme UITheme::instance;
UITheme::UITheme() {
auto themeType = static_cast<CrossPointSettings::UI_THEME>(SETTINGS.uiTheme);
setTheme(themeType);
}
void UITheme::reload() {
auto themeType = static_cast<CrossPointSettings::UI_THEME>(SETTINGS.uiTheme);
setTheme(themeType);
}
void UITheme::setTheme(CrossPointSettings::UI_THEME type) {
switch (type) {
case CrossPointSettings::UI_THEME::CLASSIC:
LOG_DBG("UI", "Using Classic theme");
currentTheme = std::make_unique<BaseTheme>();
currentMetrics = &BaseMetrics::values;
break;
case CrossPointSettings::UI_THEME::LYRA:
LOG_DBG("UI", "Using Lyra theme");
currentTheme = std::make_unique<LyraTheme>();
currentMetrics = &LyraMetrics::values;
break;
case CrossPointSettings::UI_THEME::LYRA_3_COVERS:
LOG_DBG("UI", "Using Lyra 3 Covers theme");
currentTheme = std::make_unique<Lyra3CoversTheme>();
currentMetrics = &Lyra3CoversMetrics::values;
break;
}
}
int UITheme::getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints,
bool hasSubtitle) {
const ThemeMetrics& metrics = UITheme::getInstance().getMetrics();
int reservedHeight = metrics.topPadding;
if (hasHeader) {
reservedHeight += metrics.headerHeight + metrics.verticalSpacing;
}
if (hasTabBar) {
reservedHeight += metrics.tabBarHeight;
}
if (hasButtonHints) {
reservedHeight += metrics.verticalSpacing + metrics.buttonHintsHeight;
}
const int availableHeight = renderer.getScreenHeight() - reservedHeight;
int rowHeight = hasSubtitle ? metrics.listWithSubtitleRowHeight : metrics.listRowHeight;
return availableHeight / rowHeight;
}
std::string UITheme::getCoverThumbPath(std::string coverBmpPath, int coverHeight) {
size_t pos = coverBmpPath.find("[HEIGHT]", 0);
if (pos != std::string::npos) {
coverBmpPath.replace(pos, 8, std::to_string(coverHeight));
}
return coverBmpPath;
}
UIIcon UITheme::getFileIcon(std::string filename) {
if (filename.back() == '/') {
return Folder;
}
if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") ||
StringUtils::checkFileExtension(filename, ".xtc")) {
return Book;
}
if (StringUtils::checkFileExtension(filename, ".txt") || StringUtils::checkFileExtension(filename, ".md")) {
return Text;
}
if (StringUtils::checkFileExtension(filename, ".bmp")) {
return Image;
}
return File;
}