dlxjj/imradv3
0262
1#include "ui_explorer.h"2#include "ui_message_box.h"3#include "utils.h"4#include "stx.h"5#include "imrad.h"6#include "cppgen.h"7#include <sstream>8#include <chrono>9#include <IconsFontAwesome6.h>10 11struct ExplorerEntry12{13 std::string path;14 bool folder;15 bool generated;16 std::string fileName;17 std::time_t last_write_time;18 std::string modified;19};20 21std::string explorerPath;22int explorerFilter = 0;23ImGuiTableColumnSortSpecs explorerSorting;24 25static int pathSel = -1;26static std::vector<ExplorerEntry> data;27static std::vector<std::string> suggestions;28static int suggestionSel = -1;29static std::string lastPath;30static bool scrollBack = false;31static bool showSuggestions = false;32static bool autocompleted = false;33enum MoveFocus { None, PathInput, FirstEntry };34static MoveFocus moveFocus = None;35 36bool IsHeaderFile(const fs::path& p)37{38 auto ext = u8string(p.extension());39 return ext == ".h" || ext == ".hpp" || ext == ".hxx";40}41 42bool IsJsonFile(const fs::path& p)43{44 auto ext = u8string(p.extension());45 return ext == ".json" || ext == ".JSON";46}47 48bool IsImageFile(const fs::path& p)49{50 auto ext = u8string(p.extension());51 return ext == ".jpg" || ext == ".JPG" || ext == ".jpeg" || ext == ".JPEG" || ext == ".png" || ext == ".PNG" || ext == ".bmp" || ext == ".BMP";52}53 54bool IsCppFile(const fs::path& p)55{56 auto ext = u8string(p.extension());57 return ext == ".c" || ext == ".cpp" || ext == ".cxx";58}59 60void ReloadExplorer(const CppGen& codeGen)61{62 data.clear();63 auto path = u8path(explorerPath);64 //+= '/' so it works with paths like "c:"65 if (!path.has_root_directory())66 path = u8path(explorerPath + (char)fs::path::preferred_separator);67 path.make_preferred();68 explorerPath = u8string(path);69 70 if (!fs::is_directory(path)) {71 messageBox.title = "Error";72 messageBox.message = "Can't open \"" + explorerPath + "\"";73 messageBox.buttons = ImRad::Ok;74 messageBox.OpenPopup();75 return;76 }77 78 scrollBack = true;79 std::ostringstream os;80 auto fsnow = fs::file_time_type::clock::now();81 auto snow = std::chrono::system_clock::now();82 os.imbue(std::locale(""));83 std::error_code ec;84 for (fs::directory_iterator it(path, ec); it != fs::directory_iterator(); ++it)85 {86 if (u8string(it->path().stem())[0] == '.')87 continue;88 if (!it->is_directory() &&89 //(!explorerFilter && !IsHeaderFile(it->path())))90 (!explorerFilter && !IsImageFile(it->path())))91 continue;92 ExplorerEntry entry;93 entry.path = u8string(it->path());94 entry.folder = it->is_directory();95 entry.generated = (IsHeaderFile(it->path()) || IsCppFile(it->path())) &&96 codeGen.ReadGenVersion(entry.path);97 entry.fileName = u8string(it->path().filename());98 auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(99 it->last_write_time() - fsnow + snow);100 entry.last_write_time = std::chrono::system_clock::to_time_t(sctp);101 auto* tm = std::localtime(&entry.last_write_time);102 os.str("");103 os << std::put_time(tm, "%x %X");104 entry.modified = os.str();105 data.push_back(std::move(entry));106 }107 stx::sort(data, [](const ExplorerEntry& a, const ExplorerEntry& b) {108 if (a.folder != b.folder)109 return a.folder;110 if (!explorerSorting.ColumnIndex) {111 if (explorerSorting.SortDirection == ImGuiSortDirection_Ascending)112 return path_cmp(a.fileName, b.fileName);113 else114 return path_cmp(b.fileName, a.fileName);115 }116 else {117 if (explorerSorting.SortDirection == ImGuiSortDirection_Ascending)118 return a.last_write_time < b.last_write_time;119 else120 return a.last_write_time > b.last_write_time;121 }122 });123 if (!u8path(explorerPath).relative_path().empty()) {124 std::string ppath = u8string(u8path(explorerPath).parent_path());125 ExplorerEntry e;126 e.path = ppath;127 e.folder = true;128 e.fileName = "..";129 data.insert(data.begin(), e);130 }131}132 133void GetSuggestions()134{135 suggestions.clear();136 std::error_code ec;137 auto path = u8path(explorerPath);138 //+= '/' so it works with paths like "c:"139 if (!path.has_root_directory())140 path = u8path(explorerPath + (char)fs::path::preferred_separator);141 std::string match = u8string(path.stem());142 for (fs::directory_iterator it(path.parent_path(), ec); it != fs::directory_iterator(); ++it)143 {144 std::string stem = u8string(it->path().stem());145 if (!it->is_directory() || stem[0] == '.')146 continue;147 if (stem.size() > match.size())148 stem.resize(match.size());149 if (path_cmp(stem, match) || path_cmp(match, stem))150 continue;151 std::string fileName = u8string(it->path().filename());152 suggestions.push_back(std::move(fileName));153 }154 stx::sort(suggestions, [](const std::string& a, const std::string& b) {155 return path_cmp(a, b);156 });157}158 159int ExplorerPathCallback(ImGuiInputTextCallbackData* data)160{161 if (data->EventFlag == ImGuiInputTextFlags_CallbackCharFilter) {162 return DefaultCharFilter(data);163 }164 else if (data->EventFlag == ImGuiInputTextFlags_CallbackHistory)165 {166 if (data->EventKey == ImGuiKey_UpArrow && suggestions.size()) {167 if (suggestionSel < 0) {168 suggestionSel = (int)suggestions.size() - 1;169 lastPath = explorerPath;170 }171 else if (--suggestionSel < 0)172 suggestionSel = -1;173 }174 else if (data->EventKey == ImGuiKey_DownArrow && suggestions.size()) {175 if (suggestionSel < 0) {176 suggestionSel = 0;177 lastPath = explorerPath;178 }179 else if (++suggestionSel == suggestions.size())180 suggestionSel = -1;181 }182 183 //commit suggestion184 std::string str = lastPath;185 if (suggestionSel >= 0) {186 size_t i = explorerPath.find_last_of("/\\");187 if (i == std::string::npos)188 str = explorerPath + (char)fs::path::preferred_separator;189 else190 str = explorerPath.substr(0, i + 1);191 str += suggestions[suggestionSel];192 }193 if (str.size() + 1 < data->BufSize) { //no reallocation occured194 autocompleted = true;195 data->BufDirty = true;196 strcpy(data->Buf, str.c_str());197 data->BufTextLen = (int)str.size();198 data->SelectionStart = data->SelectionEnd = data->CursorPos = data->BufTextLen;199 }200 }201 return 0;202}203 204void ExplorerUI(const CppGen& codeGen, std::function<void(const std::string& fpath)> openFileFunc)205{206 explorerPath.reserve(1024); //ImGuiInputText_HistoryCallback doesn't allow buffer reallocation??207 if (explorerPath == "")208 explorerPath = u8string(fs::current_path());209 210 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 4, 4 });211 ImGui::Begin("Explorer", 0, ImGuiWindowFlags_NoCollapse);212 if (ImGui::IsWindowAppearing()) {213 ReloadExplorer(codeGen);214 showSuggestions = false;215 autocompleted = false;216 moveFocus = FirstEntry;217 }218 219 //clickable path box220 const int sp = 4;221 const std::string ELIDE_STR = "...";222 std::string inputId = "##explorerCwd";223 if (ImGui::GetFocusID() != ImGui::GetID(inputId.c_str()))224 {225 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImGui::GetStyle().FramePadding);226 ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, ImGui::GetStyle().FrameRounding);227 ImGui::PushStyleColor(ImGuiCol_ChildBg, 0xffd0d0d0);228 ImGui::PushStyleColor(ImGuiCol_Border, 0xffb0b0b0);229 if (ImGui::BeginChild("cwdChild", { -ImGui::GetFrameHeight() - sp, ImGui::GetFrameHeight() }, ImGuiChildFlags_Borders, ImGuiWindowFlags_NavFlattened | ImGuiWindowFlags_NoScrollbar))230 {231 fs::path path = u8path(explorerPath);232 char separator = (char)fs::path::preferred_separator;233 int elideStart = (int)path.has_root_name() + (int)path.has_root_directory();234 int elideN = 0;235 ImVec2 wpad = ImGui::GetStyle().WindowPadding;236 int n = 0;237 for (const auto& p : path)238 ++n;239 for (; elideStart + elideN < n; ++elideN) {240 std::string tmp;241 int i = 0;242 for (const auto& p : path) {243 if (i < elideStart)244 tmp += u8string(p);245 else if (i == elideStart && elideN)246 tmp += ELIDE_STR + separator;247 else if (i >= elideStart + elideN)248 tmp += u8string(p) + separator;249 ++i;250 }251 if (tmp.back() == separator)252 tmp.pop_back();253 if (elideStart + elideN + 1 == n ||254 ImGui::CalcTextSize(tmp.c_str()).x + 2 * wpad.x < ImGui::GetWindowWidth())255 break;256 }257 int i = 0;258 int rootDirIdx = path.has_root_directory() ? (int)path.has_root_name() : -1;259 int lastPathSel = pathSel;260 pathSel = -1;261 bool sepSel = false;262 fs::path subpath;263 for (const auto& p : path) {264 subpath /= p;265 if (i >= elideStart && i < elideStart + elideN) {266 ++i;267 continue;268 }269 if (p.has_root_directory()) {270 ImGui::SameLine(0, 0);271 ImGui::Text("%s", u8string(p).c_str());272 if (ImGui::IsItemHovered())273 sepSel = true;274 ++i;275 continue;276 }277 if (i && i != rootDirIdx + 1) {278 ImGui::SameLine(0, 0);279 std::string sep = elideN && i == elideStart + elideN ? ELIDE_STR : "";280 sep += separator;281 ImGui::Text("%s", sep.c_str());282 if (ImGui::IsItemHovered())283 sepSel = true;284 }285 ImGui::SameLine(0, 0);286 ImGui::PushStyleColor(ImGuiCol_Text, i == lastPathSel ? 0xff0000ff : 0xff000000);287 ImGui::Text("%s", u8string(p).c_str());288 ImGui::PopStyleColor();289 if (ImGui::IsItemHovered() && i + 1 != n)290 pathSel = i;291 if (ImGui::IsItemClicked() && i + 1 != n) {292 explorerPath = u8string(subpath);293 ReloadExplorer(codeGen);294 moveFocus = FirstEntry;295 }296 ++i;297 }298 if (ImGui::IsWindowHovered() && !sepSel && pathSel < 0)299 {300 ImGui::SetMouseCursor(ImGuiMouseCursor_TextInput);301 if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))302 moveFocus = PathInput;303 }304 }305 ImGui::EndChild();306 ImGui::PopStyleVar(2);307 ImGui::PopStyleColor(2);308 }309 310 ImGui::SameLine(0, 0);311 if (ImGui::GetContentRegionAvail().x - ImGui::GetFrameHeight() - sp <= 0)312 ImGui::SetNextItemWidth(0);313 else314 ImGui::SetNextItemWidth(-ImGui::GetFrameHeight() - sp);315 //ImGui::PushStyleColor(ImGuiCol_FrameBg, 0xffd0d0d0);316 //ImGui::PushStyleColor(ImGuiCol_Border, 0xffb0b0b0);317 ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1);318 int fl = ImGuiInputTextFlags_ElideLeft | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackCharFilter;319 if (ImGui::InputText(inputId.c_str(), &explorerPath, fl, ExplorerPathCallback)) {320 if (autocompleted)321 autocompleted = false;322 else {323 GetSuggestions();324 showSuggestions = true;325 suggestionSel = -1;326 }327 }328 //ImGui::PopStyleColor(2);329 ImGui::PopStyleVar();330 if (moveFocus == PathInput)331 {332 ImGui::SetKeyboardFocusHere(-1);333 auto* state = ImGui::GetInputTextState(ImGui::GetItemID());334 if (state)335 state->ClearSelection();336 if (ImGui::IsItemFocused())337 moveFocus = None;338 }339 if (ImGui::IsItemDeactivated()) { //AfterEdit())340 ReloadExplorer(codeGen);341 showSuggestions = false;342 moveFocus = FirstEntry;343 }344 if (showSuggestions)345 {346 ImGui::PushFont(ImRad::GetFontByName("imrad.explorer"));347 ImGui::SetNextWindowPos({ ImGui::GetItemRectMin().x, ImGui::GetItemRectMax().y });348 float h = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemInnerSpacing.y;349 float pad = ImGui::GetStyle().WindowPadding.y;350 const int N = 7;351 ImGui::SetNextWindowSize({ ImGui::GetItemRectSize().x, std::min(N, (int)suggestions.size()) * h + 2 * pad });352 float selY = std::max(suggestionSel, 0) * h + pad;353 if (ImGui::BeginTooltip()) //Tooltip won't steal the focus as BeginPopup does354 {355 if (selY + h > ImGui::GetScrollY() + ImGui::GetWindowSize().y)356 ImGui::SetScrollY(selY - (N - 1) * h - pad);357 if (selY < ImGui::GetScrollY())358 ImGui::SetScrollY(selY - pad);359 for (size_t i = 0; i < suggestions.size(); ++i) {360 /*ImGui::PushStyleColor(ImGuiCol_Text, 0xff40b0b0);361 ImGui::Selectable(ICON_FA_FOLDER, i == suggestionSel, ImGuiSelectableFlags_SpanAvailWidth);362 ImGui::PopStyleColor();363 ImGui::SameLine(0, 4);*/364 ImGui::Selectable(suggestions[i].c_str(), i == suggestionSel, 0);365 }366 ImGui::EndTooltip();367 }368 ImGui::PopFont();369 }370 371 ImGui::SameLine(0, sp);372 ImGui::PushStyleColor(ImGuiCol_Text, 0xff404040);373 ImGui::PushItemFlag(ImGuiItemFlags_NoNav, true);374 if (ImGui::Button(ICON_FA_ROTATE_RIGHT "##ego")) {375 ReloadExplorer(codeGen);376 moveFocus = FirstEntry;377 }378 ImGui::PopItemFlag();379 ImGui::PopStyleColor();380 381 ImGui::PushStyleColor(ImGuiCol_TableRowBg, 0xffffffff);382 ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, 0xffffffff);383 if (scrollBack) {384 scrollBack = false;385 ImGui::SetNextWindowScroll({ 0, 0 });386 }387 float h = -ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.y;388 if (ImGui::BeginTable("files", 2, ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, { -1, h }))389 {390 ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch);391 ImGui::TableSetupColumn("Modified", ImGuiTableColumnFlags_WidthFixed, 140);392 ImGui::TableSetupScrollFreeze(0, 1);393 auto* spec = ImGui::TableGetSortSpecs();394 if (spec && spec->SpecsDirty) {395 spec->SpecsDirty = false;396 explorerSorting = *spec->Specs;397 ReloadExplorer(codeGen);398 }399 ImGui::PushItemFlag(ImGuiItemFlags_NoNav, true);400 ImGui::TableHeadersRow();401 ImGui::PopItemFlag();402 ImGui::TableNextRow();403 ImGui::TableSetColumnIndex(0);404 405 ImGui::PushFont(ImRad::GetFontByName("imrad.explorer"));406 int n = 0;407 for (const auto& entry : data)408 {409 ImGui::PushID(n++);410 bool source = IsHeaderFile(entry.path) || IsCppFile(entry.path);411 ImGui::PushStyleColor(ImGuiCol_Text,412 entry.folder ? 0xff40b0b0 :413 entry.generated ? 0xffe0a060 :414 source ? 0xff60a0d0 :415 0xffa0a0a0416 );417 ImGui::Selectable(entry.folder ? ICON_FA_FOLDER :418 entry.generated ? ICON_FA_DICE_D6 :419 source ? ICON_FA_FILE_CODE :420 ICON_FA_FILE,421 false, ImGuiSelectableFlags_SpanAllColumns);422 ImGui::PopStyleColor();423 if (n == 1 && moveFocus == FirstEntry) {424 if (ImGui::IsItemFocused())425 moveFocus = None;426 ImGui::SetKeyboardFocusHere(-1);427 }428 if (ImRad::IsItemDoubleClicked() ||429 (ImGui::IsItemActive() && ImGui::IsKeyPressed(ImGuiKey_Enter)))430 {431 if (entry.folder) {432 explorerPath = entry.path;433 ReloadExplorer(codeGen);434 ImGui::PopID();435 break;436 }437 //else if (!codeGen.ReadGenVersion(entry.path)) {438 // ShellExec(entry.path);439 //}440 else {441 openFileFunc(entry.path);442 }443 }444 ImGui::PushItemFlag(ImGuiItemFlags_NoNav, true);445 ImGui::SameLine(0, 4);446 ImGui::Selectable(entry.fileName.c_str(), false);447 ImGui::TableNextColumn();448 ImGui::Selectable(entry.modified.c_str(), false, ImGuiSelectableFlags_Disabled);449 ImGui::TableNextColumn();450 ImGui::PopItemFlag();451 ImGui::PopID();452 }453 ImGui::PopFont();454 ImGui::EndTable();455 }456 ImGui::PopStyleColor(2);457 458 ImGui::PushStyleColor(ImGuiCol_FrameBg, 0xffd0d0d0);459 ImGui::PushStyleColor(ImGuiCol_Border, 0xffb0b0b0);460 ImGui::SetNextItemWidth(-1);461 if (ImGui::Combo("##filter", &explorerFilter, "IMAGE Files (*.jpg,*.png,*.bmp)\0All Files (*.*)\0"))462 ReloadExplorer(codeGen);463 ImGui::PopStyleColor(2);464 465 ImGui::End();466 ImGui::PopStyleVar();467}468 