diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml index f7d4b1f7bd..3209f160e8 100644 --- a/doc/classes/EditorInterface.xml +++ b/doc/classes/EditorInterface.xml @@ -223,6 +223,13 @@ Returns the editor's [EditorResourcePreview] instance. + + + + Returns the editor's [ScenePaint2DEditor] instance. + [b]Warning:[/b] Removing and freeing this node will render a part of the editor useless and may cause a crash. + + diff --git a/doc/classes/ScenePaint2DEditor.xml b/doc/classes/ScenePaint2DEditor.xml new file mode 100644 index 0000000000..ad2b2e81e7 --- /dev/null +++ b/doc/classes/ScenePaint2DEditor.xml @@ -0,0 +1,46 @@ + + + + Class for interacting with the 2D editor's Scene Paint Mode. + + + This class is available only in the editor and cannot be instantiated. Access it using [method EditorInterface.get_scene_paint_2d]. + It provides methods for editor plugins to interact with the scene painting tool. + Plugins can register custom scene providers, and set the scene to be painted. + + + + + + + + Returns the painted scene, or [code]null[/code] if none is selected. The returned scene's properties can be edited. + [b]Warning:[/b] Removing and freeing this node will render the editor useless and may cause a crash. + + + + + + + + Registers a custom scene provider. + When the scene picker tool is active and the user clicks on [param control], the [param callback] is invoked and must return a [Node2D]. + The returned scene becomes the new painting sample. + + + + + + + Sets the scene sample to be painted. + + + + + + + Removes a previously registered scene provider. + + + + diff --git a/editor/editor_interface.cpp b/editor/editor_interface.cpp index 80324ff13c..bba6431b12 100644 --- a/editor/editor_interface.cpp +++ b/editor/editor_interface.cpp @@ -49,6 +49,7 @@ #include "editor/inspector/editor_resource_preview.h" #include "editor/inspector/property_selector.h" #include "editor/run/editor_run_bar.h" +#include "editor/scene/2d/scene_paint_2d_editor_plugin.h" #include "editor/scene/3d/node_3d_editor_plugin.h" #include "editor/scene/3d/node_3d_editor_viewport.h" #include "editor/scene/editor_scene_tabs.h" @@ -112,6 +113,10 @@ EditorUndoRedoManager *EditorInterface::get_editor_undo_redo() const { return EditorUndoRedoManager::get_singleton(); } +ScenePaint2DEditor *EditorInterface::get_scene_paint_2d() const { + return ScenePaint2DEditor::get_singleton(); +} + AABB EditorInterface::_calculate_aabb_for_scene(Node *p_node, AABB &p_scene_aabb) { MeshInstance3D *mesh_node = Object::cast_to(p_node); if (mesh_node && mesh_node->get_mesh().is_valid()) { @@ -855,6 +860,7 @@ void EditorInterface::_bind_methods() { ClassDB::bind_method(D_METHOD("get_editor_settings"), &EditorInterface::get_editor_settings); ClassDB::bind_method(D_METHOD("get_editor_toaster"), &EditorInterface::get_editor_toaster); ClassDB::bind_method(D_METHOD("get_editor_undo_redo"), &EditorInterface::get_editor_undo_redo); + ClassDB::bind_method(D_METHOD("get_scene_paint_2d"), &EditorInterface::get_scene_paint_2d); ClassDB::bind_method(D_METHOD("make_mesh_previews", "meshes", "preview_size"), &EditorInterface::_make_mesh_previews); diff --git a/editor/editor_interface.h b/editor/editor_interface.h index 9bb588f86b..32d03fb740 100644 --- a/editor/editor_interface.h +++ b/editor/editor_interface.h @@ -46,6 +46,7 @@ class EditorSelection; class EditorSettings; class EditorToaster; class EditorUndoRedoManager; +class ScenePaint2DEditor; class FileSystemDock; class Mesh; class Node; @@ -107,6 +108,7 @@ public: Ref get_editor_settings() const; EditorToaster *get_editor_toaster() const; EditorUndoRedoManager *get_editor_undo_redo() const; + ScenePaint2DEditor *get_scene_paint_2d() const; Vector> make_mesh_previews(const Vector> &p_meshes, Vector *p_transforms, int p_preview_size); void make_scene_preview(const String &p_path, Node *p_scene, int p_preview_size); diff --git a/editor/register_editor_types.cpp b/editor/register_editor_types.cpp index 4d79bece16..9ae12d09c6 100644 --- a/editor/register_editor_types.cpp +++ b/editor/register_editor_types.cpp @@ -153,6 +153,7 @@ void register_editor_types() { EditorStringNames::create(); GDREGISTER_CLASS(EditorPaths); + GDREGISTER_ABSTRACT_CLASS(ScenePaint2DEditor); GDREGISTER_VIRTUAL_CLASS(EditorPlugin); GDREGISTER_CLASS(EditorTranslationParserPlugin); GDREGISTER_CLASS(EditorImportPlugin); diff --git a/editor/scene/2d/scene_paint_2d_editor_plugin.cpp b/editor/scene/2d/scene_paint_2d_editor_plugin.cpp index 5a5f153714..1c1bbdc2b2 100644 --- a/editor/scene/2d/scene_paint_2d_editor_plugin.cpp +++ b/editor/scene/2d/scene_paint_2d_editor_plugin.cpp @@ -276,7 +276,7 @@ void ScenePaint2DEditor::_gui_input_viewport(const Ref &p_event) { if (mb.is_valid() && input_tool != INPUT_TOOL_PAN) { if (input_tool == INPUT_TOOL_PICK || input_tool == INPUT_TOOL_QUICK_PICK) { if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_CANVAS_ITEM); + callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_CANVAS_ITEM, (Control *)nullptr); } } else if (mb->is_pressed()) { if (mb->get_button_index() == MouseButton::LEFT) { @@ -468,7 +468,7 @@ void ScenePaint2DEditor::_file_system_input(const Ref &p_event) { Ref mb = p_event; if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_FILE_SYSTEM); + callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_FILE_SYSTEM, (Control *)nullptr); } } @@ -481,7 +481,7 @@ void ScenePaint2DEditor::_scene_tree_input(const Ref &p_event) { Ref mb = p_event; if (mb.is_valid() && mb->is_released() && mb->get_button_index() == MouseButton::LEFT) { - callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_SCENE_TREE); + callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_SCENE_TREE, (Control *)nullptr); } } @@ -493,7 +493,41 @@ void ScenePaint2DEditor::_recent_item_selected(int p_idx) { EditorSettings::get_singleton()->set_project_metadata("scene_paint_2d_editor", "recent_scenes", PackedStringArray()); return; } - callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_RECENT_LIST); + callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_RECENT_LIST, (Control *)nullptr); +} + +void ScenePaint2DEditor::_custom_source_input(const Ref &p_event, Control *p_control) { + if (input_tool != INPUT_TOOL_PICK || input_tool == INPUT_TOOL_QUICK_PICK) { + input_tool = INPUT_TOOL_NONE; + scene_picker_button->set_pressed(false); + return; + } + + Ref mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + callable_mp(this, &ScenePaint2DEditor::_update_scene_picker).call_deferred(PICK_CUSTOM_SOURCE, p_control); + } +} + +Node2D *ScenePaint2DEditor::_get_scene_from_path(const String &p_path) { + if (!ResourceLoader::exists(p_path, "PackedScene")) { + EditorNode::get_singleton()->show_warning(TTR("The selected scene could not be found. It may have been moved or deleted.")); + return nullptr; + } + + Ref scene = ResourceLoader::load(p_path); + if (scene.is_null()) { + return nullptr; + } + + Node *new_node = scene->instantiate(); + ERR_FAIL_NULL_V_EDMSG(new_node, nullptr, "The selected scene is invalid."); + Node2D *node_2d = Object::cast_to(new_node); + if (!node_2d) { + new_node->queue_free(); + return nullptr; + } + return node_2d; } bool ScenePaint2DEditor::_is_selected_scene_valid(Node2D *p_node) const { @@ -560,7 +594,7 @@ void ScenePaint2DEditor::_scene_changed() { _update_node(); } -void ScenePaint2DEditor::_update_scene_picker(int p_mode) { +void ScenePaint2DEditor::_update_scene_picker(int p_mode, Control *p_control) { if (!is_tool_selected) { return; } @@ -569,23 +603,7 @@ void ScenePaint2DEditor::_update_scene_picker(int p_mode) { PickMode pick_mode = (PickMode)p_mode; switch (pick_mode) { case PICK_FILE_SYSTEM: { - String scene_path = FileSystemDock::get_singleton()->get_current_path(); - Ref scene = ResourceLoader::load(scene_path); - if (scene.is_null()) { - return; - } - Ref scene_state = scene->get_state(); - String type; - while (scene_state.is_valid() && type.is_empty()) { - ERR_FAIL_COND(scene_state->get_node_count() < 1); - type = scene_state->get_node_type(0); - scene_state = scene_state->get_base_scene_state(); - } - ERR_FAIL_COND_EDMSG(type.is_empty(), "The selected scene is invalid."); - bool extends_current_class = ClassDB::is_parent_class(type, "Node2D"); - if (scene.is_valid() && extends_current_class) { - node_2d = Object::cast_to(scene->instantiate()); - } + node_2d = _get_scene_from_path(FileSystemDock::get_singleton()->get_current_path()); } break; case PICK_SCENE_TREE: { node_2d = Object::cast_to(SceneTreeDock::get_singleton()->get_tree_editor()->get_selected()); @@ -606,21 +624,26 @@ void ScenePaint2DEditor::_update_scene_picker(int p_mode) { } break; case PICK_RECENT_LIST: { String scene_path = recent_scenes_button->get_item_metadata(recent_idx); - if (!ResourceLoader::exists(scene_path)) { - EditorNode::get_singleton()->show_accept( - TTR("The selected scene could not be found. It may have been moved or deleted."), - TTR("OK")); + node_2d = _get_scene_from_path(scene_path); + if (!node_2d) { PackedStringArray rc = EditorSettings::get_singleton()->get_project_metadata("scene_paint_2d_editor", "recent_scenes", PackedStringArray()); rc.erase(scene_path); EditorSettings::get_singleton()->set_project_metadata("scene_paint_2d_editor", "recent_scenes", rc); callable_mp(this, &ScenePaint2DEditor::_update_recent_scenes).call_deferred(); return; } - Ref scene = ResourceLoader::load(scene_path); - if (scene.is_valid()) { - node_2d = Object::cast_to(scene->instantiate()); + } break; + case PICK_CUSTOM_SOURCE: { + Callable callback = custom_sources[p_control]; + Callable::CallError ce; + Variant ret; + const Variant *args[1]; + callback.callp(args, 0, ret, ce); + if (ce.error != Callable::CallError::CALL_OK) { + ERR_PRINT(vformat("Error calling scene provider callback: %s", Variant::get_callable_error_text(callback, args, 1, ce))); } - } + node_2d = Object::cast_to(ret.get_validated_object()); + } break; } if (_is_selected_scene_valid(node_2d)) { @@ -707,6 +730,13 @@ void ScenePaint2DEditor::_grid_step_changed() { grid_step = CanvasItemEditor::get_singleton()->get_grid_step(); } +void ScenePaint2DEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("register_scene_provider", "control", "callback"), &ScenePaint2DEditor::register_scene_provider); + ClassDB::bind_method(D_METHOD("unregister_scene_provider", "control"), &ScenePaint2DEditor::unregister_scene_provider); + ClassDB::bind_method(D_METHOD("set_painted_scene", "scene"), &ScenePaint2DEditor::set_painted_scene); + ClassDB::bind_method(D_METHOD("get_painted_scene"), &ScenePaint2DEditor::get_painted_scene); +} + void ScenePaint2DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_TRANSLATION_CHANGED: { @@ -812,7 +842,33 @@ void ScenePaint2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { } } +void ScenePaint2DEditor::register_scene_provider(Control *p_control, const Callable &p_callback) { + ERR_FAIL_COND_MSG(custom_sources.has(p_control), "Provider already registered."); + custom_sources[p_control] = p_callback; + p_control->connect(SceneStringName(gui_input), callable_mp(this, &ScenePaint2DEditor::_custom_source_input).bind(p_control)); +} + +void ScenePaint2DEditor::unregister_scene_provider(Control *p_control) { + ERR_FAIL_COND_MSG(!custom_sources.has(p_control), "Provider not found."); + custom_sources.erase(p_control); + p_control->disconnect(SceneStringName(gui_input), callable_mp(this, &ScenePaint2DEditor::_custom_source_input)); +} + +void ScenePaint2DEditor::set_painted_scene(Node2D *p_scene) { + if (_is_selected_scene_valid(p_scene)) { + _set_picked_scene(p_scene); + } else { + _set_picked_scene(nullptr); + } +} + +Node2D *ScenePaint2DEditor::get_painted_scene() const { + return instance; +} + ScenePaint2DEditor::ScenePaint2DEditor() { + singleton = this; + toolbar = memnew(HBoxContainer); CanvasItemEditor *canvas_item_editor = CanvasItemEditor::get_singleton(); canvas_item_editor->add_control_to_menu_panel(toolbar); diff --git a/editor/scene/2d/scene_paint_2d_editor_plugin.h b/editor/scene/2d/scene_paint_2d_editor_plugin.h index f513d95753..a2a6e8ae60 100644 --- a/editor/scene/2d/scene_paint_2d_editor_plugin.h +++ b/editor/scene/2d/scene_paint_2d_editor_plugin.h @@ -43,6 +43,8 @@ class OptionButton; class ScenePaint2DEditor : public Control { GDCLASS(ScenePaint2DEditor, Control); + inline static ScenePaint2DEditor *singleton = nullptr; + friend class ScenePaint2DEditorPlugin; enum PaintMode { @@ -56,6 +58,7 @@ class ScenePaint2DEditor : public Control { PICK_SCENE_TREE, PICK_CANVAS_ITEM, PICK_RECENT_LIST, + PICK_CUSTOM_SOURCE, }; enum InputTool { @@ -109,6 +112,8 @@ class ScenePaint2DEditor : public Control { Node2D *selected_scene = nullptr; + HashMap custom_sources; + void _can_handle(bool p_is_node_2d, bool p_edit); void _edit(Object *p_object); @@ -135,10 +140,12 @@ class ScenePaint2DEditor : public Control { void _scene_changed(); void _scene_picker_toggled(bool p_pressed); - void _update_scene_picker(int p_mode); + void _update_scene_picker(int p_mode, Control *p_control); void _file_system_input(const Ref &p_event); void _scene_tree_input(const Ref &p_event); void _recent_item_selected(int p_idx); + void _custom_source_input(const Ref &p_event, Control *p_control); + Node2D *_get_scene_from_path(const String &p_path); void _set_picked_scene(Node2D *p_scene); void _add_to_recent_scenes(const String &p_scene); @@ -160,11 +167,20 @@ class ScenePaint2DEditor : public Control { void _grid_step_changed(); protected: + static void _bind_methods(); void _notification(int p_what); public: + static ScenePaint2DEditor *get_singleton() { return singleton; } + void forward_canvas_draw_over_viewport(Control *p_overlay); + void register_scene_provider(Control *p_control, const Callable &p_callback); + void unregister_scene_provider(Control *p_control); + + void set_painted_scene(Node2D *p_scene); + Node2D *get_painted_scene() const; + ScenePaint2DEditor(); };