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