Working on detail eraser
This commit is contained in:
parent
60ab8f3c73
commit
c54fef383c
15 changed files with 255 additions and 58 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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() {}
|
||||
|
|
|
|||
|
|
@ -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<float> get_obj_coord_bounds() const = 0;
|
||||
|
||||
CanvasComponentContainer* compContainer = nullptr;
|
||||
|
|
|
|||
|
|
@ -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<SkPoint>(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<SkPath> 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())
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ class DrawingProgram;
|
|||
class DrawingProgramLayerListItem;
|
||||
struct DrawingProgramCacheBVHNode;
|
||||
|
||||
enum class CanvasComponentEraseDetailResult {
|
||||
NO_CHANGE,
|
||||
CHANGED,
|
||||
REMOVED
|
||||
};
|
||||
|
||||
class CanvasComponentContainer {
|
||||
public:
|
||||
typedef NetworkingObjects::NetObjOrderedList<CanvasComponentContainer> 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);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <limits>
|
||||
#include <Eigen/Geometry>
|
||||
#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<SkPath> 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<float> MeshCanvasComponent::get_obj_coord_bounds() const {
|
||||
return d.meshPath.getBounds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<float> get_obj_coord_bounds() const override;
|
||||
SCollision::BVHContainer<float> collisionTree;
|
||||
|
|
|
|||
53
src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp
Normal file
53
src/DrawingProgram/EditCanvasComponentWorldUndoAction.cpp
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EditCanvasComponentWorldUndoAction.hpp"
|
||||
#include "../CanvasComponents/CanvasComponent.hpp"
|
||||
#include "../World.hpp"
|
||||
|
||||
EditCanvasComponentWorldUndoAction::EditCanvasComponentWorldUndoAction(std::unique_ptr<CanvasComponent> 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<NetworkingObjects::NetObjID> toEditID = undoMan.get_netid_from_undoid(undoID);
|
||||
if(!toEditID.has_value())
|
||||
return false;
|
||||
auto objPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<CanvasComponentContainer>(toEditID.value());
|
||||
std::unique_ptr<CanvasComponent> 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() {}
|
||||
33
src/DrawingProgram/EditCanvasComponentWorldUndoAction.hpp
Normal file
33
src/DrawingProgram/EditCanvasComponentWorldUndoAction.hpp
Normal file
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "../CanvasComponents/CanvasComponentContainer.hpp"
|
||||
|
||||
class EditCanvasComponentWorldUndoAction : public WorldUndoAction {
|
||||
public:
|
||||
EditCanvasComponentWorldUndoAction(std::unique_ptr<CanvasComponent> 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<CanvasComponent> data;
|
||||
WorldUndoManager::UndoObjectID undoID;
|
||||
};
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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<CanvasComponent> 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<NetworkingObjects::NetObjID> toEditID = undoMan.get_netid_from_undoid(undoID);
|
||||
if(!toEditID.has_value())
|
||||
return false;
|
||||
auto objPtr = undoMan.world.netObjMan.get_obj_temporary_ref_from_id<CanvasComponentContainer>(toEditID.value());
|
||||
std::unique_ptr<CanvasComponent> 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<CanvasComponent> data;
|
||||
WorldUndoManager::UndoObjectID undoID;
|
||||
};
|
||||
|
||||
if(undoAfterEditDone)
|
||||
drawP.world.undo.push(std::make_unique<EditCanvasComponentWorldUndoAction>(std::move(oldData), drawP.world.undo.get_undoid_from_netid(objInfoBeingEdited->obj.get_net_id())));
|
||||
}
|
||||
|
||||
oldData = nullptr;
|
||||
objInfoBeingEdited = nullptr;
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@
|
|||
#include <include/core/SkBlendMode.h>
|
||||
#include <include/core/SkPathBuilder.h>
|
||||
#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<EditCanvasComponentWorldUndoAction>(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<EditCanvasComponentWorldUndoAction>(std::move(oldData), drawP.world.undo.get_undoid_from_netid(comp->obj.get_net_id())));
|
||||
}
|
||||
erasedComponents.clear();
|
||||
updatedComponents.clear();
|
||||
isErasing = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<CanvasComponentContainer::ObjInfo*, std::unique_ptr<CanvasComponent>> updatedComponents;
|
||||
std::unordered_set<CanvasComponentContainer::ObjInfo*> erasedComponents; // Pointers will be erased from this set if theyre erased in the main list (done by callback)
|
||||
private:
|
||||
SkPath erasePath;
|
||||
|
|
|
|||
|
|
@ -36,15 +36,24 @@ bool WorldUndoManager::can_redo() {
|
|||
}
|
||||
|
||||
void WorldUndoManager::push(std::unique_ptr<WorldUndoAction> undoAction) {
|
||||
push_undo(std::move(undoAction));
|
||||
std::vector<std::unique_ptr<WorldUndoAction>> 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<WorldUndoAction> undoAction) {
|
||||
void WorldUndoManager::push_on_last(std::unique_ptr<WorldUndoAction> undoAction) {
|
||||
if(undoQueue.empty())
|
||||
push(std::move(undoAction));
|
||||
else
|
||||
undoQueue.back().emplace_back(std::move(undoAction));
|
||||
}
|
||||
|
||||
void WorldUndoManager::push_undo(std::vector<std::unique_ptr<WorldUndoAction>> 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<WorldUndoAction> undoAction) {
|
|||
undoQueue.emplace_back(std::move(undoAction));
|
||||
}
|
||||
|
||||
void WorldUndoManager::push_redo(std::unique_ptr<WorldUndoAction> undoAction) {
|
||||
void WorldUndoManager::push_redo(std::vector<std::unique_ptr<WorldUndoAction>> 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<NetworkingOb
|
|||
std::vector<std::string> WorldUndoManager::get_front_undo_queue_names(unsigned count) {
|
||||
std::vector<std::string> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class WorldUndoManager {
|
|||
WorldUndoManager(World& initWorld);
|
||||
|
||||
void push(std::unique_ptr<WorldUndoAction> undoAction);
|
||||
void push_on_last(std::unique_ptr<WorldUndoAction> undoAction);
|
||||
void undo();
|
||||
void redo();
|
||||
void clear();
|
||||
|
|
@ -60,14 +61,14 @@ class WorldUndoManager {
|
|||
|
||||
World& world;
|
||||
private:
|
||||
void push_undo(std::unique_ptr<WorldUndoAction> undoAction);
|
||||
void push_redo(std::unique_ptr<WorldUndoAction> undoAction);
|
||||
void push_undo(std::vector<std::unique_ptr<WorldUndoAction>> undoAction);
|
||||
void push_redo(std::vector<std::unique_ptr<WorldUndoAction>> undoAction);
|
||||
constexpr static size_t UNDO_QUEUE_LIMIT = 250;
|
||||
|
||||
std::unordered_map<UndoObjectID, NetworkingObjects::NetObjID> undoIDToNetID;
|
||||
std::unordered_map<NetworkingObjects::NetObjID, UndoObjectID> netIDToUndoID;
|
||||
std::deque<std::unique_ptr<WorldUndoAction>> undoQueue;
|
||||
std::deque<std::unique_ptr<WorldUndoAction>> redoQueue;
|
||||
std::deque<std::vector<std::unique_ptr<WorldUndoAction>>> undoQueue;
|
||||
std::deque<std::vector<std::unique_ptr<WorldUndoAction>>> redoQueue;
|
||||
|
||||
std::optional<WorldUndoAction*> undoActionSavedAt = nullptr;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue