diff --git a/src/olympic/keel/include/keel/assetmanager.hpp b/src/olympic/keel/include/keel/assetmanager.hpp index 0a1525e2..364f26f2 100644 --- a/src/olympic/keel/include/keel/assetmanager.hpp +++ b/src/olympic/keel/include/keel/assetmanager.hpp @@ -238,6 +238,9 @@ class AssetManager { } ox::Error updateAssetId(ox::StringViewCR oldId, ox::StringViewCR newId) noexcept final { + if (!m_cache.contains(oldId)) { + return {}; + } auto &o = m_cache[oldId]; auto &n = m_cache[newId]; n = std::move(o); @@ -246,11 +249,14 @@ class AssetManager { } void gc() noexcept final { - for (auto const&ack : m_cache.keys()) { + for (size_t i = 0; i < m_cache.keys().size();) { + auto const &ack = m_cache.keys()[i]; auto &ac = m_cache[ack]; if (!ac->references()) { m_cache.erase(ack); + continue; } + ++i; } } @@ -309,6 +315,9 @@ class AssetManager { if (m_fileUpdated.contains(newId)) { return ox::Error{1, "new asset ID already has an entry"}; } + if (!m_fileUpdated.contains(oldId)) { + return {}; + } auto &o = m_fileUpdated[oldId]; auto &n = m_fileUpdated[newId]; n = std::move(o); diff --git a/src/olympic/studio/applib/src/projectexplorer.cpp b/src/olympic/studio/applib/src/projectexplorer.cpp index 942548c5..f0edd82a 100644 --- a/src/olympic/studio/applib/src/projectexplorer.cpp +++ b/src/olympic/studio/applib/src/projectexplorer.cpp @@ -30,6 +30,10 @@ void ProjectExplorer::fileMoved(ox::StringViewCR src, ox::StringViewCR dst) cons moveItem.emit(src, dst); } +void ProjectExplorer::dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept { + moveDir.emit(src, dst); +} + void ProjectExplorer::fileContextMenu(ox::StringViewCR path) const noexcept { if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::MenuItem("Delete")) { diff --git a/src/olympic/studio/applib/src/projectexplorer.hpp b/src/olympic/studio/applib/src/projectexplorer.hpp index e35d7e05..c0b4420a 100644 --- a/src/olympic/studio/applib/src/projectexplorer.hpp +++ b/src/olympic/studio/applib/src/projectexplorer.hpp @@ -20,6 +20,7 @@ class ProjectExplorer final: public FileExplorer { ox::Signal addDir; ox::Signal deleteItem; ox::Signal renameItem; + ox::Signal moveDir; ox::Signal moveItem; explicit ProjectExplorer(keel::Context &kctx) noexcept; @@ -33,6 +34,8 @@ class ProjectExplorer final: public FileExplorer { void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; + void dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept override; + void fileContextMenu(ox::StringViewCR path) const noexcept override; void dirContextMenu(ox::StringViewCR path) const noexcept override; diff --git a/src/olympic/studio/applib/src/studioapp.cpp b/src/olympic/studio/applib/src/studioapp.cpp index 54ecc628..64c6ba89 100644 --- a/src/olympic/studio/applib/src/studioapp.cpp +++ b/src/olympic/studio/applib/src/studioapp.cpp @@ -59,6 +59,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile); m_projectExplorer.renameItem.connect(this, &StudioUI::renameFile); + m_projectExplorer.moveDir.connect(this, &StudioUI::queueDirMove); m_projectExplorer.moveItem.connect(this, &StudioUI::queueFileMove); m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove); m_newProject.finished.connect(this, &StudioUI::createOpenProject); @@ -486,6 +487,11 @@ ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept { return {}; } +ox::Error StudioUI::queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept { + m_queuedDirMoves.emplace_back(std::move(src), std::move(dst)); + return {}; +} + ox::Error StudioUI::queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept { m_queuedMoves.emplace_back(std::move(src), std::move(dst)); return {}; @@ -496,6 +502,10 @@ void StudioUI::procFileMoves() noexcept { oxLogError(m_project->moveItem(m.a, m.b)); } m_queuedMoves.clear(); + for (auto const &m : m_queuedDirMoves) { + oxLogError(m_project->moveDir(m.a, m.b)); + } + m_queuedDirMoves.clear(); } } diff --git a/src/olympic/studio/applib/src/studioapp.hpp b/src/olympic/studio/applib/src/studioapp.hpp index 7e0a960d..c2e6eb32 100644 --- a/src/olympic/studio/applib/src/studioapp.hpp +++ b/src/olympic/studio/applib/src/studioapp.hpp @@ -40,7 +40,8 @@ class StudioUI: public ox::SignalHandler { BaseEditor *m_activeEditorOnLastDraw = nullptr; BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr; - ox::Vector> m_queuedMoves; + ox::Vector> m_queuedMoves; + ox::Vector> m_queuedDirMoves; NewMenu m_newMenu{keelCtx(m_tctx)}; DeleteConfirmation m_deleteConfirmation; NewDir m_newDirDialog; @@ -113,6 +114,8 @@ class StudioUI: public ox::SignalHandler { ox::Error closeFile(ox::StringViewCR path) noexcept; + ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept; + ox::Error queueFileMove(ox::StringParam src, ox::StringParam dst) noexcept; void procFileMoves() noexcept; diff --git a/src/olympic/studio/modlib/include/studio/dragdrop.hpp b/src/olympic/studio/modlib/include/studio/dragdrop.hpp index e75bb040..b4cd2798 100644 --- a/src/olympic/studio/modlib/include/studio/dragdrop.hpp +++ b/src/olympic/studio/modlib/include/studio/dragdrop.hpp @@ -13,10 +13,12 @@ struct FileRef { static constexpr auto TypeName = "net.drinkingtea.studio.FileRef"; static constexpr auto TypeVersion = 1; ox::String path; + bool isDir{}; }; OX_MODEL_BEGIN(FileRef) OX_MODEL_FIELD(path) + OX_MODEL_FIELD(isDir) OX_MODEL_END() } diff --git a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp index 673f7b94..3d5a3656 100644 --- a/src/olympic/studio/modlib/include/studio/filetreemodel.hpp +++ b/src/olympic/studio/modlib/include/studio/filetreemodel.hpp @@ -47,6 +47,8 @@ class FileExplorer: public ox::SignalHandler { virtual void fileMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; + virtual void dirMoved(ox::StringViewCR src, ox::StringViewCR dst) const noexcept; + void drawFileContextMenu(ox::CStringViewCR path) const noexcept; void drawDirContextMenu(ox::CStringViewCR path) const noexcept; diff --git a/src/olympic/studio/modlib/include/studio/project.hpp b/src/olympic/studio/modlib/include/studio/project.hpp index c058281d..76489c68 100644 --- a/src/olympic/studio/modlib/include/studio/project.hpp +++ b/src/olympic/studio/modlib/include/studio/project.hpp @@ -94,6 +94,8 @@ class Project: public ox::SignalHandler { ox::Error moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcept; + ox::Error moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept; + ox::Error deleteItem(ox::StringViewCR path) noexcept; [[nodiscard]] diff --git a/src/olympic/studio/modlib/src/filetreemodel.cpp b/src/olympic/studio/modlib/src/filetreemodel.cpp index f0ac65e3..d7d347a0 100644 --- a/src/olympic/studio/modlib/src/filetreemodel.cpp +++ b/src/olympic/studio/modlib/src/filetreemodel.cpp @@ -43,6 +43,8 @@ void FileExplorer::fileDeleted(ox::StringViewCR) const noexcept {} void FileExplorer::fileMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} +void FileExplorer::dirMoved(ox::StringViewCR, ox::StringViewCR) const noexcept {} + void FileExplorer::drawFileContextMenu(ox::CStringViewCR path) const noexcept { ig::IDStackItem const idStackItem{path}; fileContextMenu(path); @@ -93,11 +95,21 @@ void FileTreeModel::draw(turbine::Context &tctx) const noexcept { } ig::IDStackItem const idStackItem{m_name}; m_explorer.drawDirContextMenu(m_fullPath); + if (m_explorer.fileDraggable()) { + std::ignore = ig::dragDropSource([this] { + ImGui::Text("%s", m_name.c_str()); + return ig::setDragDropPayload("FileRef", FileRef{ox::String{m_fullPath}, true}); + }); + } if (ig::DragDropTarget const dragDropTarget; dragDropTarget) { auto const [ref, err] = ig::getDragDropPayload("FileRef"); if (!err) { auto const name = substr(ref.path, find(ref.path.rbegin(), ref.path.rend(), '/').offset() + 1); - m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + if (ref.isDir) { + m_explorer.dirMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + } else { + m_explorer.fileMoved(ref.path, sfmt("{}/{}", m_fullPath, name)); + } } } if (nodeOpen) { diff --git a/src/olympic/studio/modlib/src/project.cpp b/src/olympic/studio/modlib/src/project.cpp index 8c1f7f9a..1aa311a0 100644 --- a/src/olympic/studio/modlib/src/project.cpp +++ b/src/olympic/studio/modlib/src/project.cpp @@ -26,6 +26,26 @@ static void generateTypes(ox::TypeStore &ts) noexcept { } } +static ox::Result> listAllRecursive(ox::FileSystem &fs, ox::StringViewCR path) { + // there really isn't much recourse if this function fails, just log it and move on + auto [out, outErr] = fs.ls(path); + oxLogError(outErr); + for (auto const &p : out) { + auto const [stat, statErr] = fs.stat(path); + if (statErr) { + oxLogError(statErr); + continue; + } + if (stat.fileType == ox::FileType::Directory) { + OX_REQUIRE_M(l, listAllRecursive(fs, sfmt("{}/{}", path, p))); + for (auto &c : l) { + out.emplace_back(std::move(c)); + } + } + } + return std::move(out); +} + Project::Project(keel::Context &ctx, ox::String path, ox::StringViewCR projectDataDir): m_kctx(ctx), @@ -78,6 +98,21 @@ ox::Error Project::moveItem(ox::StringViewCR src, ox::StringViewCR dest) noexcep return {}; } +ox::Error Project::moveDir(ox::StringViewCR src, ox::StringViewCR dest) noexcept { + OX_REQUIRE(files, listAllRecursive(m_fs, src)); + OX_RETURN_ERROR(m_fs.move(src, dest)); + fileMoved.emit(src, dest, ox::UUID{}); + for (auto const &op : files) { + auto const name = + substr(op, ox::find(op.rbegin(), op.rend(), '/').offset() + 1); + auto const np = sfmt("{}/{}", dest, name); + OX_RETURN_ERROR(keel::updatePath(m_kctx, op, np)); + OX_REQUIRE(uuid, keel::pathToUuid(m_kctx, dest)); + fileMoved.emit(src, dest, uuid); + } + return {}; +} + ox::Error Project::deleteItem(ox::StringViewCR path) noexcept { OX_REQUIRE(stat, m_fs.stat(path)); if (stat.fileType == ox::FileType::Directory) {