[studio] Add confirmation for closing file with unsaved changes
This commit is contained in:
parent
4728699585
commit
cd1f4bdaa3
@ -64,6 +64,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
|
|||||||
m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove);
|
m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove);
|
||||||
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);
|
||||||
|
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
|
||||||
loadModules();
|
loadModules();
|
||||||
// open project and files
|
// open project and files
|
||||||
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
|
auto const [config, err] = studio::readConfig<StudioConfig>(keelCtx(m_tctx));
|
||||||
@ -134,6 +135,7 @@ void StudioUI::draw() noexcept {
|
|||||||
for (auto const p : m_popups) {
|
for (auto const p : m_popups) {
|
||||||
p->draw(m_sctx);
|
p->draw(m_sctx);
|
||||||
}
|
}
|
||||||
|
m_closeFileConfirm.draw(m_sctx);
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
handleKeyInput();
|
handleKeyInput();
|
||||||
@ -214,7 +216,7 @@ void StudioUI::drawTabs() noexcept {
|
|||||||
auto open = true;
|
auto open = true;
|
||||||
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
auto const unsavedChanges = e->unsavedChanges() ? ImGuiTabItemFlags_UnsavedDocument : 0;
|
||||||
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
|
auto const selected = m_activeEditorUpdatePending == e.get() ? ImGuiTabItemFlags_SetSelected : 0;
|
||||||
auto const flags = unsavedChanges | selected;
|
auto const flags = unsavedChanges | selected | ImGuiTabItemFlags_NoAssumedClosure;
|
||||||
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
|
if (ImGui::BeginTabItem(e->itemDisplayName().c_str(), &open, flags)) {
|
||||||
if (m_activeEditor != e.get()) [[unlikely]] {
|
if (m_activeEditor != e.get()) [[unlikely]] {
|
||||||
m_activeEditor = e.get();
|
m_activeEditor = e.get();
|
||||||
@ -237,16 +239,20 @@ void StudioUI::drawTabs() noexcept {
|
|||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
if (!open) {
|
if (!open) {
|
||||||
e->close();
|
if (e->unsavedChanges()) {
|
||||||
if (m_activeEditor == (*it).get()) {
|
m_closeFileConfirm.open();
|
||||||
m_activeEditor = nullptr;
|
} else {
|
||||||
}
|
e->close();
|
||||||
try {
|
if (m_activeEditor == (*it).get()) {
|
||||||
OX_THROW_ERROR(m_editors.erase(it).moveTo(it));
|
m_activeEditor = nullptr;
|
||||||
} catch (ox::Exception const&ex) {
|
}
|
||||||
oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.src.file_name(), ex.src.line());
|
try {
|
||||||
} catch (std::exception const&ex) {
|
OX_THROW_ERROR(m_editors.erase(it).moveTo(it));
|
||||||
oxErrf("Editor tab deletion failed: {}\n", ex.what());
|
} catch (ox::Exception const&ex) {
|
||||||
|
oxErrf("Editor tab deletion failed: {} ({}:{})\n", ex.what(), ex.src.file_name(), ex.src.line());
|
||||||
|
} catch (std::exception const&ex) {
|
||||||
|
oxErrf("Editor tab deletion failed: {}\n", ex.what());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
++it;
|
++it;
|
||||||
@ -483,6 +489,20 @@ ox::Error StudioUI::openFileActiveTab(ox::StringViewCR path, bool const makeActi
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) noexcept {
|
||||||
|
if (response == ig::PopupResponse::OK && m_activeEditor) {
|
||||||
|
for (size_t i{}; auto &e : m_editors) {
|
||||||
|
if (m_activeEditor == e.get()) {
|
||||||
|
oxLogError(closeFile(e->itemPath()));
|
||||||
|
oxLogError(m_editors.erase(i).error);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept {
|
ox::Error StudioUI::closeFile(ox::StringViewCR path) noexcept {
|
||||||
if (!m_openFiles.contains(path)) {
|
if (!m_openFiles.contains(path)) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include <ox/std/string.hpp>
|
#include <ox/std/string.hpp>
|
||||||
|
|
||||||
#include <studio/editor.hpp>
|
#include <studio/editor.hpp>
|
||||||
|
#include <studio/imguiutil.hpp>
|
||||||
#include <studio/module.hpp>
|
#include <studio/module.hpp>
|
||||||
#include <studio/project.hpp>
|
#include <studio/project.hpp>
|
||||||
#include <studio/task.hpp>
|
#include <studio/task.hpp>
|
||||||
@ -45,6 +46,7 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
NewMenu m_newMenu{keelCtx(m_tctx)};
|
NewMenu m_newMenu{keelCtx(m_tctx)};
|
||||||
DeleteConfirmation m_deleteConfirmation;
|
DeleteConfirmation m_deleteConfirmation;
|
||||||
NewDir m_newDirDialog;
|
NewDir m_newDirDialog;
|
||||||
|
ig::QuestionPopup m_closeFileConfirm{"Close File?", "This file has unsaved changes. Close?"};
|
||||||
RenameFile m_renameFile;
|
RenameFile m_renameFile;
|
||||||
NewProject m_newProject;
|
NewProject m_newProject;
|
||||||
AboutPopup m_aboutPopup;
|
AboutPopup m_aboutPopup;
|
||||||
@ -114,6 +116,8 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
|
|
||||||
ox::Error openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept;
|
ox::Error openFileActiveTab(ox::StringViewCR path, bool makeActiveTab) noexcept;
|
||||||
|
|
||||||
|
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
|
||||||
|
|
||||||
ox::Error closeFile(ox::StringViewCR path) noexcept;
|
ox::Error closeFile(ox::StringViewCR path) noexcept;
|
||||||
|
|
||||||
ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept;
|
ox::Error queueDirMove(ox::StringParam src, ox::StringParam dst) noexcept;
|
||||||
|
@ -303,6 +303,34 @@ class FilePicker {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class QuestionPopup {
|
||||||
|
private:
|
||||||
|
enum class Stage {
|
||||||
|
Closed,
|
||||||
|
Opening,
|
||||||
|
Open,
|
||||||
|
};
|
||||||
|
Stage m_stage = Stage::Closed;
|
||||||
|
bool m_open{};
|
||||||
|
ox::String m_title;
|
||||||
|
ox::String m_question;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ox::Signal<ox::Error(ig::PopupResponse)> response;
|
||||||
|
|
||||||
|
QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept;
|
||||||
|
|
||||||
|
void open() noexcept;
|
||||||
|
|
||||||
|
void close() noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]]
|
||||||
|
bool isOpen() const noexcept;
|
||||||
|
|
||||||
|
void draw(StudioContext &ctx, ImVec2 const &sz = {}) noexcept;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]]
|
[[nodiscard]]
|
||||||
bool mainWinHasFocus() noexcept;
|
bool mainWinHasFocus() noexcept;
|
||||||
|
|
||||||
|
@ -225,6 +225,59 @@ void FilePicker::show() noexcept {
|
|||||||
m_show = true;
|
m_show = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QuestionPopup::QuestionPopup(ox::StringParam title, ox::StringParam question) noexcept:
|
||||||
|
m_title{std::move(title)},
|
||||||
|
m_question{std::move(question)} {
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuestionPopup::open() noexcept {
|
||||||
|
m_stage = Stage::Opening;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuestionPopup::close() noexcept {
|
||||||
|
m_stage = Stage::Closed;
|
||||||
|
m_open = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestionPopup::isOpen() const noexcept {
|
||||||
|
return m_open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QuestionPopup::draw(StudioContext &ctx, ImVec2 const &sz) noexcept {
|
||||||
|
switch (m_stage) {
|
||||||
|
case Stage::Closed:
|
||||||
|
break;
|
||||||
|
case Stage::Opening:
|
||||||
|
ImGui::OpenPopup(m_title.c_str());
|
||||||
|
m_stage = Stage::Open;
|
||||||
|
m_open = true;
|
||||||
|
[[fallthrough]];
|
||||||
|
case Stage::Open:
|
||||||
|
centerNextWindow(ctx.tctx);
|
||||||
|
ImGui::SetNextWindowSize(static_cast<ImVec2>(sz));
|
||||||
|
constexpr auto modalFlags = ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
|
||||||
|
if (ImGui::BeginPopupModal(m_title.c_str(), &m_open, modalFlags)) {
|
||||||
|
ImGui::Text("%s", m_question.c_str());
|
||||||
|
auto const r = PopupControlsOkCancel(m_open, "Yes", "No");
|
||||||
|
response.emit(r);
|
||||||
|
switch (r) {
|
||||||
|
case PopupResponse::None:
|
||||||
|
break;
|
||||||
|
case PopupResponse::OK:
|
||||||
|
close();
|
||||||
|
break;
|
||||||
|
case PopupResponse::Cancel:
|
||||||
|
close();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool s_mainWinHasFocus{};
|
bool s_mainWinHasFocus{};
|
||||||
bool mainWinHasFocus() noexcept {
|
bool mainWinHasFocus() noexcept {
|
||||||
return s_mainWinHasFocus;
|
return s_mainWinHasFocus;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user