Working on detail eraser

This commit is contained in:
Yousef Khadadeh 2026-05-24 20:32:21 +03:00
parent 60ab8f3c73
commit c54fef383c
15 changed files with 255 additions and 58 deletions

View 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() {}

View 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;
};

View file

@ -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 {

View file

@ -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;

View file

@ -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;
}

View file

@ -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;