build file selector list in a thread
Some checks are pending
ContinuousDelivery / linux-ubuntu (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Waiting to run
ContinuousDelivery / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Waiting to run
ContinuousDelivery / windows (push) Waiting to run
ContinuousDelivery / windows-asan (push) Waiting to run
ContinuousDelivery / dumpsyms (push) Blocked by required conditions
ContinuousDelivery / release (push) Blocked by required conditions
ContinuousIntegration / linux (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:arm64-v8a vcpkg_toolkit:arm64-android-23]) (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:armeabi-v7a vcpkg_toolkit:arm-neon-android-23]) (push) Waiting to run
ContinuousIntegration / android (map[ndk_abi:x86_64 vcpkg_toolkit:x64-android-23]) (push) Waiting to run
ContinuousIntegration / macos (push) Waiting to run
ContinuousIntegration / windows (push) Waiting to run

This commit is contained in:
Green Sky 2025-04-15 19:52:37 +02:00
parent f18d716924
commit ab40ef7cb1
No known key found for this signature in database
GPG Key ID: DBE05085D874AB4A
3 changed files with 186 additions and 122 deletions

View File

@ -18,6 +18,12 @@ FileSelector::FileSelector(void) {
reset();
}
FileSelector::~FileSelector(void) {
if (_future_cache.valid()) {
_future_cache.get();
}
}
void FileSelector::requestFile(
std::function<bool(std::filesystem::path& path)>&& is_valid,
std::function<void(const std::filesystem::path& path)>&& on_choose,
@ -101,141 +107,186 @@ void FileSelector::render(void) {
static const ImU32 dir_bg0_color = ImGui::GetColorU32(ImVec4(0.6, 0.6, 0.1, 0.15));
static const ImU32 dir_bg1_color = ImGui::GetColorU32(ImVec4(0.7, 0.7, 0.2, 0.15));
std::vector<std::filesystem::directory_entry> dirs;
std::vector<std::filesystem::directory_entry> files;
for (auto const& dir_entry : std::filesystem::directory_iterator(current_path)) {
if (dir_entry.is_directory()) {
dirs.push_back(dir_entry);
} else if (dir_entry.is_regular_file()) {
files.push_back(dir_entry);
bool start_new_collection_task {false};
if (_future_cache.valid()) {
if (_future_cache.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) {
// data is ready
_current_cache = _future_cache.get();
// also queue a new one
start_new_collection_task = true;
}
} else {
start_new_collection_task = true;
}
try {
// do sorting here
// TODO: cache the result (lol)
if (ImGuiTableSortSpecs* sorts_specs = ImGui::TableGetSortSpecs(); sorts_specs != nullptr && sorts_specs->SpecsCount >= 1) {
switch (static_cast<SortID>(sorts_specs->Specs->ColumnUserID)) {
break; case SortID::name:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.path() < b.path();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.path().filename() < b.path().filename();
});
} else {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.path() > b.path();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.path().filename() > b.path().filename();
});
}
break; case SortID::size:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
// TODO: sort dirs?
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.file_size() < b.file_size();
});
} else {
// TODO: sort dirs?
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.file_size() > b.file_size();
});
}
break; case SortID::date:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() < b.last_write_time();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() < b.last_write_time();
});
} else {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() > b.last_write_time();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() > b.last_write_time();
});
}
break; default: ;
}
}
} catch (...) {
// we likely saw a file disapear
// check if cache still current
if (_current_cache.has_value() && _current_cache.value().file_path != current_path) {
// and drop it if not
_current_cache.reset();
}
for (auto const& dir_entry : dirs) {
if (ImGui::TableNextColumn()) {
if (tmp_id & 0x01) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, dir_bg0_color);
} else {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, dir_bg1_color);
}
ImGui::PushID(tmp_id++);
if (ImGui::Selectable("D", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = dir_entry.path() / "";
}
ImGui::PopID();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted((dir_entry.path().filename().generic_u8string() + "/").c_str());
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("---");
}
if (ImGui::TableNextColumn()) {
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
dir_entry.last_write_time()
- decltype(dir_entry.last_write_time())::clock::now()
+ std::chrono::system_clock::now()
);
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
const auto ltime = std::localtime(&ctime);
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon + 1, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
}
}
// files
ImGuiListClipper files_clipper;
files_clipper.Begin(files.size());
while (files_clipper.Step()) {
for (int row = files_clipper.DisplayStart; row < files_clipper.DisplayEnd; row++) {
const auto& dir_entry = files.at(row);
if (ImGui::TableNextColumn()) {
ImGui::PushID(tmp_id++);
if (ImGui::Selectable("F", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = dir_entry.path();
if (start_new_collection_task) {
_future_cache = std::async(std::launch::async, [path = current_path, sorts_specs = ImGui::TableGetSortSpecs()](void) -> CachedData {
CachedData cd;
cd.file_path = path;
auto& dirs = cd.dirs;
auto& files = cd.files;
for (auto const& dir_entry : std::filesystem::directory_iterator(path)) {
if (dir_entry.is_directory()) {
dirs.push_back(dir_entry);
} else if (dir_entry.is_regular_file()) {
files.push_back(dir_entry);
}
ImGui::PopID();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(dir_entry.path().filename().generic_u8string().c_str());
try {
// do sorting here
if (sorts_specs != nullptr && sorts_specs->SpecsCount >= 1) {
switch (static_cast<SortID>(sorts_specs->Specs->ColumnUserID)) {
break; case SortID::name:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.path() < b.path();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.path().filename() < b.path().filename();
});
} else {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.path() > b.path();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.path().filename() > b.path().filename();
});
}
break; case SortID::size:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
// TODO: sort dirs?
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.file_size() < b.file_size();
});
} else {
// TODO: sort dirs?
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.file_size() > b.file_size();
});
}
break; case SortID::date:
if (sorts_specs->Specs->SortDirection == ImGuiSortDirection_Descending) {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() < b.last_write_time();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() < b.last_write_time();
});
} else {
std::sort(dirs.begin(), dirs.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() > b.last_write_time();
});
std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) -> bool {
return a.last_write_time() > b.last_write_time();
});
}
break; default: ;
}
}
} catch (...) {
// we likely saw a file disapear
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("%s", std::to_string(dir_entry.file_size()).c_str());
}
return cd;
});
}
if (ImGui::TableNextColumn()) {
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
dir_entry.last_write_time()
- decltype(dir_entry.last_write_time())::clock::now()
+ std::chrono::system_clock::now()
);
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
if (_current_cache.has_value()) {
const auto& dirs = _current_cache.value().dirs;
const auto& files = _current_cache.value().files;
const auto ltime = std::localtime(&ctime);
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
// dirs
ImGuiListClipper dirs_clipper;
dirs_clipper.Begin(dirs.size());
while (dirs_clipper.Step()) {
for (int row = dirs_clipper.DisplayStart; row < dirs_clipper.DisplayEnd; row++) {
const auto& dir_entry = dirs.at(row);
if (ImGui::TableNextColumn()) {
if (tmp_id & 0x01) {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, dir_bg0_color);
} else {
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1, dir_bg1_color);
}
ImGui::PushID(tmp_id++);
if (ImGui::Selectable("D", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = dir_entry.path() / "";
}
ImGui::PopID();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted((dir_entry.path().filename().generic_u8string() + "/").c_str());
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("---");
}
if (ImGui::TableNextColumn()) {
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
dir_entry.last_write_time()
- decltype(dir_entry.last_write_time())::clock::now()
+ std::chrono::system_clock::now()
);
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
const auto ltime = std::localtime(&ctime);
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon + 1, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
}
}
}
// files
ImGuiListClipper files_clipper;
files_clipper.Begin(files.size());
while (files_clipper.Step()) {
for (int row = files_clipper.DisplayStart; row < files_clipper.DisplayEnd; row++) {
const auto& file_entry = files.at(row);
if (ImGui::TableNextColumn()) {
ImGui::PushID(tmp_id++);
if (ImGui::Selectable("F", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
_current_file_path = file_entry.path();
}
ImGui::PopID();
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted(file_entry.path().filename().generic_u8string().c_str());
}
if (ImGui::TableNextColumn()) {
ImGui::TextDisabled("%s", std::to_string(file_entry.file_size()).c_str());
}
if (ImGui::TableNextColumn()) {
const auto file_time_converted = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
file_entry.last_write_time()
- decltype(file_entry.last_write_time())::clock::now()
+ std::chrono::system_clock::now()
);
const auto ctime = std::chrono::system_clock::to_time_t(file_time_converted);
const auto ltime = std::localtime(&ctime);
ImGui::TextDisabled("%2d.%2d.%2d - %2d:%2d", ltime->tm_mday, ltime->tm_mon, ltime->tm_year + 1900, ltime->tm_hour, ltime->tm_min);
}
}
}
} else {
// render loading placeholder
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted("-");
}
if (ImGui::TableNextColumn()) {
ImGui::TextUnformatted("... loading ...");
}
}
ImGui::EndTable();

View File

@ -2,10 +2,20 @@
#include <filesystem>
#include <functional>
#include <optional>
#include <future>
struct FileSelector {
std::filesystem::path _current_file_path = std::filesystem::canonical(".") / ""; // add /
struct CachedData {
std::filesystem::path file_path; // can be used to check against current
std::vector<std::filesystem::directory_entry> dirs;
std::vector<std::filesystem::directory_entry> files;
};
std::optional<CachedData> _current_cache;
std::future<CachedData> _future_cache;
bool _open_popup {false};
std::function<bool(std::filesystem::path& path)> _is_valid = [](auto){ return true; };
@ -16,6 +26,7 @@ struct FileSelector {
public:
FileSelector(void);
~FileSelector(void);
// TODO: supply hints
// HACK: until we supply hints, is_valid can modify

View File

@ -25,7 +25,9 @@ void ImageViewerPopup::render(float) {
ImGui::OpenPopup("Image##ImageViewerPopup");
}
if (!ImGui::BeginPopup("Image##ImageViewerPopup", ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize)) {
// TODO: remplace with modal(?), pop up i limited to viewport in size
// or replace with fixed window where the image can be moved
if (!ImGui::BeginPopup("Image##ImageViewerPopup", ImGuiWindowFlags_NoDecoration)) {
_m = {}; // meh, event on close would be nice, but the reset is cheap
_scale = 1.f;
return;