Merge pull request #119052 from DexterFstone/Expose-ScenePaint2DEditor

Expose `ScenePaint2DEditor`
This commit is contained in:
Thaddeus Crews
2026-06-24 10:16:13 -05:00
7 changed files with 165 additions and 31 deletions
+7
View File
@@ -223,6 +223,13 @@
Returns the editor's [EditorResourcePreview] instance.
</description>
</method>
<method name="get_scene_paint_2d" qualifiers="const">
<return type="ScenePaint2DEditor" />
<description>
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.
</description>
</method>
<method name="get_script_editor" qualifiers="const">
<return type="ScriptEditor" />
<description>
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="ScenePaint2DEditor" inherits="Control" api_type="editor" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
Class for interacting with the 2D editor's Scene Paint Mode.
</brief_description>
<description>
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.
</description>
<tutorials>
</tutorials>
<methods>
<method name="get_painted_scene" qualifiers="const">
<return type="Node2D" />
<description>
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.
</description>
</method>
<method name="register_scene_provider">
<return type="void" />
<param index="0" name="control" type="Control" />
<param index="1" name="callback" type="Callable" />
<description>
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.
</description>
</method>
<method name="set_painted_scene">
<return type="void" />
<param index="0" name="scene" type="Node2D" />
<description>
Sets the scene sample to be painted.
</description>
</method>
<method name="unregister_scene_provider">
<return type="void" />
<param index="0" name="control" type="Control" />
<description>
Removes a previously registered scene provider.
</description>
</method>
</methods>
</class>
+6
View File
@@ -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<MeshInstance3D>(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);
+2
View File
@@ -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<EditorSettings> get_editor_settings() const;
EditorToaster *get_editor_toaster() const;
EditorUndoRedoManager *get_editor_undo_redo() const;
ScenePaint2DEditor *get_scene_paint_2d() const;
Vector<Ref<Texture2D>> make_mesh_previews(const Vector<Ref<Mesh>> &p_meshes, Vector<Transform3D> *p_transforms, int p_preview_size);
void make_scene_preview(const String &p_path, Node *p_scene, int p_preview_size);
+1
View File
@@ -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);
@@ -276,7 +276,7 @@ void ScenePaint2DEditor::_gui_input_viewport(const Ref<InputEvent> &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<InputEvent> &p_event) {
Ref<InputEventMouseButton> 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<InputEvent> &p_event) {
Ref<InputEventMouseButton> 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<InputEvent> &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<InputEventMouseButton> 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<PackedScene> 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<Node2D>(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<PackedScene> scene = ResourceLoader::load(scene_path);
if (scene.is_null()) {
return;
}
Ref<SceneState> 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<Node2D>(scene->instantiate());
}
node_2d = _get_scene_from_path(FileSystemDock::get_singleton()->get_current_path());
} break;
case PICK_SCENE_TREE: {
node_2d = Object::cast_to<Node2D>(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<PackedScene> scene = ResourceLoader::load(scene_path);
if (scene.is_valid()) {
node_2d = Object::cast_to<Node2D>(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<Node2D>(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);
+17 -1
View File
@@ -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<Control *, Callable> 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<InputEvent> &p_event);
void _scene_tree_input(const Ref<InputEvent> &p_event);
void _recent_item_selected(int p_idx);
void _custom_source_input(const Ref<InputEvent> &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();
};