3838#include " editor/debugger/editor_debugger_node.h"
3939#include " editor/doc/editor_help.h"
4040#include " editor/docks/filesystem_dock.h"
41+ #include " editor/editor_interface.h"
4142#include " editor/editor_node.h"
4243#include " editor/editor_string_names.h"
4344#include " editor/gui/editor_toaster.h"
4445#include " editor/inspector/editor_context_menu_plugin.h"
46+ #include " editor/inspector/editor_inspector.h"
47+ #include " editor/inspector/multi_node_edit.h"
4548#include " editor/settings/editor_command_palette.h"
4649#include " editor/settings/editor_settings.h"
4750#include " editor/themes/editor_scale.h"
@@ -145,6 +148,10 @@ void ScriptTextEditor::apply_code() {
145148 }
146149 script->set_source_code (code_editor->get_text_editor ()->get_text ());
147150 script->update_exports ();
151+ if (!pending_dragged_exports.is_empty ()) {
152+ _assign_dragged_export_variables ();
153+ }
154+
148155 code_editor->get_text_editor ()->get_syntax_highlighter ()->update_cache ();
149156}
150157
@@ -868,6 +875,10 @@ void ScriptTextEditor::_validate_script() {
868875 _update_errors ();
869876 _update_background_color ();
870877
878+ if (!pending_dragged_exports.is_empty ()) {
879+ _assign_dragged_export_variables ();
880+ }
881+
871882 emit_signal (SNAME (" name_changed" ));
872883 emit_signal (SNAME (" edited_script_changed" ));
873884}
@@ -2198,7 +2209,7 @@ static String _quote_drop_data(const String &str) {
21982209 return escaped.quote (using_single_quotes ? " '" : " \" " );
21992210}
22002211
2201- static String _get_dropped_resource_line (const Ref<Resource> &p_resource, bool p_create_field, bool p_allow_uid) {
2212+ static String _get_dropped_resource_as_member (const Ref<Resource> &p_resource, bool p_create_field, bool p_allow_uid) {
22022213 String path = p_resource->get_path ();
22032214 if (p_allow_uid) {
22042215 ResourceUID::ID id = ResourceLoader::get_resource_uid (path);
@@ -2225,6 +2236,30 @@ static String _get_dropped_resource_line(const Ref<Resource> &p_resource, bool p
22252236 return vformat (" const %s = preload(%s)" , variable_name, _quote_drop_data (path));
22262237}
22272238
2239+ String ScriptTextEditor::_get_dropped_resource_as_exported_member (const Ref<Resource> &p_resource, const Vector<ObjectID> &p_script_instance_obj_ids) {
2240+ String variable_name = p_resource->get_name ();
2241+ if (variable_name.is_empty ()) {
2242+ variable_name = p_resource->get_path ().get_file ().get_basename ();
2243+ }
2244+
2245+ variable_name = variable_name.to_snake_case ().validate_unicode_identifier ();
2246+ for (ObjectID obj_id : p_script_instance_obj_ids) {
2247+ pending_dragged_exports.push_back (DraggedExport{ obj_id, variable_name, p_resource });
2248+ }
2249+
2250+ StringName class_name = p_resource->get_class ();
2251+ Ref<Script> resource_script = p_resource->get_script ();
2252+
2253+ if (resource_script.is_valid ()) {
2254+ StringName global_resource_script_name = resource_script->get_global_name ();
2255+ if (!global_resource_script_name.is_empty ()) {
2256+ class_name = global_resource_script_name;
2257+ }
2258+ }
2259+
2260+ return vformat (" @export var %s: %s" , variable_name, class_name);
2261+ }
2262+
22282263void ScriptTextEditor::drop_data_fw (const Point2 &p_point, const Variant &p_data, Control *p_from) {
22292264 Dictionary d = p_data;
22302265
@@ -2242,7 +2277,11 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
22422277 is_empty_line = drop_at_column <= te->get_first_non_whitespace_column (drop_at_line) && te->get_selection_to_column (selection_index) == te->get_line (te->get_selection_to_line (selection_index)).length ();
22432278 }
22442279
2245- const bool drop_modifier_pressed = Input::get_singleton ()->is_key_pressed (Key::CMD_OR_CTRL);
2280+ Node *scene_root = get_tree ()->get_edited_scene_root ();
2281+
2282+ const bool member_drop_modifier_pressed = Input::get_singleton ()->is_key_pressed (Key::CMD_OR_CTRL);
2283+ const bool export_drop_modifier_pressed = Input::get_singleton ()->is_key_pressed (Key::ALT);
2284+
22462285 const bool allow_uid = Input::get_singleton ()->is_key_pressed (Key::SHIFT) != bool (EDITOR_GET (" text_editor/behavior/files/drop_preload_resources_as_uid" ));
22472286 const String &line = te->get_line (drop_at_line);
22482287
@@ -2267,31 +2306,45 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
22672306 return ;
22682307 }
22692308
2270- if (drop_modifier_pressed ) {
2309+ if (member_drop_modifier_pressed ) {
22712310 if (resource->is_built_in ()) {
22722311 String warning = TTR (" Preloading internal resources is not supported." );
22732312 EditorToaster::get_singleton ()->popup_str (warning, EditorToaster::SEVERITY_ERROR);
22742313 } else {
2275- text_to_drop = _get_dropped_resource_line (resource, is_empty_line, allow_uid);
2314+ text_to_drop = _get_dropped_resource_as_member (resource, is_empty_line, allow_uid);
22762315 }
2316+ } else if (export_drop_modifier_pressed) {
2317+ Vector<ObjectID> obj_ids = _get_objects_for_export_assignment ();
2318+ text_to_drop = _get_dropped_resource_as_exported_member (resource, obj_ids);
2319+
22772320 } else {
22782321 text_to_drop = _quote_drop_data (path);
22792322 }
2323+
2324+ if (is_empty_line) {
2325+ text_to_drop += " \n " ;
2326+ }
22802327 }
22812328
22822329 if (type == " files" || type == " files_and_dirs" ) {
22832330 const PackedStringArray files = d[" files" ];
22842331 PackedStringArray parts;
22852332
22862333 for (const String &path : files) {
2287- if (drop_modifier_pressed && ResourceLoader::exists (path)) {
2334+ if ((member_drop_modifier_pressed || export_drop_modifier_pressed) && ResourceLoader::exists (path)) {
22882335 Ref<Resource> resource = ResourceLoader::load (path);
22892336 if (resource.is_null ()) {
22902337 // Resource exists, but failed to load. We need only path and name, so we can use a dummy Resource instead.
22912338 resource.instantiate ();
22922339 resource->set_path_cache (path);
22932340 }
2294- parts.append (_get_dropped_resource_line (resource, is_empty_line, allow_uid));
2341+
2342+ if (member_drop_modifier_pressed) {
2343+ parts.append (_get_dropped_resource_as_member (resource, is_empty_line, allow_uid));
2344+ } else if (export_drop_modifier_pressed) {
2345+ Vector<ObjectID> obj_ids = _get_objects_for_export_assignment ();
2346+ parts.append (_get_dropped_resource_as_exported_member (resource, obj_ids));
2347+ }
22952348 } else {
22962349 parts.append (_quote_drop_data (path));
22972350 }
@@ -2314,7 +2367,6 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
23142367 }
23152368
23162369 if (type == " nodes" ) {
2317- Node *scene_root = get_tree ()->get_edited_scene_root ();
23182370 if (!scene_root) {
23192371 EditorNode::get_singleton ()->show_warning (TTR (" Can't drop nodes without an open scene." ));
23202372 return ;
@@ -2332,7 +2384,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
23322384
23332385 Array nodes = d[" nodes" ];
23342386
2335- if (drop_modifier_pressed ) {
2387+ if (member_drop_modifier_pressed ) {
23362388 const bool use_type = EDITOR_GET (" text_editor/completion/add_type_hints" );
23372389 add_new_line = !is_empty_line && drop_at_column != 0 ;
23382390
@@ -2358,7 +2410,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
23582410 Ref<Script> node_script = node->get_script ();
23592411 if (node_script.is_valid ()) {
23602412 StringName global_node_script_name = node_script->get_global_name ();
2361- if (global_node_script_name != StringName ()) {
2413+ if (!global_node_script_name. is_empty ()) {
23622414 class_name = global_node_script_name;
23632415 }
23642416 }
@@ -2374,6 +2426,31 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
23742426 if (is_empty_line || drop_at_column == 0 ) {
23752427 text_to_drop += " \n " ;
23762428 }
2429+ } else if (export_drop_modifier_pressed) {
2430+ Vector<ObjectID> obj_ids = _get_objects_for_export_assignment ();
2431+
2432+ for (int i = 0 ; i < nodes.size (); i++) {
2433+ NodePath np = nodes[i];
2434+ Node *node = get_node (np);
2435+ if (!node) {
2436+ continue ;
2437+ }
2438+
2439+ String variable_name = String (node->get_name ()).to_snake_case ().validate_unicode_identifier ();
2440+ StringName class_name = node->get_class_name ();
2441+ Ref<Script> node_script = node->get_script ();
2442+ if (node_script.is_valid ()) {
2443+ StringName global_node_script_name = node_script->get_global_name ();
2444+ if (!global_node_script_name.is_empty ()) {
2445+ class_name = global_node_script_name;
2446+ }
2447+ }
2448+
2449+ text_to_drop += vformat (" @export var %s: %s\n " , variable_name, class_name);
2450+ for (ObjectID obj_id : obj_ids) {
2451+ pending_dragged_exports.push_back (DraggedExport{ obj_id, variable_name, node });
2452+ }
2453+ }
23772454 } else {
23782455 for (int i = 0 ; i < nodes.size (); i++) {
23792456 if (i > 0 ) {
@@ -2431,6 +2508,80 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
24312508 te->grab_focus ();
24322509}
24332510
2511+ Vector<ObjectID> ScriptTextEditor::_get_objects_for_export_assignment () const {
2512+ Vector<ObjectID> objects;
2513+ Node *scene_root = get_tree ()->get_edited_scene_root ();
2514+ bool assign_export_variables = scene_root && ClassDB::is_parent_class (script->get_instance_base_type (), " Node" );
2515+
2516+ if (!assign_export_variables) {
2517+ return objects;
2518+ }
2519+
2520+ EditorInspector *inspector = EditorInterface::get_singleton ()->get_inspector ();
2521+ if (inspector) {
2522+ Object *edited_object = inspector->get_edited_object ();
2523+ Node *node_edit = Object::cast_to<Node>(edited_object);
2524+ MultiNodeEdit *multi_node_edit = Object::cast_to<MultiNodeEdit>(edited_object);
2525+
2526+ if (node_edit != nullptr ) {
2527+ if (node_edit->get_script () == script) {
2528+ objects.push_back (node_edit->get_instance_id ());
2529+ }
2530+ } else if (multi_node_edit != nullptr ) {
2531+ Node *es = EditorNode::get_singleton ()->get_edited_scene ();
2532+ for (int i = 0 ; i < multi_node_edit->get_node_count (); i++) {
2533+ NodePath np = multi_node_edit->get_node (i);
2534+ Node *node = es->get_node (np);
2535+ if (node->get_script () == script) {
2536+ objects.push_back (node->get_instance_id ());
2537+ }
2538+ }
2539+ }
2540+ }
2541+
2542+ // In case there is no current editor selection/editor selection does not contain this script,
2543+ // it often still makes sense to try to assign the export variable,
2544+ // so we default to the first node with the script we find in the scene.
2545+ if (objects.is_empty ()) {
2546+ Node *sn = _find_script_node (scene_root, script);
2547+ if (sn) {
2548+ objects.push_back (sn->get_instance_id ());
2549+ }
2550+ }
2551+
2552+ return objects;
2553+ }
2554+
2555+ void ScriptTextEditor::_assign_dragged_export_variables () {
2556+ ERR_FAIL_COND (pending_dragged_exports.is_empty ());
2557+
2558+ bool export_variable_set = false ;
2559+ for (const DraggedExport &dragged_export : pending_dragged_exports) {
2560+ Object *obj = ObjectDB::get_instance (dragged_export.obj_id );
2561+ if (!obj) {
2562+ WARN_PRINT (" Object not found, can't assign export variable." );
2563+ continue ;
2564+ }
2565+
2566+ ScriptInstance *si = obj->get_script_instance ();
2567+ if (!si) {
2568+ WARN_PRINT (" Script on " + obj->to_string () + " does not exist anymore, can't assign export variable." );
2569+ continue ;
2570+ }
2571+
2572+ bool success = si->set (dragged_export.variable_name , dragged_export.value );
2573+ if (success) {
2574+ export_variable_set = true ;
2575+ }
2576+ }
2577+
2578+ if (export_variable_set) {
2579+ EditorInterface::get_singleton ()->mark_scene_as_unsaved ();
2580+ }
2581+
2582+ pending_dragged_exports.clear ();
2583+ }
2584+
24342585void ScriptTextEditor::_text_edit_gui_input (const Ref<InputEvent> &ev) {
24352586 Ref<InputEventMouseButton> mb = ev;
24362587 Ref<InputEventKey> k = ev;
0 commit comments