Implement a dialog for unsaved changes (#1241)
* Track unsaved scene changes * Implement a dialog for unsaved changes * Exclude all selection actions from scene change tracking
This commit is contained in:
committed by
GitHub
parent
7318056235
commit
a00fb8efc2
@@ -177,7 +177,7 @@ void ArmatureWindow::Create(EditorComponent* _editor)
|
||||
if (args.iValue < 0)
|
||||
return;
|
||||
|
||||
wi::Archive& archive = editor->AdvanceHistory();
|
||||
wi::Archive& archive = editor->AdvanceHistory(true);
|
||||
archive << EditorComponent::HISTORYOP_SELECTION;
|
||||
// record PREVIOUS selection state...
|
||||
editor->RecordSelection(archive);
|
||||
|
||||
@@ -91,7 +91,7 @@ void ComponentsWindow::Create(EditorComponent* _editor)
|
||||
if (args.iValue < 0)
|
||||
return;
|
||||
|
||||
wi::Archive& archive = editor->AdvanceHistory();
|
||||
wi::Archive& archive = editor->AdvanceHistory(true);
|
||||
archive << EditorComponent::HISTORYOP_SELECTION;
|
||||
// record PREVIOUS selection state...
|
||||
editor->RecordSelection(archive);
|
||||
|
||||
+85
-6
@@ -1294,7 +1294,15 @@ void EditorComponent::Load()
|
||||
exitButton.SetTooltip("Exit");
|
||||
exitButton.SetColor(wi::Color(160, 50, 50, 180), wi::gui::WIDGETSTATE::IDLE);
|
||||
exitButton.SetColor(wi::Color(200, 50, 50, 255), wi::gui::WIDGETSTATE::FOCUS);
|
||||
exitButton.OnClick([](wi::gui::EventArgs args) {
|
||||
exitButton.OnClick([this](wi::gui::EventArgs args) {
|
||||
// Check all scenes for unsaved changes
|
||||
for (int i = 0; i < (int)scenes.size(); ++i)
|
||||
{
|
||||
if (!CheckUnsavedChanges(i))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
wi::platform::Exit();
|
||||
});
|
||||
topmenuWnd.AddWidget(&exitButton);
|
||||
@@ -2350,7 +2358,7 @@ void EditorComponent::Update(float dt)
|
||||
// Select...
|
||||
if (leftmouse_select || selectAll || clear_selected)
|
||||
{
|
||||
wi::Archive& archive = AdvanceHistory();
|
||||
wi::Archive& archive = AdvanceHistory(true);
|
||||
archive << HISTORYOP_SELECTION;
|
||||
// record PREVIOUS selection state...
|
||||
RecordSelection(archive);
|
||||
@@ -4735,8 +4743,9 @@ void EditorComponent::ResetHistory()
|
||||
EditorScene& editorscene = GetCurrentEditorScene();
|
||||
editorscene.historyPos = -1;
|
||||
editorscene.history.clear();
|
||||
editorscene.has_unsaved_changes = false;
|
||||
}
|
||||
wi::Archive& EditorComponent::AdvanceHistory()
|
||||
wi::Archive& EditorComponent::AdvanceHistory(const bool scene_unchanged)
|
||||
{
|
||||
EditorScene& editorscene = GetCurrentEditorScene();
|
||||
editorscene.historyPos++;
|
||||
@@ -4749,6 +4758,12 @@ wi::Archive& EditorComponent::AdvanceHistory()
|
||||
editorscene.history.emplace_back();
|
||||
editorscene.history.back().SetReadModeAndResetPos(false);
|
||||
|
||||
if (!scene_unchanged)
|
||||
{
|
||||
editorscene.has_unsaved_changes = true;
|
||||
RefreshSceneList();
|
||||
}
|
||||
|
||||
return editorscene.history.back();
|
||||
}
|
||||
void EditorComponent::ConsumeHistoryOperation(bool undo)
|
||||
@@ -5218,6 +5233,10 @@ void EditorComponent::Open(std::string filename)
|
||||
|
||||
componentsWnd.weatherWnd.UpdateData();
|
||||
componentsWnd.RefreshEntityTree();
|
||||
|
||||
GetCurrentEditorScene().has_unsaved_changes = false;
|
||||
RefreshSceneList();
|
||||
|
||||
wi::backlog::post("[Editor] finished loading model: " + filename);
|
||||
});
|
||||
});
|
||||
@@ -5282,6 +5301,7 @@ void EditorComponent::Save(const std::string& filename)
|
||||
}
|
||||
|
||||
GetCurrentEditorScene().path = filename;
|
||||
GetCurrentEditorScene().has_unsaved_changes = false;
|
||||
RefreshSceneList();
|
||||
|
||||
RegisterRecentlyUsed(filename);
|
||||
@@ -5994,28 +6014,42 @@ void EditorComponent::RefreshSceneList()
|
||||
for (int i = 0; i < int(scenes.size()); ++i)
|
||||
{
|
||||
auto& editorscene = scenes[i];
|
||||
std::string tabText;
|
||||
if (editorscene->path.empty())
|
||||
{
|
||||
if (current_localization.Get((size_t)EditorLocalization::UntitledScene))
|
||||
{
|
||||
editorscene->tabSelectButton.SetText(current_localization.Get((size_t)EditorLocalization::UntitledScene));
|
||||
tabText = current_localization.Get((size_t)EditorLocalization::UntitledScene);
|
||||
}
|
||||
else
|
||||
{
|
||||
editorscene->tabSelectButton.SetText("Untitled scene");
|
||||
tabText = "Untitled scene";
|
||||
}
|
||||
editorscene->tabSelectButton.SetTooltip("");
|
||||
}
|
||||
else
|
||||
{
|
||||
editorscene->tabSelectButton.SetText(wi::helper::GetFileNameFromPath(editorscene->path));
|
||||
tabText = wi::helper::GetFileNameFromPath(editorscene->path);
|
||||
editorscene->tabSelectButton.SetTooltip(editorscene->path);
|
||||
}
|
||||
|
||||
if (editorscene->has_unsaved_changes)
|
||||
{
|
||||
tabText = tabText + " *";
|
||||
}
|
||||
|
||||
editorscene->tabSelectButton.SetText(tabText);
|
||||
|
||||
editorscene->tabSelectButton.OnClick([this, i](wi::gui::EventArgs args) {
|
||||
SetCurrentScene(i);
|
||||
});
|
||||
editorscene->tabCloseButton.OnClick([this, i](wi::gui::EventArgs args) {
|
||||
// Check for unsaved changes before closing
|
||||
if (!CheckUnsavedChanges(i))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
wi::lua::KillProcesses();
|
||||
|
||||
translator.selected.clear();
|
||||
@@ -6090,6 +6124,51 @@ void EditorComponent::NewScene()
|
||||
cameraWnd.ResetCam();
|
||||
}
|
||||
|
||||
bool EditorComponent::CheckUnsavedChanges(int scene_index)
|
||||
{
|
||||
if (scene_index < 0)
|
||||
scene_index = current_scene;
|
||||
|
||||
if (scene_index < 0 || scene_index >= (int)scenes.size())
|
||||
return true;
|
||||
|
||||
EditorScene& editorscene = *scenes[scene_index];
|
||||
if (!editorscene.has_unsaved_changes)
|
||||
return true;
|
||||
|
||||
std::string sceneName = editorscene.path.empty() ? "" : wi::helper::GetFileNameFromPath(editorscene.path);
|
||||
std::string message = sceneName.empty() ? "Do you want to save the untitled scene?" : "Do you want to save changes to \"" + sceneName + "\"?";
|
||||
wi::helper::MessageBoxResult result = wi::helper::messageBoxCustom(message, "Unsaved changes", "YesNoCancel");
|
||||
|
||||
if (result == wi::helper::MessageBoxResult::Yes)
|
||||
{
|
||||
// User wants to save
|
||||
if (editorscene.path.empty())
|
||||
{
|
||||
// Need to prompt for save location
|
||||
SaveAs();
|
||||
// After SaveAs, check if the scene was actually saved
|
||||
return !editorscene.has_unsaved_changes;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save to existing path
|
||||
Save(editorscene.path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (result == wi::helper::MessageBoxResult::No)
|
||||
{
|
||||
// User doesn't want to save, proceed
|
||||
return true;
|
||||
}
|
||||
else // Cancel or closed dialog
|
||||
{
|
||||
// User cancelled, don't proceed
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void EditorComponent::FocusCameraOnSelected()
|
||||
{
|
||||
Scene& scene = GetCurrentScene();
|
||||
|
||||
+4
-1
@@ -174,7 +174,7 @@ public:
|
||||
void RecordEntity(wi::Archive& archive, const wi::vector<wi::ecs::Entity>& entities);
|
||||
|
||||
void ResetHistory();
|
||||
wi::Archive& AdvanceHistory();
|
||||
wi::Archive& AdvanceHistory(bool scene_unchanged = false);
|
||||
void ConsumeHistoryOperation(bool undo);
|
||||
|
||||
wi::vector<std::string> recentFilenames;
|
||||
@@ -209,6 +209,7 @@ public:
|
||||
wi::scene::TransformComponent camera_target;
|
||||
wi::vector<wi::Archive> history;
|
||||
int historyPos = -1;
|
||||
bool has_unsaved_changes = false;
|
||||
wi::gui::Button tabSelectButton;
|
||||
wi::gui::Button tabCloseButton;
|
||||
};
|
||||
@@ -222,6 +223,8 @@ public:
|
||||
void RefreshSceneList();
|
||||
void NewScene();
|
||||
|
||||
bool CheckUnsavedChanges(int scene_index = -1);
|
||||
|
||||
void FocusCameraOnSelected();
|
||||
|
||||
wi::Localization default_localization;
|
||||
|
||||
@@ -169,7 +169,7 @@ void HumanoidWindow::Create(EditorComponent* _editor)
|
||||
if (args.iValue < 0)
|
||||
return;
|
||||
|
||||
wi::Archive& archive = editor->AdvanceHistory();
|
||||
wi::Archive& archive = editor->AdvanceHistory(true);
|
||||
archive << EditorComponent::HISTORYOP_SELECTION;
|
||||
// record PREVIOUS selection state...
|
||||
editor->RecordSelection(archive);
|
||||
|
||||
@@ -40,7 +40,7 @@ void MaterialPickerWindow::RecreateButtons()
|
||||
|
||||
button.OnClick([entity, this](wi::gui::EventArgs args) {
|
||||
|
||||
wi::Archive& archive = editor->AdvanceHistory();
|
||||
wi::Archive& archive = editor->AdvanceHistory(true);
|
||||
archive << EditorComponent::HISTORYOP_SELECTION;
|
||||
// record PREVIOUS selection state...
|
||||
editor->RecordSelection(archive);
|
||||
|
||||
@@ -81,6 +81,83 @@ namespace wi::helper
|
||||
#endif // SDL2
|
||||
}
|
||||
|
||||
MessageBoxResult messageBoxCustom(const std::string& msg, const std::string& caption, const std::string& buttons)
|
||||
{
|
||||
#ifdef PLATFORM_WINDOWS_DESKTOP
|
||||
std::wstring wmsg;
|
||||
std::wstring wcaption;
|
||||
StringConvert(msg, wmsg);
|
||||
StringConvert(caption, wcaption);
|
||||
|
||||
UINT type = MB_ICONQUESTION;
|
||||
if (buttons == "YesNoCancel")
|
||||
{
|
||||
type |= MB_YESNOCANCEL;
|
||||
}
|
||||
else if (buttons == "YesNo")
|
||||
{
|
||||
type |= MB_YESNO;
|
||||
}
|
||||
else if (buttons == "OKCancel")
|
||||
{
|
||||
type |= MB_OKCANCEL;
|
||||
}
|
||||
else if (buttons == "AbortRetryIgnore")
|
||||
{
|
||||
type |= MB_ABORTRETRYIGNORE;
|
||||
}
|
||||
else // default to OK
|
||||
{
|
||||
type |= MB_OK;
|
||||
}
|
||||
|
||||
const int result = MessageBox(GetActiveWindow(), wmsg.c_str(), wcaption.c_str(), type);
|
||||
|
||||
switch (result)
|
||||
{
|
||||
case IDOK: return MessageBoxResult::OK;
|
||||
case IDCANCEL: return MessageBoxResult::Cancel;
|
||||
case IDYES: return MessageBoxResult::Yes;
|
||||
case IDNO: return MessageBoxResult::No;
|
||||
case IDABORT: return MessageBoxResult::Abort;
|
||||
case IDRETRY: return MessageBoxResult::Retry;
|
||||
case IDIGNORE: return MessageBoxResult::Ignore;
|
||||
default: return MessageBoxResult::Cancel;
|
||||
}
|
||||
#endif // PLATFORM_WINDOWS_DESKTOP
|
||||
|
||||
#ifdef SDL2
|
||||
const SDL_MessageBoxButtonData buttons_data[] = {
|
||||
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Yes" },
|
||||
{ 0, 1, "No" },
|
||||
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 2, "Cancel" },
|
||||
};
|
||||
const SDL_MessageBoxData messageboxdata = {
|
||||
SDL_MESSAGEBOX_INFORMATION,
|
||||
NULL,
|
||||
caption.c_str(),
|
||||
msg.c_str(),
|
||||
SDL_arraysize(buttons_data),
|
||||
buttons_data,
|
||||
NULL
|
||||
};
|
||||
int buttonid;
|
||||
if (SDL_ShowMessageBox(&messageboxdata, &buttonid) < 0)
|
||||
{
|
||||
return MessageBoxResult::Cancel;
|
||||
}
|
||||
switch (buttonid)
|
||||
{
|
||||
case 0: return MessageBoxResult::Yes;
|
||||
case 1: return MessageBoxResult::No;
|
||||
case 2: return MessageBoxResult::Cancel;
|
||||
default: return MessageBoxResult::Cancel;
|
||||
}
|
||||
#endif // SDL2
|
||||
|
||||
return MessageBoxResult::Cancel;
|
||||
}
|
||||
|
||||
std::string screenshot(const wi::graphics::SwapChain& swapchain, const std::string& name)
|
||||
{
|
||||
return screenshot(wi::graphics::GetDevice()->GetBackBuffer(&swapchain));
|
||||
@@ -1294,7 +1371,7 @@ namespace wi::helper
|
||||
ofn.lStructSize = sizeof(ofn);
|
||||
ofn.hwndOwner = nullptr;
|
||||
ofn.lpstrFile = szFile;
|
||||
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
|
||||
// Set lpstrFile[0] to '\0' so that GetOpenFileName does not
|
||||
// use the contents of szFile to initialize itself.
|
||||
ofn.lpstrFile[0] = '\0';
|
||||
ofn.nMaxFile = sizeof(szFile);
|
||||
@@ -1582,7 +1659,7 @@ namespace wi::helper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void StringConvert(const std::wstring& from, std::string& to)
|
||||
{
|
||||
to.clear();
|
||||
@@ -1639,7 +1716,7 @@ namespace wi::helper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int StringConvert(const char* from, wchar_t* to, int dest_size_in_characters)
|
||||
{
|
||||
if (!from || !to || dest_size_in_characters <= 0)
|
||||
@@ -1709,7 +1786,7 @@ namespace wi::helper
|
||||
to[written] = 0;
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
int StringConvert(const wchar_t* from, char* to, int dest_size_in_characters)
|
||||
{
|
||||
if (!from || !to || dest_size_in_characters <= 0)
|
||||
@@ -1817,7 +1894,7 @@ namespace wi::helper
|
||||
}
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
|
||||
void Sleep(float milliseconds)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds((int)milliseconds));
|
||||
|
||||
@@ -45,6 +45,21 @@ namespace wi::helper
|
||||
|
||||
void messageBox(const std::string& msg, const std::string& caption = "Warning!");
|
||||
|
||||
enum class MessageBoxResult
|
||||
{
|
||||
OK,
|
||||
Cancel,
|
||||
Yes,
|
||||
No,
|
||||
Abort,
|
||||
Retry,
|
||||
Ignore
|
||||
};
|
||||
|
||||
// Shows a message box with custom buttons and returns the user's choice
|
||||
// buttons can be combinations like: "OK", "OKCancel", "YesNo", "YesNoCancel", etc.
|
||||
MessageBoxResult messageBoxCustom(const std::string& msg, const std::string& caption = "Warning!", const std::string& buttons = "OK");
|
||||
|
||||
// Returns file path if successful, empty string otherwise
|
||||
std::string screenshot(const wi::graphics::SwapChain& swapchain, const std::string& name = "");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user