/*************************************************************************/ /* visual_script_editor.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2019 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 "core/object.h" #include "core/os/input.h" #include "core/os/keyboard.h" #include "core/script_language.h" #include "core/variant.h" #include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "scene/main/viewport.h" #include "visual_script_expression.h" #include "visual_script_flow_control.h" #include "visual_script_func_nodes.h" #include "visual_script_nodes.h" #ifdef TOOLS_ENABLED class VisualScriptEditorSignalEdit : public Object { GDCLASS(VisualScriptEditorSignalEdit, Object) StringName sig; public: UndoRedo *undo_redo; Ref script; protected: static void _bind_methods() { ClassDB::bind_method("_sig_changed", &VisualScriptEditorSignalEdit::_sig_changed); ADD_SIGNAL(MethodInfo("changed")); } void _sig_changed() { _change_notify(); emit_signal("changed"); } bool _set(const StringName &p_name, const Variant &p_value) { if (sig == StringName()) return false; if (p_name == "argument_count") { int new_argc = p_value; int argc = script->custom_signal_get_argument_count(sig); if (argc == new_argc) return true; undo_redo->create_action(TTR("Change Signal Arguments")); if (new_argc < argc) { for (int i = new_argc; i < argc; i++) { undo_redo->add_do_method(script.ptr(), "custom_signal_remove_argument", sig, new_argc); undo_redo->add_undo_method(script.ptr(), "custom_signal_add_argument", sig, script->custom_signal_get_argument_name(sig, i), script->custom_signal_get_argument_type(sig, i), -1); } } else if (new_argc > argc) { for (int i = argc; i < new_argc; i++) { undo_redo->add_do_method(script.ptr(), "custom_signal_add_argument", sig, Variant::NIL, "arg" + itos(i + 1), -1); undo_redo->add_undo_method(script.ptr(), "custom_signal_remove_argument", sig, argc); } } undo_redo->add_do_method(this, "_sig_changed"); undo_redo->add_undo_method(this, "_sig_changed"); undo_redo->commit_action(); return true; } if (String(p_name).begins_with("argument/")) { int idx = String(p_name).get_slice("/", 1).to_int() - 1; ERR_FAIL_INDEX_V(idx, script->custom_signal_get_argument_count(sig), false); String what = String(p_name).get_slice("/", 2); if (what == "type") { int old_type = script->custom_signal_get_argument_type(sig, idx); int new_type = p_value; undo_redo->create_action(TTR("Change Argument Type")); undo_redo->add_do_method(script.ptr(), "custom_signal_set_argument_type", sig, idx, new_type); undo_redo->add_undo_method(script.ptr(), "custom_signal_set_argument_type", sig, idx, old_type); undo_redo->commit_action(); return true; } if (what == "name") { String old_name = script->custom_signal_get_argument_name(sig, idx); String new_name = p_value; undo_redo->create_action(TTR("Change Argument name")); undo_redo->add_do_method(script.ptr(), "custom_signal_set_argument_name", sig, idx, new_name); undo_redo->add_undo_method(script.ptr(), "custom_signal_set_argument_name", sig, idx, old_name); undo_redo->commit_action(); return true; } } return false; } bool _get(const StringName &p_name, Variant &r_ret) const { if (sig == StringName()) return false; if (p_name == "argument_count") { r_ret = script->custom_signal_get_argument_count(sig); return true; } if (String(p_name).begins_with("argument/")) { int idx = String(p_name).get_slice("/", 1).to_int() - 1; ERR_FAIL_INDEX_V(idx, script->custom_signal_get_argument_count(sig), false); String what = String(p_name).get_slice("/", 2); if (what == "type") { r_ret = script->custom_signal_get_argument_type(sig, idx); return true; } if (what == "name") { r_ret = script->custom_signal_get_argument_name(sig, idx); return true; } } return false; } void _get_property_list(List *p_list) const { if (sig == StringName()) return; p_list->push_back(PropertyInfo(Variant::INT, "argument_count", PROPERTY_HINT_RANGE, "0,256")); String argt = "Variant"; for (int i = 1; i < Variant::VARIANT_MAX; i++) { argt += "," + Variant::get_type_name(Variant::Type(i)); } for (int i = 0; i < script->custom_signal_get_argument_count(sig); i++) { p_list->push_back(PropertyInfo(Variant::INT, "argument/" + itos(i + 1) + "/type", PROPERTY_HINT_ENUM, argt)); p_list->push_back(PropertyInfo(Variant::STRING, "argument/" + itos(i + 1) + "/name")); } } public: void edit(const StringName &p_sig) { sig = p_sig; _change_notify(); } VisualScriptEditorSignalEdit() { undo_redo = NULL; } }; class VisualScriptEditorVariableEdit : public Object { GDCLASS(VisualScriptEditorVariableEdit, Object) StringName var; public: UndoRedo *undo_redo; Ref script; protected: static void _bind_methods() { ClassDB::bind_method("_var_changed", &VisualScriptEditorVariableEdit::_var_changed); ClassDB::bind_method("_var_value_changed", &VisualScriptEditorVariableEdit::_var_value_changed); ADD_SIGNAL(MethodInfo("changed")); } void _var_changed() { _change_notify(); emit_signal("changed"); } void _var_value_changed() { _change_notify("value"); //so the whole tree is not redrawn, makes editing smoother in general emit_signal("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); undo_redo->add_do_method(this, "_var_changed"); undo_redo->add_undo_method(this, "_var_changed"); undo_redo->commit_action(); return true; } if (String(p_name) == "hint") { Dictionary dc = d.duplicate(); dc["hint"] = p_value; undo_redo->create_action(TTR("Set Variable Type")); undo_redo->add_do_method(script.ptr(), "set_variable_info", var, dc); undo_redo->add_undo_method(script.ptr(), "set_variable_info", var, d); undo_redo->add_do_method(this, "_var_changed"); undo_redo->add_undo_method(this, "_var_changed"); undo_redo->commit_action(); return true; } if (String(p_name) == "hint_string") { Dictionary dc = d.duplicate(); dc["hint_string"] = p_value; undo_redo->create_action(TTR("Set Variable Type")); undo_redo->add_do_method(script.ptr(), "set_variable_info", var, dc); undo_redo->add_undo_method(script.ptr(), "set_variable_info", var, d); undo_redo->add_do_method(this, "_var_changed"); undo_redo->add_undo_method(this, "_var_changed"); undo_redo->commit_action(); return true; } if (String(p_name) == "export") { script->set_variable_export(var, p_value); EditorNode::get_singleton()->get_inspector()->update_tree(); return true; } return false; } bool _get(const StringName &p_name, Variant &r_ret) const { if (var == StringName()) return false; if (String(p_name) == "value") { r_ret = script->get_variable_default_value(var); return true; } PropertyInfo pinfo = script->get_variable_info(var); if (String(p_name) == "type") { r_ret = pinfo.type; return true; } if (String(p_name) == "hint") { r_ret = pinfo.hint; return true; } if (String(p_name) == "hint_string") { r_ret = pinfo.hint_string; return true; } if (String(p_name) == "export") { r_ret = script->get_variable_export(var); return true; } return false; } void _get_property_list(List *p_list) const { if (var == StringName()) return; String argt = "Variant"; for (int i = 1; i < Variant::VARIANT_MAX; i++) { argt += "," + Variant::get_type_name(Variant::Type(i)); } p_list->push_back(PropertyInfo(Variant::INT, "type", PROPERTY_HINT_ENUM, argt)); p_list->push_back(PropertyInfo(script->get_variable_info(var).type, "value", script->get_variable_info(var).hint, script->get_variable_info(var).hint_string, PROPERTY_USAGE_DEFAULT)); // Update this when PropertyHint changes p_list->push_back(PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_ENUM, "None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,Flags,Layers2dRender,Layers2dPhysics,Layer3dRender,Layer3dPhysics,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText,PlaceholderText,ColorNoAlpha,ImageCompressLossy,ImageCompressLossLess,ObjectId,String,NodePathToEditedNode,MethodOfVariantType,MethodOfBaseType,MethodOfInstance,MethodOfScript,PropertyOfVariantType,PropertyOfBaseType,PropertyOfInstance,PropertyOfScript,ObjectTooBig,NodePathValidTypes")); p_list->push_back(PropertyInfo(Variant::STRING, "hint_string")); p_list->push_back(PropertyInfo(Variant::BOOL, "export")); } public: void edit(const StringName &p_var) { var = p_var; _change_notify(); } VisualScriptEditorVariableEdit() { undo_redo = NULL; } }; 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::html("#69ecbd"); break; case Variant::BOOL: color = Color::html("#8da6f0"); break; case Variant::INT: color = Color::html("#7dc6ef"); break; case Variant::REAL: color = Color::html("#61daf4"); break; case Variant::STRING: color = Color::html("#6ba7ec"); break; case Variant::VECTOR2: color = Color::html("#bd91f1"); break; case Variant::RECT2: color = Color::html("#f191a5"); break; case Variant::VECTOR3: color = Color::html("#d67dee"); break; case Variant::TRANSFORM2D: color = Color::html("#c4ec69"); break; case Variant::PLANE: color = Color::html("#f77070"); break; case Variant::QUAT: color = Color::html("#ec69a3"); break; case Variant::AABB: color = Color::html("#ee7991"); break; case Variant::BASIS: color = Color::html("#e3ec69"); break; case Variant::TRANSFORM: color = Color::html("#f6a86e"); break; case Variant::COLOR: color = Color::html("#9dff70"); break; case Variant::NODE_PATH: color = Color::html("#6993ec"); break; case Variant::_RID: color = Color::html("#69ec9a"); break; case Variant::OBJECT: color = Color::html("#79f3e8"); break; case Variant::DICTIONARY: color = Color::html("#77edb1"); break; case Variant::ARRAY: color = Color::html("#e0e0e0"); break; case Variant::POOL_BYTE_ARRAY: color = Color::html("#aaf4c8"); break; case Variant::POOL_INT_ARRAY: color = Color::html("#afdcf5"); break; case Variant::POOL_REAL_ARRAY: color = Color::html("#97e7f8"); break; case Variant::POOL_STRING_ARRAY: color = Color::html("#9dc4f2"); break; case Variant::POOL_VECTOR2_ARRAY: color = Color::html("#d1b3f5"); break; case Variant::POOL_VECTOR3_ARRAY: color = Color::html("#df9bf2"); break; case Variant::POOL_COLOR_ARRAY: color = Color::html("#e9ff97"); break; default: color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.7, 0.7); } else switch (p_type) { case Variant::NIL: color = Color::html("#25e3a0"); break; case Variant::BOOL: color = Color::html("#6d8eeb"); break; case Variant::INT: color = Color::html("#4fb2e9"); break; case Variant::REAL: color = Color::html("#27ccf0"); break; case Variant::STRING: color = Color::html("#4690e7"); break; case Variant::VECTOR2: color = Color::html("#ad76ee"); break; case Variant::RECT2: color = Color::html("#ee758e"); break; case Variant::VECTOR3: color = Color::html("#dc6aed"); break; case Variant::TRANSFORM2D: color = Color::html("#96ce1a"); break; case Variant::PLANE: color = Color::html("#f77070"); break; case Variant::QUAT: color = Color::html("#ec69a3"); break; case Variant::AABB: color = Color::html("#ee7991"); break; case Variant::BASIS: color = Color::html("#b2bb19"); break; case Variant::TRANSFORM: color = Color::html("#f49047"); break; case Variant::COLOR: color = Color::html("#3cbf00"); break; case Variant::NODE_PATH: color = Color::html("#6993ec"); break; case Variant::_RID: color = Color::html("#2ce573"); break; case Variant::OBJECT: color = Color::html("#12d5c3"); break; case Variant::DICTIONARY: color = Color::html("#57e99f"); break; case Variant::ARRAY: color = Color::html("#737373"); break; case Variant::POOL_BYTE_ARRAY: color = Color::html("#61ea98"); break; case Variant::POOL_INT_ARRAY: color = Color::html("#61baeb"); break; case Variant::POOL_REAL_ARRAY: color = Color::html("#40d3f2"); break; case Variant::POOL_STRING_ARRAY: color = Color::html("#609fea"); break; case Variant::POOL_VECTOR2_ARRAY: color = Color::html("#9d5dea"); break; case Variant::POOL_VECTOR3_ARRAY: color = Color::html("#ca5aea"); break; case Variant::POOL_COLOR_ARRAY: color = Color::html("#92ba00"); break; default: color.set_hsv(p_type / float(Variant::VARIANT_MAX), 0.3, 0.3); } return color; } void VisualScriptEditor::_update_graph_connections() { graph->clear_connections(); List sequence_conns; script->get_sequence_connection_list(edited_func, &sequence_conns); for (List::Element *E = sequence_conns.front(); E; E = E->next()) { graph->connect_node(itos(E->get().from_node), E->get().from_output, itos(E->get().to_node), 0); } List data_conns; script->get_data_connection_list(edited_func, &data_conns); for (List::Element *E = data_conns.front(); E; E = E->next()) { VisualScript::DataConnection dc = E->get(); Ref from_node = script->get_node(edited_func, E->get().from_node); Ref to_node = script->get_node(edited_func, E->get().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(E->get().from_node), dc.from_port, itos(E->get().to_node), dc.to_port); } } void VisualScriptEditor::_update_graph(int p_only_id) { if (updating_graph) return; updating_graph = true; //byebye all nodes if (p_only_id >= 0) { if (graph->has_node(itos(p_only_id))) { Node *gid = graph->get_node(itos(p_only_id)); if (gid) memdelete(gid); } } else { for (int i = 0; i < graph->get_child_count(); i++) { if (Object::cast_to(graph->get_child(i))) { memdelete(graph->get_child(i)); i--; } } } if (!script->has_function(edited_func)) { graph->hide(); select_func_text->show(); updating_graph = false; return; } graph->show(); select_func_text->hide(); Ref type_icons[Variant::VARIANT_MAX] = { Control::get_icon("Variant", "EditorIcons"), Control::get_icon("bool", "EditorIcons"), Control::get_icon("int", "EditorIcons"), Control::get_icon("float", "EditorIcons"), Control::get_icon("String", "EditorIcons"), Control::get_icon("Vector2", "EditorIcons"), Control::get_icon("Rect2", "EditorIcons"), Control::get_icon("Vector3", "EditorIcons"), Control::get_icon("Transform2D", "EditorIcons"), Control::get_icon("Plane", "EditorIcons"), Control::get_icon("Quat", "EditorIcons"), Control::get_icon("AABB", "EditorIcons"), Control::get_icon("Basis", "EditorIcons"), Control::get_icon("Transform", "EditorIcons"), Control::get_icon("Color", "EditorIcons"), Control::get_icon("NodePath", "EditorIcons"), Control::get_icon("RID", "EditorIcons"), Control::get_icon("MiniObject", "EditorIcons"), Control::get_icon("Dictionary", "EditorIcons"), Control::get_icon("Array", "EditorIcons"), Control::get_icon("PoolByteArray", "EditorIcons"), Control::get_icon("PoolIntArray", "EditorIcons"), Control::get_icon("PoolRealArray", "EditorIcons"), Control::get_icon("PoolStringArray", "EditorIcons"), Control::get_icon("PoolVector2Array", "EditorIcons"), Control::get_icon("PoolVector3Array", "EditorIcons"), Control::get_icon("PoolColorArray", "EditorIcons") }; Ref seq_port = Control::get_icon("VisualShaderPort", "EditorIcons"); List ids; script->get_node_list(edited_func, &ids); StringName editor_icons = "EditorIcons"; for (List::Element *E = ids.front(); E; E = E->next()) { if (p_only_id >= 0 && p_only_id != E->get()) continue; Ref node = script->get_node(edited_func, E->get()); Vector2 pos = script->get_node_position(edited_func, E->get()); GraphNode *gnode = memnew(GraphNode); gnode->set_title(node->get_caption()); gnode->set_offset(pos * EDSCALE); if (error_line == E->get()) { 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->get())); gnode->connect("dragged", this, "_node_moved", varray(E->get())); gnode->connect("close_request", this, "_remove_node", varray(E->get()), CONNECT_DEFERRED); if (E->get() != script->get_function_node_id(edited_func)) { //function can't be erased gnode->set_show_close_button(true); } bool has_gnode_text = false; if (Object::cast_to(node.ptr())) { has_gnode_text = true; LineEdit *line_edit = memnew(LineEdit); line_edit->set_text(node->get_text()); line_edit->set_expand_to_text_length(true); line_edit->add_font_override("font", get_font("source", "EditorFonts")); gnode->add_child(line_edit); line_edit->connect("text_changed", this, "_expression_text_changed", varray(E->get())); } else { String text = node->get_text(); if (!text.empty()) { has_gnode_text = true; Label *label = memnew(Label); label->set_text(text); gnode->add_child(label); } } if (Object::cast_to(node.ptr())) { Ref vsc = node; gnode->set_comment(true); gnode->set_resizable(true); gnode->set_custom_minimum_size(vsc->get_size() * EDSCALE); gnode->connect("resize_request", this, "_comment_node_resized", varray(E->get())); } if (node_styles.has(node->get_category())) { Ref sbf = node_styles[node->get_category()]; if (gnode->is_comment()) sbf = EditorNode::get_singleton()->get_theme_base()->get_theme()->get_stylebox("comment", "GraphNode"); Color c = sbf->get_border_color(); c.a = 1; if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) { Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); mono_color.a = 0.85; c = mono_color; } gnode->add_color_override("title_color", c); c.a = 0.7; gnode->add_color_override("close_color", c); gnode->add_style_override("frame", sbf); } const Color mono_color = get_color("mono_color", "Editor"); int slot_idx = 0; bool single_seq_output = node->get_output_sequence_port_count() == 1 && node->get_output_sequence_port_text(0) == String(); if ((node->has_input_sequence_port() || single_seq_output) || has_gnode_text) { // IF has_gnode_text is true BUT we have no sequence ports to draw (in here), // we still draw the disabled default ones to shift up the slots by one, // so the slots DON'T start with the content text. // IF has_gnode_text is false, but we DO want to draw default sequence ports, // we draw a dummy text to take up the position of the sequence nodes, so all the other ports are still aligned correctly. if (!has_gnode_text) { Label *dummy = memnew(Label); dummy->set_text(" "); gnode->add_child(dummy); } gnode->set_slot(0, node->has_input_sequence_port(), TYPE_SEQUENCE, mono_color, single_seq_output, TYPE_SEQUENCE, mono_color, seq_port, seq_port); slot_idx++; } int mixed_seq_ports = 0; if (!single_seq_output) { if (node->has_mixed_input_and_sequence_ports()) { mixed_seq_ports = node->get_output_sequence_port_count(); } else { for (int i = 0; i < node->get_output_sequence_port_count(); i++) { Label *text2 = memnew(Label); text2->set_text(node->get_output_sequence_port_text(i)); text2->set_align(Label::ALIGN_RIGHT); gnode->add_child(text2); gnode->set_slot(slot_idx, false, 0, Color(), true, TYPE_SEQUENCE, mono_color, seq_port, seq_port); slot_idx++; } } } for (int i = 0; i < MAX(node->get_output_value_port_count(), MAX(mixed_seq_ports, node->get_input_value_port_count())); i++) { bool left_ok = false; Variant::Type left_type = Variant::NIL; String left_name; if (i < node->get_input_value_port_count()) { PropertyInfo pi = node->get_input_value_port_info(i); left_ok = true; left_type = pi.type; left_name = pi.name; } bool right_ok = false; Variant::Type right_type = Variant::NIL; String right_name; if (i >= mixed_seq_ports && i < node->get_output_value_port_count() + mixed_seq_ports) { PropertyInfo pi = node->get_output_value_port_info(i - mixed_seq_ports); right_ok = true; right_type = pi.type; right_name = pi.name; } HBoxContainer *hbc = memnew(HBoxContainer); if (left_ok) { Ref t; if (left_type >= 0 && left_type < Variant::VARIANT_MAX) { t = type_icons[left_type]; } if (t.is_valid()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(t); tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); hbc->add_child(tf); } hbc->add_child(memnew(Label(left_name))); if (left_type != Variant::NIL && !script->is_input_value_port_connected(edited_func, E->get(), 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 Variant::CallError ce; const Variant *existingp = &value; value = Variant::construct(left_type, &existingp, 1, ce, false); } if (left_type == Variant::COLOR) { button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); button->connect("draw", this, "_draw_color_over_button", varray(button, value)); } else if (left_type == Variant::OBJECT && Ref(value).is_valid()) { Ref res = value; Array arr; arr.push_back(button->get_instance_id()); arr.push_back(String(value)); EditorResourcePreview::get_singleton()->queue_edited_resource_preview(res, this, "_button_resource_previewed", arr); } else if (pi.type == Variant::INT && pi.hint == PROPERTY_HINT_ENUM) { button->set_text(pi.hint_string.get_slice(",", value)); } else { button->set_text(value); } button->connect("pressed", this, "_default_value_edited", varray(button, E->get(), i)); hbc->add_child(button); } } else { Control *c = memnew(Control); c->set_custom_minimum_size(Size2(10, 0) * EDSCALE); hbc->add_child(c); } hbc->add_spacer(); if (i < mixed_seq_ports) { Label *text2 = memnew(Label); text2->set_text(node->get_output_sequence_port_text(i)); text2->set_align(Label::ALIGN_RIGHT); hbc->add_child(text2); } if (right_ok) { hbc->add_child(memnew(Label(right_name))); Ref t; if (right_type >= 0 && right_type < Variant::VARIANT_MAX) { t = type_icons[right_type]; } if (t.is_valid()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(t); tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); hbc->add_child(tf); } } gnode->add_child(hbc); bool dark_theme = get_constant("dark_theme", "Editor"); if (i < mixed_seq_ports) { gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), true, TYPE_SEQUENCE, mono_color, Ref(), seq_port); } else { gnode->set_slot(slot_idx, left_ok, left_type, _color_from_type(left_type, dark_theme), right_ok, right_type, _color_from_type(right_type, dark_theme)); } slot_idx++; } graph->add_child(gnode); if (gnode->is_comment()) { graph->move_child(gnode, 0); } } _update_graph_connections(); graph->call_deferred("set_scroll_ofs", script->get_function_scroll(edited_func) * EDSCALE); //may need to adapt a bit, let it do so updating_graph = false; } 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_icon("Override", "EditorIcons"), 1); functions->add_button(0, Control::get_icon("Add", "EditorIcons"), 0); functions->set_custom_color(0, Control::get_color("mono_color", "Editor")); List func_names; script->get_function_list(&func_names); for (List::Element *E = func_names.front(); E; E = E->next()) { TreeItem *ti = members->create_item(functions); ti->set_text(0, E->get()); ti->set_selectable(0, true); ti->set_editable(0, true); ti->set_metadata(0, E->get()); if (selected == E->get()) 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_icon("Add", "EditorIcons")); variables->set_custom_color(0, Control::get_color("mono_color", "Editor")); Ref type_icons[Variant::VARIANT_MAX] = { Control::get_icon("Variant", "EditorIcons"), Control::get_icon("bool", "EditorIcons"), Control::get_icon("int", "EditorIcons"), Control::get_icon("float", "EditorIcons"), Control::get_icon("String", "EditorIcons"), Control::get_icon("Vector2", "EditorIcons"), Control::get_icon("Rect2", "EditorIcons"), Control::get_icon("Vector3", "EditorIcons"), Control::get_icon("Transform2D", "EditorIcons"), Control::get_icon("Plane", "EditorIcons"), Control::get_icon("Quat", "EditorIcons"), Control::get_icon("AABB", "EditorIcons"), Control::get_icon("Basis", "EditorIcons"), Control::get_icon("Transform", "EditorIcons"), Control::get_icon("Color", "EditorIcons"), Control::get_icon("NodePath", "EditorIcons"), Control::get_icon("RID", "EditorIcons"), Control::get_icon("MiniObject", "EditorIcons"), Control::get_icon("Dictionary", "EditorIcons"), Control::get_icon("Array", "EditorIcons"), Control::get_icon("PoolByteArray", "EditorIcons"), Control::get_icon("PoolIntArray", "EditorIcons"), Control::get_icon("PoolRealArray", "EditorIcons"), Control::get_icon("PoolStringArray", "EditorIcons"), Control::get_icon("PoolVector2Array", "EditorIcons"), Control::get_icon("PoolVector3Array", "EditorIcons"), Control::get_icon("PoolColorArray", "EditorIcons") }; List var_names; script->get_variable_list(&var_names); for (List::Element *E = var_names.front(); E; E = E->next()) { TreeItem *ti = members->create_item(variables); ti->set_text(0, E->get()); Variant var = script->get_variable_default_value(E->get()); ti->set_suffix(0, "= " + String(var)); ti->set_icon(0, type_icons[script->get_variable_info(E->get()).type]); ti->set_selectable(0, true); ti->set_editable(0, true); ti->set_metadata(0, E->get()); if (selected == E->get()) 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_icon("Add", "EditorIcons")); _signals->set_custom_color(0, Control::get_color("mono_color", "Editor")); List signal_names; script->get_custom_signal_list(&signal_names); for (List::Element *E = signal_names.front(); E; E = E->next()) { TreeItem *ti = members->create_item(_signals); ti->set_text(0, E->get()); ti->set_selectable(0, true); ti->set_editable(0, true); ti->set_metadata(0, E->get()); if (selected == E->get()) ti->select(0); } String base_type = script->get_instance_base_type(); String icon_type = base_type; if (!Control::has_icon(base_type, "EditorIcons")) { icon_type = "Object"; } base_type_select->set_text(base_type); base_type_select->set_icon(Control::get_icon(icon_type, "EditorIcons")); updating_members = false; } 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_children()) { if (edited_func != selected) { revert_on_drag = edited_func; edited_func = selected; _update_members(); _update_graph(); } return; //or crash because it will become invalid } } 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_children()) { if (edited_func == selected) { edited_func = new_name; } selected = new_name; int node_id = script->get_function_node_id(name); Ref func; if (script->has_node(name, node_id)) { func = script->get_node(name, 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); } 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->get_parent() == root->get_children()->get_next()) { selected = new_name; undo_redo->create_action(TTR("Rename Variable")); undo_redo->add_do_method(script.ptr(), "rename_variable", name, new_name); undo_redo->add_undo_method(script.ptr(), "rename_variable", new_name, name); undo_redo->add_do_method(this, "_update_members"); undo_redo->add_undo_method(this, "_update_members"); undo_redo->add_do_method(this, "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_children()->get_next()->get_next()) { selected = new_name; undo_redo->create_action(TTR("Rename Signal")); undo_redo->add_do_method(script.ptr(), "rename_custom_signal", name, new_name); undo_redo->add_undo_method(script.ptr(), "rename_custom_signal", new_name, name); undo_redo->add_do_method(this, "_update_members"); undo_redo->add_undo_method(this, "_update_members"); undo_redo->add_do_method(this, "emit_signal", "edited_script_changed"); undo_redo->add_undo_method(this, "emit_signal", "edited_script_changed"); undo_redo->commit_action(); return; //or crash because it will become invalid } } void VisualScriptEditor::_member_button(Object *p_item, int p_column, int p_button) { TreeItem *ti = Object::cast_to(p_item); TreeItem *root = members->get_root(); if (ti->get_parent() == root) { //main buttons if (ti == root->get_children()) { //add function, this one uses menu if (p_button == 1) { new_virtual_method_select->select_method_from_base_type(script->get_instance_base_type(), String(), true); return; } else if (p_button == 0) { String name = _validate_name("new_function"); selected = name; edited_func = selected; Ref func_node; func_node.instance(); func_node->set_name(name); undo_redo->create_action(TTR("Add Function")); undo_redo->add_do_method(script.ptr(), "add_function", name); undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node); 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->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_children()->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_children()->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 } } } void VisualScriptEditor::_expression_text_changed(const String &p_text, int p_id) { Ref vse = script->get_node(edited_func, p_id); if (!vse.is_valid()) return; updating_graph = true; undo_redo->create_action(TTR("Change Expression"), UndoRedo::MERGE_ENDS); undo_redo->add_do_property(vse.ptr(), "expression", p_text); undo_redo->add_undo_property(vse.ptr(), "expression", vse->get("expression")); undo_redo->add_do_method(this, "_update_graph", p_id); undo_redo->add_undo_method(this, "_update_graph", p_id); undo_redo->commit_action(); Node *node = graph->get_node(itos(p_id)); if (Object::cast_to(node)) Object::cast_to(node)->set_size(Vector2(1, 1)); //shrink if text is smaller updating_graph = false; } void VisualScriptEditor::_available_node_doubleclicked() { TreeItem *item = nodes->get_selected(); if (!item) return; String which = item->get_metadata(0); if (which == String()) return; Vector2 ofs = graph->get_scroll_ofs() + graph->get_size() * 0.5; if (graph->is_using_snap()) { int snap = graph->get_snap(); ofs = ofs.snapped(Vector2(snap, snap)); } ofs /= EDSCALE; while (true) { bool exists = false; List existing; script->get_node_list(edited_func, &existing); for (List::Element *E = existing.front(); E; E = E->next()) { Point2 pos = script->get_node_position(edited_func, E->get()); if (pos.distance_to(ofs) < 15) { ofs += Vector2(graph->get_snap(), graph->get_snap()); exists = true; break; } } if (exists) continue; break; } Ref vnode = VisualScriptLanguage::singleton->create_node_from_name(which); int new_id = script->get_available_id(); undo_redo->create_action(TTR("Add Node")); undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, vnode, ofs); undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, 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); } } void VisualScriptEditor::_update_available_nodes() { nodes->clear(); TreeItem *root = nodes->create_item(); Map path_cache; String filter = node_filter->get_text(); List fnodes; VisualScriptLanguage::singleton->get_registered_node_names(&fnodes); for (List::Element *E = fnodes.front(); E; E = E->next()) { Vector path = E->get().split("/"); if (filter != String() && path.size() && path[path.size() - 1].findn(filter) == -1) continue; String sp; TreeItem *parent = root; for (int i = 0; i < path.size() - 1; i++) { if (i > 0) sp += ","; sp += path[i]; if (!path_cache.has(sp)) { TreeItem *pathn = nodes->create_item(parent); pathn->set_selectable(0, false); pathn->set_text(0, path[i].capitalize()); path_cache[sp] = pathn; parent = pathn; if (filter == String()) { pathn->set_collapsed(true); //should remember state } } else { parent = path_cache[sp]; } } TreeItem *item = nodes->create_item(parent); item->set_text(0, path[path.size() - 1].capitalize()); item->set_selectable(0, true); item->set_metadata(0, E->get()); } } 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_delete() { List to_erase; for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to(graph->get_child(i)); if (gn) { if (gn->is_selected() && gn->is_close_button_visible()) { to_erase.push_back(gn->get_name().operator String().to_int()); } } } if (to_erase.empty()) return; undo_redo->create_action(TTR("Remove VisualScript Nodes")); for (List::Element *F = to_erase.front(); F; F = F->next()) { undo_redo->add_do_method(script.ptr(), "remove_node", edited_func, F->get()); undo_redo->add_undo_method(script.ptr(), "add_node", edited_func, F->get(), script->get_node(edited_func, F->get()), script->get_node_position(edited_func, F->get())); List sequence_conns; script->get_sequence_connection_list(edited_func, &sequence_conns); for (List::Element *E = sequence_conns.front(); E; E = E->next()) { if (E->get().from_node == F->get() || E->get().to_node == F->get()) { undo_redo->add_undo_method(script.ptr(), "sequence_connect", edited_func, E->get().from_node, E->get().from_output, E->get().to_node); } } List data_conns; script->get_data_connection_list(edited_func, &data_conns); for (List::Element *E = data_conns.front(); E; E = E->next()) { if (E->get().from_node == F->get() || E->get().to_node == F->get()) { undo_redo->add_undo_method(script.ptr(), "data_connect", edited_func, E->get().from_node, E->get().from_port, 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(); } void VisualScriptEditor::_on_nodes_duplicate() { List to_duplicate; for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to(graph->get_child(i)); if (gn) { if (gn->is_selected() && gn->is_close_button_visible()) { to_duplicate.push_back(gn->get_name().operator String().to_int()); } } } if (to_duplicate.empty()) return; undo_redo->create_action(TTR("Duplicate VisualScript Nodes")); int idc = script->get_available_id() + 1; Set to_select; for (List::Element *F = to_duplicate.front(); F; F = F->next()) { Ref node = script->get_node(edited_func, F->get()); Ref dupe = node->duplicate(true); int new_id = idc++; to_select.insert(new_id); undo_redo->add_do_method(script.ptr(), "add_node", edited_func, new_id, dupe, script->get_node_position(edited_func, F->get()) + Vector2(20, 20)); undo_redo->add_undo_method(script.ptr(), "remove_node", edited_func, new_id); } undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to(graph->get_child(i)); if (gn) { int id = gn->get_name().operator String().to_int(); gn->set_selected(to_select.has(id)); } } if (to_select.size()) { EditorNode::get_singleton()->push_item(script->get_node(edited_func, to_select.front()->get()).ptr()); } } void VisualScriptEditor::_input(const Ref &p_event) { Ref mb = p_event; if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == BUTTON_LEFT) { revert_on_drag = String(); //so we can still drag functions } } void VisualScriptEditor::_generic_search() { port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position(); new_connect_node_select->select_from_visual_script(String(""), false); } void VisualScriptEditor::_members_gui_input(const Ref &p_event) { Ref key = p_event; if (key.is_valid() && key->is_pressed() && !key->is_echo()) { if (members->has_focus()) { TreeItem *ti = members->get_selected(); if (ti) { TreeItem *root = members->get_root(); if (ti->get_parent() == root->get_children()) { member_type = MEMBER_FUNCTION; } if (ti->get_parent() == root->get_children()->get_next()) { member_type = MEMBER_VARIABLE; } if (ti->get_parent() == root->get_children()->get_next()->get_next()) { member_type = MEMBER_SIGNAL; } member_name = ti->get_text(0); } if (ED_IS_SHORTCUT("visual_script_editor/delete_selected", p_event)) { _member_option(MEMBER_REMOVE); } if (ED_IS_SHORTCUT("visual_script_editor/edit_member", p_event)) { _member_option(MEMBER_EDIT); } } } } Variant VisualScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (p_from == nodes) { TreeItem *it = nodes->get_item_at_position(p_point); if (!it) return Variant(); String type = it->get_metadata(0); if (type == String()) return Variant(); Dictionary dd; dd["type"] = "visual_script_node_drag"; dd["node_type"] = type; Label *label = memnew(Label); label->set_text(it->get_text(0)); set_drag_preview(label); return dd; } if (p_from == members) { TreeItem *it = members->get_item_at_position(p_point); if (!it) return Variant(); String type = it->get_metadata(0); if (type == String()) return Variant(); Dictionary dd; TreeItem *root = members->get_root(); if (it->get_parent() == root->get_children()) { dd["type"] = "visual_script_function_drag"; dd["function"] = type; if (revert_on_drag != String()) { edited_func = revert_on_drag; //revert so function does not change revert_on_drag = String(); _update_graph(); } } else if (it->get_parent() == root->get_children()->get_next()) { dd["type"] = "visual_script_variable_drag"; dd["variable"] = type; } else if (it->get_parent() == root->get_children()->get_next()->get_next()) { dd["type"] = "visual_script_signal_drag"; dd["signal"] = type; } else { return Variant(); } Label *label = memnew(Label); label->set_text(it->get_text(0)); set_drag_preview(label); return dd; } return Variant(); } bool VisualScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { if (p_from == graph) { Dictionary d = p_data; if (d.has("type") && (String(d["type"]) == "visual_script_node_drag" || String(d["type"]) == "visual_script_function_drag" || String(d["type"]) == "visual_script_variable_drag" || String(d["type"]) == "visual_script_signal_drag" || String(d["type"]) == "obj_property" || String(d["type"]) == "resource" || String(d["type"]) == "files" || String(d["type"]) == "nodes")) { if (String(d["type"]) == "obj_property") { #ifdef OSX_ENABLED const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a Getter. Hold Shift to drop a generic signature."), find_keycode_name(KEY_META))); #else const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a Getter. Hold Shift to drop a generic signature.")); #endif } if (String(d["type"]) == "nodes") { #ifdef OSX_ENABLED const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a simple reference to the node."), find_keycode_name(KEY_META))); #else const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a simple reference to the node.")); #endif } if (String(d["type"]) == "visual_script_variable_drag") { #ifdef OSX_ENABLED const_cast(this)->_show_hint(vformat(TTR("Hold %s to drop a Variable Setter."), find_keycode_name(KEY_META))); #else const_cast(this)->_show_hint(TTR("Hold Ctrl to drop a Variable Setter.")); #endif } return true; } } return false; } #ifdef TOOLS_ENABLED static Node *_find_script_node(Node *p_edited_scene, Node *p_current_node, const Ref