From 8c3d098ff68674939ce8617ed177d6116cdbe456 Mon Sep 17 00:00:00 2001 From: enricoturri1966 <enricoturri@seznam.cz> Date: Thu, 15 Apr 2021 15:19:03 +0200 Subject: [PATCH] Project dirty state manager -> management of gizmos dirty state WIP --- src/slic3r/GUI/ImGuiWrapper.cpp | 3 +- src/slic3r/GUI/Plater.cpp | 4 +- src/slic3r/GUI/ProjectDirtyStateManager.cpp | 225 +++++++++++++++----- src/slic3r/GUI/ProjectDirtyStateManager.hpp | 43 +++- 4 files changed, 212 insertions(+), 63 deletions(-) diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index db7af046b..5726477ab 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -488,8 +488,7 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool ( int i=0; const char* item_text; - while (items_getter(is_undo, i, &item_text)) - { + while (items_getter(is_undo, i, &item_text)) { ImGui::Selectable(item_text, i < hovered); if (ImGui::IsItemHovered()) { diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5ffd4e51c..dad3fcef0 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4245,7 +4245,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) this->undo_redo_stack().release_least_recently_used(); #if ENABLE_PROJECT_DIRTY_STATE - dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot, undo_redo_stack_main(), undo_redo_stack()); #endif // ENABLE_PROJECT_DIRTY_STATE // Save the last active preset name of a particular printer technology. @@ -4385,7 +4385,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator } #if ENABLE_PROJECT_DIRTY_STATE - dirty_state.update_from_undo_redo_stack(undo_redo_stack_main(), undo_redo_stack()); + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo, undo_redo_stack_main(), undo_redo_stack()); #endif // ENABLE_PROJECT_DIRTY_STATE } diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp index 82557c000..dfdeabd02 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.cpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -37,22 +37,59 @@ static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stac return ret; } -static const UndoRedo::Snapshot* get_last_valid_snapshot(EStackType type, const UndoRedo::Stack& stack, size_t last_save_timestamp) { - auto skip_main = [last_save_timestamp](const UndoRedo::Snapshot& snapshot) { - return boost::starts_with(snapshot.name, _utf8("Selection")) || - ((boost::starts_with(snapshot.name, _utf8("Entering")) || boost::starts_with(snapshot.name, _utf8("Leaving"))) && - (last_save_timestamp == 0 || last_save_timestamp != snapshot.timestamp)); +static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos) { + 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; + }; + + auto skip_main = [&gizmos, is_gizmo_with_modifications](const UndoRedo::Snapshot& snapshot) { + 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 (!is_gizmo_with_modifications(snapshot)) + return true; + } + else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) { + if (!gizmos.is_used_and_modified(snapshot)) + return true; + } + + return false; + }; + + auto skip_gizmo = [&gizmos](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 (type == EStackType::Main && skip_main(*curr)) { + 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; } - return curr; + return curr->timestamp > 0 ? curr : nullptr; } static std::string extract_gizmo_name(const std::string& s) { @@ -69,15 +106,63 @@ static std::string extract_gizmo_name(const std::string& s) { return ret; } -void ProjectDirtyStateManager::update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) +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& [name, gizmo] : used) { + auto it = gizmo.modified_timestamps.begin(); + while (it != gizmo.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 = gizmo.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 + +bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const +{ + for (auto& [name, gizmo] : used) { + for (size_t i : gizmo.modified_timestamps) { + if (i == snapshot.timestamp) + return true; + } + } + return false; +} + +void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack) { if (!wxGetApp().initialized()) return; if (&main_stack == &active_stack) - update_from_undo_redo_main_stack(main_stack); + update_from_undo_redo_main_stack(type, main_stack); else - update_from_undo_redo_gizmo_stack(active_stack); + update_from_undo_redo_gizmo_stack(type, active_stack); wxGetApp().mainframe->update_title(); } @@ -101,34 +186,21 @@ void ProjectDirtyStateManager::reset_after_save() if (&main_stack == &active_stack) { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(main_stack); - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, main_stack, m_last_save.main); - -// if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { -// if (m_state.current_gizmo) { -// int a = 0; -// } -// else { -// int a = 0; -// } -// } - - m_last_save.main = valid_snapshot->timestamp; + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos); + assert(saveable_snapshot != nullptr); + m_last_save.main = saveable_snapshot->timestamp; } else { const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(active_stack); - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Gizmo, active_stack, m_last_save.gizmo); - + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos); const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { - if (m_state.current_gizmo) { + if (m_state.gizmos.current) { m_last_save.main = main_active_snapshot->timestamp; } -// else { -// int a = 0; -// } } - m_last_save.gizmo = valid_snapshot->timestamp; + m_last_save.gizmo = saveable_snapshot->timestamp; } reset_initial_presets(); @@ -153,75 +225,128 @@ void ProjectDirtyStateManager::render_debug_window() const auto color = [](bool value) { return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) : ImVec4(1.0f, 1.0f, 1.0f, 1.0f); }; - auto text = [](bool value) { + auto bool_to_text = [](bool value) { return value ? "true" : "false"; }; - auto append_bool_item = [color, text, &imgui](const std::string& name, bool value) { + auto append_bool_item = [color, bool_to_text, &imgui](const std::string& name, bool value) { imgui.text_colored(color(value), name); ImGui::SameLine(); - imgui.text_colored(color(value), text(value)); + imgui.text_colored(color(value), bool_to_text(value)); }; - auto append_int_item = [color, text, &imgui](const std::string& name, int value) { + auto append_int_item = [&imgui](const std::string& name, int value) { imgui.text(name); ImGui::SameLine(); imgui.text(std::to_string(value)); }; + auto append_snapshot_item = [&imgui](const std::string& label, const UndoRedo::Snapshot* snapshot) { + imgui.text(label); + ImGui::SameLine(100); + if (snapshot != nullptr) + imgui.text(snapshot->name + " (" + std::to_string(snapshot->timestamp) + ")"); + else + imgui.text("-"); + }; - imgui.begin(std::string( "Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + imgui.begin(std::string("Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); - if (ImGui::CollapsingHeader("Dirty state")) { + if (ImGui::CollapsingHeader("Dirty state", ImGuiTreeNodeFlags_DefaultOpen)) { append_bool_item("Overall:", is_dirty()); ImGui::Separator(); append_bool_item("Plater:", m_state.plater); append_bool_item("Presets:", m_state.presets); - append_bool_item("Current gizmo:", m_state.current_gizmo); - append_bool_item("Any gizmo:", !m_state.gizmos.empty()); + append_bool_item("Current gizmo:", m_state.gizmos.current); } - if (ImGui::CollapsingHeader("Last save timestamps")) { + if (ImGui::CollapsingHeader("Last save timestamps", ImGuiTreeNodeFlags_DefaultOpen)) { append_int_item("Main:", m_last_save.main); append_int_item("Gizmo:", m_last_save.gizmo); } + if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) { + const UndoRedo::Stack& stack = wxGetApp().plater()->undo_redo_stack_main(); + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + append_snapshot_item("Active:", active_snapshot); + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos); + append_snapshot_item("Last saveable:", last_saveable_snapshot); + if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { + const std::vector<UndoRedo::Snapshot>& snapshots = stack.snapshots(); + for (const UndoRedo::Snapshot& snapshot : snapshots) { + bool active = active_snapshot->timestamp == snapshot.timestamp; + imgui.text_colored(color(active), snapshot.name); + ImGui::SameLine(150); + imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); + if (&snapshot == last_saveable_snapshot) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (S)"); + } + } + } + } + + 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)) { + for (size_t i : gizmo.modified_timestamps) { + imgui.text(std::to_string(i)); + } + } + } + } + ImGui::Unindent(10.0f); + } + } + imgui.end(); } #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW -void ProjectDirtyStateManager::update_from_undo_redo_main_stack(const Slic3r::UndoRedo::Stack& stack) +void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) { m_state.plater = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + + if (type == UpdateType::TakeSnapshot) + m_state.gizmos.remove_obsolete_used(stack); + if (active_snapshot->name == _utf8("New Project") || active_snapshot->name == _utf8("Reset Project") || boost::starts_with(active_snapshot->name, _utf8("Load Project:"))) return; size_t search_timestamp = 0; - if (boost::starts_with(active_snapshot->name, _utf8("Leaving")) || boost::starts_with(active_snapshot->name, _utf8("Entering"))) { - if (m_state.current_gizmo) - m_state.gizmos.push_back(extract_gizmo_name(active_snapshot->name)); - m_state.current_gizmo = false; + if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { + m_state.gizmos.current = false; + m_last_save.gizmo = 0; + search_timestamp = m_last_save.main; + } + 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; search_timestamp = m_last_save.main; } - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Main, stack, search_timestamp); - m_state.plater = valid_snapshot->timestamp != m_last_save.main; + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos); + m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main); } -void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(const Slic3r::UndoRedo::Stack& stack) +void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) { - m_state.current_gizmo = false; + m_state.gizmos.current = false; const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); if (active_snapshot->name == "Gizmos-Initial") { - m_state.current_gizmo = (m_last_save.gizmo != 0); + m_state.gizmos.current = (m_last_save.gizmo != 0); return; } - const UndoRedo::Snapshot* valid_snapshot = get_last_valid_snapshot(EStackType::Gizmo, stack, m_last_save.gizmo); - m_state.current_gizmo = valid_snapshot->timestamp != m_last_save.gizmo; + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos); + m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo); } } // namespace GUI diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp index a1fdd1fda..a7c16705b 100644 --- a/src/slic3r/GUI/ProjectDirtyStateManager.hpp +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -8,27 +8,52 @@ namespace Slic3r { namespace UndoRedo { class Stack; +struct Snapshot; } // namespace UndoRedo -namespace GUI { +namespace GUI { class ProjectDirtyStateManager { +public: + enum class UpdateType : unsigned char + { + TakeSnapshot, + UndoRedoTo + }; + struct DirtyState { + struct Gizmos + { + struct Gizmo + { + std::vector<size_t> modified_timestamps; + }; + + bool current{ false }; + std::map<std::string, Gizmo> used; + + void add_used(const UndoRedo::Snapshot& snapshot); + void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + bool any_used_modified() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const; + }; + bool plater{ false }; bool presets{ false }; - bool current_gizmo{ false }; - std::vector<std::string> gizmos; + Gizmos gizmos; - bool is_dirty() const { return plater || presets || current_gizmo || !gizmos.empty(); } + bool is_dirty() const { return plater || presets || gizmos.current; } void reset() { plater = false; presets = false; - current_gizmo = false; - gizmos.clear(); + gizmos.current = false; } }; +private: struct LastSaveTimestamps { size_t main{ 0 }; @@ -48,7 +73,7 @@ class ProjectDirtyStateManager public: bool is_dirty() const { return m_state.is_dirty(); } - void update_from_undo_redo_stack(const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); + void update_from_undo_redo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& main_stack, const Slic3r::UndoRedo::Stack& active_stack); void update_from_presets(); void reset_after_save(); void reset_initial_presets(); @@ -57,8 +82,8 @@ public: #endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW private: - void update_from_undo_redo_main_stack(const Slic3r::UndoRedo::Stack& stack); - void update_from_undo_redo_gizmo_stack(const Slic3r::UndoRedo::Stack& stack); + void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); + void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); }; } // namespace GUI