feat(wip): implementing edit history and un-re-do

This commit is contained in:
Sara Gerretsen 2025-11-20 23:33:46 +01:00
parent b875c2089b
commit e2926f7c3f
6 changed files with 90 additions and 3 deletions

View file

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

View file

@ -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<EditAction> history{};
};

View file

@ -1,6 +1,8 @@
#include "point_primitive_node.h" #include "point_primitive_node.h"
#include "scene/3d/node_3d.h" #include "scene/3d/node_3d.h"
#include "terrain_editor/edit_history.h"
#include "terrain_editor/macros.h" #include "terrain_editor/macros.h"
#include "terrain_editor/terrain_primitive.h"
void PointPrimitiveNode::_bind_methods() { void PointPrimitiveNode::_bind_methods() {
BIND_GET_SET(primitive); BIND_GET_SET(primitive);
@ -32,8 +34,17 @@ void PointPrimitiveNode::push_transform_changes() {
if (this->primitive.is_valid()) { if (this->primitive.is_valid()) {
this->pushing_change = true; this->pushing_change = true;
Vector3 const position{ get_global_position() }; Vector3 const position{ get_global_position() };
this->primitive->set_center({ position.x, position.z }); Vector2 const center{ position.x, position.z };
this->primitive->set_height(position.y); 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; this->pushing_change = false;
} }
} }

View file

@ -1,6 +1,9 @@
#include "register_types.h" #include "register_types.h"
#include "core/config/engine.h"
#include "core/object/class_db.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/point_primitive_node.h"
#include "terrain_editor/terrain_chunk.h" #include "terrain_editor/terrain_chunk.h"
#include "terrain_editor/terrain_mesh_editor.h" #include "terrain_editor/terrain_mesh_editor.h"
@ -21,10 +24,13 @@ void initialize_terrain_editor_module(ModuleInitializationLevel p_level) {
ClassDB::register_class<TerrainMeshEditor>(); ClassDB::register_class<TerrainMeshEditor>();
ClassDB::register_class<SaveData>(); ClassDB::register_class<SaveData>();
ClassDB::register_class<TerrainChunk>(); ClassDB::register_class<TerrainChunk>();
ClassDB::register_class<EditHistory>();
Engine::get_singleton()->add_singleton(Engine::Singleton("EditHistory", (EditHistory::singleton_instance = memnew(EditHistory)), "EditHistory"));
} }
void uninitialize_terrain_editor_module(ModuleInitializationLevel p_level) { void uninitialize_terrain_editor_module(ModuleInitializationLevel p_level) {
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return; return;
} }
memdelete_notnull(EditHistory::get_singleton());
} }

View file

@ -7,6 +7,7 @@
#include "scene/3d/node_3d.h" #include "scene/3d/node_3d.h"
#include "scene/gui/file_dialog.h" #include "scene/gui/file_dialog.h"
#include "scene/resources/packed_scene.h" #include "scene/resources/packed_scene.h"
#include "terrain_editor/edit_history.h"
#include "terrain_editor/macros.h" #include "terrain_editor/macros.h"
#include "terrain_editor/point_primitive_node.h" #include "terrain_editor/point_primitive_node.h"
#include "terrain_editor/terrain_primitive.h" #include "terrain_editor/terrain_primitive.h"
@ -110,15 +111,24 @@ void TerrainMeshEditor::_notification(int what) {
void TerrainMeshEditor::unhandled_input(Ref<InputEvent> const &event) { void TerrainMeshEditor::unhandled_input(Ref<InputEvent> const &event) {
Ref<InputEventKey> key{ event }; Ref<InputEventKey> key{ event };
if (key.is_valid() && key->get_key_label() == Key::S && key->get_modifiers_mask() == (KeyModifierMask::SHIFT | KeyModifierMask::CTRL)) { 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_file_mode(FileDialog::FILE_MODE_SAVE_FILE);
this->file_dialog->set_ok_button_text("Save"); this->file_dialog->set_ok_button_text("Save");
this->file_dialog->popup_file_dialog(); this->file_dialog->popup_file_dialog();
} else if (key.is_valid() && key->get_key_label() == Key::S && key->get_modifiers_mask() == KeyModifierMask::CTRL) { } 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(); save_data();
} else if (key.is_valid() && key->get_key_label() == Key::O && key->get_modifiers_mask() == KeyModifierMask::CTRL) { } 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_file_mode(FileDialog::FILE_MODE_OPEN_FILE);
this->file_dialog->set_ok_button_text("Open"); this->file_dialog->set_ok_button_text("Open");
this->file_dialog->popup_file_dialog(); 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();
} }
} }

View file

@ -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: 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(): if not dragged and event is InputEventMouseButton and (event as InputEventMouseButton).is_pressed():
get_viewport().set_input_as_handled()
dragged = true dragged = true
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
get_viewport().set_input_as_handled()
" "
[sub_resource type="CylinderShape3D" id="CylinderShape3D_mx0s0"] [sub_resource type="CylinderShape3D" id="CylinderShape3D_mx0s0"]