From 61ba2a54f2425ea374d8a28248f91139958f81aa Mon Sep 17 00:00:00 2001 From: Sara Date: Tue, 18 Nov 2025 18:00:32 +0100 Subject: [PATCH] feat: implemented saving & loading --- modules/terrain_editor/register_types.cpp | 1 + .../terrain_editor/terrain_mesh_editor.cpp | 93 +++++++++++++++++++ modules/terrain_editor/terrain_mesh_editor.h | 26 ++++++ 3 files changed, 120 insertions(+) diff --git a/modules/terrain_editor/register_types.cpp b/modules/terrain_editor/register_types.cpp index d5a946a3..6b69a3c4 100644 --- a/modules/terrain_editor/register_types.cpp +++ b/modules/terrain_editor/register_types.cpp @@ -18,6 +18,7 @@ void initialize_terrain_editor_module(ModuleInitializationLevel p_level) { ClassDB::register_class(); ClassDB::register_class(); ClassDB::register_class(); + ClassDB::register_class(); } void uninitialize_terrain_editor_module(ModuleInitializationLevel p_level) { diff --git a/modules/terrain_editor/terrain_mesh_editor.cpp b/modules/terrain_editor/terrain_mesh_editor.cpp index 2eb7121e..a5b47241 100644 --- a/modules/terrain_editor/terrain_mesh_editor.cpp +++ b/modules/terrain_editor/terrain_mesh_editor.cpp @@ -1,20 +1,59 @@ #include "terrain_mesh_editor.h" +#include "core/input/input_event.h" +#include "core/io/file_access.h" +#include "core/io/resource_loader.h" +#include "core/io/resource_saver.h" +#include "core/os/keyboard.h" #include "scene/3d/node_3d.h" +#include "scene/gui/file_dialog.h" #include "scene/resources/packed_scene.h" #include "terrain_editor/macros.h" #include "terrain_editor/point_primitive_node.h" #include "terrain_editor/terrain_primitive.h" +void SaveData::_bind_methods() { + BIND_HPROPERTY(Variant::ARRAY, primitives, PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:TerrainPrimitive", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE)); +} + +void SaveData::write_to_file() { + ResourceSaver::save(this, this->save_file_path, ResourceSaver::FLAG_OMIT_EDITOR_PROPERTIES | ResourceSaver::FLAG_BUNDLE_RESOURCES); +} + +void SaveData::set_save_path(String value) { + this->save_file_path = value; +} + +String SaveData::get_save_path() const { + return this->save_file_path; +} + +void SaveData::set_primitives(Array array) { + this->primitives = array.duplicate(true); // do a full deep copy of the data, to avoid modification before saving +} + +Array SaveData::get_primitives() const { + return this->primitives; +} + void TerrainMeshEditor::_bind_methods() { BIND_HPROPERTY(Variant::OBJECT, point_primitive_object, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); + ClassDB::bind_method(D_METHOD("save_data"), &self_type::save_data); } void TerrainMeshEditor::ready() { connect(sig_primitive_list_changed, callable_mp(this, &self_type::on_primitive_list_changed)); on_primitive_list_changed(get_primitives()); + if (FileDialog * dialog{ memnew(FileDialog) }) { + this->file_dialog = dialog; + add_child(dialog); + dialog->set_filters({ "*.terrain.tres;World Resource Files;", "*.terrain.res;Binary World Resource Files;" }); + dialog->set_access(FileDialog::ACCESS_FILESYSTEM); + dialog->connect("file_selected", callable_mp(this, &self_type::on_save_file_selected)); + } } void TerrainMeshEditor::on_primitive_list_changed(Array primitives) { + this->out_of_date = true; for (Node3D *existing : this->primitive_nodes) { existing->queue_free(); } @@ -26,11 +65,34 @@ void TerrainMeshEditor::on_primitive_list_changed(Array primitives) { PointPrimitiveNode *primitive_node{ cast_to(this->point_primitive_object->instantiate()) }; primitive_node->set_primitive(point); this->add_child(primitive_node); + this->primitive_nodes.push_back(primitive_node); } } } } +void TerrainMeshEditor::on_save_file_selected(String path) { + switch (this->file_dialog->get_file_mode()) { + default: + print_error("Attempt to open or save with invalid file dialog mode"); + break; + case FileDialog::FILE_MODE_SAVE_FILE: + this->data->set_save_path(path); + this->data->set_primitives(get_primitives()); + this->data->write_to_file(); + break; + case FileDialog::FILE_MODE_OPEN_FILE: + Ref resource{ ResourceLoader::load(path, "SaveData", ResourceFormatLoader::CACHE_MODE_IGNORE_DEEP) }; + Ref loaded_data{ resource }; + if (loaded_data.is_valid()) { + this->data = loaded_data; + this->set_primitives(this->data->get_primitives().duplicate(true)); + this->data->set_save_path(path); + } + break; + } +} + void TerrainMeshEditor::_notification(int what) { if (Engine::get_singleton()->is_editor_hint()) { return; @@ -39,11 +101,42 @@ void TerrainMeshEditor::_notification(int what) { default: return; case NOTIFICATION_READY: + set_process_unhandled_input(true); ready(); return; } } +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)) { + 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) { + save_data(); + } else if (key.is_valid() && key->get_key_label() == Key::O && key->get_modifiers_mask() == KeyModifierMask::CTRL) { + 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(); + } +} + +void TerrainMeshEditor::save_data() { + // TODO: figure out if this is the correct way to see if the popup is popped up + if (this->file_dialog->is_visible()) { + return; + } + if (this->data->get_save_path().is_empty()) { + 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 { + this->data->set_primitives(get_primitives()); + this->data->write_to_file(); + } +} + void TerrainMeshEditor::set_point_primitive_object(Ref scene) { this->point_primitive_object = scene; } diff --git a/modules/terrain_editor/terrain_mesh_editor.h b/modules/terrain_editor/terrain_mesh_editor.h index 9fc2a14c..34cb3e29 100644 --- a/modules/terrain_editor/terrain_mesh_editor.h +++ b/modules/terrain_editor/terrain_mesh_editor.h @@ -1,20 +1,46 @@ #pragma once +#include "core/io/resource.h" +#include "scene/gui/file_dialog.h" #include "terrain_editor/terrain_mesh_generator.h" +class SaveData : public Resource { + GDCLASS(SaveData, Resource); + static void _bind_methods(); + +public: + void write_to_file(); + void set_save_path(String value); + String get_save_path() const; + void set_primitives(Array primitives); + Array get_primitives() const; + +private: + String save_file_path{}; + Array primitives{}; +}; + class TerrainMeshEditor : public TerrainMeshGenerator { GDCLASS(TerrainMeshEditor, TerrainMeshGenerator); static void _bind_methods(); void ready(); void on_primitive_list_changed(Array primitives); void on_primitive_node_removed(); + void on_save_file_selected(String path); protected: void _notification(int what); + virtual void unhandled_input(Ref const &event) override; + +public: + void save_data(); void set_point_primitive_object(Ref scene); Ref get_point_primitive_object() const; private: + Ref data{ memnew(SaveData) }; + FileDialog *file_dialog{}; + bool out_of_date{ false }; Vector primitive_nodes{}; Ref point_primitive_object{}; };