From 66229de77fdf7d091c8bdb38ec7c1a6bf4cdb7a8 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 18 Jan 2025 22:31:19 -0600 Subject: [PATCH 01/13] [ox/fs] FileSystem fixes with removing files --- deps/ox/src/ox/fs/filesystem/filesystem.cpp | 12 ----- deps/ox/src/ox/fs/filesystem/filesystem.hpp | 50 ++++++++++--------- .../ox/src/ox/fs/filesystem/passthroughfs.cpp | 16 +++--- .../ox/src/ox/fs/filesystem/passthroughfs.hpp | 4 +- 4 files changed, 36 insertions(+), 46 deletions(-) diff --git a/deps/ox/src/ox/fs/filesystem/filesystem.cpp b/deps/ox/src/ox/fs/filesystem/filesystem.cpp index 750e41720..105e2b938 100644 --- a/deps/ox/src/ox/fs/filesystem/filesystem.cpp +++ b/deps/ox/src/ox/fs/filesystem/filesystem.cpp @@ -63,18 +63,6 @@ Error FileSystem::read(const FileAddress &addr, std::size_t readStart, std::size } } -Error FileSystem::remove(const FileAddress &addr, bool recursive) noexcept { - switch (addr.type()) { - case FileAddressType::Inode: - return remove(addr.getInode().value, recursive); - case FileAddressType::ConstPath: - case FileAddressType::Path: - return remove(StringView(addr.getPath().value), recursive); - default: - return ox::Error(1); - } -} - Error FileSystem::write(const FileAddress &addr, const void *buffer, uint64_t size, FileType fileType) noexcept { switch (addr.type()) { case FileAddressType::Inode: diff --git a/deps/ox/src/ox/fs/filesystem/filesystem.hpp b/deps/ox/src/ox/fs/filesystem/filesystem.hpp index e785e4b16..ebc83217a 100644 --- a/deps/ox/src/ox/fs/filesystem/filesystem.hpp +++ b/deps/ox/src/ox/fs/filesystem/filesystem.hpp @@ -57,9 +57,9 @@ class FileSystem { virtual Result> ls(StringViewCR dir) const noexcept = 0; - virtual Error remove(StringViewCR path, bool recursive) noexcept = 0; - - Error remove(const FileAddress &addr, bool recursive = false) noexcept; + Error remove(StringViewCR path, bool recursive = false) noexcept { + return removePath(path, recursive); + } virtual Error resize(uint64_t size, void *buffer) noexcept = 0; @@ -142,6 +142,8 @@ class FileSystem { virtual Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept = 0; + virtual Error removePath(StringViewCR path, bool recursive) noexcept = 0; + virtual Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept = 0; virtual Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept = 0; @@ -209,6 +211,8 @@ class FileSystemTemplate: public MemFS { Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override; + Error removePath(StringViewCR path, bool recursive) noexcept override; + Result directAccessInode(uint64_t) const noexcept override; Result> ls(StringViewCR dir) const noexcept override; @@ -216,8 +220,6 @@ class FileSystemTemplate: public MemFS { template Error ls(StringViewCR path, F cb) const; - Error remove(StringViewCR path, bool recursive) noexcept override; - /** * Resizes FileSystem to minimum possible size. */ @@ -356,6 +358,25 @@ Error FileSystemTemplate::readFileInodeRange(uint64_t inod return m_fs.read(inode, readStart, readSize, reinterpret_cast(buffer), size); } +template +Error FileSystemTemplate::removePath(StringViewCR path, bool recursive) noexcept { + OX_REQUIRE(fd, fileSystemData()); + Directory rootDir(m_fs, fd.rootDirInode); + OX_REQUIRE(inode, rootDir.find(path)); + OX_REQUIRE(st, statInode(inode)); + if (st.fileType == FileType::NormalFile || recursive) { + if (auto err = rootDir.remove(path)) { + // removal failed, try putting the index back + oxLogError(rootDir.write(path, inode)); + return err; + } + } else { + oxTrace("FileSystemTemplate.remove.fail", "Tried to remove directory without recursive setting."); + return ox::Error(1); + } + return ox::Error(0); +} + template Result FileSystemTemplate::directAccessInode(uint64_t inode) const noexcept { auto data = m_fs.read(inode); @@ -384,25 +405,6 @@ Error FileSystemTemplate::ls(StringViewCR path, F cb) cons return dir.ls(cb); } -template -Error FileSystemTemplate::remove(StringViewCR path, bool recursive) noexcept { - OX_REQUIRE(fd, fileSystemData()); - Directory rootDir(m_fs, fd.rootDirInode); - OX_REQUIRE(inode, rootDir.find(path)); - OX_REQUIRE(st, statInode(inode)); - if (st.fileType == FileType::NormalFile || recursive) { - if (auto err = rootDir.remove(path)) { - // removal failed, try putting the index back - oxLogError(rootDir.write(path, inode)); - return err; - } - } else { - oxTrace("FileSystemTemplate.remove.fail", "Tried to remove directory without recursive setting."); - return ox::Error(1); - } - return ox::Error(0); -} - template Error FileSystemTemplate::resize() noexcept { return m_fs.resize(); diff --git a/deps/ox/src/ox/fs/filesystem/passthroughfs.cpp b/deps/ox/src/ox/fs/filesystem/passthroughfs.cpp index dc56b9c48..4cc7aa90f 100644 --- a/deps/ox/src/ox/fs/filesystem/passthroughfs.cpp +++ b/deps/ox/src/ox/fs/filesystem/passthroughfs.cpp @@ -75,14 +75,6 @@ Result> PassThroughFS::ls(StringViewCR dir) const noexcept { return out; } -Error PassThroughFS::remove(StringViewCR path, bool recursive) noexcept { - if (recursive) { - return ox::Error(std::filesystem::remove_all(m_path / stripSlash(path)) != 0); - } else { - return ox::Error(std::filesystem::remove(m_path / stripSlash(path)) != 0); - } -} - Error PassThroughFS::resize(uint64_t, void*) noexcept { // unsupported return ox::Error(1, "resize is not supported by PassThroughFS"); @@ -167,6 +159,14 @@ Error PassThroughFS::readFileInodeRange(uint64_t, std::size_t, std::size_t, void return ox::Error(1, "read(uint64_t, std::size_t, std::size_t, void*, std::size_t*) is not supported by PassThroughFS"); } +Error PassThroughFS::removePath(StringViewCR path, bool const recursive) noexcept { + if (recursive) { + return ox::Error{std::filesystem::remove_all(m_path / stripSlash(path)) == 0}; + } else { + return ox::Error{!std::filesystem::remove(m_path / stripSlash(path))}; + } +} + Error PassThroughFS::writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType) noexcept { const auto p = (m_path / stripSlash(path)); try { diff --git a/deps/ox/src/ox/fs/filesystem/passthroughfs.hpp b/deps/ox/src/ox/fs/filesystem/passthroughfs.hpp index 0a42d8292..a424cefeb 100644 --- a/deps/ox/src/ox/fs/filesystem/passthroughfs.hpp +++ b/deps/ox/src/ox/fs/filesystem/passthroughfs.hpp @@ -45,8 +45,6 @@ class PassThroughFS: public FileSystem { template Error ls(StringViewCR dir, F cb) const noexcept; - Error remove(StringViewCR path, bool recursive) noexcept override; - Error resize(uint64_t size, void *buffer) noexcept override; Result statInode(uint64_t inode) const noexcept override; @@ -75,6 +73,8 @@ class PassThroughFS: public FileSystem { Error readFileInodeRange(uint64_t inode, std::size_t readStart, std::size_t readSize, void *buffer, std::size_t *size) noexcept override; + Error removePath(StringViewCR path, bool recursive) noexcept override; + Error writeFilePath(StringViewCR path, const void *buffer, uint64_t size, FileType fileType) noexcept override; Error writeFileInode(uint64_t inode, const void *buffer, uint64_t size, FileType fileType) noexcept override; From 4e5c7499181904c6e0432a9d56e94c1449f06e79 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 18 Jan 2025 22:32:12 -0600 Subject: [PATCH 02/13] [studio] Add support for deleting files --- .../studio/applib/src/projectexplorer.cpp | 10 ++++++---- .../studio/applib/src/projectexplorer.hpp | 13 +++++++------ .../studio/applib/src/projecttreemodel.cpp | 18 ++++++++++++++---- .../studio/applib/src/projecttreemodel.hpp | 10 +++++++--- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index 0a0584228..0266cfcaa 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -12,12 +12,12 @@ namespace studio { static ox::Result> buildProjectTreeModel( ProjectExplorer &explorer, - ox::StringView name, + 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(explorer, ox::String(name), parent); + auto out = ox::make_unique(explorer, std::move(name), parent); if (stat.fileType == ox::FileType::Directory) { OX_REQUIRE_M(children, fs->ls(path)); std::sort(children.begin(), children.end()); @@ -37,12 +37,14 @@ static ox::Result> buildProjectTreeModel( ProjectExplorer::ProjectExplorer(turbine::Context &ctx) noexcept: m_ctx(ctx) { } -void ProjectExplorer::draw(studio::StudioContext &ctx) noexcept { +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); + if (m_treeModel->draw(ctx.tctx)) { + oxLogError(refreshProjectTreeModel()); + } } ImGui::EndChild(); } diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 8341670d0..87680e363 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -12,27 +12,28 @@ namespace studio { -class ProjectExplorer: public studio::Widget { +class ProjectExplorer: public Widget { private: ox::UPtr m_treeModel; turbine::Context &m_ctx; + public: + // slots + ox::Signal fileChosen; + explicit ProjectExplorer(turbine::Context &ctx) noexcept; - void draw(studio::StudioContext &ctx) noexcept override; + void draw(StudioContext &ctx) noexcept override; void setModel(ox::UPtr &&model) noexcept; ox::Error refreshProjectTreeModel(ox::StringViewCR = {}) noexcept; [[nodiscard]] - inline ox::FileSystem *romFs() noexcept { + ox::FileSystem *romFs() noexcept { return rom(m_ctx); } - // slots - public: - ox::Signal fileChosen; }; } diff --git a/src/olympic/studio/applib/src/projecttreemodel.cpp b/src/olympic/studio/applib/src/projecttreemodel.cpp index 40d98dc07..48ced550d 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.cpp +++ b/src/olympic/studio/applib/src/projecttreemodel.cpp @@ -9,8 +9,10 @@ namespace studio { -ProjectTreeModel::ProjectTreeModel(ProjectExplorer &explorer, ox::String name, - ProjectTreeModel *parent) noexcept: +ProjectTreeModel::ProjectTreeModel( + ProjectExplorer &explorer, + ox::StringParam name, + ProjectTreeModel *parent) noexcept: m_explorer(explorer), m_parent(parent), m_name(std::move(name)) { @@ -23,12 +25,13 @@ ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept: m_children(std::move(other.m_children)) { } -void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { +bool ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { + bool updated = false; constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; if (!m_children.empty()) { if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) { for (auto const&child : m_children) { - child->draw(ctx); + updated = child->draw(ctx) || updated; } ImGui::TreePop(); } @@ -39,9 +42,16 @@ void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) { m_explorer.fileChosen.emit(path); } + if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { + if (ImGui::MenuItem("Delete")) { + updated = m_explorer.romFs()->remove(path).errCode == 0 || updated; + } + ImGui::EndPopup(); + } ImGui::TreePop(); } } + return updated; } void ProjectTreeModel::setChildren(ox::Vector> children) noexcept { diff --git a/src/olympic/studio/applib/src/projecttreemodel.hpp b/src/olympic/studio/applib/src/projecttreemodel.hpp index 6e2050516..2d9313d54 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.hpp +++ b/src/olympic/studio/applib/src/projecttreemodel.hpp @@ -17,19 +17,23 @@ class ProjectTreeModel { ProjectTreeModel *m_parent = nullptr; ox::String m_name; ox::Vector> m_children; + public: - explicit ProjectTreeModel(class ProjectExplorer &explorer, ox::String name, - ProjectTreeModel *parent = nullptr) noexcept; + explicit ProjectTreeModel( + ProjectExplorer &explorer, ox::StringParam name, + ProjectTreeModel *parent = nullptr) noexcept; ProjectTreeModel(ProjectTreeModel &&other) noexcept; - void draw(turbine::Context &ctx) const noexcept; + [[nodiscard]] + bool draw(turbine::Context &ctx) const noexcept; void setChildren(ox::Vector> children) noexcept; private: [[nodiscard]] ox::BasicString<255> fullPath() const noexcept; + }; } From 6e2b4fa7b4694a0ac3b3ccedf591db8f39fa57ae Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 18 Jan 2025 23:33:55 -0600 Subject: [PATCH 03/13] [nostalgia] Cleanup player run in Makefile --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 90c6529da..aafb5608e 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,11 @@ BUILDCORE_PATH=deps/buildcore include ${BUILDCORE_PATH}/base.mk ifeq ($(BC_VAR_OS),darwin) + PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME}.app/Contents/MacOS/${BC_VAR_PROJECT_NAME} NOSTALGIA_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio.app/Contents/MacOS/${BC_VAR_PROJECT_NAME_CAP}Studio MGBA=/Applications/mGBA.app/Contents/MacOS/mGBA else + PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME} NOSTALGIA_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio MGBA=mgba-qt endif @@ -16,9 +18,12 @@ endif pkg-gba: build ${BC_CMD_ENVRUN} ${BC_PY3} ./util/scripts/pkg-gba.py sample_project ${BC_VAR_PROJECT_NAME} +.PHONY: build-player +build-player: + ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} ${BC_VAR_PROJECT_NAME} .PHONY: run -run: build - ./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME} sample_project +run: build-player + ${PROJECT_PLAYER} sample_project .PHONY: run-studio run-studio: build ${NOSTALGIA_STUDIO} From 69241476868bc4cfd69f3920bfd1b262e95025e9 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sat, 18 Jan 2025 23:45:04 -0600 Subject: [PATCH 04/13] [studio] Add ability to add file through dir context menu Also, fix dir context menu to work when dir is closed, and fix it not to override last file in the directory. --- src/olympic/studio/applib/src/newmenu.cpp | 16 ++++-- src/olympic/studio/applib/src/newmenu.hpp | 3 + .../studio/applib/src/projectexplorer.hpp | 3 +- .../studio/applib/src/projecttreemodel.cpp | 15 +++++ .../studio/applib/src/projecttreemodel.hpp | 2 + src/olympic/studio/applib/src/studioapp.cpp | 6 ++ src/olympic/studio/applib/src/studioapp.hpp | 2 + .../modlib/include/studio/itemmaker.hpp | 57 +++++++++++++++---- 8 files changed, 88 insertions(+), 16 deletions(-) diff --git a/src/olympic/studio/applib/src/newmenu.cpp b/src/olympic/studio/applib/src/newmenu.cpp index 1fbe1b641..a0b5be552 100644 --- a/src/olympic/studio/applib/src/newmenu.cpp +++ b/src/olympic/studio/applib/src/newmenu.cpp @@ -22,6 +22,12 @@ void NewMenu::open() noexcept { m_selectedType = 0; m_itemName = ""; m_typeName = ""; + m_path = ""; +} + +void NewMenu::openPath(ox::StringParam path) noexcept { + open(); + m_path = std::move(path); } void NewMenu::close() noexcept { @@ -158,14 +164,14 @@ void NewMenu::finish(StudioContext &sctx) noexcept { oxLogError(ox::Error{1, "New file error: no file name"}); return; } - auto const&im = *m_types[static_cast(m_selectedType)]; - if (sctx.project->exists(im.itemPath(m_itemName))) { + auto const&im = *m_types[m_selectedType]; + auto const path = m_path.len() ? + im.itemPath(m_itemName, m_path) : im.itemPath(m_itemName); + if (sctx.project->exists(path)) { oxLogError(ox::Error{1, "New file error: file already exists"}); return; } - auto const [path, err] = - im.write(sctx, m_itemName, m_selectedTemplate); - if (err) { + if (auto const err = im.write(sctx, path, m_selectedTemplate)) { oxLogError(err); return; } diff --git a/src/olympic/studio/applib/src/newmenu.hpp b/src/olympic/studio/applib/src/newmenu.hpp index c535764ae..30ebb685b 100644 --- a/src/olympic/studio/applib/src/newmenu.hpp +++ b/src/olympic/studio/applib/src/newmenu.hpp @@ -30,6 +30,7 @@ class NewMenu final: public Popup { Stage m_stage = Stage::Closed; ox::String m_typeName; ox::IString<255> m_itemName; + ox::String m_path; ox::Vector> m_types; size_t m_selectedType = 0; size_t m_selectedTemplate = 0; @@ -38,6 +39,8 @@ class NewMenu final: public Popup { public: NewMenu() noexcept; + void openPath(ox::StringParam path) noexcept; + void open() noexcept override; void close() noexcept override; diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 87680e363..9e1981b6b 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -19,7 +19,8 @@ class ProjectExplorer: public Widget { public: // slots - ox::Signal fileChosen; + ox::Signal fileChosen; + ox::Signal addItem; explicit ProjectExplorer(turbine::Context &ctx) noexcept; diff --git a/src/olympic/studio/applib/src/projecttreemodel.cpp b/src/olympic/studio/applib/src/projecttreemodel.cpp index 48ced550d..73b60ac8d 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.cpp +++ b/src/olympic/studio/applib/src/projecttreemodel.cpp @@ -4,6 +4,8 @@ #include +#include + #include "projectexplorer.hpp" #include "projecttreemodel.hpp" @@ -30,10 +32,14 @@ bool ProjectTreeModel::draw(turbine::Context &ctx) 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) { updated = child->draw(ctx) || updated; } ImGui::TreePop(); + } else { + ig::IDStackItem const idStackItem{m_name}; + drawDirContextMenu(); } } else { auto const path = fullPath(); @@ -58,6 +64,15 @@ void ProjectTreeModel::setChildren(ox::Vector> childr 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()); + } + ImGui::EndPopup(); + } +} + ox::BasicString<255> ProjectTreeModel::fullPath() const noexcept { if (m_parent) { return m_parent->fullPath() + "/" + ox::StringView(m_name); diff --git a/src/olympic/studio/applib/src/projecttreemodel.hpp b/src/olympic/studio/applib/src/projecttreemodel.hpp index 2d9313d54..f3ae3743a 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.hpp +++ b/src/olympic/studio/applib/src/projecttreemodel.hpp @@ -31,6 +31,8 @@ class ProjectTreeModel { void setChildren(ox::Vector> children) noexcept; private: + void drawDirContextMenu() const noexcept; + [[nodiscard]] ox::BasicString<255> fullPath() const noexcept; diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index c4193a8dc..40111f8ac 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -52,6 +52,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce m_aboutPopup(m_tctx) { turbine::setApplicationData(m_tctx, &m_sctx); m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile); + m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_newProject.finished.connect(this, &StudioUI::createOpenProject); m_newMenu.finished.connect(this, &StudioUI::openFile); ImGui::GetIO().IniFilename = nullptr; @@ -342,6 +343,11 @@ void StudioUI::handleKeyInput() noexcept { } } +ox::Error StudioUI::addFile(ox::StringViewCR path) noexcept { + m_newMenu.openPath(path); + return {}; +} + ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept { std::error_code ec; std::filesystem::create_directories(toStdStringView(path), ec); diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 649078600..32c4e44a8 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -83,6 +83,8 @@ class StudioUI: public ox::SignalHandler { void handleKeyInput() noexcept; + ox::Error addFile(ox::StringViewCR path) noexcept; + ox::Error createOpenProject(ox::StringViewCR path) noexcept; ox::Error openProjectPath(ox::StringParam path) noexcept; diff --git a/src/olympic/studio/modlib/include/studio/itemmaker.hpp b/src/olympic/studio/modlib/include/studio/itemmaker.hpp index edc536da5..e0b70e617 100644 --- a/src/olympic/studio/modlib/include/studio/itemmaker.hpp +++ b/src/olympic/studio/modlib/include/studio/itemmaker.hpp @@ -128,6 +128,16 @@ class ItemMaker { return m_templates; } + [[nodiscard]] + ox::String const&defaultPath() const noexcept { + return m_parentDir; + } + + [[nodiscard]] + ox::String itemPath(ox::StringViewCR pName, ox::StringViewCR pPath) const noexcept { + return ox::sfmt("/{}/{}.{}", pPath, pName, m_fileExt); + } + [[nodiscard]] ox::String itemPath(ox::StringViewCR pName) const noexcept { return ox::sfmt("/{}/{}.{}", m_parentDir, pName, m_fileExt); @@ -142,12 +152,40 @@ class ItemMaker { /** * Returns path of the file created. * @param ctx + * @param pPath + * @param pTemplateIdx + * @return path of file or error in Result + */ + ox::Error write( + StudioContext &ctx, + ox::StringViewCR pPath, + size_t pTemplateIdx) const noexcept { + return writeItem(ctx, pPath, pTemplateIdx); + } + + /** + * Returns path of the file created. + * @param ctx + * @param pPath * @param pName * @param pTemplateIdx * @return path of file or error in Result */ - virtual ox::Result write( - StudioContext &ctx, ox::StringViewCR pName, size_t pTemplateIdx) const noexcept = 0; + ox::Error write( + StudioContext &ctx, + ox::StringViewCR pPath, + ox::StringViewCR pName, + size_t pTemplateIdx) const noexcept { + auto const path = itemPath(pName, pPath); + return writeItem(ctx, path, pTemplateIdx); + } + + protected: + virtual ox::Error writeItem( + StudioContext &ctx, + ox::StringViewCR pPath, + size_t pTemplateIdx) const noexcept = 0; + }; template @@ -205,20 +243,19 @@ class ItemMakerT final: public ItemMaker { return ox::ModelTypeVersion_v; } - ox::Result write( - StudioContext &sctx, - ox::StringViewCR pName, + ox::Error writeItem( + StudioContext &ctx, + ox::StringViewCR pPath, size_t const pTemplateIdx) const noexcept override { - auto const path = itemPath(pName); - createUuidMapping(keelCtx(sctx.tctx), path, ox::UUID::generate().unwrap()); + createUuidMapping(keelCtx(ctx.tctx), pPath, ox::UUID::generate().unwrap()); auto const&templates = itemTemplates(); auto const tmplIdx = pTemplateIdx < templates.size() ? pTemplateIdx : 0; OX_REQUIRE_M(tmpl, templates[tmplIdx]->getItem( - keelCtx(sctx), typeName(), typeVersion())); + keelCtx(ctx), typeName(), typeVersion())); auto item = tmpl.template get(); - OX_RETURN_ERROR(sctx.project->writeObj(path, *item, m_fmt)); + OX_RETURN_ERROR(ctx.project->writeObj(pPath, *item, m_fmt)); tmpl.free(); - return path; + return {}; } }; From 643f95ec80ff964f864a60a840d20cb329ad47da Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 01:15:33 -0600 Subject: [PATCH 05/13] [studio] Add confirmation dialog for file deletion, move deletion to Project --- src/olympic/studio/applib/src/CMakeLists.txt | 1 + .../studio/applib/src/deleteconfirmation.cpp | 56 +++++++++++++++++++ .../studio/applib/src/deleteconfirmation.hpp | 44 +++++++++++++++ .../studio/applib/src/projectexplorer.cpp | 6 +- .../studio/applib/src/projectexplorer.hpp | 2 + .../studio/applib/src/projecttreemodel.cpp | 11 ++-- .../studio/applib/src/projecttreemodel.hpp | 3 +- src/olympic/studio/applib/src/studioapp.cpp | 10 +++- src/olympic/studio/applib/src/studioapp.hpp | 10 +++- .../studio/modlib/include/studio/project.hpp | 4 +- src/olympic/studio/modlib/src/project.cpp | 8 +++ 11 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 src/olympic/studio/applib/src/deleteconfirmation.cpp create mode 100644 src/olympic/studio/applib/src/deleteconfirmation.hpp diff --git a/src/olympic/studio/applib/src/CMakeLists.txt b/src/olympic/studio/applib/src/CMakeLists.txt index 52788ec97..1c94ba441 100644 --- a/src/olympic/studio/applib/src/CMakeLists.txt +++ b/src/olympic/studio/applib/src/CMakeLists.txt @@ -2,6 +2,7 @@ add_library( StudioAppLib aboutpopup.cpp clawviewer.cpp + deleteconfirmation.cpp filedialogmanager.cpp main.cpp newmenu.cpp diff --git a/src/olympic/studio/applib/src/deleteconfirmation.cpp b/src/olympic/studio/applib/src/deleteconfirmation.cpp new file mode 100644 index 000000000..c81329eef --- /dev/null +++ b/src/olympic/studio/applib/src/deleteconfirmation.cpp @@ -0,0 +1,56 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "deleteconfirmation.hpp" + +namespace studio { + +DeleteConfirmation::DeleteConfirmation(StudioContext &ctx) noexcept: + m_ctx{ctx} { + setTitle("Delete Item"); +} + +void DeleteConfirmation::openPath(ox::StringViewCR path) noexcept { + open(); + m_path = path; +} + +void DeleteConfirmation::open() noexcept { + m_path = ""; + m_stage = Stage::Opening; +} + +void DeleteConfirmation::close() noexcept { + m_stage = Stage::Closed; + m_open = false; +} + +bool DeleteConfirmation::isOpen() const noexcept { + return m_open; +} + +void DeleteConfirmation::draw(StudioContext &ctx) noexcept { + switch (m_stage) { + case Stage::Closed: + break; + case Stage::Opening: + ImGui::OpenPopup(title().c_str()); + m_stage = Stage::Open; + m_open = true; + [[fallthrough]]; + case Stage::Open: + drawWindow(ctx.tctx, m_open, [this] { + ImGui::Text("Are you sure you want to delete %s?", m_path.c_str()); + if (ig::PopupControlsOkCancel(m_open) != ig::PopupResponse::None) { + deleteFile.emit(m_path); + close(); + } + }); + break; + } +} + +} diff --git a/src/olympic/studio/applib/src/deleteconfirmation.hpp b/src/olympic/studio/applib/src/deleteconfirmation.hpp new file mode 100644 index 000000000..4a03dbc16 --- /dev/null +++ b/src/olympic/studio/applib/src/deleteconfirmation.hpp @@ -0,0 +1,44 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include +#include + +namespace studio { + +class DeleteConfirmation final: public Popup { + private: + enum class Stage { + Closed, + Opening, + Open, + }; + Stage m_stage = Stage::Closed; + bool m_open{}; + ox::String m_path; + StudioContext &m_ctx; + + public: + ox::Signal deleteFile; + + DeleteConfirmation(StudioContext &ctx) noexcept; + + void openPath(ox::StringViewCR path) noexcept; + + void open() noexcept override; + + void close() noexcept override; + + [[nodiscard]] + bool isOpen() const noexcept override; + + void draw(StudioContext &ctx) noexcept override; + +}; + +} diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index 0266cfcaa..464f6718a 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -42,9 +42,7 @@ void ProjectExplorer::draw(StudioContext &ctx) noexcept { ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true); ImGui::SetNextItemOpen(true); if (m_treeModel) { - if (m_treeModel->draw(ctx.tctx)) { - oxLogError(refreshProjectTreeModel()); - } + m_treeModel->draw(ctx.tctx); } ImGui::EndChild(); } @@ -56,7 +54,7 @@ void ProjectExplorer::setModel(ox::UPtr &&model) noexcept { ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept { OX_REQUIRE_M(model, buildProjectTreeModel(*this, "Project", "/", nullptr)); setModel(std::move(model)); - return ox::Error(0); + return {}; } } diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index 9e1981b6b..8ef25761b 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -21,6 +21,8 @@ class ProjectExplorer: public Widget { // slots ox::Signal fileChosen; ox::Signal addItem; + ox::Signal addDir; + ox::Signal deleteItem; explicit ProjectExplorer(turbine::Context &ctx) noexcept; diff --git a/src/olympic/studio/applib/src/projecttreemodel.cpp b/src/olympic/studio/applib/src/projecttreemodel.cpp index 73b60ac8d..aab3604ff 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.cpp +++ b/src/olympic/studio/applib/src/projecttreemodel.cpp @@ -27,14 +27,13 @@ ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept: m_children(std::move(other.m_children)) { } -bool ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { - bool updated = false; +void ProjectTreeModel::draw(turbine::Context &ctx) 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) { - updated = child->draw(ctx) || updated; + child->draw(ctx); } ImGui::TreePop(); } else { @@ -50,14 +49,13 @@ bool ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { } if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::MenuItem("Delete")) { - updated = m_explorer.romFs()->remove(path).errCode == 0 || updated; + m_explorer.deleteItem.emit(path); } ImGui::EndPopup(); } ImGui::TreePop(); } } - return updated; } void ProjectTreeModel::setChildren(ox::Vector> children) noexcept { @@ -69,6 +67,9 @@ void ProjectTreeModel::drawDirContextMenu() const noexcept { if (ImGui::MenuItem("Add Item")) { m_explorer.addItem.emit(fullPath()); } + if (ImGui::MenuItem("Add Directory")) { + m_explorer.addDir.emit(fullPath()); + } ImGui::EndPopup(); } } diff --git a/src/olympic/studio/applib/src/projecttreemodel.hpp b/src/olympic/studio/applib/src/projecttreemodel.hpp index f3ae3743a..f8b1bee33 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.hpp +++ b/src/olympic/studio/applib/src/projecttreemodel.hpp @@ -25,8 +25,7 @@ class ProjectTreeModel { ProjectTreeModel(ProjectTreeModel &&other) noexcept; - [[nodiscard]] - bool draw(turbine::Context &ctx) const noexcept; + void draw(turbine::Context &ctx) const noexcept; void setChildren(ox::Vector> children) noexcept; diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 40111f8ac..6265f863e 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -53,6 +53,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce turbine::setApplicationData(m_tctx, &m_sctx); m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile); m_projectExplorer.addItem.connect(this, &StudioUI::addFile); + m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); m_newProject.finished.connect(this, &StudioUI::createOpenProject); m_newMenu.finished.connect(this, &StudioUI::openFile); ImGui::GetIO().IniFilename = nullptr; @@ -348,6 +349,11 @@ ox::Error StudioUI::addFile(ox::StringViewCR path) noexcept { return {}; } +ox::Error StudioUI::deleteFile(ox::StringViewCR path) noexcept { + m_deleteConfirmation.openPath(path); + return {}; +} + ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept { std::error_code ec; std::filesystem::create_directories(toStdStringView(path), ec); @@ -362,8 +368,8 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept { OX_RETURN_ERROR( ox::make_unique_catch(keelCtx(m_tctx), std::move(path), m_projectDataDir) .moveTo(m_project)); - auto const sctx = applicationData(m_tctx); - sctx->project = m_project.get(); + m_sctx.project = m_project.get(); + m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem); turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath())); m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 32c4e44a8..c722c6084 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -12,7 +12,9 @@ #include #include #include + #include "aboutpopup.hpp" +#include "deleteconfirmation.hpp" #include "newmenu.hpp" #include "newproject.hpp" #include "projectexplorer.hpp" @@ -38,12 +40,14 @@ class StudioUI: public ox::SignalHandler { BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr; NewMenu m_newMenu; + DeleteConfirmation m_deleteConfirmation{m_sctx}; NewProject m_newProject; AboutPopup m_aboutPopup; - ox::Array const m_popups = { + ox::Array const m_popups = { &m_newMenu, &m_newProject, - &m_aboutPopup + &m_aboutPopup, + &m_deleteConfirmation, }; bool m_showProjectExplorer = true; @@ -85,6 +89,8 @@ class StudioUI: public ox::SignalHandler { ox::Error addFile(ox::StringViewCR path) noexcept; + ox::Error deleteFile(ox::StringViewCR path) noexcept; + ox::Error createOpenProject(ox::StringViewCR path) noexcept; ox::Error openProjectPath(ox::StringParam path) noexcept; diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index dc13901c8..8c8221b4c 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -45,7 +45,7 @@ constexpr ox::StringView parentDir(ox::StringView path) noexcept { return substr(path, 0, extStart); } -class Project { +class Project: public ox::SignalHandler { private: ox::SmallMap> m_typeFmt; keel::Context &m_ctx; @@ -91,6 +91,8 @@ class Project { ox::Result stat(ox::StringViewCR path) const noexcept; + ox::Error deleteItem(ox::StringViewCR path) noexcept; + [[nodiscard]] bool exists(ox::StringViewCR path) const noexcept; diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 84acad604..2777483aa 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -69,6 +69,14 @@ ox::Result Project::stat(ox::StringViewCR path) const noexcept { return m_fs.stat(path); } +ox::Error Project::deleteItem(ox::StringViewCR path) noexcept { + auto const err = m_fs.remove(path); + if (!err) { + fileDeleted.emit(path); + } + return err; +} + bool Project::exists(ox::StringViewCR path) const noexcept { return m_fs.stat(path).error == 0; } From a3e5f27ab82006979f77a01eb836ac0aa8fa6d56 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 01:43:38 -0600 Subject: [PATCH 06/13] [ox/std] Fix Mac build --- deps/ox/src/ox/std/anyptr.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deps/ox/src/ox/std/anyptr.hpp b/deps/ox/src/ox/std/anyptr.hpp index 9d566ad7c..19f96bf87 100644 --- a/deps/ox/src/ox/std/anyptr.hpp +++ b/deps/ox/src/ox/std/anyptr.hpp @@ -58,9 +58,9 @@ class AnyPtrT { template constexpr AnyPtrT(T *ptr) noexcept { if (std::is_constant_evaluated()) { - m_wrapPtr = new Wrap(ptr); + m_wrapPtr = new Wrap(ptr); } else { - m_wrapPtr = new(m_wrapData.data()) Wrap(ptr); + m_wrapPtr = new(m_wrapData.data()) Wrap(ptr); } } From a2139c09b22d1ec75fd36fbb8f7bef83ba5fe27e Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 01:44:26 -0600 Subject: [PATCH 07/13] [studio] Cleanup unused member --- src/olympic/studio/applib/src/deleteconfirmation.cpp | 3 +-- src/olympic/studio/applib/src/deleteconfirmation.hpp | 3 +-- src/olympic/studio/applib/src/studioapp.hpp | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/olympic/studio/applib/src/deleteconfirmation.cpp b/src/olympic/studio/applib/src/deleteconfirmation.cpp index c81329eef..9eaa591ca 100644 --- a/src/olympic/studio/applib/src/deleteconfirmation.cpp +++ b/src/olympic/studio/applib/src/deleteconfirmation.cpp @@ -8,8 +8,7 @@ namespace studio { -DeleteConfirmation::DeleteConfirmation(StudioContext &ctx) noexcept: - m_ctx{ctx} { +DeleteConfirmation::DeleteConfirmation() noexcept { setTitle("Delete Item"); } diff --git a/src/olympic/studio/applib/src/deleteconfirmation.hpp b/src/olympic/studio/applib/src/deleteconfirmation.hpp index 4a03dbc16..012dbcbb5 100644 --- a/src/olympic/studio/applib/src/deleteconfirmation.hpp +++ b/src/olympic/studio/applib/src/deleteconfirmation.hpp @@ -21,12 +21,11 @@ class DeleteConfirmation final: public Popup { Stage m_stage = Stage::Closed; bool m_open{}; ox::String m_path; - StudioContext &m_ctx; public: ox::Signal deleteFile; - DeleteConfirmation(StudioContext &ctx) noexcept; + DeleteConfirmation() noexcept; void openPath(ox::StringViewCR path) noexcept; diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index c722c6084..a03736240 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -40,7 +40,7 @@ class StudioUI: public ox::SignalHandler { BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr; NewMenu m_newMenu; - DeleteConfirmation m_deleteConfirmation{m_sctx}; + DeleteConfirmation m_deleteConfirmation; NewProject m_newProject; AboutPopup m_aboutPopup; ox::Array const m_popups = { From 9d1155843e80762c96fb2b85dfeaef762a24b943 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 01:48:53 -0600 Subject: [PATCH 08/13] [nostalgia] Rename player from 'nostalgia' to 'Nostalgia' --- Makefile | 13 ++++++------- src/nostalgia/player/CMakeLists.txt | 14 +++++++------- src/nostalgia/player/app.hpp | 8 -------- 3 files changed, 13 insertions(+), 22 deletions(-) delete mode 100644 src/nostalgia/player/app.hpp diff --git a/Makefile b/Makefile index aafb5608e..5e5c49cb6 100644 --- a/Makefile +++ b/Makefile @@ -5,14 +5,13 @@ BUILDCORE_PATH=deps/buildcore include ${BUILDCORE_PATH}/base.mk ifeq ($(BC_VAR_OS),darwin) - PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME}.app/Contents/MacOS/${BC_VAR_PROJECT_NAME} - NOSTALGIA_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio.app/Contents/MacOS/${BC_VAR_PROJECT_NAME_CAP}Studio + PROJECT_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio.app/Contents/MacOS/${BC_VAR_PROJECT_NAME_CAP}Studio MGBA=/Applications/mGBA.app/Contents/MacOS/mGBA else - PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME} - NOSTALGIA_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio + PROJECT_STUDIO=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP}Studio MGBA=mgba-qt endif +PROJECT_PLAYER=./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME_CAP} .PHONY: pkg-gba pkg-gba: build @@ -20,13 +19,13 @@ pkg-gba: build .PHONY: build-player build-player: - ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} ${BC_VAR_PROJECT_NAME} + ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} ${BC_VAR_PROJECT_NAME_CAP} .PHONY: run run: build-player ${PROJECT_PLAYER} sample_project .PHONY: run-studio run-studio: build - ${NOSTALGIA_STUDIO} + ${PROJECT_STUDIO} .PHONY: gba-run gba-run: pkg-gba ${MGBA} ${BC_VAR_PROJECT_NAME}.gba @@ -35,7 +34,7 @@ debug: build ${BC_CMD_HOST_DEBUGGER} ./build/${BC_VAR_CURRENT_BUILD}/bin/${BC_VAR_PROJECT_NAME} sample_project .PHONY: debug-studio debug-studio: build - ${BC_CMD_HOST_DEBUGGER} ${NOSTALGIA_STUDIO} + ${BC_CMD_HOST_DEBUGGER} ${PROJECT_STUDIO} .PHONY: configure-gba configure-gba: diff --git a/src/nostalgia/player/CMakeLists.txt b/src/nostalgia/player/CMakeLists.txt index d26095784..331df8ba7 100644 --- a/src/nostalgia/player/CMakeLists.txt +++ b/src/nostalgia/player/CMakeLists.txt @@ -1,26 +1,26 @@ add_executable( - nostalgia WIN32 + Nostalgia WIN32 app.cpp ) # enable LTO if(NOT WIN32) - set_property(TARGET nostalgia PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + set_property(TARGET Nostalgia PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) endif() if(COMMAND OBJCOPY_FILE) - set_target_properties(nostalgia + set_target_properties(Nostalgia PROPERTIES LINK_FLAGS ${LINKER_FLAGS} COMPILER_FLAGS "-mthumb -mthumb-interwork" ) - OBJCOPY_FILE(nostalgia) - #PADBIN_FILE(nostalgia) + OBJCOPY_FILE(Nostalgia) + #PADBIN_FILE(Nostalgia) endif() target_link_libraries( - nostalgia + Nostalgia NostalgiaKeelModules NostalgiaProfile OlympicApplib @@ -29,7 +29,7 @@ target_link_libraries( install( TARGETS - nostalgia + Nostalgia DESTINATION bin ) diff --git a/src/nostalgia/player/app.hpp b/src/nostalgia/player/app.hpp deleted file mode 100644 index 343571530..000000000 --- a/src/nostalgia/player/app.hpp +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. - */ - -#include -#include - -typename ox::Error run(ox::UniquePtr &&fs) noexcept; From cc466a9f1d17050d89092fd9e5ba5ff7752d1313 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 09:06:16 -0600 Subject: [PATCH 09/13] [studio] Add support for adding and deleting directories --- src/olympic/studio/applib/src/CMakeLists.txt | 1 + .../studio/applib/src/deleteconfirmation.cpp | 2 +- src/olympic/studio/applib/src/newdir.cpp | 59 +++++++++++++++++++ src/olympic/studio/applib/src/newdir.hpp | 49 +++++++++++++++ .../studio/applib/src/projecttreemodel.cpp | 12 +++- .../studio/applib/src/projecttreemodel.hpp | 2 +- src/olympic/studio/applib/src/studioapp.cpp | 10 +++- src/olympic/studio/applib/src/studioapp.hpp | 7 ++- .../modlib/include/studio/imguiutil.hpp | 11 +++- .../studio/modlib/include/studio/project.hpp | 5 ++ src/olympic/studio/modlib/src/imguiutil.cpp | 17 ++++-- src/olympic/studio/modlib/src/project.cpp | 29 +++++++-- 12 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 src/olympic/studio/applib/src/newdir.cpp create mode 100644 src/olympic/studio/applib/src/newdir.hpp diff --git a/src/olympic/studio/applib/src/CMakeLists.txt b/src/olympic/studio/applib/src/CMakeLists.txt index 1c94ba441..2127d8fe2 100644 --- a/src/olympic/studio/applib/src/CMakeLists.txt +++ b/src/olympic/studio/applib/src/CMakeLists.txt @@ -5,6 +5,7 @@ add_library( deleteconfirmation.cpp filedialogmanager.cpp main.cpp + newdir.cpp newmenu.cpp newproject.cpp projectexplorer.cpp diff --git a/src/olympic/studio/applib/src/deleteconfirmation.cpp b/src/olympic/studio/applib/src/deleteconfirmation.cpp index 9eaa591ca..9ef0e063b 100644 --- a/src/olympic/studio/applib/src/deleteconfirmation.cpp +++ b/src/olympic/studio/applib/src/deleteconfirmation.cpp @@ -43,7 +43,7 @@ void DeleteConfirmation::draw(StudioContext &ctx) noexcept { case Stage::Open: drawWindow(ctx.tctx, m_open, [this] { ImGui::Text("Are you sure you want to delete %s?", m_path.c_str()); - if (ig::PopupControlsOkCancel(m_open) != ig::PopupResponse::None) { + if (ig::PopupControlsOkCancel(m_open, "Yes", "No") != ig::PopupResponse::None) { deleteFile.emit(m_path); close(); } diff --git a/src/olympic/studio/applib/src/newdir.cpp b/src/olympic/studio/applib/src/newdir.cpp new file mode 100644 index 000000000..9f2c87093 --- /dev/null +++ b/src/olympic/studio/applib/src/newdir.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#include + +#include "newdir.hpp" + +namespace studio { + +NewDir::NewDir() noexcept { + setTitle("New Directory"); + setSize({280, 0}); +} + +void NewDir::openPath(ox::StringViewCR path) noexcept { + open(); + m_path = path; +} + +void NewDir::open() noexcept { + m_path = ""; + m_stage = Stage::Opening; +} + +void NewDir::close() noexcept { + m_stage = Stage::Closed; + m_open = false; +} + +bool NewDir::isOpen() const noexcept { + return m_open; +} + +void NewDir::draw(StudioContext &ctx) noexcept { + switch (m_stage) { + case Stage::Closed: + break; + case Stage::Opening: + ImGui::OpenPopup(title().c_str()); + m_open = true; + [[fallthrough]]; + case Stage::Open: + drawWindow(ctx.tctx, m_open, [this] { + if (m_stage == Stage::Opening) { + ImGui::SetKeyboardFocusHere(); + } + ig::InputText("Name", m_str); + if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) { + newDir.emit(m_path + "/" + m_str); + close(); + } + }); + m_stage = Stage::Open; + break; + } +} + +} diff --git a/src/olympic/studio/applib/src/newdir.hpp b/src/olympic/studio/applib/src/newdir.hpp new file mode 100644 index 000000000..b79ef0b9c --- /dev/null +++ b/src/olympic/studio/applib/src/newdir.hpp @@ -0,0 +1,49 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include + +#include +#include + +namespace studio { + +class NewDir final: public Popup { + private: + enum class Stage { + Closed, + Opening, + Open, + }; + Stage m_stage = Stage::Closed; + bool m_open{}; + ox::String m_path; + ox::IString<255> m_str; + + public: + ox::Signal newDir; + + NewDir() noexcept; + + void openPath(ox::StringViewCR path) noexcept; + + void open() noexcept override; + + void close() noexcept override; + + [[nodiscard]] + bool isOpen() const noexcept override; + + void draw(StudioContext &ctx) noexcept override; + + [[nodiscard]] + constexpr ox::CStringView value() const noexcept { + return m_str; + } + +}; + +} diff --git a/src/olympic/studio/applib/src/projecttreemodel.cpp b/src/olympic/studio/applib/src/projecttreemodel.cpp index aab3604ff..51e945ef9 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.cpp +++ b/src/olympic/studio/applib/src/projecttreemodel.cpp @@ -27,13 +27,13 @@ ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept: m_children(std::move(other.m_children)) { } -void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { +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(ctx); + child->draw(tctx); } ImGui::TreePop(); } else { @@ -54,6 +54,9 @@ void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { ImGui::EndPopup(); } ImGui::TreePop(); + ig::dragDropSource([this] { + ImGui::Text("%s", m_name.c_str()); + }); } } } @@ -70,13 +73,16 @@ void ProjectTreeModel::drawDirContextMenu() const noexcept { if (ImGui::MenuItem("Add Directory")) { m_explorer.addDir.emit(fullPath()); } + if (ImGui::MenuItem("Delete")) { + m_explorer.deleteItem.emit(fullPath()); + } ImGui::EndPopup(); } } ox::BasicString<255> ProjectTreeModel::fullPath() const noexcept { if (m_parent) { - return m_parent->fullPath() + "/" + ox::StringView(m_name); + return m_parent->fullPath() + "/" + m_name; } return {}; } diff --git a/src/olympic/studio/applib/src/projecttreemodel.hpp b/src/olympic/studio/applib/src/projecttreemodel.hpp index f8b1bee33..e7060d4e2 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.hpp +++ b/src/olympic/studio/applib/src/projecttreemodel.hpp @@ -25,7 +25,7 @@ class ProjectTreeModel { ProjectTreeModel(ProjectTreeModel &&other) noexcept; - void draw(turbine::Context &ctx) const noexcept; + void draw(turbine::Context &tctx) const noexcept; void setChildren(ox::Vector> children) noexcept; diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 6265f863e..a9358b33c 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -52,6 +52,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce m_aboutPopup(m_tctx) { turbine::setApplicationData(m_tctx, &m_sctx); m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile); + m_projectExplorer.addDir.connect(this, &StudioUI::addDir); m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); m_newProject.finished.connect(this, &StudioUI::createOpenProject); @@ -344,6 +345,11 @@ void StudioUI::handleKeyInput() noexcept { } } +ox::Error StudioUI::addDir(ox::StringViewCR path) noexcept { + m_newDirDialog.openPath(path); + return {}; +} + ox::Error StudioUI::addFile(ox::StringViewCR path) noexcept { m_newMenu.openPath(path); return {}; @@ -369,8 +375,10 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept { ox::make_unique_catch(keelCtx(m_tctx), std::move(path), m_projectDataDir) .moveTo(m_project)); m_sctx.project = m_project.get(); - m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem); turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath())); + m_deleteConfirmation.deleteFile.connect(m_sctx.project, &Project::deleteItem); + m_newDirDialog.newDir.connect(m_sctx.project, &Project::mkdir); + m_project->dirAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_openFiles.clear(); diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index a03736240..199feac27 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -15,6 +15,7 @@ #include "aboutpopup.hpp" #include "deleteconfirmation.hpp" +#include "newdir.hpp" #include "newmenu.hpp" #include "newproject.hpp" #include "projectexplorer.hpp" @@ -41,13 +42,15 @@ class StudioUI: public ox::SignalHandler { BaseEditor *m_activeEditorUpdatePending = nullptr; NewMenu m_newMenu; DeleteConfirmation m_deleteConfirmation; + NewDir m_newDirDialog; NewProject m_newProject; AboutPopup m_aboutPopup; - ox::Array const m_popups = { + ox::Array const m_popups = { &m_newMenu, &m_newProject, &m_aboutPopup, &m_deleteConfirmation, + &m_newDirDialog, }; bool m_showProjectExplorer = true; @@ -87,6 +90,8 @@ class StudioUI: public ox::SignalHandler { void handleKeyInput() noexcept; + ox::Error addDir(ox::StringViewCR path) noexcept; + ox::Error addFile(ox::StringViewCR path) noexcept; ox::Error deleteFile(ox::StringViewCR path) noexcept; diff --git a/src/olympic/studio/modlib/include/studio/imguiutil.hpp b/src/olympic/studio/modlib/include/studio/imguiutil.hpp index 01cd3e842..c82f32e86 100644 --- a/src/olympic/studio/modlib/include/studio/imguiutil.hpp +++ b/src/olympic/studio/modlib/include/studio/imguiutil.hpp @@ -175,9 +175,16 @@ enum class PopupResponse { Cancel, }; -PopupResponse PopupControlsOkCancel(float popupWidth, bool &popupOpen); +PopupResponse PopupControlsOkCancel( + float popupWidth, + bool &popupOpen, + ox::CStringViewCR ok = "OK", + ox::CStringViewCR cancel = "Cancel"); -PopupResponse PopupControlsOkCancel(bool &popupOpen); +PopupResponse PopupControlsOkCancel( + bool &popupOpen, + ox::CStringViewCR ok = "OK", + ox::CStringViewCR cancel = "Cancel"); [[nodiscard]] bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz = {285, 0}); diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index 8c8221b4c..8ee319fe4 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -19,6 +19,7 @@ namespace studio { enum class ProjectEvent { None, + DirAdded, FileAdded, // FileRecognized is triggered for all matching files upon a new // subscription to a section of the project and upon the addition of a file. @@ -120,6 +121,7 @@ class Project: public ox::SignalHandler { // signals public: ox::Signal fileEvent; + ox::Signal dirAdded; ox::Signal fileAdded; // FileRecognized is triggered for all matching files upon a new // subscription to a section of the project and upon the addition of a @@ -177,6 +179,9 @@ ox::Error Project::subscribe(ProjectEvent e, ox::SignalHandler *tgt, Functor &&s switch (e) { case ProjectEvent::None: break; + case ProjectEvent::DirAdded: + connect(this, &Project::dirAdded, tgt, slot); + break; case ProjectEvent::FileAdded: connect(this, &Project::fileAdded, tgt, slot); break; diff --git a/src/olympic/studio/modlib/src/imguiutil.cpp b/src/olympic/studio/modlib/src/imguiutil.cpp index 7618a2aae..b6d8fb2a7 100644 --- a/src/olympic/studio/modlib/src/imguiutil.cpp +++ b/src/olympic/studio/modlib/src/imguiutil.cpp @@ -54,18 +54,22 @@ bool PushButton(ox::CStringViewCR lbl, ImVec2 const&btnSz) noexcept { return ImGui::Button(lbl.c_str(), btnSz); } -PopupResponse PopupControlsOkCancel(float popupWidth, bool &popupOpen) { +PopupResponse PopupControlsOkCancel( + float popupWidth, + bool &popupOpen, + ox::CStringViewCR ok, + ox::CStringViewCR cancel) { auto out = PopupResponse::None; constexpr auto btnSz = ImVec2{50, BtnSz.y}; ImGui::Separator(); ImGui::SetCursorPosX(popupWidth - 118); - if (ImGui::Button("OK", btnSz)) { + if (ImGui::Button(ok.c_str(), btnSz)) { ImGui::CloseCurrentPopup(); popupOpen = false; out = PopupResponse::OK; } ImGui::SameLine(); - if (ImGui::IsKeyDown(ImGuiKey_Escape) || ImGui::Button("Cancel", btnSz)) { + if (ImGui::IsKeyDown(ImGuiKey_Escape) || ImGui::Button(cancel.c_str(), btnSz)) { ImGui::CloseCurrentPopup(); popupOpen = false; out = PopupResponse::Cancel; @@ -73,8 +77,11 @@ PopupResponse PopupControlsOkCancel(float popupWidth, bool &popupOpen) { return out; } -PopupResponse PopupControlsOkCancel(bool &popupOpen) { - return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17, popupOpen); +PopupResponse PopupControlsOkCancel( + bool &popupOpen, + ox::CStringViewCR ok, + ox::CStringViewCR cancel) { + return PopupControlsOkCancel(ImGui::GetContentRegionAvail().x + 17, popupOpen, ok, cancel); } bool BeginPopup(turbine::Context &ctx, ox::CStringViewCR popupName, bool &show, ImVec2 const&sz) { diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 2777483aa..cd90d8a0a 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -59,10 +59,10 @@ ox::Error Project::mkdir(ox::StringViewCR path) const noexcept { auto const [stat, err] = m_fs.stat(path); if (err) { OX_RETURN_ERROR(m_fs.mkdir(path, true)); - fileUpdated.emit(path, {}); + dirAdded.emit(path); } return stat.fileType == ox::FileType::Directory ? - ox::Error{} : ox::Error(1, "path exists as normal file"); + ox::Error{} : ox::Error{1, "path exists as normal file"}; } ox::Result Project::stat(ox::StringViewCR path) const noexcept { @@ -70,11 +70,28 @@ ox::Result Project::stat(ox::StringViewCR path) const noexcept { } ox::Error Project::deleteItem(ox::StringViewCR path) noexcept { - auto const err = m_fs.remove(path); - if (!err) { - fileDeleted.emit(path); + OX_REQUIRE(stat, m_fs.stat(path)); + if (stat.fileType == ox::FileType::Directory) { + bool partialRemoval{}; + OX_REQUIRE(members, m_fs.ls(path)); + for (auto const&p : members) { + partialRemoval = m_fs.remove(ox::sfmt("{}/{}", path, p)) || partialRemoval; + } + if (partialRemoval) { + return ox::Error{1, "failed to remove one or more directory members"}; + } + auto const err = m_fs.remove(path); + if (!err) { + fileDeleted.emit(path); + } + return err; + } else { + auto const err = m_fs.remove(path); + if (!err) { + fileDeleted.emit(path); + } + return err; } - return err; } bool Project::exists(ox::StringViewCR path) const noexcept { From 800ca8517698d35309570466ebf5da803b546958 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 09:26:06 -0600 Subject: [PATCH 10/13] [ox/std] Fix possible error that occurs with appending on boundary of small string size --- deps/ox/src/ox/std/string.hpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/deps/ox/src/ox/std/string.hpp b/deps/ox/src/ox/std/string.hpp index caaf105fd..baf8f2d65 100644 --- a/deps/ox/src/ox/std/string.hpp +++ b/deps/ox/src/ox/std/string.hpp @@ -423,9 +423,10 @@ constexpr BasicString BasicString::operato const std::size_t strLen = src.len(); const auto currentLen = len(); BasicString cpy(currentLen + strLen); - cpy.m_buff.resize(m_buff.size() + strLen); + cpy.m_buff.resize(m_buff.size() + strLen + 1); ox::listcpy(&cpy.m_buff[0], m_buff.data(), currentLen); - ox::listcpy(&cpy.m_buff[currentLen], src.data(), strLen + 1); + ox::listcpy(&cpy.m_buff[currentLen], src.data(), strLen); + cpy.m_buff[cpy.m_buff.size() - 1] = 0; return cpy; } @@ -436,7 +437,8 @@ constexpr BasicString BasicString::operato BasicString cpy(currentLen + strLen); cpy.m_buff.resize(m_buff.size() + strLen); ox::listcpy(&cpy.m_buff[0], m_buff.data(), currentLen); - ox::listcpy(&cpy.m_buff[currentLen], src.data(), strLen + 1); + ox::listcpy(&cpy.m_buff[currentLen], src.data(), strLen); + cpy.m_buff[cpy.m_buff.size() - 1] = 0; return cpy; } From 500b93562c946576f16938e49301746d8ed9f8a5 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 09:33:17 -0600 Subject: [PATCH 11/13] [studio] Make new dir window OK on Enter key --- src/olympic/studio/applib/src/newdir.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/olympic/studio/applib/src/newdir.cpp b/src/olympic/studio/applib/src/newdir.cpp index 9f2c87093..01caed375 100644 --- a/src/olympic/studio/applib/src/newdir.cpp +++ b/src/olympic/studio/applib/src/newdir.cpp @@ -46,6 +46,10 @@ void NewDir::draw(StudioContext &ctx) noexcept { ImGui::SetKeyboardFocusHere(); } ig::InputText("Name", m_str); + if (ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + newDir.emit(m_path + "/" + m_str); + close(); + } if (ig::PopupControlsOkCancel(m_open) == ig::PopupResponse::OK) { newDir.emit(m_path + "/" + m_str); close(); From 1cbc57628619b342207819f1c98a6c64fe4fc635 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 11:41:08 -0600 Subject: [PATCH 12/13] [studio] Complete drag/drop support for files --- .../studio/applib/src/projecttreemodel.cpp | 11 +++------- .../studio/applib/src/projecttreemodel.hpp | 8 ++++++- .../studio/modlib/include/studio/dragdrop.hpp | 22 +++++++++++++++++++ .../modlib/include/studio/imguiutil.hpp | 1 + .../studio/modlib/include/studio/studio.hpp | 1 + 5 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 src/olympic/studio/modlib/include/studio/dragdrop.hpp diff --git a/src/olympic/studio/applib/src/projecttreemodel.cpp b/src/olympic/studio/applib/src/projecttreemodel.cpp index 51e945ef9..cc7afd1a9 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.cpp +++ b/src/olympic/studio/applib/src/projecttreemodel.cpp @@ -4,6 +4,7 @@ #include +#include #include #include "projectexplorer.hpp" @@ -54,8 +55,9 @@ void ProjectTreeModel::draw(turbine::Context &tctx) const noexcept { ImGui::EndPopup(); } ImGui::TreePop(); - ig::dragDropSource([this] { + std::ignore = ig::dragDropSource([this] { ImGui::Text("%s", m_name.c_str()); + return ig::setDragDropPayload("FileRef", FileRef{fullPath()}); }); } } @@ -80,11 +82,4 @@ void ProjectTreeModel::drawDirContextMenu() const noexcept { } } -ox::BasicString<255> ProjectTreeModel::fullPath() const noexcept { - if (m_parent) { - return m_parent->fullPath() + "/" + m_name; - } - return {}; -} - } diff --git a/src/olympic/studio/applib/src/projecttreemodel.hpp b/src/olympic/studio/applib/src/projecttreemodel.hpp index e7060d4e2..fdfc1fae6 100644 --- a/src/olympic/studio/applib/src/projecttreemodel.hpp +++ b/src/olympic/studio/applib/src/projecttreemodel.hpp @@ -32,8 +32,14 @@ class ProjectTreeModel { private: void drawDirContextMenu() const noexcept; + template> [[nodiscard]] - ox::BasicString<255> fullPath() const noexcept; + Str fullPath() const noexcept { + if (m_parent) { + return m_parent->fullPath() + "/" + m_name; + } + return {}; + } }; diff --git a/src/olympic/studio/modlib/include/studio/dragdrop.hpp b/src/olympic/studio/modlib/include/studio/dragdrop.hpp new file mode 100644 index 000000000..e75bb0402 --- /dev/null +++ b/src/olympic/studio/modlib/include/studio/dragdrop.hpp @@ -0,0 +1,22 @@ +/* + * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved. + */ + +#pragma once + +#include +#include + +namespace studio { + +struct FileRef { + static constexpr auto TypeName = "net.drinkingtea.studio.FileRef"; + static constexpr auto TypeVersion = 1; + ox::String path; +}; + +OX_MODEL_BEGIN(FileRef) + OX_MODEL_FIELD(path) +OX_MODEL_END() + +} diff --git a/src/olympic/studio/modlib/include/studio/imguiutil.hpp b/src/olympic/studio/modlib/include/studio/imguiutil.hpp index c82f32e86..60bff7521 100644 --- a/src/olympic/studio/modlib/include/studio/imguiutil.hpp +++ b/src/olympic/studio/modlib/include/studio/imguiutil.hpp @@ -63,6 +63,7 @@ auto dragDropSource(auto const&cb) noexcept { if (ig::DragDropSource const tgt; tgt) [[unlikely]] { return cb(); } + return ox::Error{}; } else { if (ig::DragDropSource const tgt; tgt) [[unlikely]] { cb(); diff --git a/src/olympic/studio/modlib/include/studio/studio.hpp b/src/olympic/studio/modlib/include/studio/studio.hpp index b18e82636..cd7474738 100644 --- a/src/olympic/studio/modlib/include/studio/studio.hpp +++ b/src/olympic/studio/modlib/include/studio/studio.hpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include From d68e64931b37d7d8bbaff7b43bf131c7acf2aa97 Mon Sep 17 00:00:00 2001 From: Gary Talent Date: Sun, 19 Jan 2025 11:41:48 -0600 Subject: [PATCH 13/13] [nostalgia/core/studio/tilesheeteditor] Add support for dragging palette to palette selector --- .../studio/tilesheeteditor/tilesheeteditor-imgui.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp index f5717a543..5211211e0 100644 --- a/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp +++ b/src/nostalgia/modules/core/src/studio/tilesheeteditor/tilesheeteditor-imgui.cpp @@ -443,6 +443,16 @@ void TileSheetEditorImGui::drawPaletteMenu() noexcept { if (ig::ComboBox("Palette", files, m_selectedPaletteIdx)) { oxLogError(m_model.setPalette(files[m_selectedPaletteIdx])); } + if (ig::DragDropTarget const dragDropTarget; dragDropTarget) { + auto const [ref, err] = ig::getDragDropPayload("FileRef"); + if (!err) { + auto const oldVal = m_selectedPaletteIdx; + std::ignore = ox::findIdx(files.begin(), files.end(), ref.path).moveTo(m_selectedPaletteIdx); + if (oldVal != m_selectedPaletteIdx) { + oxLogError(m_model.setPalette(files[m_selectedPaletteIdx])); + } + } + } auto const pages = m_model.pal().pages.size(); if (pages > 1) { ig::IndentStackItem const indentStackItem{20};