Browse Source

Expression node for visual shaders

Chaosus 6 years ago
parent
commit
5648924eef

+ 3 - 0
editor/editor_fonts.cpp

@@ -239,6 +239,9 @@ void editor_register_fonts(Ref<Theme> p_theme) {
 	MAKE_SOURCE_FONT(df_code, int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) * EDSCALE);
 	p_theme->set_font("source", "EditorFonts", df_code);
 
+	MAKE_SOURCE_FONT(df_expression, (int(EditorSettings::get_singleton()->get("interface/editor/code_font_size")) - 1) * EDSCALE);
+	p_theme->set_font("expression", "EditorFonts", df_expression);
+
 	MAKE_SOURCE_FONT(df_output_code, int(EDITOR_DEF("run/output/font_size", 13)) * EDSCALE);
 	p_theme->set_font("output_source", "EditorFonts", df_output_code);
 

+ 565 - 14
editor/plugins/visual_shader_editor_plugin.cpp

@@ -358,7 +358,9 @@ void VisualShaderEditor::_update_graph() {
 	for (int i = 0; i < graph->get_child_count(); i++) {
 
 		if (Object::cast_to<GraphNode>(graph->get_child(i))) {
-			memdelete(graph->get_child(i));
+			Node *node = graph->get_child(i);
+			graph->remove_child(node);
+			memdelete(node);
 			i--;
 		}
 	}
@@ -377,13 +379,33 @@ void VisualShaderEditor::_update_graph() {
 
 	Vector<int> nodes = visual_shader->get_node_list(type);
 
+	Control *offset;
+
 	for (int n_i = 0; n_i < nodes.size(); n_i++) {
 
 		Vector2 position = visual_shader->get_node_position(type, nodes[n_i]);
 		Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, nodes[n_i]);
 
+		Ref<VisualShaderNodeGroupBase> group_node = Object::cast_to<VisualShaderNodeGroupBase>(vsnode.ptr());
+		bool is_group = !group_node.is_null();
+		Size2 size = Size2(0, 0);
+
+		Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(group_node.ptr());
+		bool is_expression = !expression_node.is_null();
+		String expression = "";
+
 		GraphNode *node = memnew(GraphNode);
 
+		if (is_group) {
+			size = group_node->get_size();
+
+			node->set_resizable(true);
+			node->connect("resize_request", this, "_node_resized", varray((int)type, nodes[n_i]));
+		}
+		if (is_expression) {
+			expression = expression_node->get_expression();
+		}
+
 		/*if (!vsnode->is_connected("changed", this, "_node_changed")) {
 			vsnode->connect("changed", this, "_node_changed", varray(vsnode->get_instance_id()), CONNECT_DEFERRED);
 		}*/
@@ -403,6 +425,10 @@ void VisualShaderEditor::_update_graph() {
 		Control *custom_editor = NULL;
 		int port_offset = 0;
 
+		if (is_group) {
+			port_offset++;
+		}
+
 		Ref<VisualShaderNodeUniform> uniform = vsnode;
 		if (uniform.is_valid()) {
 			graph->add_child(node);
@@ -438,6 +464,24 @@ void VisualShaderEditor::_update_graph() {
 			custom_editor = NULL;
 		}
 
+		if (is_group) {
+			HBoxContainer *hb2 = memnew(HBoxContainer);
+
+			Button *add_input_btn = memnew(Button);
+			add_input_btn->set_text(TTR("Add input +"));
+			add_input_btn->connect("pressed", this, "_add_input_port", varray(nodes[n_i], group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, "input" + itos(group_node->get_free_input_port_id())), CONNECT_DEFERRED);
+			hb2->add_child(add_input_btn);
+
+			hb2->add_spacer();
+
+			Button *add_output_btn = memnew(Button);
+			add_output_btn->set_text(TTR("Add output +"));
+			add_output_btn->connect("pressed", this, "_add_output_port", varray(nodes[n_i], group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, "output" + itos(group_node->get_free_output_port_id())), CONNECT_DEFERRED);
+			hb2->add_child(add_output_btn);
+
+			node->add_child(hb2);
+		}
+
 		for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) {
 
 			if (vsnode->is_port_separator(i)) {
@@ -507,21 +551,75 @@ void VisualShaderEditor::_update_graph() {
 
 				if (valid_left) {
 
-					Label *label = memnew(Label);
-					label->set_text(name_left);
-					label->add_style_override("normal", label_style); //more compact
-					hb->add_child(label);
+					if (is_group) {
+
+						OptionButton *type_box = memnew(OptionButton);
+						hb->add_child(type_box);
+						type_box->add_item(TTR("Scalar"));
+						type_box->add_item(TTR("Vector"));
+						type_box->add_item(TTR("Boolean"));
+						type_box->add_item(TTR("Transform"));
+						type_box->select(group_node->get_input_port_type(i));
+						type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
+						type_box->connect("item_selected", this, "_change_input_port_type", varray(nodes[n_i], i), CONNECT_DEFERRED);
+
+						LineEdit *name_box = memnew(LineEdit);
+						hb->add_child(name_box);
+						name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0));
+						name_box->set_text(name_left);
+						name_box->set_expand_to_text_length(true);
+						name_box->connect("text_entered", this, "_change_input_port_name", varray(name_box, nodes[n_i], i));
+						name_box->connect("focus_exited", this, "_port_name_focus_out", varray(name_box, nodes[n_i], i, false));
+
+						if (is_group) {
+							Button *remove_btn = memnew(Button);
+							remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons"));
+							remove_btn->set_tooltip(TTR("Remove") + " " + name_left);
+							remove_btn->connect("pressed", this, "_remove_input_port", varray(nodes[n_i], i), CONNECT_DEFERRED);
+							hb->add_child(remove_btn);
+						}
+					} else {
+
+						Label *label = memnew(Label);
+						label->set_text(name_left);
+						label->add_style_override("normal", label_style); //more compact
+						hb->add_child(label);
+					}
 				}
 
 				hb->add_spacer();
 
 				if (valid_right) {
-
-					Label *label = memnew(Label);
-					label->set_text(name_right);
-					label->set_align(Label::ALIGN_RIGHT);
-					label->add_style_override("normal", label_style); //more compact
-					hb->add_child(label);
+					if (is_group) {
+						Button *remove_btn = memnew(Button);
+						remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_icon("Remove", "EditorIcons"));
+						remove_btn->set_tooltip(TTR("Remove") + " " + name_left);
+						remove_btn->connect("pressed", this, "_remove_output_port", varray(nodes[n_i], i), CONNECT_DEFERRED);
+						hb->add_child(remove_btn);
+
+						LineEdit *name_box = memnew(LineEdit);
+						hb->add_child(name_box);
+						name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0));
+						name_box->set_text(name_right);
+						name_box->set_expand_to_text_length(true);
+						name_box->connect("text_entered", this, "_change_output_port_name", varray(name_box, nodes[n_i], i));
+						name_box->connect("focus_exited", this, "_port_name_focus_out", varray(name_box, nodes[n_i], i, true));
+
+						OptionButton *type_box = memnew(OptionButton);
+						hb->add_child(type_box);
+						type_box->add_item(TTR("Scalar"));
+						type_box->add_item(TTR("Vector"));
+						type_box->add_item(TTR("Boolean"));
+						type_box->add_item(TTR("Transform"));
+						type_box->select(group_node->get_output_port_type(i));
+						type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
+						type_box->connect("item_selected", this, "_change_output_port_type", varray(nodes[n_i], i), CONNECT_DEFERRED);
+					} else {
+						Label *label = memnew(Label);
+						label->set_text(name_right);
+						label->add_style_override("normal", label_style); //more compact
+						hb->add_child(label);
+					}
 				}
 			}
 
@@ -540,18 +638,33 @@ void VisualShaderEditor::_update_graph() {
 				hb->add_child(preview);
 			}
 
+			if (is_group) {
+				offset = memnew(Control);
+				offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE));
+				node->add_child(offset);
+				port_offset++;
+			}
+
 			node->add_child(hb);
 
 			node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]);
 		}
 
 		if (vsnode->get_output_port_for_preview() >= 0 && vsnode->get_output_port_type(vsnode->get_output_port_for_preview()) != VisualShaderNode::PORT_TYPE_TRANSFORM) {
+			offset = memnew(Control);
+			offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE));
+			node->add_child(offset);
+
 			VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview);
 			port_preview->setup(visual_shader, type, nodes[n_i], vsnode->get_output_port_for_preview());
 			port_preview->set_h_size_flags(SIZE_SHRINK_CENTER);
 			node->add_child(port_preview);
 		}
 
+		offset = memnew(Control);
+		offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE));
+		node->add_child(offset);
+
 		String error = vsnode->get_warning(visual_shader->get_mode(), type);
 		if (error != String()) {
 			Label *error_label = memnew(Label);
@@ -560,9 +673,42 @@ void VisualShaderEditor::_update_graph() {
 			node->add_child(error_label);
 		}
 
+		if (is_expression) {
+
+			TextEdit *expression_box = memnew(TextEdit);
+			expression_node->set_control(expression_box, 0);
+			node->add_child(expression_box);
+
+			Color text_color = EDITOR_GET("text_editor/highlighting/text_color");
+			Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+			Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color");
+			Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color");
+
+			expression_box->set_syntax_coloring(true);
+
+			for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) {
+
+				expression_box->add_keyword_color(E->get(), keyword_color);
+			}
+
+			expression_box->add_font_override("font", get_font("expression", "EditorFonts"));
+			expression_box->add_color_override("font_color", text_color);
+			expression_box->add_color_override("symbol_color", symbol_color);
+			expression_box->add_color_region("/*", "*/", comment_color, false);
+			expression_box->add_color_region("//", "", comment_color, false);
+
+			expression_box->set_text(expression);
+			expression_box->set_context_menu_enabled(false);
+			expression_box->set_show_line_numbers(true);
+
+			expression_box->connect("text_changed", this, "_set_expression", varray(nodes[n_i]), CONNECT_DEFERRED);
+		}
+
 		if (!uniform.is_valid()) {
 			graph->add_child(node);
 			_update_created_node(node);
+			if (is_group)
+				call_deferred("_set_node_size", (int)type, nodes[n_i], size);
 		}
 	}
 
@@ -577,6 +723,290 @@ void VisualShaderEditor::_update_graph() {
 	}
 }
 
+void VisualShaderEditor::_add_input_port(int p_node, int p_port, int p_port_type, const String &p_name) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeExpression> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Add input port"));
+	undo_redo->add_do_method(node.ptr(), "add_input_port", p_port, p_port_type, p_name);
+	undo_redo->add_undo_method(node.ptr(), "remove_input_port", p_port);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_add_output_port(int p_node, int p_port, int p_port_type, const String &p_name) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Add output port"));
+	undo_redo->add_do_method(node.ptr(), "add_output_port", p_port, p_port_type, p_name);
+	undo_redo->add_undo_method(node.ptr(), "remove_output_port", p_port);
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_change_input_port_type(int p_type, int p_node, int p_port) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Change input port type"));
+	undo_redo->add_do_method(node.ptr(), "set_input_port_type", p_port, p_type);
+	undo_redo->add_undo_method(node.ptr(), "set_input_port_type", p_port, node->get_input_port_type(p_port));
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_change_output_port_type(int p_type, int p_node, int p_port) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Change output port type"));
+	undo_redo->add_do_method(node.ptr(), "set_output_port_type", p_port, p_type);
+	undo_redo->add_undo_method(node.ptr(), "set_output_port_type", p_port, node->get_output_port_type(p_port));
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_change_input_port_name(const String &p_text, Object *line_edit, int p_node_id, int p_port_id) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node_id);
+	ERR_FAIL_COND(!node.is_valid());
+
+	undo_redo->create_action(TTR("Change input port name"));
+	undo_redo->add_do_method(node.ptr(), "set_input_port_name", p_port_id, p_text);
+	undo_redo->add_undo_method(node.ptr(), "set_input_port_name", p_port_id, node->get_input_port_name(p_port_id));
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_change_output_port_name(const String &p_text, Object *line_edit, int p_node_id, int p_port_id) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node_id);
+	ERR_FAIL_COND(!node.is_valid());
+
+	undo_redo->create_action(TTR("Change output port name"));
+	undo_redo->add_do_method(node.ptr(), "set_output_port_name", p_port_id, p_text);
+	undo_redo->add_undo_method(node.ptr(), "set_output_port_name", p_port_id, node->get_output_port_name(p_port_id));
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_remove_input_port(int p_node, int p_port) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Remove input port"));
+
+	List<VisualShader::Connection> conns;
+	visual_shader->get_node_connections(type, &conns);
+	for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
+
+		int from_node = E->get().from_node;
+		int from_port = E->get().from_port;
+		int to_node = E->get().to_node;
+		int to_port = E->get().to_port;
+
+		if (to_node == p_node) {
+			if (to_port == p_port) {
+				undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+				undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+			} else if (to_port > p_port) {
+				undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+				undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+
+				undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port - 1);
+				undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port - 1);
+			}
+		}
+	}
+
+	undo_redo->add_do_method(node.ptr(), "remove_input_port", p_port);
+	undo_redo->add_undo_method(node.ptr(), "add_input_port", p_port, (int)node->get_input_port_type(p_port), node->get_input_port_name(p_port));
+
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_remove_output_port(int p_node, int p_port) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Remove output port"));
+
+	List<VisualShader::Connection> conns;
+	visual_shader->get_node_connections(type, &conns);
+	for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
+
+		int from_node = E->get().from_node;
+		int from_port = E->get().from_port;
+		int to_node = E->get().to_node;
+		int to_port = E->get().to_port;
+
+		if (from_node == p_node) {
+			if (from_port == p_port) {
+				undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+				undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+			} else if (from_port > p_port) {
+				undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port);
+				undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port);
+
+				undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port - 1, to_node, to_port);
+				undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port - 1, to_node, to_port);
+			}
+		}
+	}
+
+	undo_redo->add_do_method(node.ptr(), "remove_output_port", p_port);
+	undo_redo->add_undo_method(node.ptr(), "add_output_port", p_port, (int)node->get_output_port_type(p_port), node->get_output_port_name(p_port));
+
+	undo_redo->add_do_method(this, "_update_graph");
+	undo_redo->add_undo_method(this, "_update_graph");
+
+	undo_redo->add_do_method(this, "_rebuild");
+	undo_redo->add_undo_method(this, "_rebuild");
+
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_set_expression(int p_node) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNodeExpression> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	TextEdit *expression_box = Object::cast_to<TextEdit>(node->get_control(0));
+
+	undo_redo->create_action(TTR("Set expression"));
+	undo_redo->add_do_method(node.ptr(), "set_expression", expression_box->get_text());
+	undo_redo->add_undo_method(node.ptr(), "set_expression", node->get_expression());
+	undo_redo->add_do_method(this, "_start_rebuild_timer", 1.0);
+	undo_redo->add_undo_method(this, "_rebuild");
+	undo_redo->commit_action();
+}
+
+void VisualShaderEditor::_start_rebuild_timer(int p_delay) {
+	build_timer->start(p_delay);
+}
+
+void VisualShaderEditor::_rebuild_timeout() {
+	_rebuild();
+}
+
+void VisualShaderEditor::_rebuild() {
+	EditorNode::get_singleton()->get_log()->clear();
+	visual_shader->rebuild();
+}
+
+void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p_size) {
+
+	VisualShader::Type type = VisualShader::Type(p_type);
+	Ref<VisualShaderNode> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	Ref<VisualShaderNodeGroupBase> group_node = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr());
+
+	if (group_node.is_null()) {
+		return;
+	}
+
+	Vector2 size = p_size;
+
+	group_node->set_size(size);
+
+	GraphNode *gn = NULL;
+	if (edit_type->get_selected() == p_type) { // check - otherwise the error will be emitted
+		Node *node2 = graph->get_node(itos(p_node));
+		gn = Object::cast_to<GraphNode>(node2);
+		if (!gn)
+			return;
+
+		gn->set_custom_minimum_size(size);
+		gn->set_size(Size2(1, 1));
+	}
+
+	Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(node.ptr());
+	if (!expression_node.is_null()) {
+		Control *text_box = expression_node->get_control(0);
+		Size2 box_size = size;
+		if (gn != NULL) {
+			if (box_size.x < 150 * EDSCALE || box_size.y < 0) {
+				box_size.x = gn->get_size().x;
+			}
+		}
+		box_size.x -= text_box->get_margin(Margin::MARGIN_LEFT);
+		box_size.x -= 28 * EDSCALE;
+		box_size.y -= text_box->get_margin(Margin::MARGIN_TOP);
+		box_size.y -= 28 * EDSCALE;
+		text_box->set_custom_minimum_size(Size2(box_size.x, box_size.y));
+		text_box->set_size(Size2(1, 1));
+	}
+}
+
+void VisualShaderEditor::_node_resized(const Vector2 &p_new_size, int p_type, int p_node) {
+
+	VisualShader::Type type = VisualShader::Type(p_type);
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node);
+	if (node.is_null()) {
+		return;
+	}
+
+	undo_redo->create_action(TTR("Resize VisualShader node"), UndoRedo::MERGE_ENDS);
+	undo_redo->add_do_method(this, "_set_node_size", p_type, p_node, p_new_size / EDSCALE);
+	undo_redo->add_undo_method(this, "_set_node_size", p_type, p_node, node->get_size());
+	undo_redo->commit_action();
+}
+
 void VisualShaderEditor::_preview_select_port(int p_node, int p_port) {
 
 	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
@@ -623,6 +1053,52 @@ void VisualShaderEditor::_line_edit_focus_out(Object *line_edit, int p_node_id)
 	_line_edit_changed(text, line_edit, p_node_id);
 }
 
+void VisualShaderEditor::_port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output) {
+
+	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+
+	Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node_id);
+	ERR_FAIL_COND(!node.is_valid());
+
+	String text = Object::cast_to<LineEdit>(line_edit)->get_text();
+
+	if (!p_output) {
+		if (node->get_input_port_name(p_port_id) == text)
+			return;
+	} else {
+		if (node->get_output_port_name(p_port_id) == text)
+			return;
+	}
+
+	List<String> input_names;
+	List<String> output_names;
+
+	for (int i = 0; i < node->get_input_port_count(); i++) {
+		if (!p_output && i == p_port_id) continue;
+		input_names.push_back(node->get_input_port_name(i));
+	}
+	for (int i = 0; i < node->get_output_port_count(); i++) {
+		if (p_output && i == p_port_id) continue;
+		output_names.push_back(node->get_output_port_name(i));
+	}
+
+	String validated_name = visual_shader->validate_port_name(text, input_names, output_names);
+	if (validated_name == "") {
+		if (!p_output) {
+			Object::cast_to<LineEdit>(line_edit)->set_text(node->get_input_port_name(p_port_id));
+		} else {
+			Object::cast_to<LineEdit>(line_edit)->set_text(node->get_output_port_name(p_port_id));
+		}
+		return;
+	}
+
+	if (!p_output) {
+		_change_input_port_name(validated_name, line_edit, p_node_id, p_port_id);
+	} else {
+		_change_output_port_name(validated_name, line_edit, p_node_id, p_port_id);
+	}
+}
+
 void VisualShaderEditor::_port_edited() {
 
 	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
@@ -757,6 +1233,12 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx) {
 	undo_redo->create_action(TTR("Add Node to Visual Shader"));
 	undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, vsnode, position, id_to_use);
 	undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_to_use);
+
+	VisualShaderNodeExpression *expr = Object::cast_to<VisualShaderNodeExpression>(vsnode.ptr());
+	if (expr) {
+		undo_redo->add_do_method(expr, "set_size", Size2(250 * EDSCALE, 150 * EDSCALE));
+	}
+
 	undo_redo->add_do_method(this, "_update_graph");
 	undo_redo->add_undo_method(this, "_update_graph");
 	undo_redo->commit_action();
@@ -831,10 +1313,25 @@ void VisualShaderEditor::_connection_to_empty(const String &p_from, int p_from_s
 void VisualShaderEditor::_delete_request(int which) {
 
 	VisualShader::Type type = VisualShader::Type(edit_type->get_selected());
+	Ref<VisualShaderNode> node = Ref<VisualShaderNode>(visual_shader->get_node(type, which));
 
 	undo_redo->create_action(TTR("Delete Node"));
 	undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, which);
-	undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, visual_shader->get_node(type, which), visual_shader->get_node_position(type, which), which);
+	undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, which), which);
+
+	// restore size, inputs and outputs if node is group
+	VisualShaderNodeGroupBase *group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr());
+	if (group) {
+		undo_redo->add_undo_method(group, "set_size", group->get_size());
+		undo_redo->add_undo_method(group, "set_inputs", group->get_inputs());
+		undo_redo->add_undo_method(group, "set_outputs", group->get_outputs());
+	}
+
+	// restore expression text if node is expression
+	VisualShaderNodeExpression *expression = Object::cast_to<VisualShaderNodeExpression>(node.ptr());
+	if (expression) {
+		undo_redo->add_undo_method(expression, "set_expression", expression->get_expression());
+	}
 
 	List<VisualShader::Connection> conns;
 	visual_shader->get_node_connections(type, &conns);
@@ -1022,6 +1519,19 @@ void VisualShaderEditor::_duplicate_nodes() {
 		undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(type, E->get()) + Vector2(10, 10) * EDSCALE, id_from);
 		undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from);
 
+		// duplicate size, inputs and outputs if node is group
+		Ref<VisualShaderNodeGroupBase> group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr());
+		if (!group.is_null()) {
+			undo_redo->add_do_method(dupli.ptr(), "set_size", group->get_size());
+			undo_redo->add_do_method(dupli.ptr(), "set_inputs", group->get_inputs());
+			undo_redo->add_do_method(dupli.ptr(), "set_outputs", group->get_outputs());
+		}
+		// duplicate expression text if node is expression
+		Ref<VisualShaderNodeExpression> expression = Object::cast_to<VisualShaderNodeExpression>(node.ptr());
+		if (!expression.is_null()) {
+			undo_redo->add_do_method(dupli.ptr(), "set_expression", expression->get_expression());
+		}
+
 		id_from++;
 	}
 
@@ -1030,7 +1540,7 @@ void VisualShaderEditor::_duplicate_nodes() {
 
 	for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) {
 		if (connection_remap.has(E->get().from_node) && connection_remap.has(E->get().to_node)) {
-			undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port);
+			undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, connection_remap[E->get().from_node], E->get().from_port, connection_remap[E->get().to_node], E->get().to_port);
 		}
 	}
 
@@ -1073,8 +1583,25 @@ void VisualShaderEditor::_on_nodes_delete() {
 	undo_redo->create_action(TTR("Delete Nodes"));
 
 	for (List<int>::Element *F = to_erase.front(); F; F = F->next()) {
+
+		Ref<VisualShaderNode> node = visual_shader->get_node(type, F->get());
+
 		undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F->get());
-		undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, visual_shader->get_node(type, F->get()), visual_shader->get_node_position(type, F->get()), F->get());
+		undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F->get()), F->get());
+
+		// restore size, inputs and outputs if node is group
+		VisualShaderNodeGroupBase *group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr());
+		if (group) {
+			undo_redo->add_undo_method(group, "set_size", group->get_size());
+			undo_redo->add_undo_method(group, "set_inputs", group->get_inputs());
+			undo_redo->add_undo_method(group, "set_outputs", group->get_outputs());
+		}
+
+		// restore expression text if node is expression
+		VisualShaderNodeExpression *expression = Object::cast_to<VisualShaderNodeExpression>(node.ptr());
+		if (expression) {
+			undo_redo->add_undo_method(expression, "set_expression", expression->get_expression());
+		}
 	}
 
 	List<VisualShader::Connection> conns;
@@ -1267,8 +1794,12 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da
 }
 
 void VisualShaderEditor::_bind_methods() {
+	ClassDB::bind_method("_rebuild", &VisualShaderEditor::_rebuild);
 	ClassDB::bind_method("_update_graph", &VisualShaderEditor::_update_graph);
 	ClassDB::bind_method("_update_options_menu", &VisualShaderEditor::_update_options_menu);
+	ClassDB::bind_method("_start_rebuild_timer", &VisualShaderEditor::_start_rebuild_timer);
+	ClassDB::bind_method("_set_expression", &VisualShaderEditor::_set_expression);
+	ClassDB::bind_method("_rebuild_timeout", &VisualShaderEditor::_rebuild_timeout);
 	ClassDB::bind_method("_add_node", &VisualShaderEditor::_add_node);
 	ClassDB::bind_method("_node_dragged", &VisualShaderEditor::_node_dragged);
 	ClassDB::bind_method("_connection_request", &VisualShaderEditor::_connection_request);
@@ -1283,11 +1814,22 @@ void VisualShaderEditor::_bind_methods() {
 	ClassDB::bind_method("_connection_to_empty", &VisualShaderEditor::_connection_to_empty);
 	ClassDB::bind_method("_line_edit_focus_out", &VisualShaderEditor::_line_edit_focus_out);
 	ClassDB::bind_method("_line_edit_changed", &VisualShaderEditor::_line_edit_changed);
+	ClassDB::bind_method("_port_name_focus_out", &VisualShaderEditor::_port_name_focus_out);
 	ClassDB::bind_method("_duplicate_nodes", &VisualShaderEditor::_duplicate_nodes);
 	ClassDB::bind_method("_mode_selected", &VisualShaderEditor::_mode_selected);
 	ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item);
 	ClassDB::bind_method("_preview_select_port", &VisualShaderEditor::_preview_select_port);
 	ClassDB::bind_method("_graph_gui_input", &VisualShaderEditor::_graph_gui_input);
+	ClassDB::bind_method("_add_input_port", &VisualShaderEditor::_add_input_port);
+	ClassDB::bind_method("_change_input_port_type", &VisualShaderEditor::_change_input_port_type);
+	ClassDB::bind_method("_change_input_port_name", &VisualShaderEditor::_change_input_port_name);
+	ClassDB::bind_method("_remove_input_port", &VisualShaderEditor::_remove_input_port);
+	ClassDB::bind_method("_add_output_port", &VisualShaderEditor::_add_output_port);
+	ClassDB::bind_method("_change_output_port_type", &VisualShaderEditor::_change_output_port_type);
+	ClassDB::bind_method("_change_output_port_name", &VisualShaderEditor::_change_output_port_name);
+	ClassDB::bind_method("_remove_output_port", &VisualShaderEditor::_remove_output_port);
+	ClassDB::bind_method("_node_resized", &VisualShaderEditor::_node_resized);
+	ClassDB::bind_method("_set_node_size", &VisualShaderEditor::_set_node_size);
 
 	ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw);
 	ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw);
@@ -1311,6 +1853,7 @@ VisualShaderEditor::VisualShaderEditor() {
 	updating = false;
 	saved_node_pos_dirty = false;
 	saved_node_pos = Point2(0, 0);
+	ShaderLanguage::get_keyword_list(&keyword_list);
 
 	graph = memnew(GraphEdit);
 	add_child(graph);
@@ -1710,6 +2253,7 @@ VisualShaderEditor::VisualShaderEditor() {
 
 	// SPECIAL
 
+	add_options.push_back(AddOption("Expression", "Special", "", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside.")));
 	add_options.push_back(AddOption("Fresnel", "Special", "", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), -1, VisualShaderNode::PORT_TYPE_SCALAR));
 
 	add_options.push_back(AddOption("ScalarDerivativeFunc", "Special", "Common", "VisualShaderNodeScalarDerivativeFunc", TTR("(GLES3 only) (Fragment/Light mode only) Scalar derivative function."), -1, VisualShaderNode::PORT_TYPE_SCALAR, VisualShader::TYPE_FRAGMENT | VisualShader::TYPE_LIGHT));
@@ -1743,6 +2287,13 @@ VisualShaderEditor::VisualShaderEditor() {
 	add_child(property_editor);
 
 	property_editor->connect("variant_changed", this, "_port_edited");
+
+	// BUILD TIMER FOR EXPRESSION NODES
+
+	build_timer = memnew(Timer);
+	add_child(build_timer);
+	build_timer->connect("timeout", this, "_rebuild_timeout");
+	build_timer->set_one_shot(true);
 }
 
 void VisualShaderEditorPlugin::edit(Object *p_object) {

+ 24 - 0
editor/plugins/visual_shader_editor_plugin.h

@@ -84,6 +84,8 @@ class VisualShaderEditor : public VBoxContainer {
 	LineEdit *node_filter;
 	RichTextLabel *node_desc;
 
+	Timer *build_timer;
+
 	void _tools_menu_option(int p_idx);
 	void _show_members_dialog(bool at_mouse_pos);
 
@@ -128,6 +130,7 @@ class VisualShaderEditor : public VBoxContainer {
 	};
 
 	Vector<AddOption> add_options;
+	List<String> keyword_list;
 
 	void _draw_color_over_button(Object *obj, Color p_color);
 
@@ -160,14 +163,35 @@ class VisualShaderEditor : public VBoxContainer {
 	void _line_edit_changed(const String &p_text, Object *line_edit, int p_node_id);
 	void _line_edit_focus_out(Object *line_edit, int p_node_id);
 
+	void _port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output);
+
 	void _duplicate_nodes();
 
 	Vector<Ref<VisualShaderNodePlugin> > plugins;
 
 	void _mode_selected(int p_id);
+	void _rebuild();
 
 	void _input_select_item(Ref<VisualShaderNodeInput> input, String name);
 
+	void _add_input_port(int p_node, int p_port, int p_type, const String &p_name);
+	void _remove_input_port(int p_node, int p_port);
+	void _change_input_port_type(int p_type, int p_node, int p_port);
+	void _change_input_port_name(const String &p_text, Object *line_edit, int p_node, int p_port);
+
+	void _add_output_port(int p_node, int p_port, int p_type, const String &p_name);
+	void _remove_output_port(int p_node, int p_port);
+	void _change_output_port_type(int p_type, int p_node, int p_port);
+	void _change_output_port_name(const String &p_text, Object *line_edit, int p_node, int p_port);
+
+	void _start_rebuild_timer(int p_delay);
+
+	void _set_expression(int p_node);
+	void _rebuild_timeout();
+
+	void _set_node_size(int p_type, int p_node, const Size2 &p_size);
+	void _node_resized(const Vector2 &p_new_size, int p_type, int p_node);
+
 	void _preview_select_port(int p_node, int p_port);
 	void _graph_gui_input(const Ref<InputEvent> p_event);
 

+ 2 - 0
scene/register_scene_types.cpp

@@ -476,6 +476,7 @@ void register_scene_types() {
 	ClassDB::register_virtual_class<VisualShaderNode>();
 	ClassDB::register_class<VisualShaderNodeInput>();
 	ClassDB::register_virtual_class<VisualShaderNodeOutput>();
+	ClassDB::register_class<VisualShaderNodeGroupBase>();
 	ClassDB::register_class<VisualShaderNodeScalarConstant>();
 	ClassDB::register_class<VisualShaderNodeBooleanConstant>();
 	ClassDB::register_class<VisualShaderNodeColorConstant>();
@@ -524,6 +525,7 @@ void register_scene_types() {
 	ClassDB::register_class<VisualShaderNodeIf>();
 	ClassDB::register_class<VisualShaderNodeSwitch>();
 	ClassDB::register_class<VisualShaderNodeFresnel>();
+	ClassDB::register_class<VisualShaderNodeExpression>();
 
 	ClassDB::register_class<ShaderMaterial>();
 	ClassDB::register_virtual_class<CanvasItem>();

+ 656 - 3
scene/resources/visual_shader.cpp

@@ -159,6 +159,7 @@ Vector2 VisualShader::get_node_position(Type p_type, int p_id) const {
 	ERR_FAIL_COND_V(!g->nodes.has(p_id), Vector2());
 	return g->nodes[p_id].position;
 }
+
 Ref<VisualShaderNode> VisualShader::get_node(Type p_type, int p_id) const {
 	ERR_FAIL_INDEX_V(p_type, TYPE_MAX, Ref<VisualShaderNode>());
 	const Graph *g = &graph[p_type];
@@ -269,6 +270,18 @@ bool VisualShader::can_connect_nodes(Type p_type, int p_from_node, int p_from_po
 	return true;
 }
 
+void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
+	ERR_FAIL_INDEX(p_type, TYPE_MAX);
+	Graph *g = &graph[p_type];
+	Connection c;
+	c.from_node = p_from_node;
+	c.from_port = p_from_port;
+	c.to_node = p_to_node;
+	c.to_port = p_to_port;
+	g->connections.push_back(c);
+	_queue_update();
+}
+
 Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
 	ERR_FAIL_INDEX_V(p_type, TYPE_MAX, ERR_CANT_CONNECT);
 	Graph *g = &graph[p_type];
@@ -304,6 +317,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,
 	_queue_update();
 	return OK;
 }
+
 void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) {
 	ERR_FAIL_INDEX(p_type, TYPE_MAX);
 	Graph *g = &graph[p_type];
@@ -334,6 +348,7 @@ Array VisualShader::_get_node_connections(Type p_type) const {
 
 	return ret;
 }
+
 void VisualShader::get_node_connections(Type p_type, List<Connection> *r_connections) const {
 	ERR_FAIL_INDEX(p_type, TYPE_MAX);
 	const Graph *g = &graph[p_type];
@@ -477,6 +492,54 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port
 
 #define IS_SYMBOL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z') || ((m_d) >= '0' && (m_d) <= '9') || (m_d) == '_')
 
+String VisualShader::validate_port_name(const String &p_name, const List<String> &p_input_ports, const List<String> &p_output_ports) const {
+	String name = p_name;
+
+	while (name.length() && !IS_INITIAL_CHAR(name[0])) {
+		name = name.substr(1, name.length() - 1);
+	}
+
+	if (name != String()) {
+
+		String valid_name;
+
+		for (int i = 0; i < name.length(); i++) {
+			if (IS_SYMBOL_CHAR(name[i])) {
+				valid_name += String::chr(name[i]);
+			} else if (name[i] == ' ') {
+				valid_name += "_";
+			}
+		}
+
+		name = valid_name;
+	}
+
+	String valid_name = name;
+	bool is_equal = false;
+
+	for (int i = 0; i < p_input_ports.size(); i++) {
+		if (name == p_input_ports[i]) {
+			is_equal = true;
+			break;
+		}
+	}
+
+	if (!is_equal) {
+		for (int i = 0; i < p_output_ports.size(); i++) {
+			if (name == p_output_ports[i]) {
+				is_equal = true;
+				break;
+			}
+		}
+	}
+
+	if (is_equal) {
+		name = "";
+	}
+
+	return name;
+}
+
 String VisualShader::validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const {
 
 	String name = p_name; //validate name first
@@ -596,7 +659,7 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) {
 			Vector<int> conns = p_value;
 			if (conns.size() % 4 == 0) {
 				for (int i = 0; i < conns.size(); i += 4) {
-					connect_nodes(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]);
+					connect_nodes_forced(type, conns[i + 0], conns[i + 1], conns[i + 2], conns[i + 3]);
 				}
 			}
 			return true;
@@ -611,6 +674,18 @@ bool VisualShader::_set(const StringName &p_name, const Variant &p_value) {
 		} else if (what == "position") {
 			set_node_position(type, id, p_value);
 			return true;
+		} else if (what == "size") {
+			((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->set_size(p_value);
+			return true;
+		} else if (what == "input_ports") {
+			((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->set_inputs(p_value);
+			return true;
+		} else if (what == "output_ports") {
+			((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->set_outputs(p_value);
+			return true;
+		} else if (what == "expression") {
+			((VisualShaderNodeExpression *)get_node(type, id).ptr())->set_expression(p_value);
+			return true;
 		}
 	}
 	return false;
@@ -668,6 +743,18 @@ bool VisualShader::_get(const StringName &p_name, Variant &r_ret) const {
 		} else if (what == "position") {
 			r_ret = get_node_position(type, id);
 			return true;
+		} else if (what == "size") {
+			r_ret = ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->get_size();
+			return true;
+		} else if (what == "input_ports") {
+			r_ret = ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->get_inputs();
+			return true;
+		} else if (what == "output_ports") {
+			r_ret = ((VisualShaderNodeGroupBase *)get_node(type, id).ptr())->get_outputs();
+			return true;
+		} else if (what == "expression") {
+			r_ret = ((VisualShaderNodeExpression *)get_node(type, id).ptr())->get_expression();
+			return true;
 		}
 	}
 	return false;
@@ -727,6 +814,15 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
 				p_list->push_back(PropertyInfo(Variant::OBJECT, prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_DO_NOT_SHARE_ON_DUPLICATE));
 			}
 			p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+
+			if (Object::cast_to<VisualShaderNodeGroupBase>(E->get().node.ptr()) != NULL) {
+				p_list->push_back(PropertyInfo(Variant::VECTOR2, prop_name + "/size", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+				p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/input_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+				p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+			}
+			if (Object::cast_to<VisualShaderNodeExpression>(E->get().node.ptr()) != NULL) {
+				p_list->push_back(PropertyInfo(Variant::STRING, prop_name + "/expression", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
+			}
 		}
 		p_list->push_back(PropertyInfo(Variant::POOL_INT_ARRAY, "nodes/" + String(type_string[i]) + "/connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR));
 	}
@@ -993,26 +1089,33 @@ void VisualShader::_input_type_changed(Type p_type, int p_id) {
 	}
 }
 
+void VisualShader::rebuild() {
+	dirty = true;
+	_update_shader();
+}
+
 void VisualShader::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("set_mode", "mode"), &VisualShader::set_mode);
 
 	ClassDB::bind_method(D_METHOD("add_node", "type", "node", "position", "id"), &VisualShader::add_node);
-	ClassDB::bind_method(D_METHOD("set_node_position", "type", "id", "position"), &VisualShader::set_node_position);
-
 	ClassDB::bind_method(D_METHOD("get_node", "type", "id"), &VisualShader::get_node);
+
+	ClassDB::bind_method(D_METHOD("set_node_position", "type", "id", "position"), &VisualShader::set_node_position);
 	ClassDB::bind_method(D_METHOD("get_node_position", "type", "id"), &VisualShader::get_node_position);
 
 	ClassDB::bind_method(D_METHOD("get_node_list", "type"), &VisualShader::get_node_list);
 	ClassDB::bind_method(D_METHOD("get_valid_node_id", "type"), &VisualShader::get_valid_node_id);
 
 	ClassDB::bind_method(D_METHOD("remove_node", "type", "id"), &VisualShader::remove_node);
+	ClassDB::bind_method(D_METHOD("rebuild"), &VisualShader::rebuild);
 
 	ClassDB::bind_method(D_METHOD("is_node_connection", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::is_node_connection);
 	ClassDB::bind_method(D_METHOD("can_connect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::is_node_connection);
 
 	ClassDB::bind_method(D_METHOD("connect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::connect_nodes);
 	ClassDB::bind_method(D_METHOD("disconnect_nodes", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::disconnect_nodes);
+	ClassDB::bind_method(D_METHOD("connect_nodes_forced", "type", "from_node", "from_port", "to_node", "to_port"), &VisualShader::connect_nodes_forced);
 
 	ClassDB::bind_method(D_METHOD("get_node_connections", "type"), &VisualShader::_get_node_connections);
 
@@ -1627,3 +1730,553 @@ void VisualShaderNodeUniform::_bind_methods() {
 
 VisualShaderNodeUniform::VisualShaderNodeUniform() {
 }
+
+////////////// GroupBase
+
+String VisualShaderNodeGroupBase::get_caption() const {
+	return "Group";
+}
+
+void VisualShaderNodeGroupBase::set_size(const Vector2 &p_size) {
+	size = p_size;
+}
+
+Vector2 VisualShaderNodeGroupBase::get_size() const {
+	return size;
+}
+
+void VisualShaderNodeGroupBase::set_inputs(const String &p_inputs) {
+
+	if (inputs == p_inputs)
+		return;
+
+	clear_input_ports();
+
+	inputs = p_inputs;
+
+	Vector<String> input_strings = inputs.split(";", false);
+
+	int input_port_count = input_strings.size();
+
+	for (int i = 0; i < input_port_count; i++) {
+
+		Vector<String> arr = input_strings[i].split(",");
+
+		int port_idx = arr[0].to_int();
+		int port_type = arr[1].to_int();
+		String port_name = arr[2];
+
+		Port port;
+		port.type = (PortType)port_type;
+		port.name = port_name;
+		input_ports[port_idx] = port;
+	}
+}
+
+String VisualShaderNodeGroupBase::get_inputs() const {
+	return inputs;
+}
+
+void VisualShaderNodeGroupBase::set_outputs(const String &p_outputs) {
+
+	if (outputs == p_outputs)
+		return;
+
+	clear_output_ports();
+
+	outputs = p_outputs;
+
+	Vector<String> output_strings = outputs.split(";", false);
+
+	int output_port_count = output_strings.size();
+
+	for (int i = 0; i < output_port_count; i++) {
+
+		Vector<String> arr = output_strings[i].split(",");
+
+		int port_idx = arr[0].to_int();
+		int port_type = arr[1].to_int();
+		String port_name = arr[2];
+
+		Port port;
+		port.type = (PortType)port_type;
+		port.name = port_name;
+		output_ports[port_idx] = port;
+	}
+}
+
+String VisualShaderNodeGroupBase::get_outputs() const {
+	return outputs;
+}
+
+void VisualShaderNodeGroupBase::add_input_port(int p_id, int p_type, const String &p_name) {
+
+	String str = itos(p_id) + "," + itos(p_type) + "," + p_name + ";";
+	Vector<String> inputs_strings = inputs.split(";", false);
+	int index = 0;
+	if (p_id < inputs_strings.size()) {
+		for (int i = 0; i < inputs_strings.size(); i++) {
+			if (i == p_id) {
+				inputs = inputs.insert(index, str);
+				break;
+			}
+			index += inputs_strings[i].size();
+		}
+	} else {
+		inputs += str;
+	}
+
+	inputs_strings = inputs.split(";", false);
+	index = 0;
+
+	for (int i = 0; i < inputs_strings.size(); i++) {
+		int count = 0;
+		for (int j = 0; j < inputs_strings[i].size(); j++) {
+			if (inputs_strings[i][j] == ',') {
+				break;
+			}
+			count++;
+		}
+
+		inputs.erase(index, count);
+		inputs = inputs.insert(index, itos(i));
+		index += inputs_strings[i].size();
+	}
+
+	_apply_port_changes();
+}
+
+void VisualShaderNodeGroupBase::remove_input_port(int p_id) {
+
+	Vector<String> inputs_strings = inputs.split(";", false);
+	int count = 0;
+	int index = 0;
+	for (int i = 0; i < inputs_strings.size(); i++) {
+		Vector<String> arr = inputs_strings[i].split(",");
+		if (arr[0].to_int() == p_id) {
+			count = inputs_strings[i].size();
+			break;
+		}
+		index += inputs_strings[i].size();
+	}
+	inputs.erase(index, count);
+
+	inputs_strings = inputs.split(";", false);
+	for (int i = p_id; i < inputs_strings.size(); i++) {
+		inputs = inputs.replace_first(inputs_strings[i].split(",")[0], itos(i));
+	}
+
+	_apply_port_changes();
+}
+
+int VisualShaderNodeGroupBase::get_input_port_count() const {
+	return input_ports.size();
+}
+
+bool VisualShaderNodeGroupBase::has_input_port(int p_id) const {
+	return input_ports.has(p_id);
+}
+
+void VisualShaderNodeGroupBase::add_output_port(int p_id, int p_type, const String &p_name) {
+
+	String str = itos(p_id) + "," + itos(p_type) + "," + p_name + ";";
+	Vector<String> outputs_strings = outputs.split(";", false);
+	int index = 0;
+	if (p_id < outputs_strings.size()) {
+		for (int i = 0; i < outputs_strings.size(); i++) {
+			if (i == p_id) {
+				outputs = outputs.insert(index, str);
+				break;
+			}
+			index += outputs_strings[i].size();
+		}
+	} else {
+		outputs += str;
+	}
+
+	outputs_strings = outputs.split(";", false);
+	index = 0;
+
+	for (int i = 0; i < outputs_strings.size(); i++) {
+		int count = 0;
+		for (int j = 0; j < outputs_strings[i].size(); j++) {
+			if (outputs_strings[i][j] == ',') {
+				break;
+			}
+			count++;
+		}
+
+		outputs.erase(index, count);
+		outputs = outputs.insert(index, itos(i));
+		index += outputs_strings[i].size();
+	}
+
+	_apply_port_changes();
+}
+
+void VisualShaderNodeGroupBase::remove_output_port(int p_id) {
+
+	Vector<String> outputs_strings = outputs.split(";", false);
+	int count = 0;
+	int index = 0;
+	for (int i = 0; i < outputs_strings.size(); i++) {
+		Vector<String> arr = outputs_strings[i].split(",");
+		if (arr[0].to_int() == p_id) {
+			count = outputs_strings[i].size();
+			break;
+		}
+		index += outputs_strings[i].size();
+	}
+	outputs.erase(index, count);
+
+	outputs_strings = outputs.split(";", false);
+	for (int i = p_id; i < outputs_strings.size(); i++) {
+		outputs = outputs.replace_first(outputs_strings[i].split(",")[0], itos(i));
+	}
+
+	_apply_port_changes();
+}
+
+int VisualShaderNodeGroupBase::get_output_port_count() const {
+	return output_ports.size();
+}
+
+bool VisualShaderNodeGroupBase::has_output_port(int p_id) const {
+	return output_ports.has(p_id);
+}
+
+void VisualShaderNodeGroupBase::clear_input_ports() {
+	input_ports.clear();
+}
+
+void VisualShaderNodeGroupBase::clear_output_ports() {
+	output_ports.clear();
+}
+
+void VisualShaderNodeGroupBase::set_input_port_type(int p_id, int p_type) {
+
+	if (input_ports[p_id].type == p_type)
+		return;
+
+	Vector<String> inputs_strings = inputs.split(";", false);
+	int count = 0;
+	int index = 0;
+	for (int i = 0; i < inputs_strings.size(); i++) {
+		Vector<String> arr = inputs_strings[i].split(",");
+		if (arr[0].to_int() == p_id) {
+			index += arr[0].size();
+			count = arr[1].size() - 1;
+			break;
+		}
+		index += inputs_strings[i].size();
+	}
+
+	inputs.erase(index, count);
+
+	inputs = inputs.insert(index, itos(p_type));
+
+	_apply_port_changes();
+}
+
+VisualShaderNodeGroupBase::PortType VisualShaderNodeGroupBase::get_input_port_type(int p_id) const {
+	ERR_FAIL_COND_V(!input_ports.has(p_id), (PortType)0);
+	return input_ports[p_id].type;
+}
+
+void VisualShaderNodeGroupBase::set_input_port_name(int p_id, const String &p_name) {
+
+	if (input_ports[p_id].name == p_name)
+		return;
+
+	Vector<String> inputs_strings = inputs.split(";", false);
+	int count = 0;
+	int index = 0;
+	for (int i = 0; i < inputs_strings.size(); i++) {
+		Vector<String> arr = inputs_strings[i].split(",");
+		if (arr[0].to_int() == p_id) {
+			index += arr[0].size() + arr[1].size();
+			count = arr[2].size() - 1;
+			break;
+		}
+		index += inputs_strings[i].size();
+	}
+
+	inputs.erase(index, count);
+
+	inputs = inputs.insert(index, p_name);
+
+	_apply_port_changes();
+}
+
+String VisualShaderNodeGroupBase::get_input_port_name(int p_id) const {
+	ERR_FAIL_COND_V(!input_ports.has(p_id), "");
+	return input_ports[p_id].name;
+}
+
+void VisualShaderNodeGroupBase::set_output_port_type(int p_id, int p_type) {
+
+	if (output_ports[p_id].type == p_type)
+		return;
+
+	Vector<String> output_strings = outputs.split(";", false);
+	int count = 0;
+	int index = 0;
+	for (int i = 0; i < output_strings.size(); i++) {
+		Vector<String> arr = output_strings[i].split(",");
+		if (arr[0].to_int() == p_id) {
+			index += arr[0].size();
+			count = arr[1].size() - 1;
+			break;
+		}
+		index += output_strings[i].size();
+	}
+
+	outputs.erase(index, count);
+
+	outputs = outputs.insert(index, itos(p_type));
+
+	_apply_port_changes();
+}
+
+VisualShaderNodeGroupBase::PortType VisualShaderNodeGroupBase::get_output_port_type(int p_id) const {
+	ERR_FAIL_COND_V(!output_ports.has(p_id), (PortType)0);
+	return output_ports[p_id].type;
+}
+
+void VisualShaderNodeGroupBase::set_output_port_name(int p_id, const String &p_name) {
+	if (output_ports[p_id].name == p_name)
+		return;
+
+	Vector<String> output_strings = outputs.split(";", false);
+	int count = 0;
+	int index = 0;
+	for (int i = 0; i < output_strings.size(); i++) {
+		Vector<String> arr = output_strings[i].split(",");
+		if (arr[0].to_int() == p_id) {
+			index += arr[0].size() + arr[1].size();
+			count = arr[2].size() - 1;
+			break;
+		}
+		index += output_strings[i].size();
+	}
+
+	outputs.erase(index, count);
+
+	outputs = outputs.insert(index, p_name);
+
+	_apply_port_changes();
+}
+
+String VisualShaderNodeGroupBase::get_output_port_name(int p_id) const {
+	ERR_FAIL_COND_V(!output_ports.has(p_id), "");
+	return output_ports[p_id].name;
+}
+
+int VisualShaderNodeGroupBase::get_free_input_port_id() const {
+	return input_ports.size();
+}
+
+int VisualShaderNodeGroupBase::get_free_output_port_id() const {
+	return output_ports.size();
+}
+
+void VisualShaderNodeGroupBase::set_control(Control *p_control, int p_index) {
+	controls[p_index] = p_control;
+}
+
+Control *VisualShaderNodeGroupBase::get_control(int p_index) {
+	ERR_FAIL_COND_V(!controls.has(p_index), NULL);
+	return controls[p_index];
+}
+
+void VisualShaderNodeGroupBase::_apply_port_changes() {
+
+	Vector<String> inputs_strings = inputs.split(";", false);
+	Vector<String> outputs_strings = outputs.split(";", false);
+
+	clear_input_ports();
+	clear_output_ports();
+
+	for (int i = 0; i < inputs_strings.size(); i++) {
+		Vector<String> arr = inputs_strings[i].split(",");
+		Port port;
+		port.type = (PortType)arr[1].to_int();
+		port.name = arr[2];
+		input_ports[i] = port;
+	}
+	for (int i = 0; i < outputs_strings.size(); i++) {
+		Vector<String> arr = outputs_strings[i].split(",");
+		Port port;
+		port.type = (PortType)arr[1].to_int();
+		port.name = arr[2];
+		output_ports[i] = port;
+	}
+}
+
+void VisualShaderNodeGroupBase::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_size", "size"), &VisualShaderNodeGroupBase::set_size);
+	ClassDB::bind_method(D_METHOD("get_size"), &VisualShaderNodeGroupBase::get_size);
+
+	ClassDB::bind_method(D_METHOD("set_inputs", "inputs"), &VisualShaderNodeGroupBase::set_inputs);
+	ClassDB::bind_method(D_METHOD("get_inputs"), &VisualShaderNodeGroupBase::get_inputs);
+
+	ClassDB::bind_method(D_METHOD("set_outputs", "outputs"), &VisualShaderNodeGroupBase::set_outputs);
+	ClassDB::bind_method(D_METHOD("get_outputs"), &VisualShaderNodeGroupBase::get_outputs);
+
+	ClassDB::bind_method(D_METHOD("add_input_port", "id", "type", "name"), &VisualShaderNodeGroupBase::add_input_port);
+	ClassDB::bind_method(D_METHOD("remove_input_port", "id"), &VisualShaderNodeGroupBase::remove_input_port);
+	ClassDB::bind_method(D_METHOD("get_input_port_count"), &VisualShaderNodeGroupBase::get_input_port_count);
+	ClassDB::bind_method(D_METHOD("has_input_port", "id"), &VisualShaderNodeGroupBase::has_input_port);
+	ClassDB::bind_method(D_METHOD("clear_input_ports"), &VisualShaderNodeGroupBase::clear_input_ports);
+
+	ClassDB::bind_method(D_METHOD("add_output_port", "id", "type", "name"), &VisualShaderNodeGroupBase::add_output_port);
+	ClassDB::bind_method(D_METHOD("remove_output_port", "id"), &VisualShaderNodeGroupBase::remove_output_port);
+	ClassDB::bind_method(D_METHOD("get_output_port_count"), &VisualShaderNodeGroupBase::get_output_port_count);
+	ClassDB::bind_method(D_METHOD("has_output_port", "id"), &VisualShaderNodeGroupBase::has_output_port);
+	ClassDB::bind_method(D_METHOD("clear_output_ports"), &VisualShaderNodeGroupBase::clear_output_ports);
+
+	ClassDB::bind_method(D_METHOD("set_input_port_name"), &VisualShaderNodeGroupBase::set_input_port_name);
+	ClassDB::bind_method(D_METHOD("set_input_port_type"), &VisualShaderNodeGroupBase::set_input_port_type);
+	ClassDB::bind_method(D_METHOD("set_output_port_name"), &VisualShaderNodeGroupBase::set_output_port_name);
+	ClassDB::bind_method(D_METHOD("set_output_port_type"), &VisualShaderNodeGroupBase::set_output_port_type);
+
+	ClassDB::bind_method(D_METHOD("get_free_input_port_id"), &VisualShaderNodeGroupBase::get_free_input_port_id);
+	ClassDB::bind_method(D_METHOD("get_free_output_port_id"), &VisualShaderNodeGroupBase::get_free_output_port_id);
+
+	ClassDB::bind_method(D_METHOD("set_control", "control", "index"), &VisualShaderNodeGroupBase::set_control);
+	ClassDB::bind_method(D_METHOD("get_control", "index"), &VisualShaderNodeGroupBase::get_control);
+}
+
+String VisualShaderNodeGroupBase::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+	return "";
+}
+
+VisualShaderNodeGroupBase::VisualShaderNodeGroupBase() {
+	size = Size2(0, 0);
+	inputs = "";
+	outputs = "";
+}
+
+////////////// Expression
+
+String VisualShaderNodeExpression::get_caption() const {
+	return "Expression";
+}
+
+void VisualShaderNodeExpression::set_expression(const String &p_expression) {
+	expression = p_expression;
+}
+
+void VisualShaderNodeExpression::build() {
+	emit_changed();
+}
+
+String VisualShaderNodeExpression::get_expression() const {
+	return expression;
+}
+
+String VisualShaderNodeExpression::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+
+	String _expression = expression;
+
+	_expression = _expression.insert(0, "\n");
+	_expression = _expression.replace("\n", "\n\t\t");
+
+	static Vector<String> pre_symbols;
+	if (pre_symbols.empty()) {
+		pre_symbols.push_back("\t");
+		pre_symbols.push_back("{");
+		pre_symbols.push_back("[");
+		pre_symbols.push_back("(");
+		pre_symbols.push_back(" ");
+		pre_symbols.push_back("-");
+		pre_symbols.push_back("*");
+		pre_symbols.push_back("/");
+		pre_symbols.push_back("+");
+		pre_symbols.push_back("=");
+		pre_symbols.push_back("&");
+		pre_symbols.push_back("|");
+		pre_symbols.push_back("!");
+	}
+
+	static Vector<String> post_symbols;
+	if (post_symbols.empty()) {
+		post_symbols.push_back("\t");
+		post_symbols.push_back("\n");
+		post_symbols.push_back("}");
+		post_symbols.push_back("]");
+		post_symbols.push_back(")");
+		post_symbols.push_back(" ");
+		post_symbols.push_back(".");
+		post_symbols.push_back("-");
+		post_symbols.push_back("*");
+		post_symbols.push_back("/");
+		post_symbols.push_back("+");
+		post_symbols.push_back("=");
+		post_symbols.push_back("&");
+		post_symbols.push_back("|");
+		post_symbols.push_back("!");
+	}
+
+	for (int i = 0; i < get_input_port_count(); i++) {
+		for (int j = 0; j < pre_symbols.size(); j++) {
+			for (int k = 0; k < post_symbols.size(); k++) {
+				_expression = _expression.replace(pre_symbols[j] + get_input_port_name(i) + post_symbols[k], pre_symbols[j] + p_input_vars[i] + post_symbols[k]);
+			}
+		}
+	}
+	for (int i = 0; i < get_output_port_count(); i++) {
+		for (int j = 0; j < pre_symbols.size(); j++) {
+			for (int k = 0; k < post_symbols.size(); k++) {
+				_expression = _expression.replace(pre_symbols[j] + get_output_port_name(i) + post_symbols[k], pre_symbols[j] + p_output_vars[i] + post_symbols[k]);
+			}
+		}
+	}
+
+	String output_initializer;
+
+	for (int i = 0; i < get_output_port_count(); i++) {
+		int port_type = get_output_port_type(i);
+		String tk = "";
+		switch (port_type) {
+			case PORT_TYPE_SCALAR:
+				tk = "0.0";
+				break;
+			case PORT_TYPE_VECTOR:
+				tk = "vec3(0.0, 0.0, 0.0)";
+				break;
+			case PORT_TYPE_BOOLEAN:
+				tk = "false";
+				break;
+			case PORT_TYPE_TRANSFORM:
+				tk = "mat4(1.0)";
+				break;
+			default:
+				continue;
+		}
+		output_initializer += "\t" + p_output_vars[i] + "=" + tk + ";\n";
+	}
+
+	String code;
+	code += output_initializer;
+	code += "\t{";
+	code += _expression;
+	code += "\n\t}";
+
+	return code;
+}
+
+void VisualShaderNodeExpression::_bind_methods() {
+
+	ClassDB::bind_method(D_METHOD("set_expression", "expression"), &VisualShaderNodeExpression::set_expression);
+	ClassDB::bind_method(D_METHOD("get_expression"), &VisualShaderNodeExpression::get_expression);
+
+	ClassDB::bind_method(D_METHOD("build"), &VisualShaderNodeExpression::build);
+
+	ADD_PROPERTY(PropertyInfo(Variant::STRING, "expression"), "set_expression", "get_expression");
+}
+
+VisualShaderNodeExpression::VisualShaderNodeExpression() {
+	expression = "";
+}

+ 92 - 0
scene/resources/visual_shader.h

@@ -135,7 +135,9 @@ public:
 	bool can_connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const;
 	Error connect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port);
 	void disconnect_nodes(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port);
+	void connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port);
 
+	void rebuild();
 	void get_node_connections(Type p_type, List<Connection> *r_connections) const;
 
 	void set_mode(Mode p_mode);
@@ -148,6 +150,7 @@ public:
 
 	String generate_preview_shader(Type p_type, int p_node, int p_port, Vector<DefaultTextureParam> &r_default_tex_params) const;
 
+	String validate_port_name(const String &p_name, const List<String> &p_input_ports, const List<String> &p_output_ports) const;
 	String validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const;
 
 	VisualShader();
@@ -314,4 +317,93 @@ public:
 	VisualShaderNodeUniform();
 };
 
+class VisualShaderNodeGroupBase : public VisualShaderNode {
+	GDCLASS(VisualShaderNodeGroupBase, VisualShaderNode)
+private:
+	void _apply_port_changes();
+
+protected:
+	Vector2 size;
+	String inputs;
+	String outputs;
+
+	struct Port {
+		PortType type;
+		String name;
+	};
+
+	Map<int, Port> input_ports;
+	Map<int, Port> output_ports;
+	Map<int, Control *> controls;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+
+	void set_size(const Vector2 &p_size);
+	Vector2 get_size() const;
+
+	void set_inputs(const String &p_inputs);
+	String get_inputs() const;
+
+	void set_outputs(const String &p_outputs);
+	String get_outputs() const;
+
+	void add_input_port(int p_id, int p_type, const String &p_name);
+	void remove_input_port(int p_id);
+	virtual int get_input_port_count() const;
+	bool has_input_port(int p_id) const;
+	void clear_input_ports();
+
+	void add_output_port(int p_id, int p_type, const String &p_name);
+	void remove_output_port(int p_id);
+	virtual int get_output_port_count() const;
+	bool has_output_port(int p_id) const;
+	void clear_output_ports();
+
+	void set_input_port_type(int p_id, int p_type);
+	virtual PortType get_input_port_type(int p_id) const;
+	void set_input_port_name(int p_id, const String &p_name);
+	virtual String get_input_port_name(int p_id) const;
+
+	void set_output_port_type(int p_id, int p_type);
+	virtual PortType get_output_port_type(int p_id) const;
+	void set_output_port_name(int p_id, const String &p_name);
+	virtual String get_output_port_name(int p_id) const;
+
+	int get_free_input_port_id() const;
+	int get_free_output_port_id() const;
+
+	void set_control(Control *p_control, int p_index);
+	Control *get_control(int p_index);
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const;
+
+	VisualShaderNodeGroupBase();
+};
+
+class VisualShaderNodeExpression : public VisualShaderNodeGroupBase {
+	GDCLASS(VisualShaderNodeExpression, VisualShaderNodeGroupBase)
+
+private:
+	String expression;
+
+protected:
+	static void _bind_methods();
+
+public:
+	virtual String get_caption() const;
+
+	void set_expression(const String &p_expression);
+	String get_expression() const;
+
+	void build();
+
+	virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const;
+
+	VisualShaderNodeExpression();
+};
+
 #endif // VISUAL_SHADER_H