Merge commit '3fddeeee3e14669f4c8a139162bf9a548716a108'
This commit is contained in:
commit
d4a691bcc2
BIN
deps/nostalgia/sample_project/TileSheets/Chester.ng
vendored
BIN
deps/nostalgia/sample_project/TileSheets/Chester.ng
vendored
Binary file not shown.
352
deps/nostalgia/sample_project/TileSheets/Chester.nts
vendored
Normal file
352
deps/nostalgia/sample_project/TileSheets/Chester.nts
vendored
Normal file
@ -0,0 +1,352 @@
|
||||
K1;5667c759-7ba1-470a-8860-72f0720dc58c;O1;net.drinkingtea.nostalgia.gfx.TileSheet;5;{
|
||||
"bpp" : 4,
|
||||
"defaultPalette" : "uuid://14fc3dd8-42ff-4bf9-81f1-a010cc5ac251",
|
||||
"idIt" : 7,
|
||||
"subsheet" :
|
||||
{
|
||||
"columns" : -1,
|
||||
"name" : "Root",
|
||||
"rows" : -1,
|
||||
"subsheets" :
|
||||
[
|
||||
{
|
||||
"columns" : 1,
|
||||
"id" : 5,
|
||||
"name" : "Blank",
|
||||
"pixels" :
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rows" : 1
|
||||
},
|
||||
{
|
||||
"columns" : 2,
|
||||
"id" : 6,
|
||||
"name" : "Dirt",
|
||||
"pixels" :
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"rows" : 2
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
BIN
deps/nostalgia/sample_project/TileSheets/SC9K_Logo.nts
vendored
Normal file
BIN
deps/nostalgia/sample_project/TileSheets/SC9K_Logo.nts
vendored
Normal file
Binary file not shown.
@ -13,6 +13,7 @@ constexpr auto TileHeight = 8;
|
||||
constexpr auto PixelsPerTile = TileWidth * TileHeight;
|
||||
|
||||
constexpr ox::StringLiteral FileExt_ng("ng");
|
||||
constexpr ox::StringLiteral FileExt_nts("nts");
|
||||
constexpr ox::StringLiteral FileExt_npal("npal");
|
||||
|
||||
}
|
||||
|
@ -14,14 +14,14 @@ namespace nostalgia::gfx {
|
||||
static class: public studio::Module {
|
||||
ox::Vector<studio::EditorMaker> editors(studio::StudioContext &ctx) const noexcept final {
|
||||
return {
|
||||
studio::editorMaker<TileSheetEditorImGui>(ctx, FileExt_ng),
|
||||
studio::editorMaker<TileSheetEditorImGui>(ctx, {FileExt_ng, FileExt_nts}),
|
||||
studio::editorMaker<PaletteEditorImGui>(ctx, FileExt_npal),
|
||||
};
|
||||
}
|
||||
|
||||
ox::Vector<ox::UPtr<studio::ItemMaker>> itemMakers(studio::StudioContext&) const noexcept final {
|
||||
ox::Vector<ox::UniquePtr<studio::ItemMaker>> out;
|
||||
out.emplace_back(ox::make<studio::ItemMakerT<TileSheet>>("Tile Sheet", "TileSheets", FileExt_ng));
|
||||
out.emplace_back(ox::make<studio::ItemMakerT<TileSheet>>("Tile Sheet", "TileSheets", FileExt_nts));
|
||||
out.emplace_back(ox::make<studio::ItemMakerT<Palette>>("Palette", "Palettes", FileExt_npal, Palette{
|
||||
.colorNames = {},
|
||||
.pages = {{"Page 1", ox::Vector<PaletteColor>{}}},
|
||||
|
@ -9,7 +9,6 @@ add_library(
|
||||
newmenu.cpp
|
||||
newproject.cpp
|
||||
projectexplorer.cpp
|
||||
projecttreemodel.cpp
|
||||
studioapp.cpp
|
||||
)
|
||||
target_compile_definitions(
|
||||
|
@ -12,9 +12,8 @@
|
||||
|
||||
namespace studio {
|
||||
|
||||
NewMenu::NewMenu() noexcept {
|
||||
NewMenu::NewMenu(keel::Context &kctx) noexcept: m_kctx{kctx} {
|
||||
setTitle("New Item");
|
||||
setSize({280, 180});
|
||||
}
|
||||
|
||||
void NewMenu::open() noexcept {
|
||||
@ -23,6 +22,11 @@ void NewMenu::open() noexcept {
|
||||
m_itemName = "";
|
||||
m_typeName = "";
|
||||
m_path = "";
|
||||
m_explorer.setModel(buildFileTreeModel(
|
||||
m_explorer,
|
||||
[](ox::StringViewCR, ox::FileStat const&s) {
|
||||
return s.fileType == ox::FileType::Directory;
|
||||
}).or_value(ox::UPtr<FileTreeModel>{}));
|
||||
}
|
||||
|
||||
void NewMenu::openPath(ox::StringParam path) noexcept {
|
||||
@ -49,8 +53,8 @@ void NewMenu::draw(StudioContext &sctx) noexcept {
|
||||
case Stage::NewItemType:
|
||||
drawNewItemType(sctx);
|
||||
break;
|
||||
case Stage::NewItemName:
|
||||
drawNewItemName(sctx);
|
||||
case Stage::NewItemPath:
|
||||
drawNewItemPath(sctx);
|
||||
break;
|
||||
case Stage::NewItemTemplate:
|
||||
drawNewItemTemplate(sctx);
|
||||
@ -79,32 +83,45 @@ void NewMenu::installItemTemplate(ox::UPtr<ItemTemplate> &tmplt) noexcept {
|
||||
}
|
||||
|
||||
void NewMenu::drawNewItemType(StudioContext const&sctx) noexcept {
|
||||
setSize({280, 180});
|
||||
drawWindow(sctx.tctx, m_open, [this] {
|
||||
ig::ListBox("Item Type", [&](size_t const i) -> ox::CStringView {
|
||||
return m_types[i]->typeDisplayName();
|
||||
}, m_types.size(), m_selectedType, {200, 100});
|
||||
auto const&im = *m_types[m_selectedType];
|
||||
drawFirstPageButtons(im.itemTemplates().size() == 1 ?
|
||||
Stage::NewItemName : Stage::NewItemTemplate);
|
||||
Stage::NewItemPath : Stage::NewItemTemplate);
|
||||
if (m_stage == Stage::NewItemPath) {
|
||||
if (m_path.len() == 0) {
|
||||
m_path = im.defaultPath();
|
||||
}
|
||||
std::ignore = m_explorer.setSelectedPath(m_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void NewMenu::drawNewItemTemplate(StudioContext &sctx) noexcept {
|
||||
void NewMenu::drawNewItemTemplate(StudioContext const&sctx) noexcept {
|
||||
setSize({280, 180});
|
||||
drawWindow(sctx.tctx, m_open, [this] {
|
||||
auto const&templates =
|
||||
m_types[m_selectedType]->itemTemplates();
|
||||
ig::ListBox("Template", [&](size_t const i) -> ox::CStringView {
|
||||
return templates[i]->name();
|
||||
}, templates.size(), m_selectedTemplate, {200, 100});
|
||||
drawButtons(Stage::NewItemType, Stage::NewItemName);
|
||||
drawButtons(Stage::NewItemType, Stage::NewItemPath);
|
||||
});
|
||||
}
|
||||
|
||||
void NewMenu::drawNewItemName(StudioContext &sctx) noexcept {
|
||||
void NewMenu::drawNewItemPath(StudioContext &sctx) noexcept {
|
||||
setSize({380, 340});
|
||||
drawWindow(sctx.tctx, m_open, [this, &sctx] {
|
||||
if (m_selectedType < m_types.size()) {
|
||||
ig::InputText("Name", m_itemName);
|
||||
}
|
||||
ImGui::NewLine();
|
||||
ImGui::Text("Path");
|
||||
auto const vp = ImGui::GetContentRegionAvail();
|
||||
m_explorer.draw(sctx, {vp.x, vp.y - 50});
|
||||
drawLastPageButtons(sctx);
|
||||
});
|
||||
}
|
||||
@ -165,8 +182,10 @@ void NewMenu::finish(StudioContext &sctx) noexcept {
|
||||
return;
|
||||
}
|
||||
auto const&im = *m_types[m_selectedType];
|
||||
auto const path = m_path.len() ?
|
||||
im.itemPath(m_itemName, m_path) : im.itemPath(m_itemName);
|
||||
if (auto p = m_explorer.selectedPath()) {
|
||||
m_path = std::move(*p);
|
||||
}
|
||||
auto const path = sfmt("{}/{}.{}", m_path, m_itemName, im.fileExt());
|
||||
if (sctx.project->exists(path)) {
|
||||
oxLogError(ox::Error{1, "New file error: file already exists"});
|
||||
return;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <studio/filetreemodel.hpp>
|
||||
#include <studio/itemmaker.hpp>
|
||||
#include <studio/popup.hpp>
|
||||
|
||||
@ -19,7 +20,7 @@ class NewMenu final: public Popup {
|
||||
Closed,
|
||||
Opening,
|
||||
NewItemType,
|
||||
NewItemName,
|
||||
NewItemPath,
|
||||
NewItemTemplate,
|
||||
};
|
||||
|
||||
@ -28,16 +29,18 @@ class NewMenu final: public Popup {
|
||||
|
||||
private:
|
||||
Stage m_stage = Stage::Closed;
|
||||
keel::Context &m_kctx;
|
||||
ox::String m_typeName;
|
||||
ox::IString<255> m_itemName;
|
||||
ox::String m_path;
|
||||
ox::Vector<ox::UPtr<studio::ItemMaker>> m_types;
|
||||
ox::Vector<ox::UPtr<ItemMaker>> m_types;
|
||||
FileExplorer m_explorer{m_kctx};
|
||||
size_t m_selectedType = 0;
|
||||
size_t m_selectedTemplate = 0;
|
||||
bool m_open = false;
|
||||
|
||||
public:
|
||||
NewMenu() noexcept;
|
||||
NewMenu(keel::Context &kctx) noexcept;
|
||||
|
||||
void openPath(ox::StringParam path) noexcept;
|
||||
|
||||
@ -72,9 +75,9 @@ class NewMenu final: public Popup {
|
||||
private:
|
||||
void drawNewItemType(StudioContext const&sctx) noexcept;
|
||||
|
||||
void drawNewItemName(StudioContext &sctx) noexcept;
|
||||
void drawNewItemPath(StudioContext &sctx) noexcept;
|
||||
|
||||
void drawNewItemTemplate(StudioContext &sctx) noexcept;
|
||||
void drawNewItemTemplate(StudioContext const &sctx) noexcept;
|
||||
|
||||
void drawButtons(Stage prev, Stage next) noexcept;
|
||||
|
||||
|
@ -10,51 +10,42 @@
|
||||
|
||||
namespace studio {
|
||||
|
||||
static ox::Result<ox::UniquePtr<ProjectTreeModel>> buildProjectTreeModel(
|
||||
ProjectExplorer &explorer,
|
||||
ox::StringParam name,
|
||||
ox::StringView path,
|
||||
ProjectTreeModel *parent) noexcept {
|
||||
auto const fs = explorer.romFs();
|
||||
OX_REQUIRE(stat, fs->stat(path));
|
||||
auto out = ox::make_unique<ProjectTreeModel>(explorer, std::move(name), parent);
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
OX_REQUIRE_M(children, fs->ls(path));
|
||||
std::sort(children.begin(), children.end());
|
||||
ox::Vector<ox::UniquePtr<ProjectTreeModel>> outChildren;
|
||||
for (auto const&childName : children) {
|
||||
if (childName[0] != '.') {
|
||||
auto const childPath = ox::sfmt("{}/{}", path, childName);
|
||||
OX_REQUIRE_M(child, buildProjectTreeModel(explorer, childName, childPath, out.get()));
|
||||
outChildren.emplace_back(std::move(child));
|
||||
}
|
||||
}
|
||||
out->setChildren(std::move(outChildren));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) {
|
||||
}
|
||||
|
||||
void ProjectExplorer::draw(StudioContext &ctx) noexcept {
|
||||
auto const viewport = ImGui::GetContentRegionAvail();
|
||||
ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true);
|
||||
ImGui::SetNextItemOpen(true);
|
||||
if (m_treeModel) {
|
||||
m_treeModel->draw(ctx.tctx);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void ProjectExplorer::setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept {
|
||||
m_treeModel = std::move(model);
|
||||
ProjectExplorer::ProjectExplorer(keel::Context &kctx) noexcept:
|
||||
FileExplorer{kctx, true} {
|
||||
}
|
||||
|
||||
ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept {
|
||||
OX_REQUIRE_M(model, buildProjectTreeModel(*this, "Project", "/", nullptr));
|
||||
OX_REQUIRE_M(model, buildFileTreeModel(*this, "Project", "/", nullptr));
|
||||
setModel(std::move(model));
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProjectExplorer::fileOpened(ox::StringViewCR path) const noexcept {
|
||||
fileChosen.emit(path);
|
||||
}
|
||||
|
||||
void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept {
|
||||
if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
if (ImGui::MenuItem("Delete")) {
|
||||
deleteItem.emit(path);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectExplorer::dirContextMenu(ox::StringViewCR path) const noexcept {
|
||||
if (ImGui::BeginPopupContextItem("DirMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
if (ImGui::MenuItem("Add Item")) {
|
||||
addItem.emit(path);
|
||||
}
|
||||
if (ImGui::MenuItem("Add Directory")) {
|
||||
addDir.emit(path);
|
||||
}
|
||||
if (ImGui::MenuItem("Delete")) {
|
||||
deleteItem.emit(path);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,15 +7,12 @@
|
||||
#include <ox/event/signal.hpp>
|
||||
#include <ox/std/memory.hpp>
|
||||
|
||||
#include <studio/filetreemodel.hpp>
|
||||
#include <studio/widget.hpp>
|
||||
#include "projecttreemodel.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ProjectExplorer: public Widget {
|
||||
private:
|
||||
ox::UPtr<ProjectTreeModel> m_treeModel;
|
||||
turbine::Context &m_ctx;
|
||||
class ProjectExplorer final: public FileExplorer {
|
||||
|
||||
public:
|
||||
// slots
|
||||
@ -24,18 +21,16 @@ class ProjectExplorer: public Widget {
|
||||
ox::Signal<ox::Error(ox::StringViewCR)> addDir;
|
||||
ox::Signal<ox::Error(ox::StringViewCR)> deleteItem;
|
||||
|
||||
explicit ProjectExplorer(turbine::Context &ctx) noexcept;
|
||||
|
||||
void draw(StudioContext &ctx) noexcept override;
|
||||
|
||||
void setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept;
|
||||
explicit ProjectExplorer(keel::Context &kctx) noexcept;
|
||||
|
||||
ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::FileSystem *romFs() noexcept {
|
||||
return rom(m_ctx);
|
||||
}
|
||||
protected:
|
||||
void fileOpened(ox::StringViewCR path) const noexcept override;
|
||||
|
||||
void fileContextMenu(ox::StringViewCR path) const noexcept override;
|
||||
|
||||
void dirContextMenu(ox::StringViewCR path) const noexcept override;
|
||||
|
||||
};
|
||||
|
||||
|
@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <studio/dragdrop.hpp>
|
||||
#include <studio/imguiutil.hpp>
|
||||
|
||||
#include "projectexplorer.hpp"
|
||||
#include "projecttreemodel.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
ProjectTreeModel::ProjectTreeModel(
|
||||
ProjectExplorer &explorer,
|
||||
ox::StringParam name,
|
||||
ProjectTreeModel *parent) noexcept:
|
||||
m_explorer(explorer),
|
||||
m_parent(parent),
|
||||
m_name(std::move(name)) {
|
||||
}
|
||||
|
||||
ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept:
|
||||
m_explorer(other.m_explorer),
|
||||
m_parent(other.m_parent),
|
||||
m_name(std::move(other.m_name)),
|
||||
m_children(std::move(other.m_children)) {
|
||||
}
|
||||
|
||||
void ProjectTreeModel::draw(turbine::Context &tctx) const noexcept {
|
||||
constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
if (!m_children.empty()) {
|
||||
if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) {
|
||||
drawDirContextMenu();
|
||||
for (auto const&child : m_children) {
|
||||
child->draw(tctx);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
} else {
|
||||
ig::IDStackItem const idStackItem{m_name};
|
||||
drawDirContextMenu();
|
||||
}
|
||||
} else {
|
||||
auto const path = fullPath();
|
||||
auto const name = ox::sfmt<ox::BasicString<255>>("{}##{}", m_name, path);
|
||||
if (ImGui::TreeNodeEx(name.c_str(), ImGuiTreeNodeFlags_Leaf)) {
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
m_explorer.fileChosen.emit(path);
|
||||
}
|
||||
if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
if (ImGui::MenuItem("Delete")) {
|
||||
m_explorer.deleteItem.emit(path);
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::TreePop();
|
||||
std::ignore = ig::dragDropSource([this] {
|
||||
ImGui::Text("%s", m_name.c_str());
|
||||
return ig::setDragDropPayload("FileRef", FileRef{fullPath<ox::String>()});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectTreeModel::setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept {
|
||||
m_children = std::move(children);
|
||||
}
|
||||
|
||||
void ProjectTreeModel::drawDirContextMenu() const noexcept {
|
||||
if (ImGui::BeginPopupContextItem("DirMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
if (ImGui::MenuItem("Add Item")) {
|
||||
m_explorer.addItem.emit(fullPath());
|
||||
}
|
||||
if (ImGui::MenuItem("Add Directory")) {
|
||||
m_explorer.addDir.emit(fullPath());
|
||||
}
|
||||
if (ImGui::MenuItem("Delete")) {
|
||||
m_explorer.deleteItem.emit(fullPath());
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ProjectTreeModel {
|
||||
private:
|
||||
class ProjectExplorer &m_explorer;
|
||||
ProjectTreeModel *m_parent = nullptr;
|
||||
ox::String m_name;
|
||||
ox::Vector<ox::UPtr<ProjectTreeModel>> m_children;
|
||||
|
||||
public:
|
||||
explicit ProjectTreeModel(
|
||||
ProjectExplorer &explorer, ox::StringParam name,
|
||||
ProjectTreeModel *parent = nullptr) noexcept;
|
||||
|
||||
ProjectTreeModel(ProjectTreeModel &&other) noexcept;
|
||||
|
||||
void draw(turbine::Context &tctx) const noexcept;
|
||||
|
||||
void setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept;
|
||||
|
||||
private:
|
||||
void drawDirContextMenu() const noexcept;
|
||||
|
||||
template<typename Str = ox::BasicString<255>>
|
||||
[[nodiscard]]
|
||||
Str fullPath() const noexcept {
|
||||
if (m_parent) {
|
||||
return m_parent->fullPath<Str>() + "/" + m_name;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -50,7 +50,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
|
||||
m_sctx(*this, ctx),
|
||||
m_tctx(ctx),
|
||||
m_projectDataDir(std::move(projectDataDir)),
|
||||
m_projectExplorer(m_tctx),
|
||||
m_projectExplorer(keelCtx(m_tctx)),
|
||||
m_newProject(m_projectDataDir),
|
||||
m_aboutPopup(m_tctx) {
|
||||
turbine::setApplicationData(m_tctx, &m_sctx);
|
||||
@ -120,7 +120,8 @@ void StudioUI::draw() noexcept {
|
||||
ig::s_mainWinHasFocus = ImGui::IsWindowFocused(
|
||||
ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy);
|
||||
if (m_showProjectExplorer) {
|
||||
m_projectExplorer.draw(m_sctx);
|
||||
auto const v = ImGui::GetContentRegionAvail();
|
||||
m_projectExplorer.draw(m_sctx, {300, v.y});
|
||||
ImGui::SameLine();
|
||||
}
|
||||
drawTabBar();
|
||||
|
@ -19,7 +19,6 @@
|
||||
#include "newmenu.hpp"
|
||||
#include "newproject.hpp"
|
||||
#include "projectexplorer.hpp"
|
||||
#include "projecttreemodel.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
@ -40,7 +39,7 @@ class StudioUI: public ox::SignalHandler {
|
||||
BaseEditor *m_activeEditorOnLastDraw = nullptr;
|
||||
BaseEditor *m_activeEditor = nullptr;
|
||||
BaseEditor *m_activeEditorUpdatePending = nullptr;
|
||||
NewMenu m_newMenu;
|
||||
NewMenu m_newMenu{keelCtx(m_tctx)};
|
||||
DeleteConfirmation m_deleteConfirmation;
|
||||
NewDir m_newDirDialog;
|
||||
NewProject m_newProject;
|
||||
|
138
deps/nostalgia/src/olympic/studio/modlib/include/studio/filetreemodel.hpp
vendored
Normal file
138
deps/nostalgia/src/olympic/studio/modlib/include/studio/filetreemodel.hpp
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include "widget.hpp"
|
||||
|
||||
namespace studio {
|
||||
|
||||
constexpr void safeDelete(class FileTreeModel *m) noexcept;
|
||||
|
||||
class FileExplorer: public ox::SignalHandler {
|
||||
|
||||
friend class FileTreeModel;
|
||||
|
||||
private:
|
||||
keel::Context &m_kctx;
|
||||
class FileTreeModel const *m_selected{};
|
||||
bool const m_fileDraggable{};
|
||||
ox::UPtr<FileTreeModel> m_treeModel;
|
||||
|
||||
public:
|
||||
explicit FileExplorer(keel::Context &kctx, bool const fileDraggable = false) noexcept:
|
||||
m_kctx{kctx},
|
||||
m_fileDraggable{fileDraggable} {}
|
||||
|
||||
virtual ~FileExplorer() = default;
|
||||
|
||||
void draw(StudioContext &ctx, ImVec2 const &sz) const noexcept;
|
||||
|
||||
void setModel(ox::UPtr<FileTreeModel> &&model, bool selectRoot = false) noexcept;
|
||||
|
||||
ox::Error setSelectedPath(ox::StringViewCR path) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Optional<ox::String> selectedPath() const;
|
||||
|
||||
virtual void fileOpened(ox::StringViewCR path) const noexcept;
|
||||
|
||||
void drawFileContextMenu(ox::CStringViewCR path) const noexcept;
|
||||
|
||||
void drawDirContextMenu(ox::CStringViewCR path) const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::FileSystem &romFs() const noexcept {
|
||||
return *m_kctx.rom;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void fileContextMenu(ox::StringViewCR path) const noexcept;
|
||||
|
||||
virtual void dirContextMenu(ox::StringViewCR path) const noexcept;
|
||||
|
||||
void setSelectedNode(FileTreeModel const *const node) noexcept {
|
||||
m_selected = node;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool selected(FileTreeModel const *const node) const noexcept {
|
||||
return m_selected == node;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool fileDraggable() const noexcept {
|
||||
return m_fileDraggable;
|
||||
}
|
||||
|
||||
private:
|
||||
ox::Result<bool> setSelectedPath(ox::StringViewCR path, FileTreeModel const&node) noexcept;
|
||||
|
||||
};
|
||||
|
||||
class FileTreeModel {
|
||||
private:
|
||||
FileExplorer &m_explorer;
|
||||
ox::String m_name;
|
||||
ox::String m_fullPath;
|
||||
ox::Vector<ox::UPtr<FileTreeModel>> m_children;
|
||||
ox::FileType const m_fileType{};
|
||||
|
||||
public:
|
||||
explicit FileTreeModel(
|
||||
FileExplorer &explorer,
|
||||
ox::StringParam name,
|
||||
ox::FileType fileType,
|
||||
FileTreeModel const *parent = nullptr) noexcept;
|
||||
|
||||
virtual ~FileTreeModel() = default;
|
||||
|
||||
FileTreeModel(FileTreeModel &&other) noexcept = default;
|
||||
|
||||
void draw(turbine::Context &tctx) const noexcept;
|
||||
|
||||
void setChildren(ox::Vector<ox::UPtr<FileTreeModel>> children) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Vector<ox::UPtr<FileTreeModel>> const&children() const noexcept {
|
||||
return m_children;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
bool isEmptyDir() const noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
ox::String const &path() const noexcept {
|
||||
return m_fullPath;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
constexpr void safeDelete(FileTreeModel *m) noexcept {
|
||||
delete m;
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
|
||||
FileExplorer &explorer,
|
||||
ox::StringParam name,
|
||||
ox::StringViewCR path,
|
||||
FileTreeModel *parent = nullptr,
|
||||
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const&filter =
|
||||
[](ox::StringViewCR, ox::FileStat const&) {return true;},
|
||||
bool showEmptyDirs = true) noexcept;
|
||||
|
||||
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
|
||||
FileExplorer &explorer,
|
||||
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const&filter =
|
||||
[](ox::StringViewCR, ox::FileStat const&) {return true;},
|
||||
bool showEmptyDirs = true) noexcept;
|
||||
|
||||
}
|
@ -95,9 +95,9 @@ class ItemMaker {
|
||||
public:
|
||||
constexpr ItemMaker(
|
||||
ox::StringParam pName,
|
||||
ox::StringParam pParentDir,
|
||||
ox::StringViewCR pParentDir,
|
||||
ox::StringParam pFileExt) noexcept:
|
||||
m_parentDir{std::move(pParentDir)},
|
||||
m_parentDir{sfmt("/{}", pParentDir)},
|
||||
m_fileExt{std::move(pFileExt)},
|
||||
m_typeDisplayName{std::move(pName)} {
|
||||
}
|
||||
@ -128,6 +128,11 @@ class ItemMaker {
|
||||
return m_templates;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::String const&fileExt() const noexcept {
|
||||
return m_fileExt;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::String const&defaultPath() const noexcept {
|
||||
return m_parentDir;
|
||||
@ -140,7 +145,7 @@ class ItemMaker {
|
||||
|
||||
[[nodiscard]]
|
||||
ox::String itemPath(ox::StringViewCR pName) const noexcept {
|
||||
return ox::sfmt("/{}/{}.{}", m_parentDir, pName, m_fileExt);
|
||||
return ox::sfmt("{}/{}.{}", m_parentDir, pName, m_fileExt);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
@ -196,12 +201,12 @@ class ItemMakerT final: public ItemMaker {
|
||||
public:
|
||||
constexpr ItemMakerT(
|
||||
ox::StringParam pDisplayName,
|
||||
ox::StringParam pParentDir,
|
||||
ox::StringViewCR pParentDir,
|
||||
ox::StringParam fileExt,
|
||||
ox::ClawFormat const pFmt = ox::ClawFormat::Metal) noexcept:
|
||||
ItemMaker(
|
||||
std::move(pDisplayName),
|
||||
std::move(pParentDir),
|
||||
pParentDir,
|
||||
std::move(fileExt)),
|
||||
m_fmt{pFmt} {
|
||||
installTemplate(ox::make_unique<ItemTemplateT<T>>());
|
||||
@ -209,13 +214,13 @@ class ItemMakerT final: public ItemMaker {
|
||||
|
||||
constexpr ItemMakerT(
|
||||
ox::StringParam pDisplayName,
|
||||
ox::StringParam pParentDir,
|
||||
ox::StringViewCR pParentDir,
|
||||
ox::StringParam fileExt,
|
||||
T const&pItem,
|
||||
ox::ClawFormat const pFmt) noexcept:
|
||||
ItemMaker(
|
||||
std::move(pDisplayName),
|
||||
std::move(pParentDir),
|
||||
pParentDir,
|
||||
std::move(fileExt)),
|
||||
m_fmt{pFmt} {
|
||||
installTemplate(ox::make_unique<ItemTemplateT<T>>("Default", std::move(pItem)));
|
||||
@ -223,13 +228,13 @@ class ItemMakerT final: public ItemMaker {
|
||||
|
||||
constexpr ItemMakerT(
|
||||
ox::StringParam pDisplayName,
|
||||
ox::StringParam pParentDir,
|
||||
ox::StringViewCR pParentDir,
|
||||
ox::StringParam fileExt,
|
||||
T &&pItem,
|
||||
ox::ClawFormat const pFmt) noexcept:
|
||||
ItemMaker(
|
||||
std::move(pDisplayName),
|
||||
std::move(pParentDir),
|
||||
pParentDir,
|
||||
std::move(fileExt)),
|
||||
m_fmt{pFmt} {
|
||||
installTemplate(ox::make_unique<ItemTemplateT<T>>("Default", std::move(pItem)));
|
||||
|
@ -37,10 +37,28 @@ class Module {
|
||||
|
||||
template<typename Editor>
|
||||
[[nodiscard]]
|
||||
studio::EditorMaker editorMaker(studio::StudioContext &ctx, ox::StringParam ext) noexcept {
|
||||
EditorMaker editorMaker(StudioContext &ctx, ox::StringParam ext) noexcept {
|
||||
return {
|
||||
{std::move(ext)},
|
||||
[&ctx](ox::StringViewCR path) -> ox::Result<studio::BaseEditor*> {
|
||||
[&ctx](ox::StringViewCR path) -> ox::Result<BaseEditor*> {
|
||||
return ox::makeCatch<Editor>(ctx, path);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template<typename Editor>
|
||||
[[nodiscard]]
|
||||
EditorMaker editorMaker(StudioContext &ctx, std::initializer_list<ox::StringView> exts) noexcept {
|
||||
return {
|
||||
[&exts] {
|
||||
ox::Vector<ox::String> fileTypes;
|
||||
fileTypes.reserve(exts.size());
|
||||
for (auto &s : exts) {
|
||||
fileTypes.emplace_back(s);
|
||||
}
|
||||
return fileTypes;
|
||||
}(),
|
||||
[&ctx](ox::StringViewCR path) -> ox::Result<BaseEditor*> {
|
||||
return ox::makeCatch<Editor>(ctx, path);
|
||||
}
|
||||
};
|
||||
|
16
deps/nostalgia/src/olympic/studio/modlib/include/studio/projectfilepicker.hpp
vendored
Normal file
16
deps/nostalgia/src/olympic/studio/modlib/include/studio/projectfilepicker.hpp
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ox/std/memory.hpp>
|
||||
#include <ox/std/string.hpp>
|
||||
|
||||
#include <turbine/context.hpp>
|
||||
|
||||
#include <studio/filetreemodel.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
}
|
@ -9,6 +9,7 @@
|
||||
#include <studio/dragdrop.hpp>
|
||||
#include <studio/editor.hpp>
|
||||
#include <studio/filedialog.hpp>
|
||||
#include <studio/filetreemodel.hpp>
|
||||
#include <studio/imguiutil.hpp>
|
||||
#include <studio/module.hpp>
|
||||
#include <studio/itemmaker.hpp>
|
||||
|
@ -2,6 +2,8 @@ add_library(
|
||||
Studio
|
||||
configio.cpp
|
||||
editor.cpp
|
||||
projectfilepicker.cpp
|
||||
filetreemodel.cpp
|
||||
imguiutil.cpp
|
||||
module.cpp
|
||||
popup.cpp
|
||||
|
172
deps/nostalgia/src/olympic/studio/modlib/src/filetreemodel.cpp
vendored
Normal file
172
deps/nostalgia/src/olympic/studio/modlib/src/filetreemodel.cpp
vendored
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <studio/dragdrop.hpp>
|
||||
#include <studio/imguiutil.hpp>
|
||||
|
||||
#include <studio/filetreemodel.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
void FileExplorer::draw(StudioContext &ctx, ImVec2 const &sz) const noexcept {
|
||||
ImGui::BeginChild("ProjectExplorer", sz, true);
|
||||
ImGui::SetNextItemOpen(true);
|
||||
if (m_treeModel) {
|
||||
m_treeModel->draw(ctx.tctx);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void FileExplorer::setModel(ox::UPtr<FileTreeModel> &&model, bool const selectRoot) noexcept {
|
||||
m_treeModel = std::move(model);
|
||||
setSelectedNode(selectRoot ? m_treeModel.get() : nullptr);
|
||||
}
|
||||
|
||||
ox::Error FileExplorer::setSelectedPath(ox::StringViewCR path) noexcept {
|
||||
return setSelectedPath(path, *m_treeModel).error;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
ox::Optional<ox::String> FileExplorer::selectedPath() const {
|
||||
if (m_selected) {
|
||||
return ox::Optional<ox::String>{ox::in_place, m_selected->path()};
|
||||
}
|
||||
return ox::Optional<ox::String>{};
|
||||
}
|
||||
|
||||
void FileExplorer::fileOpened(ox::StringViewCR) const noexcept {}
|
||||
|
||||
void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept {
|
||||
ig::IDStackItem const idStackItem{path};
|
||||
fileContextMenu(path);
|
||||
}
|
||||
|
||||
void FileExplorer::drawDirContextMenu(ox::CStringViewCR path) const noexcept {
|
||||
ig::IDStackItem const idStackItem{path};
|
||||
dirContextMenu(path);
|
||||
}
|
||||
|
||||
void FileExplorer::fileContextMenu(ox::StringViewCR) const noexcept {}
|
||||
|
||||
void FileExplorer::dirContextMenu(ox::StringViewCR) const noexcept {}
|
||||
|
||||
ox::Result<bool> FileExplorer::setSelectedPath(
|
||||
ox::StringViewCR path, FileTreeModel const&node) noexcept {
|
||||
if (path == node.path()) {
|
||||
m_selected = &node;
|
||||
return {};
|
||||
}
|
||||
for (auto &c : node.children()) {
|
||||
OX_REQUIRE(done, setSelectedPath(path, *c));
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
FileTreeModel::FileTreeModel(
|
||||
FileExplorer &explorer,
|
||||
ox::StringParam name,
|
||||
ox::FileType const fileType,
|
||||
FileTreeModel const *const parent) noexcept:
|
||||
m_explorer{explorer},
|
||||
m_name{std::move(name)},
|
||||
m_fullPath{parent ? sfmt("{}/{}", parent->m_fullPath, m_name) : ox::String{}},
|
||||
m_fileType{fileType} {}
|
||||
|
||||
void FileTreeModel::draw(turbine::Context &tctx) const noexcept {
|
||||
constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
|
||||
auto const selected = m_explorer.selected(this) ? ImGuiTreeNodeFlags_Selected : 0;
|
||||
if (!m_children.empty()) {
|
||||
auto const nodeOpen = ImGui::TreeNodeEx(m_name.c_str(), dirFlags | selected);
|
||||
if (ImGui::IsItemClicked()) {
|
||||
m_explorer.setSelectedNode(this);
|
||||
}
|
||||
ig::IDStackItem const idStackItem{m_name};
|
||||
m_explorer.drawDirContextMenu(m_fullPath);
|
||||
if (nodeOpen) {
|
||||
for (auto const&child : m_children) {
|
||||
child->draw(tctx);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
} else {
|
||||
if (ImGui::TreeNodeEx(m_name.c_str(), ImGuiTreeNodeFlags_Leaf | selected)) {
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
m_explorer.fileOpened(m_fullPath);
|
||||
}
|
||||
if (ImGui::IsItemClicked()) {
|
||||
m_explorer.setSelectedNode(this);
|
||||
}
|
||||
m_explorer.drawFileContextMenu(m_fullPath);
|
||||
ImGui::TreePop();
|
||||
if (m_explorer.fileDraggable()) {
|
||||
std::ignore = ig::dragDropSource([this] {
|
||||
ImGui::Text("%s", m_name.c_str());
|
||||
return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FileTreeModel::setChildren(ox::Vector<ox::UPtr<FileTreeModel>> children) noexcept {
|
||||
m_children = std::move(children);
|
||||
}
|
||||
|
||||
bool FileTreeModel::isEmptyDir() const noexcept {
|
||||
return m_children.empty() && m_fileType == ox::FileType::Directory;
|
||||
}
|
||||
|
||||
|
||||
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
|
||||
FileExplorer &explorer,
|
||||
ox::StringParam name,
|
||||
ox::StringViewCR path,
|
||||
FileTreeModel *parent,
|
||||
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const &filter,
|
||||
bool const showEmptyDirs) noexcept {
|
||||
auto const&fs = explorer.romFs();
|
||||
OX_REQUIRE(stat, fs.stat(path));
|
||||
auto out = ox::make_unique<FileTreeModel>(explorer, std::move(name), stat.fileType, parent);
|
||||
if (stat.fileType == ox::FileType::Directory) {
|
||||
OX_REQUIRE_M(children, fs.ls(path));
|
||||
std::sort(children.begin(), children.end());
|
||||
ox::Vector<ox::UPtr<FileTreeModel>> outChildren;
|
||||
for (auto const&childName : children) {
|
||||
if (childName[0] != '.') {
|
||||
auto const childPath = ox::sfmt("{}/{}", path, childName);
|
||||
OX_REQUIRE(childStat, fs.stat(childPath));
|
||||
if (filter(childPath, childStat)) {
|
||||
OX_REQUIRE_M(child, buildFileTreeModel(
|
||||
explorer,
|
||||
childName,
|
||||
childPath,
|
||||
out.get(),
|
||||
filter,
|
||||
showEmptyDirs));
|
||||
auto const emptyDir = child->isEmptyDir();
|
||||
if (!emptyDir || showEmptyDirs) {
|
||||
outChildren.emplace_back(std::move(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out->setChildren(std::move(outChildren));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
ox::Result<ox::UPtr<FileTreeModel>> buildFileTreeModel(
|
||||
FileExplorer &explorer,
|
||||
std::function<bool(ox::StringViewCR, ox::FileStat const&)> const&filter,
|
||||
bool const showEmptyDirs) noexcept {
|
||||
return buildFileTreeModel(explorer, "Project", "/", nullptr, filter, showEmptyDirs);
|
||||
}
|
||||
|
||||
}
|
18
deps/nostalgia/src/olympic/studio/modlib/src/projectfilepicker.cpp
vendored
Normal file
18
deps/nostalgia/src/olympic/studio/modlib/src/projectfilepicker.cpp
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
|
||||
*/
|
||||
|
||||
#include <studio/filetreemodel.hpp>
|
||||
#include <studio/projectfilepicker.hpp>
|
||||
|
||||
namespace studio {
|
||||
|
||||
class ProjectFilePicker: public FileExplorer {
|
||||
|
||||
public:
|
||||
explicit ProjectFilePicker(keel::Context &kctx) noexcept: FileExplorer{kctx} {}
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user