/*************************************************************************/ /* visual_script_editor.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* http://www.godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ /* */ /* 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 "editor/editor_node.h" #include "visual_script_nodes.h" #include "visual_script_flow_control.h" #include "visual_script_func_nodes.h" #include "visual_script_expression.h" #include "os/input.h" #include "editor/editor_resource_preview.h" #include "os/keyboard.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); } void _sig_changed() { _change_notify(); } 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("Change Signal Arguments"); if (new_argc < argc) { for(int i=new_argc;iadd_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;iadd_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("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("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;icustom_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); } void _var_changed() { _change_notify(); } void _var_value_changed() { _change_notify("value"); //so the whole tree is not redrawn, makes editing smoother in general } bool _set(const StringName& p_name, const Variant& p_value) { if (var==StringName()) return false; if (String(p_name)=="value") { undo_redo->create_action("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.copy(); dc["type"]=p_value; undo_redo->create_action("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.copy(); dc["hint"]=p_value; undo_redo->create_action("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.copy(); dc["hint_string"]=p_value; undo_redo->create_action("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); 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;ipush_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)); p_list->push_back(PropertyInfo(Variant::INT,"hint",PROPERTY_HINT_ENUM,"None,Range,ExpRange,Enum,ExpEasing,Length,SpriteFrame,KeyAccel,BitFlags,AllFlags,File,Dir,GlobalFile,GlobalDir,ResourceType,MultilineText")); 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) { Color color; 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::RECT3: 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::IMAGE: color = Color::html("93f1b9"); 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::INPUT_EVENT: color = Color::html("adf18f"); 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); } 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;iget_child_count();i++) { if (graph->get_child(i)->cast_to()) { 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("MiniVariant","EditorIcons"), Control::get_icon("MiniBoolean","EditorIcons"), Control::get_icon("MiniInteger","EditorIcons"), Control::get_icon("MiniFloat","EditorIcons"), Control::get_icon("MiniString","EditorIcons"), Control::get_icon("MiniVector2","EditorIcons"), Control::get_icon("MiniRect2","EditorIcons"), Control::get_icon("MiniVector3","EditorIcons"), Control::get_icon("MiniTransform2D","EditorIcons"), Control::get_icon("MiniPlane","EditorIcons"), Control::get_icon("MiniQuat","EditorIcons"), Control::get_icon("MiniAabb","EditorIcons"), Control::get_icon("MiniBasis","EditorIcons"), Control::get_icon("MiniTransform","EditorIcons"), Control::get_icon("MiniColor","EditorIcons"), Control::get_icon("MiniImage","EditorIcons"), Control::get_icon("MiniPath","EditorIcons"), Control::get_icon("MiniRid","EditorIcons"), Control::get_icon("MiniObject","EditorIcons"), Control::get_icon("MiniInput","EditorIcons"), Control::get_icon("MiniDictionary","EditorIcons"), Control::get_icon("MiniArray","EditorIcons"), Control::get_icon("MiniRawArray","EditorIcons"), Control::get_icon("MiniIntArray","EditorIcons"), Control::get_icon("MiniFloatArray","EditorIcons"), Control::get_icon("MiniStringArray","EditorIcons"), Control::get_icon("MiniVector2Array","EditorIcons"), Control::get_icon("MiniVector3Array","EditorIcons"), Control::get_icon("MiniColorArray","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_pos(edited_func,E->get()); GraphNode *gnode = memnew( GraphNode ); gnode->set_title(node->get_caption()); if (error_line==E->get()) { gnode->set_overlay(GraphNode::OVERLAY_POSITION); } else if (node->is_breakpoint()) { gnode->set_overlay(GraphNode::OVERLAY_BREAKPOINT); } if (EditorSettings::get_singleton()->has("editors/visual_script/color_"+node->get_category())) { gnode->set_modulate(EditorSettings::get_singleton()->get("editors/visual_script/color_"+node->get_category())); } 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); } if (node->cast_to()) { 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 { Label *text = memnew( Label ); text->set_text(node->get_text()); gnode->add_child(text); } if (node->cast_to()) { Ref vsc=node; gnode->set_comment(true); gnode->set_resizeable(true); gnode->set_custom_minimum_size(vsc->get_size()*EDSCALE); gnode->connect("resize_request",this,"_comment_node_resized",varray(E->get())); } int slot_idx=0; bool single_seq_output = node->get_output_sequence_port_count()==1 && node->get_output_sequence_port_text(0)==String(); gnode->set_slot(0,node->has_input_sequence_port(),TYPE_SEQUENCE,Color(1,1,1,1),single_seq_output,TYPE_SEQUENCE,Color(1,1,1,1),seq_port,seq_port); gnode->set_offset(pos*EDSCALE); 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;iget_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,Color(1,1,1,1),seq_port,seq_port); slot_idx++; } } } for(int i=0;iget_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 (iget_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 && iget_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_typeset_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 (iset_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_typeset_texture(t); tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); hbc->add_child(tf); } } gnode->add_child(hbc); if (iset_slot(slot_idx,left_ok,left_type,_color_from_type(left_type),true,TYPE_SEQUENCE,Color(1,1,1,1),Ref(),seq_port); } else { gnode->set_slot(slot_idx,left_ok,left_type,_color_from_type(left_type),right_ok,right_type,_color_from_type(right_type)); } 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() { 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_bg_color(0,Control::get_color("prop_section","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->add_button(0,Control::get_icon("Edit","EditorIcons"),0); function arguments are in the node now ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); ti->set_metadata(0,E->get()); if (E->get()==edited_func) { ti->set_custom_bg_color(0,get_color("prop_category","Editor")); ti->set_custom_color(0,Color(1,1,1,1)); } 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_bg_color(0,Control::get_color("prop_section","Editor")); Ref type_icons[Variant::VARIANT_MAX]={ Control::get_icon("MiniVariant","EditorIcons"), Control::get_icon("MiniBoolean","EditorIcons"), Control::get_icon("MiniInteger","EditorIcons"), Control::get_icon("MiniFloat","EditorIcons"), Control::get_icon("MiniString","EditorIcons"), Control::get_icon("MiniVector2","EditorIcons"), Control::get_icon("MiniRect2","EditorIcons"), Control::get_icon("MiniVector3","EditorIcons"), Control::get_icon("MiniMatrix32","EditorIcons"), Control::get_icon("MiniPlane","EditorIcons"), Control::get_icon("MiniQuat","EditorIcons"), Control::get_icon("MiniAabb","EditorIcons"), Control::get_icon("MiniMatrix3","EditorIcons"), Control::get_icon("MiniTransform","EditorIcons"), Control::get_icon("MiniColor","EditorIcons"), Control::get_icon("MiniImage","EditorIcons"), Control::get_icon("MiniPath","EditorIcons"), Control::get_icon("MiniRid","EditorIcons"), Control::get_icon("MiniObject","EditorIcons"), Control::get_icon("MiniInput","EditorIcons"), Control::get_icon("MiniDictionary","EditorIcons"), Control::get_icon("MiniArray","EditorIcons"), Control::get_icon("MiniRawArray","EditorIcons"), Control::get_icon("MiniIntArray","EditorIcons"), Control::get_icon("MiniFloatArray","EditorIcons"), Control::get_icon("MiniStringArray","EditorIcons"), Control::get_icon("MiniVector2Array","EditorIcons"), Control::get_icon("MiniVector3Array","EditorIcons"), Control::get_icon("MiniColorArray","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->add_button(0,Control::get_icon("Edit","EditorIcons"),0); ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); 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_bg_color(0,Control::get_color("prop_section","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->add_button(0,Control::get_icon("Edit","EditorIcons"),0); ti->add_button(0,Control::get_icon("Del","EditorIcons"),1); 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); //print_line("selected: "+String(selected)); 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; _update_graph(); 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); undo_redo->add_do_method(this,"_update_members"); undo_redo->add_undo_method(this,"_update_members"); undo_redo->commit_action(); 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->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->commit_action(); return; //or crash because it will become invalid } } void VisualScriptEditor::_override_pressed(int p_id) { //override a virtual function or method from base type ERR_FAIL_COND(!virtuals_in_menu.has(p_id)); VirtualInMenu vim=virtuals_in_menu[p_id]; String name = _validate_name(vim.name); selected=name; edited_func=selected; Ref func_node; func_node.instance(); func_node->set_name(vim.name); undo_redo->create_action(TTR("Add Function")); undo_redo->add_do_method(script.ptr(),"add_function",name); for(int i=0;iadd_argument(vim.args[i].first,vim.args[i].second); } undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id(),func_node); if (vim.ret!=Variant::NIL || vim.ret_variant) { Ref ret_node; ret_node.instance(); ret_node->set_return_type(vim.ret); ret_node->set_enable_return_value(true); ret_node->set_name(vim.name); undo_redo->add_do_method(script.ptr(),"add_node",name,script->get_available_id()+1,ret_node,Vector2(500,0)); } 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::_member_button(Object *p_item, int p_column, int p_button) { TreeItem *ti=p_item->cast_to(); 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_function_menu->clear(); new_function_menu->set_size(Size2(0,0)); int idx=0; virtuals_in_menu.clear(); List mi; ClassDB::get_method_list(script->get_instance_base_type(),&mi); for (List::Element *E=mi.front();E;E=E->next()) { MethodInfo mi=E->get(); if (mi.flags&METHOD_FLAG_VIRTUAL) { VirtualInMenu vim; vim.name=mi.name; vim.ret=mi.return_val.type; if (mi.return_val.name!=String()) vim.ret_variant=true; else vim.ret_variant=false; String desc; if (mi.return_val.type==Variant::NIL) desc="var"; else desc=Variant::get_type_name(mi.return_val.type); desc+=" "+mi.name+" ( "; for(int i=0;i0) desc+=", "; if (mi.arguments[i].type==Variant::NIL) desc+="var "; else desc+=Variant::get_type_name(mi.arguments[i].type)+" "; desc+=mi.arguments[i].name; Pair p; p.first=mi.arguments[i].type; p.second=mi.arguments[i].name; vim.args.push_back( p ); } desc+=" )"; virtuals_in_menu[idx]=vim; new_function_menu->add_item(desc,idx); idx++; } } Rect2 pos = members->get_item_rect(ti); new_function_menu->set_pos(members->get_global_pos()+pos.pos+Vector2(0,pos.size.y)); new_function_menu->popup(); 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->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->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->commit_action(); return; //or crash because it will become invalid } } else { if (ti->get_parent()==root->get_children()) { //edit/remove function String name = ti->get_metadata(0); if (p_button==1) { //delete the function undo_redo->create_action(TTR("Remove Function")); undo_redo->add_do_method(script.ptr(),"remove_function",name); undo_redo->add_undo_method(script.ptr(),"add_function",name); List nodes; script->get_node_list(name,&nodes); for (List::Element *E=nodes.front();E;E=E->next()) { undo_redo->add_undo_method(script.ptr(),"add_node",name,E->get(),script->get_node(name,E->get()),script->get_node_pos(name,E->get())); } List seq_connections; script->get_sequence_connection_list(name,&seq_connections); for (List::Element *E=seq_connections.front();E;E=E->next()) { undo_redo->add_undo_method(script.ptr(),"sequence_connect",name,E->get().from_node,E->get().from_output,E->get().to_node); } List data_connections; script->get_data_connection_list(name,&data_connections); for (List::Element *E=data_connections.front();E;E=E->next()) { undo_redo->add_undo_method(script.ptr(),"data_connect",name,E->get().from_node,E->get().from_port,E->get().to_node,E->get().to_port); } /* for(int i=0;ifunction_get_argument_count(name);i++) { undo_redo->add_undo_method(script.ptr(),"function_add_argument",name,script->function_get_argument_name(name,i),script->function_get_argument_type(name,i)); } */ 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_button==0) { } return; //or crash because it will become invalid } if (ti->get_parent()==root->get_children()->get_next()) { //edit/remove variable String name = ti->get_metadata(0); if (p_button==1) { 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(); return; //or crash because it will become invalid } else if (p_button==0) { variable_editor->edit(name); edit_variable_dialog->set_title(TTR("Editing Variable:")+" "+name); edit_variable_dialog->popup_centered_minsize(Size2(400,200)*EDSCALE); } } if (ti->get_parent()==root->get_children()->get_next()->get_next()) { //edit/remove variable String name = ti->get_metadata(0); if (p_button==1) { 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;icustom_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_button==0) { signal_editor->edit(name); edit_signal_dialog->set_title(TTR("Editing Signal:")+" "+name); edit_signal_dialog->popup_centered_minsize(Size2(400,300)*EDSCALE); } 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 (node->cast_to()) node->cast_to()->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_pos(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;i0) 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;iget_child_count();i++) { GraphNode *gn = graph->get_child(i)->cast_to(); 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("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_pos(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;iget_child_count();i++) { GraphNode *gn = graph->get_child(i)->cast_to(); 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("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(); 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_pos(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;iget_child_count();i++) { GraphNode *gn = graph->get_child(i)->cast_to(); 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 InputEvent& p_event) { if (p_event.type==InputEvent::MOUSE_BUTTON && !p_event.mouse_button.pressed && p_event.mouse_button.button_index==BUTTON_LEFT) { revert_on_drag=String(); //so we can still drag functions } } Variant VisualScriptEditor::get_drag_data_fw(const Point2& p_point,Control* p_from) { if (p_from==nodes) { TreeItem *it = nodes->get_item_at_pos(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_pos(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(TTR("Hold Meta to drop a Getter. Hold Shift to drop a generic signature.")); #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(TTR("Hold Meta to drop a simple reference to the node.")); #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(TTR("Hold Meta to drop a Variable Setter.")); #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