#pragma once #include #include #include #include #include #include #include #include #include #ifndef IMGUI_VERSION # error "include imgui.h before this header" #endif using ImGuiFileBrowserFlags = std::uint32_t; enum ImGuiFileBrowserFlags_ : std::uint32_t { ImGuiFileBrowserFlags_SelectDirectory = 1 << 0, // select directory instead of regular file ImGuiFileBrowserFlags_EnterNewFilename = 1 << 1, // allow user to enter new filename when selecting regular file ImGuiFileBrowserFlags_NoModal = 1 << 2, // file browsing window is modal by default. specify this to use a popup window ImGuiFileBrowserFlags_NoTitleBar = 1 << 3, // hide window title bar ImGuiFileBrowserFlags_NoStatusBar = 1 << 4, // hide status bar at the bottom of browsing window ImGuiFileBrowserFlags_CloseOnEsc = 1 << 5, // close file browser when pressing 'ESC' ImGuiFileBrowserFlags_CreateNewDir = 1 << 6, // allow user to create new directory ImGuiFileBrowserFlags_MultipleSelection = 1 << 7, // allow user to select multiple files. this will hide ImGuiFileBrowserFlags_EnterNewFilename ImGuiFileBrowserFlags_HideRegularFiles = 1 << 8, // hide regular files when ImGuiFileBrowserFlags_SelectDirectory is enabled ImGuiFileBrowserFlags_ConfirmOnEnter = 1 << 9, // confirm selection when pressing 'ENTER' ImGuiFileBrowserFlags_SkipItemsCausingError = 1 << 10, // when entering a new directory, any error will interrupt the process, causing the file browser to fall back to the working directory. // with this flag, if an error is caused by a specific item in the directory, that item will be skipped, allowing the process to continue. ImGuiFileBrowserFlags_EditPathString = 1 << 11, // allow user to directly edit the whole path string }; namespace ImGui { class FileBrowser { public: explicit FileBrowser( ImGuiFileBrowserFlags flags = 0, std::filesystem::path defaultDirectory = std::filesystem::current_path()); FileBrowser(const FileBrowser ©From); FileBrowser &operator=(const FileBrowser ©From); // set the window position (in pixels) // default is centered void SetWindowPos(int posX, int posY) noexcept; // set the window size (in pixels) // default is (700, 450) void SetWindowSize(int width, int height) noexcept; // set the window title text void SetTitle(std::string title); // open the browsing window void Open(); // close the browsing window void Close(); // the browsing window is opened or not bool IsOpened() const noexcept; // display the browsing window if opened void Display(); // returns true when there is a selected filename bool HasSelected() const noexcept; // set current browsing directory bool SetDirectory(const std::filesystem::path &dir = std::filesystem::current_path()); // legacy interface. use SetDirectory instead. bool SetPwd(const std::filesystem::path &dir = std::filesystem::current_path()) { return SetDirectory(dir); } // get current browsing directory const std::filesystem::path &GetDirectory() const noexcept; // legacy interface. use GetDirectory instead. const std::filesystem::path &GetPwd() const noexcept { return GetDirectory(); } // returns selected filename. make sense only when HasSelected returns true // when ImGuiFileBrowserFlags_MultipleSelection is enabled, only one of // selected filename will be returned std::filesystem::path GetSelected() const; // returns all selected filenames. // when ImGuiFileBrowserFlags_MultipleSelection is enabled, use this // instead of GetSelected std::vector GetMultiSelected() const; // set selected filename to empty void ClearSelected(); // (optional) set file type filters. eg. { ".h", ".cpp", ".hpp" } // ".*" matches any file types void SetTypeFilters(const std::vector &typeFilters); // set currently applied type filter // default value is 0 (the first type filter) void SetCurrentTypeFilterIndex(int index); // when ImGuiFileBrowserFlags_EnterNewFilename is set // this function will pre-fill the input dialog with a filename. void SetInputName(std::string_view input); private: template struct ScopeGuard { ScopeGuard(Functor&& t) : func(std::move(t)) { } ~ScopeGuard() { func(); } private: Functor func; }; struct FileRecord { bool isDir = false; std::filesystem::path name; std::string showName; std::filesystem::path extension; }; static std::string ToLower(const std::string &s); void ToolTip(const std::string_view &s); void UpdateFileRecords(); void SetCurrentDirectoryUncatched(const std::filesystem::path &pwd); bool SetCurrentDirectoryInternal( const std::filesystem::path &dir, const std::filesystem::path &preferredFallback); bool IsExtensionMatched(const std::filesystem::path &extension) const; void ClearRangeSelectionState(); static void AssignToArrayStyleString(std::vector &arr, std::string_view content); static int ExpandInputBuffer(ImGuiInputTextCallbackData *callbackData); #ifdef _WIN32 static std::uint32_t GetDrivesBitMask(); #endif // for c++17 compatibility #if defined(__cpp_lib_char8_t) static std::string u8StrToStr(std::u8string s); #endif static std::string u8StrToStr(std::string s); static std::filesystem::path u8StrToPath(const char *str); int width_; int height_; int posX_; int posY_; ImGuiFileBrowserFlags flags_; std::filesystem::path defaultDirectory_; std::string title_; std::string openLabel_; bool shouldOpen_; bool shouldClose_; bool isOpened_; bool isOk_; bool isPosSet_; std::string statusStr_; std::vector typeFilters_; unsigned int typeFilterIndex_; bool hasAllFilter_; std::filesystem::path currentDirectory_; std::vector fileRecords_; unsigned int rangeSelectionStart_; // enable range selection when shift is pressed std::set selectedFilenames_; std::string openNewDirLabel_; std::vector newDirNameBuffer_; std::vector inputNameBuffer_; std::string customizedInputName_; bool editDir_; bool setFocusToEditDir_; std::vector currDirBuffer_; #ifdef _WIN32 std::uint32_t drives_; #endif }; } // namespace ImGui inline ImGui::FileBrowser::FileBrowser(ImGuiFileBrowserFlags flags, std::filesystem::path defaultDirectory) : width_(700) , height_(450) , posX_(0) , posY_(0) , flags_(flags) , defaultDirectory_(std::move(defaultDirectory)) , shouldOpen_(false) , shouldClose_(false) , isOpened_(false) , isOk_(false) , isPosSet_(false) , rangeSelectionStart_(0) , editDir_(false) , setFocusToEditDir_(false) { assert(!((flags_ & ImGuiFileBrowserFlags_SelectDirectory) && (flags_ & ImGuiFileBrowserFlags_EnterNewFilename)) && "'EnterNewFilename' doesn't work when 'SelectDirectory' is enabled"); if(flags_ & ImGuiFileBrowserFlags_CreateNewDir) { newDirNameBuffer_.resize(32, '\0'); } SetTitle("file browser"); SetDirectory(defaultDirectory_); typeFilters_.clear(); typeFilterIndex_ = 0; hasAllFilter_ = false; #ifdef _WIN32 drives_ = GetDrivesBitMask(); #endif } inline ImGui::FileBrowser::FileBrowser(const FileBrowser ©From) : FileBrowser() { *this = copyFrom; } inline ImGui::FileBrowser &ImGui::FileBrowser::operator=( const FileBrowser ©From) { width_ = copyFrom.width_; height_ = copyFrom.height_; posX_ = copyFrom.posX_; posY_ = copyFrom.posY_; flags_ = copyFrom.flags_; SetTitle(copyFrom.title_); shouldOpen_ = copyFrom.shouldOpen_; shouldClose_ = copyFrom.shouldClose_; isOpened_ = copyFrom.isOpened_; isOk_ = copyFrom.isOk_; isPosSet_ = copyFrom.isPosSet_; statusStr_ = ""; typeFilters_ = copyFrom.typeFilters_; typeFilterIndex_ = copyFrom.typeFilterIndex_; hasAllFilter_ = copyFrom.hasAllFilter_; selectedFilenames_ = copyFrom.selectedFilenames_; rangeSelectionStart_ = copyFrom.rangeSelectionStart_; currentDirectory_ = copyFrom.currentDirectory_; fileRecords_ = copyFrom.fileRecords_; openNewDirLabel_ = copyFrom.openNewDirLabel_; newDirNameBuffer_ = copyFrom.newDirNameBuffer_; inputNameBuffer_ = copyFrom.inputNameBuffer_; customizedInputName_ = copyFrom.customizedInputName_; editDir_ = copyFrom.editDir_; currDirBuffer_ = copyFrom.currDirBuffer_; #ifdef _WIN32 drives_ = copyFrom.drives_; #endif return *this; } inline void ImGui::FileBrowser::SetWindowPos(int posX, int posY) noexcept { posX_ = posX; posY_ = posY; isPosSet_ = true; } inline void ImGui::FileBrowser::SetWindowSize(int width, int height) noexcept { assert(width > 0 && height > 0); width_ = width; height_ = height; } inline void ImGui::FileBrowser::SetTitle(std::string title) { title_ = std::move(title); const std::string thisPtrStr = std::to_string(reinterpret_cast(this)); openLabel_ = title_ + "##filebrowser_" + thisPtrStr; openNewDirLabel_ = "new dir##new_dir_" + thisPtrStr; } inline void ImGui::FileBrowser::Open() { UpdateFileRecords(); ClearSelected(); statusStr_ = std::string(); shouldOpen_ = true; shouldClose_ = false; if((flags_ & ImGuiFileBrowserFlags_EnterNewFilename) && !customizedInputName_.empty()) { AssignToArrayStyleString(inputNameBuffer_, customizedInputName_); selectedFilenames_ = { u8StrToPath(inputNameBuffer_.data()) }; } } inline void ImGui::FileBrowser::Close() { ClearSelected(); statusStr_ = std::string(); shouldClose_ = true; shouldOpen_ = false; } inline bool ImGui::FileBrowser::IsOpened() const noexcept { return isOpened_; } inline void ImGui::FileBrowser::Display() { PushID(this); ScopeGuard exitThis([this] { shouldOpen_ = false; shouldClose_ = false; PopID(); }); if(shouldOpen_) { OpenPopup(openLabel_.c_str()); } isOpened_ = false; // open the popup window if(shouldOpen_ && (flags_ & ImGuiFileBrowserFlags_NoModal)) { if(isPosSet_) { SetNextWindowPos(ImVec2(static_cast(posX_), static_cast(posY_))); } SetNextWindowSize(ImVec2(static_cast(width_), static_cast(height_))); } else { if(isPosSet_) { SetNextWindowPos(ImVec2(static_cast(posX_), static_cast(posY_)), ImGuiCond_FirstUseEver); } SetNextWindowSize(ImVec2(static_cast(width_), static_cast(height_)), ImGuiCond_FirstUseEver); } if(flags_ & ImGuiFileBrowserFlags_NoModal) { if(!BeginPopup(openLabel_.c_str())) { return; } } else if(!BeginPopupModal(openLabel_.c_str(), nullptr, flags_ & ImGuiFileBrowserFlags_NoTitleBar ? ImGuiWindowFlags_NoTitleBar : 0)) { return; } isOpened_ = true; ScopeGuard endPopup([] { EndPopup(); }); std::filesystem::path newDir; bool shouldSetNewDir = false; if(editDir_) { if(setFocusToEditDir_) // Automatically set the text box to be focused on appearing { SetKeyboardFocusHere(); } PushItemWidth(-1); const bool enter = InputText( "##directory", currDirBuffer_.data(), currDirBuffer_.size(), ImGuiInputTextFlags_CallbackResize | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll, ExpandInputBuffer, &currDirBuffer_); PopItemWidth(); if(!IsItemActive() && !setFocusToEditDir_) { editDir_ = false; } setFocusToEditDir_ = false; if(enter) { std::filesystem::path enteredDir = u8StrToPath(currDirBuffer_.data()); if(is_directory(enteredDir)) { newDir = std::move(enteredDir); shouldSetNewDir = true; } else if(is_directory(enteredDir.parent_path())) { newDir = enteredDir.parent_path(); shouldSetNewDir = true; } else { statusStr_ = "[" + std::string(currDirBuffer_.data()) + "] is not a valid directory"; } } } else { // display elements in pwd #ifdef _WIN32 const char currentDrive = static_cast(currentDirectory_.c_str()[0]); const char driveStr[] = { currentDrive, ':', '\0' }; PushItemWidth(4 * GetFontSize()); if(BeginCombo("##select_drive", driveStr)) { ScopeGuard guard([&] { EndCombo(); }); for(int i = 0; i < 26; ++i) { if(!(drives_ & (1 << i))) { continue; } const char driveCh = static_cast('A' + i); const char selectableStr[] = { driveCh, ':', '\0' }; const bool selected = currentDrive == driveCh; if(Selectable(selectableStr, selected) && !selected) { char newPwd[] = { driveCh, ':', '\\', '\0' }; SetDirectory(newPwd); } } } PopItemWidth(); SameLine(); #endif int secIdx = 0, newDirLastSecIdx = -1; for(const auto &sec : currentDirectory_) { #ifdef _WIN32 if(secIdx == 1) { ++secIdx; continue; } #endif PushID(secIdx); if(secIdx > 0) { SameLine(); } if(SmallButton(u8StrToStr(sec.u8string()).c_str())) { newDirLastSecIdx = secIdx; } PopID(); ++secIdx; } if(newDirLastSecIdx >= 0) { int i = 0; std::filesystem::path dstDir; for(const auto &sec : currentDirectory_) { if(i++ > newDirLastSecIdx) { break; } dstDir /= sec; } #ifdef _WIN32 if(newDirLastSecIdx == 0) { dstDir /= "\\"; } #endif SetDirectory(dstDir); } if(flags_ & ImGuiFileBrowserFlags_EditPathString) { SameLine(); if(SmallButton("#")) { const auto currDirStr = u8StrToStr(currentDirectory_.u8string()); currDirBuffer_.resize(currDirStr.size() + 1); std::memcpy(currDirBuffer_.data(), currDirStr.data(), currDirStr.size()); currDirBuffer_.back() = '\0'; editDir_ = true; setFocusToEditDir_ = true; } else { ToolTip("Edit the current path"); } } } SameLine(); if(SmallButton("*")) { UpdateFileRecords(); std::set newSelectedFilenames; for(auto &name : selectedFilenames_) { const auto it = std::find_if( fileRecords_.begin(), fileRecords_.end(), [&](const FileRecord &record) { return name == record.name; }); if(it != fileRecords_.end()) { newSelectedFilenames.insert(name); } } if((flags_ & ImGuiFileBrowserFlags_EnterNewFilename) && !inputNameBuffer_.empty() && inputNameBuffer_[0]) { newSelectedFilenames.insert(u8StrToPath(inputNameBuffer_.data())); } } else { ToolTip("Refresh"); } bool focusOnInputText = false; if(flags_ & ImGuiFileBrowserFlags_CreateNewDir) { SameLine(); if(SmallButton("+")) { OpenPopup(openNewDirLabel_.c_str()); newDirNameBuffer_[0] = '\0'; } else { ToolTip("Create a new directory"); } if(BeginPopup(openNewDirLabel_.c_str())) { ScopeGuard endNewDirPopup([] { EndPopup(); }); InputText( "name", newDirNameBuffer_.data(), newDirNameBuffer_.size(), ImGuiInputTextFlags_CallbackResize, ExpandInputBuffer, &newDirNameBuffer_); focusOnInputText |= IsItemFocused(); SameLine(); if(Button("ok") && newDirNameBuffer_[0] != '\0') { ScopeGuard closeNewDirPopup([] { CloseCurrentPopup(); }); if(create_directory(currentDirectory_ / u8StrToPath(newDirNameBuffer_.data()))) { UpdateFileRecords(); } else { statusStr_ = "failed to create " + std::string(newDirNameBuffer_.data()); } } } } // browse files in a child window float reserveHeight = GetFrameHeightWithSpacing(); if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename) { reserveHeight += GetFrameHeightWithSpacing(); } { BeginChild("ch", ImVec2(0, -reserveHeight), true, (flags_ & ImGuiFileBrowserFlags_NoModal) ? ImGuiWindowFlags_AlwaysHorizontalScrollbar : 0); ScopeGuard endChild([] { EndChild(); }); const bool shouldHideRegularFiles = (flags_ & ImGuiFileBrowserFlags_HideRegularFiles) && (flags_ & ImGuiFileBrowserFlags_SelectDirectory); for(unsigned int rscIndex = 0; rscIndex < fileRecords_.size(); ++rscIndex) { const auto &rsc = fileRecords_[rscIndex]; if(!rsc.isDir && shouldHideRegularFiles) { continue; } if(!rsc.isDir && !IsExtensionMatched(rsc.extension)) { continue; } if(!rsc.name.empty() && rsc.name.c_str()[0] == '$') { continue; } const bool selected = selectedFilenames_.find(rsc.name) != selectedFilenames_.end(); #if IMGUI_VERSION_NUM >= 19100 const ImGuiSelectableFlags selectableFlag = ImGuiSelectableFlags_NoAutoClosePopups; #else const ImGuiSelectableFlags selectableFlag = ImGuiSelectableFlags_DontClosePopups; #endif if(Selectable(rsc.showName.c_str(), selected, selectableFlag)) { const bool wantDir = flags_ & ImGuiFileBrowserFlags_SelectDirectory; const bool canSelect = rsc.name != ".." && rsc.isDir == wantDir; const bool rangeSelect = canSelect && GetIO().KeyShift && rangeSelectionStart_ < fileRecords_.size() && (flags_ & ImGuiFileBrowserFlags_MultipleSelection) && IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); const bool multiSelect = !rangeSelect && GetIO().KeyCtrl && (flags_ & ImGuiFileBrowserFlags_MultipleSelection) && IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows); if(rangeSelect) { const unsigned int first = (std::min)(rangeSelectionStart_, rscIndex); const unsigned int last = (std::max)(rangeSelectionStart_, rscIndex); selectedFilenames_.clear(); for(unsigned int i = first; i <= last; ++i) { if(fileRecords_[i].isDir != wantDir) { continue; } if(!wantDir && !IsExtensionMatched(fileRecords_[i].extension)) { continue; } selectedFilenames_.insert(fileRecords_[i].name); } } else if(selected) { if(!multiSelect) { selectedFilenames_ = { rsc.name }; rangeSelectionStart_ = rscIndex; } else { selectedFilenames_.erase(rsc.name); } if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename) { AssignToArrayStyleString(inputNameBuffer_, ""); } } else if(canSelect) { if(multiSelect) { selectedFilenames_.insert(rsc.name); } else { selectedFilenames_ = { rsc.name }; } if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename) { const auto rscName = u8StrToStr(rsc.name.u8string()); AssignToArrayStyleString(inputNameBuffer_, rscName); } rangeSelectionStart_ = rscIndex; } } if(IsItemClicked(0) && IsMouseDoubleClicked(0)) { if(rsc.isDir) { shouldSetNewDir = true; newDir = (rsc.name != "..") ? (currentDirectory_ / rsc.name) : currentDirectory_.parent_path(); } else if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory)) { selectedFilenames_ = { rsc.name }; isOk_ = true; CloseCurrentPopup(); } } else if(IsKeyPressed(ImGuiKey_GamepadFaceDown) && IsItemHovered()) { if(rsc.isDir) { shouldSetNewDir = true; newDir = (rsc.name != "..") ? (currentDirectory_ / rsc.name) : currentDirectory_.parent_path(); SetKeyboardFocusHere(-1); } else if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory)) { selectedFilenames_ = { rsc.name }; isOk_ = true; CloseCurrentPopup(); } } } } if(shouldSetNewDir) { SetDirectory(newDir); } if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename) { PushID(this); ScopeGuard popTextID([] { PopID(); }); if(inputNameBuffer_.empty()) { inputNameBuffer_.resize(1, '\0'); } PushItemWidth(-1); if(InputText( "", inputNameBuffer_.data(), inputNameBuffer_.size(), ImGuiInputTextFlags_CallbackResize, ExpandInputBuffer, &inputNameBuffer_) && inputNameBuffer_[0] != '\0') { selectedFilenames_ = { u8StrToPath(inputNameBuffer_.data()) }; } focusOnInputText |= IsItemFocused(); PopItemWidth(); } if(!focusOnInputText && !editDir_) { const bool selectAll = (flags_ & ImGuiFileBrowserFlags_MultipleSelection) && IsKeyPressed(ImGuiKey_A) && (IsKeyDown(ImGuiKey_LeftCtrl) || IsKeyDown(ImGuiKey_RightCtrl)); if(selectAll) { const bool needDir = flags_ & ImGuiFileBrowserFlags_SelectDirectory; selectedFilenames_.clear(); for(size_t i = 1; i < fileRecords_.size(); ++i) { auto &record = fileRecords_[i]; if(record.isDir == needDir && (needDir || IsExtensionMatched(record.extension))) { selectedFilenames_.insert(record.name); } } } } const bool isEnterPressed = (flags_ & ImGuiFileBrowserFlags_ConfirmOnEnter) && IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && IsKeyPressed(ImGuiKey_Enter); if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory)) { if((Button(" ok ") || isEnterPressed) && !selectedFilenames_.empty()) { isOk_ = true; CloseCurrentPopup(); } } else { if(Button(" ok ") || isEnterPressed) { isOk_ = true; CloseCurrentPopup(); } } SameLine(); const bool shouldClose = Button("cancel") || shouldClose_ || ((flags_ & ImGuiFileBrowserFlags_CloseOnEsc) && IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && IsKeyPressed(ImGuiKey_Escape)); if(shouldClose) { CloseCurrentPopup(); } if(!statusStr_.empty() && !(flags_ & ImGuiFileBrowserFlags_NoStatusBar)) { SameLine(); Text("%s", statusStr_.c_str()); } if(!typeFilters_.empty()) { SameLine(); PushItemWidth(8 * GetFontSize()); if(BeginCombo( "##type_filters", typeFilters_[typeFilterIndex_].c_str())) { ScopeGuard guard([&] { EndCombo(); }); for(size_t i = 0; i < typeFilters_.size(); ++i) { bool selected = i == typeFilterIndex_; if(Selectable(typeFilters_[i].c_str(), selected) && !selected) { typeFilterIndex_ = static_cast(i); } } } PopItemWidth(); } } inline bool ImGui::FileBrowser::HasSelected() const noexcept { return isOk_; } inline bool ImGui::FileBrowser::SetDirectory(const std::filesystem::path &dir) { const std::filesystem::path preferredFallback = this->GetDirectory(); return SetCurrentDirectoryInternal(dir, preferredFallback); } inline const std::filesystem::path &ImGui::FileBrowser::GetDirectory() const noexcept { return currentDirectory_; } inline std::filesystem::path ImGui::FileBrowser::GetSelected() const { // when isOk_ is true, selectedFilenames_ may be empty if SelectDirectory // is enabled. return pwd in that case. if(selectedFilenames_.empty()) { return currentDirectory_; } return currentDirectory_ / *selectedFilenames_.begin(); } inline std::vector ImGui::FileBrowser::GetMultiSelected() const { if(selectedFilenames_.empty()) { return { currentDirectory_ }; } std::vector ret; ret.reserve(selectedFilenames_.size()); for(auto &s : selectedFilenames_) { ret.push_back(currentDirectory_ / s); } return ret; } inline void ImGui::FileBrowser::ClearSelected() { selectedFilenames_.clear(); if((flags_ & ImGuiFileBrowserFlags_EnterNewFilename)) { AssignToArrayStyleString(inputNameBuffer_, ""); } isOk_ = false; } inline void ImGui::FileBrowser::SetTypeFilters(const std::vector &_typeFilters) { typeFilters_.clear(); // remove duplicate filter names due to case unsensitivity on windows #ifdef _WIN32 std::vector typeFilters; for(auto &rawFilter : _typeFilters) { std::string lowerFilter = ToLower(rawFilter); const auto it = std::find(typeFilters.begin(), typeFilters.end(), lowerFilter); if(it == typeFilters.end()) { typeFilters.push_back(std::move(lowerFilter)); } } #else auto &typeFilters = _typeFilters; #endif // insert auto-generated filter hasAllFilter_ = false; if(typeFilters.size() > 1) { hasAllFilter_ = true; std::string allFiltersName = std::string(); for(size_t i = 0; i < typeFilters.size(); ++i) { if(typeFilters[i] == std::string_view(".*")) { hasAllFilter_ = false; break; } if(i > 0) { allFiltersName += ","; } allFiltersName += typeFilters[i]; } if(hasAllFilter_) { typeFilters_.push_back(std::move(allFiltersName)); } } std::copy(typeFilters.begin(), typeFilters.end(), std::back_inserter(typeFilters_)); typeFilterIndex_ = 0; } inline void ImGui::FileBrowser::SetCurrentTypeFilterIndex(int index) { typeFilterIndex_ = static_cast(index); } inline void ImGui::FileBrowser::SetInputName(std::string_view input) { assert((flags_ & ImGuiFileBrowserFlags_EnterNewFilename) && "SetInputName can only be called when ImGuiFileBrowserFlags_EnterNewFilename is enabled"); customizedInputName_ = input; } inline std::string ImGui::FileBrowser::ToLower(const std::string &s) { std::string ret = s; for(char &c : ret) { c = static_cast(std::tolower(c)); } return ret; } inline void ImGui::FileBrowser::ToolTip(const std::string_view &s) { if (!ImGui::IsItemHovered()) { return; } ImGui::SetTooltip("%s", s.data()); } inline void ImGui::FileBrowser::UpdateFileRecords() { fileRecords_ = { FileRecord{ true, "..", "[D] ..", "" } }; for(auto &p : std::filesystem::directory_iterator(currentDirectory_)) { FileRecord rcd; try { if(p.is_regular_file()) { rcd.isDir = false; } else if(p.is_directory()) { rcd.isDir = true; } else { continue; } rcd.name = p.path().filename(); if(rcd.name.empty()) { continue; } rcd.extension = p.path().filename().extension(); rcd.showName = (rcd.isDir ? "[D] " : "[F] ") + u8StrToStr(p.path().filename().u8string()); } catch(...) { if(!(flags_ & ImGuiFileBrowserFlags_SkipItemsCausingError)) { throw; } continue; } fileRecords_.push_back(rcd); } // The default lexicographical order does not meet our sorting requirements. // We want [b0, a0, A1] to be sorted into something like [a0, A1, b0] instead of [a0, b0, A1]. // Therefore, here we compute a custom key for each filename for sorting. if(fileRecords_.size() > 2) { std::vector> keys; keys.reserve(fileRecords_.size()); for(auto &fileRecord : fileRecords_) { const auto name = u8StrToStr(fileRecord.name.u8string()); auto& key = keys.emplace_back(); key.reserve(name.size() + 1); key.emplace_back(!fileRecord.isDir); for(char c : name) { if('A' <= c && c <= 'Z') { key.emplace_back(2 * (c + 'a' - 'A') + 1); } else { key.emplace_back(2 * c); } } } std::vector fileRecordRemapIndices; fileRecordRemapIndices.reserve(fileRecords_.size()); for(uint32_t i = 0; i < fileRecords_.size(); ++i) { fileRecordRemapIndices.push_back(i); } std::sort( fileRecordRemapIndices.begin() + 1, fileRecordRemapIndices.end(), [&](uint32_t li, uint32_t ri) { return keys[li] < keys[ri]; }); std::vector remappedFileRecords; remappedFileRecords.reserve(fileRecords_.size()); for(const uint32_t index : fileRecordRemapIndices) { remappedFileRecords.emplace_back(std::move(fileRecords_[index])); } fileRecords_ = std::move(remappedFileRecords); } ClearRangeSelectionState(); } inline void ImGui::FileBrowser::SetCurrentDirectoryUncatched(const std::filesystem::path &pwd) { currentDirectory_ = absolute(pwd); UpdateFileRecords(); bool shouldClearInputNameBuffer = true; if((flags_ & ImGuiFileBrowserFlags_EnterNewFilename) && selectedFilenames_.size() == 1 && !customizedInputName_.empty() && !inputNameBuffer_.empty() && std::strcmp(inputNameBuffer_.data(), customizedInputName_.data()) == 0) { shouldClearInputNameBuffer = false; } if(shouldClearInputNameBuffer) { selectedFilenames_.clear(); AssignToArrayStyleString(inputNameBuffer_, ""); } } inline bool ImGui::FileBrowser::SetCurrentDirectoryInternal( const std::filesystem::path &dir, const std::filesystem::path &preferredFallback) { try { SetCurrentDirectoryUncatched(dir); return true; } catch(const std::exception &err) { statusStr_ = std::string("error: ") + err.what(); } catch(...) { statusStr_ = "unknown error"; } if(preferredFallback != defaultDirectory_) { try { SetCurrentDirectoryUncatched(preferredFallback); } catch(...) { SetCurrentDirectoryUncatched(defaultDirectory_); } } else { SetCurrentDirectoryUncatched(defaultDirectory_); } return false; } inline bool ImGui::FileBrowser::IsExtensionMatched(const std::filesystem::path &_extension) const { #ifdef _WIN32 std::filesystem::path extension = ToLower(u8StrToStr(_extension.u8string())); #else auto &extension = _extension; #endif // no type filters if(typeFilters_.empty()) { return true; } // invalid type filter index if(static_cast(typeFilterIndex_) >= typeFilters_.size()) { return true; } // all type filters if(hasAllFilter_ && typeFilterIndex_ == 0) { for(size_t i = 1; i < typeFilters_.size(); ++i) { if(extension == typeFilters_[i]) { return true; } } return false; } // universal filter if(typeFilters_[typeFilterIndex_] == std::string_view(".*")) { return true; } // regular filter return extension == typeFilters_[typeFilterIndex_]; } inline void ImGui::FileBrowser::ClearRangeSelectionState() { rangeSelectionStart_ = 9999999; const bool dir = flags_ & ImGuiFileBrowserFlags_SelectDirectory; for(unsigned int i = 1; i < fileRecords_.size(); ++i) { if(fileRecords_[i].isDir == dir) { if(!dir && !IsExtensionMatched(fileRecords_[i].extension)) { continue; } rangeSelectionStart_ = i; break; } } } inline void ImGui::FileBrowser::AssignToArrayStyleString(std::vector &arr, std::string_view content) { if(content.empty()) { if(!arr.empty()) { arr[0] = '\0'; } return; } if(arr.size() < content.size() + 1) { arr.resize(content.size() + 1); } std::memcpy(arr.data(), content.data(), content.size()); arr[content.size()] = '\0'; } inline int ImGui::FileBrowser::ExpandInputBuffer(ImGuiInputTextCallbackData *callbackData) { if(callbackData && callbackData->EventFlag & ImGuiInputTextFlags_CallbackResize) { auto buffer = static_cast*>(callbackData->UserData); size_t newSize = buffer->size(); while(newSize < static_cast(callbackData->BufSize)) { newSize <<= 1; } buffer->resize(newSize, '\0'); callbackData->Buf = buffer->data(); callbackData->BufDirty = true; } return 0; } #if defined(__cpp_lib_char8_t) inline std::string ImGui::FileBrowser::u8StrToStr(std::u8string s) { std::string result; result.resize(s.length()); std::memcpy(result.data(), s.data(), s.length()); return result; } #endif inline std::string ImGui::FileBrowser::u8StrToStr(std::string s) { return s; } inline std::filesystem::path ImGui::FileBrowser::u8StrToPath(const char *str) { #if defined(__cpp_lib_char8_t) // With C++20/23, it's impossible to efficiently convert a `char*` string to a `char8_t*` string without violating // the strict aliasing rule. Bad joke! const size_t len = std::strlen(str); std::u8string u8Str; u8Str.resize(len); std::memcpy(u8Str.data(), str, len); return std::filesystem::path(u8Str); #else // u8path is deprecated in C++20 return std::filesystem::u8path(str); #endif } #ifdef _WIN32 #ifndef _INC_WINDOWS #ifndef NOMINMAX #define IMGUI_FILEBROWSER_UNDEF_NOMINMAX #define NOMINMAX #endif #ifndef WIN32_LEAN_AND_MEAN #define IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // #ifndef WIN32_LEAN_AND_MEAN #include #ifdef IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN #undef IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN #undef WIN32_LEAN_AND_MEAN #endif // #ifdef IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN #ifdef IMGUI_FILEBROWSER_UNDEF_NOMINMAX #undef IMGUI_FILEBROWSER_UNDEF_NOMINMAX #undef NOMINMAX #endif // #ifdef IMGUI_FILEBROWSER_UNDEF_NOMINMAX #endif // #ifdef _INC_WINDOWS inline std::uint32_t ImGui::FileBrowser::GetDrivesBitMask() { const DWORD mask = GetLogicalDrives(); std::uint32_t ret = 0; for(int i = 0; i < 26; ++i) { if(!(mask & (1 << i))) { continue; } const char rootName[4] = { static_cast('A' + i), ':', '\\', '\0' }; const UINT type = GetDriveTypeA(rootName); if(type == DRIVE_REMOVABLE || type == DRIVE_FIXED || type == DRIVE_REMOTE) { ret |= (1 << i); } } return ret; } #endif