From c54fef383c8a8576247550ac9a6d8f81a66cdd4d Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sun, 24 May 2026 20:32:21 +0300 Subject: [PATCH] Working on detail eraser --- CMakeLists.txt | 1 + src/CanvasComponents/CanvasComponent.cpp | 8 +++ src/CanvasComponents/CanvasComponent.hpp | 4 ++ .../CanvasComponentContainer.cpp | 22 +++++++ .../CanvasComponentContainer.hpp | 7 +++ src/CanvasComponents/MeshCanvasComponent.cpp | 19 +++++++ src/CanvasComponents/MeshCanvasComponent.hpp | 2 + .../EditCanvasComponentWorldUndoAction.cpp | 53 +++++++++++++++++ .../EditCanvasComponentWorldUndoAction.hpp | 33 +++++++++++ src/DrawingProgram/ToolConfiguration.hpp | 3 +- src/DrawingProgram/Tools/EditTool.cpp | 37 +----------- src/DrawingProgram/Tools/EraserTool.cpp | 57 +++++++++++++++++-- src/DrawingProgram/Tools/EraserTool.hpp | 1 + src/WorldUndoManager.cpp | 57 ++++++++++++++----- src/WorldUndoManager.hpp | 9 +-- 15 files changed, 255 insertions(+), 58 deletions(-) create mode 100644 src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp create mode 100644 src/DrawingProgram/EditCanvasComponentWorldUndoAction.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b2576b..f26900a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,6 +71,7 @@ set(sources "src/main.cpp" "src/RichText/TextBox.cpp" "src/RichText/TextStyleModifier.cpp" "src/RichText/ParagraphStyleData.cpp" + "src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp" "src/DrawingProgram/ToolConfiguration.cpp" "src/DrawingProgram/DrawingProgramSelection.cpp" "src/DrawingProgram/DrawingProgramCache.cpp" diff --git a/src/CanvasComponents/CanvasComponent.cpp b/src/CanvasComponents/CanvasComponent.cpp index feb37de..6fd2fdd 100644 --- a/src/CanvasComponents/CanvasComponent.cpp +++ b/src/CanvasComponents/CanvasComponent.cpp @@ -71,4 +71,12 @@ bool CanvasComponent::should_draw_extra(const DrawData& drawData, const CoordSpa return true; } +bool CanvasComponent::can_erase_detail() const { + return false; +} + +CanvasComponentEraseDetailResult CanvasComponent::erase_detail(const SkPath& eraseAgainst) { + return CanvasComponentEraseDetailResult::NO_CHANGE; +} + CanvasComponent::~CanvasComponent() {} diff --git a/src/CanvasComponents/CanvasComponent.hpp b/src/CanvasComponents/CanvasComponent.hpp index 9e522f5..d3649c6 100644 --- a/src/CanvasComponents/CanvasComponent.hpp +++ b/src/CanvasComponents/CanvasComponent.hpp @@ -29,6 +29,7 @@ class DrawingProgram; class CanvasComponent { public: + virtual CanvasComponentType get_type() const = 0; virtual ~CanvasComponent(); static CanvasComponent* allocate_comp(CanvasComponentType type); @@ -59,6 +60,9 @@ class CanvasComponent { virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const = 0; virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const = 0; + virtual bool can_erase_detail() const; + virtual CanvasComponentEraseDetailResult erase_detail(const SkPath& eraseAgainst); + virtual SCollision::AABB get_obj_coord_bounds() const = 0; CanvasComponentContainer* compContainer = nullptr; diff --git a/src/CanvasComponents/CanvasComponentContainer.cpp b/src/CanvasComponents/CanvasComponentContainer.cpp index 7662e91..9873c0a 100644 --- a/src/CanvasComponents/CanvasComponentContainer.cpp +++ b/src/CanvasComponents/CanvasComponentContainer.cpp @@ -142,6 +142,28 @@ void CanvasComponentContainer::commit_transform_dont_invalidate_cache() { calculate_world_bounds(); } +CanvasComponentEraseDetailResult CanvasComponentContainer::collides_with_erase_detail(const CoordSpaceHelper& camCoords, const SkPath& checkAgainstCam) const { + if(!worldAABB.has_value()) + return CanvasComponentEraseDetailResult::NO_CHANGE; + if((camCoords.inverseScale << COMP_MAX_SHIFT_BEFORE_STOP_COLLISIONS) < coords.inverseScale) // Object is too large, just dismiss the collision + return CanvasComponentEraseDetailResult::NO_CHANGE; + else if(coords.inverseScale < (camCoords.inverseScale >> COMP_COLLIDE_MIN_SHIFT_TINY)) { + if(checkAgainstCam.contains(convert_vec2(camCoords.to_space(worldAABB.value().min)))) + return CanvasComponentEraseDetailResult::REMOVED; + return CanvasComponentEraseDetailResult::NO_CHANGE; + } + else if(get_comp().can_erase_detail()) { + TransformData drawTransform = calculate_draw_transform(camCoords); + SkMatrix m = SkMatrix::I(); + m.postScale(1.0 / drawTransform.scale, 1.0 / drawTransform.scale).postRotate(-drawTransform.rotation).postTranslate(-drawTransform.translation.x(), -drawTransform.translation.y()); + std::optional transformedPath = checkAgainstCam.tryMakeTransform(m); + if(!transformedPath.has_value()) + return CanvasComponentEraseDetailResult::NO_CHANGE; + return get_comp().erase_detail(transformedPath.value()); + } + return CanvasComponentEraseDetailResult::NO_CHANGE; +} + // We could just send one of the checkAgainst colliders, since they represent the same thing, but sending both saves on redundant transformations, since we can save both of the versions on the executor side bool CanvasComponentContainer::collides_with(const CoordSpaceHelper& camCoords, const SkPath& checkAgainstCam) const { if(!worldAABB.has_value()) diff --git a/src/CanvasComponents/CanvasComponentContainer.hpp b/src/CanvasComponents/CanvasComponentContainer.hpp index 4655c5f..3d355d9 100644 --- a/src/CanvasComponents/CanvasComponentContainer.hpp +++ b/src/CanvasComponents/CanvasComponentContainer.hpp @@ -30,6 +30,12 @@ class DrawingProgram; class DrawingProgramLayerListItem; struct DrawingProgramCacheBVHNode; +enum class CanvasComponentEraseDetailResult { + NO_CHANGE, + CHANGED, + REMOVED +}; + class CanvasComponentContainer { public: typedef NetworkingObjects::NetObjOrderedList NetList; @@ -83,6 +89,7 @@ class CanvasComponentContainer { void commit_transform(DrawingProgram& drawP); void commit_update_dont_invalidate_cache(DrawingProgram& drawP); // Must be thread safe bool should_draw(const DrawData& drawData) const; + CanvasComponentEraseDetailResult collides_with_erase_detail(const CoordSpaceHelper& camCoords, const SkPath& checkAgainstCam) const; bool collides_with(const CoordSpaceHelper& camCoords, const SkPath& checkAgainstCam) const; bool collides_with_point(const CoordSpaceHelper& camCoords, const Vector2f& checkAgainstCam) const; void send_comp_update(DrawingProgram& drawP, bool finalUpdate); diff --git a/src/CanvasComponents/MeshCanvasComponent.cpp b/src/CanvasComponents/MeshCanvasComponent.cpp index 40fbe6c..07a0626 100644 --- a/src/CanvasComponents/MeshCanvasComponent.cpp +++ b/src/CanvasComponents/MeshCanvasComponent.cpp @@ -23,6 +23,7 @@ #include #include #include "BrushComponentCode.hpp" +#include "CanvasComponent.hpp" #include "Eigen/Core" #include "Helpers/MathExtras.hpp" #include "../TimePoint.hpp" @@ -263,6 +264,24 @@ bool MeshCanvasComponent::should_draw_extra(const DrawData& drawData, const Coor //return collisionTree.is_collide(viewGenerousColliderInObjSpace); } +bool MeshCanvasComponent::can_erase_detail() const { + return true; +} + +CanvasComponentEraseDetailResult MeshCanvasComponent::erase_detail(const SkPath& eraseAgainst) { + bool intersectsAABB = d.meshPath.getBounds().intersects(eraseAgainst.getBounds()); + if(!intersectsAABB) + return CanvasComponentEraseDetailResult::NO_CHANGE; + std::optional newPath = Op(d.meshPath, eraseAgainst, SkPathOp::kDifference_SkPathOp); + if(newPath.has_value()) { + d.meshPath = newPath.value(); + if(d.meshPath.isEmpty()) + return CanvasComponentEraseDetailResult::REMOVED; + return CanvasComponentEraseDetailResult::CHANGED; + } + return CanvasComponentEraseDetailResult::NO_CHANGE; +} + SCollision::AABB MeshCanvasComponent::get_obj_coord_bounds() const { return d.meshPath.getBounds(); } diff --git a/src/CanvasComponents/MeshCanvasComponent.hpp b/src/CanvasComponents/MeshCanvasComponent.hpp index 02bf6e4..adda9ef 100644 --- a/src/CanvasComponents/MeshCanvasComponent.hpp +++ b/src/CanvasComponents/MeshCanvasComponent.hpp @@ -47,6 +47,8 @@ class MeshCanvasComponent : public CanvasComponent { virtual void initialize_draw_data(DrawingProgram& drawP) override; virtual bool collides_within_coords_point(const Vector2f& checkAgainst) const override; virtual bool collides_within_coords_skpath(const SkPath& checkAgainst) const override; + virtual bool can_erase_detail() const override; + virtual CanvasComponentEraseDetailResult erase_detail(const SkPath& eraseAgainst) override; bool should_draw_extra(const DrawData& drawData, const CoordSpaceHelper& coords) const override; virtual SCollision::AABB get_obj_coord_bounds() const override; SCollision::BVHContainer collisionTree; diff --git a/src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp b/src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp new file mode 100644 index 0000000..a73f490 --- /dev/null +++ b/src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp @@ -0,0 +1,53 @@ +/* + * InfiniPaint + * Copyright (C) 2025-2026 Yousef Khadadeh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "EditCanvasComponentWorldUndoAction.hpp" +#include "../CanvasComponents/CanvasComponent.hpp" +#include "../World.hpp" + +EditCanvasComponentWorldUndoAction::EditCanvasComponentWorldUndoAction(std::unique_ptr initData, WorldUndoManager::UndoObjectID initUndoID): + data(std::move(initData)), + undoID(initUndoID) +{} + +std::string EditCanvasComponentWorldUndoAction::get_name() const { + return "Edit Canvas Component"; +} + +bool EditCanvasComponentWorldUndoAction::undo(WorldUndoManager& undoMan) { + return undo_redo(undoMan); +} + +bool EditCanvasComponentWorldUndoAction::redo(WorldUndoManager& undoMan) { + return undo_redo(undoMan); +} + +bool EditCanvasComponentWorldUndoAction::undo_redo(WorldUndoManager& undoMan) { + std::optional toEditID = undoMan.get_netid_from_undoid(undoID); + if(!toEditID.has_value()) + return false; + auto objPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id(toEditID.value()); + std::unique_ptr newData = objPtr->get_comp().get_data_copy(); + objPtr->get_comp().set_data_from(*data); + data = std::move(newData); + objPtr->commit_update(undoMan.world.drawProg); + objPtr->send_comp_update(undoMan.world.drawProg, true); + return true; +} + +EditCanvasComponentWorldUndoAction::~EditCanvasComponentWorldUndoAction() {} diff --git a/src/DrawingProgram/EditCanvasComponentWorldUndoAction.hpp b/src/DrawingProgram/EditCanvasComponentWorldUndoAction.hpp new file mode 100644 index 0000000..d1abbfe --- /dev/null +++ b/src/DrawingProgram/EditCanvasComponentWorldUndoAction.hpp @@ -0,0 +1,33 @@ +/* + * InfiniPaint + * Copyright (C) 2025-2026 Yousef Khadadeh + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include "../CanvasComponents/CanvasComponentContainer.hpp" + +class EditCanvasComponentWorldUndoAction : public WorldUndoAction { + public: + EditCanvasComponentWorldUndoAction(std::unique_ptr initData, WorldUndoManager::UndoObjectID initUndoID); + std::string get_name() const override; + bool undo(WorldUndoManager& undoMan) override; + bool redo(WorldUndoManager& undoMan) override; + bool undo_redo(WorldUndoManager& undoMan); + ~EditCanvasComponentWorldUndoAction(); + + std::unique_ptr data; + WorldUndoManager::UndoObjectID undoID; +}; diff --git a/src/DrawingProgram/ToolConfiguration.hpp b/src/DrawingProgram/ToolConfiguration.hpp index 842255c..941e573 100644 --- a/src/DrawingProgram/ToolConfiguration.hpp +++ b/src/DrawingProgram/ToolConfiguration.hpp @@ -33,7 +33,8 @@ class ToolConfiguration { struct EraserToolConfig { float relativeWidth = 15.0f; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EraserToolConfig, relativeWidth) + bool eraseDetail = false; + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(EraserToolConfig, relativeWidth, eraseDetail) } eraser; struct EllipseDrawToolConfig { diff --git a/src/DrawingProgram/Tools/EditTool.cpp b/src/DrawingProgram/Tools/EditTool.cpp index 2be0047..d849c0a 100644 --- a/src/DrawingProgram/Tools/EditTool.cpp +++ b/src/DrawingProgram/Tools/EditTool.cpp @@ -31,6 +31,7 @@ #include "EditTools/ImageEditTool.hpp" #include "EditTools/EllipseDrawEditTool.hpp" #include "EditTools/MeshEditTool.hpp" +#include "../EditCanvasComponentWorldUndoAction.hpp" #include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp" @@ -214,42 +215,8 @@ void EditTool::switch_tool(DrawingProgramToolType newTool) { objInfoBeingEdited->obj->commit_update(drawP); objInfoBeingEdited->obj->send_comp_update(drawP, true); - if(undoAfterEditDone) { - class EditCanvasComponentWorldUndoAction : public WorldUndoAction { - public: - EditCanvasComponentWorldUndoAction(std::unique_ptr initData, WorldUndoManager::UndoObjectID initUndoID): - data(std::move(initData)), - undoID(initUndoID) - {} - std::string get_name() const override { - return "Edit Canvas Component"; - } - bool undo(WorldUndoManager& undoMan) override { - return undo_redo(undoMan); - } - bool redo(WorldUndoManager& undoMan) override { - return undo_redo(undoMan); - } - bool undo_redo(WorldUndoManager& undoMan) { - std::optional toEditID = undoMan.get_netid_from_undoid(undoID); - if(!toEditID.has_value()) - return false; - auto objPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id(toEditID.value()); - std::unique_ptr newData = objPtr->get_comp().get_data_copy(); - objPtr->get_comp().set_data_from(*data); - data = std::move(newData); - objPtr->commit_update(undoMan.world.drawProg); - objPtr->send_comp_update(undoMan.world.drawProg, true); - return true; - } - ~EditCanvasComponentWorldUndoAction() {} - - std::unique_ptr data; - WorldUndoManager::UndoObjectID undoID; - }; - + if(undoAfterEditDone) drawP.world.undo.push(std::make_unique(std::move(oldData), drawP.world.undo.get_undoid_from_netid(objInfoBeingEdited->obj.get_net_id()))); - } oldData = nullptr; objInfoBeingEdited = nullptr; diff --git a/src/DrawingProgram/Tools/EraserTool.cpp b/src/DrawingProgram/Tools/EraserTool.cpp index 2eb12ba..1f57f8f 100644 --- a/src/DrawingProgram/Tools/EraserTool.cpp +++ b/src/DrawingProgram/Tools/EraserTool.cpp @@ -23,9 +23,11 @@ #include #include #include "../../CanvasComponents/BrushComponentCode.hpp" +#include "../EditCanvasComponentWorldUndoAction.hpp" #include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp" #include "../../GUIStuff/ElementHelpers/RadioButtonHelpers.hpp" +#include "../../GUIStuff/ElementHelpers/CheckBoxHelpers.hpp" EraserTool::EraserTool(DrawingProgram& initDrawP): DrawingProgramToolBase(initDrawP) @@ -48,6 +50,7 @@ void EraserTool::gui_toolbox(Toolbar& t) { {"Layer being edited", DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED}, {"All visible layers", DrawingProgramLayerManager::LayerSelector::ALL_VISIBLE_LAYERS} }); + checkbox_boolean_field(gui, "erase details", "Erase details", &drawP.world.main.toolConfig.eraser.eraseDetail); }); } @@ -64,6 +67,7 @@ void EraserTool::gui_phone_toolbox(PhoneDrawingProgramScreen& t) { {"Layer being edited", DrawingProgramLayerManager::LayerSelector::LAYER_BEING_EDITED}, {"All visible layers", DrawingProgramLayerManager::LayerSelector::ALL_VISIBLE_LAYERS} }); + checkbox_boolean_field(gui, "erase details", "Erase details", &drawP.world.main.toolConfig.eraser.eraseDetail); }); } @@ -98,6 +102,11 @@ void EraserTool::erase_between_points() { drawP.drawCache.traverse_bvh_run_function_starting_at_node_no_collision_check(bvhNode, [&](const auto& bvhNodeChild) { drawP.drawCache.node_loop_erase_if_components(bvhNodeChild, [&](auto c) { if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector)) { + auto it = updatedComponents.find(c); + if(it != updatedComponents.end()) { + c->obj->get_comp().set_data_from(*(it->second)); + updatedComponents.erase(it); + } erasedComponents.emplace(c); return true; } @@ -108,10 +117,37 @@ void EraserTool::erase_between_points() { return false; } drawP.drawCache.node_loop_erase_if_components(bvhNode, [&](auto c) { - if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, erasePath)) { - erasedComponents.emplace(c); - drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds()); - return true; + if(drawP.world.main.toolConfig.eraser.eraseDetail) { + if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector)) { + auto dataCopy = c->obj->get_comp().get_data_copy(); + CanvasComponentEraseDetailResult result = c->obj->collides_with_erase_detail(drawP.world.drawData.cam.c, erasePath); + switch(result) { + case CanvasComponentEraseDetailResult::NO_CHANGE: + return false; + case CanvasComponentEraseDetailResult::CHANGED: { + if(!updatedComponents.contains(c)) + updatedComponents.emplace(c, std::move(dataCopy)); + c->obj->commit_update(drawP); + return false; + } + case CanvasComponentEraseDetailResult::REMOVED: + auto it = updatedComponents.find(c); + if(it != updatedComponents.end()) { + c->obj->get_comp().set_data_from(*(it->second)); + updatedComponents.erase(it); + } + erasedComponents.emplace(c); + drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds()); + return true; + } + } + } + else { + if(drawP.layerMan.component_passes_layer_selector(c, drawP.controls.layerSelector) && c->obj->collides_with(drawP.world.drawData.cam.c, erasePath)) { + erasedComponents.emplace(c); + drawP.drawCache.invalidate_cache_at_optional_aabb(c->obj->get_world_bounds()); + return true; + } } return false; }); @@ -121,10 +157,23 @@ void EraserTool::erase_between_points() { void EraserTool::erase_component(CanvasComponentContainer::ObjInfo* erasedComp) { erasedComponents.erase(erasedComp); + updatedComponents.erase(erasedComp); } void EraserTool::switch_tool(DrawingProgramToolType newTool) { drawP.layerMan.erase_component_container(erasedComponents); + bool first = true; + for(auto& [comp, oldData] : updatedComponents) { + comp->obj->send_comp_update(drawP, true); + if(erasedComponents.empty() && first) { + first = false; + drawP.world.undo.push(std::make_unique(std::move(oldData), drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id()))); + } + else + drawP.world.undo.push_on_last(std::make_unique(std::move(oldData), drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id()))); + } + erasedComponents.clear(); + updatedComponents.clear(); isErasing = false; } diff --git a/src/DrawingProgram/Tools/EraserTool.hpp b/src/DrawingProgram/Tools/EraserTool.hpp index 560b3d2..ef3e613 100644 --- a/src/DrawingProgram/Tools/EraserTool.hpp +++ b/src/DrawingProgram/Tools/EraserTool.hpp @@ -38,6 +38,7 @@ class EraserTool : public DrawingProgramToolBase { virtual void input_mouse_button_on_canvas_callback(const InputManager::MouseButtonCallbackArgs& button) override; virtual void input_mouse_motion_callback(const InputManager::MouseMotionCallbackArgs& motion) override; + std::unordered_map> updatedComponents; std::unordered_set erasedComponents; // Pointers will be erased from this set if theyre erased in the main list (done by callback) private: SkPath erasePath; diff --git a/src/WorldUndoManager.cpp b/src/WorldUndoManager.cpp index 9756a61..196e170 100644 --- a/src/WorldUndoManager.cpp +++ b/src/WorldUndoManager.cpp @@ -36,15 +36,24 @@ bool WorldUndoManager::can_redo() { } void WorldUndoManager::push(std::unique_ptr undoAction) { - push_undo(std::move(undoAction)); + std::vector> fullUndoAction; + fullUndoAction.emplace_back(std::move(undoAction)); + push_undo(std::move(fullUndoAction)); redoQueue.clear(); world.set_has_unsaved_local_changes(true); } -void WorldUndoManager::push_undo(std::unique_ptr undoAction) { +void WorldUndoManager::push_on_last(std::unique_ptr undoAction) { + if(undoQueue.empty()) + push(std::move(undoAction)); + else + undoQueue.back().emplace_back(std::move(undoAction)); +} + +void WorldUndoManager::push_undo(std::vector> undoAction) { if(undoQueue.size() == UNDO_QUEUE_LIMIT) { if(undoActionSavedAt.has_value()) { - if(undoQueue.front().get() == undoActionSavedAt.value()) + if(undoQueue.front().front().get() == undoActionSavedAt.value()) undoActionSavedAt = nullptr; else if(undoActionSavedAt.value() == nullptr) undoActionSavedAt = std::nullopt; @@ -54,7 +63,7 @@ void WorldUndoManager::push_undo(std::unique_ptr undoAction) { undoQueue.emplace_back(std::move(undoAction)); } -void WorldUndoManager::push_redo(std::unique_ptr undoAction) { +void WorldUndoManager::push_redo(std::vector> undoAction) { redoQueue.emplace_back(std::move(undoAction)); } @@ -63,8 +72,16 @@ void WorldUndoManager::undo() { return; world.bMan.refresh_gui_data(); - - if(!undoQueue.back()->undo(*this)) + + bool undoFail = false; + for(auto& u : std::views::reverse(undoQueue.back())) { + if(!u->undo(*this)) { + undoFail = true; + break; + } + } + + if(undoFail) clear(); else { push_redo(std::move(undoQueue.back())); @@ -82,7 +99,15 @@ void WorldUndoManager::redo() { world.bMan.refresh_gui_data(); - if(!redoQueue.back()->redo(*this)) + bool redoFail = false; + for(auto& u : redoQueue.back()) { + if(!u->redo(*this)) { + redoFail = true; + break; + } + } + + if(redoFail) clear(); else { push_undo(std::move(redoQueue.back())); @@ -101,10 +126,14 @@ void WorldUndoManager::clear() { } void WorldUndoManager::scale_up(const WorldScalar& scaleAmount) { - for(auto& u : undoQueue) - u->scale_up(scaleAmount); - for(auto& r : redoQueue) - r->scale_up(scaleAmount); + for(auto& u : undoQueue) { + for(auto& uStep : u) + uStep->scale_up(scaleAmount); + } + for(auto& r : redoQueue) { + for(auto& uStep : r) + uStep->scale_up(scaleAmount); + } } void WorldUndoManager::reassign_netid(const NetworkingObjects::NetObjID& oldNetObjID, const NetworkingObjects::NetObjID& newNetObjID) { @@ -177,7 +206,7 @@ bool WorldUndoManager::fill_netid_list_from_undoid_list(std::vector WorldUndoManager::get_front_undo_queue_names(unsigned count) { std::vector toRet; for(auto& u : undoQueue | std::views::reverse) { - toRet.emplace_back(u->get_name()); + toRet.emplace_back(u.front()->get_name()); if(toRet.size() == count) return toRet; } @@ -188,7 +217,7 @@ void WorldUndoManager::set_save_action() { if(undoQueue.empty()) undoActionSavedAt = nullptr; else - undoActionSavedAt = undoQueue.back().get(); + undoActionSavedAt = undoQueue.back().front().get(); world.set_has_unsaved_local_changes(false); } @@ -203,6 +232,6 @@ void WorldUndoManager::set_world_has_unsaved_local_changes() { if(undoQueue.empty()) world.set_has_unsaved_local_changes(undoActionSavedAt.value() != nullptr); else - world.set_has_unsaved_local_changes(undoActionSavedAt.value() != undoQueue.back().get()); + world.set_has_unsaved_local_changes(undoActionSavedAt.value() != undoQueue.back().front().get()); } } diff --git a/src/WorldUndoManager.hpp b/src/WorldUndoManager.hpp index fc6fa97..e13505f 100644 --- a/src/WorldUndoManager.hpp +++ b/src/WorldUndoManager.hpp @@ -40,6 +40,7 @@ class WorldUndoManager { WorldUndoManager(World& initWorld); void push(std::unique_ptr undoAction); + void push_on_last(std::unique_ptr undoAction); void undo(); void redo(); void clear(); @@ -60,14 +61,14 @@ class WorldUndoManager { World& world; private: - void push_undo(std::unique_ptr undoAction); - void push_redo(std::unique_ptr undoAction); + void push_undo(std::vector> undoAction); + void push_redo(std::vector> undoAction); constexpr static size_t UNDO_QUEUE_LIMIT = 250; std::unordered_map undoIDToNetID; std::unordered_map netIDToUndoID; - std::deque> undoQueue; - std::deque> redoQueue; + std::deque>> undoQueue; + std::deque>> redoQueue; std::optional undoActionSavedAt = nullptr;