7247247522
Unsaved scripts were previously displayed with blank tabs, which are mostly a result of deleted or improperly moved scripts. This patch makes sure that those kind of scripts are displayed as "[unsaved]" now, and ensures that scripts are removed from the list while deleting scripts from the filesystem dock preventing the unsaved tabs to appear in the first place (a user is already prompted with "no undo" warning while deleting any file). A user is always prompted to save those "[unsaved]" scripts if they attempt to close them without saving in any case except as described above.
5018 lines
173 KiB
C++
5018 lines
173 KiB
C++
/*************************************************************************/
|
|
/* visual_script_editor.cpp */
|
|
/*************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/*************************************************************************/
|
|
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
|
|
/* Copyright (c) 2014-2020 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/input/input.h"
|
|
#include "core/object.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 "editor/editor_scale.h"
|
|
#include "scene/main/window.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<VisualScript> 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<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;
|
|
_change_notify();
|
|
}
|
|
|
|
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() {
|
|
_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<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;
|
|
_change_notify();
|
|
}
|
|
|
|
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::QUAT:
|
|
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::TRANSFORM:
|
|
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::QUAT:
|
|
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::TRANSFORM:
|
|
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<StringName> funcs;
|
|
script->get_function_list(&funcs);
|
|
|
|
if (funcs.size() <= 0) {
|
|
updating_graph = false;
|
|
return;
|
|
}
|
|
|
|
for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) {
|
|
List<VisualScript::SequenceConnection> sequence_conns;
|
|
script->get_sequence_connection_list(F->get(), &sequence_conns);
|
|
|
|
for (List<VisualScript::SequenceConnection>::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<VisualScript::DataConnection> data_conns;
|
|
script->get_data_connection_list(F->get(), &data_conns);
|
|
|
|
for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) {
|
|
VisualScript::DataConnection dc = E->get();
|
|
|
|
Ref<VisualScriptNode> from_node = script->get_node(F->get(), E->get().from_node);
|
|
Ref<VisualScriptNode> to_node = script->get_node(F->get(), 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<GraphNode>(graph->get_child(i))) {
|
|
memdelete(graph->get_child(i));
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
List<StringName> funcs;
|
|
script->get_function_list(&funcs);
|
|
|
|
if (funcs.size() <= 0) {
|
|
graph->hide();
|
|
select_func_text->show();
|
|
updating_graph = false;
|
|
return;
|
|
}
|
|
|
|
graph->show();
|
|
select_func_text->hide();
|
|
|
|
Ref<Texture2D> type_icons[Variant::VARIANT_MAX] = {
|
|
Control::get_theme_icon("Variant", "EditorIcons"),
|
|
Control::get_theme_icon("bool", "EditorIcons"),
|
|
Control::get_theme_icon("int", "EditorIcons"),
|
|
Control::get_theme_icon("float", "EditorIcons"),
|
|
Control::get_theme_icon("String", "EditorIcons"),
|
|
Control::get_theme_icon("Vector2", "EditorIcons"),
|
|
Control::get_theme_icon("Vector2i", "EditorIcons"),
|
|
Control::get_theme_icon("Rect2", "EditorIcons"),
|
|
Control::get_theme_icon("Rect2i", "EditorIcons"),
|
|
Control::get_theme_icon("Vector3", "EditorIcons"),
|
|
Control::get_theme_icon("Vector3i", "EditorIcons"),
|
|
Control::get_theme_icon("Transform2D", "EditorIcons"),
|
|
Control::get_theme_icon("Plane", "EditorIcons"),
|
|
Control::get_theme_icon("Quat", "EditorIcons"),
|
|
Control::get_theme_icon("AABB", "EditorIcons"),
|
|
Control::get_theme_icon("Basis", "EditorIcons"),
|
|
Control::get_theme_icon("Transform", "EditorIcons"),
|
|
Control::get_theme_icon("Color", "EditorIcons"),
|
|
Control::get_theme_icon("NodePath", "EditorIcons"),
|
|
Control::get_theme_icon("RID", "EditorIcons"),
|
|
Control::get_theme_icon("MiniObject", "EditorIcons"),
|
|
Control::get_theme_icon("Callable", "EditorIcons"),
|
|
Control::get_theme_icon("Signal", "EditorIcons"),
|
|
Control::get_theme_icon("Dictionary", "EditorIcons"),
|
|
Control::get_theme_icon("Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedByteArray", "EditorIcons"),
|
|
Control::get_theme_icon("PackedInt32Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedFloat32Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedStringArray", "EditorIcons"),
|
|
Control::get_theme_icon("PackedVector2Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedVector3Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedColorArray", "EditorIcons")
|
|
};
|
|
|
|
Ref<Texture2D> seq_port = Control::get_theme_icon("VisualShaderPort", "EditorIcons");
|
|
|
|
for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) { // loop through all the functions
|
|
|
|
List<int> ids;
|
|
script->get_node_list(F->get(), &ids);
|
|
StringName editor_icons = "EditorIcons";
|
|
|
|
for (List<int>::Element *E = ids.front(); E; E = E->next()) {
|
|
if (p_only_id >= 0 && p_only_id != E->get()) {
|
|
continue;
|
|
}
|
|
|
|
Ref<VisualScriptNode> node = script->get_node(F->get(), E->get());
|
|
Vector2 pos = script->get_node_position(F->get(), 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", callable_mp(this, &VisualScriptEditor::_node_moved), varray(E->get()));
|
|
gnode->connect("close_request", callable_mp(this, &VisualScriptEditor::_remove_node), varray(E->get()), CONNECT_DEFERRED);
|
|
|
|
if (E->get() != script->get_function_node_id(F->get())) {
|
|
//function can't be erased
|
|
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->get()), 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->get()), 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(true);
|
|
line_edit->add_theme_font_override("font", get_theme_font("source", "EditorFonts"));
|
|
gnode->add_child(line_edit);
|
|
line_edit->connect("text_changed", callable_mp(this, &VisualScriptEditor::_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<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->get()));
|
|
}
|
|
|
|
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("comment", "GraphNode");
|
|
}
|
|
|
|
Color c = sbf->get_border_color();
|
|
Color ic = c;
|
|
c.a = 1;
|
|
if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) {
|
|
Color mono_color;
|
|
if (((c.r + c.g + c.b) / 3) < 0.7) {
|
|
mono_color = Color(1.0, 1.0, 1.0);
|
|
ic = Color(0.0, 0.0, 0.0, 0.7);
|
|
} else {
|
|
mono_color = Color(0.0, 0.0, 0.0);
|
|
ic = Color(1.0, 1.0, 1.0, 0.7);
|
|
}
|
|
mono_color.a = 0.85;
|
|
c = mono_color;
|
|
}
|
|
gnode->add_theme_color_override("title_color", c);
|
|
c.a = 0.7;
|
|
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("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;
|
|
}
|
|
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(true);
|
|
name_box->connect("resized", callable_mp(this, &VisualScriptEditor::_update_node_size), varray(E->get()));
|
|
name_box->connect("focus_exited", callable_mp(this, &VisualScriptEditor::_port_name_focus_out), varray(name_box, E->get(), 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->get(), i, true), CONNECT_DEFERRED);
|
|
}
|
|
|
|
Button *rmbtn = memnew(Button);
|
|
rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Remove", "EditorIcons"));
|
|
hbc->add_child(rmbtn);
|
|
rmbtn->connect("pressed", callable_mp(this, &VisualScriptEditor::_remove_input_port), varray(E->get(), i), CONNECT_DEFERRED);
|
|
} else {
|
|
hbc->add_child(memnew(Label(left_name)));
|
|
}
|
|
|
|
if (left_type != Variant::NIL && !script->is_input_value_port_connected(F->get(), 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
|
|
Callable::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", 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->get(), i));
|
|
hbc2->add_child(button);
|
|
}
|
|
} else {
|
|
Control *c = memnew(Control);
|
|
c->set_custom_minimum_size(Size2(10, 0) * EDSCALE);
|
|
hbc->add_child(c);
|
|
}
|
|
|
|
hbc->add_spacer();
|
|
hbc2->add_spacer();
|
|
|
|
if (i < mixed_seq_ports) {
|
|
Label *text2 = memnew(Label);
|
|
text2->set_text(node->get_output_sequence_port_text(i));
|
|
text2->set_align(Label::ALIGN_RIGHT);
|
|
hbc->add_child(text2);
|
|
}
|
|
|
|
if (right_ok) {
|
|
if (is_vslist) {
|
|
Button *rmbtn = memnew(Button);
|
|
rmbtn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Remove", "EditorIcons"));
|
|
hbc->add_child(rmbtn);
|
|
rmbtn->connect("pressed", callable_mp(this, &VisualScriptEditor::_remove_output_port), varray(E->get(), 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->get(), 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(true);
|
|
name_box->connect("resized", callable_mp(this, &VisualScriptEditor::_update_node_size), varray(E->get()));
|
|
name_box->connect("focus_exited", callable_mp(this, &VisualScriptEditor::_port_name_focus_out), varray(name_box, E->get(), 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("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<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();
|
|
// 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("set_scroll_ofs", script->get_function_scroll(default_func) * EDSCALE);
|
|
updating_graph = false;
|
|
}
|
|
|
|
void VisualScriptEditor::_change_port_type(int p_select, int p_id, int p_port, bool is_input) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptLists> vsn = script->get_node(func, p_id);
|
|
if (!vsn.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
undo_redo->create_action("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)->set_size(Vector2(1, 1)); //shrink if text is smaller
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_port_name_focus_out(const Node *p_name_box, int p_id, int p_port, bool is_input) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptLists> vsn = script->get_node(func, 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("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("Override", "EditorIcons"), 1, false, TTR("Override an existing built-in function."));
|
|
functions->add_button(0, Control::get_theme_icon("Add", "EditorIcons"), 0, false, TTR("Create a new function."));
|
|
functions->set_custom_color(0, Control::get_theme_color("mono_color", "Editor"));
|
|
|
|
List<StringName> func_names;
|
|
script->get_function_list(&func_names);
|
|
for (List<StringName>::Element *E = func_names.front(); E; E = E->next()) {
|
|
if (E->get() == default_func) {
|
|
continue;
|
|
}
|
|
|
|
TreeItem *ti = members->create_item(functions);
|
|
ti->set_text(0, E->get());
|
|
ti->set_selectable(0, true);
|
|
ti->set_metadata(0, E->get());
|
|
ti->add_button(0, Control::get_theme_icon("Edit", "EditorIcons"), 0);
|
|
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_theme_icon("Add", "EditorIcons"), -1, false, TTR("Create a new variable."));
|
|
variables->set_custom_color(0, Control::get_theme_color("mono_color", "Editor"));
|
|
|
|
Ref<Texture2D> type_icons[Variant::VARIANT_MAX] = {
|
|
Control::get_theme_icon("Variant", "EditorIcons"),
|
|
Control::get_theme_icon("bool", "EditorIcons"),
|
|
Control::get_theme_icon("int", "EditorIcons"),
|
|
Control::get_theme_icon("float", "EditorIcons"),
|
|
Control::get_theme_icon("String", "EditorIcons"),
|
|
Control::get_theme_icon("Vector2", "EditorIcons"),
|
|
Control::get_theme_icon("Vector2i", "EditorIcons"),
|
|
Control::get_theme_icon("Rect2", "EditorIcons"),
|
|
Control::get_theme_icon("Rect2i", "EditorIcons"),
|
|
Control::get_theme_icon("Vector3", "EditorIcons"),
|
|
Control::get_theme_icon("Vector3i", "EditorIcons"),
|
|
Control::get_theme_icon("Transform2D", "EditorIcons"),
|
|
Control::get_theme_icon("Plane", "EditorIcons"),
|
|
Control::get_theme_icon("Quat", "EditorIcons"),
|
|
Control::get_theme_icon("AABB", "EditorIcons"),
|
|
Control::get_theme_icon("Basis", "EditorIcons"),
|
|
Control::get_theme_icon("Transform", "EditorIcons"),
|
|
Control::get_theme_icon("Color", "EditorIcons"),
|
|
Control::get_theme_icon("NodePath", "EditorIcons"),
|
|
Control::get_theme_icon("RID", "EditorIcons"),
|
|
Control::get_theme_icon("MiniObject", "EditorIcons"),
|
|
Control::get_theme_icon("Callable", "EditorIcons"),
|
|
Control::get_theme_icon("Signal", "EditorIcons"),
|
|
Control::get_theme_icon("Dictionary", "EditorIcons"),
|
|
Control::get_theme_icon("Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedByteArray", "EditorIcons"),
|
|
Control::get_theme_icon("PackedInt32Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedFloat32Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedStringArray", "EditorIcons"),
|
|
Control::get_theme_icon("PackedVector2Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedVector3Array", "EditorIcons"),
|
|
Control::get_theme_icon("PackedColorArray", "EditorIcons")
|
|
};
|
|
|
|
List<StringName> var_names;
|
|
script->get_variable_list(&var_names);
|
|
for (List<StringName>::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_theme_icon("Add", "EditorIcons"), -1, false, TTR("Create a new signal."));
|
|
_signals->set_custom_color(0, Control::get_theme_color("mono_color", "Editor"));
|
|
|
|
List<StringName> signal_names;
|
|
script->get_custom_signal_list(&signal_names);
|
|
for (List<StringName>::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_theme_icon(base_type, "EditorIcons")) {
|
|
icon_type = "Object";
|
|
}
|
|
|
|
base_type_select->set_text(base_type);
|
|
base_type_select->set_icon(Control::get_theme_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()) {
|
|
#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_CONTROL);
|
|
#endif
|
|
if (held_ctrl) {
|
|
ERR_FAIL_COND(!script->has_function(selected));
|
|
_center_on_node(selected, 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_children()) {
|
|
selected = new_name;
|
|
|
|
int node_id = script->get_function_node_id(name);
|
|
Ref<VisualScriptFunction> 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);
|
|
}
|
|
|
|
// also fix all function calls
|
|
List<StringName> flst;
|
|
script->get_function_list(&flst);
|
|
for (List<StringName>::Element *E = flst.front(); E; E = E->next()) {
|
|
List<int> lst;
|
|
script->get_node_list(E->get(), &lst);
|
|
for (List<int>::Element *F = lst.front(); F; F = F->next()) {
|
|
Ref<VisualScriptFunctionCall> fncall = script->get_node(E->get(), F->get());
|
|
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_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, "_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_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::_create_function_dialog() {
|
|
function_create_dialog->popup_centered();
|
|
func_name_box->set_text("");
|
|
func_name_box->grab_focus();
|
|
for (int i = 0; i < func_input_vbox->get_child_count(); i++) {
|
|
Node *nd = func_input_vbox->get_child(i);
|
|
nd->queue_delete();
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_create_function() {
|
|
String name = _validate_name((func_name_box->get_text() == "") ? "new_func" : func_name_box->get_text());
|
|
selected = name;
|
|
Vector2 ofs = _get_available_pos();
|
|
|
|
Ref<VisualScriptFunction> func_node;
|
|
func_node.instance();
|
|
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);
|
|
}
|
|
|
|
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, ofs);
|
|
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();
|
|
}
|
|
|
|
void VisualScriptEditor::_add_node_dialog() {
|
|
_generic_search(script->get_instance_base_type(), graph->get_global_position() + Vector2(55, 80), true);
|
|
}
|
|
|
|
void VisualScriptEditor::_add_func_input() {
|
|
HBoxContainer *hbox = memnew(HBoxContainer);
|
|
hbox->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
|
|
Label *name_label = memnew(Label);
|
|
name_label->set_text(TTR("Name:"));
|
|
hbox->add_child(name_label);
|
|
|
|
LineEdit *name_box = memnew(LineEdit);
|
|
name_box->set_h_size_flags(SIZE_EXPAND_FILL);
|
|
name_box->set_text("input");
|
|
name_box->connect("focus_entered", callable_mp(this, &VisualScriptEditor::_deselect_input_names));
|
|
hbox->add_child(name_box);
|
|
|
|
Label *type_label = memnew(Label);
|
|
type_label->set_text(TTR("Type:"));
|
|
hbox->add_child(type_label);
|
|
|
|
OptionButton *type_box = memnew(OptionButton);
|
|
type_box->set_custom_minimum_size(Size2(120 * EDSCALE, 0));
|
|
for (int i = Variant::NIL; i < Variant::VARIANT_MAX; i++) {
|
|
type_box->add_item(Variant::get_type_name(Variant::Type(i)));
|
|
}
|
|
type_box->select(1);
|
|
hbox->add_child(type_box);
|
|
|
|
Button *delete_button = memnew(Button);
|
|
delete_button->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Remove", "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_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;
|
|
Vector2 ofs = _get_available_pos();
|
|
|
|
Ref<VisualScriptFunction> 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, ofs);
|
|
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
|
|
}
|
|
} else if (ti->get_parent() == root->get_children()) {
|
|
selected = ti->get_text(0);
|
|
function_name_edit->set_position(Input::get_singleton()->get_mouse_position() - Vector2(60, -10));
|
|
function_name_edit->popup();
|
|
function_name_box->set_text(selected);
|
|
function_name_box->select_all();
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_add_input_port(int p_id) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptLists> vsn = script->get_node(func, 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) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptLists> vsn = script->get_node(func, 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) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptLists> vsn = script->get_node(func, 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(func, p_id, p_port, &conn_from, &conn_port);
|
|
|
|
if (conn_from != -1) {
|
|
undo_redo->add_do_method(script.ptr(), "data_disconnect", func, 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", func, 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) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptLists> vsn = script->get_node(func, 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(func, &data_connections);
|
|
|
|
HashMap<int, Set<int>> conn_map;
|
|
for (const List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
|
|
if (E->get().from_node == p_id && E->get().from_port == p_port) {
|
|
// push into the connections map
|
|
if (!conn_map.has(E->get().to_node)) {
|
|
conn_map.set(E->get().to_node, Set<int>());
|
|
}
|
|
conn_map[E->get().to_node].insert(E->get().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 List<int>::Element *E = keys.front(); E; E = E->next()) {
|
|
for (const Set<int>::Element *F = conn_map[E->get()].front(); F; F = F->next()) {
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", func, p_id, p_port, E->get(), F->get());
|
|
}
|
|
}
|
|
|
|
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) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
Ref<VisualScriptExpression> vse = script->get_node(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<Control>(node)) {
|
|
Object::cast_to<Control>(node)->set_size(Vector2(1, 1)); //shrink if text is smaller
|
|
}
|
|
|
|
updating_graph = false;
|
|
}
|
|
|
|
Vector2 VisualScriptEditor::_get_available_pos(bool centered, Vector2 ofs) const {
|
|
if (centered) {
|
|
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<StringName> all_fn;
|
|
script->get_function_list(&all_fn);
|
|
for (List<StringName>::Element *F = all_fn.front(); F; F = F->next()) {
|
|
StringName curr_fn = F->get();
|
|
List<int> existing;
|
|
script->get_node_list(curr_fn, &existing);
|
|
for (List<int>::Element *E = existing.front(); E; E = E->next()) {
|
|
Point2 pos = script->get_node_position(curr_fn, E->get());
|
|
if (pos.distance_to(ofs) < 50) {
|
|
ofs += Vector2(graph->get_snap(), graph->get_snap());
|
|
exists = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (exists) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return ofs;
|
|
}
|
|
|
|
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() {
|
|
// 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.empty()) {
|
|
return;
|
|
}
|
|
|
|
undo_redo->create_action(TTR("Remove VisualScript Nodes"));
|
|
|
|
for (List<int>::Element *F = to_erase.front(); F; F = F->next()) {
|
|
int cr_node = F->get();
|
|
|
|
StringName func = _get_function_of_node(cr_node);
|
|
|
|
undo_redo->add_do_method(script.ptr(), "remove_node", func, cr_node);
|
|
undo_redo->add_undo_method(script.ptr(), "add_node", func, cr_node, script->get_node(func, cr_node), script->get_node_position(func, cr_node));
|
|
|
|
List<VisualScript::SequenceConnection> sequence_conns;
|
|
script->get_sequence_connection_list(func, &sequence_conns);
|
|
|
|
for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
|
|
if (E->get().from_node == cr_node || E->get().to_node == cr_node) {
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, E->get().from_node, E->get().from_output, E->get().to_node);
|
|
}
|
|
}
|
|
|
|
List<VisualScript::DataConnection> data_conns;
|
|
script->get_data_connection_list(func, &data_conns);
|
|
|
|
for (List<VisualScript::DataConnection>::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", 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() {
|
|
Set<int> to_duplicate;
|
|
List<StringName> funcs;
|
|
|
|
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);
|
|
funcs.push_back(_get_function_of_node(id));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (to_duplicate.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
|
|
StringName func = _get_function_of_node(F->get());
|
|
Ref<VisualScriptNode> node = script->get_node(func, 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", default_func, new_id, dupe, script->get_node_position(func, F->get()) + Vector2(20, 20));
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
|
|
}
|
|
|
|
for (List<StringName>::Element *F = funcs.front(); F; F = F->next()) {
|
|
List<VisualScript::SequenceConnection> seqs;
|
|
script->get_sequence_connection_list(F->get(), &seqs);
|
|
for (List<VisualScript::SequenceConnection>::Element *E = seqs.front(); E; E = E->next()) {
|
|
if (to_duplicate.has(E->get().from_node) && to_duplicate.has(E->get().to_node)) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
|
|
}
|
|
}
|
|
|
|
List<VisualScript::DataConnection> data;
|
|
script->get_data_connection_list(F->get(), &data);
|
|
for (List<VisualScript::DataConnection>::Element *E = data.front(); E; E = E->next()) {
|
|
if (to_duplicate.has(E->get().from_node) && to_duplicate.has(E->get().to_node)) {
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", default_func, 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));
|
|
}
|
|
}
|
|
|
|
if (to_select.size()) {
|
|
EditorNode::get_singleton()->push_item(script->get_node(default_func, to_select.front()->get()).ptr());
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_generic_search(String p_base_type, Vector2 pos, bool node_centered) {
|
|
if (node_centered) {
|
|
port_action_pos = graph->get_size() / 2.0f;
|
|
} else {
|
|
port_action_pos = graph->get_viewport()->get_mouse_position() - graph->get_global_position();
|
|
}
|
|
|
|
new_connect_node_select->select_from_visual_script(p_base_type, false, false); // neither connecting nor reset text
|
|
|
|
// ensure that the dialog fits inside the graph
|
|
Size2 bounds = graph->get_global_position() + graph->get_size() - new_connect_node_select->get_size();
|
|
pos.x = pos.x > bounds.x ? bounds.x : pos.x;
|
|
pos.y = pos.y > bounds.y ? bounds.y : pos.y;
|
|
|
|
if (pos != Vector2()) {
|
|
new_connect_node_select->set_position(pos);
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_input(const Ref<InputEvent> &p_event) {
|
|
// GUI input for VS Editor Plugin
|
|
Ref<InputEventMouseButton> key = p_event;
|
|
|
|
if (key.is_valid() && !key->is_pressed()) {
|
|
mouse_up_position = Input::get_singleton()->get_mouse_position();
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_graph_gui_input(const Ref<InputEvent> &p_event) {
|
|
Ref<InputEventMouseButton> key = p_event;
|
|
|
|
if (key.is_valid() && key->is_pressed() && key->get_button_mask() == BUTTON_RIGHT) {
|
|
saved_position = graph->get_local_mouse_position();
|
|
|
|
Point2 gpos = Input::get_singleton()->get_mouse_position();
|
|
_generic_search(script->get_instance_base_type(), gpos);
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_members_gui_input(const Ref<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_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ref<InputEventMouseButton> btn = p_event;
|
|
if (btn.is_valid() && btn->is_doubleclick()) {
|
|
TreeItem *ti = members->get_selected();
|
|
if (ti && ti->get_parent() == members->get_root()->get_children()) { // to check if it's a function
|
|
_center_on_node(ti->get_metadata(0), 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(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);
|
|
}
|
|
|
|
// also fix all function calls
|
|
List<StringName> flst;
|
|
script->get_function_list(&flst);
|
|
for (List<StringName>::Element *E = flst.front(); E; E = E->next()) {
|
|
List<int> lst;
|
|
script->get_node_list(E->get(), &lst);
|
|
for (List<int>::Element *F = lst.front(); F; F = F->next()) {
|
|
Ref<VisualScriptFunctionCall> fncall = script->get_node(E->get(), F->get());
|
|
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 == 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;
|
|
} 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<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 ofs = graph->get_scroll_ofs() + p_point;
|
|
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
|
|
int new_id = _create_new_node_from_name(d["node_type"], ofs, default_func);
|
|
|
|
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_CONTROL);
|
|
#endif
|
|
Vector2 ofs = graph->get_scroll_ofs() + p_point;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
|
|
Ref<VisualScriptNode> vnode;
|
|
if (use_set) {
|
|
Ref<VisualScriptVariableSet> vnodes;
|
|
vnodes.instance();
|
|
vnodes->set_variable(d["variable"]);
|
|
vnode = vnodes;
|
|
} else {
|
|
Ref<VisualScriptVariableGet> vnodeg;
|
|
vnodeg.instance();
|
|
vnodeg->set_variable(d["variable"]);
|
|
vnode = vnodeg;
|
|
}
|
|
|
|
int new_id = script->get_available_id();
|
|
|
|
undo_redo->create_action(TTR("Add Node"));
|
|
undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, vnode, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_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);
|
|
}
|
|
}
|
|
|
|
if (String(d["type"]) == "visual_script_function_drag") {
|
|
Vector2 ofs = graph->get_scroll_ofs() + p_point;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
|
|
Ref<VisualScriptFunctionCall> vnode;
|
|
vnode.instance();
|
|
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", default_func, new_id, vnode, ofs);
|
|
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", default_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);
|
|
}
|
|
}
|
|
|
|
if (String(d["type"]) == "visual_script_signal_drag") {
|
|
Vector2 ofs = graph->get_scroll_ofs() + p_point;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
|
|
Ref<VisualScriptEmitSignal> vnode;
|
|
vnode.instance();
|
|
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", default_func, new_id, vnode, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_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);
|
|
}
|
|
}
|
|
|
|
if (String(d["type"]) == "resource") {
|
|
Vector2 ofs = graph->get_scroll_ofs() + p_point;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
|
|
Ref<VisualScriptPreload> prnode;
|
|
prnode.instance();
|
|
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", default_func, new_id, prnode, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_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);
|
|
}
|
|
}
|
|
|
|
if (String(d["type"]) == "files") {
|
|
Vector2 ofs = graph->get_scroll_ofs() + p_point;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
|
|
Array files = d["files"];
|
|
|
|
List<int> new_ids;
|
|
int new_id = script->get_available_id();
|
|
|
|
if (files.size()) {
|
|
undo_redo->create_action(TTR("Add Preload Node"));
|
|
|
|
for (int i = 0; i < files.size(); i++) {
|
|
Ref<Resource> res = ResourceLoader::load(files[i]);
|
|
if (!res.is_valid()) {
|
|
continue;
|
|
}
|
|
|
|
Ref<VisualScriptPreload> prnode;
|
|
prnode.instance();
|
|
prnode->set_preload(res);
|
|
|
|
undo_redo->add_do_method(script.ptr(), "add_node", default_func, new_id, prnode, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, new_id);
|
|
new_ids.push_back(new_id);
|
|
new_id++;
|
|
ofs += Vector2(20, 20) * EDSCALE;
|
|
}
|
|
|
|
undo_redo->add_do_method(this, "_update_graph");
|
|
undo_redo->add_undo_method(this, "_update_graph");
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
for (List<int>::Element *E = new_ids.front(); E; E = E->next()) {
|
|
Node *node = graph->get_node(itos(E->get()));
|
|
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_CONTROL);
|
|
#endif
|
|
|
|
Array nodes = d["nodes"];
|
|
|
|
Vector2 ofs = graph->get_scroll_ofs() + p_point;
|
|
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
ofs /= EDSCALE;
|
|
|
|
undo_redo->create_action(TTR("Add Node(s) From Tree"));
|
|
int base_id = script->get_available_id();
|
|
|
|
if (nodes.size() > 1) {
|
|
use_node = true;
|
|
}
|
|
|
|
for (int i = 0; i < nodes.size(); i++) {
|
|
NodePath np = nodes[i];
|
|
Node *node = get_node(np);
|
|
if (!node) {
|
|
continue;
|
|
}
|
|
|
|
Ref<VisualScriptNode> n;
|
|
|
|
if (use_node) {
|
|
Ref<VisualScriptSceneNode> scene_node;
|
|
scene_node.instance();
|
|
scene_node->set_node_path(sn->get_path_to(node));
|
|
n = scene_node;
|
|
} else {
|
|
// ! Doesn't work properly
|
|
Ref<VisualScriptFunctionCall> call;
|
|
call.instance();
|
|
call->set_call_mode(VisualScriptFunctionCall::CALL_MODE_NODE_PATH);
|
|
call->set_base_path(sn->get_path_to(node));
|
|
call->set_base_type(node->get_class());
|
|
n = call;
|
|
method_select->select_from_instance(node, "", true, node->get_class());
|
|
selecting_method_id = base_id;
|
|
}
|
|
|
|
undo_redo->add_do_method(script.ptr(), "add_node", default_func, base_id, n, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, base_id);
|
|
|
|
base_id++;
|
|
ofs += Vector2(25, 25);
|
|
}
|
|
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 ofs = graph->get_scroll_ofs() + p_point;
|
|
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
|
|
ofs /= EDSCALE;
|
|
#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_CONTROL);
|
|
#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.instance();
|
|
pset->set_call_mode(VisualScriptPropertySet::CALL_MODE_INSTANCE);
|
|
pset->set_base_type(obj->get_class());
|
|
/*if (use_value) {
|
|
pset->set_use_builtin_value(true);
|
|
pset->set_builtin_value(d["value"]);
|
|
}*/
|
|
vnode = pset;
|
|
} else {
|
|
Ref<VisualScriptPropertyGet> pget;
|
|
pget.instance();
|
|
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", default_func, base_id, vnode, ofs);
|
|
undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]);
|
|
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", default_func, 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.instance();
|
|
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.instance();
|
|
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", default_func, base_id, vnode, ofs);
|
|
undo_redo->add_do_method(vnode.ptr(), "set_property", d["property"]);
|
|
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", default_func, base_id);
|
|
|
|
undo_redo->add_do_method(this, "_update_graph");
|
|
undo_redo->add_undo_method(this, "_update_graph");
|
|
undo_redo->commit_action();
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::_selected_method(const String &p_method, const String &p_type, const bool p_connecting) {
|
|
Ref<VisualScriptFunctionCall> vsfc = script->get_node(default_func, selecting_method_id);
|
|
if (!vsfc.is_valid()) {
|
|
return;
|
|
}
|
|
vsfc->set_function(p_method);
|
|
}
|
|
|
|
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("normal", "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));
|
|
|
|
default_func = script->get_default_func();
|
|
|
|
if (!script->has_function(default_func)) // this is the supposed default function
|
|
{
|
|
script->add_function(default_func);
|
|
script->set_edited(true); //so that if a function was added it's saved
|
|
}
|
|
|
|
_update_graph();
|
|
_update_members();
|
|
}
|
|
|
|
void VisualScriptEditor::enable_editor() {
|
|
}
|
|
|
|
Vector<String> VisualScriptEditor::get_functions() {
|
|
return Vector<String>();
|
|
}
|
|
|
|
void VisualScriptEditor::reload_text() {
|
|
}
|
|
|
|
String VisualScriptEditor::get_name() {
|
|
String name;
|
|
|
|
if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) {
|
|
name = script->get_path().get_file();
|
|
if (is_unsaved()) {
|
|
if (script->get_path().empty()) {
|
|
name = TTR("[unsaved]");
|
|
}
|
|
name += "(*)";
|
|
}
|
|
} else if (script->get_name() != "") {
|
|
name = script->get_name();
|
|
} else {
|
|
name = script->get_class() + "(" + itos(script->get_instance_id()) + ")";
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
Ref<Texture2D> VisualScriptEditor::get_theme_icon() {
|
|
return Control::get_theme_icon("VisualScript", "EditorIcons");
|
|
}
|
|
|
|
bool VisualScriptEditor::is_unsaved() {
|
|
bool unsaved =
|
|
script->is_edited() ||
|
|
script->are_subnodes_edited() ||
|
|
script->get_path().empty(); // In memory.
|
|
return unsaved;
|
|
}
|
|
|
|
Variant VisualScriptEditor::get_edit_state() {
|
|
Dictionary d;
|
|
d["function"] = default_func;
|
|
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;
|
|
if (d.has("function")) {
|
|
selected = default_func;
|
|
}
|
|
|
|
_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(const StringName &p_func, 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_offset() - graph->get_size() * 0.5 + gn->get_size() * 0.5;
|
|
graph->set_scroll_ofs(new_scroll);
|
|
script->set_function_scroll(p_func, 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;
|
|
}
|
|
|
|
List<StringName> functions;
|
|
script->get_function_list(&functions);
|
|
for (List<StringName>::Element *E = functions.front(); E; E = E->next()) {
|
|
if (script->has_node(E->get(), p_line)) {
|
|
_update_graph();
|
|
_update_members();
|
|
|
|
call_deferred("call_deferred", "_center_on_node", E->get(), p_line); //editor might be just created and size might not exist yet
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
void VisualScriptEditor::get_breakpoints(List<int> *p_breakpoints) {
|
|
List<StringName> functions;
|
|
script->get_function_list(&functions);
|
|
for (List<StringName>::Element *E = functions.front(); E; E = E->next()) {
|
|
List<int> nodes;
|
|
script->get_node_list(E->get(), &nodes);
|
|
for (List<int>::Element *F = nodes.front(); F; F = F->next()) {
|
|
Ref<VisualScriptNode> vsn = script->get_node(E->get(), F->get());
|
|
if (vsn->is_breakpoint()) {
|
|
p_breakpoints->push_back(F->get() - 1); //subtract 1 because breakpoints in text start from zero
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::add_callback(const String &p_function, PackedStringArray p_args) {
|
|
if (script->has_function(p_function)) {
|
|
_update_members();
|
|
_update_graph();
|
|
_center_on_node(p_function, script->get_function_node_id(p_function));
|
|
return;
|
|
}
|
|
|
|
Ref<VisualScriptFunction> func;
|
|
func.instance();
|
|
for (int i = 0; i < p_args.size(); i++) {
|
|
String name = p_args[i];
|
|
Variant::Type type = Variant::NIL;
|
|
|
|
if (name.find(":") != -1) {
|
|
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);
|
|
}
|
|
|
|
func->set_name(p_function);
|
|
script->add_function(p_function);
|
|
script->add_node(p_function, script->get_available_id(), func);
|
|
|
|
_update_members();
|
|
_update_graph();
|
|
|
|
_center_on_node(p_function, 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
|
|
}
|
|
}
|
|
|
|
void VisualScriptEditor::set_tooltip_request_func(String p_method, Object *p_obj) {
|
|
}
|
|
|
|
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 == String());
|
|
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(const StringName &p_func, int p_id, const Vector2 &p_to) {
|
|
if (!script->has_function(p_func)) {
|
|
return;
|
|
}
|
|
|
|
Node *node = graph->get_node(itos(p_id));
|
|
|
|
if (Object::cast_to<GraphNode>(node)) {
|
|
Object::cast_to<GraphNode>(node)->set_offset(p_to);
|
|
}
|
|
|
|
script->set_node_position(p_func, p_id, p_to / EDSCALE);
|
|
}
|
|
|
|
StringName VisualScriptEditor::_get_function_of_node(int p_id) const {
|
|
List<StringName> funcs;
|
|
script->get_function_list(&funcs);
|
|
for (List<StringName>::Element *E = funcs.front(); E; E = E->next()) {
|
|
if (script->has_node(E->get(), p_id)) {
|
|
return E->get();
|
|
}
|
|
}
|
|
|
|
return ""; // this is passed to avoid crash and is tested against later
|
|
}
|
|
|
|
void VisualScriptEditor::_node_moved(Vector2 p_from, Vector2 p_to, int p_id) {
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
undo_redo->add_do_method(this, "_move_node", func, p_id, p_to);
|
|
undo_redo->add_undo_method(this, "_move_node", func, p_id, p_from);
|
|
}
|
|
|
|
void VisualScriptEditor::_remove_node(int p_id) {
|
|
undo_redo->create_action(TTR("Remove VisualScript Node"));
|
|
|
|
StringName func = _get_function_of_node(p_id);
|
|
|
|
undo_redo->add_do_method(script.ptr(), "remove_node", func, p_id);
|
|
undo_redo->add_undo_method(script.ptr(), "add_node", func, p_id, script->get_node(func, p_id), script->get_node_position(func, p_id));
|
|
|
|
List<VisualScript::SequenceConnection> sequence_conns;
|
|
script->get_sequence_connection_list(func, &sequence_conns);
|
|
|
|
for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
|
|
if (E->get().from_node == p_id || E->get().to_node == p_id) {
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, E->get().from_node, E->get().from_output, E->get().to_node);
|
|
}
|
|
}
|
|
|
|
List<VisualScript::DataConnection> data_conns;
|
|
script->get_data_connection_list(func, &data_conns);
|
|
|
|
for (List<VisualScript::DataConnection>::Element *E = data_conns.front(); E; E = E->next()) {
|
|
if (E->get().from_node == p_id || E->get().to_node == p_id) {
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", 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::_node_ports_changed(const String &p_func, int p_id) {
|
|
_update_graph(p_id);
|
|
}
|
|
|
|
bool VisualScriptEditor::node_has_sequence_connections(const StringName &p_func, int p_id) {
|
|
List<VisualScript::SequenceConnection> sequence_conns;
|
|
script->get_sequence_connection_list(p_func, &sequence_conns);
|
|
|
|
for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
|
|
int from = E->get().from_node;
|
|
int to = E->get().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) {
|
|
StringName from_func = _get_function_of_node(p_from.to_int());
|
|
|
|
Ref<VisualScriptNode> from_node = script->get_node(from_func, 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
|
|
}
|
|
|
|
StringName to_func = _get_function_of_node(p_to.to_int());
|
|
|
|
Ref<VisualScriptNode> to_node = script->get_node(to_func, 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);
|
|
|
|
// 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_func == to_func) {
|
|
func = to_func;
|
|
} else if (from_seq) {
|
|
// this is a sequence connection
|
|
_move_nodes_with_rescan(to_func, from_func, p_to.to_int()); // this function moves the nodes from func1 to func2
|
|
func = from_func;
|
|
} else {
|
|
if (node_has_sequence_connections(to_func, p_to.to_int())) {
|
|
if (node_has_sequence_connections(from_func, p_from.to_int())) {
|
|
ERR_PRINT("Trying to connect between different sequence node trees");
|
|
return;
|
|
} else {
|
|
_move_nodes_with_rescan(from_func, to_func, p_from.to_int());
|
|
func = to_func;
|
|
}
|
|
} else if (node_has_sequence_connections(from_func, p_from.to_int())) {
|
|
if (from_func == default_func) {
|
|
_move_nodes_with_rescan(from_func, to_func, p_from.to_int());
|
|
func = to_func;
|
|
} else {
|
|
_move_nodes_with_rescan(to_func, from_func, p_to.to_int());
|
|
func = from_func;
|
|
}
|
|
} else {
|
|
if (to_func == default_func) {
|
|
_move_nodes_with_rescan(to_func, from_func, p_to.to_int());
|
|
func = from_func;
|
|
} else {
|
|
_move_nodes_with_rescan(from_func, to_func, p_from.to_int());
|
|
func = to_func;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (from_seq) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", func, 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", func, p_from.to_int(), from_port, p_to.to_int());
|
|
} else {
|
|
bool converted = false;
|
|
int conv_node = -1;
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
Variant::Type to_type = to_node->get_input_value_port_info(to_port).type;
|
|
Variant::Type from_type = from_node->get_output_value_port_info(from_port).type;
|
|
|
|
if (to_type != Variant::NIL && from_type != Variant::NIL && to_type != from_type) {
|
|
// add a constructor node between the ports
|
|
bool exceptions = false; // true if there are any exceptions
|
|
exceptions = exceptions || (to_type == Variant::INT && from_type == Variant::FLOAT);
|
|
exceptions = exceptions || (to_type == Variant::FLOAT && from_type == Variant::INT);
|
|
if (Variant::can_convert(from_type, to_type) && !exceptions) {
|
|
MethodInfo mi;
|
|
mi.name = Variant::get_type_name(to_type);
|
|
PropertyInfo pi;
|
|
pi.name = "from";
|
|
pi.type = from_type;
|
|
mi.arguments.push_back(pi);
|
|
mi.return_val.type = to_type;
|
|
// we know that this is allowed so create a new constructor node
|
|
Ref<VisualScriptConstructor> constructor;
|
|
constructor.instance();
|
|
constructor->set_constructor_type(to_type);
|
|
constructor->set_constructor(mi);
|
|
// add the new constructor node
|
|
|
|
GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(p_from));
|
|
GraphNode *gn2 = Object::cast_to<GraphNode>(graph->get_node(p_to));
|
|
if (gn && gn2) {
|
|
Vector2 from_node_size = gn->get_rect().get_size();
|
|
Vector2 to_node_size = gn2->get_rect().get_size();
|
|
Vector2 to_node_pos = script->get_node_position(func, p_to.to_int());
|
|
Vector2 from_node_pos = script->get_node_position(func, p_from.to_int());
|
|
Vector2 new_to_node_pos = from_node_pos;
|
|
Vector2 constructor_pos;
|
|
if ((to_node_pos.x - from_node_pos.x) < 0) {
|
|
// to is behind from node
|
|
if (to_node_pos.x > (from_node_pos.x - to_node_size.x - 240)) {
|
|
new_to_node_pos.x = from_node_pos.x - to_node_size.x - 240; // approx size of constructor node + padding
|
|
} else {
|
|
new_to_node_pos.x = to_node_pos.x;
|
|
}
|
|
new_to_node_pos.y = to_node_pos.y;
|
|
constructor_pos.x = from_node_pos.x - 210;
|
|
constructor_pos.y = to_node_pos.y;
|
|
} else {
|
|
// to is ahead of from node
|
|
if (to_node_pos.x < (from_node_size.x + from_node_pos.x + 240)) {
|
|
new_to_node_pos.x = from_node_size.x + from_node_pos.x + 240; // approx size of constructor node + padding
|
|
} else {
|
|
new_to_node_pos.x = to_node_pos.x;
|
|
}
|
|
new_to_node_pos.y = to_node_pos.y;
|
|
constructor_pos.x = from_node_size.x + from_node_pos.x + 10;
|
|
constructor_pos.y = to_node_pos.y;
|
|
}
|
|
undo_redo->add_do_method(this, "_move_node", func, p_to.to_int(), new_to_node_pos);
|
|
undo_redo->add_undo_method(this, "_move_node", func, p_to.to_int(), to_node_pos);
|
|
conv_node = script->get_available_id();
|
|
undo_redo->add_do_method(script.ptr(), "add_node", func, conv_node, constructor, _get_available_pos(false, constructor_pos));
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", func, conv_node);
|
|
converted = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// disconnect current, and connect the new one
|
|
if (script->is_input_value_port_connected(func, 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(func, p_to.to_int(), to_port, &conn_from, &conn_port);
|
|
undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_to.to_int(), to_port);
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", func, conn_from, conn_port, data_disconnect_node, data_disconnect_port);
|
|
undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, data_disconnect_node, data_disconnect_port);
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", func, 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(func, p_to.to_int(), to_port, &conn_from, &conn_port);
|
|
undo_redo->add_do_method(script.ptr(), "data_disconnect", func, conn_from, conn_port, p_to.to_int(), to_port);
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", func, conn_from, conn_port, p_to.to_int(), to_port);
|
|
}
|
|
}
|
|
if (!converted) {
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
|
|
undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
|
|
} else {
|
|
// this is noice
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", func, p_from.to_int(), from_port, conv_node, 0);
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", func, conv_node, 0, p_to.to_int(), to_port);
|
|
// I don't think this is needed but gonna leave it here for now... until I need to finalise it all
|
|
undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, p_from.to_int(), from_port, conv_node, 0);
|
|
undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, conv_node, 0, p_to.to_int(), to_port);
|
|
}
|
|
//update nodes in graph
|
|
if (!converted) {
|
|
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->add_do_method(this, "_update_graph_connections");
|
|
undo_redo->add_undo_method(this, "_update_graph_connections");
|
|
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void VisualScriptEditor::_graph_disconnected(const String &p_from, int p_from_slot, const String &p_to, int p_to_slot) {
|
|
StringName func = _get_function_of_node(p_from.to_int());
|
|
ERR_FAIL_COND(func != _get_function_of_node(p_to.to_int()));
|
|
|
|
Ref<VisualScriptNode> from_node = script->get_node(func, 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(func, 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", func, p_from.to_int(), from_port, p_to.to_int());
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", func, p_from.to_int(), from_port, p_to.to_int());
|
|
} 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", func, p_from.to_int(), from_port, p_to.to_int(), to_port);
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", func, 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->add_do_method(this, "_update_graph_connections");
|
|
undo_redo->add_undo_method(this, "_update_graph_connections");
|
|
|
|
undo_redo->commit_action();
|
|
}
|
|
|
|
void VisualScriptEditor::_move_nodes_with_rescan(const StringName &p_func_from, const StringName &p_func_to, int p_id) {
|
|
Set<int> nodes_to_move;
|
|
HashMap<int, Map<int, int>> seqconns_to_move; // from => List(outp, to)
|
|
HashMap<int, Map<int, Pair<int, int>>> dataconns_to_move; // to => List(inp_p => from, outp)
|
|
|
|
nodes_to_move.insert(p_id);
|
|
Set<int> sequence_connections;
|
|
{
|
|
List<VisualScript::SequenceConnection> sequence_conns;
|
|
script->get_sequence_connection_list(p_func_from, &sequence_conns);
|
|
|
|
HashMap<int, Map<int, int>> seqcons; // from => List(out_p => to)
|
|
|
|
for (List<VisualScript::SequenceConnection>::Element *E = sequence_conns.front(); E; E = E->next()) {
|
|
int from = E->get().from_node;
|
|
int to = E->get().to_node;
|
|
int out_p = E->get().from_output;
|
|
if (!seqcons.has(from)) {
|
|
seqcons.set(from, Map<int, int>());
|
|
}
|
|
seqcons[from].insert(out_p, to);
|
|
sequence_connections.insert(to);
|
|
sequence_connections.insert(from);
|
|
}
|
|
|
|
int conn = p_id;
|
|
List<int> stack;
|
|
HashMap<int, Set<int>> seen; // from, outp
|
|
while (seqcons.has(conn)) {
|
|
for (auto E = seqcons[conn].front(); E; E = E->next()) {
|
|
if (seen.has(conn) && seen[conn].has(E->key())) {
|
|
if (!E->next()) {
|
|
if (stack.size() > 0) {
|
|
conn = stack.back()->get();
|
|
stack.pop_back();
|
|
break;
|
|
}
|
|
conn = -101;
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
if (!seen.has(conn)) {
|
|
seen.set(conn, Set<int>());
|
|
}
|
|
seen[conn].insert(E->key());
|
|
stack.push_back(conn);
|
|
if (!seqconns_to_move.has(conn)) {
|
|
seqconns_to_move.set(conn, Map<int, int>());
|
|
}
|
|
seqconns_to_move[conn].insert(E->key(), E->get());
|
|
conn = E->get();
|
|
nodes_to_move.insert(conn);
|
|
break;
|
|
}
|
|
if (!seqcons.has(conn) && stack.size() > 0) {
|
|
conn = stack.back()->get();
|
|
stack.pop_back();
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
List<VisualScript::DataConnection> data_connections;
|
|
script->get_data_connection_list(p_func_from, &data_connections);
|
|
int func_from_node_id = script->get_function_node_id(p_func_from);
|
|
|
|
HashMap<int, Map<int, Pair<int, int>>> connections;
|
|
|
|
for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
|
|
int from = E->get().from_node;
|
|
int to = E->get().to_node;
|
|
int out_p = E->get().from_port;
|
|
int in_p = E->get().to_port;
|
|
|
|
// skip if the from_node is a function node
|
|
if (from == func_from_node_id) {
|
|
continue;
|
|
}
|
|
|
|
if (!connections.has(to)) {
|
|
connections.set(to, Map<int, Pair<int, int>>());
|
|
}
|
|
connections[to].insert(in_p, Pair<int, int>(from, out_p));
|
|
}
|
|
|
|
// go through the HashMap and do all sorts of crazy ass stuff now...
|
|
Set<int> nodes_to_be_added;
|
|
for (Set<int>::Element *F = nodes_to_move.front(); F; F = F->next()) {
|
|
HashMap<int, Set<int>> seen;
|
|
List<int> stack;
|
|
int id = F->get();
|
|
while (connections.has(id)) {
|
|
for (auto E = connections[id].front(); E; E = E->next()) {
|
|
if (seen.has(id) && seen[id].has(E->key())) {
|
|
if (!E->next()) {
|
|
if (stack.size() > 0) {
|
|
id = stack.back()->get();
|
|
stack.pop_back();
|
|
break;
|
|
}
|
|
id = -11; // I assume ids can't be negative should confirm it...
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (sequence_connections.has(E->get().first)) {
|
|
if (!nodes_to_move.has(E->get().first)) {
|
|
if (stack.size() > 0) {
|
|
id = stack.back()->get();
|
|
stack.pop_back();
|
|
break;
|
|
}
|
|
id = -11; // I assume ids can't be negative should confirm it...
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!seen.has(id)) {
|
|
seen.set(id, Set<int>());
|
|
}
|
|
seen[id].insert(E->key());
|
|
stack.push_back(id);
|
|
if (!dataconns_to_move.has(id)) {
|
|
dataconns_to_move.set(id, Map<int, Pair<int, int>>());
|
|
}
|
|
dataconns_to_move[id].insert(E->key(), Pair<int, int>(E->get().first, E->get().second));
|
|
id = E->get().first;
|
|
nodes_to_be_added.insert(id);
|
|
break;
|
|
}
|
|
if (!connections.has(id) && stack.size() > 0) {
|
|
id = stack.back()->get();
|
|
stack.pop_back();
|
|
}
|
|
}
|
|
}
|
|
for (Set<int>::Element *E = nodes_to_be_added.front(); E; E = E->next()) {
|
|
nodes_to_move.insert(E->get());
|
|
}
|
|
}
|
|
|
|
// * this is primarily for the sake of the having proper undo
|
|
List<VisualScript::SequenceConnection> seqext;
|
|
List<VisualScript::DataConnection> dataext;
|
|
|
|
List<VisualScript::SequenceConnection> seq_connections;
|
|
script->get_sequence_connection_list(p_func_from, &seq_connections);
|
|
|
|
for (List<VisualScript::SequenceConnection>::Element *E = seq_connections.front(); E; E = E->next()) {
|
|
if (!nodes_to_move.has(E->get().from_node) && nodes_to_move.has(E->get().to_node)) {
|
|
seqext.push_back(E->get());
|
|
} else if (nodes_to_move.has(E->get().from_node) && !nodes_to_move.has(E->get().to_node)) {
|
|
seqext.push_back(E->get());
|
|
}
|
|
}
|
|
|
|
List<VisualScript::DataConnection> data_connections;
|
|
script->get_data_connection_list(p_func_from, &data_connections);
|
|
|
|
for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
|
|
if (!nodes_to_move.has(E->get().from_node) && nodes_to_move.has(E->get().to_node)) {
|
|
dataext.push_back(E->get());
|
|
} else if (nodes_to_move.has(E->get().from_node) && !nodes_to_move.has(E->get().to_node)) {
|
|
dataext.push_back(E->get());
|
|
}
|
|
}
|
|
|
|
// undo_redo->create_action("Rescan Functions");
|
|
|
|
for (Set<int>::Element *E = nodes_to_move.front(); E; E = E->next()) {
|
|
int id = E->get();
|
|
|
|
undo_redo->add_do_method(script.ptr(), "remove_node", p_func_from, id);
|
|
undo_redo->add_do_method(script.ptr(), "add_node", p_func_to, id, script->get_node(p_func_from, id), script->get_node_position(p_func_from, id));
|
|
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", p_func_to, id);
|
|
undo_redo->add_undo_method(script.ptr(), "add_node", p_func_from, id, script->get_node(p_func_from, id), script->get_node_position(p_func_from, id));
|
|
}
|
|
|
|
List<int> skeys;
|
|
seqconns_to_move.get_key_list(&skeys);
|
|
for (List<int>::Element *E = skeys.front(); E; E = E->next()) {
|
|
int from_node = E->get();
|
|
for (Map<int, int>::Element *F = seqconns_to_move[from_node].front(); F; F = F->next()) {
|
|
int from_port = F->key();
|
|
int to_node = F->get();
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", p_func_to, from_node, from_port, to_node);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_func_from, from_node, from_port, to_node);
|
|
}
|
|
}
|
|
|
|
List<int> keys;
|
|
dataconns_to_move.get_key_list(&keys);
|
|
for (List<int>::Element *E = keys.front(); E; E = E->next()) {
|
|
int to_node = E->get(); // to_node
|
|
for (Map<int, Pair<int, int>>::Element *F = dataconns_to_move[E->get()].front(); F; F = F->next()) {
|
|
int inp_p = F->key();
|
|
Pair<int, int> fro = F->get();
|
|
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", p_func_to, fro.first, fro.second, to_node, inp_p);
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", p_func_from, fro.first, fro.second, to_node, inp_p);
|
|
}
|
|
}
|
|
|
|
// this to have proper undo operations
|
|
for (List<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) {
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", p_func_from, E->get().from_node, E->get().from_output, E->get().to_node);
|
|
}
|
|
for (List<VisualScript::DataConnection>::Element *E = dataext.front(); E; E = E->next()) {
|
|
undo_redo->add_undo_method(script.ptr(), "data_connect", p_func_from, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
|
|
}
|
|
// this doesn't need do methods as they are handled by the subsequent do calls implicitly
|
|
|
|
undo_redo->add_do_method(this, "_update_graph");
|
|
undo_redo->add_undo_method(this, "_update_graph");
|
|
|
|
// 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;
|
|
}
|
|
|
|
StringName func = _get_function_of_node(p_from.to_int());
|
|
|
|
Ref<VisualScriptNode> vsn = script->get_node(func, p_from.to_int());
|
|
if (!vsn.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
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, func);
|
|
} 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, func);
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
StringName func = _get_function_of_node(p_port_action_node);
|
|
|
|
Ref<VisualScriptNode> node = script->get_node(func, p_port_action_node);
|
|
|
|
if (!node.is_valid()) {
|
|
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(func, 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, const StringName &func) {
|
|
Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
ofs /= EDSCALE;
|
|
|
|
Set<int> vn;
|
|
|
|
switch (p_option) {
|
|
case CREATE_CALL_SET_GET: {
|
|
Ref<VisualScriptFunctionCall> n;
|
|
n.instance();
|
|
|
|
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;
|
|
if (script->get_node(func, port_action_node)->get_output_value_port_count() > 0) {
|
|
type_string = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
|
|
}
|
|
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);
|
|
} else {
|
|
new_connect_node_select->select_from_base_type(n->get_base_type());
|
|
}
|
|
} else if (tg.type == Variant::NIL) {
|
|
new_connect_node_select->select_from_base_type("");
|
|
} 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(func, port_action_node)->get_output_value_port_count() > 0) {
|
|
property_info = script->get_node(func, 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 != String()) {
|
|
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;
|
|
}
|
|
StringName func = _get_function_of_node(port_action_node);
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", func, port_action_node, port, new_id, 0);
|
|
undo_redo->add_undo_method(script.ptr(), "data_disconnect", func, 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) {
|
|
Vector2 ofs = graph->get_scroll_ofs() + port_action_pos;
|
|
if (graph->is_using_snap()) {
|
|
int snap = graph->get_snap();
|
|
ofs = ofs.snapped(Vector2(snap, snap));
|
|
}
|
|
ofs /= EDSCALE;
|
|
ofs /= graph->get_zoom();
|
|
|
|
Set<int> vn;
|
|
|
|
bool port_node_exists = true;
|
|
|
|
StringName func = _get_function_of_node(port_action_node);
|
|
if (func == StringName()) {
|
|
func = default_func;
|
|
port_node_exists = false;
|
|
}
|
|
|
|
if (p_category == "visualscript") {
|
|
Ref<VisualScriptNode> vnode_new = VisualScriptLanguage::singleton->create_node_from_name(p_text);
|
|
Ref<VisualScriptNode> vnode_old;
|
|
if (port_node_exists) {
|
|
vnode_old = script->get_node(func, port_action_node);
|
|
}
|
|
int new_id = script->get_available_id();
|
|
|
|
if (Object::cast_to<VisualScriptOperator>(vnode_new.ptr()) && vnode_old.is_valid()) {
|
|
Variant::Type type = vnode_old->get_output_value_port_info(port_action_output).type;
|
|
Object::cast_to<VisualScriptOperator>(vnode_new.ptr())->set_typed(type);
|
|
}
|
|
|
|
if (Object::cast_to<VisualScriptTypeCast>(vnode_new.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>(vnode_new.ptr())->set_base_type(hint_name);
|
|
} else if (type == Variant::NIL) {
|
|
Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type("");
|
|
} else {
|
|
Object::cast_to<VisualScriptTypeCast>(vnode_new.ptr())->set_base_type(Variant::get_type_name(type));
|
|
}
|
|
}
|
|
|
|
undo_redo->create_action(TTR("Add Node"));
|
|
undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode_new, ofs);
|
|
if (vnode_old.is_valid() && p_connecting) {
|
|
connect_seq(vnode_old, vnode_new, new_id);
|
|
connect_data(vnode_old, vnode_new, new_id);
|
|
}
|
|
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", func, new_id);
|
|
undo_redo->add_do_method(this, "_update_graph");
|
|
undo_redo->add_undo_method(this, "_update_graph");
|
|
undo_redo->commit_action();
|
|
return;
|
|
}
|
|
|
|
Ref<VisualScriptNode> vnode;
|
|
Ref<VisualScriptPropertySet> script_prop_set;
|
|
|
|
if (p_category == String("method")) {
|
|
Ref<VisualScriptFunctionCall> n;
|
|
n.instance();
|
|
vnode = n;
|
|
} else if (p_category == String("set")) {
|
|
Ref<VisualScriptPropertySet> n;
|
|
n.instance();
|
|
vnode = n;
|
|
script_prop_set = n;
|
|
} else if (p_category == String("get")) {
|
|
Ref<VisualScriptPropertyGet> n;
|
|
n.instance();
|
|
n->set_property(p_text);
|
|
vnode = n;
|
|
}
|
|
|
|
if (p_category == String("action")) {
|
|
if (p_text == "VisualScriptCondition") {
|
|
Ref<VisualScriptCondition> n;
|
|
n.instance();
|
|
vnode = n;
|
|
}
|
|
if (p_text == "VisualScriptSwitch") {
|
|
Ref<VisualScriptSwitch> n;
|
|
n.instance();
|
|
vnode = n;
|
|
} else if (p_text == "VisualScriptSequence") {
|
|
Ref<VisualScriptSequence> n;
|
|
n.instance();
|
|
vnode = n;
|
|
} else if (p_text == "VisualScriptIterator") {
|
|
Ref<VisualScriptIterator> n;
|
|
n.instance();
|
|
vnode = n;
|
|
} else if (p_text == "VisualScriptWhile") {
|
|
Ref<VisualScriptWhile> n;
|
|
n.instance();
|
|
vnode = n;
|
|
} else if (p_text == "VisualScriptReturn") {
|
|
Ref<VisualScriptReturn> n;
|
|
n.instance();
|
|
vnode = n;
|
|
}
|
|
}
|
|
|
|
int new_id = script->get_available_id();
|
|
undo_redo->create_action(TTR("Add Node"));
|
|
undo_redo->add_do_method(script.ptr(), "add_node", func, new_id, vnode, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", func, 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();
|
|
|
|
if (script_prop_set.is_valid()) {
|
|
script_prop_set->set_property(p_text);
|
|
}
|
|
|
|
port_action_new_node = new_id;
|
|
|
|
Ref<VisualScriptNode> vsn = script->get_node(func, port_action_new_node);
|
|
|
|
if (Object::cast_to<VisualScriptFunctionCall>(vsn.ptr())) {
|
|
Ref<VisualScriptFunctionCall> vsfc = vsn;
|
|
vsfc->set_function(p_text);
|
|
|
|
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(func, port_action_node).is_valid()) {
|
|
PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint;
|
|
String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
|
|
|
|
if (base_type != String() && hint == PROPERTY_HINT_TYPE_STRING) {
|
|
vsfc->set_base_type(base_type);
|
|
}
|
|
if (p_text == "call" || p_text == "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>(vsn.ptr())) {
|
|
Ref<VisualScriptPropertySet> vsp = vsn;
|
|
|
|
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(func, port_action_node).is_valid()) {
|
|
PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint;
|
|
String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
|
|
|
|
if (base_type != String() && 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>(vsn.ptr())) {
|
|
Ref<VisualScriptPropertyGet> vsp = vsn;
|
|
|
|
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(func, port_action_node).is_valid()) {
|
|
PropertyHint hint = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint;
|
|
String base_type = script->get_node(func, port_action_node)->get_output_value_port_info(port_action_output).hint_string;
|
|
if (base_type != String() && 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 (port_node_exists) {
|
|
Ref<VisualScriptNode> vnode_old = script->get_node(func, port_action_node);
|
|
if (vnode_old.is_valid() && p_connecting) {
|
|
connect_seq(vnode_old, vnode, port_action_new_node);
|
|
connect_data(vnode_old, vnode, port_action_new_node);
|
|
}
|
|
}
|
|
_update_graph(port_action_new_node);
|
|
if (port_node_exists) {
|
|
_update_graph_connections();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
StringName func = _get_function_of_node(port_action_node);
|
|
|
|
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(func, port_action_node).has(pass_port)) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, pass_port, new_id);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, 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(func, port_action_node).has(return_port)) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, return_port, new_id);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, 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(func, port_action_node).has(port_action_output)) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, port_action_output, new_id);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, port_action_node, port_action_output, new_id);
|
|
break;
|
|
} else if (!script->get_output_sequence_ports_connected(func, port_action_node).has(port)) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", func, port_action_node, port, new_id);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", func, 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;
|
|
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 (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) {
|
|
if (E->get().name == name) {
|
|
minfo = E->get();
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
ERR_FAIL_COND(!found);
|
|
}
|
|
|
|
selected = name;
|
|
Ref<VisualScriptFunction> 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);
|
|
|
|
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 ofs = _get_available_pos();
|
|
|
|
undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id(), func_node, ofs);
|
|
if (minfo.return_val.type != Variant::NIL || minfo.return_val.usage & PROPERTY_USAGE_NIL_IS_VARIANT) {
|
|
Ref<VisualScriptReturn> ret_node;
|
|
ret_node.instance();
|
|
ret_node->set_return_type(minfo.return_val.type);
|
|
ret_node->set_enable_return_value(true);
|
|
ret_node->set_name(name);
|
|
undo_redo->add_do_method(script.ptr(), "add_node", name, script->get_available_id() + 1, ret_node, _get_available_pos(false, ofs + 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::_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, const StringName &p_func) {
|
|
StringName func = default_func;
|
|
if (p_func != StringName()) {
|
|
func = p_func;
|
|
}
|
|
|
|
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", func, new_id, vnode, p_point);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", func, 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(_get_function_of_node(editing_id), 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(_get_function_of_node(p_id), 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;
|
|
const Variant *existingp = &existing;
|
|
existing = Variant::construct(pinfo.type, &existingp, 1, ce, false);
|
|
}
|
|
|
|
default_value_edit->set_position(Object::cast_to<Control>(p_button)->get_global_position() + Vector2(0, Object::cast_to<Control>(p_button)->get_size().y));
|
|
default_value_edit->set_size(Size2(1, 1));
|
|
|
|
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::_notification(int p_what) {
|
|
switch (p_what) {
|
|
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;
|
|
}
|
|
|
|
edit_variable_edit->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
|
|
edit_signal_edit->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
|
|
func_input_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree"));
|
|
|
|
Ref<Theme> tm = EditorNode::get_singleton()->get_theme_base()->get_theme();
|
|
|
|
bool dark_theme = tm->get_constant("dark_theme", "Editor");
|
|
|
|
List<Pair<String, Color>> colors;
|
|
if (dark_theme) {
|
|
colors.push_back(Pair<String, Color>("flow_control", Color(0.96, 0.96, 0.96)));
|
|
colors.push_back(Pair<String, Color>("functions", Color(0.96, 0.52, 0.51)));
|
|
colors.push_back(Pair<String, Color>("data", Color(0.5, 0.96, 0.81)));
|
|
colors.push_back(Pair<String, Color>("operators", Color(0.67, 0.59, 0.87)));
|
|
colors.push_back(Pair<String, Color>("custom", Color(0.5, 0.73, 0.96)));
|
|
colors.push_back(Pair<String, Color>("constants", Color(0.96, 0.5, 0.69)));
|
|
} else {
|
|
colors.push_back(Pair<String, Color>("flow_control", Color(0.26, 0.26, 0.26)));
|
|
colors.push_back(Pair<String, Color>("functions", Color(0.95, 0.4, 0.38)));
|
|
colors.push_back(Pair<String, Color>("data", Color(0.07, 0.73, 0.51)));
|
|
colors.push_back(Pair<String, Color>("operators", Color(0.51, 0.4, 0.82)));
|
|
colors.push_back(Pair<String, Color>("custom", Color(0.31, 0.63, 0.95)));
|
|
colors.push_back(Pair<String, Color>("constants", Color(0.94, 0.18, 0.49)));
|
|
}
|
|
|
|
for (List<Pair<String, Color>>::Element *E = colors.front(); E; E = E->next()) {
|
|
Ref<StyleBoxFlat> sb = tm->get_stylebox("frame", "GraphNode");
|
|
if (!sb.is_null()) {
|
|
Ref<StyleBoxFlat> frame_style = sb->duplicate();
|
|
Color c = sb->get_border_color();
|
|
Color cn = E->get().second;
|
|
cn.a = c.a;
|
|
frame_style->set_border_color(cn);
|
|
node_styles[E->get().first] = frame_style;
|
|
}
|
|
}
|
|
|
|
if (is_visible_in_tree() && script.is_valid()) {
|
|
_update_members();
|
|
_update_graph();
|
|
}
|
|
} break;
|
|
case NOTIFICATION_VISIBILITY_CHANGED: {
|
|
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;
|
|
|
|
// Just use the default func for all the properties that need to be handled for drawing rather than adding to the Visual Script Class
|
|
if (script->has_function(default_func)) {
|
|
script->set_function_scroll(default_func, 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;
|
|
}
|
|
|
|
StringName func = _get_function_of_node(p_node);
|
|
|
|
Ref<VisualScriptComment> vsc = script->get_node(func, 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;
|
|
}
|
|
|
|
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", p_new_size / EDSCALE);
|
|
undo_redo->add_undo_method(vsc.ptr(), "set_size", vsc->get_size());
|
|
undo_redo->commit_action();
|
|
|
|
gn->set_custom_minimum_size(p_new_size);
|
|
gn->set_size(Size2(1, 1));
|
|
graph->set_block_minimum_size_adjust(false);
|
|
updating_graph = false;
|
|
}
|
|
|
|
void VisualScriptEditor::_menu_option(int p_what) {
|
|
switch (p_what) {
|
|
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();
|
|
StringName func = _get_function_of_node(id);
|
|
Ref<VisualScriptNode> vsn = script->get_node(func, id);
|
|
if (vsn.is_valid()) {
|
|
vsn->set_breakpoint(!vsn->is_breakpoint());
|
|
reselect.push_back(gn->get_name());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
_update_graph();
|
|
|
|
for (List<String>::Element *E = reselect.front(); E; E = E->next()) {
|
|
GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(E->get()));
|
|
gn->set_selected(true);
|
|
}
|
|
|
|
} break;
|
|
case EDIT_FIND_NODE_TYPE: {
|
|
_generic_search(script->get_instance_base_type());
|
|
} break;
|
|
case EDIT_COPY_NODES:
|
|
case EDIT_CUT_NODES: {
|
|
if (!script->has_function(default_func)) {
|
|
break;
|
|
}
|
|
|
|
clipboard->nodes.clear();
|
|
clipboard->data_connections.clear();
|
|
clipboard->sequence_connections.clear();
|
|
|
|
Set<String> funcs;
|
|
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();
|
|
StringName func = _get_function_of_node(id);
|
|
Ref<VisualScriptNode> node = script->get_node(func, 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(func, id);
|
|
funcs.insert(String(func));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (clipboard->nodes.empty()) {
|
|
break;
|
|
}
|
|
|
|
for (Set<String>::Element *F = funcs.front(); F; F = F->next()) {
|
|
List<VisualScript::SequenceConnection> sequence_connections;
|
|
|
|
script->get_sequence_connection_list(F->get(), &sequence_connections);
|
|
|
|
for (List<VisualScript::SequenceConnection>::Element *E = sequence_connections.front(); E; E = E->next()) {
|
|
if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) {
|
|
clipboard->sequence_connections.insert(E->get());
|
|
}
|
|
}
|
|
|
|
List<VisualScript::DataConnection> data_connections;
|
|
|
|
script->get_data_connection_list(F->get(), &data_connections);
|
|
|
|
for (List<VisualScript::DataConnection>::Element *E = data_connections.front(); E; E = E->next()) {
|
|
if (clipboard->nodes.has(E->get().from_node) && clipboard->nodes.has(E->get().to_node)) {
|
|
clipboard->data_connections.insert(E->get());
|
|
}
|
|
}
|
|
}
|
|
if (p_what == EDIT_CUT_NODES) {
|
|
_on_nodes_delete(); // oh yeah, also delete on cut
|
|
}
|
|
|
|
} break;
|
|
case EDIT_PASTE_NODES: {
|
|
if (!script->has_function(default_func)) {
|
|
break;
|
|
}
|
|
|
|
if (clipboard->nodes.empty()) {
|
|
EditorNode::get_singleton()->show_warning(TTR("Clipboard is empty!"));
|
|
break;
|
|
}
|
|
|
|
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<StringName> functions;
|
|
script->get_function_list(&functions);
|
|
for (List<StringName>::Element *F = functions.front(); F; F = F->next()) {
|
|
List<int> nodes;
|
|
script->get_node_list(F->get(), &nodes);
|
|
for (List<int>::Element *E = nodes.front(); E; E = E->next()) {
|
|
Vector2 pos = script->get_node_position(F->get(), E->get()).snapped(Vector2(2, 2));
|
|
existing_positions.insert(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Map<int, Ref<VisualScriptNode>>::Element *E = clipboard->nodes.front(); E; E = E->next()) {
|
|
Ref<VisualScriptNode> node = E->get()->duplicate();
|
|
|
|
int new_id = idc++;
|
|
to_select.insert(new_id);
|
|
|
|
remap[E->key()] = new_id;
|
|
|
|
Vector2 paste_pos = clipboard->nodes_positions[E->key()];
|
|
|
|
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", default_func, new_id, node, paste_pos);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", default_func, 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", default_func, remap[E->get().from_node], E->get().from_output, remap[E->get().to_node]);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_disconnect", default_func, 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", default_func, 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", default_func, 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));
|
|
}
|
|
}
|
|
} break;
|
|
case EDIT_CREATE_FUNCTION: {
|
|
StringName 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();
|
|
StringName func = _get_function_of_node(id);
|
|
Ref<VisualScriptNode> node = script->get_node(func, 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()) {
|
|
if (func != function && function != StringName("")) {
|
|
EditorNode::get_singleton()->show_warning(TTR("Can't create function of nodes from nodes of multiple functions."));
|
|
return;
|
|
}
|
|
nodes.insert(id, node);
|
|
selections.insert(id);
|
|
function = func;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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(function, 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(function, &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 (Map<int, Ref<VisualScriptNode>>::Element *E = nodes.front(); E; E = E->next()) {
|
|
Ref<VisualScriptNode> nd = script->get_node(function, E->key());
|
|
if (nd.is_valid() && nd->has_input_sequence_port()) {
|
|
if (top_nd < 0) {
|
|
top_nd = E->key();
|
|
top = script->get_node_position(function, top_nd);
|
|
}
|
|
Vector2 pos = script->get_node_position(function, E->key());
|
|
if (top.y > pos.y) {
|
|
top_nd = E->key();
|
|
top = pos;
|
|
}
|
|
}
|
|
}
|
|
Ref<VisualScriptNode> nd = script->get_node(function, 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 (List<VisualScript::SequenceConnection>::Element *E = seqs.front(); E; E = E->next()) {
|
|
if (nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
|
|
seqmove.insert(E->get());
|
|
nodes_from.insert(E->get().from_node);
|
|
} else if (nodes.has(E->get().from_node) && !nodes.has(E->get().to_node)) {
|
|
seqext.insert(E->get());
|
|
} else if (!nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
|
|
if (start_node == -1) {
|
|
seqext.insert(E->get());
|
|
start_node = E->get().to_node;
|
|
} else {
|
|
EditorNode::get_singleton()->show_warning(TTR("Try to only have one sequence input in selection."));
|
|
return;
|
|
}
|
|
}
|
|
nodes_to.insert(E->get().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
|
|
// ie 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(function, &dats);
|
|
for (List<VisualScript::DataConnection>::Element *E = dats.front(); E; E = E->next()) {
|
|
if (nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
|
|
datamove.insert(E->get());
|
|
} else if (!nodes.has(E->get().from_node) && nodes.has(E->get().to_node)) {
|
|
// add all these as inputs for the Function
|
|
Ref<VisualScriptNode> node = script->get_node(function, E->get().to_node);
|
|
if (node.is_valid()) {
|
|
dataext.insert(E->get());
|
|
PropertyInfo pi = node->get_input_value_port_info(E->get().to_port);
|
|
inputs.push_back(pi.type);
|
|
input_connections.push_back(Pair<int, int>(E->get().to_node, E->get().to_port));
|
|
}
|
|
} else if (nodes.has(E->get().from_node) && !nodes.has(E->get().to_node)) {
|
|
dataext.insert(E->get());
|
|
}
|
|
}
|
|
}
|
|
|
|
String new_fn = _validate_name("new_function");
|
|
|
|
Vector2 ofs = _get_available_pos(false, script->get_node_position(function, start_node) - Vector2(80, 150));
|
|
|
|
Ref<VisualScriptFunction> func_node;
|
|
func_node.instance();
|
|
func_node->set_name(new_fn);
|
|
|
|
undo_redo->create_action(TTR("Create Function"));
|
|
|
|
undo_redo->add_do_method(script.ptr(), "add_function", new_fn);
|
|
int fn_id = script->get_available_id();
|
|
undo_redo->add_do_method(script.ptr(), "add_node", new_fn, fn_id, func_node, ofs);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_function", new_fn);
|
|
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");
|
|
|
|
// Move the nodes
|
|
|
|
for (Map<int, Ref<VisualScriptNode>>::Element *E = nodes.front(); E; E = E->next()) {
|
|
undo_redo->add_do_method(script.ptr(), "remove_node", function, E->key());
|
|
undo_redo->add_do_method(script.ptr(), "add_node", new_fn, E->key(), E->get(), script->get_node_position(function, E->key()));
|
|
|
|
// undo_redo->add_undo_method(script.ptr(), "remove_node", new_fn, E->key()); not needed cause we already remove the function :P
|
|
undo_redo->add_undo_method(script.ptr(), "add_node", function, E->key(), E->get(), script->get_node_position(function, E->key()));
|
|
}
|
|
|
|
for (Set<VisualScript::SequenceConnection>::Element *E = seqmove.front(); E; E = E->next()) {
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, E->get().from_node, E->get().from_output, E->get().to_node);
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", function, E->get().from_node, E->get().from_output, E->get().to_node);
|
|
}
|
|
|
|
for (Set<VisualScript::DataConnection>::Element *E = datamove.front(); E; E = E->next()) {
|
|
undo_redo->add_do_method(script.ptr(), "data_connect", new_fn, 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", function, E->get().from_node, E->get().from_port, E->get().to_node, E->get().to_port);
|
|
}
|
|
|
|
// Add undo for external connections as well so that it's easier to revert back and forth
|
|
// these didn't require do methods as it's already handled internally by other do calls
|
|
for (Set<VisualScript::SequenceConnection>::Element *E = seqext.front(); E; E = E->next()) {
|
|
undo_redo->add_undo_method(script.ptr(), "sequence_connect", function, 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_undo_method(script.ptr(), "data_connect", function, 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", new_fn, fn_id, 0, start_node);
|
|
|
|
// end nodes are mapped to the return nodes with data connections if possible
|
|
int m = 1;
|
|
for (Set<int>::Element *G = end_nodes.front(); G; G = G->next()) {
|
|
Ref<VisualScriptReturn> ret_node;
|
|
ret_node.instance();
|
|
|
|
int ret_id = fn_id + (m++);
|
|
selections.insert(ret_id);
|
|
Vector2 ofsi = _get_available_pos(false, script->get_node_position(function, G->get()) + Vector2(80, -100));
|
|
undo_redo->add_do_method(script.ptr(), "add_node", new_fn, ret_id, ret_node, ofsi);
|
|
undo_redo->add_undo_method(script.ptr(), "remove_node", new_fn, ret_id);
|
|
|
|
undo_redo->add_do_method(script.ptr(), "sequence_connect", new_fn, G->get(), 0, ret_id);
|
|
// add data outputs from each of the end_nodes
|
|
Ref<VisualScriptNode> vsn = script->get_node(function, 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", new_fn, G->get(), 0, ret_id, 0);
|
|
}
|
|
}
|
|
|
|
// * 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", new_fn, fn_id, i, F->get().first, F->get().second);
|
|
i++; // increment i
|
|
}
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
// Ensure Preview Selection is of newly created function node
|
|
if (selections.size()) {
|
|
EditorNode::get_singleton()->push_item(func_node.ptr());
|
|
}
|
|
|
|
} break;
|
|
case REFRESH_GRAPH: {
|
|
_update_graph();
|
|
} 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 List<VisualScript::SequenceConnection>::Element *E = p_seqs.front(); E; E = E->next()) {
|
|
int from = E->get().from_node;
|
|
int to = E->get().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_global_position() + p_pos);
|
|
member_popup->set_size(Vector2());
|
|
|
|
function_name_edit->set_position(members->get_global_position() + p_pos);
|
|
function_name_edit->set_size(Vector2());
|
|
|
|
TreeItem *root = members->get_root();
|
|
|
|
Ref<Texture2D> del_icon = Control::get_theme_icon("Remove", "EditorIcons");
|
|
|
|
Ref<Texture2D> edit_icon = Control::get_theme_icon("Edit", "EditorIcons");
|
|
|
|
if (ti->get_parent() == root->get_children()) {
|
|
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("visual_script_editor/delete_selected"), MEMBER_REMOVE);
|
|
member_popup->popup();
|
|
return;
|
|
}
|
|
|
|
if (ti->get_parent() == root->get_children()->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("visual_script_editor/delete_selected"), MEMBER_REMOVE);
|
|
member_popup->popup();
|
|
return;
|
|
}
|
|
|
|
if (ti->get_parent() == root->get_children()->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("visual_script_editor/delete_selected"), 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;
|
|
|
|
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<int> nodes;
|
|
script->get_node_list(name, &nodes);
|
|
for (List<int>::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_position(name, E->get()));
|
|
}
|
|
|
|
List<VisualScript::SequenceConnection> seq_connections;
|
|
|
|
script->get_sequence_connection_list(name, &seq_connections);
|
|
|
|
for (List<VisualScript::SequenceConnection>::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<VisualScript::DataConnection> data_connections;
|
|
|
|
script->get_data_connection_list(name, &data_connections);
|
|
|
|
for (List<VisualScript::DataConnection>::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);
|
|
}
|
|
|
|
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::_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("_input", &VisualScriptEditor::_input);
|
|
|
|
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);
|
|
|
|
ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &VisualScriptEditor::add_syntax_highlighter);
|
|
}
|
|
|
|
VisualScriptEditor::VisualScriptEditor() {
|
|
if (!clipboard) {
|
|
clipboard = memnew(Clipboard);
|
|
}
|
|
updating_graph = false;
|
|
saved_pos_dirty = false;
|
|
saved_position = Vector2(0, 0);
|
|
|
|
edit_menu = memnew(MenuButton);
|
|
edit_menu->set_text(TTR("Edit"));
|
|
edit_menu->set_switch_on_hover(true);
|
|
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/delete_selected"), 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("visual_script_editor/copy_nodes"), EDIT_COPY_NODES);
|
|
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/cut_nodes"), EDIT_CUT_NODES);
|
|
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("visual_script_editor/paste_nodes"), 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("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(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_margins_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("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);
|
|
graph->hide();
|
|
graph->connect("scroll_offset_changed", callable_mp(this, &VisualScriptEditor::_graph_ofs_changed));
|
|
|
|
/// 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()->set_text(TTR("Create"));
|
|
function_create_dialog->get_ok()->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_align(Label::ALIGN_CENTER);
|
|
select_func_text->set_valign(Label::VALIGN_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_margin(MARGIN_TOP, ANCHOR_END, -100);
|
|
hint_text->set_anchor_and_margin(MARGIN_BOTTOM, ANCHOR_END, 0);
|
|
hint_text->set_anchor_and_margin(MARGIN_RIGHT, ANCHOR_END, 0);
|
|
hint_text->set_align(Label::ALIGN_CENTER);
|
|
hint_text->set_valign(Label::VALIGN_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()->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()->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);
|
|
set_process_unhandled_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));
|
|
|
|
method_select = memnew(VisualScriptPropertySelector);
|
|
add_child(method_select);
|
|
method_select->connect("selected", callable_mp(this, &VisualScriptEditor::_selected_method));
|
|
error_line = -1;
|
|
|
|
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()->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));
|
|
}
|
|
|
|
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/delete_selected", TTR("Delete Selected"), KEY_DELETE);
|
|
ED_SHORTCUT("visual_script_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9);
|
|
ED_SHORTCUT("visual_script_editor/find_node_type", TTR("Find Node Type"), KEY_MASK_CMD + KEY_F);
|
|
ED_SHORTCUT("visual_script_editor/copy_nodes", TTR("Copy Nodes"), KEY_MASK_CMD + KEY_C);
|
|
ED_SHORTCUT("visual_script_editor/cut_nodes", TTR("Cut Nodes"), KEY_MASK_CMD + KEY_X);
|
|
ED_SHORTCUT("visual_script_editor/paste_nodes", TTR("Paste Nodes"), KEY_MASK_CMD + KEY_V);
|
|
ED_SHORTCUT("visual_script_editor/create_function", TTR("Make Function"), KEY_MASK_CMD + KEY_G);
|
|
ED_SHORTCUT("visual_script_editor/refresh_nodes", TTR("Refresh Graph"), KEY_MASK_CMD + KEY_R);
|
|
ED_SHORTCUT("visual_script_editor/edit_member", TTR("Edit Member"), KEY_MASK_CMD + KEY_E);
|
|
}
|
|
|
|
void VisualScriptEditor::register_editor() {
|
|
// Too early to register stuff here, request a callback.
|
|
EditorNode::add_plugin_init_callback(register_editor_callback);
|
|
}
|
|
|
|
Ref<VisualScriptNode> _VisualScriptEditor::create_node_custom(const String &p_name) {
|
|
Ref<VisualScriptCustomNode> node;
|
|
node.instance();
|
|
node->set_script(singleton->custom_nodes[p_name]);
|
|
return node;
|
|
}
|
|
|
|
_VisualScriptEditor *_VisualScriptEditor::singleton = nullptr;
|
|
Map<String, REF> _VisualScriptEditor::custom_nodes;
|
|
|
|
_VisualScriptEditor::_VisualScriptEditor() {
|
|
singleton = this;
|
|
}
|
|
|
|
_VisualScriptEditor::~_VisualScriptEditor() {
|
|
custom_nodes.clear();
|
|
}
|
|
|
|
void _VisualScriptEditor::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, &_VisualScriptEditor::create_node_custom);
|
|
emit_signal("custom_nodes_updated");
|
|
}
|
|
|
|
void _VisualScriptEditor::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("custom_nodes_updated");
|
|
}
|
|
|
|
void _VisualScriptEditor::_bind_methods() {
|
|
ClassDB::bind_method(D_METHOD("add_custom_node", "name", "category", "script"), &_VisualScriptEditor::add_custom_node);
|
|
ClassDB::bind_method(D_METHOD("remove_custom_node", "name", "category"), &_VisualScriptEditor::remove_custom_node);
|
|
ADD_SIGNAL(MethodInfo("custom_nodes_updated"));
|
|
}
|
|
|
|
void VisualScriptEditor::validate() {
|
|
}
|
|
#endif
|