[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
aboutpopup.cpp
clawviewer.cpp
deleteconfirmation.cpp
filedialogmanager.cpp
main.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::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<ProjectTreeModel> &&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 {};
}
}

View File

@ -21,6 +21,8 @@ class ProjectExplorer: public Widget {
// slots
ox::Signal<ox::Error(ox::StringViewCR)> fileChosen;
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;

View File

@ -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<ox::UPtr<ProjectTreeModel>> 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();
}
}

View File

@ -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<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);
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<Project>(keelCtx(m_tctx), std::move(path), m_projectDataDir)
.moveTo(m_project));
auto const sctx = applicationData<StudioContext>(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);

View File

@ -12,7 +12,9 @@
#include <studio/module.hpp>
#include <studio/project.hpp>
#include <studio/task.hpp>
#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<Popup*, 3> const m_popups = {
ox::Array<Popup*, 4> 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;

View File

@ -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<ox::String, ox::Optional<ox::ClawFormat>> m_typeFmt;
keel::Context &m_ctx;
@ -91,6 +91,8 @@ class Project {
ox::Result<ox::FileStat> stat(ox::StringViewCR path) const noexcept;
ox::Error deleteItem(ox::StringViewCR path) noexcept;
[[nodiscard]]
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);
}
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;
}