diff options
Diffstat (limited to 'modules/visual_script/editor')
4 files changed, 6686 insertions, 0 deletions
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..5ea8eaff00 --- /dev/null +++ b/modules/visual_script/editor/visual_script_editor.cpp @@ -0,0 +1,4825 @@ +/*************************************************************************/ +/* visual_script_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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/gui/view_panner.h" +#include "scene/main/window.h" + +#ifdef TOOLS_ENABLED +class VisualScriptEditorSignalEdit : public Object { + GDCLASS(VisualScriptEditorSignalEdit, Object); + + StringName sig; + +public: + UndoRedo *undo_redo; + Ref<VisualScript> 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<PropertyInfo> *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<VisualScript> 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); + InspectorDock::get_inspector_singleton()->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<PropertyInfo> *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<VisualScript::SequenceConnection> 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<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(&data_conns); + + for (VisualScript::DataConnection &dc : data_conns) { + Ref<VisualScriptNode> from_node = script->get_node(dc.from_node); + Ref<VisualScriptNode> 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<GraphNode>(graph->get_child(i))) { + memdelete(graph->get_child(i)); + i--; + } + } + } + graph->show(); + select_func_text->hide(); + + Ref<Texture2D> 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<Texture2D> seq_port = Control::get_theme_icon(SNAME("VisualShaderPort"), SNAME("EditorIcons")); + List<int> node_ids; + script->get_node_list(&node_ids); + + List<int> ids; + script->get_node_list(&ids); + + for (int &E : ids) { + if (p_only_id >= 0 && p_only_id != E) { + continue; + } + + Ref<VisualScriptNode> 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<VisualScriptFunction> v = node; + if (!v.is_valid()) { + gnode->set_show_close_button(true); + } + } + + bool has_gnode_text = false; + + Ref<VisualScriptLists> 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<VisualScriptExpression>(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<VisualScriptComment>(node.ptr())) { + Ref<VisualScriptComment> 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<StyleBoxFlat> 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_horizontal_alignment(HORIZONTAL_ALIGNMENT_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<Texture2D> 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<Resource>(value).is_valid()) { + Ref<Resource> 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_horizontal_alignment(HORIZONTAL_ALIGNMENT_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<Texture2D> 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<Texture2D>(), 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<VisualScriptLists> 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<Control>(node)) { + Object::cast_to<Control>(node)->reset_size(); // 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<VisualScriptLists> vsn = script->get_node(p_id); + if (!vsn.is_valid()) { + return; + } + + String text; + + if (Object::cast_to<LineEdit>(p_name_box)) { + text = Object::cast_to<LineEdit>(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<StringName> func_names; + script->get_function_list(&func_names); + func_names.sort_custom<StringName::AlphCompare>(); + 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<Texture2D> 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<StringName> 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<StringName> 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<VisualScriptFunction> 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<int> lst; + script->get_node_list(&lst); + for (int &F : lst) { + Ref<VisualScriptFunctionCall> 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); + + // Also fix all variable setter & getter calls + List<int> lst; + script->get_node_list(&lst); + for (int &P : lst) { + Ref<VisualScriptPropertySet> pset = script->get_node(P); + if (pset.is_valid() && pset->get_property() == name) { + undo_redo->add_do_method(pset.ptr(), "set_property", new_name); + undo_redo->add_undo_method(pset.ptr(), "set_property", name); + } + Ref<VisualScriptPropertyGet> pget = script->get_node(P); + if (pget.is_valid() && pget->get_property() == name) { + undo_redo->add_do_method(pget.ptr(), "set_property", new_name); + undo_redo->add_undo_method(pget.ptr(), "set_property", 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); + + // Also fix all signal emitting nodes + List<int> lst; + script->get_node_list(&lst); + for (int &P : lst) { + Ref<VisualScriptEmitSignal> psig = script->get_node(P); + if (psig.is_valid() && psig->get_signal() == name) { + undo_redo->add_do_method(psig.ptr(), "set_signal", new_name); + undo_redo->add_undo_method(psig.ptr(), "set_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. + } +} + +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().is_empty()) ? "new_func" : func_name_box->get_text()); + selected = name; + Vector2 pos = _get_available_pos(); + + Ref<VisualScriptFunction> 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<OptionButton>(func_input_vbox->get_child(i)->get_child(3)); + LineEdit *lne = Object::cast_to<LineEdit>(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(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<LineEdit>(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<TreeItem>(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(), true); + return; + } else if (p_button == 0) { + String name = _validate_name("new_function"); + selected = name; + Vector2 pos = _get_available_pos(); + + Ref<VisualScriptFunction> 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(get_screen_position() + get_local_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<VisualScriptLists> 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<VisualScriptLists> 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<VisualScriptLists> 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<VisualScriptLists> 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<VisualScript::DataConnection> data_connections; + script->get_data_connection_list(&data_connections); + + HashMap<int, Set<int>> 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<int>()); + } + 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<int> keys; + conn_map.get_key_list(&keys); + for (const int &E : keys) { + for (const Set<int>::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<VisualScriptExpression> 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<Control>(node)) { + Object::cast_to<Control>(node)->reset_size(); // 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<int> 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<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected()) { + int id = gn->get_name().operator String().to_int(); + Ref<VisualScriptNode> node = script->get_node(id); + if (Object::cast_to<VisualScriptFunction>(*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<VisualScript::SequenceConnection> 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<VisualScript::DataConnection> 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<int, int> remap; + + undo_redo->create_action(TTR("Paste VisualScript Nodes")); + int idc = script->get_available_id() + 1; + + Set<int> to_select; + + Set<Vector2> existing_positions; + + { + List<int> 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<int, Ref<VisualScriptNode>> &E : clipboard->nodes) { + Ref<VisualScriptNode> 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<VisualScript::SequenceConnection>::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<VisualScript::DataConnection>::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<GraphNode>(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<int> to_erase; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(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<VisualScript::SequenceConnection> 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<VisualScript::DataConnection> 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<int> to_duplicate; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(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<int> to_select; + HashMap<int, int> remap; + + for (Set<int>::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<VisualScriptNode> node = script->get_node(F->get()); + + Ref<VisualScriptNode> 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<VisualScript::SequenceConnection> 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<VisualScript::DataConnection> 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<GraphNode>(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(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(script, false); // do not 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<InputEvent> &p_event) { + ERR_FAIL_COND(p_event.is_null()); + + // GUI input for VS Editor Plugin + Ref<InputEventMouseButton> key = p_event; + + if (key.is_valid() && key->is_pressed()) { + mouse_up_position = get_screen_position() + get_local_mouse_position(); + } +} + +void VisualScriptEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> key = p_event; + + if (key.is_valid() && key->is_pressed() && key->get_button_mask() == MouseButton::RIGHT) { + bool is_empty_selection = true; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn && gn->is_selected()) { + is_empty_selection = false; + break; + } + } + if (is_empty_selection && clipboard->nodes.is_empty()) { + _generic_search(); + } else { + popup_menu->set_item_disabled(int(EDIT_CUT_NODES), is_empty_selection); + popup_menu->set_item_disabled(int(EDIT_COPY_NODES), is_empty_selection); + popup_menu->set_item_disabled(int(EDIT_PASTE_NODES), clipboard->nodes.is_empty()); + popup_menu->set_item_disabled(int(EDIT_DELETE_NODES), is_empty_selection); + popup_menu->set_item_disabled(int(EDIT_DUPLICATE_NODES), is_empty_selection); + popup_menu->set_item_disabled(int(EDIT_CLEAR_COPY_BUFFER), clipboard->nodes.is_empty()); + + popup_menu->set_position(mouse_up_position); + popup_menu->popup(); + } + } +} + +void VisualScriptEditor::_members_gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventKey> 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<InputEventMouseButton> 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<VisualScriptFunction> 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<int> lst; + script->get_node_list(&lst); + for (int &F : lst) { + Ref<VisualScriptFunctionCall> 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<InputEvent> &p_event) { + if (!function_name_edit->is_visible()) { + return; + } + + Ref<InputEventKey> 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.is_empty()) { + 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<VisualScriptEditor *>(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<VisualScriptEditor *>(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<VisualScriptEditor *>(this)->_show_hint(vformat(TTR("Hold %s to drop a simple reference to the node."), find_keycode_name(Key::META))); +#else + const_cast<VisualScriptEditor *>(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<VisualScriptEditor *>(this)->_show_hint(vformat(TTR("Hold %s to drop a Variable Setter."), find_keycode_name(Key::META))); +#else + const_cast<VisualScriptEditor *>(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<Script> &script) { + if (p_edited_scene != p_current_node && p_current_node->get_owner() != p_edited_scene) { + return nullptr; + } + + Ref<Script> scr = p_current_node->get_script(); + + if (scr.is_valid() && scr == script) { + return p_current_node; + } + + for (int i = 0; i < p_current_node->get_child_count(); i++) { + Node *n = _find_script_node(p_edited_scene, p_current_node->get_child(i), script); + if (n) { + return n; + } + } + + return nullptr; +} + +void VisualScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (p_from != graph) { + return; + } + + Dictionary d = p_data; + + if (!d.has("type")) { + return; + } + + if (String(d["type"]) == "visual_script_node_drag") { + if (!d.has("node_type") || String(d["node_type"]) == "Null") { + return; + } + + Vector2 pos = _get_pos_in_graph(p_point); + + int new_id = _create_new_node_from_name(d["node_type"], pos); + + Node *node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (String(d["type"]) == "visual_script_variable_drag") { +#ifdef OSX_ENABLED + bool use_set = Input::get_singleton()->is_key_pressed(Key::META); +#else + bool use_set = Input::get_singleton()->is_key_pressed(Key::CTRL); +#endif + Vector2 pos = _get_pos_in_graph(p_point); + + Ref<VisualScriptNode> vnode; + if (use_set) { + Ref<VisualScriptPropertySet> pset; + pset.instantiate(); + vnode = pset; + } else { + Ref<VisualScriptPropertyGet> pget; + pget.instantiate(); + vnode = pget; + } + + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(vnode.ptr(), "set_property", d["variable"]); + undo_redo->add_do_method(vnode.ptr(), "set_base_script", script->get_path()); + + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + Node *node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (String(d["type"]) == "visual_script_function_drag") { + Vector2 pos = _get_pos_in_graph(p_point); + + Ref<VisualScriptFunctionCall> vnode; + vnode.instantiate(); + vnode->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SELF); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); + undo_redo->add_do_method(vnode.ptr(), "set_base_type", script->get_instance_base_type()); + undo_redo->add_do_method(vnode.ptr(), "set_function", d["function"]); + + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + Node *node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (String(d["type"]) == "visual_script_signal_drag") { + Vector2 pos = _get_pos_in_graph(p_point); + + Ref<VisualScriptEmitSignal> vnode; + vnode.instantiate(); + vnode->set_signal(d["signal"]); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + Node *node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (String(d["type"]) == "resource") { + Vector2 pos = _get_pos_in_graph(p_point); + + Ref<VisualScriptPreload> prnode; + prnode.instantiate(); + prnode->set_preload(d["resource"]); + + int new_id = script->get_available_id(); + + undo_redo->create_action(TTR("Add Preload Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + Node *node = graph->get_node(itos(new_id)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + + if (String(d["type"]) == "files") { +#ifdef OSX_ENABLED + bool use_preload = Input::get_singleton()->is_key_pressed(Key::META); +#else + bool use_preload = Input::get_singleton()->is_key_pressed(Key::CTRL); +#endif + Vector2 pos = _get_pos_in_graph(p_point); + + Array files = d["files"]; + + List<int> new_ids; + int new_id = script->get_available_id(); + + if (files.size()) { + undo_redo->create_action(TTR("Add Node(s)")); + + for (int i = 0; i < files.size(); i++) { + Ref<Resource> res = ResourceLoader::load(files[i]); + if (!res.is_valid()) { + continue; + } + Ref<Script> drop_script = ResourceLoader::load(files[i]); + if (drop_script.is_valid() && drop_script->is_tool() && drop_script->get_instance_base_type() == "VisualScriptCustomNode" && !use_preload) { + Ref<VisualScriptCustomNode> vscn; + vscn.instantiate(); + vscn->set_script(drop_script); + + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vscn, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + } else { + Ref<VisualScriptPreload> prnode; + prnode.instantiate(); + prnode->set_preload(res); + + undo_redo->add_do_method(script.ptr(), "add_node", new_id, prnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + } + new_ids.push_back(new_id); + new_id++; + pos += Vector2(20, 20); + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + } + + for (int &E : new_ids) { + Node *node = graph->get_node(itos(E)); + if (node) { + graph->set_selected(node); + _node_selected(node); + } + } + } + + if (String(d["type"]) == "nodes") { + Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); + + if (!sn) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop nodes because script '%s' is not used in this scene."), get_name())); + return; + } + +#ifdef OSX_ENABLED + bool use_node = Input::get_singleton()->is_key_pressed(Key::META); +#else + bool use_node = Input::get_singleton()->is_key_pressed(Key::CTRL); +#endif + + Array nodes = d["nodes"]; + + Vector2 pos = _get_pos_in_graph(p_point); + + undo_redo->create_action(TTR("Add Node(s) From Tree")); + int base_id = script->get_available_id(); + + if (use_node || nodes.size() > 1) { + for (int i = 0; i < nodes.size(); i++) { + NodePath np = nodes[i]; + Node *node = get_node(np); + if (!node) { + continue; + } + + Ref<VisualScriptNode> n; + + Ref<VisualScriptSceneNode> scene_node; + scene_node.instantiate(); + scene_node->set_node_path(sn->get_path_to(node)); + n = scene_node; + + undo_redo->add_do_method(script.ptr(), "add_node", base_id, n, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", base_id); + + base_id++; + pos += Vector2(25, 25); + } + + } else { + NodePath np = nodes[0]; + Node *node = get_node(np); + drop_position = pos; + drop_node = node; + drop_path = sn->get_path_to(node); + new_connect_node_select->select_from_instance(node, false); + } + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + } + + if (String(d["type"]) == "obj_property") { + Node *sn = _find_script_node(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root(), script); + + if (!sn && !Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Can't drop properties because script '%s' is not used in this scene.\nDrop holding 'Shift' to just copy the signature."), get_name())); + return; + } + + Object *obj = d["object"]; + + if (!obj) { + return; + } + + Node *node = Object::cast_to<Node>(obj); + Vector2 pos = _get_pos_in_graph(p_point); + +#ifdef OSX_ENABLED + bool use_get = Input::get_singleton()->is_key_pressed(Key::META); +#else + bool use_get = Input::get_singleton()->is_key_pressed(Key::CTRL); +#endif + + if (!node || Input::get_singleton()->is_key_pressed(Key::SHIFT)) { + if (use_get) { + undo_redo->create_action(TTR("Add Getter Property")); + } else { + undo_redo->create_action(TTR("Add Setter Property")); + } + + int base_id = script->get_available_id(); + + Ref<VisualScriptNode> vnode; + + if (!use_get) { + Ref<VisualScriptPropertySet> pset; + pset.instantiate(); + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + pset->set_base_type(obj->get_class()); + vnode = pset; + } else { + Ref<VisualScriptPropertyGet> pget; + pget.instantiate(); + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + pget->set_base_type(obj->get_class()); + vnode = pget; + } + + undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, pos); + undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]); + if (!obj->get_script().is_null()) { + undo_redo->add_do_method(vnode.ptr(), "set_base_script", Ref<Script>(obj->get_script())->get_path()); + } + if (!use_get) { + undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]); + } + + undo_redo->add_undo_method(script.ptr(), "remove_node", base_id); + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + + } else { + if (use_get) { + undo_redo->create_action(TTR("Add Getter Property")); + } else { + undo_redo->create_action(TTR("Add Setter Property")); + } + + int base_id = script->get_available_id(); + + Ref<VisualScriptNode> vnode; + + if (!use_get) { + Ref<VisualScriptPropertySet> pset; + pset.instantiate(); + if (sn == node) { + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); + } else { + pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); + pset->set_base_path(sn->get_path_to(node)); + } + vnode = pset; + } else { + Ref<VisualScriptPropertyGet> pget; + pget.instantiate(); + if (sn == node) { + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); + } else { + pget->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); + pget->set_base_path(sn->get_path_to(node)); + } + vnode = pget; + } + undo_redo->add_do_method(script.ptr(), "add_node", base_id, vnode, pos); + undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]); + if (!obj->get_script().is_null()) { + undo_redo->add_do_method(vnode.ptr(), "set_base_script", Ref<Script>(obj->get_script())->get_path()); + } + if (!use_get) { + undo_redo->add_do_method(vnode.ptr(), "set_default_input_value", 0, d["value"]); + } + + undo_redo->add_undo_method(script.ptr(), "remove_node", base_id); + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + } + } +} + +void VisualScriptEditor::_draw_color_over_button(Object *obj, Color p_color) { + Button *button = Object::cast_to<Button>(obj); + if (!button) { + return; + } + + Ref<StyleBox> normal = get_theme_stylebox(SNAME("normal"), SNAME("Button")); + button->draw_rect(Rect2(normal->get_offset(), button->get_size() - normal->get_minimum_size()), p_color); +} + +void VisualScriptEditor::_button_resource_previewed(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) { + Array ud = p_ud; + ERR_FAIL_COND(ud.size() != 2); + + ObjectID id = ud[0]; + Object *obj = ObjectDB::get_instance(id); + + if (!obj) { + return; + } + + Button *b = Object::cast_to<Button>(obj); + ERR_FAIL_COND(!b); + + if (p_preview.is_null()) { + b->set_text(ud[1]); + } else { + b->set_icon(p_preview); + } +} + +///////////////////////// + +void VisualScriptEditor::apply_code() { +} + +RES VisualScriptEditor::get_edited_resource() const { + return script; +} + +void VisualScriptEditor::set_edited_resource(const RES &p_res) { + ERR_FAIL_COND(script.is_valid()); + ERR_FAIL_COND(p_res.is_null()); + script = p_res; + signal_editor->script = script; + signal_editor->undo_redo = undo_redo; + variable_editor->script = script; + variable_editor->undo_redo = undo_redo; + + script->connect("node_ports_changed", callable_mp(this, &VisualScriptEditor::_node_ports_changed)); + + _update_graph(); + call_deferred(SNAME("_update_members")); +} + +void VisualScriptEditor::enable_editor() { +} + +Vector<String> VisualScriptEditor::get_functions() { + return Vector<String>(); +} + +void VisualScriptEditor::reload_text() { +} + +String VisualScriptEditor::get_name() { + String name; + + name = script->get_path().get_file(); + if (name.is_empty()) { + // This appears for newly created built-in scripts before saving the scene. + name = TTR("[unsaved]"); + } else if (script->is_built_in()) { + const String &script_name = script->get_name(); + if (!script_name.is_empty()) { + // If the built-in script has a custom resource name defined, + // display the built-in script name as follows: `ResourceName (scene_file.tscn)` + name = vformat("%s (%s)", script_name, name.get_slice("::", 0)); + } + } + + if (is_unsaved()) { + name += "(*)"; + } + + return name; +} + +Ref<Texture2D> VisualScriptEditor::get_theme_icon() { + String icon_name = "VisualScript"; + if (script->is_built_in()) { + icon_name += "Internal"; + } + + if (Control::has_theme_icon(icon_name, "EditorIcons")) { + return Control::get_theme_icon(icon_name, SNAME("EditorIcons")); + } + + return Control::get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); +} + +bool VisualScriptEditor::is_unsaved() { + bool unsaved = + script->is_edited() || + script->are_subnodes_edited() || + script->get_path().is_empty(); // In memory. + return unsaved; +} + +Variant VisualScriptEditor::get_edit_state() { + Dictionary d; + d["scroll"] = graph->get_scroll_ofs(); + d["zoom"] = graph->get_zoom(); + d["using_snap"] = graph->is_using_snap(); + d["snap"] = graph->get_snap(); + return d; +} + +void VisualScriptEditor::set_edit_state(const Variant &p_state) { + Dictionary d = p_state; + + _update_graph(); + _update_members(); + + if (d.has("scroll")) { + graph->set_scroll_ofs(d["scroll"]); + } + if (d.has("zoom")) { + graph->set_zoom(d["zoom"]); + } + if (d.has("snap")) { + graph->set_snap(d["snap"]); + } + if (d.has("snap_enabled")) { + graph->set_use_snap(d["snap_enabled"]); + } +} + +void VisualScriptEditor::_center_on_node(int p_id) { + Node *n = graph->get_node(itos(p_id)); + GraphNode *gn = Object::cast_to<GraphNode>(n); + + // Clear selection. + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gnd = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gnd) { + gnd->set_selected(false); + } + } + + if (gn) { + gn->set_selected(true); + Vector2 new_scroll = gn->get_position_offset() - graph->get_size() * 0.5 + gn->get_size() * 0.5; + graph->set_scroll_ofs(new_scroll); + script->set_scroll(new_scroll / EDSCALE); + script->set_edited(true); + } +} + +void VisualScriptEditor::goto_line(int p_line, bool p_with_error) { + p_line += 1; // Add one because script lines begin from 0. + + if (p_with_error) { + error_line = p_line; + } + + if (script->has_node(p_line)) { + _update_graph(); + _update_members(); + + call_deferred(SNAME("call_deferred"), "_center_on_node", p_line); // The editor might be just created and size might not exist yet. + } +} + +void VisualScriptEditor::set_executing_line(int p_line) { + // todo: add a way to show which node is executing right now. +} + +void VisualScriptEditor::clear_executing_line() { + // todo: add a way to show which node is executing right now. +} + +void VisualScriptEditor::trim_trailing_whitespace() { +} + +void VisualScriptEditor::insert_final_newline() { +} + +void VisualScriptEditor::convert_indent_to_spaces() { +} + +void VisualScriptEditor::convert_indent_to_tabs() { +} + +void VisualScriptEditor::ensure_focus() { + graph->grab_focus(); +} + +void VisualScriptEditor::tag_saved_version() { +} + +void VisualScriptEditor::reload(bool p_soft) { + _update_graph(); +} + +Array VisualScriptEditor::get_breakpoints() { + Array breakpoints; + List<StringName> functions; + script->get_function_list(&functions); + for (int i = 0; i < functions.size(); i++) { + List<int> nodes; + script->get_node_list(&nodes); + for (int &F : nodes) { + Ref<VisualScriptNode> vsn = script->get_node(F); + if (vsn->is_breakpoint()) { + breakpoints.push_back(F - 1); // Subtract 1 because breakpoints in text start from zero. + } + } + } + return breakpoints; +} + +void VisualScriptEditor::add_callback(const String &p_function, PackedStringArray p_args) { + if (script->has_function(p_function)) { + _update_members(); + _update_graph(); + _center_on_node(script->get_function_node_id(p_function)); + return; + } + + Ref<VisualScriptFunction> func; + func.instantiate(); + for (int i = 0; i < p_args.size(); i++) { + String name = p_args[i]; + Variant::Type type = Variant::NIL; + + if (name.contains(":")) { + String tt = name.get_slice(":", 1); + name = name.get_slice(":", 0); + for (int j = 0; j < Variant::VARIANT_MAX; j++) { + String tname = Variant::get_type_name(Variant::Type(j)); + if (tname == tt) { + type = Variant::Type(j); + break; + } + } + } + + func->add_argument(type, name); + } + int fn_id = script->get_available_id(); + func->set_name(p_function); + script->add_function(p_function, fn_id); + script->add_node(fn_id, func); + + _update_members(); + _update_graph(); + + _center_on_node(script->get_function_node_id(p_function)); +} + +bool VisualScriptEditor::show_members_overview() { + return false; +} + +void VisualScriptEditor::update_settings() { + _update_graph(); +} + +void VisualScriptEditor::set_debugger_active(bool p_active) { + if (!p_active) { + error_line = -1; + _update_graph(); //clear line break + } +} + +Control *VisualScriptEditor::get_base_editor() const { + return graph; +} + +void VisualScriptEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { +} + +Control *VisualScriptEditor::get_edit_menu() { + return edit_menu; +} + +void VisualScriptEditor::_change_base_type() { + select_base_type->popup_create(true, true); +} + +void VisualScriptEditor::_toggle_tool_script() { + script->set_tool_enabled(!script->is_tool()); +} + +void VisualScriptEditor::clear_edit_menu() { + memdelete(edit_menu); + memdelete(members_section); +} + +void VisualScriptEditor::_change_base_type_callback() { + String bt = select_base_type->get_selected_type(); + + ERR_FAIL_COND(bt.is_empty()); + undo_redo->create_action(TTR("Change Base Type")); + undo_redo->add_do_method(script.ptr(), "set_instance_base_type", bt); + undo_redo->add_undo_method(script.ptr(), "set_instance_base_type", script->get_instance_base_type()); + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->commit_action(); +} + +void VisualScriptEditor::_node_selected(Node *p_node) { + Ref<VisualScriptNode> vnode = p_node->get_meta("__vnode"); + if (vnode.is_null()) { + return; + } + + EditorNode::get_singleton()->push_item(vnode.ptr()); //edit node in inspector +} + +static bool _get_out_slot(const Ref<VisualScriptNode> &p_node, int p_slot, int &r_real_slot, bool &r_sequence) { + if (p_slot < p_node->get_output_sequence_port_count()) { + r_sequence = true; + r_real_slot = p_slot; + + return true; + } + + r_real_slot = p_slot - p_node->get_output_sequence_port_count(); + r_sequence = false; + + return (r_real_slot < p_node->get_output_value_port_count()); +} + +static bool _get_in_slot(const Ref<VisualScriptNode> &p_node, int p_slot, int &r_real_slot, bool &r_sequence) { + if (p_slot == 0 && p_node->has_input_sequence_port()) { + r_sequence = true; + r_real_slot = 0; + return true; + } + + r_real_slot = p_slot - (p_node->has_input_sequence_port() ? 1 : 0); + r_sequence = false; + + return r_real_slot < p_node->get_input_value_port_count(); +} + +void VisualScriptEditor::_begin_node_move() { + undo_redo->create_action(TTR("Move Node(s)")); +} + +void VisualScriptEditor::_end_node_move() { + undo_redo->commit_action(); +} + +void VisualScriptEditor::_move_node(int p_id, const Vector2 &p_to) { + if (!script->has_node(p_id)) { + return; + } + + Node *node = graph->get_node(itos(p_id)); + + if (Object::cast_to<GraphNode>(node)) { + Object::cast_to<GraphNode>(node)->set_position_offset(p_to); + } + + script->set_node_position(p_id, p_to / EDSCALE); +} + +void VisualScriptEditor::_node_moved(Vector2 p_from, Vector2 p_to, int p_id) { + undo_redo->add_do_method(this, "_move_node", p_id, p_to); + undo_redo->add_undo_method(this, "_move_node", p_id, p_from); +} + +void VisualScriptEditor::_remove_node(int p_id) { + undo_redo->create_action(TTR("Remove VisualScript Node")); + + undo_redo->add_do_method(script.ptr(), "remove_node", p_id); + undo_redo->add_undo_method(script.ptr(), "add_node", p_id, script->get_node(p_id), script->get_node_position(p_id)); + + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(&sequence_conns); + + for (const VisualScript::SequenceConnection &E : sequence_conns) { + if (E.from_node == p_id || E.to_node == p_id) { + undo_redo->add_undo_method(script.ptr(), "sequence_connect", E.from_node, E.from_output, E.to_node); + } + } + + List<VisualScript::DataConnection> data_conns; + script->get_data_connection_list(&data_conns); + + for (const VisualScript::DataConnection &E : data_conns) { + if (E.from_node == p_id || E.to_node == p_id) { + 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::_node_ports_changed(int p_id) { + _update_graph(p_id); +} + +bool VisualScriptEditor::node_has_sequence_connections(int p_id) { + List<VisualScript::SequenceConnection> sequence_conns; + script->get_sequence_connection_list(&sequence_conns); + + for (const VisualScript::SequenceConnection &E : sequence_conns) { + int from = E.from_node; + int to = E.to_node; + + if (to == p_id || from == p_id) { + return true; + } + } + + return false; +} + +void VisualScriptEditor::_graph_connected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) { + Ref<VisualScriptNode> from_node = script->get_node(p_from.to_int()); + ERR_FAIL_COND(!from_node.is_valid()); + + bool from_seq; + int from_port; + + if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq)) { + return; //can't connect this, it's invalid + } + + Ref<VisualScriptNode> to_node = script->get_node(p_to.to_int()); + ERR_FAIL_COND(!to_node.is_valid()); + + bool to_seq; + int to_port; + + if (!_get_in_slot(to_node, p_to_slot, to_port, to_seq)) { + return; //can't connect this, it's invalid + } + + ERR_FAIL_COND(from_seq != to_seq); + + // Checking to prevent warnings. + if (from_seq) { + if (script->has_sequence_connection(p_from.to_int(), from_port, p_to.to_int())) { + return; + } + } else if (script->has_data_connection(p_from.to_int(), from_port, p_to.to_int(), to_port)) { + return; + } + + // Preventing connection to itself. + if (p_from.to_int() == p_to.to_int()) { + return; + } + + // Do all the checks here. + StringName func; // This the func where we store the one the nodes at the end of the resolution on having multiple nodes. + + undo_redo->create_action(TTR("Connect Nodes")); + + if (from_seq) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", p_from.to_int(), from_port, p_to.to_int()); + // This undo error on undo after move can't be removed without painful gymnastics + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", p_from.to_int(), from_port, p_to.to_int()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + } else { + bool converted = false; + + Ref<VisualScriptOperator> oper = to_node; + if (oper.is_valid() && oper->get_typed() == Variant::NIL) { + // It's an operator Node and if the type is already nil + if (from_node->get_output_value_port_info(from_port).type != Variant::NIL) { + oper->set_typed(from_node->get_output_value_port_info(from_port).type); + } + } + + Ref<VisualScriptOperator> operf = from_node; + if (operf.is_valid() && operf->get_typed() == Variant::NIL) { + // It's an operator Node and if the type is already nil + if (to_node->get_input_value_port_info(to_port).type != Variant::NIL) { + operf->set_typed(to_node->get_input_value_port_info(to_port).type); + } + } + + // Disconnect current, and connect the new one + if (script->is_input_value_port_connected(p_to.to_int(), to_port)) { + if (can_swap && data_disconnect_node == p_to.to_int()) { + int conn_from; + int conn_port; + script->get_input_value_port_connection_source(p_to.to_int(), to_port, &conn_from, &conn_port); + undo_redo->add_do_method(script.ptr(), "data_disconnect", conn_from, conn_port, p_to.to_int(), to_port); + undo_redo->add_do_method(script.ptr(), "data_connect", conn_from, conn_port, data_disconnect_node, data_disconnect_port); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", conn_from, conn_port, data_disconnect_node, data_disconnect_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", conn_from, conn_port, p_to.to_int(), to_port); + can_swap = false; // swapped + } else { + int conn_from; + int conn_port; + script->get_input_value_port_connection_source(p_to.to_int(), to_port, &conn_from, &conn_port); + undo_redo->add_do_method(script.ptr(), "data_disconnect", conn_from, conn_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", conn_from, conn_port, p_to.to_int(), to_port); + } + } + if (!converted) { + undo_redo->add_do_method(script.ptr(), "data_connect", p_from.to_int(), from_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", p_from.to_int(), from_port, p_to.to_int(), to_port); + + // Update nodes in graph + undo_redo->add_do_method(this, "_update_graph", p_from.to_int()); + undo_redo->add_do_method(this, "_update_graph", p_to.to_int()); + undo_redo->add_undo_method(this, "_update_graph", p_from.to_int()); + undo_redo->add_undo_method(this, "_update_graph", p_to.to_int()); + } else { + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + } + } + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) { + Ref<VisualScriptNode> from_node = script->get_node(p_from.to_int()); + ERR_FAIL_COND(!from_node.is_valid()); + + bool from_seq; + int from_port; + + if (!_get_out_slot(from_node, p_from_slot, from_port, from_seq)) { + return; // Can't connect this, it's invalid. + } + + Ref<VisualScriptNode> to_node = script->get_node(p_to.to_int()); + ERR_FAIL_COND(!to_node.is_valid()); + + bool to_seq; + int to_port; + + if (!_get_in_slot(to_node, p_to_slot, to_port, to_seq)) { + return; // Can't connect this, it's invalid. + } + + ERR_FAIL_COND(from_seq != to_seq); + + undo_redo->create_action(TTR("Disconnect Nodes")); + + if (from_seq) { + undo_redo->add_do_method(script.ptr(), "sequence_disconnect", p_from.to_int(), from_port, p_to.to_int()); + undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_from.to_int(), from_port, p_to.to_int()); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + } else { + can_swap = true; + data_disconnect_node = p_to.to_int(); + data_disconnect_port = to_port; + + undo_redo->add_do_method(script.ptr(), "data_disconnect", p_from.to_int(), from_port, p_to.to_int(), to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", p_from.to_int(), from_port, p_to.to_int(), to_port); + // Update relevant nodes in the graph. + undo_redo->add_do_method(this, "_update_graph", p_from.to_int()); + undo_redo->add_do_method(this, "_update_graph", p_to.to_int()); + undo_redo->add_undo_method(this, "_update_graph", p_from.to_int()); + undo_redo->add_undo_method(this, "_update_graph", p_to.to_int()); + } + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_graph_connect_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_pos) { + Node *node = graph->get_node(p_from); + GraphNode *gn = Object::cast_to<GraphNode>(node); + if (!gn) { + return; + } + + Ref<VisualScriptNode> vsn = script->get_node(p_from.to_int()); + if (!vsn.is_valid()) { + return; + } + if (vsn->get_output_value_port_count() || vsn->get_output_sequence_port_count()) { + port_action_pos = p_release_pos; + } + + if (p_from_slot < vsn->get_output_sequence_port_count()) { + port_action_node = p_from.to_int(); + port_action_output = p_from_slot; + _port_action_menu(CREATE_ACTION); + } else { + port_action_output = p_from_slot - vsn->get_output_sequence_port_count(); + port_action_node = p_from.to_int(); + _port_action_menu(CREATE_CALL_SET_GET); + } +} + +VisualScriptNode::TypeGuess VisualScriptEditor::_guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &visited_nodes) { + VisualScriptNode::TypeGuess tg; + tg.type = Variant::NIL; + + if (visited_nodes.has(p_port_action_node)) { + return tg; //no loop + } + + visited_nodes.insert(p_port_action_node); + + Ref<VisualScriptNode> node = script->get_node(p_port_action_node); + + if (!node.is_valid() || node->get_output_value_port_count() <= p_port_action_output) { + return tg; + } + + Vector<VisualScriptNode::TypeGuess> in_guesses; + + for (int i = 0; i < node->get_input_value_port_count(); i++) { + PropertyInfo pi = node->get_input_value_port_info(i); + VisualScriptNode::TypeGuess g; + g.type = pi.type; + + if (g.type == Variant::NIL || g.type == Variant::OBJECT) { + // Any or object input, must further guess what this is. + int from_node; + int from_port; + + if (script->get_input_value_port_connection_source(p_port_action_node, i, &from_node, &from_port)) { + g = _guess_output_type(from_node, from_port, visited_nodes); + } else { + Variant defval = node->get_default_input_value(i); + if (defval.get_type() == Variant::OBJECT) { + Object *obj = defval; + + if (obj) { + g.type = Variant::OBJECT; + g.gdclass = obj->get_class(); + g.script = obj->get_script(); + } + } + } + } + + in_guesses.push_back(g); + } + + return node->guess_output_type(in_guesses.ptrw(), p_port_action_output); +} + +void VisualScriptEditor::_port_action_menu(int p_option) { + Set<int> vn; + + switch (p_option) { + case CREATE_CALL_SET_GET: { + Ref<VisualScriptFunctionCall> n; + n.instantiate(); + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + + if (tg.gdclass != StringName()) { + n->set_base_type(tg.gdclass); + } else { + n->set_base_type("Object"); + } + String type_string; + String base_script = ""; + if (script->get_node(port_action_node)->get_output_value_port_count() > 0) { + type_string = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint_string; + VisualScriptFunctionCall *vsfc = Object::cast_to<VisualScriptFunctionCall>(*script->get_node(port_action_node)); + if (vsfc) { + base_script = vsfc->get_base_script(); + } else { + VisualScriptPropertyGet *vspg = Object::cast_to<VisualScriptPropertyGet>(*script->get_node(port_action_node)); + if (vspg) { + base_script = vspg->get_base_script(); + } else { + VisualScriptPropertySet *vsps = Object::cast_to<VisualScriptPropertySet>(*script->get_node(port_action_node)); + if (vsps) { + base_script = vsps->get_base_script(); + } + } + } + } + if (tg.type == Variant::OBJECT) { + if (tg.script.is_valid()) { + new_connect_node_select->select_from_script(tg.script); + } else if (type_string != String()) { + new_connect_node_select->select_from_base_type(type_string, base_script); + } else { + new_connect_node_select->select_from_base_type(n->get_base_type(), base_script); + } + } else if (tg.type == Variant::NIL) { + new_connect_node_select->select_from_base_type("", base_script); + } else { + new_connect_node_select->select_from_basic_type(tg.type); + } + // Ensure that the dialog fits inside the graph. + Vector2 pos = mouse_up_position; + 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; + new_connect_node_select->set_position(pos); + } break; + case CREATE_ACTION: { + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + PropertyInfo property_info; + if (script->get_node(port_action_node)->get_output_value_port_count() > 0) { + property_info = script->get_node(port_action_node)->get_output_value_port_info(port_action_output); + } + if (tg.type == Variant::OBJECT) { + if (property_info.type == Variant::OBJECT && !property_info.hint_string.is_empty()) { + new_connect_node_select->select_from_action(property_info.hint_string); + } else { + new_connect_node_select->select_from_action(""); + } + } else if (tg.type == Variant::NIL) { + new_connect_node_select->select_from_action(""); + } else { + new_connect_node_select->select_from_action(Variant::get_type_name(tg.type)); + } + // Ensure that the dialog fits inside the graph. + Vector2 pos = mouse_up_position; + 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; + new_connect_node_select->set_position(pos); + } break; + } +} + +void VisualScriptEditor::connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id) { + undo_redo->create_action(TTR("Connect Node Data")); + VisualScriptReturn *vnode_return = Object::cast_to<VisualScriptReturn>(vnode.ptr()); + if (vnode_return != nullptr && vnode_old->get_output_value_port_count() > 0) { + vnode_return->set_enable_return_value(true); + } + if (vnode_old->get_output_value_port_count() <= 0) { + undo_redo->commit_action(); + return; + } + if (vnode->get_input_value_port_count() <= 0) { + undo_redo->commit_action(); + return; + } + int port = port_action_output; + int value_count = vnode_old->get_output_value_port_count(); + if (port >= value_count) { + port = 0; + } + undo_redo->add_do_method(script.ptr(), "data_connect", port_action_node, port, new_id, 0); + undo_redo->add_undo_method(script.ptr(), "data_disconnect", port_action_node, port, new_id, 0); + undo_redo->commit_action(); +} + +void VisualScriptEditor::_selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting) { +#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 + Vector2 pos = _get_pos_in_graph(port_action_pos); + + Set<int> vn; + bool port_node_exists = true; + + if (drop_position != Vector2()) { + pos = drop_position; + } + drop_position = Vector2(); + + Ref<VisualScriptNode> vnode; + Ref<VisualScriptNode> vnode_old; + if (port_node_exists && p_connecting) { + vnode_old = script->get_node(port_action_node); + } + + if (p_category.begins_with("VisualScriptNode")) { + Ref<VisualScriptNode> n = VisualScriptLanguage::singleton->create_node_from_name(p_text); + + if (Object::cast_to<VisualScriptTypeCast>(n.ptr()) && vnode_old.is_valid()) { + Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type; + String hint_name = vnode_old->get_output_value_port_info(port_action_output).hint_string; + + if (type == Variant::OBJECT) { + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(hint_name); + } else if (type == Variant::NIL) { + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(""); + } else { + Object::cast_to<VisualScriptTypeCast>(n.ptr())->set_base_type(Variant::get_type_name(type)); + } + } + vnode = n; + } + + if (p_category == String("Class") && !p_connecting) { + Ref<VisualScriptFunctionCall> n; + n.instantiate(); + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SINGLETON); + n->set_singleton("ClassDB"); + n->set_function("instantiate"); + // Did not find a way to edit the input port value + vnode = n; + } else if (p_category == String("class_method")) { + Ref<VisualScriptFunctionCall> n; + n.instantiate(); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptFunctionCall::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } + } + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } + } + vnode = n; + } else if (p_category == String("class_property")) { + Vector<String> property_path = p_text.split(":"); + if (held_ctrl) { + Ref<VisualScriptPropertySet> n; + n.instantiate(); + n->set_property(property_path[1]); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptPropertySet::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } + } + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } + } + vnode = n; + } else { + Ref<VisualScriptPropertyGet> n; + n.instantiate(); + n->set_property(property_path[1]); + if (!drop_path.is_empty()) { + if (drop_path == ".") { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_SELF); + } else { + n->set_call_mode(VisualScriptPropertyGet::CALL_MODE_NODE_PATH); + n->set_base_path(drop_path); + } + } + if (drop_node) { + n->set_base_type(drop_node->get_class()); + if (drop_node->get_script_instance()) { + n->set_base_script(drop_node->get_script_instance()->get_script()->get_path()); + } + } + vnode = n; + } + } else if (p_category == String("class_constant")) { + Vector<String> property_path = p_text.split(":"); + if (ClassDB::class_exists(property_path[0])) { + Ref<VisualScriptClassConstant> n; + n.instantiate(); + n->set_base_type(property_path[0]); + n->set_class_constant(property_path[1]); + vnode = n; + } else { + Ref<VisualScriptBasicTypeConstant> n; + n.instantiate(); + if (property_path[0] == "Nil") { + n->set_basic_type(Variant::NIL); + } else if (property_path[0] == "bool") { + n->set_basic_type(Variant::BOOL); + } else if (property_path[0] == "int") { + n->set_basic_type(Variant::INT); + } else if (property_path[0] == "float") { + n->set_basic_type(Variant::FLOAT); + } else if (property_path[0] == "String") { + n->set_basic_type(Variant::STRING); + } else if (property_path[0] == "Vector2") { + n->set_basic_type(Variant::VECTOR2); + } else if (property_path[0] == "Vector2i") { + n->set_basic_type(Variant::VECTOR2I); + } else if (property_path[0] == "Rect2") { + n->set_basic_type(Variant::RECT2); + } else if (property_path[0] == "Rect2i") { + n->set_basic_type(Variant::RECT2I); + } else if (property_path[0] == "Transform2D") { + n->set_basic_type(Variant::TRANSFORM2D); + } else if (property_path[0] == "Vector3") { + n->set_basic_type(Variant::VECTOR3); + } else if (property_path[0] == "Vector3i") { + n->set_basic_type(Variant::VECTOR3I); + } else if (property_path[0] == "Plane") { + n->set_basic_type(Variant::PLANE); + } else if (property_path[0] == "ABB") { + n->set_basic_type(Variant::AABB); + } else if (property_path[0] == "Quaternion") { + n->set_basic_type(Variant::QUATERNION); + } else if (property_path[0] == "Basis") { + n->set_basic_type(Variant::BASIS); + } else if (property_path[0] == "Transform3D") { + n->set_basic_type(Variant::TRANSFORM3D); + } else if (property_path[0] == "Color") { + n->set_basic_type(Variant::COLOR); + } else if (property_path[0] == "RID") { + n->set_basic_type(Variant::RID); + } else if (property_path[0] == "Object") { + n->set_basic_type(Variant::OBJECT); + } else if (property_path[0] == "Callable") { + n->set_basic_type(Variant::CALLABLE); + } else if (property_path[0] == "Signal") { + n->set_basic_type(Variant::SIGNAL); + } else if (property_path[0] == "StringName") { + n->set_basic_type(Variant::STRING_NAME); + } else if (property_path[0] == "NodePath") { + n->set_basic_type(Variant::NODE_PATH); + } else if (property_path[0] == "Dictionary") { + n->set_basic_type(Variant::DICTIONARY); + } else if (property_path[0] == "Array") { + n->set_basic_type(Variant::ARRAY); + } else if (property_path[0] == "PackedByteArray") { + n->set_basic_type(Variant::PACKED_BYTE_ARRAY); + } else if (property_path[0] == "PackedInt32Array") { + n->set_basic_type(Variant::PACKED_INT32_ARRAY); + } else if (property_path[0] == "PackedInt64Array") { + n->set_basic_type(Variant::PACKED_INT64_ARRAY); + } else if (property_path[0] == "PackedFloat32Array") { + n->set_basic_type(Variant::PACKED_FLOAT32_ARRAY); + } else if (property_path[0] == "PackedStringArray") { + n->set_basic_type(Variant::PACKED_STRING_ARRAY); + } else if (property_path[0] == "PackedVector2Array") { + n->set_basic_type(Variant::PACKED_VECTOR2_ARRAY); + } else if (property_path[0] == "PackedVector3Array") { + n->set_basic_type(Variant::PACKED_VECTOR3_ARRAY); + } else if (property_path[0] == "PackedColorArray") { + n->set_basic_type(Variant::PACKED_COLOR_ARRAY); + } + n->set_basic_type_constant(property_path[1]); + vnode = n; + } + + } else if (p_category == String("class_signal")) { + Vector<String> property_path = p_text.split(":"); + ERR_FAIL_COND(!(script->has_custom_signal(property_path[1]) || ClassDB::has_signal(script->get_instance_base_type(), property_path[1]))); + + Ref<VisualScriptEmitSignal> n; + n.instantiate(); + n->set_signal(property_path[1]); + vnode = n; + } + if (vnode == nullptr) { + print_error("Category not handled: " + p_category.quote()); + } + + if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr()) && p_category != "Class") { + Vector<String> property_path = p_text.split(":"); + String class_of_method = property_path[0]; + String method_name = property_path[1]; + + Ref<VisualScriptFunctionCall> vsfc = vnode; + vsfc->set_function(method_name); + + if (port_node_exists && p_connecting) { + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + + if (tg.type == Variant::OBJECT) { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + vsfc->set_base_type(String("")); + if (tg.gdclass != StringName()) { + vsfc->set_base_type(tg.gdclass); + } else if (script->get_node(port_action_node).is_valid()) { + PropertyHint hint = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (!base_type.is_empty() && hint == PROPERTY_HINT_TYPE_STRING) { + vsfc->set_base_type(base_type); + } + if (method_name == "call" || method_name == "call_deferred") { + vsfc->set_function(String("")); + } + } + if (tg.script.is_valid()) { + vsfc->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_INSTANCE); + vsfc->set_base_type(String("")); + } else { + vsfc->set_call_mode(VisualScriptFunctionCall::CALL_MODE_BASIC_TYPE); + vsfc->set_basic_type(tg.type); + } + } + } + + if (port_node_exists && p_connecting) { + if (Object::cast_to<VisualScriptPropertySet>(vnode.ptr())) { + Ref<VisualScriptPropertySet> vsp = vnode; + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + if (tg.gdclass != StringName()) { + vsp->set_base_type(tg.gdclass); + + } else if (script->get_node(port_action_node).is_valid()) { + PropertyHint hint = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint_string; + + if (!base_type.is_empty() && hint == PROPERTY_HINT_TYPE_STRING) { + vsp->set_base_type(base_type); + } + } + if (tg.script.is_valid()) { + vsp->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + } else { + vsp->set_call_mode(VisualScriptPropertySet::CALL_MODE_BASIC_TYPE); + vsp->set_basic_type(tg.type); + } + } + + if (Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())) { + Ref<VisualScriptPropertyGet> vsp = vnode; + + VisualScriptNode::TypeGuess tg = _guess_output_type(port_action_node, port_action_output, vn); + if (tg.type == Variant::OBJECT) { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + if (tg.gdclass != StringName()) { + vsp->set_base_type(tg.gdclass); + + } else if (script->get_node(port_action_node).is_valid()) { + PropertyHint hint = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint; + String base_type = script->get_node(port_action_node)->get_output_value_port_info(port_action_output).hint_string; + if (!base_type.is_empty() && hint == PROPERTY_HINT_TYPE_STRING) { + vsp->set_base_type(base_type); + } + } + if (tg.script.is_valid()) { + vsp->set_base_script(tg.script->get_path()); + } + } else if (tg.type == Variant::NIL) { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_INSTANCE); + vsp->set_base_type(String("")); + } else { + vsp->set_call_mode(VisualScriptPropertyGet::CALL_MODE_BASIC_TYPE); + vsp->set_basic_type(tg.type); + } + } + } + if (vnode == nullptr) { + print_error("Not able to create node from category: \"" + p_category + "\" and text \"" + p_text + "\" Not created"); + return; + } + + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph", new_id); + undo_redo->add_undo_method(this, "_update_graph", new_id); + undo_redo->commit_action(); + + port_action_new_node = new_id; + + String base_script = ""; + String base_type = ""; + if (port_node_exists) { + if (vnode_old.is_valid()) { + if (Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptTypeCast>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptFunctionCall>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptPropertySet>(vnode_old.ptr())->get_base_script(); + } else if (Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())) { + base_type = Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())->get_base_type(); + base_script = Object::cast_to<VisualScriptPropertyGet>(vnode_old.ptr())->get_base_script(); + } + } + + Vector<String> property_path = p_text.split(":"); + if (ClassDB::is_parent_class(script->get_instance_base_type(), property_path[0]) || script->get_path().ends_with(property_path[0].unquote())) { + if (!p_connecting) { + base_type = script->get_instance_base_type(); + base_script = script->get_path(); + } + } else { + base_type = property_path[0]; + base_script = ""; + } + + if (drop_node) { + Ref<Script> script = drop_node->get_script(); + if (script != nullptr) { + base_script = script->get_path(); + } + } + + if (vnode_old.is_valid() && p_connecting) { + if (base_type == "") { + base_type = property_path[0]; + } else if (ClassDB::is_parent_class(property_path[0], base_type)) { + base_type = property_path[0]; + } + connect_seq(vnode_old, vnode, port_action_new_node); + connect_data(vnode_old, vnode, port_action_new_node); + } + } + if (Object::cast_to<VisualScriptTypeCast>(vnode.ptr())) { + Object::cast_to<VisualScriptTypeCast>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptTypeCast>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())) { + Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptFunctionCall>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptPropertySet>(vnode.ptr())) { + Object::cast_to<VisualScriptPropertySet>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptPropertySet>(vnode.ptr())->set_base_script(base_script); + } else if (Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())) { + Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())->set_base_type(base_type); + Object::cast_to<VisualScriptPropertyGet>(vnode.ptr())->set_base_script(base_script); + } + + drop_path = String(); + drop_node = nullptr; + + _update_graph(port_action_new_node); +} + +void VisualScriptEditor::connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id) { + VisualScriptOperator *vnode_operator = Object::cast_to<VisualScriptOperator>(vnode_new.ptr()); + if (vnode_operator != nullptr && !vnode_operator->has_input_sequence_port()) { + return; + } + VisualScriptConstructor *vnode_constructor = Object::cast_to<VisualScriptConstructor>(vnode_new.ptr()); + if (vnode_constructor != nullptr) { + return; + } + if (vnode_old->get_output_sequence_port_count() <= 0) { + return; + } + if (!vnode_new->has_input_sequence_port()) { + return; + } + + undo_redo->create_action(TTR("Connect Node Sequence")); + int pass_port = -vnode_old->get_output_sequence_port_count() + 1; + int return_port = port_action_output - 1; + if (vnode_old->get_output_value_port_info(port_action_output).name == String("pass") && + !script->get_output_sequence_ports_connected(port_action_node).has(pass_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", port_action_node, pass_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", port_action_node, pass_port, new_id); + } else if (vnode_old->get_output_value_port_info(port_action_output).name == String("return") && + !script->get_output_sequence_ports_connected(port_action_node).has(return_port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", port_action_node, return_port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", port_action_node, return_port, new_id); + } else { + for (int port = 0; port < vnode_old->get_output_sequence_port_count(); port++) { + int count = vnode_old->get_output_sequence_port_count(); + if (port_action_output < count && !script->get_output_sequence_ports_connected(port_action_node).has(port_action_output)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", port_action_node, port_action_output, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", port_action_node, port_action_output, new_id); + break; + } else if (!script->get_output_sequence_ports_connected(port_action_node).has(port)) { + undo_redo->add_do_method(script.ptr(), "sequence_connect", port_action_node, port, new_id); + undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", port_action_node, port, new_id); + break; + } + } + } + + undo_redo->commit_action(); +} + +void VisualScriptEditor::_selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting) { + String name = p_text.substr(p_text.find_char(':') + 1); + if (script->has_function(name)) { + EditorNode::get_singleton()->show_warning(vformat(TTR("Script already has function '%s'"), name)); + return; + } + + MethodInfo minfo; + { + List<MethodInfo> methods; + bool found = false; + ClassDB::get_virtual_methods(script->get_instance_base_type(), &methods); + for (const MethodInfo &E : methods) { + if (E.name == name) { + minfo = E; + found = true; + } + } + + ERR_FAIL_COND(!found); + } + + selected = name; + Ref<VisualScriptFunction> 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); + + for (int i = 0; i < minfo.arguments.size(); i++) { + func_node->add_argument(minfo.arguments[i].type, minfo.arguments[i].name, -1, minfo.arguments[i].hint, minfo.arguments[i].hint_string); + } + + Vector2 pos = _get_available_pos(); + + undo_redo->add_do_method(script.ptr(), "add_node", fn_id, func_node, pos); + undo_redo->add_undo_method(script.ptr(), "remove_node", fn_id); + if (minfo.return_val.type != Variant::NIL || minfo.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) { + Ref<VisualScriptReturn> ret_node; + ret_node.instantiate(); + ret_node->set_return_type(minfo.return_val.type); + ret_node->set_enable_return_value(true); + ret_node->set_name(name); + int nid = script->get_available_id() + 1; + undo_redo->add_do_method(script.ptr(), "add_node", nid, ret_node, _get_available_pos(false, pos + Vector2(500, 0))); + undo_redo->add_undo_method(script.ptr(), "remove_node", nid); + } + + undo_redo->add_undo_method(script.ptr(), "remove_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->commit_action(); + + _update_graph(); +} + +void VisualScriptEditor::_cancel_connect_node() { + // Ensure the cancel is done. + port_action_new_node = -1; +} + +int VisualScriptEditor::_create_new_node_from_name(const String &p_text, const Vector2 &p_point) { + Ref<VisualScriptNode> vnode = VisualScriptLanguage::singleton->create_node_from_name(p_text); + int new_id = script->get_available_id(); + undo_redo->create_action(TTR("Add Node")); + undo_redo->add_do_method(script.ptr(), "add_node", new_id, vnode, p_point); + undo_redo->add_undo_method(script.ptr(), "remove_node", new_id); + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + undo_redo->commit_action(); + return new_id; +} + +void VisualScriptEditor::_default_value_changed() { + Ref<VisualScriptNode> vsn = script->get_node(editing_id); + if (vsn.is_null()) { + return; + } + + undo_redo->create_action(TTR("Change Input Value")); + undo_redo->add_do_method(vsn.ptr(), "set_default_input_value", editing_input, default_value_edit->get_variant()); + undo_redo->add_undo_method(vsn.ptr(), "set_default_input_value", editing_input, vsn->get_default_input_value(editing_input)); + + undo_redo->add_do_method(this, "_update_graph", editing_id); + undo_redo->add_undo_method(this, "_update_graph", editing_id); + undo_redo->commit_action(); +} + +void VisualScriptEditor::_default_value_edited(Node *p_button, int p_id, int p_input_port) { + Ref<VisualScriptNode> vsn = script->get_node(p_id); + if (vsn.is_null()) { + return; + } + + PropertyInfo pinfo = vsn->get_input_value_port_info(p_input_port); + Variant existing = vsn->get_default_input_value(p_input_port); + if (pinfo.type != Variant::NIL && existing.get_type() != pinfo.type) { + Callable::CallError ce; + Variant e = existing; + const Variant *existingp = &e; + Variant::construct(pinfo.type, existing, &existingp, 1, ce); + } + + default_value_edit->set_position(Object::cast_to<Control>(p_button)->get_screen_position() + Vector2(0, Object::cast_to<Control>(p_button)->get_size().y) * graph->get_zoom()); + default_value_edit->reset_size(); + + if (pinfo.type == Variant::NODE_PATH) { + Node *edited_scene = get_tree()->get_edited_scene_root(); + if (edited_scene) { // Fixing an old crash bug ( Visual Script Crashes on editing NodePath with an empty scene open). + Node *script_node = _find_script_node(edited_scene, edited_scene, script); + + if (script_node) { + // Pick a node relative to the script, IF the script exists. + pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE; + pinfo.hint_string = script_node->get_path(); + } else { + // Pick a path relative to edited scene. + pinfo.hint = PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE; + pinfo.hint_string = get_tree()->get_edited_scene_root()->get_path(); + } + } + } + + if (default_value_edit->edit(nullptr, pinfo.name, pinfo.type, existing, pinfo.hint, pinfo.hint_string)) { + if (pinfo.hint == PROPERTY_HINT_MULTILINE_TEXT) { + default_value_edit->popup_centered_ratio(); + } else { + default_value_edit->popup(); + } + } + + editing_id = p_id; + editing_input = p_input_port; +} + +void VisualScriptEditor::_show_hint(const String &p_hint) { + hint_text->set_text(p_hint); + hint_text->show(); + hint_text_timer->start(); +} + +void VisualScriptEditor::_hide_timer() { + hint_text->hide(); +} + +void VisualScriptEditor::_toggle_scripts_pressed() { + ScriptEditor::get_singleton()->toggle_scripts_panel(); + update_toggle_scripts_button(); +} + +void VisualScriptEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning"))); + } break; + + case NOTIFICATION_READY: { + variable_editor->connect("changed", callable_mp(this, &VisualScriptEditor::_update_members)); + variable_editor->connect("changed", callable_mp(this, &VisualScriptEditor::_update_graph), varray(-1), CONNECT_DEFERRED); + signal_editor->connect("changed", callable_mp(this, &VisualScriptEditor::_update_members)); + signal_editor->connect("changed", callable_mp(this, &VisualScriptEditor::_update_graph), varray(-1), CONNECT_DEFERRED); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + if (p_what != NOTIFICATION_READY && !is_visible_in_tree()) { + return; + } + + update_toggle_scripts_button(); + + edit_variable_edit->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + edit_signal_edit->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + func_input_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + + Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme(); + + bool dark_theme = tm->get_constant("dark_theme", "Editor"); + + if (dark_theme) { + node_colors["flow_control"] = Color(0.96, 0.96, 0.96); + node_colors["functions"] = Color(0.96, 0.52, 0.51); + node_colors["data"] = Color(0.5, 0.96, 0.81); + node_colors["operators"] = Color(0.67, 0.59, 0.87); + node_colors["custom"] = Color(0.5, 0.73, 0.96); + node_colors["constants"] = Color(0.96, 0.5, 0.69); + } else { + node_colors["flow_control"] = Color(0.26, 0.26, 0.26); + node_colors["functions"] = Color(0.95, 0.4, 0.38); + node_colors["data"] = Color(0.07, 0.73, 0.51); + node_colors["operators"] = Color(0.51, 0.4, 0.82); + node_colors["custom"] = Color(0.31, 0.63, 0.95); + node_colors["constants"] = Color(0.94, 0.18, 0.49); + } + + for (const KeyValue<StringName, Color> &E : node_colors) { + const Ref<StyleBoxFlat> sb = tm->get_stylebox(SNAME("frame"), SNAME("GraphNode")); + + if (!sb.is_null()) { + Ref<StyleBoxFlat> frame_style = sb->duplicate(); + // Adjust the border color to be close to the GraphNode's background color. + // This keeps the node's title area from being too distracting. + Color color = dark_theme ? E.value.darkened(0.75) : E.value.lightened(0.75); + color.a = 0.9; + frame_style->set_border_color(color); + node_styles[E.key] = frame_style; + } + } + + if (is_visible_in_tree() && script.is_valid()) { + _update_members(); + _update_graph(); + } + } break; + case NOTIFICATION_VISIBILITY_CHANGED: { + update_toggle_scripts_button(); + members_section->set_visible(is_visible_in_tree()); + } break; + } +} + +void VisualScriptEditor::_graph_ofs_changed(const Vector2 &p_ofs) { + if (updating_graph || !script.is_valid()) { + return; + } + + updating_graph = true; + + script->set_scroll(graph->get_scroll_ofs() / EDSCALE); + script->set_edited(true); + updating_graph = false; +} + +void VisualScriptEditor::_comment_node_resized(const Vector2 &p_new_size, int p_node) { + if (updating_graph) { + return; + } + Ref<VisualScriptComment> vsc = script->get_node(p_node); + if (vsc.is_null()) { + return; + } + + Node *node = graph->get_node(itos(p_node)); + GraphNode *gn = Object::cast_to<GraphNode>(node); + if (!gn) { + return; + } + + Vector2 new_size = p_new_size; + if (graph->is_using_snap()) { + Vector2 snap = Vector2(graph->get_snap(), graph->get_snap()); + Vector2 min_size = (gn->get_minimum_size() + (snap * 0.5)).snapped(snap); + new_size = new_size.snapped(snap).max(min_size); + } + + updating_graph = true; + + graph->set_block_minimum_size_adjust(true); //faster resize + + undo_redo->create_action(TTR("Resize Comment"), UndoRedo::MERGE_ENDS); + undo_redo->add_do_method(vsc.ptr(), "set_size", new_size / EDSCALE); + undo_redo->add_undo_method(vsc.ptr(), "set_size", vsc->get_size()); + undo_redo->commit_action(); + + gn->set_custom_minimum_size(new_size); + gn->reset_size(); + graph->set_block_minimum_size_adjust(false); + updating_graph = false; +} + +void VisualScriptEditor::_menu_option(int p_what) { + switch (p_what) { + case EDIT_ADD_NODE: { + _generic_search(); + } break; + case EDIT_DELETE_NODES: { + _on_nodes_delete(); + } break; + case EDIT_TOGGLE_BREAKPOINT: { + List<String> reselect; + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected()) { + int id = String(gn->get_name()).to_int(); + Ref<VisualScriptNode> vsn = script->get_node(id); + if (vsn.is_valid()) { + vsn->set_breakpoint(!vsn->is_breakpoint()); + reselect.push_back(gn->get_name()); + } + } + } + } + + _update_graph(); + + for (const String &E : reselect) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(E)); + gn->set_selected(true); + } + + } break; + case EDIT_FIND_NODE_TYPE: { + _generic_search(); + } break; + case EDIT_COPY_NODES: { + _on_nodes_copy(); + } break; + case EDIT_CUT_NODES: { + _on_nodes_copy(); + _on_nodes_delete(); + } break; + case EDIT_PASTE_NODES: { + _on_nodes_paste(); + } break; + case EDIT_DUPLICATE_NODES: { + _on_nodes_duplicate(); + } break; + case EDIT_CREATE_FUNCTION: { + // Create Function. + Map<int, Ref<VisualScriptNode>> nodes; + Set<int> selections; + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + if (gn->is_selected()) { + int id = String(gn->get_name()).to_int(); + Ref<VisualScriptNode> node = script->get_node(id); + if (Object::cast_to<VisualScriptFunction>(*node)) { + EditorNode::get_singleton()->show_warning(TTR("Can't create function with a function node.")); + return; + } + if (node.is_valid()) { + nodes.insert(id, node); + selections.insert(id); + } + } + } + } + + if (nodes.size() == 0) { + return; // nothing to be done if there are no valid nodes selected + } + + Set<VisualScript::SequenceConnection> seqmove; + Set<VisualScript::DataConnection> datamove; + + Set<VisualScript::SequenceConnection> seqext; + Set<VisualScript::DataConnection> dataext; + + int start_node = -1; + Set<int> end_nodes; + if (nodes.size() == 1) { + Ref<VisualScriptNode> nd = script->get_node(nodes.front()->key()); + if (nd.is_valid() && nd->has_input_sequence_port()) { + start_node = nodes.front()->key(); + } else { + EditorNode::get_singleton()->show_warning(TTR("Select at least one node with sequence port.")); + return; + } + } else { + List<VisualScript::SequenceConnection> seqs; + script->get_sequence_connection_list(&seqs); + + if (seqs.size() == 0) { + // In case there are no sequence connections, + // select the top most node cause that's probably how, + // the user wants to connect the nodes. + int top_nd = -1; + Vector2 top; + for (const KeyValue<int, Ref<VisualScriptNode>> &E : nodes) { + Ref<VisualScriptNode> nd = script->get_node(E.key); + if (nd.is_valid() && nd->has_input_sequence_port()) { + if (top_nd < 0) { + top_nd = E.key; + top = script->get_node_position(top_nd); + } + Vector2 pos = script->get_node_position(E.key); + if (top.y > pos.y) { + top_nd = E.key; + top = pos; + } + } + } + Ref<VisualScriptNode> nd = script->get_node(top_nd); + if (nd.is_valid() && nd->has_input_sequence_port()) { + start_node = top_nd; + } else { + EditorNode::get_singleton()->show_warning(TTR("Select at least one node with sequence port.")); + return; + } + } else { + // Pick the node with input sequence. + Set<int> nodes_from; + Set<int> nodes_to; + for (const VisualScript::SequenceConnection &E : seqs) { + if (nodes.has(E.from_node) && nodes.has(E.to_node)) { + seqmove.insert(E); + nodes_from.insert(E.from_node); + } else if (nodes.has(E.from_node) && !nodes.has(E.to_node)) { + seqext.insert(E); + } else if (!nodes.has(E.from_node) && nodes.has(E.to_node)) { + if (start_node == -1) { + seqext.insert(E); + start_node = E.to_node; + } else { + EditorNode::get_singleton()->show_warning(TTR("Try to only have one sequence input in selection.")); + return; + } + } + nodes_to.insert(E.to_node); + } + + // To use to add return nodes. + _get_ends(start_node, seqs, selections, end_nodes); + + if (start_node == -1) { + // If we still don't have a start node then, + // run through the nodes and select the first tree node, + // i.e. node without any input sequence but output sequence. + for (Set<int>::Element *E = nodes_from.front(); E; E = E->next()) { + if (!nodes_to.has(E->get())) { + start_node = E->get(); + } + } + } + } + } + + if (start_node == -1) { + return; // This should not happen, but just in case something goes wrong. + } + + List<Variant::Type> inputs; // input types + List<Pair<int, int>> input_connections; + { + List<VisualScript::DataConnection> dats; + script->get_data_connection_list(&dats); + for (const VisualScript::DataConnection &E : dats) { + if (nodes.has(E.from_node) && nodes.has(E.to_node)) { + datamove.insert(E); + } else if (!nodes.has(E.from_node) && nodes.has(E.to_node)) { + // Add all these as inputs for the Function. + Ref<VisualScriptNode> node = script->get_node(E.to_node); + if (node.is_valid()) { + dataext.insert(E); + PropertyInfo pi = node->get_input_value_port_info(E.to_port); + inputs.push_back(pi.type); + input_connections.push_back(Pair<int, int>(E.to_node, E.to_port)); + } + } else if (nodes.has(E.from_node) && !nodes.has(E.to_node)) { + dataext.insert(E); + } + } + } + int fn_id = script->get_available_id(); + { + String new_fn = _validate_name("new_function"); + + Vector2 pos = _get_available_pos(false, script->get_node_position(start_node) - Vector2(80, 150)); + + Ref<VisualScriptFunction> func_node; + func_node.instantiate(); + func_node->set_name(new_fn); + + undo_redo->create_action(TTR("Create Function")); + + undo_redo->add_do_method(script.ptr(), "add_function", new_fn, 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", new_fn); + 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, "emit_signal", "edited_script_changed"); + undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); + // Might make the system more intelligent by checking port from info. + int i = 0; + List<Pair<int, int>>::Element *F = input_connections.front(); + for (List<Variant::Type>::Element *E = inputs.front(); E && F; E = E->next(), F = F->next()) { + func_node->add_argument(E->get(), "arg_" + String::num_int64(i), i); + undo_redo->add_do_method(script.ptr(), "data_connect", fn_id, i, F->get().first, F->get().second); + i++; // increment i + } + // Ensure Preview Selection is of newly created function node. + if (selections.size()) { + EditorNode::get_singleton()->push_item(func_node.ptr()); + } + } + // Move the nodes. + + // Handles reconnection of sequence connections on undo, start here in case of issues. + for (Set<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "sequence_disconnect", E->get().from_node, E->get().from_output, E->get().to_node); + undo_redo->add_undo_method(script.ptr(), "sequence_connect", E->get().from_node, E->get().from_output, E->get().to_node); + } + for (Set<VisualScript::DataConnection>::Element *E = dataext.front(); E; E = E->next()) { + undo_redo->add_do_method(script.ptr(), "data_disconnect", E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + undo_redo->add_undo_method(script.ptr(), "data_connect", E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port); + } + + // I don't really think we need support for non sequenced functions at this moment. + undo_redo->add_do_method(script.ptr(), "sequence_connect", fn_id, 0, start_node); + + // Could fail with the new changes, start here when searching for bugs in create function shortcut. + int m = 1; + for (Set<int>::Element *G = end_nodes.front(); G; G = G->next()) { + Ref<VisualScriptReturn> ret_node; + ret_node.instantiate(); + + int ret_id = fn_id + (m++); + selections.insert(ret_id); + Vector2 posi = _get_available_pos(false, script->get_node_position(G->get()) + Vector2(80, -100)); + undo_redo->add_do_method(script.ptr(), "add_node", ret_id, ret_node, posi); + undo_redo->add_undo_method(script.ptr(), "remove_node", ret_id); + + undo_redo->add_do_method(script.ptr(), "sequence_connect", G->get(), 0, ret_id); + // Add data outputs from each of the end_nodes. + Ref<VisualScriptNode> vsn = script->get_node(G->get()); + if (vsn.is_valid() && vsn->get_output_value_port_count() > 0) { + ret_node->set_enable_return_value(true); + // Use the zeroth data port cause that's the likely one that is planned to be used. + ret_node->set_return_type(vsn->get_output_value_port_info(0).type); + undo_redo->add_do_method(script.ptr(), "data_connect", G->get(), 0, ret_id, 0); + } + } + + undo_redo->add_do_method(this, "_update_graph"); + undo_redo->add_undo_method(this, "_update_graph"); + + undo_redo->commit_action(); + + // Make sure all Nodes get marked for selection so that they can be moved together. + selections.insert(fn_id); + for (int k = 0; k < graph->get_child_count(); k++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(k)); + if (gn) { + int id = gn->get_name().operator String().to_int(); + gn->set_selected(selections.has(id)); + } + } + + } break; + case REFRESH_GRAPH: { + _update_graph(); + } break; + case EDIT_CLEAR_COPY_BUFFER: { + clipboard->nodes.clear(); + clipboard->nodes_positions.clear(); + clipboard->data_connections.clear(); + clipboard->sequence_connections.clear(); + } break; + } +} + +// This is likely going to be very slow and I am not sure if I should keep it, +// but I hope that it will not be a problem considering that we won't be creating functions so frequently, +// and cyclic connections would be a problem but hopefully we won't let them get to this point. +void VisualScriptEditor::_get_ends(int p_node, const List<VisualScript::SequenceConnection> &p_seqs, const Set<int> &p_selected, Set<int> &r_end_nodes) { + for (const VisualScript::SequenceConnection &E : p_seqs) { + int from = E.from_node; + int to = E.to_node; + + if (from == p_node && p_selected.has(to)) { + // This is an interior connection move forward to the to node. + _get_ends(to, p_seqs, p_selected, r_end_nodes); + } else if (from == p_node && !p_selected.has(to)) { + r_end_nodes.insert(from); + } + } +} + +void VisualScriptEditor::_member_rmb_selected(const Vector2 &p_pos) { + TreeItem *ti = members->get_selected(); + ERR_FAIL_COND(!ti); + + member_popup->clear(); + member_popup->set_position(members->get_screen_position() + p_pos); + member_popup->reset_size(); + + function_name_edit->set_position(members->get_screen_position() + p_pos); + function_name_edit->reset_size(); + + TreeItem *root = members->get_root(); + + Ref<Texture2D> del_icon = Control::get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")); + + Ref<Texture2D> edit_icon = Control::get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")); + + if (ti->get_parent() == root->get_first_child()) { + member_type = MEMBER_FUNCTION; + member_name = ti->get_text(0); + member_popup->add_icon_shortcut(edit_icon, ED_GET_SHORTCUT("visual_script_editor/edit_member"), MEMBER_EDIT); + member_popup->add_separator(); + member_popup->add_icon_shortcut(del_icon, ED_GET_SHORTCUT("ui_graph_delete"), MEMBER_REMOVE); + member_popup->popup(); + return; + } + + if (ti->get_parent() == root->get_first_child()->get_next()) { + member_type = MEMBER_VARIABLE; + member_name = ti->get_text(0); + member_popup->add_icon_shortcut(edit_icon, ED_GET_SHORTCUT("visual_script_editor/edit_member"), MEMBER_EDIT); + member_popup->add_separator(); + member_popup->add_icon_shortcut(del_icon, ED_GET_SHORTCUT("ui_graph_delete"), MEMBER_REMOVE); + member_popup->popup(); + return; + } + + if (ti->get_parent() == root->get_first_child()->get_next()->get_next()) { + member_type = MEMBER_SIGNAL; + member_name = ti->get_text(0); + member_popup->add_icon_shortcut(edit_icon, ED_GET_SHORTCUT("visual_script_editor/edit_member"), MEMBER_EDIT); + member_popup->add_separator(); + member_popup->add_icon_shortcut(del_icon, ED_GET_SHORTCUT("ui_graph_delete"), MEMBER_REMOVE); + member_popup->popup(); + return; + } +} + +void VisualScriptEditor::_member_option(int p_option) { + switch (member_type) { + case MEMBER_FUNCTION: { + if (p_option == MEMBER_REMOVE) { + // Delete the function. + String name = member_name; + List<String> lst; + int fn_node = script->get_function_node_id(name); + undo_redo->create_action(TTR("Remove Function")); + undo_redo->add_do_method(script.ptr(), "remove_function", name); + undo_redo->add_do_method(script.ptr(), "remove_node", fn_node); + undo_redo->add_undo_method(script.ptr(), "add_function", name, fn_node); + undo_redo->add_undo_method(script.ptr(), "add_node", fn_node, script->get_node(fn_node), script->get_node_position(fn_node)); + List<VisualScript::SequenceConnection> seqcons; + script->get_sequence_connection_list(&seqcons); + for (const VisualScript::SequenceConnection &E : seqcons) { + if (E.from_node == fn_node) { + undo_redo->add_undo_method(script.ptr(), "sequence_connect", fn_node, E.from_output, E.to_node); + } + } + List<VisualScript::DataConnection> datcons; + script->get_data_connection_list(&datcons); + for (const VisualScript::DataConnection &E : datcons) { + if (E.from_node == fn_node) { + undo_redo->add_undo_method(script.ptr(), "data_connect", fn_node, E.from_port, E.to_node, E.to_port); + } + } + 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->commit_action(); + } else if (p_option == MEMBER_EDIT) { + selected = members->get_selected()->get_text(0); + function_name_edit->popup(); + function_name_box->set_text(selected); + function_name_box->select_all(); + } + } break; + case MEMBER_VARIABLE: { + String name = member_name; + + if (p_option == MEMBER_REMOVE) { + undo_redo->create_action(TTR("Remove Variable")); + undo_redo->add_do_method(script.ptr(), "remove_variable", name); + undo_redo->add_undo_method(script.ptr(), "add_variable", name, script->get_variable_default_value(name)); + undo_redo->add_undo_method(script.ptr(), "set_variable_info", name, script->call("get_variable_info", name)); //return as dict + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->commit_action(); + } else if (p_option == MEMBER_EDIT) { + variable_editor->edit(name); + edit_variable_dialog->set_title(TTR("Editing Variable:") + " " + name); + edit_variable_dialog->popup_centered(Size2(400, 200) * EDSCALE); + } + } break; + case MEMBER_SIGNAL: { + String name = member_name; + + if (p_option == MEMBER_REMOVE) { + undo_redo->create_action(TTR("Remove Signal")); + undo_redo->add_do_method(script.ptr(), "remove_custom_signal", name); + undo_redo->add_undo_method(script.ptr(), "add_custom_signal", name); + + for (int i = 0; i < script->custom_signal_get_argument_count(name); i++) { + undo_redo->add_undo_method(script.ptr(), "custom_signal_add_argument", name, script->custom_signal_get_argument_name(name, i), script->custom_signal_get_argument_type(name, i)); + } + + undo_redo->add_do_method(this, "_update_members"); + undo_redo->add_undo_method(this, "_update_members"); + undo_redo->commit_action(); + } else if (p_option == MEMBER_EDIT) { + signal_editor->edit(name); + edit_signal_dialog->set_title(TTR("Editing Signal:") + " " + name); + edit_signal_dialog->popup_centered(Size2(400, 300) * EDSCALE); + } + } break; + } +} + +void VisualScriptEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { +} + +void VisualScriptEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) { +} + +void VisualScriptEditor::update_toggle_scripts_button() { + if (is_layout_rtl()) { + toggle_scripts_button->set_icon(Control::get_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Forward") : SNAME("Back"), SNAME("EditorIcons"))); + } else { + toggle_scripts_button->set_icon(Control::get_theme_icon(ScriptEditor::get_singleton()->is_scripts_panel_toggled() ? SNAME("Back") : SNAME("Forward"), SNAME("EditorIcons"))); + } + toggle_scripts_button->set_tooltip(vformat("%s (%s)", TTR("Toggle Scripts Panel"), ED_GET_SHORTCUT("script_editor/toggle_scripts_panel")->get_as_text())); +} + +void VisualScriptEditor::_bind_methods() { + ClassDB::bind_method("_move_node", &VisualScriptEditor::_move_node); + ClassDB::bind_method("_update_graph", &VisualScriptEditor::_update_graph, DEFVAL(-1)); + + ClassDB::bind_method("_center_on_node", &VisualScriptEditor::_center_on_node); + ClassDB::bind_method("_button_resource_previewed", &VisualScriptEditor::_button_resource_previewed); + ClassDB::bind_method("_port_action_menu", &VisualScriptEditor::_port_action_menu); + + ClassDB::bind_method("_create_new_node_from_name", &VisualScriptEditor::_create_new_node_from_name); + + ClassDB::bind_method("_get_drag_data_fw", &VisualScriptEditor::get_drag_data_fw); + ClassDB::bind_method("_can_drop_data_fw", &VisualScriptEditor::can_drop_data_fw); + ClassDB::bind_method("_drop_data_fw", &VisualScriptEditor::drop_data_fw); + + ClassDB::bind_method("_update_graph_connections", &VisualScriptEditor::_update_graph_connections); + ClassDB::bind_method("_update_members", &VisualScriptEditor::_update_members); + + ClassDB::bind_method("_generic_search", &VisualScriptEditor::_generic_search); +} + +VisualScriptEditor::VisualScriptEditor() { + if (!clipboard) { + clipboard = memnew(Clipboard); + } + + edit_menu = memnew(MenuButton); + edit_menu->set_shortcut_context(this); + edit_menu->set_text(TTR("Edit")); + edit_menu->set_switch_on_hover(true); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_graph_delete"), EDIT_DELETE_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/toggle_breakpoint"), EDIT_TOGGLE_BREAKPOINT); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/find_node_type"), EDIT_FIND_NODE_TYPE); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT_NODES); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_paste"), EDIT_PASTE_NODES); + edit_menu->get_popup()->add_separator(); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/create_function"), EDIT_CREATE_FUNCTION); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/refresh_nodes"), REFRESH_GRAPH); + edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &VisualScriptEditor::_menu_option)); + + members_section = memnew(VBoxContainer); + // Add but wait until done setting up this. + ScriptEditor::get_singleton()->get_left_list_split()->call_deferred(SNAME("add_child"), members_section); + members_section->set_v_size_flags(SIZE_EXPAND_FILL); + + CheckButton *tool_script_check = memnew(CheckButton); + tool_script_check->set_text(TTR("Make Tool:")); + members_section->add_child(tool_script_check); + tool_script_check->connect("pressed", callable_mp(this, &VisualScriptEditor::_toggle_tool_script)); + + /// Members /// + + members = memnew(Tree); + members_section->add_margin_child(TTR("Members:"), members, true); + members->set_custom_minimum_size(Size2(0, 50 * EDSCALE)); + members->set_hide_root(true); + members->connect("button_pressed", callable_mp(this, &VisualScriptEditor::_member_button)); + members->connect("item_edited", callable_mp(this, &VisualScriptEditor::_member_edited)); + members->connect("cell_selected", callable_mp(this, &VisualScriptEditor::_member_selected), varray(), CONNECT_DEFERRED); + members->connect("gui_input", callable_mp(this, &VisualScriptEditor::_members_gui_input)); + members->connect("item_rmb_selected", callable_mp(this, &VisualScriptEditor::_member_rmb_selected)); + members->set_allow_rmb_select(true); + members->set_allow_reselect(true); + members->set_hide_folding(true); + members->set_drag_forwarding(this); + + member_popup = memnew(PopupMenu); + add_child(member_popup); + member_popup->connect("id_pressed", callable_mp(this, &VisualScriptEditor::_member_option)); + + function_name_edit = memnew(AcceptDialog); + function_name_box = memnew(LineEdit); + function_name_edit->add_child(function_name_box); + function_name_box->connect("gui_input", callable_mp(this, &VisualScriptEditor::_fn_name_box_input)); + function_name_box->set_expand_to_text_length_enabled(true); + add_child(function_name_edit); + + /// Actual Graph /// + + graph = memnew(GraphEdit); + add_child(graph); + graph->set_v_size_flags(Control::SIZE_EXPAND_FILL); + graph->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + graph->connect("node_selected", callable_mp(this, &VisualScriptEditor::_node_selected)); + graph->connect("begin_node_move", callable_mp(this, &VisualScriptEditor::_begin_node_move)); + graph->connect("end_node_move", callable_mp(this, &VisualScriptEditor::_end_node_move)); + graph->connect("copy_nodes_request", callable_mp(this, &VisualScriptEditor::_on_nodes_copy)); + graph->connect("paste_nodes_request", callable_mp(this, &VisualScriptEditor::_on_nodes_paste)); + graph->connect("delete_nodes_request", callable_mp(this, &VisualScriptEditor::_on_nodes_delete)); + graph->connect("duplicate_nodes_request", callable_mp(this, &VisualScriptEditor::_on_nodes_duplicate)); + graph->connect("gui_input", callable_mp(this, &VisualScriptEditor::_graph_gui_input)); + graph->set_drag_forwarding(this); + float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); + graph->set_minimap_opacity(graph_minimap_opacity); + graph->hide(); + graph->connect("scroll_offset_changed", callable_mp(this, &VisualScriptEditor::_graph_ofs_changed)); + + status_bar = memnew(HBoxContainer); + add_child(status_bar); + status_bar->set_h_size_flags(SIZE_EXPAND_FILL); + status_bar->set_custom_minimum_size(Size2(0, 24 * EDSCALE)); + + toggle_scripts_button = memnew(Button); + toggle_scripts_button->set_flat(true); + toggle_scripts_button->connect("pressed", callable_mp(this, &VisualScriptEditor::_toggle_scripts_pressed)); + status_bar->add_child(toggle_scripts_button); + + /// Add Buttons to Top Bar/Zoom bar. + HBoxContainer *graph_hbc = graph->get_zoom_hbox(); + + Label *base_lbl = memnew(Label); + base_lbl->set_text(TTR("Change Base Type:") + " "); + graph_hbc->add_child(base_lbl); + + base_type_select = memnew(Button); + base_type_select->connect("pressed", callable_mp(this, &VisualScriptEditor::_change_base_type)); + graph_hbc->add_child(base_type_select); + + Button *add_nds = memnew(Button); + add_nds->set_text(TTR("Add Nodes...")); + graph_hbc->add_child(add_nds); + add_nds->connect("pressed", callable_mp(this, &VisualScriptEditor::_add_node_dialog)); + + Button *fn_btn = memnew(Button); + fn_btn->set_text(TTR("Add Function...")); + graph_hbc->add_child(fn_btn); + fn_btn->connect("pressed", callable_mp(this, &VisualScriptEditor::_create_function_dialog)); + + // Add Function Dialog. + VBoxContainer *function_vb = memnew(VBoxContainer); + function_vb->set_v_size_flags(SIZE_EXPAND_FILL); + function_vb->set_custom_minimum_size(Size2(450, 300) * EDSCALE); + + HBoxContainer *func_name_hbox = memnew(HBoxContainer); + function_vb->add_child(func_name_hbox); + + Label *func_name_label = memnew(Label); + func_name_label->set_text(TTR("Name:")); + func_name_hbox->add_child(func_name_label); + + func_name_box = memnew(LineEdit); + func_name_box->set_h_size_flags(SIZE_EXPAND_FILL); + func_name_box->set_placeholder(TTR("function_name")); + func_name_box->set_text(""); + func_name_box->connect("focus_entered", callable_mp(this, &VisualScriptEditor::_deselect_input_names)); + func_name_hbox->add_child(func_name_box); + + // Add minor setting for function if needed, here! + + function_vb->add_child(memnew(HSeparator)); + + Button *add_input_button = memnew(Button); + add_input_button->set_h_size_flags(SIZE_EXPAND_FILL); + add_input_button->set_text(TTR("Add Input")); + add_input_button->connect("pressed", callable_mp(this, &VisualScriptEditor::_add_func_input)); + function_vb->add_child(add_input_button); + + func_input_scroll = memnew(ScrollContainer); + func_input_scroll->set_v_size_flags(SIZE_EXPAND_FILL); + function_vb->add_child(func_input_scroll); + + func_input_vbox = memnew(VBoxContainer); + func_input_vbox->set_h_size_flags(SIZE_EXPAND_FILL); + func_input_scroll->add_child(func_input_vbox); + + function_create_dialog = memnew(ConfirmationDialog); + function_create_dialog->set_title(TTR("Create Function")); + function_create_dialog->add_child(function_vb); + function_create_dialog->get_ok_button()->set_text(TTR("Create")); + function_create_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualScriptEditor::_create_function)); + add_child(function_create_dialog); + + select_func_text = memnew(Label); + select_func_text->set_text(TTR("Select or create a function to edit its graph.")); + select_func_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + select_func_text->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); + select_func_text->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(select_func_text); + + hint_text = memnew(Label); + hint_text->set_anchor_and_offset(SIDE_TOP, ANCHOR_END, -100); + hint_text->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, 0); + hint_text->set_anchor_and_offset(SIDE_RIGHT, ANCHOR_END, 0); + hint_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + hint_text->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); + graph->add_child(hint_text); + + hint_text_timer = memnew(Timer); + hint_text_timer->set_wait_time(4); + hint_text_timer->connect("timeout", callable_mp(this, &VisualScriptEditor::_hide_timer)); + add_child(hint_text_timer); + + // Allowed casts (connections). + for (int i = 0; i < Variant::VARIANT_MAX; i++) { + graph->add_valid_connection_type(Variant::NIL, i); + graph->add_valid_connection_type(i, Variant::NIL); + for (int j = 0; j < Variant::VARIANT_MAX; j++) { + if (Variant::can_convert(Variant::Type(i), Variant::Type(j))) { + graph->add_valid_connection_type(i, j); + } + } + + graph->add_valid_right_disconnect_type(i); + } + + graph->add_valid_left_disconnect_type(TYPE_SEQUENCE); + + graph->connect("connection_request", callable_mp(this, &VisualScriptEditor::_graph_connected)); + graph->connect("disconnection_request", callable_mp(this, &VisualScriptEditor::_graph_disconnected)); + graph->connect("connection_to_empty", callable_mp(this, &VisualScriptEditor::_graph_connect_to_empty)); + + edit_signal_dialog = memnew(AcceptDialog); + edit_signal_dialog->get_ok_button()->set_text(TTR("Close")); + add_child(edit_signal_dialog); + + signal_editor = memnew(VisualScriptEditorSignalEdit); + edit_signal_edit = memnew(EditorInspector); + edit_signal_dialog->add_child(edit_signal_edit); + + edit_signal_edit->edit(signal_editor); + + edit_variable_dialog = memnew(AcceptDialog); + edit_variable_dialog->get_ok_button()->set_text(TTR("Close")); + add_child(edit_variable_dialog); + + variable_editor = memnew(VisualScriptEditorVariableEdit); + edit_variable_edit = memnew(EditorInspector); + edit_variable_dialog->add_child(edit_variable_edit); + + edit_variable_edit->edit(variable_editor); + + select_base_type = memnew(CreateDialog); + select_base_type->set_base_type("Object"); // Anything goes. + select_base_type->connect("create", callable_mp(this, &VisualScriptEditor::_change_base_type_callback)); + add_child(select_base_type); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + updating_members = false; + + set_process_input(true); + + default_value_edit = memnew(CustomPropertyEditor); + add_child(default_value_edit); + default_value_edit->connect("variant_changed", callable_mp(this, &VisualScriptEditor::_default_value_changed)); + + new_connect_node_select = memnew(VisualScriptPropertySelector); + add_child(new_connect_node_select); + new_connect_node_select->connect("selected", callable_mp(this, &VisualScriptEditor::_selected_connect_node)); + new_connect_node_select->get_cancel_button()->connect("pressed", callable_mp(this, &VisualScriptEditor::_cancel_connect_node)); + + new_virtual_method_select = memnew(VisualScriptPropertySelector); + add_child(new_virtual_method_select); + new_virtual_method_select->connect("selected", callable_mp(this, &VisualScriptEditor::_selected_new_virtual_method)); + + popup_menu = memnew(PopupMenu); + add_child(popup_menu); + popup_menu->add_item(TTR("Add Node"), EDIT_ADD_NODE); + popup_menu->add_separator(); + popup_menu->add_item(TTR("Cut"), EDIT_CUT_NODES); + popup_menu->add_item(TTR("Copy"), EDIT_COPY_NODES); + popup_menu->add_item(TTR("Paste"), EDIT_PASTE_NODES); + popup_menu->add_item(TTR("Delete"), EDIT_DELETE_NODES); + popup_menu->add_item(TTR("Duplicate"), EDIT_DUPLICATE_NODES); + popup_menu->add_item(TTR("Clear Copy Buffer"), EDIT_CLEAR_COPY_BUFFER); + popup_menu->connect("id_pressed", callable_mp(this, &VisualScriptEditor::_menu_option)); +} + +VisualScriptEditor::~VisualScriptEditor() { + undo_redo->clear_history(); // Avoid crashes. + memdelete(signal_editor); + memdelete(variable_editor); +} + +static ScriptEditorBase *create_editor(const RES &p_resource) { + if (Object::cast_to<VisualScript>(*p_resource)) { + return memnew(VisualScriptEditor); + } + + return nullptr; +} + +VisualScriptEditor::Clipboard *VisualScriptEditor::clipboard = nullptr; + +void VisualScriptEditor::free_clipboard() { + if (clipboard) { + memdelete(clipboard); + } +} + +static void register_editor_callback() { + ScriptEditor::register_create_script_editor_function(create_editor); + + ED_SHORTCUT("visual_script_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), Key::F9); + ED_SHORTCUT("visual_script_editor/find_node_type", TTR("Find Node Type"), KeyModifierMask::CMD + Key::F); + ED_SHORTCUT("visual_script_editor/create_function", TTR("Make Function"), KeyModifierMask::CMD + Key::G); + ED_SHORTCUT("visual_script_editor/refresh_nodes", TTR("Refresh Graph"), KeyModifierMask::CMD + Key::R); + ED_SHORTCUT("visual_script_editor/edit_member", TTR("Edit Member"), KeyModifierMask::CMD + Key::E); +} + +void VisualScriptEditor::register_editor() { + // Too early to register stuff here, request a callback. + EditorNode::add_plugin_init_callback(register_editor_callback); +} + +void VisualScriptEditor::validate() { +} + +// VisualScriptCustomNodes + +Ref<VisualScriptNode> VisualScriptCustomNodes::create_node_custom(const String &p_name) { + Ref<VisualScriptCustomNode> node; + node.instantiate(); + node->set_script(singleton->custom_nodes[p_name]); + return node; +} + +VisualScriptCustomNodes *VisualScriptCustomNodes::singleton = nullptr; +Map<String, REF> VisualScriptCustomNodes::custom_nodes; + +VisualScriptCustomNodes::VisualScriptCustomNodes() { + singleton = this; +} + +VisualScriptCustomNodes::~VisualScriptCustomNodes() { + custom_nodes.clear(); +} + +void VisualScriptCustomNodes::add_custom_node(const String &p_name, const String &p_category, const Ref<Script> &p_script) { + String node_name = "custom/" + p_category + "/" + p_name; + custom_nodes.insert(node_name, p_script); + VisualScriptLanguage::singleton->add_register_func(node_name, &VisualScriptCustomNodes::create_node_custom); + emit_signal(SNAME("custom_nodes_updated")); +} + +void VisualScriptCustomNodes::remove_custom_node(const String &p_name, const String &p_category) { + String node_name = "custom/" + p_category + "/" + p_name; + custom_nodes.erase(node_name); + VisualScriptLanguage::singleton->remove_register_func(node_name); + emit_signal(SNAME("custom_nodes_updated")); +} + +void VisualScriptCustomNodes::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_custom_node", "name", "category", "script"), &VisualScriptCustomNodes::add_custom_node); + ClassDB::bind_method(D_METHOD("remove_custom_node", "name", "category"), &VisualScriptCustomNodes::remove_custom_node); + ADD_SIGNAL(MethodInfo("custom_nodes_updated")); +} + +#endif diff --git a/modules/visual_script/editor/visual_script_editor.h b/modules/visual_script/editor/visual_script_editor.h new file mode 100644 index 0000000000..b01732b2fd --- /dev/null +++ b/modules/visual_script/editor/visual_script_editor.h @@ -0,0 +1,375 @@ +/*************************************************************************/ +/* visual_script_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +#ifndef VISUALSCRIPT_EDITOR_H +#define VISUALSCRIPT_EDITOR_H + +#include "../visual_script.h" +#include "editor/create_dialog.h" +#include "editor/plugins/script_editor_plugin.h" +#include "editor/property_editor.h" +#include "scene/gui/graph_edit.h" +#include "visual_script_property_selector.h" + +class VisualScriptEditorSignalEdit; +class VisualScriptEditorVariableEdit; + +#ifdef TOOLS_ENABLED + +// TODO: Maybe this class should be refactored. +// See https://github.com/godotengine/godot/issues/51913 +class VisualScriptEditor : public ScriptEditorBase { + GDCLASS(VisualScriptEditor, ScriptEditorBase); + + enum { + TYPE_SEQUENCE = 1000, + INDEX_BASE_SEQUENCE = 1024 + }; + + enum { + EDIT_ADD_NODE, + EDIT_SEPARATOR, // popup menu separator - ignored + EDIT_CUT_NODES, + EDIT_COPY_NODES, + EDIT_PASTE_NODES, + EDIT_DELETE_NODES, + EDIT_DUPLICATE_NODES, + EDIT_CLEAR_COPY_BUFFER, + + EDIT_CREATE_FUNCTION, + EDIT_TOGGLE_BREAKPOINT, + EDIT_FIND_NODE_TYPE, + REFRESH_GRAPH, + }; + + enum PortAction { + CREATE_CALL_SET_GET, + CREATE_ACTION, + }; + + enum MemberAction { + MEMBER_EDIT, + MEMBER_REMOVE + }; + + enum MemberType { + MEMBER_FUNCTION, + MEMBER_VARIABLE, + MEMBER_SIGNAL + }; + + VBoxContainer *members_section = nullptr; + MenuButton *edit_menu = nullptr; + + Ref<VisualScript> script; + + Button *base_type_select = nullptr; + + LineEdit *func_name_box = nullptr; + ScrollContainer *func_input_scroll = nullptr; + VBoxContainer *func_input_vbox = nullptr; + ConfirmationDialog *function_create_dialog = nullptr; + + GraphEdit *graph = nullptr; + HBoxContainer *status_bar = nullptr; + Button *toggle_scripts_button = nullptr; + + VisualScriptEditorSignalEdit *signal_editor = nullptr; + + AcceptDialog *edit_signal_dialog = nullptr; + EditorInspector *edit_signal_edit = nullptr; + + VisualScriptPropertySelector *method_select = nullptr; + VisualScriptPropertySelector *new_connect_node_select = nullptr; + VisualScriptPropertySelector *new_virtual_method_select = nullptr; + + VisualScriptEditorVariableEdit *variable_editor = nullptr; + + AcceptDialog *edit_variable_dialog = nullptr; + EditorInspector *edit_variable_edit = nullptr; + + CustomPropertyEditor *default_value_edit = nullptr; + + UndoRedo *undo_redo = nullptr; + + Tree *members = nullptr; + AcceptDialog *function_name_edit = nullptr; + LineEdit *function_name_box = nullptr; + + Label *hint_text = nullptr; + Timer *hint_text_timer = nullptr; + + Label *select_func_text = nullptr; + + bool updating_graph = false; + + void _show_hint(const String &p_hint); + void _hide_timer(); + + CreateDialog *select_base_type = nullptr; + + struct VirtualInMenu { + String name; + Variant::Type ret; + bool ret_variant; + Vector<Pair<Variant::Type, String>> args; + }; + + Map<StringName, Color> node_colors; + HashMap<StringName, Ref<StyleBox>> node_styles; + + void _update_graph_connections(); + void _update_graph(int p_only_id = -1); + + bool updating_members; + + void _update_members(); + String _sanitized_variant_text(const StringName &property_name); + + StringName selected; + + String _validate_name(const String &p_name) const; + + struct Clipboard { + Map<int, Ref<VisualScriptNode>> nodes; + Map<int, Vector2> nodes_positions; + + Set<VisualScript::SequenceConnection> sequence_connections; + Set<VisualScript::DataConnection> data_connections; + }; + + static Clipboard *clipboard; + + PopupMenu *popup_menu = nullptr; + PopupMenu *member_popup = nullptr; + MemberType member_type; + String member_name; + + PortAction port_action; + int port_action_node = 0; + int port_action_output = 0; + Vector2 port_action_pos; + int port_action_new_node = 0; + + bool saved_pos_dirty = false; + + Vector2 mouse_up_position; + + void _port_action_menu(int p_option); + + void connect_data(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode, int new_id); + + NodePath drop_path; + Node *drop_node = nullptr; + Vector2 drop_position; + void _selected_connect_node(const String &p_text, const String &p_category, const bool p_connecting = true); + void connect_seq(Ref<VisualScriptNode> vnode_old, Ref<VisualScriptNode> vnode_new, int new_id); + + void _cancel_connect_node(); + int _create_new_node_from_name(const String &p_text, const Vector2 &p_point); + void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting); + + int error_line = -1; + + void _node_selected(Node *p_node); + void _center_on_node(int p_id); + + void _node_filter_changed(const String &p_text); + void _change_base_type_callback(); + void _change_base_type(); + void _toggle_tool_script(); + void _member_selected(); + void _member_edited(); + + void _begin_node_move(); + void _end_node_move(); + void _move_node(int p_id, const Vector2 &p_to); + + void _get_ends(int p_node, const List<VisualScript::SequenceConnection> &p_seqs, const Set<int> &p_selected, Set<int> &r_end_nodes); + + void _node_moved(Vector2 p_from, Vector2 p_to, int p_id); + void _remove_node(int p_id); + void _graph_connected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot); + void _graph_disconnected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot); + void _graph_connect_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_pos); + + void _node_ports_changed(int p_id); + void _node_create(); + + void _update_available_nodes(); + + void _member_button(Object *p_item, int p_column, int p_button); + + void _expression_text_changed(const String &p_text, int p_id); + void _add_input_port(int p_id); + void _add_output_port(int p_id); + void _remove_input_port(int p_id, int p_port); + void _remove_output_port(int p_id, int p_port); + void _change_port_type(int p_select, int p_id, int p_port, bool is_input); + void _update_node_size(int p_id); + void _port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input); + + Vector2 _get_pos_in_graph(Vector2 p_point) const; + Vector2 _get_available_pos(bool p_centered = true, Vector2 p_pos = Vector2()) const; + + bool node_has_sequence_connections(int p_id); + + void _generic_search(Vector2 pos = Vector2(), bool node_centered = false); + + virtual void input(const Ref<InputEvent> &p_event) override; + void _graph_gui_input(const Ref<InputEvent> &p_event); + void _members_gui_input(const Ref<InputEvent> &p_event); + void _fn_name_box_input(const Ref<InputEvent> &p_event); + void _rename_function(const String &p_name, const String &p_new_name); + + void _create_function_dialog(); + void _create_function(); + void _add_func_input(); + void _remove_func_input(Node *p_node); + void _deselect_input_names(); + void _add_node_dialog(); + void _node_item_selected(); + void _node_item_unselected(); + + void _on_nodes_copy(); + void _on_nodes_paste(); + void _on_nodes_delete(); + void _on_nodes_duplicate(); + + Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + + int editing_id = 0; + int editing_input = 0; + + bool can_swap = false; + int data_disconnect_node = 0; + int data_disconnect_port = 0; + + void _default_value_changed(); + void _default_value_edited(Node *p_button, int p_id, int p_input_port); + + void _menu_option(int p_what); + + void _graph_ofs_changed(const Vector2 &p_ofs); + void _comment_node_resized(const Vector2 &p_new_size, int p_node); + + void _draw_color_over_button(Object *obj, Color p_color); + void _button_resource_previewed(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); + + VisualScriptNode::TypeGuess _guess_output_type(int p_port_action_node, int p_port_action_output, Set<int> &p_visited_nodes); + + void _member_rmb_selected(const Vector2 &p_pos); + void _member_option(int p_option); + + void _toggle_scripts_pressed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual void add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) override; + virtual void set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) override; + + virtual void apply_code() override; + virtual RES get_edited_resource() const override; + virtual void set_edited_resource(const RES &p_res) override; + virtual void enable_editor() override; + virtual Vector<String> get_functions() override; + virtual void reload_text() override; + virtual String get_name() override; + virtual Ref<Texture2D> get_theme_icon() override; + virtual bool is_unsaved() override; + virtual Variant get_edit_state() override; + virtual void set_edit_state(const Variant &p_state) override; + virtual void goto_line(int p_line, bool p_with_error = false) override; + virtual void set_executing_line(int p_line) override; + virtual void clear_executing_line() override; + virtual void trim_trailing_whitespace() override; + virtual void insert_final_newline() override; + virtual void convert_indent_to_spaces() override; + virtual void convert_indent_to_tabs() override; + virtual void ensure_focus() override; + virtual void tag_saved_version() override; + virtual void reload(bool p_soft) override; + virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enable) override{}; + virtual void clear_breakpoints() override{}; + virtual void add_callback(const String &p_function, PackedStringArray p_args) override; + virtual void update_settings() override; + virtual bool show_members_overview() override; + virtual void set_debugger_active(bool p_active) override; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) override; + virtual Control *get_edit_menu() override; + virtual void clear_edit_menu() override; + virtual void set_find_replace_bar(FindReplaceBar *p_bar) override { p_bar->hide(); }; // Not needed here. + virtual bool can_lose_focus_on_node_selection() override { return false; } + virtual void validate() override; + + virtual Control *get_base_editor() const override; + + static void register_editor(); + + static void free_clipboard(); + + void update_toggle_scripts_button() override; + + VisualScriptEditor(); + ~VisualScriptEditor(); +}; + +// Singleton +class VisualScriptCustomNodes : public Object { + GDCLASS(VisualScriptCustomNodes, Object); + + friend class VisualScriptLanguage; + +protected: + static void _bind_methods(); + static VisualScriptCustomNodes *singleton; + + static Map<String, REF> custom_nodes; + static Ref<VisualScriptNode> create_node_custom(const String &p_name); + +public: + static VisualScriptCustomNodes *get_singleton() { return singleton; } + + void add_custom_node(const String &p_name, const String &p_category, const Ref<Script> &p_script); + void remove_custom_node(const String &p_name, const String &p_category); + + VisualScriptCustomNodes(); + ~VisualScriptCustomNodes(); +}; + +#endif + +#endif // VISUALSCRIPT_EDITOR_H diff --git a/modules/visual_script/editor/visual_script_property_selector.cpp b/modules/visual_script/editor/visual_script_property_selector.cpp new file mode 100644 index 0000000000..cf0111ee7c --- /dev/null +++ b/modules/visual_script/editor/visual_script_property_selector.cpp @@ -0,0 +1,1267 @@ +/*************************************************************************/ +/* visual_script_property_selector.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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_property_selector.h" + +#include "../visual_script.h" +#include "../visual_script_builtin_funcs.h" +#include "../visual_script_flow_control.h" +#include "../visual_script_func_nodes.h" +#include "../visual_script_nodes.h" +#include "core/os/keyboard.h" +#include "editor/doc_tools.h" +#include "editor/editor_feature_profile.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "scene/main/node.h" +#include "scene/main/window.h" + +void VisualScriptPropertySelector::_update_icons() { + search_box->set_right_icon(results_tree->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + search_box->set_clear_button_enabled(true); + search_box->add_theme_icon_override("right_icon", results_tree->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + + search_visual_script_nodes->set_icon(results_tree->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons"))); + search_classes->set_icon(results_tree->get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + search_methods->set_icon(results_tree->get_theme_icon(SNAME("MemberMethod"), SNAME("EditorIcons"))); + search_operators->set_icon(results_tree->get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + search_signals->set_icon(results_tree->get_theme_icon(SNAME("MemberSignal"), SNAME("EditorIcons"))); + search_constants->set_icon(results_tree->get_theme_icon(SNAME("MemberConstant"), SNAME("EditorIcons"))); + search_properties->set_icon(results_tree->get_theme_icon(SNAME("MemberProperty"), SNAME("EditorIcons"))); + search_theme_items->set_icon(results_tree->get_theme_icon(SNAME("MemberTheme"), SNAME("EditorIcons"))); + + case_sensitive_button->set_icon(results_tree->get_theme_icon(SNAME("MatchCase"), SNAME("EditorIcons"))); + hierarchy_button->set_icon(results_tree->get_theme_icon(SNAME("ClassList"), SNAME("EditorIcons"))); +} + +void VisualScriptPropertySelector::_sbox_input(const Ref<InputEvent> &p_ie) { + Ref<InputEventKey> k = p_ie; + + if (k.is_valid()) { + switch (k->get_keycode()) { + case Key::UP: + case Key::DOWN: + case Key::PAGEUP: + case Key::PAGEDOWN: { + results_tree->gui_input(k); + search_box->accept_event(); + } break; + default: + break; + } + } +} + +void VisualScriptPropertySelector::_update_results_i(int p_int) { + _update_results(); +} + +void VisualScriptPropertySelector::_update_results_s(String p_string) { + _update_results(); +} + +void VisualScriptPropertySelector::_update_results() { + _update_icons(); + search_runner = Ref<SearchRunner>(memnew(SearchRunner(this, results_tree))); + set_process(true); +} + +void VisualScriptPropertySelector::_confirmed() { + TreeItem *ti = results_tree->get_selected(); + if (!ti) { + return; + } + emit_signal(SNAME("selected"), ti->get_metadata(0), ti->get_metadata(1), connecting); + set_visible(false); +} + +void VisualScriptPropertySelector::_item_selected() { + if (results_tree->get_selected()->has_meta("description")) { + help_bit->set_text(results_tree->get_selected()->get_meta("description")); + } else { + help_bit->set_text("No description available"); + } +} + +void VisualScriptPropertySelector::_hide_requested() { + _cancel_pressed(); // From AcceptDialog. +} + +void VisualScriptPropertySelector::_notification(int p_what) { + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _update_icons(); + } break; + case NOTIFICATION_ENTER_TREE: { + connect("confirmed", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); + } break; + case NOTIFICATION_PROCESS: { + // Update background search. + if (search_runner.is_valid()) { + if (search_runner->work()) { + // Search done. + get_ok_button()->set_disabled(!results_tree->get_selected()); + + search_runner = Ref<SearchRunner>(); + set_process(false); + } + } else { + // if one is valid + set_process(false); + } + } break; + } +} + +void VisualScriptPropertySelector::select_method_from_base_type(const String &p_base, const bool p_virtuals_only, const bool p_connecting, bool clear_text) { + set_title(TTR("Select method from base type")); + base_type = p_base; + base_script = ""; + type = Variant::NIL; + connecting = p_connecting; + + if (clear_text) { + if (p_virtuals_only) { + search_box->set_text("._"); // show all _methods + search_box->set_caret_column(2); + } else { + search_box->set_text("."); // show all methods + search_box->set_caret_column(1); + } + } + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(false); + search_constants->set_pressed(false); + search_properties->set_pressed(false); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + + _update_results(); +} + +void VisualScriptPropertySelector::select_from_base_type(const String &p_base, const String &p_base_script, bool p_virtuals_only, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from base type")); + base_type = p_base; + base_script = p_base_script.lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + type = Variant::NIL; + connecting = p_connecting; + + if (clear_text) { + if (p_virtuals_only) { + search_box->set_text("_"); + } else { + search_box->set_text(" "); + } + } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(false); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + // When class is Input only show inheritors + scope_combo->select(0); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_script(const Ref<Script> &p_script, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from script")); + ERR_FAIL_COND(p_script.is_null()); + + base_type = p_script->get_instance_base_type(); + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + type = Variant::NIL; + script = p_script->get_instance_id(); + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(""); + } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(true); + search_methods->set_pressed(true); + search_operators->set_pressed(true); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_basic_type(Variant::Type p_type, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from basic type")); + ERR_FAIL_COND(p_type == Variant::NIL); + base_type = Variant::get_type_name(p_type); + base_script = ""; + type = p_type; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(" "); + } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(true); + search_signals->set_pressed(false); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + + _update_results(); +} + +void VisualScriptPropertySelector::select_from_action(const String &p_type, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from action")); + base_type = p_type; + base_script = ""; + type = Variant::NIL; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(""); + } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(true); + search_classes->set_pressed(false); + search_methods->set_pressed(false); + search_operators->set_pressed(false); + search_signals->set_pressed(false); + search_constants->set_pressed(false); + search_properties->set_pressed(false); + search_theme_items->set_pressed(false); + + scope_combo->select(0); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_instance(Object *p_instance, const bool p_connecting, bool clear_text) { + set_title(TTR("Select from instance")); + base_type = p_instance->get_class(); + + const Ref<Script> &p_script = p_instance->get_script(); + if (p_script == nullptr) { + base_script = ""; + } else { + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + } + + type = Variant::NIL; + connecting = p_connecting; + + if (clear_text) { + search_box->set_text(" "); + } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(false); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::select_from_visual_script(const Ref<Script> &p_script, bool clear_text) { + set_title(TTR("Select from visual script")); + base_type = p_script->get_instance_base_type(); + if (p_script == nullptr) { + base_script = ""; + } else { + base_script = p_script->get_path().lstrip("res://").quote(); // filepath to EditorHelp::get_doc_data().name + } + type = Variant::NIL; + connecting = false; + + if (clear_text) { + search_box->set_text(" "); + } + search_box->select_all(); + + search_visual_script_nodes->set_pressed(true); + search_classes->set_pressed(false); + search_methods->set_pressed(true); + search_operators->set_pressed(false); + search_signals->set_pressed(true); + search_constants->set_pressed(true); + search_properties->set_pressed(true); + search_theme_items->set_pressed(false); + + scope_combo->select(2); //id0 = "Search Related" //id2 = "Search Base" //id3 = "Search Inheriters" //id4 = "Search Unrelated" //id5 "Search All" + + results_tree->clear(); + show_window(.5f); + search_box->grab_focus(); + _update_results(); +} + +void VisualScriptPropertySelector::show_window(float p_screen_ratio) { + popup_centered_ratio(p_screen_ratio); +} + +void VisualScriptPropertySelector::_bind_methods() { + ADD_SIGNAL(MethodInfo("selected", PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::STRING, "category"), PropertyInfo(Variant::BOOL, "connecting"))); +} + +VisualScriptPropertySelector::VisualScriptPropertySelector() { + vbox = memnew(VBoxContainer); + add_child(vbox); + + HBoxContainer *hbox = memnew(HBoxContainer); + hbox->set_alignment(hbox->ALIGNMENT_CENTER); + vbox->add_child(hbox); + + case_sensitive_button = memnew(Button); + case_sensitive_button->set_flat(true); + case_sensitive_button->set_tooltip(TTR("Case Sensitive")); + case_sensitive_button->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + case_sensitive_button->set_toggle_mode(true); + case_sensitive_button->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(case_sensitive_button); + + hierarchy_button = memnew(Button); + hierarchy_button->set_flat(true); + hierarchy_button->set_tooltip(TTR("Show Hierarchy")); + hierarchy_button->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + hierarchy_button->set_toggle_mode(true); + hierarchy_button->set_pressed(true); + hierarchy_button->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(hierarchy_button); + + hbox->add_child(memnew(VSeparator)); + + search_visual_script_nodes = memnew(Button); + search_visual_script_nodes->set_flat(true); + search_visual_script_nodes->set_tooltip(TTR("Search Visual Script Nodes")); + search_visual_script_nodes->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_visual_script_nodes->set_toggle_mode(true); + search_visual_script_nodes->set_pressed(true); + search_visual_script_nodes->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_visual_script_nodes); + + search_classes = memnew(Button); + search_classes->set_flat(true); + search_classes->set_tooltip(TTR("Search Classes")); + search_classes->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_classes->set_toggle_mode(true); + search_classes->set_pressed(true); + search_classes->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_classes); + + search_operators = memnew(Button); + search_operators->set_flat(true); + search_operators->set_tooltip(TTR("Search Operators")); + search_operators->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_operators->set_toggle_mode(true); + search_operators->set_pressed(true); + search_operators->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_operators); + + hbox->add_child(memnew(VSeparator)); + + search_methods = memnew(Button); + search_methods->set_flat(true); + search_methods->set_tooltip(TTR("Search Methods")); + search_methods->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_methods->set_toggle_mode(true); + search_methods->set_pressed(true); + search_methods->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_methods); + + search_signals = memnew(Button); + search_signals->set_flat(true); + search_signals->set_tooltip(TTR("Search Signals")); + search_signals->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_signals->set_toggle_mode(true); + search_signals->set_pressed(true); + search_signals->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_signals); + + search_constants = memnew(Button); + search_constants->set_flat(true); + search_constants->set_tooltip(TTR("Search Constants")); + search_constants->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_constants->set_toggle_mode(true); + search_constants->set_pressed(true); + search_constants->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_constants); + + search_properties = memnew(Button); + search_properties->set_flat(true); + search_properties->set_tooltip(TTR("Search Properties")); + search_properties->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_properties->set_toggle_mode(true); + search_properties->set_pressed(true); + search_properties->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_properties); + + search_theme_items = memnew(Button); + search_theme_items->set_flat(true); + search_theme_items->set_tooltip(TTR("Search Theme Items")); + search_theme_items->connect("pressed", callable_mp(this, &VisualScriptPropertySelector::_update_results)); + search_theme_items->set_toggle_mode(true); + search_theme_items->set_pressed(true); + search_theme_items->set_focus_mode(Control::FOCUS_NONE); + hbox->add_child(search_theme_items); + + scope_combo = memnew(OptionButton); + scope_combo->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + scope_combo->set_tooltip(TTR("Select the search limits")); + scope_combo->set_stretch_ratio(0); // Fixed width. + scope_combo->add_item(TTR("Search Related"), SCOPE_RELATED); + scope_combo->add_separator(); + scope_combo->add_item(TTR("Search Base"), SCOPE_BASE); + scope_combo->add_item(TTR("Search Inheriters"), SCOPE_INHERITERS); + scope_combo->add_item(TTR("Search Unrelated"), SCOPE_UNRELATED); + scope_combo->add_item(TTR("Search All"), SCOPE_ALL); + scope_combo->connect("item_selected", callable_mp(this, &VisualScriptPropertySelector::_update_results_i)); + hbox->add_child(scope_combo); + + search_box = memnew(LineEdit); + search_box->set_tooltip(TTR("Enter \" \" to show all filtered options\nEnter \".\" to show all filtered methods, operators and constructors\nUse CTRL_KEY to drop property setters")); + search_box->set_custom_minimum_size(Size2(200, 0) * EDSCALE); + search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); + search_box->connect("text_changed", callable_mp(this, &VisualScriptPropertySelector::_update_results_s)); + search_box->connect("gui_input", callable_mp(this, &VisualScriptPropertySelector::_sbox_input)); + register_text_enter(search_box); + vbox->add_child(search_box); + + results_tree = memnew(Tree); + results_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + results_tree->set_hide_root(true); + results_tree->set_hide_folding(false); + results_tree->set_columns(2); + results_tree->set_column_title(0, TTR("Name")); + results_tree->set_column_clip_content(0, true); + results_tree->set_column_title(1, TTR("Member Type")); + results_tree->set_column_expand(1, false); + results_tree->set_column_custom_minimum_width(1, 150 * EDSCALE); + results_tree->set_column_clip_content(1, true); + results_tree->set_custom_minimum_size(Size2(0, 100) * EDSCALE); + results_tree->set_select_mode(Tree::SELECT_ROW); + results_tree->connect("item_activated", callable_mp(this, &VisualScriptPropertySelector::_confirmed)); + results_tree->connect("item_selected", callable_mp(this, &VisualScriptPropertySelector::_item_selected)); + vbox->add_child(results_tree); + + help_bit = memnew(EditorHelpBit); + vbox->add_child(help_bit); + help_bit->connect("request_hide", callable_mp(this, &VisualScriptPropertySelector::_hide_requested)); + get_ok_button()->set_text(TTR("Open")); + get_ok_button()->set_disabled(true); + set_hide_on_ok(false); +} + +bool VisualScriptPropertySelector::SearchRunner::_is_class_disabled_by_feature_profile(const StringName &p_class) { + Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile(); + if (profile.is_null()) { + return false; + } + + StringName class_name = p_class; + while (class_name != StringName()) { + if (!ClassDB::class_exists(class_name)) { + return false; + } + + if (profile->is_class_disabled(class_name)) { + return true; + } + class_name = ClassDB::get_parent_class(class_name); + } + + return false; +} + +bool VisualScriptPropertySelector::SearchRunner::_is_class_disabled_by_scope(const StringName &p_class) { + bool is_base_script = false; + if (p_class == selector_ui->base_script) { + is_base_script = true; + } + bool is_base = false; + if (selector_ui->base_type == p_class) { + is_base = true; + } + bool is_parent = false; + if ((ClassDB::is_parent_class(selector_ui->base_type, p_class)) && !is_base) { + is_parent = true; + } + + bool is_inheriter = false; + List<StringName> inheriters; + ClassDB::get_inheriters_from_class(selector_ui->base_type, &inheriters); + if (inheriters.find(p_class)) { + is_inheriter = true; + } + + if (scope_flags & SCOPE_BASE) { + if (is_base_script || is_base || is_parent) { + return false; + } + } + if (scope_flags & SCOPE_INHERITERS) { + if (is_base_script || is_base || is_inheriter) { + return false; + } + } + // if (scope_flags & SCOPE_RELATED) { + // /* code */ + // } + if (scope_flags & SCOPE_UNRELATED) { + if (!is_base_script && !is_base && !is_inheriter) { + return false; + } + } + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_slice() { + bool phase_done = false; + switch (phase) { + case PHASE_INIT: + phase_done = _phase_init(); + break; + case PHASE_MATCH_CLASSES_INIT: + phase_done = _phase_match_classes_init(); + break; + case PHASE_NODE_CLASSES_INIT: + phase_done = _phase_node_classes_init(); + break; + case PHASE_NODE_CLASSES_BUILD: + phase_done = _phase_node_classes_build(); + break; + case PHASE_MATCH_CLASSES: + phase_done = _phase_match_classes(); + break; + case PHASE_CLASS_ITEMS_INIT: + phase_done = _phase_class_items_init(); + break; + case PHASE_CLASS_ITEMS: + phase_done = _phase_class_items(); + break; + case PHASE_MEMBER_ITEMS_INIT: + phase_done = _phase_member_items_init(); + break; + case PHASE_MEMBER_ITEMS: + phase_done = _phase_member_items(); + break; + case PHASE_SELECT_MATCH: + phase_done = _phase_select_match(); + break; + case PHASE_MAX: + return true; + default: + WARN_PRINT("Invalid or unhandled phase in EditorHelpSearch::Runner, aborting search."); + return true; + }; + + if (phase_done) { + phase++; + } + return false; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_init() { + search_flags = 0; // selector_ui->filter_combo->get_selected_id(); + if (selector_ui->search_visual_script_nodes->is_pressed()) { + search_flags |= SEARCH_VISUAL_SCRIPT_NODES; + } + if (selector_ui->search_classes->is_pressed()) { + search_flags |= SEARCH_CLASSES; + } + // if (selector_ui->search_constructors->is_pressed()) { + search_flags |= SEARCH_CONSTRUCTORS; + // } + if (selector_ui->search_methods->is_pressed()) { + search_flags |= SEARCH_METHODS; + } + if (selector_ui->search_operators->is_pressed()) { + search_flags |= SEARCH_OPERATORS; + } + if (selector_ui->search_signals->is_pressed()) { + search_flags |= SEARCH_SIGNALS; + } + if (selector_ui->search_constants->is_pressed()) { + search_flags |= SEARCH_CONSTANTS; + } + if (selector_ui->search_properties->is_pressed()) { + search_flags |= SEARCH_PROPERTIES; + } + if (selector_ui->search_theme_items->is_pressed()) { + search_flags |= SEARCH_THEME_ITEMS; + } + if (selector_ui->case_sensitive_button->is_pressed()) { + search_flags |= SEARCH_CASE_SENSITIVE; + } + if (selector_ui->hierarchy_button->is_pressed()) { + search_flags |= SEARCH_SHOW_HIERARCHY; + } + scope_flags = selector_ui->scope_combo->get_selected_id(); + + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes_init() { + combined_docs = EditorHelp::get_doc_data()->class_list; + matches.clear(); + matched_item = nullptr; + match_highest_score = 0; + + if ( + (selector_ui->base_script.unquote() != "") && + (selector_ui->base_script.unquote() != ".") && + !combined_docs.has(selector_ui->base_script)) { + String file_path = "res://" + selector_ui->base_script.unquote(); // EditorHelp::get_doc_data().name to filepath + Ref<Script> script; + script = ResourceLoader::load(file_path); + if (!script.is_null()) { + DocData::ClassDoc class_doc = DocData::ClassDoc(); + + class_doc.name = selector_ui->base_script; + + class_doc.inherits = script->get_instance_base_type(); + class_doc.brief_description = ".vs files not supported by EditorHelp::get_doc_data()"; + class_doc.description = ""; + + Object *obj = ObjectDB::get_instance(script->get_instance_id()); + if (Object::cast_to<Script>(obj)) { + List<MethodInfo> methods; + Object::cast_to<Script>(obj)->get_script_method_list(&methods); + for (List<MethodInfo>::Element *M = methods.front(); M; M = M->next()) { + class_doc.methods.push_back(_get_method_doc(M->get())); + } + + List<MethodInfo> signals; + Object::cast_to<Script>(obj)->get_script_signal_list(&signals); + for (List<MethodInfo>::Element *S = signals.front(); S; S = S->next()) { + class_doc.signals.push_back(_get_method_doc(S->get())); + } + + List<PropertyInfo> properties; + Object::cast_to<Script>(obj)->get_script_property_list(&properties); + for (List<PropertyInfo>::Element *P = properties.front(); P; P = P->next()) { + DocData::PropertyDoc pd = DocData::PropertyDoc(); + pd.name = P->get().name; + pd.type = Variant::get_type_name(P->get().type); + class_doc.properties.push_back(pd); + } + } + combined_docs.insert(class_doc.name, class_doc); + } + } + iterator_doc = combined_docs.front(); + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_node_classes_init() { + VisualScriptLanguage::singleton->get_registered_node_names(&vs_nodes); + _add_class_doc("functions", "", ""); + _add_class_doc("operators", "", ""); + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_node_classes_build() { + if (vs_nodes.is_empty()) { + return true; + } + String registerd_node_name = vs_nodes[0]; + vs_nodes.pop_front(); + + Vector<String> path = registerd_node_name.split("/"); + if (path[0] == "constants") { + _add_class_doc(registerd_node_name, "", "constants"); + } else if (path[0] == "custom") { + _add_class_doc(registerd_node_name, "", "custom"); + } else if (path[0] == "data") { + _add_class_doc(registerd_node_name, "", "data"); + } else if (path[0] == "flow_control") { + _add_class_doc(registerd_node_name, "", "flow_control"); + } else if (path[0] == "functions") { + if (path[1] == "built_in") { + _add_class_doc(registerd_node_name, "functions", "built_in"); + } else if (path[1] == "by_type") { + if (search_flags & SEARCH_CLASSES) { + _add_class_doc(registerd_node_name, path[2], "by_type_class"); + } + } else if (path[1] == "constructors") { + if (search_flags & SEARCH_CLASSES) { + _add_class_doc(registerd_node_name, path[2].substr(0, path[2].find_char('(')), "constructors_class"); + } + } else if (path[1] == "deconstruct") { + _add_class_doc(registerd_node_name, "", "deconstruct"); + } else if (path[1] == "wait") { + _add_class_doc(registerd_node_name, "functions", "yield"); + } else { + _add_class_doc(registerd_node_name, "functions", ""); + } + } else if (path[0] == "index") { + _add_class_doc(registerd_node_name, "", "index"); + } else if (path[0] == "operators") { + if (path[1] == "bitwise") { + _add_class_doc(registerd_node_name, "operators", "bitwise"); + } else if (path[1] == "compare") { + _add_class_doc(registerd_node_name, "operators", "compare"); + } else if (path[1] == "logic") { + _add_class_doc(registerd_node_name, "operators", "logic"); + } else if (path[1] == "math") { + _add_class_doc(registerd_node_name, "operators", "math"); + } else { + _add_class_doc(registerd_node_name, "operators", ""); + } + } + return false; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_match_classes() { + DocData::ClassDoc &class_doc = iterator_doc->value(); + if ( + (!_is_class_disabled_by_feature_profile(class_doc.name) && !_is_class_disabled_by_scope(class_doc.name)) || + _match_visual_script(class_doc)) { + if (class_doc.inherits == "VisualScriptCustomNode") { + class_doc.script_path = "res://" + class_doc.name.unquote(); + Ref<Script> script = ResourceLoader::load(class_doc.script_path); + Ref<VisualScriptCustomNode> vsn; + vsn.instantiate(); + vsn->set_script(script); + class_doc.name = vsn->get_caption(); + if (combined_docs.has(vsn->get_category())) { + class_doc.inherits = vsn->get_category(); + } else if (combined_docs.has("VisualScriptNode/" + vsn->get_category())) { + class_doc.inherits = "VisualScriptNode/" + vsn->get_category(); + } else if (combined_docs.has("VisualScriptCustomNode/" + vsn->get_category())) { + class_doc.inherits = "VisualScriptCustomNode/" + vsn->get_category(); + } else { + class_doc.inherits = ""; + } + class_doc.category = "VisualScriptCustomNode/" + vsn->get_category(); + class_doc.brief_description = ""; + class_doc.constructors.clear(); + class_doc.methods.clear(); + class_doc.operators.clear(); + class_doc.signals.clear(); + class_doc.constants.clear(); + class_doc.enums.clear(); + class_doc.properties.clear(); + class_doc.theme_properties.clear(); + } + + matches[class_doc.name] = ClassMatch(); + ClassMatch &match = matches[class_doc.name]; + + match.category = class_doc.category; + match.doc = &class_doc; + // Match class name. + if (search_flags & SEARCH_CLASSES || _match_visual_script(class_doc)) { + if (term == "") { + match.name = !_match_is_hidden(class_doc); + } else { + match.name = _match_string(term, class_doc.name); + } + // match.name = term == "" || _match_string(term, class_doc.name); + } + + // Match members if the term is long enough. + if (term.length() >= 0) { + if (search_flags & SEARCH_CONSTRUCTORS) { + for (int i = 0; i < class_doc.constructors.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.constructors[i].name : class_doc.constructors[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.constructors.push_back(const_cast<DocData::MethodDoc *>(&class_doc.constructors[i])); + } + } + } + if (search_flags & SEARCH_METHODS) { + for (int i = 0; i < class_doc.methods.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.methods[i].name : class_doc.methods[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.methods.push_back(const_cast<DocData::MethodDoc *>(&class_doc.methods[i])); + } + } + } + if (search_flags & SEARCH_OPERATORS) { + for (int i = 0; i < class_doc.operators.size(); i++) { + String method_name = (search_flags & SEARCH_CASE_SENSITIVE) ? class_doc.operators[i].name : class_doc.operators[i].name.to_lower(); + if (method_name.find(term) > -1 || + term == " " || + (term.begins_with(".") && method_name.begins_with(term.substr(1))) || + (term.ends_with("(") && method_name.ends_with(term.left(term.length() - 1).strip_edges())) || + (term.begins_with(".") && term.ends_with("(") && method_name == term.substr(1, term.length() - 2).strip_edges())) { + match.operators.push_back(const_cast<DocData::MethodDoc *>(&class_doc.operators[i])); + } + } + } + if (search_flags & SEARCH_SIGNALS) { + for (int i = 0; i < class_doc.signals.size(); i++) { + if (_match_string(term, class_doc.signals[i].name) || + term == " ") { + match.signals.push_back(const_cast<DocData::MethodDoc *>(&class_doc.signals[i])); + } + } + } + if (search_flags & SEARCH_CONSTANTS) { + for (int i = 0; i < class_doc.constants.size(); i++) { + if (_match_string(term, class_doc.constants[i].name) || + term == " ") { + match.constants.push_back(const_cast<DocData::ConstantDoc *>(&class_doc.constants[i])); + } + } + } + if (search_flags & SEARCH_PROPERTIES) { + for (int i = 0; i < class_doc.properties.size(); i++) { + if (_match_string(term, class_doc.properties[i].name) || + term == " " || + _match_string(term, class_doc.properties[i].getter) || + _match_string(term, class_doc.properties[i].setter)) { + match.properties.push_back(const_cast<DocData::PropertyDoc *>(&class_doc.properties[i])); + } + } + } + if (search_flags & SEARCH_THEME_ITEMS) { + for (int i = 0; i < class_doc.theme_properties.size(); i++) { + if (_match_string(term, class_doc.theme_properties[i].name) || + term == " ") { + match.theme_properties.push_back(const_cast<DocData::ThemeItemDoc *>(&class_doc.theme_properties[i])); + } + } + } + } + } + + iterator_doc = iterator_doc->next(); + return !iterator_doc; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_class_items_init() { + results_tree->clear(); + iterator_match = matches.front(); + + root_item = results_tree->create_item(); + class_items.clear(); + + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_class_items() { + if (!iterator_match) { + return true; + } + + ClassMatch &match = iterator_match->value(); + + if (search_flags & SEARCH_SHOW_HIERARCHY) { + if (match.required()) { + _create_class_hierarchy(match); + } + } else { + if (match.name) { + _create_class_item(root_item, match.doc, true); + } + } + + iterator_match = iterator_match->next(); + return !iterator_match; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_member_items_init() { + iterator_match = matches.front(); + + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_member_items() { + if (!iterator_match) { + return true; + } + + ClassMatch &match = iterator_match->value(); + + TreeItem *parent = (search_flags & SEARCH_SHOW_HIERARCHY) ? class_items[match.doc->name] : root_item; + bool constructor_created = false; + for (int i = 0; i < match.methods.size(); i++) { + String text = match.methods[i]->name; + if (!constructor_created) { + if (match.doc->name == match.methods[i]->name) { + text += " " + TTR("(constructors)"); + constructor_created = true; + } + } else { + if (match.doc->name == match.methods[i]->name) { + continue; + } + } + _create_method_item(parent, match.doc, text, match.methods[i]); + } + for (int i = 0; i < match.signals.size(); i++) { + _create_signal_item(parent, match.doc, match.signals[i]); + } + for (int i = 0; i < match.constants.size(); i++) { + _create_constant_item(parent, match.doc, match.constants[i]); + } + for (int i = 0; i < match.properties.size(); i++) { + _create_property_item(parent, match.doc, match.properties[i]); + } + for (int i = 0; i < match.theme_properties.size(); i++) { + _create_theme_property_item(parent, match.doc, match.theme_properties[i]); + } + + iterator_match = iterator_match->next(); + return !iterator_match; +} + +bool VisualScriptPropertySelector::SearchRunner::_phase_select_match() { + if (matched_item) { + matched_item->select(0); + } + return true; +} + +bool VisualScriptPropertySelector::SearchRunner::_match_string(const String &p_term, const String &p_string) const { + if (search_flags & SEARCH_CASE_SENSITIVE) { + return p_string.find(p_term) > -1; + } else { + return p_string.findn(p_term) > -1; + } +} + +bool VisualScriptPropertySelector::SearchRunner::_match_visual_script(DocData::ClassDoc &class_doc) { + if (class_doc.category.ends_with("_class")) { + if (class_doc.category.begins_with("VisualScript") && search_flags & SEARCH_CLASSES) { + if (matches.has(class_doc.inherits)) { + return true; + } + } + return false; + } + if (class_doc.category.begins_with("VisualScript") && search_flags & SEARCH_VISUAL_SCRIPT_NODES) { + return true; + } + if (class_doc.name.begins_with("operators") && search_flags & SEARCH_OPERATORS) { + return true; + } + if (class_doc.category.begins_with("VisualScriptNode/deconstruct")) { + if (class_doc.name.find(selector_ui->base_type, 0) > -1) { + return true; + } + } + + return false; +} + +bool VisualScriptPropertySelector::SearchRunner::_match_is_hidden(DocData::ClassDoc &class_doc) { + if (class_doc.category.begins_with("VisualScript")) { + if (class_doc.name.begins_with("flow_control")) { + return false; + } else if (class_doc.name.begins_with("operators")) { + return !(search_flags & SEARCH_OPERATORS); + } else if (class_doc.name.begins_with("functions/built_in/print")) { + return false; + } + return true; + } + return false; +} + +void VisualScriptPropertySelector::SearchRunner::_match_item(TreeItem *p_item, const String &p_text) { + float inverse_length = 1.f / float(p_text.length()); + + // Favor types where search term is a substring close to the start of the type. + float w = 0.5f; + int pos = p_text.findn(term); + float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w); + + // Favor shorter items: they resemble the search term more. + w = 0.1f; + score *= (1 - w) + w * (term.length() * inverse_length); + + if (match_highest_score == 0 || score > match_highest_score) { + matched_item = p_item; + match_highest_score = score; + } +} + +void VisualScriptPropertySelector::SearchRunner::_add_class_doc(String class_name, String inherits, String category) { + DocData::ClassDoc class_doc = DocData::ClassDoc(); + class_doc.name = class_name; + class_doc.inherits = inherits; + class_doc.category = "VisualScriptNode/" + category; + class_doc.brief_description = category; + combined_docs.insert(class_doc.name, class_doc); +} + +DocData::MethodDoc VisualScriptPropertySelector::SearchRunner::_get_method_doc(MethodInfo method_info) { + DocData::MethodDoc method_doc = DocData::MethodDoc(); + method_doc.name = method_info.name; + method_doc.return_type = Variant::get_type_name(method_info.return_val.type); + method_doc.description = "No description available"; + for (List<PropertyInfo>::Element *P = method_info.arguments.front(); P; P = P->next()) { + DocData::ArgumentDoc argument_doc = DocData::ArgumentDoc(); + argument_doc.name = P->get().name; + argument_doc.type = Variant::get_type_name(P->get().type); + method_doc.arguments.push_back(argument_doc); + } + return method_doc; +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_hierarchy(const ClassMatch &p_match) { + if (class_items.has(p_match.doc->name)) { + return class_items[p_match.doc->name]; + } + + // Ensure parent nodes are created first. + TreeItem *parent = root_item; + if (p_match.doc->inherits != "") { + if (class_items.has(p_match.doc->inherits)) { + parent = class_items[p_match.doc->inherits]; + } else if (matches.has(p_match.doc->inherits)) { + ClassMatch &base_match = matches[p_match.doc->inherits]; + parent = _create_class_hierarchy(base_match); + } + } + + TreeItem *class_item = _create_class_item(parent, p_match.doc, !p_match.name); + class_items[p_match.doc->name] = class_item; + return class_item; +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray) { + Ref<Texture2D> icon = empty_icon; + String text_0 = p_doc->name; + String text_1 = "Class"; + + String what = "Class"; + String details = p_doc->name; + if (p_doc->category.begins_with("VisualScriptCustomNode/")) { + Vector<String> path = p_doc->name.split("/"); + icon = ui_service->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); + text_0 = path[path.size() - 1]; + text_1 = "VisualScriptCustomNode"; + what = "VisualScriptCustomNode"; + details = "CustomNode"; + } else if (p_doc->category.begins_with("VisualScriptNode/")) { + Vector<String> path = p_doc->name.split("/"); + icon = ui_service->get_theme_icon(SNAME("VisualScript"), SNAME("EditorIcons")); + text_0 = path[path.size() - 1]; + if (p_doc->category.begins_with("VisualScriptNode/deconstruct")) { + text_0 = "deconstruct " + text_0; + } + text_1 = "VisualScriptNode"; + what = "VisualScriptNode"; + details = p_doc->name; + + if (path.size() == 1) { + if (path[0] == "functions" || path[0] == "operators") { + text_1 = "VisualScript"; + p_gray = true; + what = "no_result"; + details = ""; + } + } + + } else { + if (p_doc->name.is_quoted()) { + text_0 = p_doc->name.unquote().get_file(); + if (ui_service->has_theme_icon(p_doc->inherits, "EditorIcons")) { + icon = ui_service->get_theme_icon(p_doc->inherits, "EditorIcons"); + } + } else if (ui_service->has_theme_icon(p_doc->name, "EditorIcons")) { + icon = ui_service->get_theme_icon(p_doc->name, "EditorIcons"); + } else if (ClassDB::class_exists(p_doc->name) && ClassDB::is_parent_class(p_doc->name, "Object")) { + icon = ui_service->get_theme_icon(SNAME("Object"), SNAME("EditorIcons")); + } + } + String tooltip = p_doc->brief_description.strip_edges(); + + TreeItem *item = results_tree->create_item(p_parent); + item->set_icon(0, icon); + item->set_text(0, text_0); + item->set_text(1, TTR(text_1)); + item->set_tooltip(0, tooltip); + item->set_tooltip(1, tooltip); + item->set_metadata(0, details); + item->set_metadata(1, what); + if (p_gray) { + item->set_custom_color(0, disabled_color); + item->set_custom_color(1, disabled_color); + } + + _match_item(item, p_doc->name); + + return item; +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc) { + String tooltip = p_doc->return_type + " " + p_class_doc->name + "." + p_doc->name + "("; + for (int i = 0; i < p_doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + if (arg.default_value != "") { + tooltip += " = " + arg.default_value; + } + if (i < p_doc->arguments.size() - 1) { + tooltip += ", "; + } + } + tooltip += ")"; + return _create_member_item(p_parent, p_class_doc->name, "MemberMethod", p_doc->name, p_text, TTRC("Method"), "method", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc) { + String tooltip = p_doc->return_type + " " + p_class_doc->name + "." + p_doc->name + "("; + for (int i = 0; i < p_doc->arguments.size(); i++) { + const DocData::ArgumentDoc &arg = p_doc->arguments[i]; + tooltip += arg.type + " " + arg.name; + if (arg.default_value != "") { + tooltip += " = " + arg.default_value; + } + if (i < p_doc->arguments.size() - 1) { + tooltip += ", "; + } + } + tooltip += ")"; + return _create_member_item(p_parent, p_class_doc->name, "MemberSignal", p_doc->name, p_doc->name, TTRC("Signal"), "signal", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc) { + String tooltip = p_class_doc->name + "." + p_doc->name; + return _create_member_item(p_parent, p_class_doc->name, "MemberConstant", p_doc->name, p_doc->name, TTRC("Constant"), "constant", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc) { + String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; + tooltip += "\n " + p_class_doc->name + "." + p_doc->setter + "(value) setter"; + tooltip += "\n " + p_class_doc->name + "." + p_doc->getter + "() getter"; + return _create_member_item(p_parent, p_class_doc->name, "MemberProperty", p_doc->name, p_doc->name, TTRC("Property"), "property", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc) { + String tooltip = p_doc->type + " " + p_class_doc->name + "." + p_doc->name; + return _create_member_item(p_parent, p_class_doc->name, "MemberTheme", p_doc->name, p_doc->name, TTRC("Theme Property"), "theme_item", tooltip, p_doc->description); +} + +TreeItem *VisualScriptPropertySelector::SearchRunner::_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_description) { + Ref<Texture2D> icon; + String text; + if (search_flags & SEARCH_SHOW_HIERARCHY) { + icon = ui_service->get_theme_icon(p_icon, SNAME("EditorIcons")); + text = p_text; + } else { + icon = ui_service->get_theme_icon(p_icon, SNAME("EditorIcons")); + text = p_class_name + "." + p_text; + } + + TreeItem *item = results_tree->create_item(p_parent); + item->set_icon(0, icon); + item->set_text(0, text); + item->set_text(1, TTRGET(p_type)); + item->set_tooltip(0, p_tooltip); + item->set_tooltip(1, p_tooltip); + item->set_metadata(0, p_class_name + ":" + p_name); + item->set_metadata(1, "class_" + p_metatype); + item->set_meta("description", p_description); + + _match_item(item, p_name); + + return item; +} + +bool VisualScriptPropertySelector::SearchRunner::work(uint64_t slot) { + // Return true when the search has been completed, otherwise false. + const uint64_t until = OS::get_singleton()->get_ticks_usec() + slot; + while (!_slice()) { + if (OS::get_singleton()->get_ticks_usec() > until) { + return false; + } + } + return true; +} + +VisualScriptPropertySelector::SearchRunner::SearchRunner(VisualScriptPropertySelector *p_selector_ui, Tree *p_results_tree) : + selector_ui(p_selector_ui), + ui_service(p_selector_ui->vbox), + results_tree(p_results_tree), + term(p_selector_ui->search_box->get_text()), + empty_icon(ui_service->get_theme_icon(SNAME("ArrowRight"), SNAME("EditorIcons"))), + disabled_color(ui_service->get_theme_color(SNAME("disabled_font_color"), SNAME("Editor"))) { +} diff --git a/modules/visual_script/editor/visual_script_property_selector.h b/modules/visual_script/editor/visual_script_property_selector.h new file mode 100644 index 0000000000..6b5112f1af --- /dev/null +++ b/modules/visual_script/editor/visual_script_property_selector.h @@ -0,0 +1,219 @@ +/*************************************************************************/ +/* visual_script_property_selector.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +#ifndef VISUALSCRIPT_PROPERTYSELECTOR_H +#define VISUALSCRIPT_PROPERTYSELECTOR_H + +#include "../visual_script.h" +#include "editor/editor_help.h" +#include "editor/property_editor.h" +#include "scene/gui/rich_text_label.h" + +class VisualScriptPropertySelector : public ConfirmationDialog { + GDCLASS(VisualScriptPropertySelector, ConfirmationDialog); + + enum SearchFlags { + SEARCH_CLASSES = 1 << 0, + SEARCH_CONSTRUCTORS = 1 << 1, + SEARCH_METHODS = 1 << 2, + SEARCH_OPERATORS = 1 << 3, + SEARCH_SIGNALS = 1 << 4, + SEARCH_CONSTANTS = 1 << 5, + SEARCH_PROPERTIES = 1 << 6, + SEARCH_THEME_ITEMS = 1 << 7, + SEARCH_VISUAL_SCRIPT_NODES = 1 << 8, + SEARCH_ALL = SEARCH_CLASSES | SEARCH_CONSTRUCTORS | SEARCH_METHODS | SEARCH_OPERATORS | SEARCH_SIGNALS | SEARCH_CONSTANTS | SEARCH_PROPERTIES | SEARCH_THEME_ITEMS, + SEARCH_CASE_SENSITIVE = 1 << 29, + SEARCH_SHOW_HIERARCHY = 1 << 30, + }; + + enum ScopeFlags { + SCOPE_BASE = 1 << 0, + SCOPE_INHERITERS = 1 << 1, + SCOPE_UNRELATED = 1 << 2, + SCOPE_RELATED = SCOPE_BASE | SCOPE_INHERITERS, + SCOPE_ALL = SCOPE_BASE | SCOPE_INHERITERS | SCOPE_UNRELATED + }; + + LineEdit *search_box; + + Button *case_sensitive_button; + Button *hierarchy_button; + + Button *search_visual_script_nodes; + Button *search_classes; + Button *search_operators; + + Button *search_methods; + Button *search_signals; + Button *search_constants; + Button *search_properties; + Button *search_theme_items; + + OptionButton *scope_combo; + Tree *results_tree; + + class SearchRunner; + Ref<SearchRunner> search_runner; + + void _update_icons(); + + void _sbox_input(const Ref<InputEvent> &p_ie); + void _update_results_i(int p_int); + void _update_results_s(String p_string); + void _update_results(); + + void _confirmed(); + void _item_selected(); + void _hide_requested(); + + EditorHelpBit *help_bit; + + bool properties = false; + bool visual_script_generic = false; + bool connecting = false; + String selected; + Variant::Type type; + String base_type; + String base_script; + ObjectID script; + Object *instance; + bool virtuals_only = false; + VBoxContainer *vbox; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void select_method_from_base_type(const String &p_base, const bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_base_type(const String &p_base, const String &p_base_script = "", bool p_virtuals_only = false, const bool p_connecting = true, bool clear_text = true); + void select_from_script(const Ref<Script> &p_script, const bool p_connecting = true, bool clear_text = true); + void select_from_basic_type(Variant::Type p_type, const bool p_connecting = true, bool clear_text = true); + void select_from_action(const String &p_type, const bool p_connecting = true, bool clear_text = true); + void select_from_instance(Object *p_instance, const bool p_connecting = true, bool clear_text = true); + void select_from_visual_script(const Ref<Script> &p_script, bool clear_text = true); + + void show_window(float p_screen_ratio); + + VisualScriptPropertySelector(); +}; + +class VisualScriptPropertySelector::SearchRunner : public RefCounted { + enum Phase { + PHASE_INIT, + PHASE_MATCH_CLASSES_INIT, + PHASE_NODE_CLASSES_INIT, + PHASE_NODE_CLASSES_BUILD, + PHASE_MATCH_CLASSES, + PHASE_CLASS_ITEMS_INIT, + PHASE_CLASS_ITEMS, + PHASE_MEMBER_ITEMS_INIT, + PHASE_MEMBER_ITEMS, + PHASE_SELECT_MATCH, + PHASE_MAX + }; + int phase = 0; + + struct ClassMatch { + DocData::ClassDoc *doc; + bool name = false; + String category = ""; + Vector<DocData::MethodDoc *> constructors; + Vector<DocData::MethodDoc *> methods; + Vector<DocData::MethodDoc *> operators; + Vector<DocData::MethodDoc *> signals; + Vector<DocData::ConstantDoc *> constants; + Vector<DocData::PropertyDoc *> properties; + Vector<DocData::ThemeItemDoc *> theme_properties; + + bool required() { + return name || methods.size() || signals.size() || constants.size() || properties.size() || theme_properties.size(); + } + }; + + VisualScriptPropertySelector *selector_ui; + Control *ui_service; + Tree *results_tree; + String term; + int search_flags; + int scope_flags; + + Ref<Texture2D> empty_icon; + Color disabled_color; + + Map<String, DocData::ClassDoc>::Element *iterator_doc = nullptr; + Map<String, ClassMatch> matches; + Map<String, ClassMatch>::Element *iterator_match = nullptr; + TreeItem *root_item = nullptr; + Map<String, TreeItem *> class_items; + TreeItem *matched_item = nullptr; + float match_highest_score = 0; + + Map<String, DocData::ClassDoc> combined_docs; + List<String> vs_nodes; + + bool _is_class_disabled_by_feature_profile(const StringName &p_class); + bool _is_class_disabled_by_scope(const StringName &p_class); + + bool _slice(); + bool _phase_init(); + bool _phase_match_classes_init(); + bool _phase_node_classes_init(); + bool _phase_node_classes_build(); + bool _phase_match_classes(); + bool _phase_class_items_init(); + bool _phase_class_items(); + bool _phase_member_items_init(); + bool _phase_member_items(); + bool _phase_select_match(); + + bool _match_string(const String &p_term, const String &p_string) const; + bool _match_visual_script(DocData::ClassDoc &class_doc); + bool _match_is_hidden(DocData::ClassDoc &class_doc); + void _match_item(TreeItem *p_item, const String &p_text); + void _add_class_doc(String class_name, String inherits, String category); + DocData::MethodDoc _get_method_doc(MethodInfo method_info); + TreeItem *_create_class_hierarchy(const ClassMatch &p_match); + TreeItem *_create_class_item(TreeItem *p_parent, const DocData::ClassDoc *p_doc, bool p_gray); + TreeItem *_create_method_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const String &p_text, const DocData::MethodDoc *p_doc); + TreeItem *_create_signal_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::MethodDoc *p_doc); + TreeItem *_create_constant_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ConstantDoc *p_doc); + TreeItem *_create_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::PropertyDoc *p_doc); + TreeItem *_create_theme_property_item(TreeItem *p_parent, const DocData::ClassDoc *p_class_doc, const DocData::ThemeItemDoc *p_doc); + TreeItem *_create_member_item(TreeItem *p_parent, const String &p_class_name, const String &p_icon, const String &p_name, const String &p_text, const String &p_type, const String &p_metatype, const String &p_tooltip, const String &p_description); + +public: + bool work(uint64_t slot = 100000); + + SearchRunner(VisualScriptPropertySelector *p_selector_ui, Tree *p_results_tree); +}; + +#endif // VISUALSCRIPT_PROPERTYSELECTOR_H |