[turbine,studio] Make Studio confirm with user before closing app if any unsaved changes
All checks were successful
Build / build (push) Successful in 1m15s
All checks were successful
Build / build (push) Successful in 1m15s
This commit is contained in:
parent
4770bb6a93
commit
e5dd448fe7
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace studio::files {
|
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, 0x01, 0x00, 0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x04,
|
||||||
0x00, 0x10, 0x47, 0x50, 0x4f, 0x53, 0x7d, 0xaa, 0x71, 0x8c,
|
0x00, 0x10, 0x47, 0x50, 0x4f, 0x53, 0x7d, 0xaa, 0x71, 0x8c,
|
||||||
0x00, 0x02, 0x08, 0xa8, 0x00, 0x00, 0x59, 0x0c, 0x47, 0x53,
|
0x00, 0x02, 0x08, 0xa8, 0x00, 0x00, 0x59, 0x0c, 0x47, 0x53,
|
||||||
|
@ -19,6 +19,11 @@
|
|||||||
|
|
||||||
namespace studio {
|
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 {
|
void navigateTo(StudioContext &ctx, ox::StringParam filePath, ox::StringParam navArgs) noexcept {
|
||||||
ctx.ui.navigateTo(std::move(filePath), std::move(navArgs));
|
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);
|
io.Fonts->AddFontFromMemoryTTF(const_cast<uint8_t*>(font.data()), static_cast<int>(font.size()), 13, &fontCfg);
|
||||||
}
|
}
|
||||||
turbine::setApplicationData(m_tctx, &m_sctx);
|
turbine::setApplicationData(m_tctx, &m_sctx);
|
||||||
|
turbine::setShutdownHandler(m_tctx, shutdownHandler);
|
||||||
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
|
m_projectExplorer.fileChosen.connect(this, &StudioUI::openFile);
|
||||||
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
|
m_projectExplorer.addDir.connect(this, &StudioUI::addDir);
|
||||||
m_projectExplorer.addItem.connect(this, &StudioUI::addFile);
|
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_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_closeAppConfirm.response.connect(this, &StudioUI::handleCloseAppResponse);
|
||||||
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
|
m_closeFileConfirm.response.connect(this, &StudioUI::handleCloseFileResponse);
|
||||||
loadModules();
|
loadModules();
|
||||||
// open project and files
|
// open project and files
|
||||||
@ -158,6 +165,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_closeAppConfirm.draw(m_sctx);
|
||||||
m_closeFileConfirm.draw(m_sctx);
|
m_closeFileConfirm.draw(m_sctx);
|
||||||
m_copyFilePopup.draw(m_sctx);
|
m_copyFilePopup.draw(m_sctx);
|
||||||
}
|
}
|
||||||
@ -167,6 +175,16 @@ void StudioUI::draw() noexcept {
|
|||||||
procFileMoves();
|
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 {
|
void StudioUI::drawMenu() noexcept {
|
||||||
if (ImGui::BeginMainMenuBar()) {
|
if (ImGui::BeginMainMenuBar()) {
|
||||||
if (ImGui::BeginMenu("File")) {
|
if (ImGui::BeginMenu("File")) {
|
||||||
@ -267,6 +285,7 @@ void StudioUI::drawTabs() noexcept {
|
|||||||
}
|
}
|
||||||
if (!open) {
|
if (!open) {
|
||||||
if (e->unsavedChanges()) {
|
if (e->unsavedChanges()) {
|
||||||
|
m_closeAppConfirm.open();
|
||||||
m_closeFileConfirm.open();
|
m_closeFileConfirm.open();
|
||||||
++it;
|
++it;
|
||||||
} else {
|
} else {
|
||||||
@ -384,6 +403,7 @@ void StudioUI::handleKeyInput() noexcept {
|
|||||||
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
|
} else if (ImGui::IsKeyPressed(ImGuiKey_W)) {
|
||||||
if (m_activeEditor) {
|
if (m_activeEditor) {
|
||||||
if (m_activeEditor->unsavedChanges()) {
|
if (m_activeEditor->unsavedChanges()) {
|
||||||
|
m_closeAppConfirm.open();
|
||||||
m_closeFileConfirm.open();
|
m_closeFileConfirm.open();
|
||||||
} else {
|
} else {
|
||||||
oxLogError(closeCurrentFile());
|
oxLogError(closeCurrentFile());
|
||||||
@ -548,6 +568,13 @@ ox::Error StudioUI::makeCopyDlg(ox::StringViewCR path) noexcept {
|
|||||||
return m_copyFilePopup.open(path);
|
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 {
|
ox::Error StudioUI::handleCloseFileResponse(ig::PopupResponse const response) noexcept {
|
||||||
if (response == ig::PopupResponse::OK && m_activeEditor) {
|
if (response == ig::PopupResponse::OK && m_activeEditor) {
|
||||||
return closeCurrentFile();
|
return closeCurrentFile();
|
||||||
|
@ -49,6 +49,10 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
DeleteConfirmation m_deleteConfirmation;
|
DeleteConfirmation m_deleteConfirmation;
|
||||||
NewDir m_newDirDialog;
|
NewDir m_newDirDialog;
|
||||||
ig::QuestionPopup m_closeFileConfirm{"Close File?", "This file has unsaved changes. Close?"};
|
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;
|
MakeCopyPopup m_copyFilePopup;
|
||||||
RenameFile m_renameFile;
|
RenameFile m_renameFile;
|
||||||
NewProject m_newProject;
|
NewProject m_newProject;
|
||||||
@ -80,6 +84,8 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
return m_project.get();
|
return m_project.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool handleShutdown() noexcept;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void draw() noexcept;
|
void draw() noexcept;
|
||||||
|
|
||||||
@ -128,6 +134,8 @@ class StudioUI: public ox::SignalHandler {
|
|||||||
|
|
||||||
ox::Error makeCopyDlg(ox::StringViewCR path) noexcept;
|
ox::Error makeCopyDlg(ox::StringViewCR path) noexcept;
|
||||||
|
|
||||||
|
ox::Error handleCloseAppResponse(ig::PopupResponse response) noexcept;
|
||||||
|
|
||||||
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
|
ox::Error handleCloseFileResponse(ig::PopupResponse response) noexcept;
|
||||||
|
|
||||||
ox::Error closeCurrentFile() noexcept;
|
ox::Error closeCurrentFile() noexcept;
|
||||||
|
@ -19,8 +19,6 @@ class Context;
|
|||||||
|
|
||||||
void safeDelete(Context *p);
|
void safeDelete(Context *p);
|
||||||
|
|
||||||
void shutdown(Context &ctx) noexcept;
|
|
||||||
|
|
||||||
keel::Context const&keelCtx(Context const&ctx) noexcept;
|
keel::Context const&keelCtx(Context const&ctx) noexcept;
|
||||||
|
|
||||||
keel::Context &keelCtx(Context &ctx) noexcept;
|
keel::Context &keelCtx(Context &ctx) noexcept;
|
||||||
|
@ -29,4 +29,8 @@ TimeMs ticksMs(Context const&ctx) noexcept;
|
|||||||
|
|
||||||
void requestShutdown(Context &ctx) noexcept;
|
void requestShutdown(Context &ctx) noexcept;
|
||||||
|
|
||||||
|
using ShutdownHandler = bool (*)(Context&);
|
||||||
|
|
||||||
|
void setShutdownHandler(Context &ctx, ShutdownHandler handler) noexcept;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
namespace turbine {
|
namespace turbine {
|
||||||
|
|
||||||
class Context {
|
class Context final {
|
||||||
public:
|
public:
|
||||||
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
|
UpdateHandler updateHandler = [](Context&) -> int {return 0;};
|
||||||
keel::Context keelCtx;
|
keel::Context keelCtx;
|
||||||
@ -27,10 +27,6 @@ class Context {
|
|||||||
Context(Context const&other) noexcept = delete;
|
Context(Context const&other) noexcept = delete;
|
||||||
Context(Context const&&other) noexcept = delete;
|
Context(Context const&&other) noexcept = delete;
|
||||||
|
|
||||||
virtual inline ~Context() noexcept {
|
|
||||||
shutdown(*this);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -86,4 +86,7 @@ void requestShutdown(Context &ctx) noexcept {
|
|||||||
ctx.running = false;
|
ctx.running = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setShutdownHandler(Context&, ShutdownHandler) noexcept {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ class Context {
|
|||||||
uint64_t keysDown = 0;
|
uint64_t keysDown = 0;
|
||||||
uint64_t prevFpsCheckTime = 0;
|
uint64_t prevFpsCheckTime = 0;
|
||||||
uint64_t draws = 0;
|
uint64_t draws = 0;
|
||||||
|
bool running{};
|
||||||
|
ShutdownHandler shutdownHandler{};
|
||||||
|
|
||||||
Context() noexcept = default;
|
Context() noexcept = default;
|
||||||
|
|
||||||
|
@ -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 {
|
ox::Error run(Context &ctx) noexcept {
|
||||||
uint64_t sleepTime = 0;
|
uint64_t sleepTime = 0;
|
||||||
while (!glfwWindowShouldClose(ctx.window)) {
|
ctx.running = true;
|
||||||
|
while (ctx.running) {
|
||||||
ctx.refreshWithinMs = 10 * 1000; // refresh within 10 seconds
|
ctx.refreshWithinMs = 10 * 1000; // refresh within 10 seconds
|
||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
auto const ticks = ticksMs(ctx);
|
auto const ticks = ticksMs(ctx);
|
||||||
@ -92,22 +104,17 @@ ox::Error run(Context &ctx) noexcept {
|
|||||||
if (realSleepTime && ctx.mandatoryRefreshPeriodEnd <= ticks) {
|
if (realSleepTime && ctx.mandatoryRefreshPeriodEnd <= ticks) {
|
||||||
glfwWaitEventsTimeout(static_cast<double>(realSleepTime) / 1000);
|
glfwWaitEventsTimeout(static_cast<double>(realSleepTime) / 1000);
|
||||||
}
|
}
|
||||||
|
if (glfwWindowShouldClose(ctx.window)) {
|
||||||
|
if (ctx.shutdownHandler(ctx)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
glfwSetWindowShouldClose(ctx.window, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
shutdown(ctx);
|
shutdown(ctx);
|
||||||
return {};
|
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 {
|
TimeMs ticksMs(Context const&ctx) noexcept {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
auto const now = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
|
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 {
|
void requestShutdown(Context &ctx) noexcept {
|
||||||
glfwSetWindowShouldClose(ctx.window, true);
|
ctx.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setShutdownHandler(Context &ctx, ShutdownHandler const handler) noexcept {
|
||||||
|
ctx.shutdownHandler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user