#pragma once #include #include #include #include #include #include #include #include #include #include #include "IconsForkAwesome.h" #ifndef IMGUI_VERSION # error "include imgui.h before this header" #endif using ImGuiFileBrowserFlags = int; enum ImGuiFileBrowserFlags_ { 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 }; namespace ImGui { class FileBrowser { public: // pwd is set to current working directory by default explicit FileBrowser(ImGuiFileBrowserFlags flags = 0); 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 and the "ok" button was clicked bool HasSelected() const noexcept; // set current browsing directory bool SetPwd(const std::filesystem::path &pwd = std::filesystem::current_path()); // get current browsing directory const std::filesystem::path &GetPwd() const noexcept; // 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 UpdateFileRecords(); void SetPwdUncatched(const std::filesystem::path &pwd); bool IsExtensionMatched(const std::filesystem::path &extension) const; #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); int width_; int height_; int posX_; int posY_; ImGuiFileBrowserFlags flags_; std::string title_; std::string openLabel_; bool openFlag_; bool closeFlag_; bool isOpened_; bool ok_; bool posIsSet_; std::string statusStr_; std::vector typeFilters_; unsigned int typeFilterIndex_; bool hasAllFilter_; std::filesystem::path pwd_; std::set selectedFilenames_; std::vector fileRecords_; // IMPROVE: truncate when selectedFilename_.length() > inputNameBuf_.size() - 1 static constexpr size_t INPUT_NAME_BUF_SIZE = 512; std::unique_ptr> inputNameBuf_; std::string openNewDirLabel_; std::unique_ptr> newDirNameBuf_; #ifdef _WIN32 uint32_t drives_; #endif }; } // namespace ImGui inline ImGui::FileBrowser::FileBrowser(ImGuiFileBrowserFlags flags) : width_(700), height_(450), posX_(0), posY_(0), flags_(flags), openFlag_(false), closeFlag_(false), isOpened_(false), ok_(false), posIsSet_(false), inputNameBuf_(std::make_unique>()) { if(flags_ & ImGuiFileBrowserFlags_CreateNewDir) { newDirNameBuf_ = std::make_unique>(); } inputNameBuf_->front() = '\0'; inputNameBuf_->back() = '\0'; SetTitle("file browser"); SetPwd(std::filesystem::current_path()); 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_); openFlag_ = copyFrom.openFlag_; closeFlag_ = copyFrom.closeFlag_; isOpened_ = copyFrom.isOpened_; ok_ = copyFrom.ok_; posIsSet_ = copyFrom.posIsSet_; statusStr_ = ""; typeFilters_ = copyFrom.typeFilters_; typeFilterIndex_ = copyFrom.typeFilterIndex_; hasAllFilter_ = copyFrom.hasAllFilter_; pwd_ = copyFrom.pwd_; selectedFilenames_ = copyFrom.selectedFilenames_; fileRecords_ = copyFrom.fileRecords_; *inputNameBuf_ = *copyFrom.inputNameBuf_; openNewDirLabel_ = copyFrom.openNewDirLabel_; if(flags_ & ImGuiFileBrowserFlags_CreateNewDir) { newDirNameBuf_ = std::make_unique< std::array>(); *newDirNameBuf_ = *copyFrom.newDirNameBuf_; } #ifdef _WIN32 drives_ = copyFrom.drives_; #endif return *this; } inline void ImGui::FileBrowser::SetWindowPos(int posx, int posy) noexcept { posX_ = posx; posY_ = posy; posIsSet_ = 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); openLabel_ = title_ + "##filebrowser_" + std::to_string(reinterpret_cast(this)); openNewDirLabel_ = "new dir##new_dir_" + std::to_string(reinterpret_cast(this)); } inline void ImGui::FileBrowser::Open() { ClearSelected(); UpdateFileRecords(); statusStr_ = std::string(); openFlag_ = true; closeFlag_ = false; } inline void ImGui::FileBrowser::Close() { ClearSelected(); statusStr_ = std::string(); closeFlag_ = true; openFlag_ = false; } inline bool ImGui::FileBrowser::IsOpened() const noexcept { return isOpened_; } inline void ImGui::FileBrowser::Display() { PushID(this); ScopeGuard exitThis([this] { openFlag_ = false; closeFlag_ = false; PopID(); }); if(openFlag_) { OpenPopup(openLabel_.c_str()); } isOpened_ = false; // open the popup window if(openFlag_ && (flags_ & ImGuiFileBrowserFlags_NoModal)) { if (posIsSet_) SetNextWindowPos( ImVec2(static_cast(posX_), static_cast(posY_))); SetNextWindowSize( ImVec2(static_cast(width_), static_cast(height_))); } else { if (posIsSet_) 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(); }); // display elements in pwd #ifdef _WIN32 char currentDrive = static_cast(pwd_.c_str()[0]); 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; } char driveCh = static_cast('A' + i); char selectableStr[] = { driveCh, ':', '\0' }; bool selected = currentDrive == driveCh; if(Selectable(selectableStr, selected) && !selected) { char newPwd[] = { driveCh, ':', '\\', '\0' }; SetPwd(newPwd); } } } PopItemWidth(); SameLine(); #endif int secIdx = 0, newPwdLastSecIdx = -1; for(const auto &sec : pwd_) { #ifdef _WIN32 if(secIdx == 1) { ++secIdx; continue; } #endif PushID(secIdx); if(secIdx > 0) { SameLine(); } if(SmallButton(u8StrToStr(sec.u8string()).c_str())) { newPwdLastSecIdx = secIdx; } PopID(); ++secIdx; } if(newPwdLastSecIdx >= 0) { int i = 0; std::filesystem::path newPwd; for(const auto &sec : pwd_) { if(i++ > newPwdLastSecIdx) { break; } newPwd /= sec; } #ifdef _WIN32 if(newPwdLastSecIdx == 0) { newPwd /= "\\"; } #endif SetPwd(newPwd); } SameLine(); if(SmallButton("*")) { UpdateFileRecords(); std::set newSelectedFilenames; for(auto &name : selectedFilenames_) { auto it = std::find_if( fileRecords_.begin(), fileRecords_.end(), [&](const FileRecord &record) { return name == record.name; }); if(it != fileRecords_.end()) { newSelectedFilenames.insert(name); } } if(inputNameBuf_ && (*inputNameBuf_)[0]) { newSelectedFilenames.insert(inputNameBuf_->data()); } } if(newDirNameBuf_) { SameLine(); if(SmallButton("+")) { OpenPopup(openNewDirLabel_.c_str()); (*newDirNameBuf_)[0] = '\0'; } if(BeginPopup(openNewDirLabel_.c_str())) { ScopeGuard endNewDirPopup([] { EndPopup(); }); InputText("Name:", newDirNameBuf_->data(), newDirNameBuf_->size()); SameLine(); if(Button("OK") && (*newDirNameBuf_)[0] != '\0') { ScopeGuard closeNewDirPopup([] { CloseCurrentPopup(); }); if(create_directory(pwd_ / newDirNameBuf_->data())) { UpdateFileRecords(); } else { statusStr_ = "failed to create " + std::string(newDirNameBuf_->data()); } } } } // browse files in a child window float reserveHeight = GetFrameHeightWithSpacing(); std::filesystem::path newPwd; bool setNewPwd = false; if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory) && (flags_ & ImGuiFileBrowserFlags_EnterNewFilename)) reserveHeight += GetFrameHeightWithSpacing(); { BeginChild("ch", ImVec2(0, -reserveHeight), true, (flags_ & ImGuiFileBrowserFlags_NoModal) ? ImGuiWindowFlags_AlwaysHorizontalScrollbar : 0); ScopeGuard endChild([] { EndChild(); }); for(auto &rsc : fileRecords_) { if(!rsc.isDir && !IsExtensionMatched(rsc.extension)) { continue; } if(!rsc.name.empty() && rsc.name.c_str()[0] == '$') { continue; } bool selected = selectedFilenames_.find(rsc.name) != selectedFilenames_.end(); if(Selectable(rsc.showName.c_str(), selected, ImGuiSelectableFlags_DontClosePopups)) { const bool multiSelect = (flags_ & ImGuiFileBrowserFlags_MultipleSelection) && IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && (GetIO().KeyCtrl || GetIO().KeyShift); if(selected) { if(!multiSelect) { selectedFilenames_.clear(); } else { selectedFilenames_.erase(rsc.name); } (*inputNameBuf_)[0] = '\0'; } else if(rsc.name != "..") { if((rsc.isDir && (flags_ & ImGuiFileBrowserFlags_SelectDirectory)) || (!rsc.isDir && !(flags_ & ImGuiFileBrowserFlags_SelectDirectory))) { if(multiSelect) { selectedFilenames_.insert(rsc.name); } else { selectedFilenames_ = { rsc.name }; } if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory)) { #ifdef _MSC_VER strcpy_s( inputNameBuf_->data(), inputNameBuf_->size(), u8StrToStr(rsc.name.u8string()).c_str()); #else std::strncpy(inputNameBuf_->data(), u8StrToStr(rsc.name.u8string()).c_str(), inputNameBuf_->size() - 1); #endif } } } else { if(!multiSelect) { selectedFilenames_.clear(); } } } if(IsItemClicked(0) && IsMouseDoubleClicked(0)) { if(rsc.isDir) { setNewPwd = true; newPwd = (rsc.name != "..") ? (pwd_ / rsc.name) : pwd_.parent_path(); } else if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory)) { selectedFilenames_ = { rsc.name }; ok_ = true; CloseCurrentPopup(); } } } } if(setNewPwd) { SetPwd(newPwd); } if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory) && (flags_ & ImGuiFileBrowserFlags_EnterNewFilename)) { PushID(this); ScopeGuard popTextID([] { PopID(); }); PushItemWidth(-1); if(InputText("", inputNameBuf_->data(), inputNameBuf_->size()) && inputNameBuf_->at(0) != '\0') { selectedFilenames_ = { inputNameBuf_->data() }; } PopItemWidth(); } if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory)) { if(Button(" OK ") && !selectedFilenames_.empty()) { ok_ = true; CloseCurrentPopup(); } } else { if(Button(" OK ")) { ok_ = true; CloseCurrentPopup(); } } SameLine(); bool shouldExit = Button("Cancel") || closeFlag_ || ((flags_ & ImGuiFileBrowserFlags_CloseOnEsc) && IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) && IsKeyPressed(ImGuiKey_Escape)); if(shouldExit) { 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 ok_; } inline bool ImGui::FileBrowser::SetPwd(const std::filesystem::path &pwd) { try { SetPwdUncatched(pwd); return true; } catch(const std::exception &err) { statusStr_ = std::string("last error: ") + err.what(); } catch(...) { statusStr_ = "last error: unknown"; } SetPwdUncatched(std::filesystem::current_path()); return false; } inline const class std::filesystem::path &ImGui::FileBrowser::GetPwd() const noexcept { return pwd_; } inline std::filesystem::path ImGui::FileBrowser::GetSelected() const { // when ok_ is true, selectedFilenames_ may be empty if SelectDirectory // is enabled. return pwd in that case. if(selectedFilenames_.empty()) { return pwd_; } return pwd_ / *selectedFilenames_.begin(); } inline std::vector ImGui::FileBrowser::GetMultiSelected() const { if(selectedFilenames_.empty()) { return { pwd_ }; } std::vector ret; ret.reserve(selectedFilenames_.size()); for(auto &s : selectedFilenames_) { ret.push_back(pwd_ / s); } return ret; } inline void ImGui::FileBrowser::ClearSelected() { selectedFilenames_.clear(); (*inputNameBuf_)[0] = '\0'; ok_ = 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); 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 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) { if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename) { if(input.size() >= static_cast(INPUT_NAME_BUF_SIZE)) { // If input doesn't fit trim off characters input = input.substr(0, INPUT_NAME_BUF_SIZE - 1); } std::copy(input.begin(), input.end(), inputNameBuf_->begin()); inputNameBuf_->at(input.size()) = '\0'; selectedFilenames_ = { inputNameBuf_->data() }; } } 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::UpdateFileRecords() { fileRecords_ = { FileRecord{ true, "..", ICON_FK_FOLDER " ..", "" } }; for(auto &p : std::filesystem::directory_iterator(pwd_)) { FileRecord rcd; 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 ? ICON_FK_FOLDER " " : ICON_FK_FILE " ") + u8StrToStr(p.path().filename().u8string()); fileRecords_.push_back(rcd); } std::sort(fileRecords_.begin(), fileRecords_.end(), [](const FileRecord &L, const FileRecord &R) { return (L.isDir ^ R.isDir) ? L.isDir : (L.name < R.name); }); } inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd) { pwd_ = absolute(pwd); UpdateFileRecords(); selectedFilenames_.clear(); (*inputNameBuf_)[0] = '\0'; } inline bool ImGui::FileBrowser::IsExtensionMatched( const std::filesystem::path &_extension) const { #ifdef _WIN32 std::filesystem::path extension = ToLower(_extension.string()); #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_]; } #if defined(__cpp_lib_char8_t) inline std::string ImGui::FileBrowser::u8StrToStr(std::u8string s) { return std::string(s.begin(), s.end()); } #endif inline std::string ImGui::FileBrowser::u8StrToStr(std::string s) { return s; } #ifdef _WIN32 #ifndef _INC_WINDOWS #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 #endif // #ifdef _INC_WINDOWS inline std::uint32_t ImGui::FileBrowser::GetDrivesBitMask() { DWORD mask = GetLogicalDrives(); uint32_t ret = 0; for(int i = 0; i < 26; ++i) { if(!(mask & (1 << i))) { continue; } char rootName[4] = { static_cast('A' + i), ':', '\\', '\0' }; UINT type = GetDriveTypeA(rootName); if(type == DRIVE_REMOVABLE || type == DRIVE_FIXED || type == DRIVE_REMOTE) { ret |= (1 << i); } } return ret; } #endif