/*************************************************************************/ /* visual_shader_editor_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "visual_shader_editor_plugin.h" #include "core/config/project_settings.h" #include "core/core_string_names.h" #include "core/input/input.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "core/os/keyboard.h" #include "editor/editor_log.h" #include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" #include "editor/plugins/shader_editor_plugin.h" #include "scene/animation/animation_player.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/view_panner.h" #include "scene/main/window.h" #include "scene/resources/visual_shader_nodes.h" #include "scene/resources/visual_shader_particle_nodes.h" #include "scene/resources/visual_shader_sdf_nodes.h" #include "servers/display_server.h" #include "servers/rendering/shader_types.h" struct FloatConstantDef { String name; float value = 0; String desc; }; static FloatConstantDef float_constant_defs[] = { { "E", Math_E, TTR("E constant (2.718282). Represents the base of the natural logarithm.") }, { "Epsilon", CMP_EPSILON, TTR("Epsilon constant (0.00001). Smallest possible scalar number.") }, { "Phi", 1.618034f, TTR("Phi constant (1.618034). Golden ratio.") }, { "Pi/4", Math_PI / 4, TTR("Pi/4 constant (0.785398) or 45 degrees.") }, { "Pi/2", Math_PI / 2, TTR("Pi/2 constant (1.570796) or 90 degrees.") }, { "Pi", Math_PI, TTR("Pi constant (3.141593) or 180 degrees.") }, { "Tau", Math_TAU, TTR("Tau constant (6.283185) or 360 degrees.") }, { "Sqrt2", Math_SQRT2, TTR("Sqrt2 constant (1.414214). Square root of 2.") } }; const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConstantDef); /////////////////// void VisualShaderNodePlugin::set_editor(VisualShaderEditor *p_editor) { vseditor = p_editor; } Control *VisualShaderNodePlugin::create_editor(const Ref &p_parent_resource, const Ref &p_node) { Object *ret; if (GDVIRTUAL_CALL(_create_editor, p_parent_resource, p_node, ret)) { return Object::cast_to(ret); } return nullptr; } void VisualShaderNodePlugin::_bind_methods() { GDVIRTUAL_BIND(_create_editor, "parent_resource", "visual_shader_node"); } /////////////////// static Ref make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { Ref style(memnew(StyleBoxEmpty)); style->set_default_margin(SIDE_LEFT, p_margin_left * EDSCALE); style->set_default_margin(SIDE_RIGHT, p_margin_right * EDSCALE); style->set_default_margin(SIDE_BOTTOM, p_margin_bottom * EDSCALE); style->set_default_margin(SIDE_TOP, p_margin_top * EDSCALE); return style; } /////////////////// VisualShaderGraphPlugin::VisualShaderGraphPlugin() { } void VisualShaderGraphPlugin::_bind_methods() { ClassDB::bind_method("add_node", &VisualShaderGraphPlugin::add_node); ClassDB::bind_method("remove_node", &VisualShaderGraphPlugin::remove_node); ClassDB::bind_method("connect_nodes", &VisualShaderGraphPlugin::connect_nodes); ClassDB::bind_method("disconnect_nodes", &VisualShaderGraphPlugin::disconnect_nodes); ClassDB::bind_method("set_node_position", &VisualShaderGraphPlugin::set_node_position); ClassDB::bind_method("update_node", &VisualShaderGraphPlugin::update_node); ClassDB::bind_method("update_node_deferred", &VisualShaderGraphPlugin::update_node_deferred); ClassDB::bind_method("set_input_port_default_value", &VisualShaderGraphPlugin::set_input_port_default_value); ClassDB::bind_method("set_uniform_name", &VisualShaderGraphPlugin::set_uniform_name); ClassDB::bind_method("set_expression", &VisualShaderGraphPlugin::set_expression); ClassDB::bind_method("update_curve", &VisualShaderGraphPlugin::update_curve); ClassDB::bind_method("update_curve_xyz", &VisualShaderGraphPlugin::update_curve_xyz); } void VisualShaderGraphPlugin::set_editor(VisualShaderEditor *p_editor) { editor = p_editor; } void VisualShaderGraphPlugin::register_shader(VisualShader *p_shader) { visual_shader = Ref(p_shader); } void VisualShaderGraphPlugin::set_connections(const List &p_connections) { connections = p_connections; } void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { for (const KeyValue &E : links[p_node_id].output_ports) { if (E.value.preview_button != nullptr) { E.value.preview_button->set_pressed(false); } } if (links[p_node_id].preview_visible && !is_dirty() && links[p_node_id].preview_box != nullptr) { links[p_node_id].graph_node->remove_child(links[p_node_id].preview_box); memdelete(links[p_node_id].preview_box); links[p_node_id].graph_node->reset_size(); links[p_node_id].preview_visible = false; } if (p_port_id != -1 && links[p_node_id].output_ports[p_port_id].preview_button != nullptr) { if (is_dirty()) { links[p_node_id].preview_pos = links[p_node_id].graph_node->get_child_count(); } VBoxContainer *vbox = memnew(VBoxContainer); links[p_node_id].graph_node->add_child(vbox); if (links[p_node_id].preview_pos != -1) { links[p_node_id].graph_node->move_child(vbox, links[p_node_id].preview_pos); } links[p_node_id].graph_node->set_slot_draw_stylebox(vbox->get_index(), false); Control *offset = memnew(Control); offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); vbox->add_child(offset); VisualShaderNodePortPreview *port_preview = memnew(VisualShaderNodePortPreview); port_preview->setup(visual_shader, visual_shader->get_shader_type(), p_node_id, p_port_id); port_preview->set_h_size_flags(Control::SIZE_SHRINK_CENTER); vbox->add_child(port_preview); links[p_node_id].preview_visible = true; links[p_node_id].preview_box = vbox; links[p_node_id].output_ports[p_port_id].preview_button->set_pressed(true); } } } void VisualShaderGraphPlugin::update_node_deferred(VisualShader::Type p_type, int p_node_id) { call_deferred(SNAME("update_node"), p_type, p_node_id); } void VisualShaderGraphPlugin::update_node(VisualShader::Type p_type, int p_node_id) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id)) { return; } remove_node(p_type, p_node_id); add_node(p_type, p_node_id); } void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, Variant p_value) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id)) { return; } Button *button = links[p_node_id].input_ports[p_port_id].default_input_button; switch (p_value.get_type()) { case Variant::COLOR: { button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); Callable ce = callable_mp(editor, &VisualShaderEditor::_draw_color_over_button); if (!button->is_connected("draw", ce)) { button->connect("draw", ce.bind(button, p_value)); } } break; case Variant::BOOL: { button->set_text(((bool)p_value) ? "true" : "false"); } break; case Variant::INT: case Variant::FLOAT: { button->set_text(String::num(p_value, 4)); } break; case Variant::VECTOR2: { Vector2 v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3)); } break; case Variant::VECTOR3: { Vector3 v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3)); } break; case Variant::QUATERNION: { Quaternion v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3) + "," + String::num(v.w, 3)); } break; default: { } } } void VisualShaderGraphPlugin::set_uniform_name(VisualShader::Type p_type, int p_node_id, const String &p_name) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].uniform_name != nullptr) { links[p_node_id].uniform_name->set_text(p_name); } } void VisualShaderGraphPlugin::update_curve(int p_node_id) { if (links.has(p_node_id) && links[p_node_id].curve_editors[0]) { Ref tex = Object::cast_to(links[p_node_id].visual_node); ERR_FAIL_COND(!tex.is_valid()); if (tex->get_texture().is_valid()) { links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve()); } tex->emit_signal(CoreStringNames::get_singleton()->changed); } } void VisualShaderGraphPlugin::update_curve_xyz(int p_node_id) { if (links.has(p_node_id) && links[p_node_id].curve_editors[0] && links[p_node_id].curve_editors[1] && links[p_node_id].curve_editors[2]) { Ref tex = Object::cast_to(links[p_node_id].visual_node); ERR_FAIL_COND(!tex.is_valid()); if (tex->get_texture().is_valid()) { links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve_x()); links[p_node_id].curve_editors[1]->set_curve(tex->get_texture()->get_curve_y()); links[p_node_id].curve_editors[2]->set_curve(tex->get_texture()->get_curve_z()); } tex->emit_signal(CoreStringNames::get_singleton()->changed); } } int VisualShaderGraphPlugin::get_constant_index(float p_constant) const { for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { if (Math::is_equal_approx(p_constant, float_constant_defs[i].value)) { return i + 1; } } return 0; } void VisualShaderGraphPlugin::set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression) { if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id) || !links[p_node_id].expression_edit) { return; } links[p_node_id].expression_edit->set_text(p_expression); } void VisualShaderGraphPlugin::update_node_size(int p_node_id) { if (!links.has(p_node_id)) { return; } links[p_node_id].graph_node->reset_size(); } void VisualShaderGraphPlugin::register_default_input_button(int p_node_id, int p_port_id, Button *p_button) { links[p_node_id].input_ports.insert(p_port_id, { p_button }); } void VisualShaderGraphPlugin::register_expression_edit(int p_node_id, CodeEdit *p_expression_edit) { links[p_node_id].expression_edit = p_expression_edit; } void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, CurveEditor *p_curve_editor) { links[p_node_id].curve_editors[p_index] = p_curve_editor; } void VisualShaderGraphPlugin::update_uniform_refs() { for (KeyValue &E : links) { VisualShaderNodeUniformRef *ref = Object::cast_to(E.value.visual_node); if (ref) { remove_node(E.value.type, E.key); add_node(E.value.type, E.key); } } } VisualShader::Type VisualShaderGraphPlugin::get_shader_type() const { return visual_shader->get_shader_type(); } void VisualShaderGraphPlugin::set_node_position(VisualShader::Type p_type, int p_id, const Vector2 &p_position) { if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { links[p_id].graph_node->set_position_offset(p_position); } } bool VisualShaderGraphPlugin::is_preview_visible(int p_id) const { return links[p_id].preview_visible; } void VisualShaderGraphPlugin::clear_links() { links.clear(); } bool VisualShaderGraphPlugin::is_dirty() const { return dirty; } void VisualShaderGraphPlugin::make_dirty(bool p_enabled) { dirty = p_enabled; } void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node) { links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, HashMap(), HashMap(), nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } }); } void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) { links[p_node_id].output_ports.insert(p_port, { p_button }); } void VisualShaderGraphPlugin::register_uniform_name(int p_node_id, LineEdit *p_uniform_name) { links[p_node_id].uniform_name = p_uniform_name; } void VisualShaderGraphPlugin::update_theme() { vector_expanded_color[0] = editor->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); // red vector_expanded_color[1] = editor->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); // green vector_expanded_color[2] = editor->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); // blue vector_expanded_color[3] = editor->get_theme_color(SNAME("axis_w_color"), SNAME("Editor")); // alpha } void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (!visual_shader.is_valid() || p_type != visual_shader->get_shader_type()) { return; } GraphEdit *graph = editor->graph; if (!graph) { return; } VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); if (!graph_plugin) { return; } Shader::Mode mode = visual_shader->get_mode(); Control *offset; static Ref label_style = make_empty_stylebox(2, 1, 2, 1); static const Color type_color[] = { Color(0.38, 0.85, 0.96), // scalar (float) Color(0.49, 0.78, 0.94), // scalar (int) Color(0.74, 0.57, 0.95), // vector2 Color(0.84, 0.49, 0.93), // vector3 Color(1.0, 0.125, 0.95), // vector4 Color(0.55, 0.65, 0.94), // boolean Color(0.96, 0.66, 0.43), // transform Color(1.0, 1.0, 0.0), // sampler }; static const String vector_expanded_name[4] = { "red", "green", "blue", "alpha" }; // Visual shader specific theme for MSDF font. Ref vstheme; vstheme.instantiate(); Ref label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", "EditorFonts"); vstheme->set_font("font", "Label", label_font); vstheme->set_font("font", "LineEdit", label_font); vstheme->set_font("font", "Button", label_font); Ref vsnode = visual_shader->get_node(p_type, p_id); Ref resizable_node = Object::cast_to(vsnode.ptr()); bool is_resizable = !resizable_node.is_null(); Size2 size = Size2(0, 0); Ref group_node = Object::cast_to(vsnode.ptr()); bool is_group = !group_node.is_null(); bool is_comment = false; Ref expression_node = Object::cast_to(group_node.ptr()); bool is_expression = !expression_node.is_null(); String expression = ""; VisualShaderNodeCustom *custom_node = Object::cast_to(vsnode.ptr()); if (custom_node) { custom_node->_set_initialized(true); } // Create graph node. GraphNode *node = memnew(GraphNode); graph->add_child(node); node->set_theme(vstheme); editor->_update_created_node(node); register_link(p_type, p_id, vsnode.ptr(), node); if (is_resizable) { size = resizable_node->get_size(); node->set_resizable(true); node->connect("resize_request", callable_mp(editor, &VisualShaderEditor::_node_resized).bind((int)p_type, p_id)); } if (is_expression) { expression = expression_node->get_expression(); } node->set_position_offset(visual_shader->get_node_position(p_type, p_id)); node->set_title(vsnode->get_caption()); node->set_name(itos(p_id)); if (p_id >= 2) { node->set_show_close_button(true); node->connect("close_request", callable_mp(editor, &VisualShaderEditor::_delete_node_request).bind(p_type, p_id), CONNECT_DEFERRED); } node->connect("dragged", callable_mp(editor, &VisualShaderEditor::_node_dragged).bind(p_id)); Control *custom_editor = nullptr; int port_offset = 1; Control *content_offset = memnew(Control); content_offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); node->add_child(content_offset); if (is_group) { port_offset += 1; } if (is_resizable) { Ref comment_node = Object::cast_to(vsnode.ptr()); if (comment_node.is_valid()) { is_comment = true; node->set_comment(true); Label *comment_label = memnew(Label); node->add_child(comment_label); comment_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); comment_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); comment_label->set_text(comment_node->get_description()); } editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } Ref emit = vsnode; if (emit.is_valid()) { node->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); } Ref uniform_ref = vsnode; if (uniform_ref.is_valid()) { uniform_ref->set_shader_rid(visual_shader->get_rid()); uniform_ref->update_uniform_type(); } Ref uniform = vsnode; HBoxContainer *hb = nullptr; if (uniform.is_valid()) { LineEdit *uniform_name = memnew(LineEdit); register_uniform_name(p_id, uniform_name); uniform_name->set_h_size_flags(Control::SIZE_EXPAND_FILL); uniform_name->set_text(uniform->get_uniform_name()); uniform_name->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_uniform_line_edit_changed).bind(p_id)); uniform_name->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_uniform_line_edit_focus_out).bind(uniform_name, p_id)); if (vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { hb = memnew(HBoxContainer); hb->add_child(uniform_name); node->add_child(hb); } else { node->add_child(uniform_name); } port_offset++; } for (int i = 0; i < editor->plugins.size(); i++) { vsnode->set_meta("id", p_id); vsnode->set_meta("shader_type", (int)p_type); custom_editor = editor->plugins.write[i]->create_editor(visual_shader, vsnode); vsnode->remove_meta("id"); vsnode->remove_meta("shader_type"); if (custom_editor) { if (vsnode->is_show_prop_names()) { custom_editor->call_deferred(SNAME("_show_prop_names"), true); } break; } } Ref curve = vsnode; Ref curve_xyz = vsnode; bool is_curve = curve.is_valid() || curve_xyz.is_valid(); if (is_curve) { hb = memnew(HBoxContainer); node->add_child(hb); } if (curve.is_valid()) { custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve); if (curve->get_texture().is_valid() && !curve->get_texture()->is_connected("changed", ce)) { curve->get_texture()->connect("changed", ce.bind(p_id)); } CurveEditor *curve_editor = memnew(CurveEditor); node->add_child(curve_editor); register_curve_editor(p_id, 0, curve_editor); curve_editor->set_custom_minimum_size(Size2(300, 0)); curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); if (curve->get_texture().is_valid()) { curve_editor->set_curve(curve->get_texture()->get_curve()); } } if (curve_xyz.is_valid()) { custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve_xyz); if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", ce)) { curve_xyz->get_texture()->connect("changed", ce.bind(p_id)); } CurveEditor *curve_editor_x = memnew(CurveEditor); node->add_child(curve_editor_x); register_curve_editor(p_id, 0, curve_editor_x); curve_editor_x->set_custom_minimum_size(Size2(300, 0)); curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); if (curve_xyz->get_texture().is_valid()) { curve_editor_x->set_curve(curve_xyz->get_texture()->get_curve_x()); } CurveEditor *curve_editor_y = memnew(CurveEditor); node->add_child(curve_editor_y); register_curve_editor(p_id, 1, curve_editor_y); curve_editor_y->set_custom_minimum_size(Size2(300, 0)); curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); if (curve_xyz->get_texture().is_valid()) { curve_editor_y->set_curve(curve_xyz->get_texture()->get_curve_y()); } CurveEditor *curve_editor_z = memnew(CurveEditor); node->add_child(curve_editor_z); register_curve_editor(p_id, 2, curve_editor_z); curve_editor_z->set_custom_minimum_size(Size2(300, 0)); curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL); if (curve_xyz->get_texture().is_valid()) { curve_editor_z->set_curve(curve_xyz->get_texture()->get_curve_z()); } } if (custom_editor) { if (is_curve || (hb == nullptr && !vsnode->is_use_prop_slots() && (vsnode->get_output_port_count() == 0 || vsnode->get_output_port_name(0) == "") && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == ""))) { //will be embedded in first port } else { port_offset++; node->add_child(custom_editor); custom_editor = nullptr; } } if (is_group) { if (group_node->is_editable()) { HBoxContainer *hb2 = memnew(HBoxContainer); String input_port_name = "input" + itos(group_node->get_free_input_port_id()); String output_port_name = "output" + itos(group_node->get_free_output_port_id()); for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) { if (i < vsnode->get_input_port_count()) { if (input_port_name == vsnode->get_input_port_name(i)) { input_port_name = "_" + input_port_name; } } if (i < vsnode->get_output_port_count()) { if (output_port_name == vsnode->get_output_port_name(i)) { output_port_name = "_" + output_port_name; } } } Button *add_input_btn = memnew(Button); add_input_btn->set_text(TTR("Add Input")); add_input_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_add_input_port).bind(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, input_port_name), 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", callable_mp(editor, &VisualShaderEditor::_add_output_port).bind(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, output_port_name), CONNECT_DEFERRED); hb2->add_child(add_output_btn); node->add_child(hb2); } } int output_port_count = 0; for (int i = 0; i < vsnode->get_output_port_count(); i++) { if (vsnode->_is_output_port_expanded(i)) { switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_VECTOR_2D: { output_port_count += 2; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { output_port_count += 3; } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { output_port_count += 4; } break; default: break; } } output_port_count++; } int max_ports = MAX(vsnode->get_input_port_count(), output_port_count); VisualShaderNode::PortType expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; int expanded_port_counter = 0; for (int i = 0, j = 0; i < max_ports; i++, j++) { switch (expanded_type) { case VisualShaderNode::PORT_TYPE_VECTOR_2D: { if (expanded_port_counter >= 2) { expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; expanded_port_counter = 0; i -= 2; } } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { if (expanded_port_counter >= 3) { expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; expanded_port_counter = 0; i -= 3; } } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { if (expanded_port_counter >= 4) { expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; expanded_port_counter = 0; i -= 4; } } break; default: break; } if (vsnode->is_port_separator(i)) { node->add_child(memnew(HSeparator)); port_offset++; } bool valid_left = j < vsnode->get_input_port_count(); VisualShaderNode::PortType port_left = VisualShaderNode::PORT_TYPE_SCALAR; bool port_left_used = false; String name_left; if (valid_left) { name_left = vsnode->get_input_port_name(i); port_left = vsnode->get_input_port_type(i); for (const VisualShader::Connection &E : connections) { if (E.to_node == p_id && E.to_port == j) { port_left_used = true; break; } } } bool valid_right = true; VisualShaderNode::PortType port_right = VisualShaderNode::PORT_TYPE_SCALAR; String name_right; if (expanded_type == VisualShaderNode::PORT_TYPE_SCALAR) { valid_right = i < vsnode->get_output_port_count(); if (valid_right) { name_right = vsnode->get_output_port_name(i); port_right = vsnode->get_output_port_type(i); } } else { name_right = vector_expanded_name[expanded_port_counter++]; } bool is_first_hbox = false; if (i == 0 && hb != nullptr) { is_first_hbox = true; } else { hb = memnew(HBoxContainer); } hb->add_theme_constant_override("separation", 7 * EDSCALE); Variant default_value; if (valid_left && !port_left_used) { default_value = vsnode->get_input_port_default_value(i); } Button *button = memnew(Button); hb->add_child(button); register_default_input_button(p_id, i, button); button->connect("pressed", callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(button, p_id, i)); if (default_value.get_type() != Variant::NIL) { // only a label set_input_port_default_value(p_type, p_id, i, default_value); } else { button->hide(); } if (i == 0 && custom_editor) { hb->add_child(custom_editor); custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); } else { if (valid_left) { if (is_group) { OptionButton *type_box = memnew(OptionButton); hb->add_child(type_box); type_box->add_item(TTR("Float")); type_box->add_item(TTR("Int")); type_box->add_item(TTR("Vector2")); type_box->add_item(TTR("Vector3")); type_box->add_item(TTR("Vector4")); type_box->add_item(TTR("Boolean")); type_box->add_item(TTR("Transform")); type_box->add_item(TTR("Sampler")); 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", callable_mp(editor, &VisualShaderEditor::_change_input_port_type).bind(p_id, i), CONNECT_DEFERRED); LineEdit *name_box = memnew(LineEdit); hb->add_child(name_box); name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_left); name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_input_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, false), CONNECT_DEFERRED); Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); remove_btn->set_tooltip(TTR("Remove") + " " + name_left); remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_input_port).bind(p_id, i), CONNECT_DEFERRED); hb->add_child(remove_btn); } else { Label *label = memnew(Label); label->set_text(name_left); label->add_theme_style_override("normal", label_style); //more compact hb->add_child(label); if (vsnode->is_input_port_default(i, mode) && !port_left_used) { Label *hint_label = memnew(Label); hint_label->set_text(TTR("[default]")); hint_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); hint_label->add_theme_style_override("normal", label_style); hb->add_child(hint_label); } } } if (!is_group && !is_first_hbox) { hb->add_spacer(); } if (valid_right) { if (is_group) { Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); remove_btn->set_tooltip(TTR("Remove") + " " + name_left); remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_output_port).bind(p_id, 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(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_right); name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_output_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, true), CONNECT_DEFERRED); OptionButton *type_box = memnew(OptionButton); hb->add_child(type_box); type_box->add_item(TTR("Float")); type_box->add_item(TTR("Int")); type_box->add_item(TTR("Vector2")); type_box->add_item(TTR("Vector3")); type_box->add_item(TTR("Vector4")); 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", callable_mp(editor, &VisualShaderEditor::_change_output_port_type).bind(p_id, i), CONNECT_DEFERRED); } else { Label *label = memnew(Label); label->set_text(name_right); label->add_theme_style_override("normal", label_style); //more compact hb->add_child(label); } } } if (valid_right) { if (vsnode->is_output_port_expandable(i)) { TextureButton *expand = memnew(TextureButton); expand->set_toggle_mode(true); expand->set_normal_texture(editor->get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); expand->set_pressed_texture(editor->get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons"))); expand->set_v_size_flags(Control::SIZE_SHRINK_CENTER); expand->set_pressed(vsnode->_is_output_port_expanded(i)); expand->connect("pressed", callable_mp(editor, &VisualShaderEditor::_expand_output_port).bind(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); hb->add_child(expand); } if (vsnode->has_output_port_preview(i) && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); preview->set_normal_texture(editor->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); preview->set_pressed_texture(editor->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); register_output_port(p_id, j, preview); preview->connect("pressed", callable_mp(editor, &VisualShaderEditor::_preview_select_port).bind(p_id, j), CONNECT_DEFERRED); 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++; } if (!is_first_hbox) { node->add_child(hb); } if (expanded_type != VisualShaderNode::PORT_TYPE_SCALAR) { continue; } int idx = 1; if (!is_first_hbox) { idx = i + port_offset; } node->set_slot(idx, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); if (vsnode->_is_output_port_expanded(i)) { switch (vsnode->get_output_port_type(i)) { case VisualShaderNode::PORT_TYPE_VECTOR_2D: { port_offset++; valid_left = (i + 1) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 1); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); port_offset++; valid_left = (i + 2) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 2); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_2D; } break; case VisualShaderNode::PORT_TYPE_VECTOR_3D: { port_offset++; valid_left = (i + 1) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 1); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); port_offset++; valid_left = (i + 2) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 2); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); port_offset++; valid_left = (i + 3) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 3); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_3D; } break; case VisualShaderNode::PORT_TYPE_VECTOR_4D: { port_offset++; valid_left = (i + 1) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 1); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); port_offset++; valid_left = (i + 2) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 2); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); port_offset++; valid_left = (i + 3) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 3); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); port_offset++; valid_left = (i + 4) < vsnode->get_input_port_count(); port_left = VisualShaderNode::PORT_TYPE_SCALAR; if (valid_left) { port_left = vsnode->get_input_port_type(i + 4); } node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[3]); expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_4D; } break; default: break; } } } if (vsnode->get_output_port_for_preview() >= 0) { show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview()); } else { offset = memnew(Control); offset->set_custom_minimum_size(Size2(0, 4 * EDSCALE)); node->add_child(offset); } String error = vsnode->get_warning(mode, p_type); if (!error.is_empty()) { Label *error_label = memnew(Label); error_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_label->set_text(error); node->add_child(error_label); } if (is_expression) { CodeEdit *expression_box = memnew(CodeEdit); Ref expression_syntax_highlighter; expression_syntax_highlighter.instantiate(); expression_node->set_ctrl_pressed(expression_box, 0); node->add_child(expression_box); register_expression_edit(p_id, expression_box); Color background_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); Color number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); Color members_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); expression_box->set_syntax_highlighter(expression_syntax_highlighter); expression_box->add_theme_color_override("background_color", background_color); for (const String &E : editor->keyword_list) { if (ShaderLanguage::is_control_flow_keyword(E)) { expression_syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); } else { expression_syntax_highlighter->add_keyword_color(E, keyword_color); } } expression_box->add_theme_font_override("font", editor->get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); expression_box->add_theme_font_size_override("font_size", editor->get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); expression_box->add_theme_color_override("font_color", text_color); expression_syntax_highlighter->set_number_color(number_color); expression_syntax_highlighter->set_symbol_color(symbol_color); expression_syntax_highlighter->set_function_color(function_color); expression_syntax_highlighter->set_member_variable_color(members_color); expression_syntax_highlighter->add_color_region("/*", "*/", comment_color, false); expression_syntax_highlighter->add_color_region("//", "", comment_color, true); expression_box->clear_comment_delimiters(); expression_box->add_comment_delimiter("/*", "*/", false); expression_box->add_comment_delimiter("//", "", true); if (!expression_box->has_auto_brace_completion_open_key("/*")) { expression_box->add_auto_brace_completion_pair("/*", "*/"); } expression_box->set_text(expression); expression_box->set_context_menu_enabled(false); expression_box->set_draw_line_numbers(true); expression_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_expression_focus_out).bind(expression_box, p_id)); } if (is_comment) { graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content } } void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id) { if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { links[p_id].graph_node->get_parent()->remove_child(links[p_id].graph_node); memdelete(links[p_id].graph_node); links.erase(p_id); } } void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { GraphEdit *graph = editor->graph; if (!graph) { return; } if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) { graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); connections.push_back({ p_from_node, p_from_port, p_to_node, p_to_port }); if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr) { links[p_to_node].input_ports[p_to_port].default_input_button->hide(); } } } void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { GraphEdit *graph = editor->graph; if (!graph) { return; } if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) { graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); for (const List::Element *E = connections.front(); E; E = E->next()) { if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { connections.erase(E); break; } } if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr && links[p_to_node].visual_node->get_input_port_default_value(p_to_port).get_type() != Variant::NIL) { links[p_to_node].input_ports[p_to_port].default_input_button->show(); set_input_port_default_value(p_type, p_to_node, p_to_port, links[p_to_node].visual_node->get_input_port_default_value(p_to_port)); } } } VisualShaderGraphPlugin::~VisualShaderGraphPlugin() { } ///////////////// Vector2 VisualShaderEditor::selection_center; List VisualShaderEditor::copy_items_buffer; List VisualShaderEditor::copy_connections_buffer; void VisualShaderEditor::edit(VisualShader *p_visual_shader) { bool changed = false; if (p_visual_shader) { if (visual_shader.is_null()) { changed = true; } else { if (visual_shader.ptr() != p_visual_shader) { changed = true; } } visual_shader = Ref(p_visual_shader); graph_plugin->register_shader(visual_shader.ptr()); Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); if (!visual_shader->is_connected("changed", ce)) { visual_shader->connect("changed", ce); } visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE); _set_mode(visual_shader->get_mode()); } else { if (visual_shader.is_valid()) { Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); if (visual_shader->is_connected("changed", ce)) { visual_shader->disconnect("changed", ce); } } visual_shader.unref(); } if (visual_shader.is_null()) { hide(); } else { if (changed) { // to avoid tree collapse _update_varying_tree(); _update_options_menu(); _update_preview(); _update_graph(); } } } void VisualShaderEditor::update_nodes() { _update_nodes(); } void VisualShaderEditor::add_plugin(const Ref &p_plugin) { if (plugins.has(p_plugin)) { return; } plugins.push_back(p_plugin); } void VisualShaderEditor::remove_plugin(const Ref &p_plugin) { plugins.erase(p_plugin); } void VisualShaderEditor::clear_custom_types() { for (int i = 0; i < add_options.size(); i++) { if (add_options[i].is_custom) { add_options.remove_at(i); i--; } } } void VisualShaderEditor::add_custom_type(const String &p_name, const Ref