25 std::map<std::string, FileBrowserNode*> children;
31std::deque<FileBrowserNode> s_nodePool;
33FileBrowserNode* allocNode()
35 s_nodePool.emplace_back();
36 return &s_nodePool.back();
45const char* s_filterLabels[] = {
61const char* s_filterExtensions[] = {
62 "m2",
"wmo",
"adt",
"wav",
"ogg",
"mp3",
63 "blp",
"bls",
"dbc",
"db2",
"lua",
"xml",
"skin"
66constexpr int s_filterCount =
sizeof(s_filterLabels) /
sizeof(s_filterLabels[0]);
70char s_searchBuf[256] = {};
71FileBrowserNode* s_fileTreeRoot =
nullptr;
72bool s_fileTreeDirty =
true;
73int s_fileTreeFileCount = 0;
79 s_fileTreeRoot = allocNode();
80 s_fileTreeRoot->name =
"Root";
83 auto s = search.find_first_not_of(
" \t\r\n");
84 auto e = search.find_last_not_of(
" \t\r\n");
85 search = (s == std::string::npos) ?
"" : search.substr(s, e - s + 1);
87 const std::string ext = std::string(
".") + s_filterExtensions[s_filterMode];
89 s_fileTreeFileCount = 0;
92 const auto& fname = gf->fullname();
98 ++s_fileTreeFileCount;
100 std::string displayName = std::format(
"{} [{}]", fname, gf->fileDataId());
101 std::transform(displayName.begin(), displayName.end(), displayName.begin(),
102 [](
unsigned char c) { return static_cast<char>(std::tolower(c)); });
103 std::replace(displayName.begin(), displayName.end(),
'/',
'\\');
104 if (!displayName.empty())
105 displayName[0] =
static_cast<char>(std::toupper(
static_cast<unsigned char>(displayName[0])));
108 FileBrowserNode* cur = s_fileTreeRoot;
110 for (
int i = 0; i < static_cast<int>(parts.size()) - 1; ++i)
112 auto it = cur->children.find(parts[i]);
113 if (it == cur->children.end())
115 auto* child = allocNode();
116 child->name = parts[i];
117 cur->children[parts[i]] = child;
126 auto* leaf = allocNode();
127 leaf->name = parts.back();
129 cur->children[parts.back()] = leaf;
132 s_fileTreeDirty =
false;
133 LOG_INFO <<
"File tree rebuilt: " << s_fileTreeFileCount <<
" files matching filter.";
146 if (ImGui::Begin(
"File Browser", &visible))
152 ImGui::Text(
"Loading...");
153 ImGui::ProgressBar(load.progress, ImVec2(-1, 0));
154 ImGui::TextWrapped(
"%s", load.statusText);
158 if (load.statusText && load.statusText[0] !=
'\0')
159 ImGui::TextWrapped(
"%s", load.statusText);
161 ImGui::TextWrapped(
"Game not loaded. Use Settings panel to set the WoW path and click Load WoW.");
167 ImGui::Text(
"Filter:");
168 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
169 if (ImGui::Combo(
"##filter", &s_filterMode, s_filterLabels, s_filterCount))
170 s_fileTreeDirty =
true;
172 ImGui::Text(
"Search:");
173 ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 60.0f);
174 if (ImGui::InputText(
"##search", s_searchBuf,
sizeof(s_searchBuf),
175 ImGuiInputTextFlags_EnterReturnsTrue))
176 s_fileTreeDirty =
true;
178 if (ImGui::Button(
"Apply", ImVec2(-1, 0)))
179 s_fileTreeDirty =
true;
185 ImGui::Text(
"Files: %d", s_fileTreeFileCount);
189 ImGui::BeginChild(
"FileTree", ImVec2(0, 0), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar);
192 std::function<void(FileBrowserNode*)> drawNode = [&](FileBrowserNode* node)
194 for (
auto& [name, child] : node->children)
198 ImGuiTreeNodeFlags leafFlags =
199 ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen |
200 ImGuiTreeNodeFlags_SpanAvailWidth;
201 ImGui::TreeNodeEx(child->name.c_str(), leafFlags);
202 if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
203 selected = child->file;
207 bool open = ImGui::TreeNodeEx(child->name.c_str(),
208 ImGuiTreeNodeFlags_SpanAvailWidth);
217 drawNode(s_fileTreeRoot);
229 s_fileTreeDirty =
true;
235 s_fileTreeRoot =
nullptr;
Abstract base class representing a file within the game data archive.
GameFile * draw(bool &visible, const LoadState &load)
void shutdown()
Free all internal allocations (call at application shutdown).
void markDirty()
Mark the file tree as dirty so it will be rebuilt on the next draw().
std::vector< std::string > split(const std::string &s, char delimiter)
Split a string by a single-character delimiter.
bool containsIgnoreCase(const std::string &s, const std::string &substr)
bool endsWithIgnoreCase(const std::string &s, const std::string &suffix)
std::string toLower(const std::string &s)