[studio] Add confirmation dialog for file deletion, move deletion to Project
All checks were successful
Build / build (push) Successful in 3m16s

This commit is contained in:
Gary Talent 2025-01-19 01:15:33 -06:00
parent 6924147686
commit 643f95ec80
11 changed files with 139 additions and 16 deletions

View File

@ -2,6 +2,7 @@ add_library(
StudioAppLib StudioAppLib
aboutpopup.cpp aboutpopup.cpp
clawviewer.cpp clawviewer.cpp
deleteconfirmation.cpp
filedialogmanager.cpp filedialogmanager.cpp
main.cpp main.cpp
newmenu.cpp newmenu.cpp

View File

@ -0,0 +1,56 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#include <studio/imguiutil.hpp>
#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;
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
*/
#pragma once
#include <ox/std/string.hpp>
#include <studio/context.hpp>
#include <studio/popup.hpp>
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<ox::Error(ox::StringViewCR path)> 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;
};
}

View File

@ -42,9 +42,7 @@ void ProjectExplorer::draw(StudioContext &ctx) noexcept {
ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true); ImGui::BeginChild("ProjectExplorer", ImVec2(300, viewport.y), true);
ImGui::SetNextItemOpen(true); ImGui::SetNextItemOpen(true);
if (m_treeModel) { if (m_treeModel) {
if (m_treeModel->draw(ctx.tctx)) { m_treeModel->draw(ctx.tctx);
oxLogError(refreshProjectTreeModel());
}
} }
ImGui::EndChild(); ImGui::EndChild();
} }
@ -56,7 +54,7 @@ void ProjectExplorer::setModel(ox::UPtr<ProjectTreeModel> &&model) noexcept {
ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept { ox::Error ProjectExplorer::refreshProjectTreeModel(ox::StringViewCR) noexcept {
OX_REQUIRE_M(model, buildProjectTreeModel(*this, "Project", "/", nullptr)); OX_REQUIRE_M(model, buildProjectTreeModel(*this, "Project", "/", nullptr));
setModel(std::move(model)); setModel(std::move(model));
return ox::Error(0); return {};
} }
} }

View File

@ -21,6 +21,8 @@ class ProjectExplorer: public Widget {
// slots // slots
ox::Signal<ox::Error(ox::StringViewCR)> fileChosen; ox::Signal<ox::Error(ox::StringViewCR)> fileChosen;
ox::Signal<ox::Error(ox::StringViewCR)> addItem; ox::Signal<ox::Error(ox::StringViewCR)> addItem;
ox::Signal<ox::Error(ox::StringViewCR)> addDir;
ox::Signal<ox::Error(ox::StringViewCR)> deleteItem;
explicit ProjectExplorer(turbine::Context &ctx) noexcept; explicit ProjectExplorer(turbine::Context &ctx) noexcept;

View File

@ -27,14 +27,13 @@ ProjectTreeModel::ProjectTreeModel(ProjectTreeModel &&other) noexcept:
m_children(std::move(other.m_children)) { m_children(std::move(other.m_children)) {
} }
bool ProjectTreeModel::draw(turbine::Context &ctx) const noexcept { void ProjectTreeModel::draw(turbine::Context &ctx) const noexcept {
bool updated = false;
constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; constexpr auto dirFlags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick;
if (!m_children.empty()) { if (!m_children.empty()) {
if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) { if (ImGui::TreeNodeEx(m_name.c_str(), dirFlags)) {
drawDirContextMenu(); drawDirContextMenu();
for (auto const&child : m_children) { for (auto const&child : m_children) {
updated = child->draw(ctx) || updated; child->draw(ctx);
} }
ImGui::TreePop(); ImGui::TreePop();
} else { } else {
@ -50,14 +49,13 @@ bool ProjectTreeModel::draw(turbine::Context &ctx) const noexcept {
} }
if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) { if (ImGui::BeginPopupContextItem("FileMenu", ImGuiPopupFlags_MouseButtonRight)) {
if (ImGui::MenuItem("Delete")) { if (ImGui::MenuItem("Delete")) {
updated = m_explorer.romFs()->remove(path).errCode == 0 || updated; m_explorer.deleteItem.emit(path);
} }
ImGui::EndPopup(); ImGui::EndPopup();
} }
ImGui::TreePop(); ImGui::TreePop();
} }
} }
return updated;
} }
void ProjectTreeModel::setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept { void ProjectTreeModel::setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept {
@ -69,6 +67,9 @@ void ProjectTreeModel::drawDirContextMenu() const noexcept {
if (ImGui::MenuItem("Add Item")) { if (ImGui::MenuItem("Add Item")) {
m_explorer.addItem.emit(fullPath()); m_explorer.addItem.emit(fullPath());
} }
if (ImGui::MenuItem("Add Directory")) {
m_explorer.addDir.emit(fullPath());
}
ImGui::EndPopup(); ImGui::EndPopup();
} }
} }

View File

@ -25,8 +25,7 @@ class ProjectTreeModel {
ProjectTreeModel(ProjectTreeModel &&other) noexcept; ProjectTreeModel(ProjectTreeModel &&other) noexcept;
[[nodiscard]] void draw(turbine::Context &ctx) const noexcept;
bool draw(turbine::Context &ctx) const noexcept;
void setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept; void setChildren(ox::Vector<ox::UPtr<ProjectTreeModel>> children) noexcept;

View File

@ -53,6 +53,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
turbine::setApplicationData(m_tctx, &m_sctx); turbine::setApplicationData(m_tctx, &m_sctx);
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile); m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
m_projectExplorer.addItem.connect(this, &StudioUI::addFile); m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
m_projectExplorer.deleteItem.connect(this, &StudioUI::deleteFile);
m_newProject.finished.connect(this, &StudioUI::createOpenProject); m_newProject.finished.connect(this, &StudioUI::createOpenProject);
m_newMenu.finished.connect(this, &StudioUI::openFile); m_newMenu.finished.connect(this, &StudioUI::openFile);
ImGui::GetIO().IniFilename = nullptr; ImGui::GetIO().IniFilename = nullptr;
@ -348,6 +349,11 @@ ox::Error StudioUI::addFile(ox::StringViewCR path) noexcept {
return {}; return {};
} }
ox::Error StudioUI::deleteFile(ox::StringViewCR path) noexcept {
m_deleteConfirmation.openPath(path);
return {};
}
ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept { ox::Error StudioUI::createOpenProject(ox::StringViewCR path) noexcept {
std::error_code ec; std::error_code ec;
std::filesystem::create_directories(toStdStringView(path), ec); std::filesystem::create_directories(toStdStringView(path), ec);
@ -362,8 +368,8 @@ ox::Error StudioUI::openProjectPath(ox::StringParam path) noexcept {
OX_RETURN_ERROR( OX_RETURN_ERROR(
ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir) ox::make_unique_catch<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
.moveTo(m_project)); .moveTo(m_project));
auto const sctx = applicationData<StudioContext>(m_tctx); m_sctx.project = m_project.get();
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())); turbine::setWindowTitle(m_tctx, ox::sfmt("{} - {}", keelCtx(m_tctx).appName, m_project->projectPath()));
m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileAdded.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);
m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel); m_project->fileDeleted.connect(&m_projectExplorer, &ProjectExplorer::refreshProjectTreeModel);

View File

@ -12,7 +12,9 @@
#include <studio/module.hpp> #include <studio/module.hpp>
#include <studio/project.hpp> #include <studio/project.hpp>
#include <studio/task.hpp> #include <studio/task.hpp>
#include "aboutpopup.hpp" #include "aboutpopup.hpp"
#include "deleteconfirmation.hpp"
#include "newmenu.hpp" #include "newmenu.hpp"
#include "newproject.hpp" #include "newproject.hpp"
#include "projectexplorer.hpp" #include "projectexplorer.hpp"
@ -38,12 +40,14 @@ class StudioUI: public ox::SignalHandler {
BaseEditor *m_activeEditor = nullptr; BaseEditor *m_activeEditor = nullptr;
BaseEditor *m_activeEditorUpdatePending = nullptr; BaseEditor *m_activeEditorUpdatePending = nullptr;
NewMenu m_newMenu; NewMenu m_newMenu;
DeleteConfirmation m_deleteConfirmation{m_sctx};
NewProject m_newProject; NewProject m_newProject;
AboutPopup m_aboutPopup; AboutPopup m_aboutPopup;
ox::Array<Popup*, 3> const m_popups = { ox::Array<Popup*, 4> const m_popups = {
&m_newMenu, &m_newMenu,
&m_newProject, &m_newProject,
&m_aboutPopup &m_aboutPopup,
&m_deleteConfirmation,
}; };
bool m_showProjectExplorer = true; bool m_showProjectExplorer = true;
@ -85,6 +89,8 @@ class StudioUI: public ox::SignalHandler {
ox::Error addFile(ox::StringViewCR path) noexcept; ox::Error addFile(ox::StringViewCR path) noexcept;
ox::Error deleteFile(ox::StringViewCR path) noexcept;
ox::Error createOpenProject(ox::StringViewCR path) noexcept; ox::Error createOpenProject(ox::StringViewCR path) noexcept;
ox::Error openProjectPath(ox::StringParam path) noexcept; ox::Error openProjectPath(ox::StringParam path) noexcept;

View File

@ -45,7 +45,7 @@ constexpr ox::StringView parentDir(ox::StringView path) noexcept {
return substr(path, 0, extStart); return substr(path, 0, extStart);
} }
class Project { class Project: public ox::SignalHandler {
private: private:
ox::SmallMap<ox::String, ox::Optional<ox::ClawFormat>> m_typeFmt; ox::SmallMap<ox::String, ox::Optional<ox::ClawFormat>> m_typeFmt;
keel::Context &m_ctx; keel::Context &m_ctx;
@ -91,6 +91,8 @@ class Project {
ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept; ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept;
ox::Error deleteItem(ox::StringViewCR path) noexcept;
[[nodiscard]] [[nodiscard]]
bool exists(ox::StringViewCR path) const noexcept; bool exists(ox::StringViewCR path) const noexcept;

View File

@ -69,6 +69,14 @@ ox::Result<ox::FileStat> Project::stat(ox::StringViewCR path) const noexcept {
return m_fs.stat(path); 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 { bool Project::exists(ox::StringViewCR path) const noexcept {
return m_fs.stat(path).error == 0; return m_fs.stat(path).error == 0;
} }