#include "DirectoryPickerActivity.h" #include #include #include #include "MappedInputManager.h" #include "components/UITheme.h" #include "fontIds.h" #include "util/StringUtils.h" void DirectoryPickerActivity::onEnter() { Activity::onEnter(); basepath = initialPath; if (basepath.empty()) basepath = "/"; // Validate the initial path exists; fall back to root if not auto dir = Storage.open(basepath.c_str()); if (!dir || !dir.isDirectory()) { if (dir) dir.close(); basepath = "/"; } else { dir.close(); } selectorIndex = 0; loadDirectories(); requestUpdate(); } void DirectoryPickerActivity::onExit() { directories.clear(); Activity::onExit(); } void DirectoryPickerActivity::loadDirectories() { directories.clear(); auto root = Storage.open(basepath.c_str()); if (!root || !root.isDirectory()) { if (root) root.close(); return; } root.rewindDirectory(); char name[256]; 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()) { directories.emplace_back(std::string(name) + "/"); } file.close(); } root.close(); StringUtils::sortFileList(directories); } void DirectoryPickerActivity::loop() { // Absorb the Confirm release from the parent activity that launched us if (waitForConfirmRelease) { if (!mappedInput.isPressed(MappedInputManager::Button::Confirm)) { waitForConfirmRelease = false; } return; } // Index 0 = "Save Here", indices 1..N = directory entries const int totalItems = 1 + static_cast(directories.size()); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { if (selectorIndex == 0) { onSelect(basepath); } else { const auto& dirName = directories[selectorIndex - 1]; // Strip trailing '/' std::string folderName = dirName.substr(0, dirName.length() - 1); basepath = (basepath.back() == '/' ? basepath : basepath + "/") + folderName; selectorIndex = 0; loadDirectories(); requestUpdate(); } return; } if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { if (basepath == "/") { onCancel(); } else { auto slash = basepath.find_last_of('/'); basepath = (slash == 0) ? "/" : basepath.substr(0, slash); selectorIndex = 0; loadDirectories(); requestUpdate(); } return; } buttonNavigator.onNextRelease([this, totalItems] { selectorIndex = ButtonNavigator::nextIndex(selectorIndex, totalItems); requestUpdate(); }); buttonNavigator.onPreviousRelease([this, totalItems] { selectorIndex = ButtonNavigator::previousIndex(selectorIndex, totalItems); requestUpdate(); }); const int pageItems = UITheme::getInstance().getNumberOfItemsPerPage(renderer, true, false, true, false); buttonNavigator.onNextContinuous([this, totalItems, pageItems] { selectorIndex = ButtonNavigator::nextPageIndex(selectorIndex, totalItems, pageItems); requestUpdate(); }); buttonNavigator.onPreviousContinuous([this, totalItems, pageItems] { selectorIndex = ButtonNavigator::previousPageIndex(selectorIndex, totalItems, pageItems); requestUpdate(); }); } void DirectoryPickerActivity::render(Activity::RenderLock&&) { renderer.clearScreen(); const auto& metrics = UITheme::getInstance().getMetrics(); const auto pageWidth = renderer.getScreenWidth(); const auto pageHeight = renderer.getScreenHeight(); std::string folderName = (basepath == "/") ? tr(STR_SD_CARD) : basepath.substr(basepath.rfind('/') + 1); GUI.drawHeader(renderer, Rect{0, metrics.topPadding, pageWidth, metrics.headerHeight}, tr(STR_SELECT_FOLDER)); const int contentTop = metrics.topPadding + metrics.headerHeight + metrics.verticalSpacing; const int contentHeight = pageHeight - contentTop - metrics.buttonHintsHeight - metrics.verticalSpacing; const int totalItems = 1 + static_cast(directories.size()); GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, totalItems, selectorIndex, [this](int index) -> std::string { if (index == 0) { std::string label = std::string(tr(STR_SAVE_HERE)) + " (" + basepath + ")"; return label; } // Strip trailing '/' for display const auto& dir = directories[index - 1]; return dir.substr(0, dir.length() - 1); }, nullptr, [this](int index) -> UIIcon { return (index == 0) ? UIIcon::File : UIIcon::Folder; }); const char* backLabel = (basepath == "/") ? tr(STR_CANCEL) : tr(STR_BACK); const char* confirmLabel = (selectorIndex == 0) ? tr(STR_SAVE_HERE) : tr(STR_OPEN); const auto labels = mappedInput.mapLabels(backLabel, confirmLabel, tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer(); }