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 750e4172..105e2b93 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 e785e4b1..ebc83217 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 dc56b9c4..4cc7aa90 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 0a42d829..a424cefe 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 0a058422..0266cfca 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 8341670d..87680e36 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 40d98dc0..48ced550 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 6e205051..2d9313d5 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 90c6529d..aafb5608 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 1fbe1b64..a0b5be55 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 c535764a..30ebb685 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 87680e36..9e1981b6 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 48ced550..73b60ac8 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 2d9313d5..f3ae3743 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 c4193a8d..40111f8a 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 64907860..32c4e44a 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 edc536da..e0b70e61 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 52788ec9..1c94ba44 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 00000000..c81329ee --- /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 00000000..4a03dbc1 --- /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 0266cfca..464f6718 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 9e1981b6..8ef25761 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 73b60ac8..aab3604f 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 f3ae3743..f8b1bee3 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 40111f8a..6265f863 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 32c4e44a..c722c608 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 dc13901c..8c8221b4 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 84acad60..2777483a 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 9d566ad7..19f96bf8 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 c81329ee..9eaa591c 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 4a03dbc1..012dbcbb 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 c722c608..a0373624 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 aafb5608..5e5c49cb 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 d2609578..331df8ba 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 34357153..00000000 --- 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 1c94ba44..2127d8fe 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 9eaa591c..9ef0e063 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 00000000..9f2c8709 --- /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 00000000..b79ef0b9 --- /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 aab3604f..51e945ef 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 f8b1bee3..e7060d4e 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 6265f863..a9358b33 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 a0373624..199feac2 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 01cd3e84..c82f32e8 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 8c8221b4..8ee319fe 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 7618a2aa..b6d8fb2a 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 2777483a..cd90d8a0 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 caaf105f..baf8f2d6 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 9f2c8709..01caed37 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 51e945ef..cc7afd1a 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 e7060d4e..fdfc1fae 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 00000000..e75bb040 --- /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 c82f32e8..60bff752 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 b18e8263..cd747473 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 f5717a54..5211211e 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};