From e2926f7c3fd4ce457ec70c327083e19c59822b22 Mon Sep 17 00:00:00 2001 From: Sara Date: Thu, 20 Nov 2025 23:33:46 +0100 Subject: [PATCH] feat(wip): implementing edit history and un-re-do --- modules/terrain_editor/edit_history.cpp | 33 +++++++++++++++++++ modules/terrain_editor/edit_history.h | 27 +++++++++++++++ .../terrain_editor/point_primitive_node.cpp | 15 +++++++-- modules/terrain_editor/register_types.cpp | 6 ++++ .../terrain_editor/terrain_mesh_editor.cpp | 10 ++++++ .../primitive_nodes/point_primitive_node.tscn | 2 +- 6 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 modules/terrain_editor/edit_history.cpp create mode 100644 modules/terrain_editor/edit_history.h diff --git a/modules/terrain_editor/edit_history.cpp b/modules/terrain_editor/edit_history.cpp new file mode 100644 index 00000000..15c1baac --- /dev/null +++ b/modules/terrain_editor/edit_history.cpp @@ -0,0 +1,33 @@ +#include "edit_history.h" +#include "macros.h" + +EditHistory *EditHistory::singleton_instance{ nullptr }; + +void EditHistory::_bind_methods() { + ClassDB::bind_method(D_METHOD("push_action", "do_action", "undo_action"), &self_type::push_action); + ClassDB::bind_method(D_METHOD("undo_last"), &self_type::undo); + ClassDB::bind_method(D_METHOD("redo_last"), &self_type::redo); +} + +void EditHistory::push_action(Callable do_action, Callable undo_action) { + if (this->undo_count != 0) { + this->history.resize(this->history.size() - this->undo_count); + this->undo_count = 0; + } + do_action.call(); + this->history.push_back({ do_action, undo_action }); +} + +void EditHistory::undo() { + if (this->undo_count + 1 < this->history.size()) { + this->undo_count++; + this->history[this->history.size() - this->undo_count].undo_action.call(); + } +} + +void EditHistory::redo() { + if (this->undo_count > 1) { + this->undo_count--; + this->history[this->history.size() - this->undo_count].do_action.call(); + } +} diff --git a/modules/terrain_editor/edit_history.h b/modules/terrain_editor/edit_history.h new file mode 100644 index 00000000..f35b0c28 --- /dev/null +++ b/modules/terrain_editor/edit_history.h @@ -0,0 +1,27 @@ +#pragma once + +#include "core/object/class_db.h" +#include "core/object/object.h" +#include "core/variant/callable.h" +#include "terrain_editor/register_types.h" + +class EditHistory : public Object { + GDCLASS(EditHistory, Object); + static void _bind_methods(); + static EditHistory *singleton_instance; + friend void initialize_terrain_editor_module(ModuleInitializationLevel p_level); + struct EditAction { + Callable do_action; + Callable undo_action; + }; + +public: + static EditHistory *get_singleton() { return singleton_instance; } + void push_action(Callable do_action, Callable undo_action); + void undo(); + void redo(); + +private: + size_t undo_count{ 0 }; + Vector history{}; +}; diff --git a/modules/terrain_editor/point_primitive_node.cpp b/modules/terrain_editor/point_primitive_node.cpp index 4222f2d4..fb9bcb53 100644 --- a/modules/terrain_editor/point_primitive_node.cpp +++ b/modules/terrain_editor/point_primitive_node.cpp @@ -1,6 +1,8 @@ #include "point_primitive_node.h" #include "scene/3d/node_3d.h" +#include "terrain_editor/edit_history.h" #include "terrain_editor/macros.h" +#include "terrain_editor/terrain_primitive.h" void PointPrimitiveNode::_bind_methods() { BIND_GET_SET(primitive); @@ -32,8 +34,17 @@ void PointPrimitiveNode::push_transform_changes() { if (this->primitive.is_valid()) { this->pushing_change = true; Vector3 const position{ get_global_position() }; - this->primitive->set_center({ position.x, position.z }); - this->primitive->set_height(position.y); + Vector2 const center{ position.x, position.z }; + if (center != this->primitive->get_center()) { + EditHistory::get_singleton()->push_action( + callable_mp(*this->primitive, &PointPrimitive::set_center).bind(center), + callable_mp(*this->primitive, &PointPrimitive::set_center).bind(this->primitive->get_center())); + } + if (position.y != this->primitive->get_height()) { + EditHistory::get_singleton()->push_action( + callable_mp(*this->primitive, &PointPrimitive::set_height).bind(position.y), + callable_mp(*this->primitive, &PointPrimitive::set_height).bind(this->primitive->get_height())); + } this->pushing_change = false; } } diff --git a/modules/terrain_editor/register_types.cpp b/modules/terrain_editor/register_types.cpp index b92917d5..b9d1604f 100644 --- a/modules/terrain_editor/register_types.cpp +++ b/modules/terrain_editor/register_types.cpp @@ -1,6 +1,9 @@ #include "register_types.h" +#include "core/config/engine.h" #include "core/object/class_db.h" +#include "scene/main/scene_tree.h" +#include "terrain_editor/edit_history.h" #include "terrain_editor/point_primitive_node.h" #include "terrain_editor/terrain_chunk.h" #include "terrain_editor/terrain_mesh_editor.h" @@ -21,10 +24,13 @@ void initialize_terrain_editor_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); + Engine::get_singleton()->add_singleton(Engine::Singleton("EditHistory", (EditHistory::singleton_instance = memnew(EditHistory)), "EditHistory")); } void uninitialize_terrain_editor_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } + memdelete_notnull(EditHistory::get_singleton()); } diff --git a/modules/terrain_editor/terrain_mesh_editor.cpp b/modules/terrain_editor/terrain_mesh_editor.cpp index a5b47241..a2ab6424 100644 --- a/modules/terrain_editor/terrain_mesh_editor.cpp +++ b/modules/terrain_editor/terrain_mesh_editor.cpp @@ -7,6 +7,7 @@ #include "scene/3d/node_3d.h" #include "scene/gui/file_dialog.h" #include "scene/resources/packed_scene.h" +#include "terrain_editor/edit_history.h" #include "terrain_editor/macros.h" #include "terrain_editor/point_primitive_node.h" #include "terrain_editor/terrain_primitive.h" @@ -110,15 +111,24 @@ void TerrainMeshEditor::_notification(int what) { void TerrainMeshEditor::unhandled_input(Ref const &event) { Ref key{ event }; if (key.is_valid() && key->get_key_label() == Key::S && key->get_modifiers_mask() == (KeyModifierMask::SHIFT | KeyModifierMask::CTRL)) { + get_viewport()->set_input_as_handled(); this->file_dialog->set_file_mode(FileDialog::FILE_MODE_SAVE_FILE); this->file_dialog->set_ok_button_text("Save"); this->file_dialog->popup_file_dialog(); } else if (key.is_valid() && key->get_key_label() == Key::S && key->get_modifiers_mask() == KeyModifierMask::CTRL) { + get_viewport()->set_input_as_handled(); save_data(); } else if (key.is_valid() && key->get_key_label() == Key::O && key->get_modifiers_mask() == KeyModifierMask::CTRL) { + get_viewport()->set_input_as_handled(); this->file_dialog->set_file_mode(FileDialog::FILE_MODE_OPEN_FILE); this->file_dialog->set_ok_button_text("Open"); this->file_dialog->popup_file_dialog(); + } else if (key.is_valid() && key->get_key_label() == Key::Z && key->get_modifiers_mask() == KeyModifierMask::CTRL) { + get_viewport()->set_input_as_handled(); + EditHistory::get_singleton()->undo(); + } else if (key.is_valid() && key->get_key_label() == Key::Z && key->get_modifiers_mask() == (KeyModifierMask::CTRL | KeyModifierMask::SHIFT)) { + get_viewport()->set_input_as_handled(); + EditHistory::get_singleton()->redo(); } } diff --git a/project/objects/primitive_nodes/point_primitive_node.tscn b/project/objects/primitive_nodes/point_primitive_node.tscn index 991b3c6d..69e2b006 100644 --- a/project/objects/primitive_nodes/point_primitive_node.tscn +++ b/project/objects/primitive_nodes/point_primitive_node.tscn @@ -23,9 +23,9 @@ func _input(event: InputEvent) -> void: func _input_event(_camera: Camera3D, event: InputEvent, _event_position: Vector3, _normal: Vector3, _shape_idx: int) -> void: if not dragged and event is InputEventMouseButton and (event as InputEventMouseButton).is_pressed(): + get_viewport().set_input_as_handled() dragged = true Input.mouse_mode = Input.MOUSE_MODE_CAPTURED - get_viewport().set_input_as_handled() " [sub_resource type="CylinderShape3D" id="CylinderShape3D_mx0s0"]