Redo of the project state, implementation of Undo/Redo stack compression

This commit is contained in:
Vojtech Bubnik 2021-09-29 16:43:07 +02:00 committed by Lukas Matena
parent 96f4d71c71
commit 1f3b272d77
9 changed files with 222 additions and 376 deletions

View file

@ -6,7 +6,6 @@
#include "MainFrame.hpp"
#include "I18N.hpp"
#include "Plater.hpp"
#include "../Utils/UndoRedo.hpp"
#include <boost/algorithm/string/predicate.hpp>
@ -16,226 +15,38 @@
namespace Slic3r {
namespace GUI {
enum class EStackType
void ProjectDirtyStateManager::update_from_undo_redo_stack(bool dirty)
{
Main,
Gizmo
};
// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack
static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const size_t active_snapshot_time = stack.active_snapshot_time();
const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time));
const int idx = it - snapshots.begin() - 1;
const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && idx < int(snapshots.size()) - 1) ?
&snapshots[idx] : nullptr;
assert(ret != nullptr);
return ret;
}
// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack
static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack,
const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) {
// returns true if the given snapshot is not saveable
auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) {
auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) {
if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
if (gizmos.current)
return true;
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1)));
if (gizmos.is_used_and_modified(*leaving_snapshot))
return true;
}
}
return false;
};
if (snapshot.name == _utf8("New Project"))
return true;
else if (snapshot.name == _utf8("Reset Project"))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Load Project")))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Selection")))
return true;
else if (boost::starts_with(snapshot.name, _utf8("Entering"))) {
if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot))
return true;
}
else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) {
if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot))
return true;
}
return false;
};
// returns true if the given snapshot is not saveable
auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) {
// put here any needed condition to skip the snapshot
return false;
};
const UndoRedo::Snapshot* curr = get_active_snapshot(stack);
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
size_t shift = 1;
while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) {
const UndoRedo::Snapshot* temp = curr;
curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift)));
shift = (curr == temp) ? shift + 1 : 1;
}
if (type == EStackType::Main) {
if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) {
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving")))
curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1)));
}
}
return curr->timestamp > 0 ? curr : nullptr;
}
// returns the name of the gizmo contained in the given string
static std::string extract_gizmo_name(const std::string& s) {
static const std::array<std::string, 2> prefixes = { _utf8("Entering"), _utf8("Leaving") };
std::string ret;
for (const std::string& prefix : prefixes) {
if (boost::starts_with(s, prefix))
ret = s.substr(prefix.length() + 1);
if (!ret.empty())
break;
}
return ret;
}
void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot)
{
const std::string name = extract_gizmo_name(snapshot.name);
auto it = used.find(name);
if (it == used.end())
it = used.insert({ name, { {} } }).first;
it->second.modified_timestamps.push_back(snapshot.timestamp);
}
void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack)
{
const std::vector<UndoRedo::Snapshot>& snapshots = main_stack.snapshots();
for (auto& item : used) {
auto it = item.second.modified_timestamps.begin();
while (it != item.second.modified_timestamps.end()) {
size_t timestamp = *it;
auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; });
if (snapshot_it == snapshots.end())
it = item.second.modified_timestamps.erase(it);
else
++it;
}
}
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const
{
for (auto& [name, gizmo] : used) {
if (!gizmo.modified_timestamps.empty())
return true;
}
return false;
}
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
// returns true if the given snapshot is contained in any of the gizmos caches
bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const
{
for (const auto& item : used) {
for (size_t i : item.second.modified_timestamps) {
if (i == snapshot.timestamp)
return true;
}
}
return false;
}
void ProjectDirtyStateManager::DirtyState::Gizmos::reset()
{
used.clear();
}
void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type)
{
if (!wxGetApp().initialized())
return;
const Plater* plater = wxGetApp().plater();
if (plater == nullptr)
return;
const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
if (&main_stack == &active_stack)
update_from_undo_redo_main_stack(type, main_stack);
else
update_from_undo_redo_gizmo_stack(type, active_stack);
wxGetApp().mainframe->update_title();
m_plater_dirty = dirty;
if (const Plater *plater = wxGetApp().plater(); plater && wxGetApp().initialized())
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::update_from_presets()
{
m_state.presets = false;
m_presets_dirty = false;
// check switching of the presets only for exist/loaded project, but not for new
if (!wxGetApp().plater()->get_project_filename().IsEmpty()) {
for (const auto& [type, name] : wxGetApp().get_selected_presets())
m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
m_presets_dirty |= !m_initial_presets[type].empty() && m_initial_presets[type] != name;
}
m_state.presets |= wxGetApp().has_unsaved_preset_changes();
m_presets_dirty |= wxGetApp().has_unsaved_preset_changes();
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::reset_after_save()
{
const Plater* plater = wxGetApp().plater();
const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main();
const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active();
if (&main_stack == &active_stack) {
const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main);
m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
}
else {
// Gizmo is active with its own Undo / Redo stack (for example the SLA support point editing gizmo).
const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack);
if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) {
if (m_state.gizmos.current)
m_last_save.main = main_active_snapshot->timestamp + 1;
}
const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main);
m_last_save.gizmo = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0;
}
reset_initial_presets();
m_state.reset();
this->reset_initial_presets();
m_plater_dirty = false;
m_presets_dirty = false;
wxGetApp().mainframe->update_title();
}
void ProjectDirtyStateManager::reset_initial_presets()
{
m_initial_presets = std::array<std::string, Preset::TYPE_COUNT>();
for (const auto& [type, name] : wxGetApp().get_selected_presets()) {
m_initial_presets.fill(std::string{});
for (const auto& [type, name] : wxGetApp().get_selected_presets())
m_initial_presets[type] = name;
}
}
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
@ -324,89 +135,10 @@ void ProjectDirtyStateManager::render_debug_window() const
}
}
if (m_state.gizmos.any_used_modified()) {
if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Indent(10.0f);
for (const auto& [name, gizmo] : m_state.gizmos.used) {
if (!gizmo.modified_timestamps.empty()) {
if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
std::string modified_timestamps;
for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) {
if (i > 0)
modified_timestamps += " | ";
modified_timestamps += std::to_string(gizmo.modified_timestamps[i]);
}
imgui.text(modified_timestamps);
}
}
}
ImGui::Unindent(10.0f);
}
}
imgui.end();
}
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
{
m_state.plater = false;
if (type == UpdateType::TakeSnapshot) {
if (m_last_save.main != 0) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; });
if (snapshot_it == snapshots.end())
m_last_save.main = 0;
}
m_state.gizmos.remove_obsolete_used(stack);
}
const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
if (active_snapshot->name == _utf8("New Project") ||
active_snapshot->name == _utf8("Reset Project") ||
boost::starts_with(active_snapshot->name, _utf8("Load Project")))
return;
if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) {
if (type == UpdateType::UndoRedoTo) {
std::string topmost_redo;
wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo);
if (boost::starts_with(topmost_redo, _utf8("Leaving"))) {
const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots();
const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1)));
if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) {
m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main);
return;
}
}
}
m_state.gizmos.current = false;
m_last_save.gizmo = 0;
}
else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) {
if (m_state.gizmos.current)
m_state.gizmos.add_used(*active_snapshot);
m_state.gizmos.current = false;
m_last_save.gizmo = 0;
}
const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main);
m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main);
}
void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack)
{
m_state.gizmos.current = false;
const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack);
if (active_snapshot->name == "Gizmos-Initial")
return;
const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main);
m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo);
}
} // namespace GUI
} // namespace Slic3r