EchoMap 2026-07-25 6d3977c
An experimental cross-platform digital signal processing application for sound-source localisation.
Loading...
Searching...
No Matches
FilesystemCombo.cpp
Go to the documentation of this file.
1
9
10#if !defined(__EMSCRIPTEN__) || defined(__DOXYGEN__)
11
12#include "FilesystemCombo.hpp"
13
14#include <imgui.h>
15
16#include <algorithm>
17#include <cstring>
18
19#include "../../EchoMap.hpp"
20
21namespace echomap
22{
23
25 EchoMap* const app
26) :
27 app(app)
28{
29 std::error_code error_code;
30 const auto cwd = std::filesystem::current_path(error_code);
31
32 if (error_code || cwd.empty() || !std::filesystem::is_directory(cwd))
33 throw std::runtime_error("Could not get the current working directory. Is the filesystem readable?");
34
36}
37
39 std::filesystem::path& selected_path
40)
41{
42 bool changed = false;
43
44 ImGui::SetNextItemWidth(-std::numeric_limits<float>::min());
45
46 const std::string preview = selected_path.empty() ? std::string{current_root.data()} : selected_path.string();
47
48 if (ImGui::BeginCombo("##FilesystemCombo", preview.c_str(), ImGuiComboFlags_HeightLarge)) {
49 if (ImGui::IsWindowAppearing()) {
50 ImGui::SetKeyboardFocusHere();
52 }
53
54 changed = draw_combo_body(selected_path);
55
56 ImGui::EndCombo();
57 app->increment_forced_frames();
58 }
59
60 return changed;
61}
62
65 const bool is_directory_hint
66) :
67 path(path),
68 name(path.filename().string()),
69 is_directory(is_directory_hint),
70 imgui_id((is_directory ? name + '/' : name) + "##" + path.string()),
71 flags(is_directory ? ImGuiSelectableFlags_NoAutoClosePopups : ImGuiSelectableFlags_None)
72{
74}
75
76bool FilesystemCombo::Entry::draw() const noexcept
77{
78 return ImGui::Selectable(imgui_id.c_str(), false, flags);
79}
80
82{
83 cache.reset();
84}
85
88) :
90{
91 std::error_code error_code;
92
95 std::filesystem::directory_options::skip_permission_denied |
96 std::filesystem::directory_options::follow_directory_symlink,
97 error_code
98 };
99
101
102 while (!error_code && iterator != end) {
103 const std::filesystem::directory_entry& directory_entry = *iterator;
104 std::error_code entry_error_code;
105 const std::filesystem::file_status status = directory_entry.status(entry_error_code);
106
107 if (!entry_error_code) {
108 // ReSharper disable CppTooWideScopeInitStatement
109 const bool is_directory = std::filesystem::is_directory(status);
110 const bool is_regular_file = std::filesystem::is_regular_file(status);
111 // ReSharper restore CppTooWideScopeInitStatement
112
113 if (is_directory || is_regular_file)
114 entries.emplace_back(directory_entry.path(), is_directory);
115 }
116
117 iterator.increment(error_code);
118 }
119
121}
122
124 const std::filesystem::path& path
125)
126{
127 const std::string path_string = path.string();
128 const std::size_t length = std::min(current_root.size() - 1, path_string.size());
129
130 std::memcpy(current_root.data(), path_string.data(), length);
131 current_root[length] = '\0';
132
133 current_target = get_browse_target();
134}
135
136bool FilesystemCombo::draw_combo_body(
137 std::filesystem::path& selected_path
138)
139{
140 ImGui::SetNextItemWidth(-std::numeric_limits<float>::min());
141 const std::string old_root = current_root.data();
142 const bool enter_pressed =
143 ImGui::InputText("##path", current_root.data(), current_root.size(), ImGuiInputTextFlags_EnterReturnsTrue);
144
145 bool changed = false;
146
147 if (old_root != current_root.data())
148 // If the browse target has changed, update the cached member variable.
149 current_target = get_browse_target();
150
151 if (enter_pressed) {
152 // If the user has explicitly pressed 'enter', accept whatever is selected at this time.
153 accept_path(
154 selected_path,
156 );
157
158 changed = true;
159 ImGui::CloseCurrentPopup();
160 }
161
162 ImGui::Separator();
163
165 ImGui::TextDisabled("Not a readable directory.");
166 else {
167 changed |= draw_parent_entry(current_target.parent_directory);
168 if (!cache || cache->directory != current_target.parent_directory)
170
171 changed |= draw_child_entries(current_target.filter, selected_path);
172 }
173
174 return changed;
175}
176
177FilesystemCombo::BrowseTarget FilesystemCombo::get_browse_target() const
178{
179 std::error_code error_code;
180 const std::string typed_string = current_root.data();
181 auto const typed_path = typed_string.empty() ? std::filesystem::path{} : std::filesystem::path{typed_string};
182 auto lookup_path = typed_path;
183
184 if (lookup_path.empty())
185 lookup_path = std::filesystem::current_path(error_code);
186 else if (lookup_path.is_relative())
187 lookup_path = std::filesystem::absolute(lookup_path, error_code);
188
189 if (error_code || lookup_path.empty())
190 lookup_path = ".";
191
192 error_code.clear();
193
194 if (std::filesystem::is_directory(lookup_path, error_code))
195 return {.target_path = lookup_path, .parent_directory = lookup_path, .filter = {}};
196
197 std::filesystem::path directory = lookup_path.parent_path();
198
199 if (directory.empty()) {
200 error_code.clear();
201 directory = std::filesystem::current_path(error_code);
202
203 if (error_code || directory.empty())
204 directory = ".";
205 }
206
207 return {.target_path = lookup_path, .parent_directory = directory, .filter = lookup_path.filename().string()};
208}
209
210bool FilesystemCombo::draw_parent_entry(
211 const std::filesystem::path& directory
212)
213{
214 const std::filesystem::path parent = directory.parent_path();
215
216 if (parent.empty() || parent == directory)
217 return false;
218
219 if (ImGui::Selectable("../", false, ImGuiSelectableFlags_NoAutoClosePopups)) {
220 update_current_state(parent);
221 return true;
222 }
223
224 return false;
225}
226
227bool FilesystemCombo::draw_child_entries(
228 const std::string_view filter,
229 std::filesystem::path& selected_path
230)
231{
232 assert(cache.has_value());
233
234 bool has_visible_entries = false;
235 bool changed = false;
236
237 for (const auto& entry : cache->entries) {
238 if (!filter.empty() && !entry.name.contains(filter))
239 continue;
240
241 has_visible_entries = true;
242
243 if (!entry.draw())
244 continue;
245
246 if (entry.is_directory) {
247 update_current_state(entry.path);
248 return false;
249 }
250
251 selected_path = entry.path;
252 update_current_state(entry.path.parent_path());
253
254 changed = true;
255 ImGui::CloseCurrentPopup();
256
257 return changed;
258 }
259
260 if (!has_visible_entries)
261 ImGui::TextDisabled("No matching entries.");
262
263 return changed;
264}
265
266void FilesystemCombo::accept_path(
267 std::filesystem::path& selected_path,
268 const std::filesystem::path& path
269)
270{
273 return;
274 }
275
276 selected_path = path;
277
278 if (path.has_parent_path())
280 else
282}
283
284} // namespace echomap
285
286#endif // __EMSCRIPTEN__
EchoMap class specification.
FilesystemCombo specification.
T absolute(T... args)
T c_str(T... args)
The EchoMap maintains state for the application including WebGPU and Dear ImGui context,...
Definition EchoMap.hpp:35
void invalidate_cache()
Invalidates the EntryCache.
bool operator()(std::filesystem::path &selected_path)
Draws the FilesystemCombo control.
FilesystemCombo(EchoMap *app)
Sets up a new FilesystemCombo with the given EchoMap application context.
std::optional< EntryCache > cache
Cache of entries visible in the combo box.
BrowseTarget current_target
The current target specified in the filter window.
EchoMap * app
Pointer to the owning application instance.
void update_current_state(const std::filesystem::path &path)
Copies and formats the given path into the fixed-size current directory state member variable.
std::array< char, max_path_length > current_root
The current raw string entered by the user.
T clear(T... args)
T current_path(T... args)
T data(T... args)
T filename(T... args)
T has_parent_path(T... args)
T is_directory(T... args)
T is_regular_file(T... args)
T memcpy(T... args)
T min(T... args)
The main EchoMap outermost namespace for all non-exported symbols.
T parent_path(T... args)
T size(T... args)
T sort(T... args)
std::filesystem::path parent_directory
The parent directory of the target path.
std::filesystem::path target_path
The selected path.
std::string filter
Cached string of the target path filename.
EntryCache(const std::filesystem::path &directory)
Creates a new EntryCache and populates it for the given root directory.
std::filesystem::path directory
The root directory on the file-system.
std::vector< Entry > entries
The first-level children of the root.
bool is_directory
Directory sentinel flag.
std::string name
The cached display name of the path.
std::string imgui_id
Dear ImGui display name, including ID disambiguator.
std::filesystem::path path
The path represented by the Entry.
Entry(const std::filesystem::path &path, bool is_directory_hint)
Create a new Entry for a regular file or directory.
ImGuiSelectableFlags flags
Dear ImGui flags for rendering the Selectable.
bool draw() const noexcept
Draws the Entry as a Dear ImGui Selectable.