A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: show full path bar in file browser (#1411)

![obraz](https://github.com/user-attachments/assets/f848a381-7c45-4724-912b-56168791bcf3)


## Summary

Adds a full path display at the bottom of the file browser with a
separator line matching the header style. Path uses the small font,
left-truncates to always show the deepest folder when path is too long.
Toggleable via Settings > System > Show Full Path (default on).

### 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? _**< PARTIALLY >**_

---------

Co-authored-by: Patryk Radtke <patryk@Patryks-MacBook-Pro.local>

authored by

zgredex
Patryk Radtke
and committed by
GitHub
4e9c7a78 cc23aaa9

+37 -6
+34 -3
src/activities/home/FileBrowserActivity.cpp
··· 164 164 return; 165 165 } 166 166 167 - const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); 167 + const int pathReserved = renderer.getLineHeight(SMALL_FONT_ID) + UITheme::getInstance().getMetrics().verticalSpacing; 168 + const int pageItems = UITheme::getNumberOfItemsPerPage(renderer, true, false, true, false, pathReserved); 168 169 169 170 if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { 170 171 if (files.empty()) return; ··· 294 295 std::string folderName = (basepath == "/") ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1); 295 296 GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, folderName.c_str()); 296 297 298 + const int pathLineHeight = renderer.getLineHeight(SMALL_FONT_ID); 299 + const int pathReserved = pathLineHeight + metrics.verticalSpacing; 297 300 const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; 298 - const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing; 301 + const int contentHeight = 302 + pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing - pathReserved; 299 303 if (files.empty()) { 300 304 renderer.drawText(UI_10_FONT_ID, metrics.contentSidePadding, contentTop + 20, tr(STR_NO_FILES_FOUND)); 301 305 } else { ··· 306 310 [this](int index) { return getFileExtension(files[index]); }, false); 307 311 } 308 312 313 + // Full path display 314 + { 315 + const int pathY = pageHeight - metrics.buttonHintsHeight - metrics.verticalSpacing - pathLineHeight; 316 + const int separatorY = pathY - metrics.verticalSpacing / 2; 317 + renderer.drawLine(0, separatorY, pageWidth - 1, separatorY, 3, true); 318 + const int pathMaxWidth = pageWidth - metrics.contentSidePadding * 2; 319 + // Left-truncate so the deepest directory is always visible 320 + const char* pathStr = basepath.c_str(); 321 + const char* pathDisplay = pathStr; 322 + char leftTruncBuf[256]; 323 + if (renderer.getTextWidth(SMALL_FONT_ID, pathStr) > pathMaxWidth) { 324 + const char ellipsis[] = "\xe2\x80\xa6"; // UTF-8 ellipsis (…) 325 + const int ellipsisWidth = renderer.getTextWidth(SMALL_FONT_ID, ellipsis); 326 + const int available = pathMaxWidth - ellipsisWidth; 327 + // Walk forward from the start until the suffix fits, skipping UTF-8 continuation bytes 328 + const char* p = pathStr; 329 + while (*p) { 330 + if (renderer.getTextWidth(SMALL_FONT_ID, p) <= available) break; 331 + ++p; 332 + while (*p && (static_cast<unsigned char>(*p) & 0xC0) == 0x80) ++p; 333 + } 334 + snprintf(leftTruncBuf, sizeof(leftTruncBuf), "%s%s", ellipsis, p); 335 + pathDisplay = leftTruncBuf; 336 + } 337 + renderer.drawText(SMALL_FONT_ID, metrics.contentSidePadding, pathY, pathDisplay); 338 + } 339 + 309 340 // Help text 310 341 const auto labels = 311 342 mappedInput.mapLabels(basepath == "/" ? tr(STR_HOME) : tr(STR_BACK), files.empty() ? "" : tr(STR_OPEN), ··· 319 350 for (size_t i = 0; i < files.size(); i++) 320 351 if (files[i] == name) return i; 321 352 return 0; 322 - } 353 + }
+2 -2
src/components/UITheme.cpp
··· 49 49 } 50 50 51 51 int UITheme::getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints, 52 - bool hasSubtitle) { 52 + bool hasSubtitle, int extraReservedHeight) { 53 53 const ThemeMetrics& metrics = UITheme::getInstance().getMetrics(); 54 54 int reservedHeight = metrics.topPadding; 55 55 if (hasHeader) { ··· 61 61 if (hasButtonHints) { 62 62 reservedHeight += metrics.verticalSpacing + metrics.buttonHintsHeight; 63 63 } 64 - const int availableHeight = renderer.getScreenHeight() - reservedHeight; 64 + const int availableHeight = renderer.getScreenHeight() - reservedHeight - extraReservedHeight; 65 65 int rowHeight = hasSubtitle ? metrics.listWithSubtitleRowHeight : metrics.listRowHeight; 66 66 return availableHeight / rowHeight; 67 67 }
+1 -1
src/components/UITheme.h
··· 19 19 void reload(); 20 20 void setTheme(CrossPointSettings::UI_THEME type); 21 21 static int getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints, 22 - bool hasSubtitle); 22 + bool hasSubtitle, int extraReservedHeight = 0); 23 23 static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight); 24 24 static UIIcon getFileIcon(const std::string& filename); 25 25 static int getStatusBarHeight();