From 2ad3d1bd7a77f5f0318645f9c2d36a3b29bfee88 Mon Sep 17 00:00:00 2001 From: Aaron Franke Date: Thu, 11 Nov 2021 23:48:34 -0600 Subject: Make OpenSimplex and VisualScript modules not depend on the editor --- .../visual_script/editor/visual_script_editor.cpp | 4612 ++++++++++++++++++++ 1 file changed, 4612 insertions(+) create mode 100644 modules/visual_script/editor/visual_script_editor.cpp (limited to 'modules/visual_script/editor/visual_script_editor.cpp') diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp new file mode 100644 index 0000000000..6fa43636de --- /dev/null +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -0,0 +1,4612 @@ +/*************************************************************************/ +/* visual_script_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "visual_script_editor.h" + +#include "../visual_script_expression.h" +#include "../visual_script_flow_control.h" +#include "../visual_script_func_nodes.h" +#include "../visual_script_nodes.h" +#include "core/input/input.h" +#include "core/object/class_db.h" +#include "core/object/script_language.h" +#include "core/os/keyboard.h" +#include "core/variant/variant.h" +#include "editor/editor_node.h" +#include "editor/editor_resource_preview.h" +#include "editor/editor_scale.h" +#include "scene/main/window.h" + +#ifdef TOOLS_ENABLED +class VisualScriptEditorSignalEdit : public Object { + GDCLASS(VisualScriptEditorSignalEdit, Object); + + StringName sig; + +public: + UndoRedo *undo_redo; + Ref script; + +protected: + static void _bind_methods() { + ClassDB::bind_method("_sig_changed", &VisualScriptEditorSignalEdit::_sig_changed); + ADD_SIGNAL(MethodInfo("changed")); + } + + void _sig_changed() { + notify_property_list_changed(); + emit_signal(SNAME("changed")); + } + + bool _set(const StringName &p_name, const Variant &p_value) { + if (sig == StringName()) { + return false; + } + + if (p_name == "argument_count") { + int new_argc = p_value; + int argc = script->custom_signal_get_argument_count(sig); + if (argc == new_argc) { + return true; + } + + undo_redo->create_action(TTR("Change Signal Arguments")); + + if (new_argc < argc) { + for (int i = new_argc; i < argc; i++) { + undo_redo->add_do_method(script.ptr(), "custom_signal_remove_argument", sig, new_argc); + undo_redo->add_undo_method(script.ptr(), "custom_signal_add_argument", sig, script->custom_signal_get_argument_name(sig, i), script->custom_signal_get_argument_type(sig, i), -1); + } + } else if (new_argc > argc) { + for (int i = argc; i < new_argc; i++) { + undo_redo->add_do_method(script.ptr(), "custom_signal_add_argument", sig, Variant::NIL, "arg" + itos(i + 1), -1); + undo_redo->add_undo_method(script.ptr(), "custom_signal_remove_argument", sig, argc); + } + } + + undo_redo->add_do_method(this, "_sig_changed"); + undo_redo->add_undo_method(this, "_sig_changed"); + + undo_redo->commit_action(); + + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/", 1).to_int() - 1; + ERR_FAIL_INDEX_V(idx, script->custom_signal_get_argument_count(sig), false); + String what = String(p_name).get_slice("/", 2); + if (what == "type") { + int old_type = script->custom_signal_get_argument_type(sig, idx); + int new_type = p_value; + undo_redo->create_action(TTR("Change Argument Type")); + undo_redo->add_do_method(script.ptr(), "custom_signal_set_argument_type", sig, idx, new_type); + undo_redo->add_undo_method(script.ptr(), "custom_signal_set_argument_type", sig, idx, old_type); + undo_redo->commit_action(); + + return true; + } + + if (what == "name") { + String old_name = script->custom_signal_get_argument_name(sig, idx); + String new_name = p_value; + undo_redo->create_action(TTR("Change Argument name")); + undo_redo->add_do_method(script.ptr(), "custom_signal_set_argument_name", sig, idx, new_name); + undo_redo->add_undo_method(script.ptr(), "custom_signal_set_argument_name", sig, idx, old_name); + undo_redo->commit_action(); + return true; + } + } + + return false; + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + if (sig == StringName()) { + return false; + } + + if (p_name == "argument_count") { + r_ret = script->custom_signal_get_argument_count(sig); + return true; + } + if (String(p_name).begins_with("argument/")) { + int idx = String(p_name).get_slice("/", 1).to_int() - 1; + ERR_FAIL_INDEX_V(idx, script->custom_signal_get_argument_count(sig), false); + String what = String(p_name).get_slice("/", 2); + if (what == "type") { + r_ret = script->custom_signal_get_argument_type(sig, idx); + return true; + } + if (what == "name") { + r_ret = script->custom_signal_get_argument_name(sig, idx); + return true; + } + } + + return false; + } + void _get_property_list(List *p_list) const { + if (sig == StringName()) { + return; + } + + p_list->push_back(PropertyInfo(Variant::INT, "argument_count", PROPERTY_HINT_RANGE, "0,256")); + String argt = "Variant"; + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + argt += "," + Variant::get_type_name(Variant::Type(i)); + } + + for (int i = 0; i < script->custom_signal_get_argument_count(sig); i++) { + p_list->push_back(PropertyInfo(Variant::INT, "argument/" + itos(i + 1) + "/type", PROPERTY_HINT_ENUM, argt)); + p_list->push_back(PropertyInfo(Variant::STRING, "argument/" + itos(i + 1) + "/name")); + } + } + +public: + void edit(const StringName &p_sig) { + sig = p_sig; + notify_property_list_changed(); + } + + VisualScriptEditorSignalEdit() { undo_redo = nullptr; } +}; + +class VisualScriptEditorVariableEdit : public Object { + GDCLASS(VisualScriptEditorVariableEdit, Object); + + StringName var; + +public: + UndoRedo *undo_redo; + Ref script; + +protected: + static void _bind_methods() { + ClassDB::bind_method("_var_changed", &VisualScriptEditorVariableEdit::_var_changed); + ClassDB::bind_method("_var_value_changed", &VisualScriptEditorVariableEdit::_var_value_changed); + ADD_SIGNAL(MethodInfo("changed")); + } + + void _var_changed() { + notify_property_list_changed(); + emit_signal(SNAME("changed")); + } + void _var_value_changed() { + emit_signal(SNAME("changed")); + } + + bool _set(const StringName &p_name, const Variant &p_value) { + if (var == StringName()) { + return false; + } + + if (String(p_name) == "value") { + undo_redo->create_action(TTR("Set Variable Default Value")); + Variant current = script->get_variable_default_value(var); + undo_redo->add_do_method(script.ptr(), "set_variable_default_value", var, p_value); + undo_redo->add_undo_method(script.ptr(), "set_variable_default_value", var, current); + undo_redo->add_do_method(this, "_var_value_changed"); + undo_redo->add_undo_method(this, "_var_value_changed"); + undo_redo->commit_action(); + return true; + } + + Dictionary d = script->call("get_variable_info", var); + + if (String(p_name) == "type") { + Dictionary dc = d.duplicate(); + dc["type"] = p_value; + undo_redo->create_action(TTR("Set Variable Type")); + undo_redo->add_do_method(script.ptr(), "set_variable_info", var, dc); + undo_redo->add_undo_method(script.ptr(), "set_variable_info", var, d); + + // Setting the default value. + Variant::Type type = (Variant::Type)(int)p_value; + if (type != Variant::NIL) { + Variant default_value; + Callable::CallError ce; + Variant::construct(type, default_value, nullptr, 0, ce); + if (ce.error == Callable::CallError::CALL_OK) { + undo_redo->add_do_method(script.ptr(), "set_variable_default_value", var, default_value); + undo_redo->add_undo_method(script.ptr(), "set_variable_default_value", var, dc["value"]); + } + } + + undo_redo->add_do_method(this, "_var_changed"); + undo_redo->add_undo_method(this, "_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name) == "hint") { + Dictionary dc = d.duplicate(); + dc["hint"] = p_value; + undo_redo->create_action(TTR("Set Variable Type")); + undo_redo->add_do_method(script.ptr(), "set_variable_info", var, dc); + undo_redo->add_undo_method(script.ptr(), "set_variable_info", var, d); + undo_redo->add_do_method(this, "_var_changed"); + undo_redo->add_undo_method(this, "_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name) == "hint_string") { + Dictionary dc = d.duplicate(); + dc["hint_string"] = p_value; + undo_redo->create_action(TTR("Set Variable Type")); + undo_redo->add_do_method(script.ptr(), "set_variable_info", var, dc); + undo_redo->add_undo_method(script.ptr(), "set_variable_info", var, d); + undo_redo->add_do_method(this, "_var_changed"); + undo_redo->add_undo_method(this, "_var_changed"); + undo_redo->commit_action(); + return true; + } + + if (String(p_name) == "export") { + script->set_variable_export(var, p_value); + EditorNode::get_singleton()->get_inspector()->update_tree(); + return true; + } + + return false; + } + + bool _get(const StringName &p_name, Variant &r_ret) const { + if (var == StringName()) { + return false; + } + + if (String(p_name) == "value") { + r_ret = script->get_variable_default_value(var); + return true; + } + + PropertyInfo pinfo = script->get_variable_info(var); + + if (String(p_name) == "type") { + r_ret = pinfo.type; + return true; + } + if (String(p_name) == "hint") { + r_ret = pinfo.hint; + return true; + } + if (String(p_name) == "hint_string") { + r_ret = pinfo.hint_string; + return true; + } + + if (String(p_name) == "export") { + r_ret = script->get_variable_export(var); + return true; + } + + return false; + } + void _get_property_list(List *p_list) const { + if (var == StringName()) { + return; + } + + String argt = "Variant"; + for (int i = 1; i < Variant::VARIANT_MAX; i++) { + argt += "," + Variant::get_type_name(Variant::Type(i)); + } + p_list->push_back(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, argt)); + p_list->push_back(PropertyInfo(script->get_variable_info(var).type, "value", script->get_variable_info(var).hint, script->get_variable_info(var).hint_string, PROPERTY_USAGE_DEFAULT)); + // Update this when PropertyHint changes. + p_list->push_back(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,Flags,Layers2dRender,Layers2dPhysics,Layer3dRender,Layer3dPhysics,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText,PlaceholderText,ColorNoAlpha,ImageCompressLossy,ImageCompressLossLess,ObjectId,String,NodePathToEditedNode,MethodOfVariantType,MethodOfBaseType,MethodOfInstance,MethodOfScript,PropertyOfVariantType,PropertyOfBaseType,PropertyOfInstance,PropertyOfScript,ObjectTooBig,NodePathValidTypes")); + p_list->push_back(PropertyInfo(Variant::STRING, "hint_string")); + p_list->push_back(PropertyInfo(Variant::BOOL, "export")); + } + +public: + void edit(const StringName &p_var) { + var = p_var; + notify_property_list_changed(); + } + + VisualScriptEditorVariableEdit() { undo_redo = nullptr; } +}; + +static Color _color_from_type(Variant::Type p_type, bool dark_theme = true) { + Color color; + if (dark_theme) { + switch (p_type) { + case Variant::NIL: + color = Color(0.41, 0.93, 0.74); + break; + + case Variant::BOOL: + color = Color(0.55, 0.65, 0.94); + break; + case Variant::INT: + color = Color(0.49, 0.78, 0.94); + break; + case Variant::FLOAT: + color = Color(0.38, 0.85, 0.96); + break; + case Variant::STRING: + color = Color(0.42, 0.65, 0.93); + break; + + case Variant::VECTOR2: + color = Color(0.74, 0.57, 0.95); + break; + case Variant::VECTOR2I: + color = Color(0.74, 0.57, 0.95); + break; + case Variant::RECT2: + color = Color(0.95, 0.57, 0.65); + break; + case Variant::RECT2I: + color = Color(0.95, 0.57, 0.65); + break; + case Variant::VECTOR3: + color = Color(0.84, 0.49, 0.93); + break; + case Variant::VECTOR3I: + color = Color(0.84, 0.49, 0.93); + break; + case Variant::TRANSFORM2D: + color = Color(0.77, 0.93, 0.41); + break; + case Variant::PLANE: + color = Color(0.97, 0.44, 0.44); + break; + case Variant::QUATERNION: + color = Color(0.93, 0.41, 0.64); + break; + case Variant::AABB: + color = Color(0.93, 0.47, 0.57); + break; + case Variant::BASIS: + color = Color(0.89, 0.93, 0.41); + break; + case Variant::TRANSFORM3D: + color = Color(0.96, 0.66, 0.43); + break; + + case Variant::COLOR: + color = Color(0.62, 1.0, 0.44); + break; + case Variant::NODE_PATH: + color = Color(0.41, 0.58, 0.93); + break; + case Variant::RID: + color = Color(0.41, 0.93, 0.6); + break; + case Variant::OBJECT: + color = Color(0.47, 0.95, 0.91); + break; + case Variant::DICTIONARY: + color = Color(0.47, 0.93, 0.69); + break; + + case Variant::ARRAY: + color = Color(0.88, 0.88, 0.88); + break; + case Variant::PACKED_BYTE_ARRAY: + color = Color(0.67, 0.96, 0.78); + break; + case Variant::PACKED_INT32_ARRAY: + color = Color(0.69, 0.86, 0.96); + break; + case Variant::PACKED_FLOAT32_ARRAY: + color = Color(0.59, 0.91, 0.97); + break; + case Variant::PACKED_INT64_ARRAY: + color = Color(0.69, 0.86, 0.96); + break; + case Variant::PACKED_FLOAT64_ARRAY: + color = Color(0.59, 0.91, 0.97); + break; + case Variant::PACKED_STRING_ARRAY: + color = Color(0.62, 0.77, 0.95); + break; + case Variant::PACKED_VECTOR2_ARRAY: + color = Color(0.82, 0.7, 0.96); + break; + case Variant::PACKED_VECTOR3_ARRAY: + color = Color(0.87, 0.61, 0.95); + break; + case Variant::PACKED_COLOR_ARRAY: + color = Color(0.91, 1.0, 0.59); + break; + + default: + color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.7, 0.7); + } + } else { + switch (p_type) { + case Variant::NIL: + color = Color(0.15, 0.89, 0.63); + break; + + case Variant::BOOL: + color = Color(0.43, 0.56, 0.92); + break; + case Variant::INT: + color = Color(0.31, 0.7, 0.91); + break; + case Variant::FLOAT: + color = Color(0.15, 0.8, 0.94); + break; + case Variant::STRING: + color = Color(0.27, 0.56, 0.91); + break; + + case Variant::VECTOR2: + color = Color(0.68, 0.46, 0.93); + break; + case Variant::VECTOR2I: + color = Color(0.68, 0.46, 0.93); + break; + case Variant::RECT2: + color = Color(0.93, 0.46, 0.56); + break; + case Variant::RECT2I: + color = Color(0.93, 0.46, 0.56); + break; + case Variant::VECTOR3: + color = Color(0.86, 0.42, 0.93); + break; + case Variant::VECTOR3I: + color = Color(0.86, 0.42, 0.93); + break; + case Variant::TRANSFORM2D: + color = Color(0.59, 0.81, 0.1); + break; + case Variant::PLANE: + color = Color(0.97, 0.44, 0.44); + break; + case Variant::QUATERNION: + color = Color(0.93, 0.41, 0.64); + break; + case Variant::AABB: + color = Color(0.93, 0.47, 0.57); + break; + case Variant::BASIS: + color = Color(0.7, 0.73, 0.1); + break; + case Variant::TRANSFORM3D: + color = Color(0.96, 0.56, 0.28); + break; + + case Variant::COLOR: + color = Color(0.24, 0.75, 0.0); + break; + case Variant::NODE_PATH: + color = Color(0.41, 0.58, 0.93); + break; + case Variant::RID: + color = Color(0.17, 0.9, 0.45); + break; + case Variant::OBJECT: + color = Color(0.07, 0.84, 0.76); + break; + case Variant::DICTIONARY: + color = Color(0.34, 0.91, 0.62); + break; + + case Variant::ARRAY: + color = Color(0.45, 0.45, 0.45); + break; + case Variant::PACKED_BYTE_ARRAY: + color = Color(0.38, 0.92, 0.6); + break; + case Variant::PACKED_INT32_ARRAY: + color = Color(0.38, 0.73, 0.92); + break; + case Variant::PACKED_FLOAT32_ARRAY: + color = Color(0.25, 0.83, 0.95); + break; + case Variant::PACKED_INT64_ARRAY: + color = Color(0.38, 0.73, 0.92); + break; + case Variant::PACKED_FLOAT64_ARRAY: + color = Color(0.25, 0.83, 0.95); + break; + case Variant::PACKED_STRING_ARRAY: + color = Color(0.38, 0.62, 0.92); + break; + case Variant::PACKED_VECTOR2_ARRAY: + color = Color(0.62, 0.36, 0.92); + break; + case Variant::PACKED_VECTOR3_ARRAY: + color = Color(0.79, 0.35, 0.92); + break; + case Variant::PACKED_COLOR_ARRAY: + color = Color(0.57, 0.73, 0.0); + break; + + default: + color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.3, 0.3); + } + } + + return color; +} + +void VisualScriptEditor::_update_graph_connections() { + graph->clear_connections(); + + List sequence_conns; + script->get_sequence_connection_list(&sequence_conns); + + for (const VisualScript::SequenceConnection &E : sequence_conns) { + graph->connect_node(itos(E.from_node), E.from_output, itos(E.to_node), 0); + } + + List data_conns; + script->get_data_connection_list(&data_conns); + + for (VisualScript::DataConnection &dc : data_conns) { + Ref from_node = script->get_node(dc.from_node); + Ref to_node = script->get_node(dc.to_node); + + if (to_node->has_input_sequence_port()) { + dc.to_port++; + } + + dc.from_port += from_node->get_output_sequence_port_count(); + + graph->connect_node(itos(dc.from_node), dc.from_port, itos(dc.to_node), dc.to_port); + } +} + +void VisualScriptEditor::_update_graph(int p_only_id) { + if (updating_graph) { + return; + } + + updating_graph = true; + + //byebye all nodes + if (p_only_id >= 0) { + if (graph->has_node(itos(p_only_id))) { + Node *gid = graph->get_node(itos(p_only_id)); + if (gid) { + memdelete(gid); + } + } + } else { + for (int i = 0; i < graph->get_child_count(); i++) { + if (Object::cast_to(graph->get_child(i))) { + memdelete(graph->get_child(i)); + i--; + } + } + } + graph->show(); + select_func_text->hide(); + + Ref type_icons[Variant::VARIANT_MAX] = { + Control::get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("float"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("String"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector2i"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Rect2"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Rect2i"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector3i"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Transform2D"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Plane"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Quaternion"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("AABB"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("StringName"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("RID"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("MiniObject"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Callable"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Signal"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Dictionary"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedByteArray"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedInt32Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedInt64Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedFloat32Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedFloat64Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedStringArray"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedVector2Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons")) + }; + + Ref seq_port = Control::get_theme_icon(SNAME("VisualShaderPort"), SNAME("EditorIcons")); + List node_ids; + script->get_node_list(&node_ids); + + List ids; + script->get_node_list(&ids); + StringName editor_icons = "EditorIcons"; + + for (int &E : ids) { + if (p_only_id >= 0 && p_only_id != E) { + continue; + } + + Ref node = script->get_node(E); + Vector2 pos = script->get_node_position(E); + + GraphNode *gnode = memnew(GraphNode); + gnode->set_title(node->get_caption()); + gnode->set_position_offset(pos * EDSCALE); + if (error_line == E) { + gnode->set_overlay(GraphNode::OVERLAY_POSITION); + } else if (node->is_breakpoint()) { + gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT); + } + + gnode->set_meta("__vnode", node); + gnode->set_name(itos(E)); + gnode->connect("dragged", callable_mp(this, &VisualScriptEditor::_node_moved), varray(E)); + gnode->connect("close_request", callable_mp(this, &VisualScriptEditor::_remove_node), varray(E), CONNECT_DEFERRED); + + { + Ref v = node; + if (!v.is_valid()) { + gnode->set_show_close_button(true); + } + } + + bool has_gnode_text = false; + + Ref nd_list = node; + bool is_vslist = nd_list.is_valid(); + if (is_vslist) { + HBoxContainer *hbnc = memnew(HBoxContainer); + if (nd_list->is_input_port_editable()) { + has_gnode_text = true; + Button *btn = memnew(Button); + btn->set_text(TTR("Add Input Port")); + hbnc->add_child(btn); + btn->connect("pressed", callable_mp(this, &VisualScriptEditor::_add_input_port), varray(E), CONNECT_DEFERRED); + } + if (nd_list->is_output_port_editable()) { + if (nd_list->is_input_port_editable()) { + hbnc->add_spacer(); + } + has_gnode_text = true; + Button *btn = memnew(Button); + btn->set_text(TTR("Add Output Port")); + hbnc->add_child(btn); + btn->connect("pressed", callable_mp(this, &VisualScriptEditor::_add_output_port), varray(E), CONNECT_DEFERRED); + } + gnode->add_child(hbnc); + } else if (Object::cast_to(node.ptr())) { + has_gnode_text = true; + LineEdit *line_edit = memnew(LineEdit); + line_edit->set_text(node->get_text()); + line_edit->set_expand_to_text_length_enabled(true); + line_edit->add_theme_font_override("font", get_theme_font(SNAME("source"), SNAME("EditorFonts"))); + gnode->add_child(line_edit); + line_edit->connect("text_changed", callable_mp(this, &VisualScriptEditor::_expression_text_changed), varray(E)); + } else { + String text = node->get_text(); + if (!text.is_empty()) { + has_gnode_text = true; + Label *label = memnew(Label); + label->set_text(text); + gnode->add_child(label); + } + } + + if (Object::cast_to(node.ptr())) { + Ref vsc = node; + gnode->set_comment(true); + gnode->set_resizable(true); + gnode->set_custom_minimum_size(vsc->get_size() * EDSCALE); + gnode->connect("resize_request", callable_mp(this, &VisualScriptEditor::_comment_node_resized), varray(E)); + } + + if (node_styles.has(node->get_category())) { + Ref sbf = node_styles[node->get_category()]; + if (gnode->is_comment()) { + sbf = EditorNode::get_singleton()->get_theme_base()->get_theme()->get_stylebox(SNAME("comment"), SNAME("GraphNode")); + } + + Color c = sbf->get_border_color(); + c = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0, 0.85) : Color(0.0, 0.0, 0.0, 0.85); + Color ic = c; + gnode->add_theme_color_override("title_color", c); + c.a = 1; + gnode->add_theme_color_override("close_color", c); + gnode->add_theme_color_override("resizer_color", ic); + gnode->add_theme_style_override("frame", sbf); + } + + const Color mono_color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); + + int slot_idx = 0; + + bool single_seq_output = node->get_output_sequence_port_count() == 1 && node->get_output_sequence_port_text(0) == String(); + if ((node->has_input_sequence_port() || single_seq_output) || has_gnode_text) { + // IF has_gnode_text is true BUT we have no sequence ports to draw (in here), + // we still draw the disabled default ones to shift up the slots by one, + // so the slots DON'T start with the content text. + + // IF has_gnode_text is false, but we DO want to draw default sequence ports, + // we draw a dummy text to take up the position of the sequence nodes, so all the other ports are still aligned correctly. + if (!has_gnode_text) { + Label *dummy = memnew(Label); + dummy->set_text(" "); + gnode->add_child(dummy); + } + gnode->set_slot(0, node->has_input_sequence_port(), TYPE_SEQUENCE, mono_color, single_seq_output, TYPE_SEQUENCE, mono_color, seq_port, seq_port); + slot_idx++; + } + + int mixed_seq_ports = 0; + + if (!single_seq_output) { + if (node->has_mixed_input_and_sequence_ports()) { + mixed_seq_ports = node->get_output_sequence_port_count(); + } else { + for (int i = 0; i < node->get_output_sequence_port_count(); i++) { + Label *text2 = memnew(Label); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + gnode->add_child(text2); + gnode->set_slot(slot_idx, false, 0, Color(), true, TYPE_SEQUENCE, mono_color, seq_port, seq_port); + slot_idx++; + } + } + } + + for (int i = 0; i < MAX(node->get_output_value_port_count(), MAX(mixed_seq_ports, node->get_input_value_port_count())); i++) { + bool left_ok = false; + Variant::Type left_type = Variant::NIL; + String left_name; + + if (i < node->get_input_value_port_count()) { + PropertyInfo pi = node->get_input_value_port_info(i); + left_ok = true; + left_type = pi.type; + left_name = pi.name; + } + + bool right_ok = false; + Variant::Type right_type = Variant::NIL; + String right_name; + + if (i >= mixed_seq_ports && i < node->get_output_value_port_count() + mixed_seq_ports) { + PropertyInfo pi = node->get_output_value_port_info(i - mixed_seq_ports); + right_ok = true; + right_type = pi.type; + right_name = pi.name; + } + VBoxContainer *vbc = memnew(VBoxContainer); + HBoxContainer *hbc = memnew(HBoxContainer); + HBoxContainer *hbc2 = memnew(HBoxContainer); + vbc->add_child(hbc); + vbc->add_child(hbc2); + if (left_ok) { + Ref t; + if (left_type >= 0 && left_type < Variant::VARIANT_MAX) { + t = type_icons[left_type]; + } + if (t.is_valid()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(t); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } + + if (is_vslist) { + if (nd_list->is_input_port_name_editable()) { + LineEdit *name_box = memnew(LineEdit); + hbc->add_child(name_box); + name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0)); + name_box->set_text(left_name); + name_box->set_expand_to_text_length_enabled(true); + name_box->connect("resized", callable_mp(this, &VisualScriptEditor::_update_node_size), varray(E)); + name_box->connect("focus_exited", callable_mp(this, &VisualScriptEditor::_port_name_focus_out), varray(name_box, E, i, true)); + } else { + hbc->add_child(memnew(Label(left_name))); + } + + if (nd_list->is_input_port_type_editable()) { + OptionButton *opbtn = memnew(OptionButton); + for (int j = Variant::NIL; j < Variant::VARIANT_MAX; j++) { + opbtn->add_item(Variant::get_type_name(Variant::Type(j))); + } + opbtn->select(left_type); + opbtn->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + hbc->add_child(opbtn); + opbtn->connect("item_selected", callable_mp(this, &VisualScriptEditor::_change_port_type), varray(E, i, true), CONNECT_DEFERRED); + } + + Button *rmbtn = memnew(Button); + rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + hbc->add_child(rmbtn); + rmbtn->connect("pressed", callable_mp(this, &VisualScriptEditor::_remove_input_port), varray(E, i), CONNECT_DEFERRED); + } else { + hbc->add_child(memnew(Label(left_name))); + } + + if (left_type != Variant::NIL && !script->is_input_value_port_connected(E, i)) { + PropertyInfo pi = node->get_input_value_port_info(i); + Button *button = memnew(Button); + Variant value = node->get_default_input_value(i); + if (value.get_type() != left_type) { + //different type? for now convert + //not the same, reconvert + Callable::CallError ce; + const Variant *existingp = &value; + Variant::construct(left_type, value, &existingp, 1, ce); + } + + if (left_type == Variant::COLOR) { + button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); + button->connect("draw", callable_mp(this, &VisualScriptEditor::_draw_color_over_button), varray(button, value)); + } else if (left_type == Variant::OBJECT && Ref(value).is_valid()) { + Ref res = value; + Array arr; + arr.push_back(button->get_instance_id()); + arr.push_back(String(value)); + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_button_resource_previewed", arr); + + } else if (pi.type == Variant::INT && pi.hint == PROPERTY_HINT_ENUM) { + button->set_text(pi.hint_string.get_slice(",", value)); + } else { + button->set_text(value); + } + button->connect("pressed", callable_mp(this, &VisualScriptEditor::_default_value_edited), varray(button, E, i)); + hbc2->add_child(button); + } + } else { + Control *c = memnew(Control); + c->set_custom_minimum_size(Size2(10, 0) * EDSCALE); + hbc->add_child(c); + } + + hbc->add_spacer(); + hbc2->add_spacer(); + + if (i < mixed_seq_ports) { + Label *text2 = memnew(Label); + text2->set_text(node->get_output_sequence_port_text(i)); + text2->set_align(Label::ALIGN_RIGHT); + hbc->add_child(text2); + } + + if (right_ok) { + if (is_vslist) { + Button *rmbtn = memnew(Button); + rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + hbc->add_child(rmbtn); + rmbtn->connect("pressed", callable_mp(this, &VisualScriptEditor::_remove_output_port), varray(E, i), CONNECT_DEFERRED); + + if (nd_list->is_output_port_type_editable()) { + OptionButton *opbtn = memnew(OptionButton); + for (int j = Variant::NIL; j < Variant::VARIANT_MAX; j++) { + opbtn->add_item(Variant::get_type_name(Variant::Type(j))); + } + opbtn->select(right_type); + opbtn->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); + hbc->add_child(opbtn); + opbtn->connect("item_selected", callable_mp(this, &VisualScriptEditor::_change_port_type), varray(E, i, false), CONNECT_DEFERRED); + } + + if (nd_list->is_output_port_name_editable()) { + LineEdit *name_box = memnew(LineEdit); + hbc->add_child(name_box); + name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0)); + name_box->set_text(right_name); + name_box->set_expand_to_text_length_enabled(true); + name_box->connect("resized", callable_mp(this, &VisualScriptEditor::_update_node_size), varray(E)); + name_box->connect("focus_exited", callable_mp(this, &VisualScriptEditor::_port_name_focus_out), varray(name_box, E, i, false)); + } else { + hbc->add_child(memnew(Label(right_name))); + } + } else { + hbc->add_child(memnew(Label(right_name))); + } + + Ref t; + if (right_type >= 0 && right_type < Variant::VARIANT_MAX) { + t = type_icons[right_type]; + } + if (t.is_valid()) { + TextureRect *tf = memnew(TextureRect); + tf->set_texture(t); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); + hbc->add_child(tf); + } + } + + gnode->add_child(vbc); + + bool dark_theme = get_theme_constant(SNAME("dark_theme"), SNAME("Editor")); + if (i < mixed_seq_ports) { + gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), true, TYPE_SEQUENCE, mono_color, Ref(), seq_port); + } else { + gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), right_ok, right_type, _color_from_type(right_type, dark_theme)); + } + + slot_idx++; + } + + graph->add_child(gnode); + + if (gnode->is_comment()) { + graph->move_child(gnode, 0); + } + } + + _update_graph_connections(); + + float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); + graph->set_minimap_opacity(graph_minimap_opacity); + + // Use default_func instead of default_func for now I think that should be good stop gap solution to ensure not breaking anything. + graph->call_deferred(SNAME("set_scroll_ofs"), script->get_scroll() * EDSCALE); + updating_graph = false; +} + +void VisualScriptEditor::_change_port_type(int p_select, int p_id, int p_port, bool is_input) { + Ref vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + undo_redo->create_action(TTR("Change Port Type")); + if (is_input) { + undo_redo->add_do_method(vsn.ptr(), "set_input_data_port_type", p_port, Variant::Type(p_select)); + undo_redo->add_undo_method(vsn.ptr(), "set_input_data_port_type", p_port, vsn->get_input_value_port_info(p_port).type); + } else { + undo_redo->add_do_method(vsn.ptr(), "set_output_data_port_type", p_port, Variant::Type(p_select)); + undo_redo->add_undo_method(vsn.ptr(), "set_output_data_port_type", p_port, vsn->get_output_value_port_info(p_port).type); + } + undo_redo->commit_action(); +} + +void VisualScriptEditor::_update_node_size(int p_id) { + Node *node = graph->get_node(itos(p_id)); + if (Object::cast_to(node)) { + Object::cast_to(node)->set_size(Vector2(1, 1)); // Shrink if text is smaller. + } +} + +void VisualScriptEditor::_port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input) { + Ref vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + String text; + + if (Object::cast_to(p_name_box)) { + text = Object::cast_to(p_name_box)->get_text(); + } else { + return; + } + + undo_redo->create_action(TTR("Change Port Name")); + if (is_input) { + undo_redo->add_do_method(vsn.ptr(), "set_input_data_port_name", p_port, text); + undo_redo->add_undo_method(vsn.ptr(), "set_input_data_port_name", p_port, vsn->get_input_value_port_info(p_port).name); + } else { + undo_redo->add_do_method(vsn.ptr(), "set_output_data_port_name", p_port, text); + undo_redo->add_undo_method(vsn.ptr(), "set_output_data_port_name", p_port, vsn->get_output_value_port_info(p_port).name); + } + undo_redo->commit_action(); +} + +void VisualScriptEditor::_update_members() { + ERR_FAIL_COND(!script.is_valid()); + + updating_members = true; + + members->clear(); + TreeItem *root = members->create_item(); + + TreeItem *functions = members->create_item(root); + functions->set_selectable(0, false); + functions->set_text(0, TTR("Functions:")); + functions->add_button(0, Control::get_theme_icon(SNAME("Override"), SNAME("EditorIcons")), 1, false, TTR("Override an existing built-in function.")); + functions->add_button(0, Control::get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), 0, false, TTR("Create a new function.")); + functions->set_custom_color(0, Control::get_theme_color(SNAME("mono_color"), SNAME("Editor"))); + + List func_names; + script->get_function_list(&func_names); + func_names.sort_custom(); + for (const StringName &E : func_names) { + TreeItem *ti = members->create_item(functions); + ti->set_text(0, E); + ti->set_selectable(0, true); + ti->set_metadata(0, E); + ti->add_button(0, Control::get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")), 0); + if (selected == E) { + ti->select(0); + } + } + + TreeItem *variables = members->create_item(root); + variables->set_selectable(0, false); + variables->set_text(0, TTR("Variables:")); + variables->add_button(0, Control::get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), -1, false, TTR("Create a new variable.")); + variables->set_custom_color(0, Control::get_theme_color(SNAME("mono_color"), SNAME("Editor"))); + + Ref type_icons[Variant::VARIANT_MAX] = { + Control::get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("int"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("float"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("String"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector2i"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Rect2"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Rect2i"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Vector3i"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Transform2D"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Plane"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Quaternion"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("AABB"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("RID"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("MiniObject"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Callable"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Signal"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Dictionary"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedByteArray"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedInt32Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedFloat32Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedStringArray"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedVector2Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")), + Control::get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons")) + }; + + List var_names; + script->get_variable_list(&var_names); + for (const StringName &E : var_names) { + TreeItem *ti = members->create_item(variables); + + ti->set_text(0, E); + + ti->set_suffix(0, "= " + _sanitized_variant_text(E)); + ti->set_icon(0, type_icons[script->get_variable_info(E).type]); + + ti->set_selectable(0, true); + ti->set_editable(0, true); + ti->set_metadata(0, E); + if (selected == E) { + ti->select(0); + } + } + + TreeItem *_signals = members->create_item(root); + _signals->set_selectable(0, false); + _signals->set_text(0, TTR("Signals:")); + _signals->add_button(0, Control::get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), -1, false, TTR("Create a new signal.")); + _signals->set_custom_color(0, Control::get_theme_color(SNAME("mono_color"), SNAME("Editor"))); + + List signal_names; + script->get_custom_signal_list(&signal_names); + for (const StringName &E : signal_names) { + TreeItem *ti = members->create_item(_signals); + ti->set_text(0, E); + ti->set_selectable(0, true); + ti->set_editable(0, true); + ti->set_metadata(0, E); + if (selected == E) { + ti->select(0); + } + } + + String base_type = script->get_instance_base_type(); + String icon_type = base_type; + if (!Control::has_theme_icon(base_type, SNAME("EditorIcons"))) { + icon_type = "Object"; + } + + base_type_select->set_text(base_type); + base_type_select->set_icon(Control::get_theme_icon(icon_type, SNAME("EditorIcons"))); + + updating_members = false; +} + +String VisualScriptEditor::_sanitized_variant_text(const StringName &property_name) { + Variant var = script->get_variable_default_value(property_name); + + if (script->get_variable_info(property_name).type != Variant::NIL) { + Callable::CallError ce; + const Variant *converted = &var; + Variant n; + Variant::construct(script->get_variable_info(property_name).type, n, &converted, 1, ce); + var = n; + } + + return String(var); +} + +void VisualScriptEditor::_member_selected() { + if (updating_members) { + return; + } + + TreeItem *ti = members->get_selected(); + ERR_FAIL_COND(!ti); + + selected = ti->get_metadata(0); + + if (ti->get_parent() == members->get_root()->get_first_child()) { +#ifdef OSX_ENABLED + bool held_ctrl = Input::get_singleton()->is_key_pressed(KEY_META); +#else + bool held_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); +#endif + if (held_ctrl) { + ERR_FAIL_COND(!script->has_function(selected)); + _center_on_node(script->get_function_node_id(selected)); + } + } +} + +void VisualScriptEditor::_member_edited() { + if (updating_members) { + return; + } + + TreeItem *ti = members->get_edited(); + ERR_FAIL_COND(!ti); + + String name = ti->get_metadata(0); + String new_name = ti->get_text(0); + + if (name == new_name) { + return; + } + + if (!new_name.is_valid_identifier()) { + EditorNode::get_singleton()->show_warning(TTR("Name is not a valid identifier:") + " " + new_name); + updating_members = true; + ti->set_text(0, name); + updating_members = false; + return; + } + + if (script->has_function(new_name) || script->has_variable(new_name) || script->has_custom_signal(new_name)) { + EditorNode::get_singleton()->show_warning(TTR("Name already in use by another func/var/signal:") + " " + new_name); + updating_members = true; + ti->set_text(0, name); + updating_members = false; + return; + } + + TreeItem *root = members->get_root(); + + if (ti->get_parent() == root->get_first_child()) { + selected = new_name; + + int node_id = script->get_function_node_id(name); + Ref func; + if (script->has_node(node_id)) { + func = script->get_node(node_id); + } + undo_redo->create_action(TTR("Rename Function")); + undo_redo->add_do_method(script.ptr(), "rename_function", name, new_name); + undo_redo->add_undo_method(script.ptr(), "rename_function", new_name, name); + if (func.is_valid()) { + undo_redo->add_do_method(func.ptr(), "set_name", new_name); + undo_redo->add_undo_method(func.ptr(), "set_name", name); + } + + // Also fix all function calls. + List lst; + script->get_node_list(&lst); + for (int &F : lst) { + Ref fncall = script->get_node(F); + if (!fncall.is_valid()) { + continue; + } + if (fncall->get_function() == name) { + undo_redo->add_do_method(fncall.ptr(), "set_function", new_name); + undo_redo->add_undo_method(fncall.ptr(), "set_function", name); + } + } + + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + + return; // Or crash because it will become invalid. + } + + if (ti->get_parent() == root->get_first_child()->get_next()) { + selected = new_name; + undo_redo->create_action(TTR("Rename Variable")); + undo_redo->add_do_method(script.ptr(), "rename_variable", name, new_name); + undo_redo->add_undo_method(script.ptr(), "rename_variable", new_name, name); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + + return; // Or crash because it will become invalid. + } + + if (ti->get_parent() == root->get_first_child()->get_next()->get_next()) { + selected = new_name; + undo_redo->create_action(TTR("Rename Signal")); + undo_redo->add_do_method(script.ptr(), "rename_custom_signal", name, new_name); + undo_redo->add_undo_method(script.ptr(), "rename_custom_signal", new_name, name); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + + return; // Or crash because it will become invalid. + } +} + +void VisualScriptEditor::_create_function_dialog() { + function_create_dialog->popup_centered(); + func_name_box->set_text(""); + func_name_box->grab_focus(); + for (int i = 0; i < func_input_vbox->get_child_count(); i++) { + Node *nd = func_input_vbox->get_child(i); + nd->queue_delete(); + } +} + +void VisualScriptEditor::_create_function() { + String name = _validate_name((func_name_box->get_text() == "") ? "new_func" : func_name_box->get_text()); + selected = name; + Vector2 pos = _get_available_pos(); + + Ref func_node; + func_node.instantiate(); + func_node->set_name(name); + + for (int i = 0; i < func_input_vbox->get_child_count(); i++) { + OptionButton *opbtn = Object::cast_to(func_input_vbox->get_child(i)->get_child(3)); + LineEdit *lne = Object::cast_to(func_input_vbox->get_child(i)->get_child(1)); + if (!opbtn || !lne) { + continue; + } + Variant::Type arg_type = Variant::Type(opbtn->get_selected()); + String arg_name = lne->get_text(); + func_node->add_argument(arg_type, arg_name); + } + + int func_node_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(), "add_function", name, func_node_id); + undo_redo->add_undo_method(script.ptr(), "remove_function", name); + undo_redo->add_do_method(script.ptr(), "add_node", func_node_id, func_node, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", func_node_id); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + + _update_graph(); +} + +void VisualScriptEditor::_add_node_dialog() { + _generic_search(script->get_instance_base_type(), graph->get_global_position() + Vector2(55, 80), true); +} + +void VisualScriptEditor::_add_func_input() { + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->set_h_size_flags(SIZE_EXPAND_FILL); + + Label *name_label = memnew(Label); + name_label->set_text(TTR("Name:")); + hbox->add_child(name_label); + + LineEdit *name_box = memnew(LineEdit); + name_box->set_h_size_flags(SIZE_EXPAND_FILL); + name_box->set_text("input"); + name_box->connect("focus_entered", callable_mp(this, &VisualScriptEditor::_deselect_input_names)); + hbox->add_child(name_box); + + Label *type_label = memnew(Label); + type_label->set_text(TTR("Type:")); + hbox->add_child(type_label); + + OptionButton *type_box = memnew(OptionButton); + type_box->set_custom_minimum_size(Size2(120 * EDSCALE, 0)); + for (int i = Variant::NIL; i < Variant::VARIANT_MAX; i++) { + type_box->add_item(Variant::get_type_name(Variant::Type(i))); + } + type_box->select(1); + hbox->add_child(type_box); + + Button *delete_button = memnew(Button); + delete_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + delete_button->set_tooltip(vformat(TTR("Delete input port"))); + hbox->add_child(delete_button); + + for (int i = 0; i < func_input_vbox->get_child_count(); i++) { + LineEdit *line_edit = (LineEdit *)func_input_vbox->get_child(i)->get_child(1); + line_edit->deselect(); + } + + func_input_vbox->add_child(hbox); + hbox->set_meta("id", hbox->get_index()); + + delete_button->connect("pressed", callable_mp(this, &VisualScriptEditor::_remove_func_input), varray(hbox)); + + name_box->select_all(); + name_box->grab_focus(); +} + +void VisualScriptEditor::_remove_func_input(Node *p_node) { + func_input_vbox->remove_child(p_node); + p_node->queue_delete(); +} + +void VisualScriptEditor::_deselect_input_names() { + int cn = func_input_vbox->get_child_count(); + for (int i = 0; i < cn; i++) { + LineEdit *lne = Object::cast_to(func_input_vbox->get_child(i)->get_child(1)); + if (lne) { + lne->deselect(); + } + } +} + +void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_button) { + TreeItem *ti = Object::cast_to(p_item); + + TreeItem *root = members->get_root(); + + if (ti->get_parent() == root) { + //main buttons + if (ti == root->get_first_child()) { + // Add function, this one uses menu. + + if (p_button == 1) { + // Ensure script base exists otherwise use custom base type. + ERR_FAIL_COND(script.is_null()); + new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), String(), true); + return; + } else if (p_button == 0) { + String name = _validate_name("new_function"); + selected = name; + Vector2 pos = _get_available_pos(); + + Ref func_node; + func_node.instantiate(); + func_node->set_name(name); + int fn_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Function")); + undo_redo->add_do_method(script.ptr(), "add_function", name, fn_id); + undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos); + undo_redo->add_undo_method(script.ptr(), "remove_function", name); + undo_redo->add_undo_method(script.ptr(), "remove_node", fn_id); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + + _update_graph(); + } + + return; // Or crash because it will become invalid. + } + + if (ti == root->get_first_child()->get_next()) { + // Add variable. + String name = _validate_name("new_variable"); + selected = name; + + undo_redo->create_action(TTR("Add Variable")); + undo_redo->add_do_method(script.ptr(), "add_variable", name); + undo_redo->add_undo_method(script.ptr(), "remove_variable", name); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + return; // Or crash because it will become invalid. + } + + if (ti == root->get_first_child()->get_next()->get_next()) { + // Add variable. + String name = _validate_name("new_signal"); + selected = name; + + undo_redo->create_action(TTR("Add Signal")); + undo_redo->add_do_method(script.ptr(), "add_custom_signal", name); + undo_redo->add_undo_method(script.ptr(), "remove_custom_signal", name); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); + return; // Or crash because it will become invalid. + } + } else if (ti->get_parent() == root->get_first_child()) { + selected = ti->get_text(0); + function_name_edit->set_position(Input::get_singleton()->get_mouse_position() - Vector2(60, -10)); + function_name_edit->popup(); + function_name_box->set_text(selected); + function_name_box->select_all(); + } +} + +void VisualScriptEditor::_add_input_port(int p_id) { + Ref vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + updating_graph = true; + + undo_redo->create_action(TTR("Add Input Port"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsn.ptr(), "add_input_data_port", Variant::NIL, "arg", -1); + undo_redo->add_do_method(this, "_update_graph", p_id); + + undo_redo->add_undo_method(vsn.ptr(), "remove_input_data_port", vsn->get_input_value_port_count()); + undo_redo->add_undo_method(this, "_update_graph", p_id); + + updating_graph = false; + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_add_output_port(int p_id) { + Ref vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + updating_graph = true; + + undo_redo->create_action(TTR("Add Output Port"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsn.ptr(), "add_output_data_port", Variant::NIL, "arg", -1); + undo_redo->add_do_method(this, "_update_graph", p_id); + + undo_redo->add_undo_method(vsn.ptr(), "remove_output_data_port", vsn->get_output_value_port_count()); + undo_redo->add_undo_method(this, "_update_graph", p_id); + + updating_graph = false; + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_remove_input_port(int p_id, int p_port) { + Ref vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + updating_graph = true; + + undo_redo->create_action(TTR("Remove Input Port"), UndoRedo::MERGE_ENDS); + + int conn_from = -1, conn_port = -1; + script->get_input_value_port_connection_source(p_id, p_port, &conn_from, &conn_port); + + if (conn_from != -1) { + undo_redo->add_do_method(script.ptr(), "data_disconnect", conn_from, conn_port, p_id, p_port); + } + + undo_redo->add_do_method(vsn.ptr(), "remove_input_data_port", p_port); + undo_redo->add_do_method(this, "_update_graph", p_id); + + if (conn_from != -1) { + undo_redo->add_undo_method(script.ptr(), "data_connect", conn_from, conn_port, p_id, p_port); + } + + undo_redo->add_undo_method(vsn.ptr(), "add_input_data_port", vsn->get_input_value_port_info(p_port).type, vsn->get_input_value_port_info(p_port).name, p_port); + undo_redo->add_undo_method(this, "_update_graph", p_id); + + updating_graph = false; + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_remove_output_port(int p_id, int p_port) { + Ref vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + updating_graph = true; + + undo_redo->create_action(TTR("Remove Output Port"), UndoRedo::MERGE_ENDS); + + List data_connections; + script->get_data_connection_list(&data_connections); + + HashMap> conn_map; + for (const VisualScript::DataConnection &E : data_connections) { + if (E.from_node == p_id && E.from_port == p_port) { + // Push into the connections map. + if (!conn_map.has(E.to_node)) { + conn_map.set(E.to_node, Set()); + } + conn_map[E.to_node].insert(E.to_port); + } + } + + undo_redo->add_do_method(vsn.ptr(), "remove_output_data_port", p_port); + undo_redo->add_do_method(this, "_update_graph", p_id); + + List keys; + conn_map.get_key_list(&keys); + for (const int &E : keys) { + for (const Set::Element *F = conn_map[E].front(); F; F = F->next()) { + undo_redo->add_undo_method(script.ptr(), "data_connect", p_id, p_port, E, F); + } + } + + undo_redo->add_undo_method(vsn.ptr(), "add_output_data_port", vsn->get_output_value_port_info(p_port).type, vsn->get_output_value_port_info(p_port).name, p_port); + undo_redo->add_undo_method(this, "_update_graph", p_id); + + updating_graph = false; + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id) { + Ref vse = script->get_node(p_id); + if (!vse.is_valid()) { + return; + } + + updating_graph = true; + + undo_redo->create_action(TTR("Change Expression"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(vse.ptr(), "expression", p_text); + undo_redo->add_undo_property(vse.ptr(), "expression", vse->get("expression")); + undo_redo->add_do_method(this, "_update_graph", p_id); + undo_redo->add_undo_method(this, "_update_graph", p_id); + undo_redo->commit_action(); + + Node *node = graph->get_node(itos(p_id)); + if (Object::cast_to(node)) { + Object::cast_to(node)->set_size(Vector2(1, 1)); // Shrink if text is smaller. + } + + updating_graph = false; +} + +Vector2 VisualScriptEditor::_get_pos_in_graph(Vector2 p_point) const { + Vector2 pos = (graph->get_scroll_ofs() + p_point) / (graph->get_zoom() * EDSCALE); + if (graph->is_using_snap()) { + int snap = graph->get_snap(); + pos = pos.snapped(Vector2(snap, snap)); + } + return pos; +} + +Vector2 VisualScriptEditor::_get_available_pos(bool p_centered, Vector2 p_pos) const { + if (p_centered) { + p_pos = _get_pos_in_graph(graph->get_size() * 0.5); + } + + while (true) { + bool exists = false; + List existing; + script->get_node_list(&existing); + for (int &E : existing) { + Point2 pos = script->get_node_position(E); + if (pos.distance_to(p_pos) < 50) { + p_pos += Vector2(graph->get_snap(), graph->get_snap()); + exists = true; + break; + } + } + if (exists) { + continue; + } + break; + } + + return p_pos; +} + +String VisualScriptEditor::_validate_name(const String &p_name) const { + String valid = p_name; + + int counter = 1; + while (true) { + bool exists = script->has_function(valid) || script->has_variable(valid) || script->has_custom_signal(valid); + + if (exists) { + counter++; + valid = p_name + "_" + itos(counter); + continue; + } + + break; + } + + return valid; +} + +void VisualScriptEditor::_on_nodes_copy() { + clipboard->nodes.clear(); + clipboard->data_connections.clear(); + clipboard->sequence_connections.clear(); + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to(graph->get_child(i)); + if (gn) { + if (gn->is_selected()) { + int id = gn->get_name().operator String().to_int(); + Ref node = script->get_node(id); + if (Object::cast_to(*node)) { + EditorNode::get_singleton()->show_warning(TTR("Can't copy the function node.")); + return; + } + if (node.is_valid()) { + clipboard->nodes[id] = node->duplicate(true); + clipboard->nodes_positions[id] = script->get_node_position(id); + } + } + } + } + + if (clipboard->nodes.is_empty()) { + return; + } + + List sequence_connections; + script->get_sequence_connection_list(&sequence_connections); + + for (const VisualScript::SequenceConnection &E : sequence_connections) { + if (clipboard->nodes.has(E.from_node) && clipboard->nodes.has(E.to_node)) { + clipboard->sequence_connections.insert(E); + } + } + + List data_connections; + script->get_data_connection_list(&data_connections); + + for (const VisualScript::DataConnection &E : data_connections) { + if (clipboard->nodes.has(E.from_node) && clipboard->nodes.has(E.to_node)) { + clipboard->data_connections.insert(E); + } + } +} + +void VisualScriptEditor::_on_nodes_paste() { + if (clipboard->nodes.is_empty()) { + EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty!")); + return; + } + + Map remap; + + undo_redo->create_action(TTR("Paste VisualScript Nodes")); + int idc = script->get_available_id() + 1; + + Set to_select; + + Set existing_positions; + + { + List nodes; + script->get_node_list(&nodes); + for (int &E : nodes) { + Vector2 pos = script->get_node_position(E).snapped(Vector2(2, 2)); + existing_positions.insert(pos); + } + } + + bool first_paste = true; + Vector2 position_offset = Vector2(0, 0); + + for (KeyValue> &E : clipboard->nodes) { + Ref node = E.value->duplicate(); + + int new_id = idc++; + to_select.insert(new_id); + + remap[E.key] = new_id; + + Vector2 paste_pos = clipboard->nodes_positions[E.key]; + + if (first_paste) { + position_offset = _get_pos_in_graph(mouse_up_position - graph->get_global_position()) - paste_pos; + first_paste = false; + } + + paste_pos += position_offset; + + while (existing_positions.has(paste_pos.snapped(Vector2(2, 2)))) { + paste_pos += Vector2(20, 20) * EDSCALE; + } + + undo_redo->add_do_method(script.ptr(), "add_node", new_id, node, paste_pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + } + + for (Set::Element *E = clipboard->sequence_connections.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]); + } + + for (Set::Element *E = clipboard->data_connections.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "data_connect", remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", remap[E->get().from_node], E->get().from_port, remap[E->get().to_node], E->get().to_port); + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + undo_redo->commit_action(); + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to(graph->get_child(i)); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(to_select.has(id)); + } + } +} + +void VisualScriptEditor::_on_nodes_delete() { + // Delete all the selected nodes. + + List to_erase; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to(graph->get_child(i)); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + to_erase.push_back(gn->get_name().operator String().to_int()); + } + } + } + + if (to_erase.is_empty()) { + return; + } + + undo_redo->create_action(TTR("Remove VisualScript Nodes")); + + for (int &F : to_erase) { + int cr_node = F; + + undo_redo->add_do_method(script.ptr(), "remove_node", cr_node); + undo_redo->add_undo_method(script.ptr(), "add_node", cr_node, script->get_node(cr_node), script->get_node_position(cr_node)); + + List sequence_conns; + script->get_sequence_connection_list(&sequence_conns); + + for (const VisualScript::SequenceConnection &E : sequence_conns) { + if (E.from_node == cr_node || E.to_node == cr_node) { + undo_redo->add_undo_method(script.ptr(), "sequence_connect", E.from_node, E.from_output, E.to_node); + } + } + + List data_conns; + script->get_data_connection_list(&data_conns); + + for (const VisualScript::DataConnection &E : data_conns) { + if (E.from_node == F || E.to_node == F) { + undo_redo->add_undo_method(script.ptr(), "data_connect", E.from_node, E.from_port, E.to_node, E.to_port); + } + } + } + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_on_nodes_duplicate() { + Set to_duplicate; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to(graph->get_child(i)); + if (gn) { + if (gn->is_selected() && gn->is_close_button_visible()) { + int id = gn->get_name().operator String().to_int(); + to_duplicate.insert(id); + } + } + } + + if (to_duplicate.is_empty()) { + return; + } + + undo_redo->create_action(TTR("Duplicate VisualScript Nodes")); + int idc = script->get_available_id() + 1; + + Set to_select; + HashMap remap; + + for (Set::Element *F = to_duplicate.front(); F; F = F->next()) { + // Duplicate from the specific function but place it into the default func as it would lack the connections. + Ref node = script->get_node(F->get()); + + Ref dupe = node->duplicate(true); + + int new_id = idc++; + remap.set(F->get(), new_id); + + to_select.insert(new_id); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, dupe, script->get_node_position(F->get()) + Vector2(20, 20)); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + } + + List seqs; + script->get_sequence_connection_list(&seqs); + for (const VisualScript::SequenceConnection &E : seqs) { + if (to_duplicate.has(E.from_node) && to_duplicate.has(E.to_node)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", remap[E.from_node], E.from_output, remap[E.to_node]); + } + } + + List data; + script->get_data_connection_list(&data); + for (const VisualScript::DataConnection &E : data) { + if (to_duplicate.has(E.from_node) && to_duplicate.has(E.to_node)) { + undo_redo->add_do_method(script.ptr(), "data_connect", remap[E.from_node], E.from_port, remap[E.to_node], E.to_port); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + undo_redo->commit_action(); + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to(graph->get_child(i)); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(to_select.has(id)); + } + } + + if (to_select.size()) { + EditorNode::get_singleton()->push_item(script->get_node(to_select.front()->get()).ptr()); + } +} + +void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool node_centered) { + if (node_centered) { + port_action_pos = graph->get_size() / 2.0f; + } else { + port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position(); + } + + new_connect_node_select->select_from_visual_script(p_base_type, false, false); // neither connecting nor reset text + + // Ensure that the dialog fits inside the graph. + Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size(); + pos.x = pos.x > bounds.x ? bounds.x : pos.x; + pos.y = pos.y > bounds.y ? bounds.y : pos.y; + + if (pos != Vector2()) { + new_connect_node_select->set_position(pos); + } +} + +void VisualScriptEditor::input(const Ref &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + // GUI input for VS Editor Plugin + Ref key = p_event; + + if (key.is_valid() && !key->is_pressed()) { + mouse_up_position = Input::get_singleton()->get_mouse_position(); + } +} + +void VisualScriptEditor::_graph_gui_input(const Ref &p_event) { + Ref key = p_event; + + if (key.is_valid() && key->is_pressed() && key->get_button_mask() == MOUSE_BUTTON_RIGHT) { + saved_position = graph->get_local_mouse_position(); + + Point2 gpos = Input::get_singleton()->get_mouse_position(); + _generic_search(script->get_instance_base_type(), gpos); + } +} + +void VisualScriptEditor::_members_gui_input(const Ref &p_event) { + Ref key = p_event; + if (key.is_valid() && key->is_pressed() && !key->is_echo()) { + if (members->has_focus()) { + TreeItem *ti = members->get_selected(); + if (ti) { + TreeItem *root = members->get_root(); + if (ti->get_parent() == root->get_first_child()) { + member_type = MEMBER_FUNCTION; + } + if (ti->get_parent() == root->get_first_child()->get_next()) { + member_type = MEMBER_VARIABLE; + } + if (ti->get_parent() == root->get_first_child()->get_next()->get_next()) { + member_type = MEMBER_SIGNAL; + } + member_name = ti->get_text(0); + } + if (ED_IS_SHORTCUT("ui_graph_delete", p_event)) { + _member_option(MEMBER_REMOVE); + } + if (ED_IS_SHORTCUT("visual_script_editor/edit_member", p_event)) { + _member_option(MEMBER_EDIT); + } + } + } + + Ref btn = p_event; + if (btn.is_valid() && btn->is_double_click()) { + TreeItem *ti = members->get_selected(); + if (ti && ti->get_parent() == members->get_root()->get_first_child()) { // to check if it's a function + _center_on_node(script->get_function_node_id(ti->get_metadata(0))); + } + } +} + +void VisualScriptEditor::_rename_function(const String &name, const String &new_name) { + if (!new_name.is_valid_identifier()) { + EditorNode::get_singleton()->show_warning(TTR("Name is not a valid identifier:") + " " + new_name); + return; + } + + if (script->has_function(new_name) || script->has_variable(new_name) || script->has_custom_signal(new_name)) { + EditorNode::get_singleton()->show_warning(TTR("Name already in use by another func/var/signal:") + " " + new_name); + return; + } + + int node_id = script->get_function_node_id(name); + Ref func; + if (script->has_node(node_id)) { + func = script->get_node(node_id); + } + undo_redo->create_action(TTR("Rename Function")); + undo_redo->add_do_method(script.ptr(), "rename_function", name, new_name); + undo_redo->add_undo_method(script.ptr(), "rename_function", new_name, name); + if (func.is_valid()) { + undo_redo->add_do_method(func.ptr(), "set_name", new_name); + undo_redo->add_undo_method(func.ptr(), "set_name", name); + } + + // Also fix all function calls. + List lst; + script->get_node_list(&lst); + for (int &F : lst) { + Ref fncall = script->get_node(F); + if (!fncall.is_valid()) { + continue; + } + if (fncall->get_function() == name) { + undo_redo->add_do_method(fncall.ptr(), "set_function", new_name); + undo_redo->add_undo_method(fncall.ptr(), "set_function", name); + } + } + + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + undo_redo->commit_action(); +} + +void VisualScriptEditor::_fn_name_box_input(const Ref &p_event) { + if (!function_name_edit->is_visible()) { + return; + } + + Ref key = p_event; + if (key.is_valid() && key->is_pressed() && key->get_keycode() == KEY_ENTER) { + function_name_edit->hide(); + _rename_function(selected, function_name_box->get_text()); + function_name_box->clear(); + } +} + +Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { + if (p_from == members) { + TreeItem *it = members->get_item_at_position(p_point); + if (!it) { + return Variant(); + } + + String type = it->get_metadata(0); + + if (type == String()) { + return Variant(); + } + + Dictionary dd; + TreeItem *root = members->get_root(); + + if (it->get_parent() == root->get_first_child()) { + dd["type"] = "visual_script_function_drag"; + dd["function"] = type; + } else if (it->get_parent() == root->get_first_child()->get_next()) { + dd["type"] = "visual_script_variable_drag"; + dd["variable"] = type; + } else if (it->get_parent() == root->get_first_child()->get_next()->get_next()) { + dd["type"] = "visual_script_signal_drag"; + dd["signal"] = type; + + } else { + return Variant(); + } + + Label *label = memnew(Label); + label->set_text(it->get_text(0)); + set_drag_preview(label); + return dd; + } + return Variant(); +} + +bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + if (p_from == graph) { + Dictionary d = p_data; + if (d.has("type") && + (String(d["type"]) == "visual_script_node_drag" || + String(d["type"]) == "visual_script_function_drag" || + String(d["type"]) == "visual_script_variable_drag" || + String(d["type"]) == "visual_script_signal_drag" || + String(d["type"]) == "obj_property" || + String(d["type"]) == "resource" || + String(d["type"]) == "files" || + String(d["type"]) == "nodes")) { + if (String(d["type"]) == "obj_property") { +#ifdef OSX_ENABLED + const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a Getter. Hold Shift to drop a generic signature."), find_keycode_name(KEY_META))); +#else + const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature.")); +#endif + } + + if (String(d["type"]) == "nodes") { +#ifdef OSX_ENABLED + const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a simple reference to the node."), find_keycode_name(KEY_META))); +#else + const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a simple reference to the node.")); +#endif + } + + if (String(d["type"]) == "visual_script_variable_drag") { +#ifdef OSX_ENABLED + const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a Variable Setter."), find_keycode_name(KEY_META))); +#else + const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a Variable Setter.")); +#endif + } + + return true; + } + } + + return false; +} + +static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref