Skip to content

Commit 4108003

Browse files
committed
Add editor::FileBrowser
- Rename `BrowseGltf` to `FileBrowser`. - Incorporate "*.skybox" into browsers and drops. - Use `string_view` for events.
1 parent 941e8c2 commit 4108003

File tree

8 files changed

+114
-44
lines changed

8 files changed

+114
-44
lines changed

lib/engine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ target_sources(${PROJECT_NAME} PRIVATE
4343
include/${target_prefix}/engine/editor/browse_file.hpp
4444
include/${target_prefix}/engine/editor/common.hpp
4545
include/${target_prefix}/engine/editor/drag_drop_id.hpp
46+
include/${target_prefix}/engine/editor/file_browser.hpp
4647
include/${target_prefix}/engine/editor/inspector.hpp
4748
include/${target_prefix}/engine/editor/log.hpp
4849
include/${target_prefix}/engine/editor/reflector.hpp
@@ -54,6 +55,7 @@ target_sources(${PROJECT_NAME} PRIVATE
5455

5556
src/editor/browse_file.cpp
5657
src/editor/common.cpp
58+
src/editor/file_browser.cpp
5759
src/editor/inspector.cpp
5860
src/editor/log.cpp
5961
src/editor/reflector.cpp
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#pragma once
2+
#include <facade/engine/editor/browse_file.hpp>
3+
4+
namespace facade::editor {
5+
///
6+
/// \brief Persistent modal pop up to browse for a file given a list of extensions.
7+
///
8+
/// Uses Modal and BrowseFile.
9+
///
10+
class FileBrowser {
11+
public:
12+
struct Result {
13+
std::string selected{};
14+
std::string_view browse_path{};
15+
bool path_changed{};
16+
};
17+
18+
FileBrowser(std::string label, std::vector<std::string> extensions, std::string browse_path);
19+
20+
Result update(bool& out_trigger);
21+
22+
std::vector<std::string> extensions;
23+
24+
private:
25+
std::string m_browse_path;
26+
std::string m_label;
27+
std::vector<std::string_view> m_extensions_view{};
28+
env::DirEntries m_dir_entries{};
29+
bool m_trigger{};
30+
};
31+
} // namespace facade::editor

lib/engine/src/editor/browse_file.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <imgui.h>
33
#include <facade/engine/editor/browse_file.hpp>
44
#include <facade/util/env.hpp>
5+
#include <facade/util/fixed_string.hpp>
56
#include <filesystem>
67

78
namespace facade::editor {
@@ -75,6 +76,16 @@ auto BrowseFile::operator()(NotClosed<Popup> popup, std::string& out_path) -> Re
7576
if (ImGui::Button("Downloads")) { cd(std::move(downloads)); }
7677
}
7778

79+
ImGui::Separator();
80+
auto exts = FixedString{};
81+
if (extensions.empty()) {
82+
exts = "*.*";
83+
} else {
84+
exts = FixedString{"*{}", extensions[0]};
85+
for (auto it = std::next(extensions.begin()); it != extensions.end(); ++it) { exts += FixedString{", *{}", *it}; }
86+
}
87+
ImGui::Text("%s", exts.c_str());
88+
7889
ImGui::Separator();
7990
if (auto window = editor::Window{popup, "File Tree"}) {
8091
env::GetDirEntries{.extensions = extensions}(out_entries, out_path.c_str());
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#include <imgui.h>
2+
#include <facade/engine/editor/file_browser.hpp>
3+
4+
namespace facade::editor {
5+
FileBrowser::FileBrowser(std::string label, std::vector<std::string> extensions, std::string browse_path)
6+
: extensions(std::move(extensions)), m_browse_path(std::move(browse_path)), m_label(std::move(label)) {
7+
if (m_label.empty()) { m_label = "Browse..."; }
8+
}
9+
10+
auto FileBrowser::update(bool& out_trigger) -> Result {
11+
auto ret = Result{};
12+
if (out_trigger && !ImGui::IsPopupOpen(m_label.c_str())) { Modal::open(m_label.c_str()); }
13+
out_trigger = false;
14+
if (ImGui::IsPopupOpen(m_label.c_str())) { ImGui::SetNextWindowSize({400.0f, 250.0f}, ImGuiCond_FirstUseEver); }
15+
if (auto popup = Modal{m_label.c_str()}) {
16+
m_extensions_view.clear();
17+
m_extensions_view.reserve(extensions.size());
18+
for (auto const& extension : extensions) { m_extensions_view.push_back(extension); }
19+
auto [selected, changed] = BrowseFile{.out_entries = m_dir_entries, .extensions = m_extensions_view}(popup, m_browse_path);
20+
ret.path_changed = changed;
21+
ret.browse_path = m_browse_path;
22+
if (!selected.empty()) {
23+
popup.close_current();
24+
ret.selected = std::move(selected);
25+
}
26+
}
27+
return ret;
28+
}
29+
} // namespace facade::editor

src/gui/events.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
#pragma once
2-
#include <string>
2+
#include <string_view>
33

44
namespace facade::event {
55
struct Shutdown {};
66
struct OpenRecent {
7-
std::string path{};
7+
std::string_view path{};
88
};
99
struct OpenFile {};
1010
struct BrowseCd {
11-
std::string path{};
11+
std::string_view path{};
1212
};
1313
struct FileDrop {
14-
std::string path{};
14+
std::string_view path{};
1515
};
1616
} // namespace facade::event

src/gui/gltf_sources.cpp

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,29 @@ namespace facade {
1010
namespace {
1111
namespace fs = std::filesystem;
1212

13-
fs::path find_gltf(fs::path root) {
14-
if (root.extension() == ".gltf") { return root; }
13+
fs::path find_file_with_ext(fs::path root, env::MatchList exts) {
14+
auto const match = [exts](std::string_view ext) { return std::any_of(exts.begin(), exts.end(), [ext](std::string_view rhs) { return ext == rhs; }); };
15+
if (match(root.extension().string())) { return root; }
1516
if (!fs::is_directory(root)) { return {}; }
1617
for (auto const& it : fs::directory_iterator{root}) {
1718
if (!it.is_regular_file()) { continue; }
1819
auto path = it.path();
19-
if (path.extension() == ".gltf") { return path; }
20+
if (match(path.extension().string())) { return path; }
2021
}
2122
return {};
2223
}
2324
} // namespace
2425

25-
BrowseGltf::BrowseGltf(std::shared_ptr<Events> const& events, std::string browse_path)
26-
: m_events(events), m_observer(events, [this](event::OpenFile) { m_trigger = true; }), m_browse_path(std::move(browse_path)) {}
26+
FileBrowser::FileBrowser(std::shared_ptr<Events> const& events, std::string browse_path)
27+
: m_events(events), m_observer(events, [this](event::OpenFile) { m_trigger = true; }),
28+
m_browser("Browse...", {".gltf", ".skybox"}, std::move(browse_path)) {}
2729

28-
std::string BrowseGltf::update() {
29-
if (m_trigger) {
30-
editor::Popup::open("Browse...");
31-
m_trigger = false;
30+
std::string FileBrowser::update() {
31+
auto result = m_browser.update(m_trigger);
32+
if (result.path_changed) {
33+
if (auto events = m_events.lock()) { events->dispatch(event::BrowseCd{result.browse_path}); }
3234
}
33-
if (ImGui::IsPopupOpen("Browse...")) { ImGui::SetNextWindowSize({400.0f, 250.0f}, ImGuiCond_FirstUseEver); }
34-
if (auto popup = editor::Modal{"Browse..."}) {
35-
static constexpr std::string_view gltf_ext_v[] = {".gltf"};
36-
auto [selected, dir_changed] = editor::BrowseFile{.out_entries = m_dir_entries, .extensions = gltf_ext_v}(popup, m_browse_path);
37-
if (dir_changed) {
38-
if (auto events = m_events.lock()) { events->dispatch(event::BrowseCd{m_browse_path}); }
39-
}
40-
if (!selected.empty()) {
41-
popup.close_current();
42-
return selected;
43-
}
44-
}
45-
return {};
35+
return std::move(result.selected);
4636
}
4737

4838
OpenRecent::OpenRecent(std::shared_ptr<Events> const& events) : m_observer(events, [this](event::OpenRecent const& recent) { m_path = recent.path; }) {}
@@ -53,11 +43,12 @@ DropFile::DropFile(std::shared_ptr<Events> const& events) : m_observer(events, [
5343

5444
std::string DropFile::update() {
5545
if (m_path.empty()) { return {}; }
56-
if (auto ret = find_gltf(m_path); fs::is_regular_file(ret)) {
46+
static constexpr std::string_view exts_v[] = {".gltf", ".skybox"};
47+
if (auto ret = find_file_with_ext(m_path, exts_v); fs::is_regular_file(ret)) {
5748
m_path.clear();
5849
return ret.generic_string();
5950
}
60-
logger::error("Failed to locate .gltf in path: [{}]", m_path);
51+
logger::error("Failed to locate .gltf / .skybox in path: [{}]", m_path);
6152
m_path.clear();
6253
return {};
6354
}

src/gui/gltf_sources.hpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
#pragma once
22
#include <events/events.hpp>
3+
#include <facade/engine/editor/file_browser.hpp>
34
#include <facade/util/env.hpp>
45
#include <gui/events.hpp>
56
#include <gui/path_source.hpp>
67

78
namespace facade {
8-
class BrowseGltf : public PathSource {
9+
class FileBrowser : public PathSource {
910
public:
10-
BrowseGltf(std::shared_ptr<Events> const& events, std::string browse_path);
11+
FileBrowser(std::shared_ptr<Events> const& events, std::string browse_path);
1112

1213
private:
1314
std::weak_ptr<Events> m_events;
1415
Observer<event::OpenFile> m_observer;
15-
env::DirEntries m_dir_entries{};
16-
std::string m_browse_path{};
16+
editor::FileBrowser m_browser;
1717
bool m_trigger{};
1818

1919
std::string update() final;

src/main.cpp

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include <imgui.h>
2222

23+
#include <charconv>
2324
#include <filesystem>
2425
#include <iostream>
2526

@@ -113,7 +114,7 @@ void run(AppOpts const& opts) {
113114

114115
auto path_sources = PathSource::List{};
115116
path_sources.sources.push_back(std::make_unique<DropFile>(events));
116-
path_sources.sources.push_back(std::make_unique<BrowseGltf>(events, config.config.file_menu.browse_path));
117+
path_sources.sources.push_back(std::make_unique<FileBrowser>(events, config.config.file_menu.browse_path));
117118
path_sources.sources.push_back(std::make_unique<OpenRecent>(events));
118119

119120
auto quit = Observer<event::Shutdown>{events, [&engine](event::Shutdown) { engine->request_stop(); }};
@@ -156,7 +157,14 @@ void run(AppOpts const& opts) {
156157
window_menu.display_menu(menu);
157158
window_menu.display_windows(*engine);
158159

159-
if (auto path = path_sources.update(); !path.empty()) { load_async(std::move(path)); }
160+
if (auto path = path_sources.update(); !path.empty()) {
161+
auto const ext = fs::path{path}.extension().string();
162+
if (ext == ".gltf") {
163+
load_async(std::move(path));
164+
} else {
165+
logger::warn("Unrecognized path: {}", path);
166+
}
167+
}
160168
}
161169

162170
config.update(*engine);
@@ -179,16 +187,14 @@ void run(AppOpts const& opts) {
179187
}
180188
}
181189

182-
std::optional<std::uint32_t> to_u32(std::string const& s) {
183-
try {
184-
int i = std::stoi(s);
185-
if (i < 0) {
186-
logger::error("Invalid value: {}", i);
187-
return {};
188-
}
189-
return static_cast<std::uint32_t>(i);
190-
} catch (std::exception const& e) { logger::error("Invalid value: {} ({})", s, e.what()); }
191-
return {};
190+
std::optional<std::uint32_t> to_u32(std::string_view const s) {
191+
auto ret = std::uint32_t{};
192+
auto [_, ec] = std::from_chars(s.data(), s.data() + s.size(), ret);
193+
if (ec != std::errc{}) {
194+
logger::error("Invalid value: {}", s);
195+
return {};
196+
}
197+
return ret;
192198
}
193199
} // namespace
194200

0 commit comments

Comments
 (0)