[turbine,studio] Make Studio confirm with user before closing app if any unsaved changes
All checks were successful
Build / build (push) Successful in 1m15s

This commit is contained in:
Gary Talent 2025-05-01 23:15:06 -05:00
parent 4770bb6a93
commit e5dd448fe7
9 changed files with 70 additions and 21 deletions

View File

@ -5,7 +5,7 @@
namespace studio::files {
static constexpr ox::Array<uint8_t, 162588> RobotoMedium_ttfData {
static const ox::Array<uint8_t, 162588> RobotoMedium_ttfData {
0x00, 0x01, 0x00, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x04,
0x00, 0x10, 0x47, 0x50, 0x4f, 0x53, 0x7d, 0xaa, 0x71, 0x8c,
0x00, 0x02, 0x08, 0xa8, 0x00, 0x00, 0x59, 0x0c, 0x47, 0x53,

View File

@ -19,6 +19,11 @@
namespace studio {
static bool shutdownHandler(turbine::Context &ctx) {
auto sctx = turbine::applicationData<StudioContext>(ctx);
return sctx->ui.handleShutdown();
}
void navigateTo(StudioContext &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
ctx.ui.navigateTo(std::move(filePath), std::move(navArgs));
}
@ -72,6 +77,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
io.Fonts->AddFontFromMemoryTTF(const_cast<uint8_t*>(font.data()), static_cast<int>(font.size()), 13, &fontCfg);
}
turbine::setApplicationData(m_tctx, &m_sctx);
turbine::setShutdownHandler(m_tctx, shutdownHandler);
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
@ -83,6 +89,7 @@ StudioUI::StudioUI(turbine::Context &ctx, ox::StringParam projectDataDir) noexce
m_renameFile.moveFile.connect(this, &StudioUI::queueFileMove);
m_newProject.finished.connect(this, &StudioUI::createOpenProject);
m_newMenu.finished.connect(this, &StudioUI::openFile);
m_closeAppConfirm.response.connect(this, &StudioUI::handleCloseAppResponse);
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
loadModules();
// open project and files
@ -158,6 +165,7 @@ void StudioUI::draw() noexcept {
for (auto const p : m_popups) {
p->draw(m_sctx);
}
m_closeAppConfirm.draw(m_sctx);
m_closeFileConfirm.draw(m_sctx);
m_copyFilePopup.draw(m_sctx);
}
@ -167,6 +175,16 @@ void StudioUI::draw() noexcept {
procFileMoves();
}
bool StudioUI::handleShutdown() noexcept {
auto const out = ox::all_of(m_editors.begin(), m_editors.end(), [](ox::UPtr<BaseEditor> const &e) {
return !e->unsavedChanges();
});
if (!out) {
m_closeAppConfirm.open();
}
return out;
}
void StudioUI::drawMenu() noexcept {
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("File")) {
@ -267,6 +285,7 @@ void StudioUI::drawTabs() noexcept {
}
if (!open) {
if (e->unsavedChanges()) {
m_closeAppConfirm.open();
m_closeFileConfirm.open();
++it;
} else {
@ -384,6 +403,7 @@ void StudioUI::handleKeyInput() noexcept {
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
if (m_activeEditor) {
if (m_activeEditor->unsavedChanges()) {
m_closeAppConfirm.open();
m_closeFileConfirm.open();
} else {
oxLogError(closeCurrentFile());
@ -548,6 +568,13 @@ ox::Error StudioUI::makeCopyDlg(ox::StringViewCR path) noexcept {
return m_copyFilePopup.open(path);
}
ox::Error StudioUI::handleCloseAppResponse(ig::PopupResponse const response) noexcept {
if (response == ig::PopupResponse::OK && m_activeEditor) {
turbine::requestShutdown(m_tctx);
}
return {};
}
ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) noexcept {
if (response == ig::PopupResponse::OK && m_activeEditor) {
return closeCurrentFile();

View File

@ -49,6 +49,10 @@ class StudioUI: public ox::SignalHandler {
DeleteConfirmation m_deleteConfirmation;
NewDir m_newDirDialog;
ig::QuestionPopup m_closeFileConfirm{"Close File?", "This file has unsaved changes. Close?"};
ig::QuestionPopup m_closeAppConfirm{
"Close Application?",
"There are files with unsaved changes. Close?"
};
MakeCopyPopup m_copyFilePopup;
RenameFile m_renameFile;
NewProject m_newProject;
@ -80,6 +84,8 @@ class StudioUI: public ox::SignalHandler {
return m_project.get();
}
bool handleShutdown() noexcept;
protected:
void draw() noexcept;
@ -128,6 +134,8 @@ class StudioUI: public ox::SignalHandler {
ox::Error makeCopyDlg(ox::StringViewCR path) noexcept;
ox::Error handleCloseAppResponse(ig::PopupResponse response) noexcept;
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
ox::Error closeCurrentFile() noexcept;

View File

@ -19,8 +19,6 @@ class Context;
void safeDelete(Context *p);
void shutdown(Context &ctx) noexcept;
keel::Context const&keelCtx(Context const&ctx) noexcept;
keel::Context &keelCtx(Context &ctx) noexcept;

View File

@ -29,4 +29,8 @@ TimeMs ticksMs(Context const&ctx) noexcept;
void requestShutdown(Context &ctx) noexcept;
using ShutdownHandler = bool (*)(Context&);
void setShutdownHandler(Context &ctx, ShutdownHandler handler) noexcept;
}

View File

@ -12,7 +12,7 @@
namespace turbine {
class Context {
class Context final {
public:
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
keel::Context keelCtx;
@ -27,10 +27,6 @@ class Context {
Context(Context const&other) noexcept = delete;
Context(Context const&&other) noexcept = delete;
virtual inline ~Context() noexcept {
shutdown(*this);
}
};
}

View File

@ -86,4 +86,7 @@ void requestShutdown(Context &ctx) noexcept {
ctx.running = false;
}
void setShutdownHandler(Context&, ShutdownHandler) noexcept {
}
}

View File

@ -30,6 +30,8 @@ class Context {
uint64_t keysDown = 0;
uint64_t prevFpsCheckTime = 0;
uint64_t draws = 0;
bool running{};
ShutdownHandler shutdownHandler{};
Context() noexcept = default;

View File

@ -68,9 +68,21 @@ static void tickFps(Context &ctx, uint64_t const nowMs) noexcept {
}
}
static void shutdown(Context &ctx) noexcept {
if (ctx.window) {
#if TURBINE_USE_IMGUI
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
#endif
glfwDestroyWindow(ctx.window);
ctx.window = nullptr;
}
}
ox::Error run(Context &ctx) noexcept {
uint64_t sleepTime = 0;
while (!glfwWindowShouldClose(ctx.window)) {
ctx.running = true;
while (ctx.running) {
ctx.refreshWithinMs = 10 * 1000; // refresh within 10 seconds
glfwPollEvents();
auto const ticks = ticksMs(ctx);
@ -92,22 +104,17 @@ ox::Error run(Context &ctx) noexcept {
if (realSleepTime && ctx.mandatoryRefreshPeriodEnd <= ticks) {
glfwWaitEventsTimeout(static_cast<double>(realSleepTime) / 1000);
}
if (glfwWindowShouldClose(ctx.window)) {
if (ctx.shutdownHandler(ctx)) {
break;
}
glfwSetWindowShouldClose(ctx.window, false);
}
}
shutdown(ctx);
return {};
}
void shutdown(Context &ctx) noexcept {
if (ctx.window) {
#if TURBINE_USE_IMGUI
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
#endif
glfwDestroyWindow(ctx.window);
ctx.window = nullptr;
}
}
TimeMs ticksMs(Context const&ctx) noexcept {
using namespace std::chrono;
auto const now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
@ -119,7 +126,11 @@ bool buttonDown(Context const&ctx, Key const key) noexcept {
}
void requestShutdown(Context &ctx) noexcept {
glfwSetWindowShouldClose(ctx.window, true);
ctx.running = false;
}
void setShutdownHandler(Context &ctx, ShutdownHandler const handler) noexcept {
ctx.shutdownHandler = handler;
}
}