imfilebrowser.h 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. #pragma once
  2. #include <algorithm>
  3. #include <array>
  4. #include <cctype>
  5. #include <cstring>
  6. #include <filesystem>
  7. #include <memory>
  8. #include <set>
  9. #include <string>
  10. #include <string_view>
  11. #include <vector>
  12. #ifndef IMGUI_VERSION
  13. # error "include imgui.h before this header"
  14. #endif
  15. using ImGuiFileBrowserFlags = int;
  16. enum ImGuiFileBrowserFlags_
  17. {
  18. ImGuiFileBrowserFlags_SelectDirectory = 1 << 0, // select directory instead of regular file
  19. ImGuiFileBrowserFlags_EnterNewFilename = 1 << 1, // allow user to enter new filename when selecting regular file
  20. ImGuiFileBrowserFlags_NoModal = 1 << 2, // file browsing window is modal by default. specify this to use a popup window
  21. ImGuiFileBrowserFlags_NoTitleBar = 1 << 3, // hide window title bar
  22. ImGuiFileBrowserFlags_NoStatusBar = 1 << 4, // hide status bar at the bottom of browsing window
  23. ImGuiFileBrowserFlags_CloseOnEsc = 1 << 5, // close file browser when pressing 'ESC'
  24. ImGuiFileBrowserFlags_CreateNewDir = 1 << 6, // allow user to create new directory
  25. ImGuiFileBrowserFlags_MultipleSelection = 1 << 7, // allow user to select multiple files. this will hide ImGuiFileBrowserFlags_EnterNewFilename
  26. ImGuiFileBrowserFlags_HideRegularFiles = 1 << 8, // hide regular files when ImGuiFileBrowserFlags_SelectDirectory is enabled
  27. ImGuiFileBrowserFlags_ConfirmOnEnter = 1 << 9, // confirm selection when pressnig 'ENTER'
  28. };
  29. namespace ImGui
  30. {
  31. class FileBrowser
  32. {
  33. public:
  34. // pwd is set to current working directory by default
  35. explicit FileBrowser(ImGuiFileBrowserFlags flags = 0);
  36. FileBrowser(const FileBrowser &copyFrom);
  37. FileBrowser &operator=(const FileBrowser &copyFrom);
  38. // set the window position (in pixels)
  39. // default is centered
  40. void SetWindowPos(int posx, int posy) noexcept;
  41. // set the window size (in pixels)
  42. // default is (700, 450)
  43. void SetWindowSize(int width, int height) noexcept;
  44. // set the window title text
  45. void SetTitle(std::string title);
  46. // open the browsing window
  47. void Open();
  48. // close the browsing window
  49. void Close();
  50. // the browsing window is opened or not
  51. bool IsOpened() const noexcept;
  52. // display the browsing window if opened
  53. void Display();
  54. // returns true when there is a selected filename and the "ok" button was clicked
  55. bool HasSelected() const noexcept;
  56. // set current browsing directory
  57. bool SetPwd(const std::filesystem::path &pwd =
  58. std::filesystem::current_path());
  59. // get current browsing directory
  60. const std::filesystem::path &GetPwd() const noexcept;
  61. // returns selected filename. make sense only when HasSelected returns true
  62. // when ImGuiFileBrowserFlags_MultipleSelection is enabled, only one of
  63. // selected filename will be returned
  64. std::filesystem::path GetSelected() const;
  65. // returns all selected filenames.
  66. // when ImGuiFileBrowserFlags_MultipleSelection is enabled, use this
  67. // instead of GetSelected
  68. std::vector<std::filesystem::path> GetMultiSelected() const;
  69. // set selected filename to empty
  70. void ClearSelected();
  71. // (optional) set file type filters. eg. { ".h", ".cpp", ".hpp" }
  72. // ".*" matches any file types
  73. void SetTypeFilters(const std::vector<std::string> &typeFilters);
  74. // set currently applied type filter
  75. // default value is 0 (the first type filter)
  76. void SetCurrentTypeFilterIndex(int index);
  77. // when ImGuiFileBrowserFlags_EnterNewFilename is set
  78. // this function will pre-fill the input dialog with a filename.
  79. void SetInputName(std::string_view input);
  80. private:
  81. template <class Functor>
  82. struct ScopeGuard
  83. {
  84. ScopeGuard(Functor&& t) : func(std::move(t)) { }
  85. ~ScopeGuard() { func(); }
  86. private:
  87. Functor func;
  88. };
  89. struct FileRecord
  90. {
  91. bool isDir = false;
  92. std::filesystem::path name;
  93. std::string showName;
  94. std::filesystem::path extension;
  95. };
  96. static std::string ToLower(const std::string &s);
  97. void UpdateFileRecords();
  98. void SetPwdUncatched(const std::filesystem::path &pwd);
  99. bool IsExtensionMatched(const std::filesystem::path &extension) const;
  100. void ClearRangeSelectionState();
  101. #ifdef _WIN32
  102. static std::uint32_t GetDrivesBitMask();
  103. #endif
  104. // for c++17 compatibility
  105. #if defined(__cpp_lib_char8_t)
  106. static std::string u8StrToStr(std::u8string s);
  107. #endif
  108. static std::string u8StrToStr(std::string s);
  109. int width_;
  110. int height_;
  111. int posX_;
  112. int posY_;
  113. ImGuiFileBrowserFlags flags_;
  114. std::string title_;
  115. std::string openLabel_;
  116. bool openFlag_;
  117. bool closeFlag_;
  118. bool isOpened_;
  119. bool ok_;
  120. bool posIsSet_;
  121. std::string statusStr_;
  122. std::vector<std::string> typeFilters_;
  123. unsigned int typeFilterIndex_;
  124. bool hasAllFilter_;
  125. std::filesystem::path pwd_;
  126. std::set<std::filesystem::path> selectedFilenames_;
  127. unsigned int rangeSelectionStart_; // enable range selection when shift is pressed
  128. std::vector<FileRecord> fileRecords_;
  129. // IMPROVE: truncate when selectedFilename_.length() > inputNameBuf_.size() - 1
  130. static constexpr size_t INPUT_NAME_BUF_SIZE = 512;
  131. std::unique_ptr<std::array<char, INPUT_NAME_BUF_SIZE>> inputNameBuf_;
  132. std::string openNewDirLabel_;
  133. std::unique_ptr<std::array<char, INPUT_NAME_BUF_SIZE>> newDirNameBuf_;
  134. #ifdef _WIN32
  135. uint32_t drives_;
  136. #endif
  137. };
  138. } // namespace ImGui
  139. inline ImGui::FileBrowser::FileBrowser(ImGuiFileBrowserFlags flags)
  140. : width_(700), height_(450), posX_(0), posY_(0), flags_(flags),
  141. openFlag_(false), closeFlag_(false), isOpened_(false), ok_(false), posIsSet_(false),
  142. rangeSelectionStart_(0), inputNameBuf_(std::make_unique<std::array<char, INPUT_NAME_BUF_SIZE>>())
  143. {
  144. if(flags_ & ImGuiFileBrowserFlags_CreateNewDir)
  145. {
  146. newDirNameBuf_ =
  147. std::make_unique<std::array<char, INPUT_NAME_BUF_SIZE>>();
  148. }
  149. inputNameBuf_->front() = '\0';
  150. inputNameBuf_->back() = '\0';
  151. SetTitle("file browser");
  152. SetPwd(std::filesystem::current_path());
  153. typeFilters_.clear();
  154. typeFilterIndex_ = 0;
  155. hasAllFilter_ = false;
  156. #ifdef _WIN32
  157. drives_ = GetDrivesBitMask();
  158. #endif
  159. }
  160. inline ImGui::FileBrowser::FileBrowser(const FileBrowser &copyFrom)
  161. : FileBrowser()
  162. {
  163. *this = copyFrom;
  164. }
  165. inline ImGui::FileBrowser &ImGui::FileBrowser::operator=(
  166. const FileBrowser &copyFrom)
  167. {
  168. width_ = copyFrom.width_;
  169. height_ = copyFrom.height_;
  170. posX_ = copyFrom.posX_;
  171. posY_ = copyFrom.posY_;
  172. flags_ = copyFrom.flags_;
  173. SetTitle(copyFrom.title_);
  174. openFlag_ = copyFrom.openFlag_;
  175. closeFlag_ = copyFrom.closeFlag_;
  176. isOpened_ = copyFrom.isOpened_;
  177. ok_ = copyFrom.ok_;
  178. posIsSet_ = copyFrom.posIsSet_;
  179. statusStr_ = "";
  180. typeFilters_ = copyFrom.typeFilters_;
  181. typeFilterIndex_ = copyFrom.typeFilterIndex_;
  182. hasAllFilter_ = copyFrom.hasAllFilter_;
  183. pwd_ = copyFrom.pwd_;
  184. selectedFilenames_ = copyFrom.selectedFilenames_;
  185. rangeSelectionStart_ = copyFrom.rangeSelectionStart_;
  186. fileRecords_ = copyFrom.fileRecords_;
  187. *inputNameBuf_ = *copyFrom.inputNameBuf_;
  188. openNewDirLabel_ = copyFrom.openNewDirLabel_;
  189. if(flags_ & ImGuiFileBrowserFlags_CreateNewDir)
  190. {
  191. newDirNameBuf_ = std::make_unique<
  192. std::array<char, INPUT_NAME_BUF_SIZE>>();
  193. *newDirNameBuf_ = *copyFrom.newDirNameBuf_;
  194. }
  195. #ifdef _WIN32
  196. drives_ = copyFrom.drives_;
  197. #endif
  198. return *this;
  199. }
  200. inline void ImGui::FileBrowser::SetWindowPos(int posx, int posy) noexcept
  201. {
  202. posX_ = posx;
  203. posY_ = posy;
  204. posIsSet_ = true;
  205. }
  206. inline void ImGui::FileBrowser::SetWindowSize(int width, int height) noexcept
  207. {
  208. assert(width > 0 && height > 0);
  209. width_ = width;
  210. height_ = height;
  211. }
  212. inline void ImGui::FileBrowser::SetTitle(std::string title)
  213. {
  214. title_ = std::move(title);
  215. openLabel_ = title_ + "##filebrowser_" +
  216. std::to_string(reinterpret_cast<size_t>(this));
  217. openNewDirLabel_ = "new dir##new_dir_" +
  218. std::to_string(reinterpret_cast<size_t>(this));
  219. }
  220. inline void ImGui::FileBrowser::Open()
  221. {
  222. UpdateFileRecords();
  223. ClearSelected();
  224. statusStr_ = std::string();
  225. openFlag_ = true;
  226. closeFlag_ = false;
  227. }
  228. inline void ImGui::FileBrowser::Close()
  229. {
  230. ClearSelected();
  231. statusStr_ = std::string();
  232. closeFlag_ = true;
  233. openFlag_ = false;
  234. }
  235. inline bool ImGui::FileBrowser::IsOpened() const noexcept
  236. {
  237. return isOpened_;
  238. }
  239. inline void ImGui::FileBrowser::Display()
  240. {
  241. PushID(this);
  242. ScopeGuard exitThis([this]
  243. {
  244. openFlag_ = false;
  245. closeFlag_ = false;
  246. PopID();
  247. });
  248. if(openFlag_)
  249. {
  250. OpenPopup(openLabel_.c_str());
  251. }
  252. isOpened_ = false;
  253. // open the popup window
  254. if(openFlag_ && (flags_ & ImGuiFileBrowserFlags_NoModal))
  255. {
  256. if(posIsSet_)
  257. {
  258. SetNextWindowPos(
  259. ImVec2(static_cast<float>(posX_), static_cast<float>(posY_)));
  260. }
  261. SetNextWindowSize(
  262. ImVec2(static_cast<float>(width_), static_cast<float>(height_)));
  263. }
  264. else
  265. {
  266. if(posIsSet_)
  267. {
  268. SetNextWindowPos(
  269. ImVec2(static_cast<float>(posX_), static_cast<float>(posY_)),
  270. ImGuiCond_FirstUseEver);
  271. }
  272. SetNextWindowSize(
  273. ImVec2(static_cast<float>(width_), static_cast<float>(height_)),
  274. ImGuiCond_FirstUseEver);
  275. }
  276. if(flags_ & ImGuiFileBrowserFlags_NoModal)
  277. {
  278. if(!BeginPopup(openLabel_.c_str()))
  279. {
  280. return;
  281. }
  282. }
  283. else if(!BeginPopupModal(openLabel_.c_str(), nullptr,
  284. flags_ & ImGuiFileBrowserFlags_NoTitleBar ?
  285. ImGuiWindowFlags_NoTitleBar : 0))
  286. {
  287. return;
  288. }
  289. isOpened_ = true;
  290. ScopeGuard endPopup([] { EndPopup(); });
  291. // display elements in pwd
  292. #ifdef _WIN32
  293. char currentDrive = static_cast<char>(pwd_.c_str()[0]);
  294. char driveStr[] = { currentDrive, ':', '\0' };
  295. PushItemWidth(4 * GetFontSize());
  296. if(BeginCombo("##select_drive", driveStr))
  297. {
  298. ScopeGuard guard([&] { EndCombo(); });
  299. for(int i = 0; i < 26; ++i)
  300. {
  301. if(!(drives_ & (1 << i)))
  302. {
  303. continue;
  304. }
  305. char driveCh = static_cast<char>('A' + i);
  306. char selectableStr[] = { driveCh, ':', '\0' };
  307. bool selected = currentDrive == driveCh;
  308. if(Selectable(selectableStr, selected) && !selected)
  309. {
  310. char newPwd[] = { driveCh, ':', '\\', '\0' };
  311. SetPwd(newPwd);
  312. }
  313. }
  314. }
  315. PopItemWidth();
  316. SameLine();
  317. #endif
  318. int secIdx = 0, newPwdLastSecIdx = -1;
  319. for(const auto &sec : pwd_)
  320. {
  321. #ifdef _WIN32
  322. if(secIdx == 1)
  323. {
  324. ++secIdx;
  325. continue;
  326. }
  327. #endif
  328. PushID(secIdx);
  329. if(secIdx > 0)
  330. {
  331. SameLine();
  332. }
  333. if(SmallButton(u8StrToStr(sec.u8string()).c_str()))
  334. {
  335. newPwdLastSecIdx = secIdx;
  336. }
  337. PopID();
  338. ++secIdx;
  339. }
  340. if(newPwdLastSecIdx >= 0)
  341. {
  342. int i = 0;
  343. std::filesystem::path newPwd;
  344. for(const auto &sec : pwd_)
  345. {
  346. if(i++ > newPwdLastSecIdx)
  347. {
  348. break;
  349. }
  350. newPwd /= sec;
  351. }
  352. #ifdef _WIN32
  353. if(newPwdLastSecIdx == 0)
  354. {
  355. newPwd /= "\\";
  356. }
  357. #endif
  358. SetPwd(newPwd);
  359. }
  360. SameLine();
  361. if(SmallButton("*"))
  362. {
  363. UpdateFileRecords();
  364. std::set<std::filesystem::path> newSelectedFilenames;
  365. for(auto &name : selectedFilenames_)
  366. {
  367. auto it = std::find_if(
  368. fileRecords_.begin(), fileRecords_.end(),
  369. [&](const FileRecord &record)
  370. {
  371. return name == record.name;
  372. });
  373. if(it != fileRecords_.end())
  374. {
  375. newSelectedFilenames.insert(name);
  376. }
  377. }
  378. if(inputNameBuf_ && (*inputNameBuf_)[0])
  379. {
  380. newSelectedFilenames.insert(inputNameBuf_->data());
  381. }
  382. }
  383. bool focusOnInputText = false;
  384. if(newDirNameBuf_)
  385. {
  386. SameLine();
  387. if(SmallButton("+"))
  388. {
  389. OpenPopup(openNewDirLabel_.c_str());
  390. (*newDirNameBuf_)[0] = '\0';
  391. }
  392. if(BeginPopup(openNewDirLabel_.c_str()))
  393. {
  394. ScopeGuard endNewDirPopup([] { EndPopup(); });
  395. InputText("name", newDirNameBuf_->data(), newDirNameBuf_->size());
  396. focusOnInputText |= IsItemFocused();
  397. SameLine();
  398. if(Button("ok") && (*newDirNameBuf_)[0] != '\0')
  399. {
  400. ScopeGuard closeNewDirPopup([] { CloseCurrentPopup(); });
  401. if(create_directory(pwd_ / newDirNameBuf_->data()))
  402. {
  403. UpdateFileRecords();
  404. }
  405. else
  406. {
  407. statusStr_ = "failed to create " +
  408. std::string(newDirNameBuf_->data());
  409. }
  410. }
  411. }
  412. }
  413. // browse files in a child window
  414. float reserveHeight = GetFrameHeightWithSpacing();
  415. std::filesystem::path newPwd; bool setNewPwd = false;
  416. if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory) &&
  417. (flags_ & ImGuiFileBrowserFlags_EnterNewFilename))
  418. reserveHeight += GetFrameHeightWithSpacing();
  419. {
  420. BeginChild("ch", ImVec2(0, -reserveHeight), true,
  421. (flags_ & ImGuiFileBrowserFlags_NoModal) ?
  422. ImGuiWindowFlags_AlwaysHorizontalScrollbar : 0);
  423. ScopeGuard endChild([] { EndChild(); });
  424. const bool shouldHideRegularFiles =
  425. (flags_ & ImGuiFileBrowserFlags_HideRegularFiles) &&
  426. (flags_ & ImGuiFileBrowserFlags_SelectDirectory);
  427. for(unsigned int rscIndex = 0; rscIndex < fileRecords_.size(); ++rscIndex)
  428. {
  429. auto &rsc = fileRecords_[rscIndex];
  430. if(!rsc.isDir && shouldHideRegularFiles)
  431. {
  432. continue;
  433. }
  434. if(!rsc.isDir && !IsExtensionMatched(rsc.extension))
  435. {
  436. continue;
  437. }
  438. if(!rsc.name.empty() && rsc.name.c_str()[0] == '$')
  439. {
  440. continue;
  441. }
  442. const bool selected = selectedFilenames_.find(rsc.name)
  443. != selectedFilenames_.end();
  444. if(Selectable(rsc.showName.c_str(), selected,
  445. ImGuiSelectableFlags_DontClosePopups))
  446. {
  447. const bool wantDir = flags_ & ImGuiFileBrowserFlags_SelectDirectory;
  448. const bool canSelect = rsc.name != ".." && rsc.isDir == wantDir;
  449. const bool rangeSelect =
  450. canSelect && GetIO().KeyShift &&
  451. rangeSelectionStart_ < fileRecords_.size() &&
  452. (flags_ & ImGuiFileBrowserFlags_MultipleSelection) &&
  453. IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
  454. const bool multiSelect =
  455. !rangeSelect && GetIO().KeyCtrl &&
  456. (flags_ & ImGuiFileBrowserFlags_MultipleSelection) &&
  457. IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
  458. if(rangeSelect)
  459. {
  460. const unsigned int first = (std::min)(rangeSelectionStart_, rscIndex);
  461. const unsigned int last = (std::max)(rangeSelectionStart_, rscIndex);
  462. selectedFilenames_.clear();
  463. for(unsigned int i = first; i <= last; ++i)
  464. {
  465. if(fileRecords_[i].isDir != wantDir)
  466. {
  467. continue;
  468. }
  469. if(!wantDir && !IsExtensionMatched(fileRecords_[i].extension))
  470. {
  471. continue;
  472. }
  473. selectedFilenames_.insert(fileRecords_[i].name);
  474. }
  475. }
  476. else if(selected)
  477. {
  478. if(!multiSelect)
  479. {
  480. selectedFilenames_ = { rsc.name };
  481. rangeSelectionStart_ = rscIndex;
  482. }
  483. else
  484. {
  485. selectedFilenames_.erase(rsc.name);
  486. }
  487. (*inputNameBuf_)[0] = '\0';
  488. }
  489. else if(canSelect)
  490. {
  491. if(multiSelect)
  492. {
  493. selectedFilenames_.insert(rsc.name);
  494. }
  495. else
  496. {
  497. selectedFilenames_ = { rsc.name };
  498. }
  499. if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory))
  500. {
  501. #ifdef _MSC_VER
  502. strcpy_s(
  503. inputNameBuf_->data(), inputNameBuf_->size(),
  504. u8StrToStr(rsc.name.u8string()).c_str());
  505. #else
  506. std::strncpy(inputNameBuf_->data(),
  507. u8StrToStr(rsc.name.u8string()).c_str(),
  508. inputNameBuf_->size() - 1);
  509. #endif
  510. }
  511. rangeSelectionStart_ = rscIndex;
  512. }
  513. else
  514. {
  515. if(!multiSelect)
  516. {
  517. selectedFilenames_.clear();
  518. }
  519. }
  520. }
  521. if(IsItemClicked(0) && IsMouseDoubleClicked(0))
  522. {
  523. if(rsc.isDir)
  524. {
  525. setNewPwd = true;
  526. newPwd = (rsc.name != "..") ? (pwd_ / rsc.name) :
  527. pwd_.parent_path();
  528. }
  529. else if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory))
  530. {
  531. selectedFilenames_ = { rsc.name };
  532. ok_ = true;
  533. CloseCurrentPopup();
  534. }
  535. }
  536. }
  537. }
  538. if(setNewPwd)
  539. {
  540. SetPwd(newPwd);
  541. }
  542. if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory) &&
  543. (flags_ & ImGuiFileBrowserFlags_EnterNewFilename))
  544. {
  545. PushID(this);
  546. ScopeGuard popTextID([] { PopID(); });
  547. PushItemWidth(-1);
  548. if(InputText("", inputNameBuf_->data(), inputNameBuf_->size()) &&
  549. inputNameBuf_->at(0) != '\0')
  550. {
  551. selectedFilenames_ = { inputNameBuf_->data() };
  552. }
  553. focusOnInputText |= IsItemFocused();
  554. PopItemWidth();
  555. }
  556. if(!focusOnInputText)
  557. {
  558. const bool selectAll = (flags_ & ImGuiFileBrowserFlags_MultipleSelection) &&
  559. IsKeyPressed(ImGuiKey_A) && (IsKeyDown(ImGuiKey_LeftCtrl) ||
  560. IsKeyDown(ImGuiKey_RightCtrl));
  561. if(selectAll)
  562. {
  563. const bool needDir = flags_ & ImGuiFileBrowserFlags_SelectDirectory;
  564. selectedFilenames_.clear();
  565. for(size_t i = 1; i < fileRecords_.size(); ++i)
  566. {
  567. auto &record = fileRecords_[i];
  568. if(record.isDir == needDir &&
  569. (needDir || IsExtensionMatched(record.extension)))
  570. {
  571. selectedFilenames_.insert(record.name);
  572. }
  573. }
  574. }
  575. }
  576. const bool enter =
  577. (flags_ & ImGuiFileBrowserFlags_ConfirmOnEnter) &&
  578. IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
  579. IsKeyPressed(ImGuiKey_Enter);
  580. if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory))
  581. {
  582. if((Button(" ok ") || enter) && !selectedFilenames_.empty())
  583. {
  584. ok_ = true;
  585. CloseCurrentPopup();
  586. }
  587. }
  588. else
  589. {
  590. if(Button(" ok ") || enter)
  591. {
  592. ok_ = true;
  593. CloseCurrentPopup();
  594. }
  595. }
  596. SameLine();
  597. bool shouldExit =
  598. Button("cancel") || closeFlag_ ||
  599. ((flags_ & ImGuiFileBrowserFlags_CloseOnEsc) &&
  600. IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
  601. IsKeyPressed(ImGuiKey_Escape));
  602. if(shouldExit)
  603. {
  604. CloseCurrentPopup();
  605. }
  606. if(!statusStr_.empty() && !(flags_ & ImGuiFileBrowserFlags_NoStatusBar))
  607. {
  608. SameLine();
  609. Text("%s", statusStr_.c_str());
  610. }
  611. if(!typeFilters_.empty())
  612. {
  613. SameLine();
  614. PushItemWidth(8 * GetFontSize());
  615. if(BeginCombo(
  616. "##type_filters", typeFilters_[typeFilterIndex_].c_str()))
  617. {
  618. ScopeGuard guard([&] { EndCombo(); });
  619. for(size_t i = 0; i < typeFilters_.size(); ++i)
  620. {
  621. bool selected = i == typeFilterIndex_;
  622. if(Selectable(typeFilters_[i].c_str(), selected) && !selected)
  623. {
  624. typeFilterIndex_ = static_cast<unsigned int>(i);
  625. }
  626. }
  627. }
  628. PopItemWidth();
  629. }
  630. }
  631. inline bool ImGui::FileBrowser::HasSelected() const noexcept
  632. {
  633. return ok_;
  634. }
  635. inline bool ImGui::FileBrowser::SetPwd(const std::filesystem::path &pwd)
  636. {
  637. try
  638. {
  639. SetPwdUncatched(pwd);
  640. return true;
  641. }
  642. catch(const std::exception &err)
  643. {
  644. statusStr_ = std::string("last error: ") + err.what();
  645. }
  646. catch(...)
  647. {
  648. statusStr_ = "last error: unknown";
  649. }
  650. SetPwdUncatched(std::filesystem::current_path());
  651. return false;
  652. }
  653. inline const class std::filesystem::path &ImGui::FileBrowser::GetPwd() const noexcept
  654. {
  655. return pwd_;
  656. }
  657. inline std::filesystem::path ImGui::FileBrowser::GetSelected() const
  658. {
  659. // when ok_ is true, selectedFilenames_ may be empty if SelectDirectory
  660. // is enabled. return pwd in that case.
  661. if(selectedFilenames_.empty())
  662. {
  663. return pwd_;
  664. }
  665. return pwd_ / *selectedFilenames_.begin();
  666. }
  667. inline std::vector<std::filesystem::path>
  668. ImGui::FileBrowser::GetMultiSelected() const
  669. {
  670. if(selectedFilenames_.empty())
  671. {
  672. return { pwd_ };
  673. }
  674. std::vector<std::filesystem::path> ret;
  675. ret.reserve(selectedFilenames_.size());
  676. for(auto &s : selectedFilenames_)
  677. {
  678. ret.push_back(pwd_ / s);
  679. }
  680. return ret;
  681. }
  682. inline void ImGui::FileBrowser::ClearSelected()
  683. {
  684. selectedFilenames_.clear();
  685. (*inputNameBuf_)[0] = '\0';
  686. ok_ = false;
  687. }
  688. inline void ImGui::FileBrowser::SetTypeFilters(
  689. const std::vector<std::string> &_typeFilters)
  690. {
  691. typeFilters_.clear();
  692. // remove duplicate filter names due to case unsensitivity on windows
  693. #ifdef _WIN32
  694. std::vector<std::string> typeFilters;
  695. for(auto &rawFilter : _typeFilters)
  696. {
  697. std::string lowerFilter = ToLower(rawFilter);
  698. auto it = std::find(typeFilters.begin(), typeFilters.end(), lowerFilter);
  699. if(it == typeFilters.end())
  700. {
  701. typeFilters.push_back(std::move(lowerFilter));
  702. }
  703. }
  704. #else
  705. auto &typeFilters = _typeFilters;
  706. #endif
  707. // insert auto-generated filter
  708. hasAllFilter_ = false;
  709. if(typeFilters.size() > 1)
  710. {
  711. hasAllFilter_ = true;
  712. std::string allFiltersName = std::string();
  713. for(size_t i = 0; i < typeFilters.size(); ++i)
  714. {
  715. if(typeFilters[i] == std::string_view(".*"))
  716. {
  717. hasAllFilter_ = false;
  718. break;
  719. }
  720. if(i > 0)
  721. {
  722. allFiltersName += ",";
  723. }
  724. allFiltersName += typeFilters[i];
  725. }
  726. if(hasAllFilter_)
  727. {
  728. typeFilters_.push_back(std::move(allFiltersName));
  729. }
  730. }
  731. std::copy(
  732. typeFilters.begin(), typeFilters.end(),
  733. std::back_inserter(typeFilters_));
  734. typeFilterIndex_ = 0;
  735. }
  736. inline void ImGui::FileBrowser::SetCurrentTypeFilterIndex(int index)
  737. {
  738. typeFilterIndex_ = static_cast<unsigned int>(index);
  739. }
  740. inline void ImGui::FileBrowser::SetInputName(std::string_view input)
  741. {
  742. if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename)
  743. {
  744. if(input.size() >= static_cast<size_t>(INPUT_NAME_BUF_SIZE))
  745. {
  746. // If input doesn't fit trim off characters
  747. input = input.substr(0, INPUT_NAME_BUF_SIZE - 1);
  748. }
  749. std::copy(input.begin(), input.end(), inputNameBuf_->begin());
  750. inputNameBuf_->at(input.size()) = '\0';
  751. selectedFilenames_ = { inputNameBuf_->data() };
  752. }
  753. }
  754. inline std::string ImGui::FileBrowser::ToLower(const std::string &s)
  755. {
  756. std::string ret = s;
  757. for(char &c : ret)
  758. {
  759. c = static_cast<char>(std::tolower(c));
  760. }
  761. return ret;
  762. }
  763. inline void ImGui::FileBrowser::UpdateFileRecords()
  764. {
  765. fileRecords_ = { FileRecord{ true, "..", "[D] ..", "" } };
  766. for(auto &p : std::filesystem::directory_iterator(pwd_))
  767. {
  768. FileRecord rcd;
  769. if(p.is_regular_file())
  770. {
  771. rcd.isDir = false;
  772. }
  773. else if(p.is_directory())
  774. {
  775. rcd.isDir = true;
  776. }
  777. else
  778. {
  779. continue;
  780. }
  781. rcd.name = p.path().filename();
  782. if(rcd.name.empty())
  783. {
  784. continue;
  785. }
  786. rcd.extension = p.path().filename().extension();
  787. rcd.showName = (rcd.isDir ? "[D] " : "[F] ") +
  788. u8StrToStr(p.path().filename().u8string());
  789. fileRecords_.push_back(rcd);
  790. }
  791. std::sort(fileRecords_.begin(), fileRecords_.end(),
  792. [](const FileRecord &L, const FileRecord &R)
  793. {
  794. return (L.isDir ^ R.isDir) ? L.isDir : (L.name < R.name);
  795. });
  796. ClearRangeSelectionState();
  797. }
  798. inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd)
  799. {
  800. pwd_ = absolute(pwd);
  801. UpdateFileRecords();
  802. selectedFilenames_.clear();
  803. (*inputNameBuf_)[0] = '\0';
  804. }
  805. inline bool ImGui::FileBrowser::IsExtensionMatched(
  806. const std::filesystem::path &_extension) const
  807. {
  808. #ifdef _WIN32
  809. std::filesystem::path extension = ToLower(u8StrToStr(_extension.u8string()));
  810. #else
  811. auto &extension = _extension;
  812. #endif
  813. // no type filters
  814. if(typeFilters_.empty())
  815. {
  816. return true;
  817. }
  818. // invalid type filter index
  819. if(static_cast<size_t>(typeFilterIndex_) >= typeFilters_.size())
  820. {
  821. return true;
  822. }
  823. // all type filters
  824. if(hasAllFilter_ && typeFilterIndex_ == 0)
  825. {
  826. for(size_t i = 1; i < typeFilters_.size(); ++i)
  827. {
  828. if(extension == typeFilters_[i])
  829. {
  830. return true;
  831. }
  832. }
  833. return false;
  834. }
  835. // universal filter
  836. if(typeFilters_[typeFilterIndex_] == std::string_view(".*"))
  837. {
  838. return true;
  839. }
  840. // regular filter
  841. return extension == typeFilters_[typeFilterIndex_];
  842. }
  843. inline void ImGui::FileBrowser::ClearRangeSelectionState()
  844. {
  845. rangeSelectionStart_ = 9999999;
  846. const bool dir = flags_ & ImGuiFileBrowserFlags_SelectDirectory;
  847. for(unsigned int i = 1; i < fileRecords_.size(); ++i)
  848. {
  849. if(fileRecords_[i].isDir == dir)
  850. {
  851. if(!dir && !IsExtensionMatched(fileRecords_[i].extension))
  852. {
  853. continue;
  854. }
  855. rangeSelectionStart_ = i;
  856. break;
  857. }
  858. }
  859. }
  860. #if defined(__cpp_lib_char8_t)
  861. inline std::string ImGui::FileBrowser::u8StrToStr(std::u8string s)
  862. {
  863. return std::string(s.begin(), s.end());
  864. }
  865. #endif
  866. inline std::string ImGui::FileBrowser::u8StrToStr(std::string s)
  867. {
  868. return s;
  869. }
  870. #ifdef _WIN32
  871. #ifndef _INC_WINDOWS
  872. #ifndef WIN32_LEAN_AND_MEAN
  873. #define IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN
  874. #define WIN32_LEAN_AND_MEAN
  875. #endif // #ifndef WIN32_LEAN_AND_MEAN
  876. #include <windows.h>
  877. #ifdef IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN
  878. #undef IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN
  879. #undef WIN32_LEAN_AND_MEAN
  880. #endif // #ifdef IMGUI_FILEBROWSER_UNDEF_WIN32_LEAN_AND_MEAN
  881. #endif // #ifdef _INC_WINDOWS
  882. inline std::uint32_t ImGui::FileBrowser::GetDrivesBitMask()
  883. {
  884. DWORD mask = GetLogicalDrives();
  885. uint32_t ret = 0;
  886. for(int i = 0; i < 26; ++i)
  887. {
  888. if(!(mask & (1 << i)))
  889. {
  890. continue;
  891. }
  892. char rootName[4] = { static_cast<char>('A' + i), ':', '\\', '\0' };
  893. UINT type = GetDriveTypeA(rootName);
  894. if(type == DRIVE_REMOVABLE || type == DRIVE_FIXED || type == DRIVE_REMOTE)
  895. {
  896. ret |= (1 << i);
  897. }
  898. }
  899. return ret;
  900. }
  901. #endif