Add popup to save before closing
This commit is contained in:
parent
1955de9e3e
commit
c3cc09e6b4
9 changed files with 230 additions and 53 deletions
|
|
@ -82,3 +82,43 @@ std::string remove_carriage_returns_from_str(std::string s) {
|
|||
std::erase(s, '\r');
|
||||
return s;
|
||||
}
|
||||
|
||||
std::string ensure_string_unique(const std::vector<std::string>& stringList, std::string str) {
|
||||
for(;;) {
|
||||
bool isUnique = true;
|
||||
for(const std::string& strToCheckAgainst : stringList) {
|
||||
if(strToCheckAgainst == str) {
|
||||
size_t leftParenthesisIndex = str.find_last_of('(');
|
||||
size_t rightParenthesisIndex = str.find_last_of(')');
|
||||
bool failToIncrement = true;
|
||||
if(leftParenthesisIndex != std::string::npos && rightParenthesisIndex != std::string::npos && leftParenthesisIndex < rightParenthesisIndex) {
|
||||
std::string numStr = str.substr(leftParenthesisIndex + 1, rightParenthesisIndex - leftParenthesisIndex - 1);
|
||||
bool isAllDigits = true;
|
||||
for(char c : numStr) {
|
||||
if(!isdigit(c)) {
|
||||
isAllDigits = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isAllDigits) {
|
||||
try {
|
||||
int s = std::stoi(numStr);
|
||||
s++;
|
||||
str = str.substr(0, leftParenthesisIndex);
|
||||
str += "(" + std::to_string(s) + ")";
|
||||
failToIncrement = false;
|
||||
}
|
||||
catch(...) {}
|
||||
}
|
||||
}
|
||||
if(failToIncrement)
|
||||
str += " (2)";
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isUnique)
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@ std::string byte_vector_to_hex_str(const std::vector<uint8_t>& byteVec);
|
|||
std::string read_file_to_string(const std::filesystem::path& filePath);
|
||||
bool is_valid_http_url(const std::string& str);
|
||||
std::string remove_carriage_returns_from_str(std::string s);
|
||||
std::string ensure_string_unique(const std::vector<std::string>& stringList, std::string str);
|
||||
|
|
|
|||
|
|
@ -80,10 +80,8 @@ void MainProgram::update() {
|
|||
return false;
|
||||
});
|
||||
|
||||
if(setTabToClose) {
|
||||
worlds.erase(worlds.begin() + setTabToClose.value());
|
||||
setTabToClose.reset();
|
||||
}
|
||||
for(auto& s : setTabsToClose)
|
||||
std::erase(worlds, s.lock());
|
||||
|
||||
std::erase_if(worlds, [&](const std::shared_ptr<World>& w) {
|
||||
return w->setToDestroy;
|
||||
|
|
@ -129,6 +127,10 @@ void MainProgram::update() {
|
|||
return;
|
||||
}
|
||||
|
||||
bool MainProgram::app_close_requested() {
|
||||
return toolbar.app_close_requested();
|
||||
}
|
||||
|
||||
void MainProgram::update_display_names() {
|
||||
for(auto& w : worlds) {
|
||||
if(!w->netObjMan.is_connected())
|
||||
|
|
@ -157,8 +159,8 @@ void MainProgram::new_tab_open() {
|
|||
worldIndex = worlds.size() - 1;
|
||||
}
|
||||
|
||||
void MainProgram::set_tab_to_close(size_t tabToClose) {
|
||||
setTabToClose = tabToClose;
|
||||
void MainProgram::set_tab_to_close(const std::weak_ptr<World>& tabToClose) {
|
||||
setTabsToClose.emplace_back(tabToClose);
|
||||
}
|
||||
|
||||
void MainProgram::init_net_library() {
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class MainProgram {
|
|||
void early_destroy();
|
||||
|
||||
void new_tab(const World::OpenWorldInfo& tabInfo, bool createSameThread = false);
|
||||
void set_tab_to_close(size_t tabToClose);
|
||||
void set_tab_to_close(const std::weak_ptr<World>& tabToClose);
|
||||
bool network_being_used();
|
||||
bool net_server_hosted();
|
||||
void update_display_names();
|
||||
|
|
@ -100,9 +100,11 @@ class MainProgram {
|
|||
void set_vsync_value(int vsyncValue);
|
||||
void update_scale_and_density();
|
||||
float get_scale_and_density_factor_gui();
|
||||
bool app_close_requested();
|
||||
|
||||
std::filesystem::path homePath;
|
||||
std::filesystem::path configPath;
|
||||
std::filesystem::path documentsPath;
|
||||
|
||||
bool drawGui = true;
|
||||
bool takingScreenshot = false;
|
||||
|
|
@ -121,7 +123,7 @@ class MainProgram {
|
|||
std::atomic<bool> tabSetToOpen = false;
|
||||
World::OpenWorldInfo newTabToOpenInfo;
|
||||
|
||||
std::optional<size_t> setTabToClose;
|
||||
std::vector<std::weak_ptr<World>> setTabsToClose;
|
||||
|
||||
std::string gen_random_display_name();
|
||||
|
||||
|
|
|
|||
122
src/Toolbar.cpp
122
src/Toolbar.cpp
|
|
@ -375,6 +375,8 @@ void Toolbar::update() {
|
|||
top_toolbar();
|
||||
if(!main.world->clientStillConnecting)
|
||||
drawing_program_gui();
|
||||
if(!closePopupData.worldsToClose.empty())
|
||||
close_popup_gui();
|
||||
if(viewWebVersionWelcome)
|
||||
web_version_welcome();
|
||||
#ifndef __EMSCRIPTEN__
|
||||
|
|
@ -423,11 +425,110 @@ void Toolbar::update() {
|
|||
calculate_final_gui_scale();
|
||||
}
|
||||
|
||||
bool Toolbar::app_close_requested() {
|
||||
for(auto& w : main.worlds) {
|
||||
if(w->should_ask_before_closing())
|
||||
add_world_to_close_popup_data(w);
|
||||
}
|
||||
closePopupData.closeAppWhenDone = true;
|
||||
return closePopupData.worldsToClose.empty();
|
||||
}
|
||||
|
||||
void Toolbar::add_world_to_close_popup_data(const std::shared_ptr<World>& w) {
|
||||
auto it = std::find_if(closePopupData.worldsToClose.begin(), closePopupData.worldsToClose.end(), [&](auto& wStruct) {
|
||||
return wStruct.w.lock() == w;
|
||||
});
|
||||
if(it == closePopupData.worldsToClose.end())
|
||||
closePopupData.worldsToClose.emplace_back(w, true);
|
||||
}
|
||||
|
||||
void Toolbar::close_popup_gui() {
|
||||
std::erase_if(closePopupData.worldsToClose, [](auto& wPair) {
|
||||
return wPair.w.expired();
|
||||
});
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
||||
.padding = CLAY_PADDING_ALL(io->theme->padding1),
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_TOP},
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor1),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1),
|
||||
.floating = {.attachPoints = {.element = CLAY_ATTACH_POINT_CENTER_CENTER, .parent = CLAY_ATTACH_POINT_CENTER_CENTER}, .attachTo = CLAY_ATTACH_TO_PARENT}
|
||||
}) {
|
||||
gui.obstructing_window();
|
||||
gui.push_id("Close single file popup gui");
|
||||
gui.text_label("Files may contain unsaved changes");
|
||||
size_t i = 0;
|
||||
gui.push_id("File list");
|
||||
for(auto& [w, setToSave] : closePopupData.worldsToClose) {
|
||||
auto wLock = w.lock();
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_FIT(0) },
|
||||
.padding = CLAY_PADDING_ALL(io->theme->padding1),
|
||||
.childGap = io->theme->childGap1,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
||||
.layoutDirection = CLAY_LEFT_TO_RIGHT
|
||||
},
|
||||
.backgroundColor = convert_vec4<Clay_Color>(io->theme->backColor2),
|
||||
.cornerRadius = CLAY_CORNER_RADIUS(io->theme->windowCorners1),
|
||||
}) {
|
||||
gui.push_id(i);
|
||||
gui.checkbox("set to save checkbox", &setToSave);
|
||||
CLAY_AUTO_ID({
|
||||
.layout = {
|
||||
.sizing = {.width = CLAY_SIZING_FIT(0), .height = CLAY_SIZING_FIT(0) },
|
||||
.childGap = 0,
|
||||
.childAlignment = { .x = CLAY_ALIGN_X_LEFT, .y = CLAY_ALIGN_Y_CENTER},
|
||||
.layoutDirection = CLAY_TOP_TO_BOTTOM
|
||||
}
|
||||
}) {
|
||||
gui.text_label(wLock->name);
|
||||
gui.text_label_light(wLock->filePath.empty() ? "Autosave in " + main.documentsPath.string() : wLock->filePath.string());
|
||||
}
|
||||
gui.pop_id();
|
||||
}
|
||||
++i;
|
||||
}
|
||||
gui.pop_id();
|
||||
if(gui.text_button_wide("Save", "Save")) {
|
||||
for(auto& [w, setToSave] : closePopupData.worldsToClose) {
|
||||
auto wLock = w.lock();
|
||||
if(setToSave) {
|
||||
if(wLock->filePath.empty())
|
||||
wLock->autosave_to_directory(main.documentsPath);
|
||||
else
|
||||
wLock->save_to_file(wLock->filePath);
|
||||
}
|
||||
main.set_tab_to_close(w);
|
||||
}
|
||||
closePopupData.worldsToClose.clear();
|
||||
if(closePopupData.closeAppWhenDone)
|
||||
main.setToQuit = true;
|
||||
}
|
||||
if(gui.text_button_wide("Discard All", "Discard All")) {
|
||||
for(auto& [w, setToSave] : closePopupData.worldsToClose)
|
||||
main.set_tab_to_close(w);
|
||||
closePopupData.worldsToClose.clear();
|
||||
if(closePopupData.closeAppWhenDone)
|
||||
main.setToQuit = true;
|
||||
}
|
||||
if(gui.text_button_wide("Cancel", "Cancel")) {
|
||||
closePopupData.worldsToClose.clear();
|
||||
closePopupData.closeAppWhenDone = false;
|
||||
}
|
||||
gui.pop_id();
|
||||
}
|
||||
}
|
||||
|
||||
void Toolbar::save_func() {
|
||||
if(main.world->filePath == std::filesystem::path())
|
||||
save_as_func();
|
||||
else
|
||||
main.world->save_to_file(main.world->filePath.string());
|
||||
main.world->save_to_file(main.world->filePath);
|
||||
}
|
||||
|
||||
void Toolbar::save_as_func() {
|
||||
|
|
@ -484,10 +585,22 @@ void Toolbar::top_toolbar() {
|
|||
}
|
||||
}
|
||||
std::vector<std::pair<std::string, std::string>> tabNames;
|
||||
for(size_t i = 0; i < main.worlds.size(); i++)
|
||||
tabNames.emplace_back(main.worlds[i]->netObjMan.is_connected() ? "data/icons/network.svg" : "", main.worlds[i]->name + (main.worlds[i]->hasUnsavedLocalChanges ? "*" : ""));
|
||||
for(size_t i = 0; i < main.worlds.size(); i++) {
|
||||
auto& w = main.worlds[i];
|
||||
bool shouldAddStarNextToName = w->should_ask_before_closing() && !w->netServer && !w->netClient;
|
||||
tabNames.emplace_back(w->netObjMan.is_connected() ? "data/icons/network.svg" : "", w->name + (shouldAddStarNextToName ? "*" : ""));
|
||||
}
|
||||
|
||||
std::optional<size_t> closedTab;
|
||||
gui.tab_list("file tab list", tabNames, main.worldIndex, closedTab);
|
||||
if(closedTab) {
|
||||
auto& closedWorldPtr = main.worlds[closedTab.value()];
|
||||
if(closedWorldPtr->should_ask_before_closing())
|
||||
add_world_to_close_popup_data(closedWorldPtr);
|
||||
else
|
||||
main.set_tab_to_close(closedWorldPtr);
|
||||
}
|
||||
|
||||
if(!main.world->clientStillConnecting) {
|
||||
if(main.world->netObjMan.is_connected() && gui.svg_icon_button_transparent("Player List Toggle Button", "data/icons/list.svg", playerMenuOpen))
|
||||
playerMenuOpen = !playerMenuOpen;
|
||||
|
|
@ -524,9 +637,6 @@ void Toolbar::top_toolbar() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
if(closedTab)
|
||||
main.set_tab_to_close(closedTab.value());
|
||||
if(gridMenu.popupOpen)
|
||||
grid_menu(gridMenuPopUpJustOpen);
|
||||
if(bookmarkMenuPopupOpen)
|
||||
|
|
|
|||
|
|
@ -90,6 +90,8 @@ class Toolbar {
|
|||
bool flipZoomToolDirection = false;
|
||||
|
||||
float final_gui_scale();
|
||||
|
||||
bool app_close_requested();
|
||||
private:
|
||||
void calculate_final_gui_scale();
|
||||
float final_gui_scale_not_fit();
|
||||
|
|
@ -117,6 +119,17 @@ class Toolbar {
|
|||
void web_version_welcome();
|
||||
void still_connecting_center_message();
|
||||
void no_layers_being_edited_message();
|
||||
void close_popup_gui();
|
||||
void add_world_to_close_popup_data(const std::shared_ptr<World>& w);
|
||||
|
||||
struct ClosePopupData {
|
||||
struct CloseWorldData {
|
||||
std::weak_ptr<World> w;
|
||||
bool setToSave = true;
|
||||
};
|
||||
std::vector<CloseWorldData> worldsToClose; // Using a vector to ensure that the worlds are in proper order
|
||||
bool closeAppWhenDone = false;
|
||||
} closePopupData;
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
void update_notification_gui();
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "Helpers/NetworkingObjects/NetObjTemporaryPtr.decl.hpp"
|
||||
#include "Helpers/NetworkingObjects/NetObjUnorderedSet.hpp"
|
||||
#include "CommandList.hpp"
|
||||
#include "Helpers/StringHelpers.hpp"
|
||||
#include "MainProgram.hpp"
|
||||
#include "SharedTypes.hpp"
|
||||
#include "Toolbar.hpp"
|
||||
|
|
@ -275,42 +276,10 @@ void World::set_name(const std::string& n) {
|
|||
}
|
||||
|
||||
void World::ensure_display_name_unique(std::string& displayName) {
|
||||
for(;;) {
|
||||
bool isUnique = true;
|
||||
for(auto& client : clients->get_data()) {
|
||||
if(client->get_display_name() == displayName) {
|
||||
size_t leftParenthesisIndex = displayName.find_last_of('(');
|
||||
size_t rightParenthesisIndex = displayName.find_last_of(')');
|
||||
bool failToIncrement = true;
|
||||
if(leftParenthesisIndex != std::string::npos && rightParenthesisIndex != std::string::npos && leftParenthesisIndex < rightParenthesisIndex) {
|
||||
std::string numStr = displayName.substr(leftParenthesisIndex + 1, rightParenthesisIndex - leftParenthesisIndex - 1);
|
||||
bool isAllDigits = true;
|
||||
for(char c : numStr) {
|
||||
if(!isdigit(c)) {
|
||||
isAllDigits = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isAllDigits) {
|
||||
try {
|
||||
int s = std::stoi(numStr);
|
||||
s++;
|
||||
displayName = displayName.substr(0, leftParenthesisIndex);
|
||||
displayName += "(" + std::to_string(s) + ")";
|
||||
failToIncrement = false;
|
||||
}
|
||||
catch(...) {}
|
||||
}
|
||||
}
|
||||
if(failToIncrement)
|
||||
displayName += " (2)";
|
||||
isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(isUnique)
|
||||
break;
|
||||
}
|
||||
std::vector<std::string> strList;
|
||||
for(auto& client : clients->get_data())
|
||||
strList.emplace_back(client->get_display_name());
|
||||
displayName = ensure_string_unique(strList, displayName);
|
||||
}
|
||||
|
||||
void World::start_hosting(const std::string& initNetSource, const std::string& serverLocalID) {
|
||||
|
|
@ -368,6 +337,16 @@ void World::start_hosting(const std::string& initNetSource, const std::string& s
|
|||
});
|
||||
}
|
||||
|
||||
void World::autosave_to_directory(const std::filesystem::path& directoryToSaveAt) {
|
||||
std::vector<std::string> strList;
|
||||
for(auto& entry : std::filesystem::directory_iterator(directoryToSaveAt)) {
|
||||
if(entry.path().has_stem() && entry.path().has_extension() && entry.path().extension() == ("." + FILE_EXTENSION))
|
||||
strList.emplace_back(entry.path().stem());
|
||||
}
|
||||
std::string nameToSaveUnder = ensure_string_unique(strList, name);
|
||||
save_to_file(directoryToSaveAt / std::filesystem::path(nameToSaveUnder + "." + FILE_EXTENSION));
|
||||
}
|
||||
|
||||
void World::save_to_file(const std::filesystem::path& filePathToSaveAt) {
|
||||
try {
|
||||
filePath = force_extension_on_path(filePathToSaveAt, FILE_EXTENSION);
|
||||
|
|
@ -505,6 +484,20 @@ void World::scale_up(const WorldScalar& scaleUpAmount) {
|
|||
undo.scale_up(scaleUpAmount);
|
||||
}
|
||||
|
||||
bool World::should_ask_before_closing() {
|
||||
// If it's a client that didn't save to a file previously, we can ignore asking before quitting (probably don't intend to save anyway)
|
||||
// If it's a server or a client, and the previous statement is false, we should always ask before quitting because the data could be modified by someone on the network
|
||||
// If it's a local file, ask before quitting if we detect any local changes
|
||||
// If on web version, don't ask before closing
|
||||
#ifdef __EMCSRIPTEN__
|
||||
return false;
|
||||
#else
|
||||
if(netClient || netServer)
|
||||
return !(netClient && filePath.empty());
|
||||
return hasUnsavedLocalChanges;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef ENABLE_ORDERED_LIST_TEST
|
||||
void World::list_debug_test_update() {
|
||||
if(std::chrono::steady_clock::now() < (listDebugTestTimeStart + std::chrono::minutes(1))) {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class World {
|
|||
|
||||
bool clientStillConnecting = false;
|
||||
|
||||
void autosave_to_directory(const std::filesystem::path& directoryToSaveAt);
|
||||
void save_to_file(const std::filesystem::path& filePathToSaveAt);
|
||||
void load_from_file(const std::filesystem::path& filePathToLoadFrom, std::string_view buffer);
|
||||
|
||||
|
|
@ -89,6 +90,8 @@ class World {
|
|||
void scale_up(const WorldScalar& scaleUpAmount);
|
||||
void scale_up_step();
|
||||
|
||||
bool should_ask_before_closing();
|
||||
|
||||
bool hasUnsavedLocalChanges = false;
|
||||
bool setToDestroy = false;
|
||||
NetworkingObjects::DelayUpdateSerializedClassManager delayedUpdateObjectManager;
|
||||
|
|
|
|||
19
src/main.cpp
19
src/main.cpp
|
|
@ -437,6 +437,15 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
|
|||
mS.m->logFile = &mS.logFile;
|
||||
mS.m->configPath = mS.configPath;
|
||||
mS.m->homePath = mS.homePath;
|
||||
#ifndef __EMSCRIPTEN__
|
||||
mS.m->documentsPath = sago::getDocumentsFolder();
|
||||
if(!std::filesystem::exists(mS.m->documentsPath) || !std::filesystem::is_directory(mS.m->documentsPath)) {
|
||||
mS.m->documentsPath = sago::getDesktopFolder();
|
||||
if(!std::filesystem::exists(mS.m->documentsPath) || !std::filesystem::is_directory(mS.m->documentsPath)) {
|
||||
mS.m->documentsPath = mS.m->configPath;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
mS.m->window.sdlWindow = mS.window;
|
||||
mS.m->update_scale_and_density();
|
||||
#ifdef __EMSCRIPTEN__
|
||||
|
|
@ -626,11 +635,15 @@ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
|
|||
#endif
|
||||
switch(event->type) {
|
||||
case SDL_EVENT_QUIT:
|
||||
return SDL_APP_SUCCESS;
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
if(event->window.windowID == SDL_GetWindowID(mS.window))
|
||||
if(mS.m->app_close_requested())
|
||||
return SDL_APP_SUCCESS;
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
if(event->window.windowID == SDL_GetWindowID(mS.window)) {
|
||||
if(mS.m->app_close_requested())
|
||||
return SDL_APP_SUCCESS;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_WINDOW_MOVED:
|
||||
mS.m->window.pos.x() = event->window.data1;
|
||||
mS.m->window.pos.y() = event->window.data2;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue