From f59419bfe067ac28cda1f91a6106fce1459d301b Mon Sep 17 00:00:00 2001 From: Hendrik Brucker Date: Fri, 19 Jan 2024 11:35:46 +0100 Subject: [PATCH] Add connection-related VisualShader operations - Insert a node in a connection - Drop an unconnected node on a connection to insert it - Delete a connection --- .../plugins/visual_shader_editor_plugin.cpp | 227 +++++++++++++++++- editor/plugins/visual_shader_editor_plugin.h | 14 +- editor/themes/editor_theme_manager.cpp | 2 +- scene/resources/visual_shader.cpp | 14 ++ scene/resources/visual_shader.h | 1 + 5 files changed, 251 insertions(+), 7 deletions(-) diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 6de37172b3b..89fff008ea3 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -3191,6 +3191,15 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector &p_ops, Stri bool created_expression_port = false; + // A node is inserted in an already present connection. + if (from_node != -1 && from_slot != -1 && to_node != -1 && to_slot != -1) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, to_node, to_slot); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, to_node, to_slot); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, to_node, to_slot); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_slot, to_node, to_slot); + } + + // Create a connection from the new node to an input port of an existing one. if (to_node != -1 && to_slot != -1) { VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot); @@ -3260,7 +3269,10 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector &p_ops, Stri } } } - } else if (from_node != -1 && from_slot != -1) { + } + + // Create a connection from the output port of an existing node to the new one. + if (from_node != -1 && from_slot != -1) { VisualShaderNode::PortType output_port_type = visual_shader->get_node(type, from_node)->get_output_port_type(from_slot); if (expr && expr->is_editable()) { @@ -3483,8 +3495,11 @@ void VisualShaderEditor::_nodes_dragged() { undo_redo->add_undo_method(graph_plugin.ptr(), "set_node_position", E.type, E.node, E.from); } - drag_buffer.clear(); undo_redo->commit_action(); + + _handle_node_drop_on_connection(); + + drag_buffer.clear(); } void VisualShaderEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { @@ -3564,6 +3579,132 @@ void VisualShaderEditor::_connection_from_empty(const String &p_to, int p_to_slo _show_members_dialog(true, input_port_type, output_port_type); } +bool VisualShaderEditor::_check_node_drop_on_connection(const Vector2 &p_position, Ref *r_closest_connection, int *r_from_port, int *r_to_port) { + VisualShader::Type shader_type = get_current_shader_type(); + + // Get selected graph node. + Ref selected_vsnode; + int selected_node_id = -1; + int selected_node_count = 0; + Rect2 selected_node_rect; + + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *graph_node = Object::cast_to(graph->get_child(i)); + if (graph_node && graph_node->is_selected()) { + selected_node_id = String(graph_node->get_name()).to_int(); + Ref vsnode = visual_shader->get_node(shader_type, selected_node_id); + if (!vsnode->is_closable()) { + continue; + } + + selected_node_count += 1; + + Ref node = visual_shader->get_node(shader_type, selected_node_id); + selected_vsnode = node; + selected_node_rect = graph_node->get_rect(); + } + } + + // Only a single node - which has both input and output ports but is not connected yet - can be inserted. + if (selected_node_count != 1 || !selected_vsnode.is_valid()) { + return false; + } + + // Check whether the dragged node was dropped over a connection. + List> intersecting_connections = graph->get_connections_intersecting_with_rect(selected_node_rect); + + if (intersecting_connections.is_empty()) { + return false; + } + + Ref intersecting_connection = intersecting_connections.front()->get(); + + if (selected_vsnode->is_any_port_connected() || selected_vsnode->get_input_port_count() == 0 || selected_vsnode->get_output_port_count() == 0) { + return false; + } + + VisualShaderNode::PortType original_port_type_from = visual_shader->get_node(shader_type, String(intersecting_connection->from_node).to_int())->get_output_port_type(intersecting_connection->from_port); + VisualShaderNode::PortType original_port_type_to = visual_shader->get_node(shader_type, String(intersecting_connection->to_node).to_int())->get_input_port_type(intersecting_connection->to_port); + + // Searching for the default port or the first compatible input port of the node to insert. + int _to_port = -1; + for (int i = 0; i < selected_vsnode->get_input_port_count(); i++) { + if (visual_shader->is_port_types_compatible(original_port_type_from, selected_vsnode->get_input_port_type(i))) { + if (i == selected_vsnode->get_default_input_port(original_port_type_from)) { + _to_port = i; + break; + } else if (_to_port == -1) { + _to_port = i; + } + } + } + + // Searching for the first compatible output port of the node to insert. + int _from_port = -1; + for (int i = 0; i < selected_vsnode->get_output_port_count(); i++) { + if (visual_shader->is_port_types_compatible(selected_vsnode->get_output_port_type(i), original_port_type_to)) { + _from_port = i; + break; + } + } + + if (_to_port == -1 || _from_port == -1) { + return false; + } + + if (r_closest_connection != nullptr) { + *r_closest_connection = intersecting_connection; + } + if (r_from_port != nullptr) { + *r_from_port = _from_port; + } + if (r_to_port != nullptr) { + *r_to_port = _to_port; + } + + return true; +} + +void VisualShaderEditor::_handle_node_drop_on_connection() { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Insert node")); + + // Check whether the dragged node was dropped over a connection. + Ref closest_connection; + int _from_port = -1; + int _to_port = -1; + + if (!_check_node_drop_on_connection(graph->get_local_mouse_position(), &closest_connection, &_from_port, &_to_port)) { + return; + } + + int selected_node_id = drag_buffer[0].node; + VisualShader::Type shader_type = get_current_shader_type(); + Ref selected_vsnode = visual_shader->get_node(shader_type, selected_node_id); + + // Delete the old connection. + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + + // Add the connection to the dropped node. + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", shader_type, String(closest_connection->from_node).to_int(), closest_connection->from_port, selected_node_id, _to_port); + + // Add the connection from the dropped node. + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", shader_type, selected_node_id, _from_port, String(closest_connection->to_node).to_int(), closest_connection->to_port); + + undo_redo->commit_action(); + + call_deferred(SNAME("_update_graph")); +} + void VisualShaderEditor::_delete_nodes(int p_type, const List &p_nodes) { VisualShader::Type type = VisualShader::Type(p_type); List conns; @@ -3923,9 +4064,19 @@ void VisualShaderEditor::_node_selected(Object *p_node) { } void VisualShaderEditor::_graph_gui_input(const Ref &p_event) { + Ref mm = p_event; Ref mb = p_event; VisualShader::Type type = get_current_shader_type(); + // Highlight valid connection on which a node can be dropped. + if (mm.is_valid() && mm->get_button_mask().has_flag(MouseButtonMask::LEFT)) { + Ref closest_connection; + graph->reset_all_connection_activity(); + if (_check_node_drop_on_connection(graph->get_local_mouse_position(), &closest_connection)) { + graph->set_connection_activity(closest_connection->from_node, closest_connection->from_port, closest_connection->to_node, closest_connection->to_port, 1.0); + } + } + Ref selected_vsnode; // Right click actions. if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { @@ -3981,7 +4132,16 @@ void VisualShaderEditor::_graph_gui_input(const Ref &p_event) { } } - if (selected_closable_graph_elements.is_empty() && copy_buffer_empty) { + menu_point = graph->get_local_mouse_position(); + Point2 gpos = get_screen_position() + get_local_mouse_position(); + + Ref closest_connection = graph->get_closest_connection_at_point(menu_point); + if (closest_connection.is_valid()) { + clicked_connection = closest_connection; + connection_popup_menu->set_position(gpos); + connection_popup_menu->reset_size(); + connection_popup_menu->popup(); + } else if (selected_closable_graph_elements.is_empty() && copy_buffer_empty) { _show_members_dialog(true); } else { popup_menu->set_item_disabled(NodeMenuOptions::CUT, selected_closable_graph_elements.is_empty()); @@ -4053,8 +4213,6 @@ void VisualShaderEditor::_graph_gui_input(const Ref &p_event) { popup_menu->add_item(TTR("Set Comment Description"), NodeMenuOptions::SET_COMMENT_DESCRIPTION); } - menu_point = graph->get_local_mouse_position(); - Point2 gpos = get_screen_position() + get_local_mouse_position(); popup_menu->set_position(gpos); popup_menu->reset_size(); popup_menu->popup(); @@ -4757,6 +4915,27 @@ void VisualShaderEditor::_member_create() { TreeItem *item = members->get_selected(); if (item != nullptr && item->has_meta("id")) { int idx = members->get_selected()->get_meta("id"); + if (connection_node_insert_requested) { + from_node = String(clicked_connection->from_node).to_int(); + from_slot = clicked_connection->from_port; + to_node = String(clicked_connection->to_node).to_int(); + to_slot = clicked_connection->to_port; + + connection_node_insert_requested = false; + + saved_node_pos_dirty = true; + + // Find both graph nodes and get their positions. + GraphNode *from_graph_element = Object::cast_to(graph->get_node(itos(from_node))); + GraphNode *to_graph_element = Object::cast_to(graph->get_node(itos(to_node))); + + ERR_FAIL_NULL(from_graph_element); + ERR_FAIL_NULL(to_graph_element); + + // Since the size of the node to add is not known yet, it's not possible to center it exactly. + float zoom = graph->get_zoom(); + saved_node_pos = 0.5 * (from_graph_element->get_position() + zoom * from_graph_element->get_output_port_position(from_slot) + to_graph_element->get_position() + zoom * to_graph_element->get_input_port_position(to_slot)); + } _add_node(idx, add_options[idx].ops); members_dialog->hide(); } @@ -4767,6 +4946,7 @@ void VisualShaderEditor::_member_cancel() { to_slot = -1; from_node = -1; from_slot = -1; + connection_node_insert_requested = false; } void VisualShaderEditor::_update_varying_tree() { @@ -4938,6 +5118,37 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { } } +void VisualShaderEditor::_connection_menu_id_pressed(int p_idx) { + switch (p_idx) { + case ConnectionMenuOptions::DISCONNECT: { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Disconnect")); + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", get_current_shader_type(), String(clicked_connection->from_node).to_int(), clicked_connection->from_port, String(clicked_connection->to_node).to_int(), clicked_connection->to_port); + undo_redo->commit_action(); + } break; + case ConnectionMenuOptions::INSERT_NEW_NODE: { + VisualShaderNode::PortType input_port_type = VisualShaderNode::PORT_TYPE_MAX; + VisualShaderNode::PortType output_port_type = VisualShaderNode::PORT_TYPE_MAX; + Ref node1 = visual_shader->get_node(get_current_shader_type(), String(clicked_connection->from_node).to_int()); + if (node1.is_valid()) { + output_port_type = node1->get_output_port_type(from_slot); + } + Ref node2 = visual_shader->get_node(get_current_shader_type(), String(clicked_connection->to_node).to_int()); + if (node2.is_valid()) { + input_port_type = node2->get_input_port_type(to_slot); + } + + connection_node_insert_requested = true; + _show_members_dialog(true, input_port_type, output_port_type); + } break; + default: + break; + } +} + Variant VisualShaderEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (p_from == members) { TreeItem *it = members->get_item_at_position(p_point); @@ -5417,6 +5628,12 @@ VisualShaderEditor::VisualShaderEditor() { popup_menu->add_item(TTR("Clear Copy Buffer"), NodeMenuOptions::CLEAR_COPY_BUFFER); popup_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_node_menu_id_pressed)); + connection_popup_menu = memnew(PopupMenu); + add_child(connection_popup_menu); + connection_popup_menu->add_item(TTR("Disconnect"), ConnectionMenuOptions::DISCONNECT); + connection_popup_menu->add_item(TTR("Insert New Node"), ConnectionMenuOptions::INSERT_NEW_NODE); + connection_popup_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_connection_menu_id_pressed)); + /////////////////////////////////////// // SHADER NODES TREE /////////////////////////////////////// diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 5f1fde3a52f..39e721f226e 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -34,13 +34,13 @@ #include "editor/editor_plugin.h" #include "editor/editor_properties.h" #include "editor/plugins/editor_resource_conversion_plugin.h" +#include "scene/gui/graph_edit.h" #include "scene/resources/syntax_highlighter.h" #include "scene/resources/visual_shader.h" class CodeEdit; class ColorPicker; class CurveEditor; -class GraphEdit; class GraphElement; class MenuButton; class PopupPanel; @@ -203,6 +203,7 @@ class VisualShaderEditor : public VBoxContainer { VisualShaderNode::PortType members_input_port_type = VisualShaderNode::PORT_TYPE_MAX; VisualShaderNode::PortType members_output_port_type = VisualShaderNode::PORT_TYPE_MAX; PopupMenu *popup_menu = nullptr; + PopupMenu *connection_popup_menu = nullptr; PopupMenu *constants_submenu = nullptr; MenuButton *tools = nullptr; @@ -282,6 +283,11 @@ class VisualShaderEditor : public VBoxContainer { SET_COMMENT_DESCRIPTION, }; + enum ConnectionMenuOptions { + INSERT_NEW_NODE, + DISCONNECT, + }; + enum class VaryingMenuOptions { ADD, REMOVE, @@ -397,6 +403,9 @@ class VisualShaderEditor : public VBoxContainer { int from_node = -1; int from_slot = -1; + Ref clicked_connection; + bool connection_node_insert_requested = false; + HashSet selected_constants; HashSet selected_parameters; int selected_comment = -1; @@ -409,6 +418,8 @@ class VisualShaderEditor : public VBoxContainer { void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); + bool _check_node_drop_on_connection(const Vector2 &p_position, Ref *r_closest_connection, int *r_node_id = nullptr, int *r_to_port = nullptr); + void _handle_node_drop_on_connection(); void _comment_title_popup_show(const Point2 &p_position, int p_node_id); void _comment_title_popup_hide(); @@ -501,6 +512,7 @@ class VisualShaderEditor : public VBoxContainer { Vector2 menu_point; void _node_menu_id_pressed(int p_idx); + void _connection_menu_id_pressed(int p_idx); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index d1fc6021ebe..9c1861e29dd 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1365,7 +1365,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref &p_theme, Th } p_theme->set_color("selection_fill", "GraphEdit", p_theme->get_color(SNAME("box_selection_fill_color"), EditorStringName(Editor))); p_theme->set_color("selection_stroke", "GraphEdit", p_theme->get_color(SNAME("box_selection_stroke_color"), EditorStringName(Editor))); - p_theme->set_color("activity", "GraphEdit", p_config.accent_color); + p_theme->set_color("activity", "GraphEdit", p_config.dark_theme ? Color(1, 1, 1) : Color(0, 0, 0)); p_theme->set_color("connection_hover_tint_color", "GraphEdit", p_config.dark_theme ? Color(0, 0, 0, 0.3) : Color(1, 1, 1, 0.3)); p_theme->set_color("connection_valid_target_tint_color", "GraphEdit", p_config.dark_theme ? Color(1, 1, 1, 0.4) : Color(0, 0, 0, 0.4)); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 8ff5b54fbe2..41660767abb 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -246,6 +246,20 @@ void VisualShaderNode::set_input_port_connected(int p_port, bool p_connected) { connected_input_ports[p_port] = p_connected; } +bool VisualShaderNode::is_any_port_connected() const { + for (const KeyValue &E : connected_input_ports) { + if (E.value) { + return true; + } + } + for (const KeyValue &E : connected_output_ports) { + if (E.value > 0) { + return true; + } + } + return false; +} + bool VisualShaderNode::is_generate_input_var(int p_port) const { return true; } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index 501a538c865..7faebb86ab8 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -314,6 +314,7 @@ public: void set_output_port_connected(int p_port, bool p_connected); bool is_input_port_connected(int p_port) const; void set_input_port_connected(int p_port, bool p_connected); + bool is_any_port_connected() const; virtual bool is_generate_input_var(int p_port) const; virtual bool has_output_port_preview(int p_port) const;