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; }