diff options
25 files changed, 1985 insertions, 287 deletions
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 18469b00a8..001839d745 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -109,6 +109,14 @@ Rearranges selected nodes in a layout with minimum crossings between connections and uniform horizontal and vertical gap between nodes. </description> </method> + <method name="attach_graph_element_to_frame"> + <return type="void" /> + <param index="0" name="element" type="StringName" /> + <param index="1" name="frame" type="StringName" /> + <description> + Attaches the [param element] [GraphElement] to the [param frame] [GraphFrame]. + </description> + </method> <method name="clear_connections"> <return type="void" /> <description> @@ -125,6 +133,13 @@ Create a connection between the [param from_port] of the [param from_node] [GraphNode] and the [param to_port] of the [param to_node] [GraphNode]. If the connection already exists, no connection is created. </description> </method> + <method name="detach_graph_element_from_frame"> + <return type="void" /> + <param index="0" name="element" type="StringName" /> + <description> + Detaches the [param element] [GraphElement] from the [GraphFrame] it is currently attached to. + </description> + </method> <method name="disconnect_node"> <return type="void" /> <param index="0" name="from_node" type="StringName" /> @@ -143,6 +158,13 @@ [b]Note:[/b] This method suppresses any other connection request signals apart from [signal connection_drag_ended]. </description> </method> + <method name="get_attached_nodes_of_frame"> + <return type="StringName[]" /> + <param index="0" name="frame" type="StringName" /> + <description> + Returns an array of node names that are attached to the [GraphFrame] with the given name. + </description> + </method> <method name="get_closest_connection_at_point" qualifiers="const"> <return type="Dictionary" /> <param index="0" name="point" type="Vector2" /> @@ -179,6 +201,13 @@ Returns an [Array] containing the list of connections that intersect with the given [Rect2]. A connection consists in a structure of the form [code]{ from_port: 0, from_node: "GraphNode name 0", to_port: 1, to_node: "GraphNode name 1" }[/code]. </description> </method> + <method name="get_element_frame"> + <return type="GraphFrame" /> + <param index="0" name="element" type="StringName" /> + <description> + Returns the [GraphFrame] that contains the [GraphElement] with the given name. + </description> + </method> <method name="get_menu_hbox"> <return type="HBoxContainer" /> <description> @@ -395,6 +424,21 @@ Emitted at the end of a [GraphElement]'s movement. </description> </signal> + <signal name="frame_rect_changed"> + <param index="0" name="frame" type="GraphFrame" /> + <param index="1" name="new_rect" type="Vector2" /> + <description> + Emitted when the [GraphFrame] [param frame] is resized to [param new_rect]. + </description> + </signal> + <signal name="graph_elements_linked_to_frame_request"> + <param index="0" name="elements" type="Array" /> + <param index="1" name="frame" type="StringName" /> + <description> + Emitted when one or more [GraphElement]s are dropped onto the [GraphFrame] named [param frame], when they were not previously attached to any other one. + [param elements] is an array of [GraphElement]s to be attached. + </description> + </signal> <signal name="node_deselected"> <param index="0" name="node" type="Node" /> <description> diff --git a/doc/classes/GraphElement.xml b/doc/classes/GraphElement.xml index 17c4184a20..7cd6496773 100644 --- a/doc/classes/GraphElement.xml +++ b/doc/classes/GraphElement.xml @@ -17,7 +17,7 @@ </member> <member name="resizable" type="bool" setter="set_resizable" getter="is_resizable" default="false"> If [code]true[/code], the user can resize the GraphElement. - [b]Note:[/b] Dragging the handle will only emit the [signal resize_request] signal, the GraphElement needs to be resized manually. + [b]Note:[/b] Dragging the handle will only emit the [signal resize_request] and [signal resize_end] signals, the GraphElement needs to be resized manually. </member> <member name="selectable" type="bool" setter="set_selectable" getter="is_selectable" default="true"> If [code]true[/code], the user can select the GraphElement. @@ -59,8 +59,14 @@ Emitted when displaying the GraphElement over other ones is requested. Happens on focusing (clicking into) the GraphElement. </description> </signal> + <signal name="resize_end"> + <param index="0" name="new_size" type="Vector2" /> + <description> + Emitted when releasing the mouse button after dragging the resizer handle (see [member resizable]). + </description> + </signal> <signal name="resize_request"> - <param index="0" name="new_minsize" type="Vector2" /> + <param index="0" name="new_size" type="Vector2" /> <description> Emitted when resizing the GraphElement is requested. Happens on dragging the resizer handle (see [member resizable]). </description> diff --git a/doc/classes/GraphFrame.xml b/doc/classes/GraphFrame.xml new file mode 100644 index 0000000000..52bb451d95 --- /dev/null +++ b/doc/classes/GraphFrame.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="GraphFrame" inherits="GraphElement" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + GraphFrame is a special [GraphElement] that can be used to organize other [GraphElement]s inside a [GraphEdit]. + </brief_description> + <description> + GraphFrame is a special [GraphElement] to which other [GraphElement]s can be attached. It can be configured to automatically resize to enclose all attached [GraphElement]s. If the frame is moved, all the attached [GraphElement]s inside it will be moved as well. + A GraphFrame is always kept behind the connection layer and other [GraphElement]s inside a [GraphEdit]. + </description> + <tutorials> + </tutorials> + <methods> + <method name="get_titlebar_hbox"> + <return type="HBoxContainer" /> + <description> + Returns the [HBoxContainer] used for the title bar, only containing a [Label] for displaying the title by default. + This can be used to add custom controls to the title bar such as option or close buttons. + </description> + </method> + </methods> + <members> + <member name="autoshrink_enabled" type="bool" setter="set_autoshrink_enabled" getter="is_autoshrink_enabled" default="true"> + If [code]true[/code], the frame's rect will be adjusted automatically to enclose all attached [GraphElement]s. + </member> + <member name="autoshrink_margin" type="int" setter="set_autoshrink_margin" getter="get_autoshrink_margin" default="40"> + The margin around the attached nodes that is used to calculate the size of the frame when [member autoshrink_enabled] is [code]true[/code]. + </member> + <member name="drag_margin" type="int" setter="set_drag_margin" getter="get_drag_margin" default="16"> + The margin inside the frame that can be used to drag the frame. + </member> + <member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="0" /> + <member name="tint_color" type="Color" setter="set_tint_color" getter="get_tint_color" default="Color(0.3, 0.3, 0.3, 0.75)"> + The color of the frame when [member tint_color_enabled] is [code]true[/code]. + </member> + <member name="tint_color_enabled" type="bool" setter="set_tint_color_enabled" getter="is_tint_color_enabled" default="false"> + If [code]true[/code], the tint color will be used to tint the frame. + </member> + <member name="title" type="String" setter="set_title" getter="get_title" default=""""> + Title of the frame. + </member> + </members> + <signals> + <signal name="autoshrink_changed"> + <description> + Emitted when [member autoshrink_enabled] or [member autoshrink_margin] changes. + </description> + </signal> + </signals> + <theme_items> + <theme_item name="resizer_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> + The color modulation applied to the resizer icon. + </theme_item> + <theme_item name="panel" data_type="style" type="StyleBox"> + The default [StyleBox] used for the background of the [GraphFrame]. + </theme_item> + <theme_item name="panel_selected" data_type="style" type="StyleBox"> + The [StyleBox] used for the background of the [GraphFrame] when it is selected. + </theme_item> + <theme_item name="titlebar" data_type="style" type="StyleBox"> + The [StyleBox] used for the title bar of the [GraphFrame]. + </theme_item> + <theme_item name="titlebar_selected" data_type="style" type="StyleBox"> + The [StyleBox] used for the title bar of the [GraphFrame] when it is selected. + </theme_item> + </theme_items> +</class> diff --git a/doc/classes/VisualShader.xml b/doc/classes/VisualShader.xml index 0b416214b5..c8230d94e4 100644 --- a/doc/classes/VisualShader.xml +++ b/doc/classes/VisualShader.xml @@ -29,6 +29,15 @@ Adds a new varying value node to the shader. </description> </method> + <method name="attach_node_to_frame"> + <return type="void" /> + <param index="0" name="type" type="int" enum="VisualShader.Type" /> + <param index="1" name="id" type="int" /> + <param index="2" name="frame" type="int" /> + <description> + Attaches the given node to the given frame. + </description> + </method> <method name="can_connect_nodes" qualifiers="const"> <return type="bool" /> <param index="0" name="type" type="int" enum="VisualShader.Type" /> @@ -62,6 +71,14 @@ Connects the specified nodes and ports, even if they can't be connected. Such connection is invalid and will not function properly. </description> </method> + <method name="detach_node_from_frame"> + <return type="void" /> + <param index="0" name="type" type="int" enum="VisualShader.Type" /> + <param index="1" name="id" type="int" /> + <description> + Detaches the given node from the frame it is attached to. + </description> + </method> <method name="disconnect_nodes"> <return type="void" /> <param index="0" name="type" type="int" enum="VisualShader.Type" /> diff --git a/doc/classes/VisualShaderNode.xml b/doc/classes/VisualShaderNode.xml index cc6394e2da..5b82d20246 100644 --- a/doc/classes/VisualShaderNode.xml +++ b/doc/classes/VisualShaderNode.xml @@ -61,6 +61,9 @@ </method> </methods> <members> + <member name="linked_parent_graph_frame" type="int" setter="set_frame" getter="get_frame" default="-1"> + Represents the index of the frame this node is linked to. If set to [code]-1[/code] the node is not linked to any frame. + </member> <member name="output_port_for_preview" type="int" setter="set_output_port_for_preview" getter="get_output_port_for_preview" default="-1"> Sets the output port index which will be showed for preview. If set to [code]-1[/code] no port will be open for preview. </member> diff --git a/doc/classes/VisualShaderNodeComment.xml b/doc/classes/VisualShaderNodeComment.xml deleted file mode 100644 index b4063409b9..0000000000 --- a/doc/classes/VisualShaderNodeComment.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> -<class name="VisualShaderNodeComment" inherits="VisualShaderNodeResizableBase" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> - <brief_description> - A comment node to be placed on visual shader graph. - </brief_description> - <description> - A resizable rectangular area with changeable [member title] and [member description] used for better organizing of other visual shader nodes. - </description> - <tutorials> - </tutorials> - <members> - <member name="description" type="String" setter="set_description" getter="get_description" default=""""> - An additional description which placed below the title. - </member> - <member name="title" type="String" setter="set_title" getter="get_title" default=""Comment""> - A title of the node. - </member> - </members> -</class> diff --git a/doc/classes/VisualShaderNodeFrame.xml b/doc/classes/VisualShaderNodeFrame.xml new file mode 100644 index 0000000000..3126a56abe --- /dev/null +++ b/doc/classes/VisualShaderNodeFrame.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="VisualShaderNodeFrame" inherits="VisualShaderNodeResizableBase" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A frame other visual shader nodes can be attached to for better organization. + </brief_description> + <description> + A rectangular frame that can be used to group visual shader nodes together to improve organization. + Nodes attached to the frame will move with it when it is dragged and it can automatically resize to enclose all attached nodes. + Its title, description and color can be customized. + </description> + <tutorials> + </tutorials> + <methods> + <method name="add_attached_node"> + <return type="void" /> + <param index="0" name="node" type="int" /> + <description> + Adds a node to the list of nodes attached to the frame. Should not be called directly, use the [method VisualShader.attach_node_to_frame] method instead. + </description> + </method> + <method name="remove_attached_node"> + <return type="void" /> + <param index="0" name="node" type="int" /> + <description> + Removes a node from the list of nodes attached to the frame. Should not be called directly, use the [method VisualShader.detach_node_from_frame] method instead. + </description> + </method> + </methods> + <members> + <member name="attached_nodes" type="PackedInt32Array" setter="set_attached_nodes" getter="get_attached_nodes" default="PackedInt32Array()"> + The list of nodes attached to the frame. + </member> + <member name="autoshrink" type="bool" setter="set_autoshrink_enabled" getter="is_autoshrink_enabled" default="true"> + If [code]true[/code], the frame will automatically resize to enclose all attached nodes. + </member> + <member name="tint_color" type="Color" setter="set_tint_color" getter="get_tint_color" default="Color(0.3, 0.3, 0.3, 0.75)"> + The color of the frame when [member tint_color_enabled] is [code]true[/code]. + </member> + <member name="tint_color_enabled" type="bool" setter="set_tint_color_enabled" getter="is_tint_color_enabled" default="false"> + If [code]true[/code], the frame will be tinted with the color specified in [member tint_color]. + </member> + <member name="title" type="String" setter="set_title" getter="get_title" default=""Title""> + The title of the node. + </member> + </members> +</class> diff --git a/editor/icons/GraphFrame.svg b/editor/icons/GraphFrame.svg new file mode 100644 index 0000000000..cd25c84756 --- /dev/null +++ b/editor/icons/GraphFrame.svg @@ -0,0 +1 @@ +<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 1c-1.108 0-2 .892-2 2v10c0 1.108.892 2 2 2h10c1.108 0 2-.892 2-2V3c0-1.108-.892-2-2-2zm1.25 2h6.5c.692 0 1.25.558 1.25 1.25V5c-1.645 0-3 1.355-3 3s1.355 3 3 3v.75c0 .692-.558 1.25-1.25 1.25h-6.5C3.558 13 3 12.442 3 11.75v-7.5C3 3.558 3.558 3 4.25 3zM12 6a2 2 0 110 4 2 2 0 010-4z" fill="#8eef97" paint-order="stroke markers fill"/></svg> diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index e75bb454ae..a7c64b79db 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -170,7 +170,7 @@ void AnimationNodeBlendTreeEditor::update_graph() { name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out).bind(agnode), CONNECT_DEFERRED); name->connect("text_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_rename_lineedit_changed), CONNECT_DEFERRED); base = 1; - agnode->set_closable(true); + agnode->set_deletable(true); if (!read_only) { Button *delete_button = memnew(Button); @@ -541,7 +541,7 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<String GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); if (gn && gn->is_selected()) { Ref<AnimationNode> anode = blend_tree->get_node(gn->get_name()); - if (anode->is_closable()) { + if (anode->is_deletable()) { to_erase.push_back(gn->get_name()); } } @@ -549,7 +549,7 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<String } else { for (int i = 0; i < p_nodes.size(); i++) { Ref<AnimationNode> anode = blend_tree->get_node(p_nodes[i]); - if (anode->is_closable()) { + if (anode->is_deletable()) { to_erase.push_back(p_nodes[i]); } } diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index c83c47577d..71af1dadd9 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -48,6 +48,7 @@ #include "scene/gui/button.h" #include "scene/gui/check_box.h" #include "scene/gui/code_edit.h" +#include "scene/gui/color_picker.h" #include "scene/gui/graph_edit.h" #include "scene/gui/menu_button.h" #include "scene/gui/option_button.h" @@ -120,6 +121,11 @@ void VisualShaderGraphPlugin::_bind_methods() { 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); + ClassDB::bind_method(D_METHOD("attach_node_to_frame", "type", "id", "frame"), &VisualShaderGraphPlugin::attach_node_to_frame); + ClassDB::bind_method(D_METHOD("detach_node_from_frame", "type", "id"), &VisualShaderGraphPlugin::detach_node_from_frame); + ClassDB::bind_method(D_METHOD("set_frame_color_enabled", "type", "id", "enabled"), &VisualShaderGraphPlugin::set_frame_color_enabled); + ClassDB::bind_method(D_METHOD("set_frame_color", "type", "id", "color"), &VisualShaderGraphPlugin::set_frame_color); + ClassDB::bind_method(D_METHOD("set_frame_autoshrink_enabled", "type", "id", "enabled"), &VisualShaderGraphPlugin::set_frame_autoshrink_enabled); } void VisualShaderGraphPlugin::set_editor(VisualShaderEditor *p_editor) { @@ -191,7 +197,9 @@ void VisualShaderGraphPlugin::update_node(VisualShader::Type p_type, int p_node_ return; } remove_node(p_type, p_node_id, true); - add_node(p_type, p_node_id, true); + add_node(p_type, p_node_id, true, true); + + // TODO: Restore focus here? } void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, const Variant &p_value) { @@ -282,6 +290,92 @@ void VisualShaderGraphPlugin::set_expression(VisualShader::Type p_type, int p_no links[p_node_id].expression_edit->set_text(p_expression); } +void VisualShaderGraphPlugin::attach_node_to_frame(VisualShader::Type p_type, int p_node_id, int p_frame_id) { + if (p_type != visual_shader->get_shader_type() || !links.has(p_node_id) || !links.has(p_frame_id)) { + return; + } + + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + // Get the hint label and hide it before attaching the node to prevent resizing issues with the frame. + GraphFrame *frame = Object::cast_to<GraphFrame>(links[p_frame_id].graph_element); + ERR_FAIL_COND_MSG(!frame, "VisualShader node to attach to is not a frame node."); + + Label *frame_hint_label = Object::cast_to<Label>(frame->get_child(0, false)); + if (frame_hint_label) { + frame_hint_label->hide(); + } + + graph->attach_graph_element_to_frame(itos(p_node_id), itos(p_frame_id)); +} + +void VisualShaderGraphPlugin::detach_node_from_frame(VisualShader::Type p_type, int p_node_id) { + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + const StringName node_name = itos(p_node_id); + GraphFrame *frame = graph->get_element_frame(node_name); + if (!frame) { + return; + } + + graph->detach_graph_element_from_frame(node_name); + + bool no_more_frames_attached = graph->get_attached_nodes_of_frame(frame->get_name()).is_empty(); + + if (no_more_frames_attached) { + // Get the hint label and show it. + Label *frame_hint_label = Object::cast_to<Label>(frame->get_child(0, false)); + ERR_FAIL_COND_MSG(!frame_hint_label, "Frame node does not have a hint label."); + + frame_hint_label->show(); + } +} + +void VisualShaderGraphPlugin::set_frame_color_enabled(VisualShader::Type p_type, int p_node_id, bool p_enable) { + GraphEdit *graph = editor->graph; + ERR_FAIL_COND(!graph); + + const NodePath node_name = itos(p_node_id); + GraphFrame *frame = Object::cast_to<GraphFrame>(graph->get_node_or_null(node_name)); + if (!frame) { + return; + } + + frame->set_tint_color_enabled(p_enable); +} + +void VisualShaderGraphPlugin::set_frame_color(VisualShader::Type p_type, int p_node_id, const Color &p_color) { + GraphEdit *graph = editor->graph; + ERR_FAIL_COND(!graph); + + const NodePath node_name = itos(p_node_id); + GraphFrame *frame = Object::cast_to<GraphFrame>(graph->get_node_or_null(node_name)); + if (!frame) { + return; + } + + frame->set_tint_color(p_color); +} + +void VisualShaderGraphPlugin::set_frame_autoshrink_enabled(VisualShader::Type p_type, int p_node_id, bool p_enable) { + GraphEdit *graph = editor->graph; + ERR_FAIL_COND(!graph); + + const NodePath node_name = itos(p_node_id); + GraphFrame *frame = Object::cast_to<GraphFrame>(graph->get_node_or_null(node_name)); + if (!frame) { + return; + } + + frame->set_autoshrink_enabled(p_enable); +} + Ref<Script> VisualShaderGraphPlugin::get_node_script(int p_node_id) const { if (!links.has(p_node_id)) { return Ref<Script>(); @@ -295,13 +389,6 @@ Ref<Script> VisualShaderGraphPlugin::get_node_script(int p_node_id) const { return Ref<Script>(); } -void VisualShaderGraphPlugin::update_node_size(int p_node_id) { - if (!links.has(p_node_id)) { - return; - } - links[p_node_id].graph_element->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 }); } @@ -319,7 +406,7 @@ void VisualShaderGraphPlugin::update_parameter_refs() { VisualShaderNodeParameterRef *ref = Object::cast_to<VisualShaderNodeParameterRef>(E.value.visual_node); if (ref) { remove_node(E.value.type, E.key, true); - add_node(E.value.type, E.key, true); + add_node(E.value.type, E.key, true, true); } } } @@ -328,6 +415,38 @@ VisualShader::Type VisualShaderGraphPlugin::get_shader_type() const { return visual_shader->get_shader_type(); } +// Only updates the linked frames of the given node, not the node itself (in case it's a frame node). +void VisualShaderGraphPlugin::update_frames(VisualShader::Type p_type, int p_node) { + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_node); + if (!vsnode.is_valid()) { + WARN_PRINT("Update linked frames: Node not found."); + return; + } + + int frame_vsnode_id = vsnode->get_frame(); + if (frame_vsnode_id == -1) { + return; + } + + Ref<VisualShaderNodeFrame> frame_node = visual_shader->get_node(p_type, frame_vsnode_id); + if (!frame_node.is_valid() || !links.has(frame_vsnode_id)) { + return; + } + + GraphFrame *frame = Object::cast_to<GraphFrame>(links[frame_vsnode_id].graph_element); + if (!frame) { + return; + } + + // Update the frame node recursively. + editor->graph->_update_graph_frame(frame); +} + 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_element->set_position_offset(p_position); @@ -390,7 +509,7 @@ bool VisualShaderGraphPlugin::is_node_has_parameter_instances_relatively(VisualS return result; } -void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool p_just_update) { +void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool p_just_update, bool p_update_frames) { if (!visual_shader.is_valid() || p_type != visual_shader->get_shader_type()) { return; } @@ -437,6 +556,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool static const String vector_expanded_name[4] = { "red", "green", "blue", "alpha" }; Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id); + ERR_FAIL_COND(vsnode.is_null()); Ref<VisualShaderNodeResizableBase> resizable_node = vsnode; bool is_resizable = resizable_node.is_valid(); @@ -445,8 +565,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool Ref<VisualShaderNodeGroupBase> group_node = vsnode; bool is_group = group_node.is_valid(); - Ref<VisualShaderNodeComment> comment_node = vsnode; - bool is_comment = comment_node.is_valid(); + Ref<VisualShaderNodeFrame> frame_node = vsnode; + bool is_frame = frame_node.is_valid(); Ref<VisualShaderNodeExpression> expression_node = group_node; bool is_expression = expression_node.is_valid(); @@ -457,12 +577,21 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool custom_node->_set_initialized(true); } - GraphNode *node = memnew(GraphNode); - node->set_title(vsnode->get_caption()); + GraphElement *node; + if (is_frame) { + GraphFrame *frame = memnew(GraphFrame); + frame->set_title(vsnode->get_caption()); + node = frame; + } else { + GraphNode *gnode = memnew(GraphNode); + gnode->set_title(vsnode->get_caption()); + node = gnode; + } + node->set_name(itos(p_id)); // All nodes are closable except the output node. if (p_id >= 2) { - vsnode->set_closable(true); + vsnode->set_deletable(true); node->connect("delete_request", callable_mp(editor, &VisualShaderEditor::_delete_node_request).bind(p_type, p_id), CONNECT_DEFERRED); } graph->add_child(node); @@ -496,27 +625,64 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool 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)); + node->connect("resize_end", callable_mp(editor, &VisualShaderEditor::_node_resized).bind((int)p_type, p_id)); + node->set_size(size); + // node->call_deferred(SNAME("set_size"), size); + // editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } + if (is_expression) { expression = expression_node->get_expression(); } - if (is_comment) { - node->set_visible(false); - } - node->set_position_offset(visual_shader->get_node_position(p_type, p_id)); - node->set_name(itos(p_id)); - node->connect("dragged", callable_mp(editor, &VisualShaderEditor::_node_dragged).bind(p_id)); Control *custom_editor = nullptr; int port_offset = 1; - if (is_resizable) { - editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); + if (p_update_frames) { + if (vsnode->get_frame() > -1) { + graph->attach_graph_element_to_frame(itos(p_id), itos(vsnode->get_frame())); + } else { + graph->detach_graph_element_from_frame(itos(p_id)); + } + } + + if (is_frame) { + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(node); + ERR_FAIL_NULL(graph_frame); + + graph_frame->set_tint_color_enabled(frame_node->is_tint_color_enabled()); + graph_frame->set_tint_color(frame_node->get_tint_color()); + + // Add hint label. + Label *frame_hint_label = memnew(Label); + node->add_child(frame_hint_label); + frame_hint_label->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER); + frame_hint_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + frame_hint_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + frame_hint_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + frame_hint_label->set_text(TTR("Drag and drop nodes here to attach them.")); + frame_hint_label->set_modulate(Color(1.0, 1.0, 1.0, 0.3)); + graph_frame->set_autoshrink_enabled(frame_node->is_autoshrink_enabled()); + + if (frame_node->get_attached_nodes().is_empty()) { + frame_hint_label->show(); + } else { + frame_hint_label->hide(); + } + + // Attach all nodes. + if (p_update_frames && frame_node->get_attached_nodes().size() > 0) { + for (const int &id : frame_node->get_attached_nodes()) { + graph->attach_graph_element_to_frame(itos(id), node->get_name()); + } + } + + // We should be done here. + return; } Control *content_offset = memnew(Control); @@ -977,7 +1143,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool if (!is_first_hbox) { idx = i + port_offset; } - if (!is_comment) { + if (!is_frame) { GraphNode *graph_node = Object::cast_to<GraphNode>(node); graph_node->set_slot(idx, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); @@ -1099,6 +1265,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool Ref<CodeHighlighter> expression_syntax_highlighter; expression_syntax_highlighter.instantiate(); expression_node->set_ctrl_pressed(expression_box, 0); + expression_box->set_v_size_flags(Control::SIZE_EXPAND_FILL); node->add_child(expression_box); register_expression_edit(p_id, expression_box); @@ -1154,8 +1321,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id, bool p_just_update) { if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { - Node *graph_edit_node = links[p_id].graph_element->get_parent(); - graph_edit_node->remove_child(links[p_id].graph_element); + GraphEdit *graph_edit = editor->graph; + if (!graph_edit) { + return; + } + + graph_edit->remove_child(links[p_id].graph_element); memdelete(links[p_id].graph_element); if (!p_just_update) { links.erase(p_id); @@ -2121,7 +2292,8 @@ void VisualShaderEditor::_update_graph() { graph_plugin->update_theme(); for (int n_i = 0; n_i < nodes.size(); n_i++) { - graph_plugin->add_node(type, nodes[n_i], false); + // Update frame related stuff later since we need to have all nodes in the graph. + graph_plugin->add_node(type, nodes[n_i], false, false); } for (const VisualShader::Connection &E : node_connections) { @@ -2133,6 +2305,17 @@ void VisualShaderEditor::_update_graph() { graph->connect_node(itos(from), from_idx, itos(to), to_idx); } + // Attach nodes to frames. + for (int node_id : nodes) { + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + ERR_CONTINUE_MSG(vsnode.is_null(), "Node is null."); + + if (vsnode->get_frame() != -1) { + int frame_name = vsnode->get_frame(); + graph->attach_graph_element_to_frame(itos(node_id), itos(frame_name)); + } + } + float graph_minimap_opacity = EDITOR_GET("editors/visual_editors/minimap_opacity"); graph->set_minimap_opacity(graph_minimap_opacity); float graph_lines_curvature = EDITOR_GET("editors/visual_editors/lines_curvature"); @@ -2532,38 +2715,30 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p return; } - graph_element->set_custom_minimum_size(size); - graph_element->reset_size(); - - if (!expression_node.is_null() && text_box) { - Size2 box_size = size; - if (box_size.x < 150 * EDSCALE || box_size.y < 0) { - box_size.x = graph_element->get_size().x; - } - box_size.x -= text_box->get_offset(SIDE_LEFT); - box_size.x -= 28 * EDSCALE; - box_size.y -= text_box->get_offset(SIDE_TOP); - box_size.y -= 28 * EDSCALE; - text_box->set_custom_minimum_size(box_size); - text_box->reset_size(); - } + graph_element->set_size(size); } + + // Update all parent frames. + graph_plugin->update_frames(type, p_node); } +// Called once after the node was resized. void VisualShaderEditor::_node_resized(const Vector2 &p_new_size, int p_type, int p_node) { Ref<VisualShaderNodeResizableBase> node = visual_shader->get_node(VisualShader::Type(p_type), p_node); if (node.is_null()) { return; } - Vector2 new_size = p_new_size; - if (graph->is_snapping_enabled() ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { - new_size = new_size.snapped(Vector2(graph->get_snapping_distance(), graph->get_snapping_distance())); + GraphElement *graph_element = Object::cast_to<GraphElement>(graph->get_node(itos(p_node))); + if (!graph_element) { + return; } + Size2 size = graph_element->get_size(); + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Resize VisualShader Node"), UndoRedo::MERGE_ENDS); - undo_redo->add_do_method(this, "_set_node_size", p_type, p_node, new_size); + undo_redo->create_action(TTR("Resize VisualShader Node")); + undo_redo->add_do_method(this, "_set_node_size", p_type, p_node, size); undo_redo->add_undo_method(this, "_set_node_size", p_type, p_node, node->get_size()); undo_redo->commit_action(); } @@ -2587,93 +2762,152 @@ void VisualShaderEditor::_preview_select_port(int p_node, int p_port) { undo_redo->commit_action(); } -void VisualShaderEditor::_comment_title_popup_show(const Point2 &p_position, int p_node_id) { +void VisualShaderEditor::_frame_title_popup_show(const Point2 &p_position, int p_node_id) { VisualShader::Type type = get_current_shader_type(); - Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, p_node_id); + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, p_node_id); if (node.is_null()) { return; } - comment_title_change_edit->set_text(node->get_title()); - comment_title_change_popup->set_meta("id", p_node_id); - comment_title_change_popup->popup(); - comment_title_change_popup->set_position(p_position); + frame_title_change_edit->set_text(node->get_title()); + frame_title_change_popup->set_meta("id", p_node_id); + frame_title_change_popup->popup(); + frame_title_change_popup->set_position(p_position); + + // Select current text. + frame_title_change_edit->grab_focus(); } -void VisualShaderEditor::_comment_title_text_changed(const String &p_new_text) { - comment_title_change_edit->reset_size(); - comment_title_change_popup->reset_size(); +void VisualShaderEditor::_frame_title_text_changed(const String &p_new_text) { + frame_title_change_edit->reset_size(); + frame_title_change_popup->reset_size(); } -void VisualShaderEditor::_comment_title_text_submitted(const String &p_new_text) { - comment_title_change_popup->hide(); +void VisualShaderEditor::_frame_title_text_submitted(const String &p_new_text) { + frame_title_change_popup->hide(); } -void VisualShaderEditor::_comment_title_popup_focus_out() { - comment_title_change_popup->hide(); +void VisualShaderEditor::_frame_title_popup_focus_out() { + frame_title_change_popup->hide(); } -void VisualShaderEditor::_comment_title_popup_hide() { - ERR_FAIL_COND(!comment_title_change_popup->has_meta("id")); - int node_id = (int)comment_title_change_popup->get_meta("id"); +void VisualShaderEditor::_frame_title_popup_hide() { + int node_id = (int)frame_title_change_popup->get_meta("id", -1); + ERR_FAIL_COND(node_id == -1); VisualShader::Type type = get_current_shader_type(); - Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, node_id); + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, node_id); ERR_FAIL_COND(node.is_null()); - if (node->get_title() == comment_title_change_edit->get_text()) { + if (node->get_title() == frame_title_change_edit->get_text()) { return; // nothing changed - ignored } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Set Comment Title")); - undo_redo->add_do_method(node.ptr(), "set_title", comment_title_change_edit->get_text()); + undo_redo->create_action(TTR("Set Frame Title")); + undo_redo->add_do_method(node.ptr(), "set_title", frame_title_change_edit->get_text()); undo_redo->add_undo_method(node.ptr(), "set_title", node->get_title()); undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, node_id); undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, node_id); undo_redo->commit_action(); } -void VisualShaderEditor::_comment_desc_popup_show(const Point2 &p_position, int p_node_id) { +void VisualShaderEditor::_frame_color_enabled_changed(int p_node_id) { + int item_index = popup_menu->get_item_index(NodeMenuOptions::ENABLE_FRAME_COLOR); + + // The new state. + bool tint_color_enabled = !popup_menu->is_item_checked(item_index); + + popup_menu->set_item_checked(item_index, tint_color_enabled); + int frame_color_item_idx = popup_menu->get_item_index(NodeMenuOptions::SET_FRAME_COLOR); + if (tint_color_enabled && frame_color_item_idx == -1) { + popup_menu->add_item(TTR("Set Tint Color"), NodeMenuOptions::SET_FRAME_COLOR); + } else if (!tint_color_enabled && frame_color_item_idx != -1) { + popup_menu->remove_item(frame_color_item_idx); + } + VisualShader::Type type = get_current_shader_type(); - Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, p_node_id); + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, p_node_id); + + ERR_FAIL_COND(node.is_null()); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Toggle Frame Color")); + undo_redo->add_do_method(node.ptr(), "set_tint_color_enabled", tint_color_enabled); + undo_redo->add_undo_method(node.ptr(), "set_tint_color_enabled", node->is_tint_color_enabled()); + undo_redo->add_do_method(graph_plugin.ptr(), "set_frame_color_enabled", (int)type, p_node_id, tint_color_enabled); + undo_redo->add_undo_method(graph_plugin.ptr(), "set_frame_color_enabled", (int)type, p_node_id, node->is_tint_color_enabled()); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_frame_color_popup_show(const Point2 &p_position, int p_node_id) { + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, p_node_id); if (node.is_null()) { return; } - comment_desc_change_edit->set_text(node->get_description()); - comment_desc_change_popup->set_meta("id", p_node_id); - comment_desc_change_popup->reset_size(); - comment_desc_change_popup->popup(); - comment_desc_change_popup->set_position(p_position); + frame_tint_color_picker->set_pick_color(node->get_tint_color()); + frame_tint_color_pick_popup->set_meta("id", p_node_id); + frame_tint_color_pick_popup->set_position(p_position); + frame_tint_color_pick_popup->popup(); } -void VisualShaderEditor::_comment_desc_text_changed() { - comment_desc_change_edit->reset_size(); - comment_desc_change_popup->reset_size(); +void VisualShaderEditor::_frame_color_confirm() { + frame_tint_color_pick_popup->hide(); } -void VisualShaderEditor::_comment_desc_confirm() { - comment_desc_change_popup->hide(); +void VisualShaderEditor::_frame_color_changed(const Color &p_color) { + ERR_FAIL_COND(!frame_tint_color_pick_popup->has_meta("id")); + int node_id = (int)frame_tint_color_pick_popup->get_meta("id"); + + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, node_id); + ERR_FAIL_COND(node.is_null()); + node->set_tint_color(p_color); + graph_plugin->set_frame_color(type, node_id, p_color); } -void VisualShaderEditor::_comment_desc_popup_hide() { - ERR_FAIL_COND(!comment_desc_change_popup->has_meta("id")); - int node_id = (int)comment_desc_change_popup->get_meta("id"); +void VisualShaderEditor::_frame_color_popup_hide() { + ERR_FAIL_COND(!frame_tint_color_pick_popup->has_meta("id")); + int node_id = (int)frame_tint_color_pick_popup->get_meta("id"); VisualShader::Type type = get_current_shader_type(); - Ref<VisualShaderNodeComment> node = visual_shader->get_node(type, node_id); - + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, node_id); ERR_FAIL_COND(node.is_null()); - if (node->get_description() == comment_desc_change_edit->get_text()) { - return; // nothing changed - ignored + if (node->get_tint_color() == frame_tint_color_picker->get_pick_color()) { + return; } + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Set Comment Description")); - undo_redo->add_do_method(node.ptr(), "set_description", comment_desc_change_edit->get_text()); - undo_redo->add_undo_method(node.ptr(), "set_description", node->get_title()); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, node_id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", (int)type, node_id); + undo_redo->create_action(TTR("Set Frame Color")); + undo_redo->add_do_method(node.ptr(), "set_tint_color", frame_tint_color_picker->get_pick_color()); + undo_redo->add_undo_method(node.ptr(), "set_tint_color", node->get_tint_color()); + undo_redo->add_do_method(graph_plugin.ptr(), "set_frame_color", (int)type, node_id, frame_tint_color_picker->get_pick_color()); + undo_redo->add_undo_method(graph_plugin.ptr(), "set_frame_color", (int)type, node_id, node->get_tint_color()); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_frame_autoshrink_enabled_changed(int p_node_id) { + int item_index = popup_menu->get_item_index(NodeMenuOptions::ENABLE_FRAME_AUTOSHRINK); + + bool autoshrink_enabled = popup_menu->is_item_checked(item_index); + + popup_menu->set_item_checked(item_index, !autoshrink_enabled); + + VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNodeFrame> node = visual_shader->get_node(type, p_node_id); + + ERR_FAIL_COND(node.is_null()); + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Toggle Auto Shrink")); + undo_redo->add_do_method(node.ptr(), "set_autoshrink_enabled", !autoshrink_enabled); + undo_redo->add_undo_method(node.ptr(), "set_autoshrink_enabled", autoshrink_enabled); + undo_redo->add_do_method(graph_plugin.ptr(), "set_frame_autoshrink_enabled", (int)type, p_node_id, !autoshrink_enabled); + undo_redo->add_undo_method(graph_plugin.ptr(), "set_frame_autoshrink_enabled", (int)type, p_node_id, autoshrink_enabled); undo_redo->commit_action(); + + popup_menu->hide(); } void VisualShaderEditor::_parameter_line_edit_changed(const String &p_text, int p_node_id) { @@ -3230,7 +3464,7 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons } 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); - undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_to_use, false); + undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_to_use, false, true); undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_to_use, false); VisualShaderNodeExpression *expr = Object::cast_to<VisualShaderNodeExpression>(vsnode.ptr()); @@ -3238,6 +3472,11 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons expr->set_size(Size2(250 * EDSCALE, 150 * EDSCALE)); } + Ref<VisualShaderNodeFrame> frame = vsnode; + if (frame.is_valid()) { + frame->set_size(Size2(320 * EDSCALE, 180 * EDSCALE)); + } + bool created_expression_port = false; // A node is inserted in an already present connection. @@ -3535,7 +3774,11 @@ void VisualShaderEditor::_nodes_dragged() { drag_dirty = false; EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Node(s) Moved")); + if (frame_node_id_to_link_to == -1) { + undo_redo->create_action(TTR("Move VisualShader Node(s)")); + } else { + undo_redo->create_action(TTR("Move and Attach VisualShader Node(s) to parent frame")); + } for (const DragOp &E : drag_buffer) { undo_redo->add_do_method(visual_shader.ptr(), "set_node_position", E.type, E.node, E.to); @@ -3544,11 +3787,23 @@ void VisualShaderEditor::_nodes_dragged() { undo_redo->add_undo_method(graph_plugin.ptr(), "set_node_position", E.type, E.node, E.from); } + for (const int node_id : nodes_link_to_frame_buffer) { + VisualShader::Type type = visual_shader->get_shader_type(); + Ref<VisualShaderNode> vs_node = visual_shader->get_node(type, node_id); + + undo_redo->add_do_method(visual_shader.ptr(), "attach_node_to_frame", type, node_id, frame_node_id_to_link_to); + undo_redo->add_do_method(graph_plugin.ptr(), "attach_node_to_frame", type, node_id, frame_node_id_to_link_to); + undo_redo->add_undo_method(graph_plugin.ptr(), "detach_node_from_frame", type, node_id); + undo_redo->add_undo_method(visual_shader.ptr(), "detach_node_from_frame", type, node_id); + } + undo_redo->commit_action(); _handle_node_drop_on_connection(); drag_buffer.clear(); + nodes_link_to_frame_buffer.clear(); + frame_node_id_to_link_to = -1; } void VisualShaderEditor::_connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index) { @@ -3642,7 +3897,7 @@ bool VisualShaderEditor::_check_node_drop_on_connection(const Vector2 &p_positio if (graph_node && graph_node->is_selected()) { selected_node_id = String(graph_node->get_name()).to_int(); Ref<VisualShaderNode> vsnode = visual_shader->get_node(shader_type, selected_node_id); - if (!vsnode->is_closable()) { + if (!vsnode->is_deletable()) { continue; } @@ -3768,14 +4023,50 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { } } + // The VS nodes need to be added before attaching them to frames. + for (const int &F : p_nodes) { + Ref<VisualShaderNode> node = visual_shader->get_node(type, F); + undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F), F); + undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F, false, false); + } + + // Update frame references. + for (const int &node_id : p_nodes) { + Ref<VisualShaderNodeFrame> frame = visual_shader->get_node(type, node_id); + if (frame.is_valid()) { + for (const int &attached_node_id : frame->get_attached_nodes()) { + undo_redo->add_do_method(visual_shader.ptr(), "detach_node_from_frame", type, attached_node_id); + undo_redo->add_do_method(graph_plugin.ptr(), "detach_node_from_frame", type, attached_node_id); + undo_redo->add_undo_method(visual_shader.ptr(), "attach_node_to_frame", type, attached_node_id, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "attach_node_to_frame", type, attached_node_id, node_id); + } + } + + Ref<VisualShaderNode> node = visual_shader->get_node(type, node_id); + if (node->get_frame() == -1) { + continue; + } + + undo_redo->add_do_method(visual_shader.ptr(), "detach_node_from_frame", type, node_id); + undo_redo->add_do_method(graph_plugin.ptr(), "detach_node_from_frame", type, node_id); + undo_redo->add_undo_method(visual_shader.ptr(), "attach_node_to_frame", type, node_id, node->get_frame()); + undo_redo->add_undo_method(graph_plugin.ptr(), "attach_node_to_frame", type, node_id, node->get_frame()); + } + + // Restore size of the frame nodes. + for (const int &F : p_nodes) { + Ref<VisualShaderNodeFrame> frame = visual_shader->get_node(type, F); + if (frame.is_valid()) { + undo_redo->add_undo_method(this, "_set_node_size", type, F, frame->get_size()); + } + } + HashSet<String> parameter_names; for (const int &F : p_nodes) { Ref<VisualShaderNode> node = visual_shader->get_node(type, F); undo_redo->add_do_method(visual_shader.ptr(), "remove_node", type, F); - undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F), F); - undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F, false); VisualShaderNodeParameter *parameter = Object::cast_to<VisualShaderNodeParameter>(node.ptr()); if (parameter) { @@ -4048,9 +4339,48 @@ void VisualShaderEditor::_convert_constants_to_parameters(bool p_vice_versa) { undo_redo->commit_action(); } +void VisualShaderEditor::_detach_nodes_from_frame(int p_type, const List<int> &p_nodes) { + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + for (int node_id : p_nodes) { + Ref<VisualShaderNode> node = visual_shader->get_node((VisualShader::Type)p_type, node_id); + if (!node.is_valid()) { + continue; + } + int frame_id = node->get_frame(); + if (frame_id != -1) { + undo_redo->add_do_method(graph_plugin.ptr(), "detach_node_from_frame", p_type, node_id); + undo_redo->add_do_method(visual_shader.ptr(), "detach_node_from_frame", p_type, node_id); + undo_redo->add_undo_method(visual_shader.ptr(), "attach_node_to_frame", p_type, node_id, frame_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "attach_node_to_frame", p_type, node_id, frame_id); + } + } +} + +void VisualShaderEditor::_detach_nodes_from_frame_request() { + // Called from context menu. + List<int> to_detach_node_ids; + for (int i = 0; i < graph->get_child_count(); i++) { + GraphElement *gn = Object::cast_to<GraphElement>(graph->get_child(i)); + if (gn) { + int id = String(gn->get_name()).to_int(); + if (gn->is_selected()) { + to_detach_node_ids.push_back(id); + } + } + } + if (to_detach_node_ids.is_empty()) { + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + undo_redo->create_action(TTR("Detach VisualShader Node(s) from Frame")); + _detach_nodes_from_frame(get_current_shader_type(), to_detach_node_ids); + undo_redo->commit_action(); +} + void VisualShaderEditor::_delete_node_request(int p_type, int p_node) { Ref<VisualShaderNode> node = visual_shader->get_node((VisualShader::Type)p_type, p_node); - if (!node->is_closable()) { + if (!node->is_deletable()) { return; } @@ -4070,13 +4400,15 @@ void VisualShaderEditor::_delete_nodes_request(const TypedArray<StringName> &p_n // Called from context menu. for (int i = 0; i < graph->get_child_count(); i++) { GraphElement *graph_element = Object::cast_to<GraphElement>(graph->get_child(i)); - if (graph_element && graph_element->is_selected()) { - VisualShader::Type type = get_current_shader_type(); - int id = String(graph_element->get_name()).to_int(); - Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); - if (vsnode->is_closable()) { - to_erase.push_back(graph_element->get_name().operator String().to_int()); - } + if (!graph_element) { + continue; + } + + VisualShader::Type type = get_current_shader_type(); + int id = String(graph_element->get_name()).to_int(); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); + if (vsnode->is_deletable() && graph_element->is_selected()) { + to_erase.push_back(graph_element->get_name().operator String().to_int()); } } } else { @@ -4084,7 +4416,7 @@ void VisualShaderEditor::_delete_nodes_request(const TypedArray<StringName> &p_n for (int i = 0; i < p_nodes.size(); i++) { int id = p_nodes[i].operator String().to_int(); Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); - if (vsnode->is_closable()) { + if (vsnode->is_deletable()) { to_erase.push_back(id); } } @@ -4131,45 +4463,54 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { selected_constants.clear(); selected_parameters.clear(); - selected_comment = -1; + selected_frame = -1; selected_float_constant = -1; - List<int> selected_closable_graph_elements; + List<int> selected_deletable_graph_elements; + List<GraphElement *> selected_graph_elements; for (int i = 0; i < graph->get_child_count(); i++) { GraphElement *graph_element = Object::cast_to<GraphElement>(graph->get_child(i)); - if (graph_element && graph_element->is_selected()) { - int id = String(graph_element->get_name()).to_int(); - Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); - if (!vsnode->is_closable()) { - continue; - } + if (!graph_element) { + continue; + } + int id = String(graph_element->get_name()).to_int(); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); + + if (!graph_element->is_selected()) { + continue; + } - selected_closable_graph_elements.push_back(id); + selected_graph_elements.push_back(graph_element); - Ref<VisualShaderNode> node = visual_shader->get_node(type, id); - selected_vsnode = node; + if (!vsnode->is_deletable()) { + continue; + } - VisualShaderNodeComment *frame_node = Object::cast_to<VisualShaderNodeComment>(node.ptr()); - if (frame_node != nullptr) { - selected_comment = id; - } - VisualShaderNodeConstant *constant_node = Object::cast_to<VisualShaderNodeConstant>(node.ptr()); - if (constant_node != nullptr) { - selected_constants.insert(id); - } - VisualShaderNodeFloatConstant *float_constant_node = Object::cast_to<VisualShaderNodeFloatConstant>(node.ptr()); - if (float_constant_node != nullptr) { - selected_float_constant = id; - } - VisualShaderNodeParameter *parameter_node = Object::cast_to<VisualShaderNodeParameter>(node.ptr()); - if (parameter_node != nullptr && parameter_node->is_convertible_to_constant()) { - selected_parameters.insert(id); - } + selected_deletable_graph_elements.push_back(id); + + Ref<VisualShaderNode> node = visual_shader->get_node(type, id); + selected_vsnode = node; + + VisualShaderNodeFrame *frame_node = Object::cast_to<VisualShaderNodeFrame>(node.ptr()); + if (frame_node != nullptr) { + selected_frame = id; + } + VisualShaderNodeConstant *constant_node = Object::cast_to<VisualShaderNodeConstant>(node.ptr()); + if (constant_node != nullptr) { + selected_constants.insert(id); + } + VisualShaderNodeFloatConstant *float_constant_node = Object::cast_to<VisualShaderNodeFloatConstant>(node.ptr()); + if (float_constant_node != nullptr) { + selected_float_constant = id; + } + VisualShaderNodeParameter *parameter_node = Object::cast_to<VisualShaderNodeParameter>(node.ptr()); + if (parameter_node != nullptr && parameter_node->is_convertible_to_constant()) { + selected_parameters.insert(id); } } - if (selected_closable_graph_elements.size() > 1) { - selected_comment = -1; + if (selected_deletable_graph_elements.size() > 1) { + selected_frame = -1; selected_float_constant = -1; } @@ -4190,14 +4531,14 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { 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) { + } else if (selected_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()); - popup_menu->set_item_disabled(NodeMenuOptions::COPY, selected_closable_graph_elements.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::CUT, selected_deletable_graph_elements.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::COPY, selected_deletable_graph_elements.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::PASTE, copy_buffer_empty); - popup_menu->set_item_disabled(NodeMenuOptions::DELETE, selected_closable_graph_elements.is_empty()); - popup_menu->set_item_disabled(NodeMenuOptions::DUPLICATE, selected_closable_graph_elements.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::DELETE, selected_deletable_graph_elements.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::DUPLICATE, selected_deletable_graph_elements.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::CLEAR_COPY_BUFFER, copy_buffer_empty); int temp = popup_menu->get_item_index(NodeMenuOptions::SEPARATOR2); @@ -4220,11 +4561,23 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { if (temp != -1) { popup_menu->remove_item(temp); } - temp = popup_menu->get_item_index(NodeMenuOptions::SET_COMMENT_TITLE); + temp = popup_menu->get_item_index(NodeMenuOptions::UNLINK_FROM_PARENT_FRAME); + if (temp != -1) { + popup_menu->remove_item(temp); + } + temp = popup_menu->get_item_index(NodeMenuOptions::SET_FRAME_TITLE); + if (temp != -1) { + popup_menu->remove_item(temp); + } + temp = popup_menu->get_item_index(NodeMenuOptions::ENABLE_FRAME_COLOR); if (temp != -1) { popup_menu->remove_item(temp); } - temp = popup_menu->get_item_index(NodeMenuOptions::SET_COMMENT_DESCRIPTION); + temp = popup_menu->get_item_index(NodeMenuOptions::SET_FRAME_COLOR); + if (temp != -1) { + popup_menu->remove_item(temp); + } + temp = popup_menu->get_item_index(NodeMenuOptions::ENABLE_FRAME_AUTOSHRINK); if (temp != -1) { popup_menu->remove_item(temp); } @@ -4253,10 +4606,36 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } } - if (selected_comment != -1) { + // Check if any selected node is attached to a frame. + bool is_attached_to_frame = false; + for (GraphElement *graph_element : selected_graph_elements) { + if (graph->get_element_frame(graph_element->get_name())) { + is_attached_to_frame = true; + break; + } + } + + if (is_attached_to_frame) { + popup_menu->add_item(TTR("Detach from Parent Frame"), NodeMenuOptions::UNLINK_FROM_PARENT_FRAME); + } + + if (selected_frame != -1) { popup_menu->add_separator("", NodeMenuOptions::SEPARATOR3); - popup_menu->add_item(TTR("Set Comment Title"), NodeMenuOptions::SET_COMMENT_TITLE); - popup_menu->add_item(TTR("Set Comment Description"), NodeMenuOptions::SET_COMMENT_DESCRIPTION); + popup_menu->add_item(TTR("Set Frame Title"), NodeMenuOptions::SET_FRAME_TITLE); + popup_menu->add_check_item(TTR("Enable Auto Shrink"), NodeMenuOptions::ENABLE_FRAME_AUTOSHRINK); + popup_menu->add_check_item(TTR("Enable Tint Color"), NodeMenuOptions::ENABLE_FRAME_COLOR); + + VisualShaderNodeFrame *frame_ref = Object::cast_to<VisualShaderNodeFrame>(selected_vsnode.ptr()); + if (frame_ref) { + int item_index = popup_menu->get_item_index(NodeMenuOptions::ENABLE_FRAME_COLOR); + popup_menu->set_item_checked(item_index, frame_ref->is_tint_color_enabled()); + if (frame_ref->is_tint_color_enabled()) { + popup_menu->add_item(TTR("Set Tint Color"), NodeMenuOptions::SET_FRAME_COLOR); + } + + item_index = popup_menu->get_item_index(NodeMenuOptions::ENABLE_FRAME_AUTOSHRINK); + popup_menu->set_item_checked(item_index, frame_ref->is_autoshrink_enabled()); + } } popup_menu->set_position(gpos); @@ -4481,6 +4860,28 @@ void VisualShaderEditor::_node_changed(int p_id) { } } +void VisualShaderEditor::_nodes_linked_to_frame_request(const TypedArray<StringName> &p_nodes, const StringName &p_frame) { + Vector<int> node_ids; + for (int i = 0; i < p_nodes.size(); i++) { + node_ids.push_back(p_nodes[i].operator String().to_int()); + } + frame_node_id_to_link_to = p_frame.operator String().to_int(); + nodes_link_to_frame_buffer = node_ids; +} + +void VisualShaderEditor::_frame_rect_changed(const GraphFrame *p_frame, const Rect2 &p_new_rect) { + if (p_frame == nullptr) { + return; + } + + int node_id = String(p_frame->get_name()).to_int(); + Ref<VisualShaderNodeResizableBase> vsnode = visual_shader->get_node(visual_shader->get_shader_type(), node_id); + if (vsnode.is_null()) { + return; + } + vsnode->set_size(p_new_rect.size / graph->get_zoom()); +} + void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, List<VisualShader::Connection> &r_connections) { VisualShader::Type type = (VisualShader::Type)p_type; @@ -4567,7 +4968,7 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c int base_id = visual_shader->get_valid_node_id(type); int id_from = base_id; - HashMap<int, int> connection_remap; + HashMap<int, int> connection_remap; // Used for connections and frame attachments. HashSet<int> unsupported_set; HashSet<int> added_set; @@ -4578,12 +4979,19 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c } connection_remap[item.id] = id_from; Ref<VisualShaderNode> node = item.node->duplicate(); + node->set_frame(-1); // Do not reattach nodes to frame (for now). Ref<VisualShaderNodeResizableBase> resizable_base = Object::cast_to<VisualShaderNodeResizableBase>(node.ptr()); if (resizable_base.is_valid()) { undo_redo->add_do_method(node.ptr(), "set_size", item.size); } + Ref<VisualShaderNodeFrame> frame = Object::cast_to<VisualShaderNodeFrame>(node.ptr()); + if (frame.is_valid()) { + // Do not reattach nodes to frame (for now). + undo_redo->add_do_method(node.ptr(), "set_attached_nodes", PackedInt32Array()); + } + Ref<VisualShaderNodeGroupBase> group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr()); if (group.is_valid()) { undo_redo->add_do_method(node.ptr(), "set_inputs", item.group_inputs); @@ -4596,12 +5004,26 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c } undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, node, item.position + p_offset, id_from); - undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from, false); + undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from, false, false); added_set.insert(id_from); id_from++; } + // Attach nodes to frame. + for (const CopyItem &item : r_items) { + Ref<VisualShaderNode> node = item.node; + if (node->get_frame() == -1) { + continue; + } + + int new_node_id = connection_remap[item.id]; + int new_frame_id = connection_remap[node->get_frame()]; + undo_redo->add_do_method(visual_shader.ptr(), "attach_node_to_frame", type, new_node_id, new_frame_id); + undo_redo->add_do_method(graph_plugin.ptr(), "attach_node_to_frame", type, new_node_id, new_frame_id); + } + + // Connect nodes. for (const VisualShader::Connection &E : p_connections) { if (unsupported_set.has(E.from_node) || unsupported_set.has(E.to_node)) { continue; @@ -4624,7 +5046,7 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c undo_redo->commit_action(); - // reselect nodes by excluding the other ones + // Reselect nodes by excluding the other ones. for (int i = 0; i < graph->get_child_count(); i++) { GraphElement *graph_element = Object::cast_to<GraphElement>(graph->get_child(i)); if (graph_element) { @@ -5159,11 +5581,20 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { case NodeMenuOptions::CONVERT_PARAMETERS_TO_CONSTANTS: _convert_constants_to_parameters(true); break; - case NodeMenuOptions::SET_COMMENT_TITLE: - _comment_title_popup_show(get_screen_position() + get_local_mouse_position(), selected_comment); + case NodeMenuOptions::UNLINK_FROM_PARENT_FRAME: + _detach_nodes_from_frame_request(); + break; + case NodeMenuOptions::SET_FRAME_TITLE: + _frame_title_popup_show(get_screen_position() + get_local_mouse_position(), selected_frame); + break; + case NodeMenuOptions::ENABLE_FRAME_COLOR: + _frame_color_enabled_changed(selected_frame); break; - case NodeMenuOptions::SET_COMMENT_DESCRIPTION: - _comment_desc_popup_show(get_screen_position() + get_local_mouse_position(), selected_comment); + case NodeMenuOptions::SET_FRAME_COLOR: + _frame_color_popup_show(get_screen_position() + get_local_mouse_position(), selected_frame); + break; + case NodeMenuOptions::ENABLE_FRAME_AUTOSHRINK: + _frame_autoshrink_enabled_changed(selected_frame); break; default: break; @@ -5618,6 +6049,9 @@ VisualShaderEditor::VisualShaderEditor() { graph->get_menu_hbox()->move_child(add_node, 0); add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog).bind(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX)); + graph->connect("graph_elements_linked_to_frame_request", callable_mp(this, &VisualShaderEditor::_nodes_linked_to_frame_request)); + graph->connect("frame_rect_changed", callable_mp(this, &VisualShaderEditor::_frame_rect_changed)); + varying_button = memnew(MenuButton); varying_button->set_text(TTR("Manage Varyings")); varying_button->set_switch_on_hover(true); @@ -5673,6 +6107,7 @@ VisualShaderEditor::VisualShaderEditor() { popup_menu = memnew(PopupMenu); add_child(popup_menu); + popup_menu->set_hide_on_checkable_item_selection(false); popup_menu->add_item(TTR("Add Node"), NodeMenuOptions::ADD); popup_menu->add_separator(); popup_menu->add_item(TTR("Cut"), NodeMenuOptions::CUT); @@ -5838,34 +6273,33 @@ VisualShaderEditor::VisualShaderEditor() { alert->get_label()->set_custom_minimum_size(Size2(400, 60) * EDSCALE); add_child(alert); - comment_title_change_popup = memnew(PopupPanel); - comment_title_change_edit = memnew(LineEdit); - comment_title_change_edit->set_expand_to_text_length_enabled(true); - comment_title_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_title_text_changed)); - comment_title_change_edit->connect("text_submitted", callable_mp(this, &VisualShaderEditor::_comment_title_text_submitted)); - comment_title_change_popup->add_child(comment_title_change_edit); - comment_title_change_edit->reset_size(); - comment_title_change_popup->reset_size(); - comment_title_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_comment_title_popup_focus_out)); - comment_title_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_comment_title_popup_hide)); - add_child(comment_title_change_popup); - - comment_desc_change_popup = memnew(PopupPanel); - VBoxContainer *comment_desc_vbox = memnew(VBoxContainer); - comment_desc_change_popup->add_child(comment_desc_vbox); - comment_desc_change_edit = memnew(TextEdit); - comment_desc_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_desc_text_changed)); - comment_desc_vbox->add_child(comment_desc_change_edit); - comment_desc_change_edit->set_custom_minimum_size(Size2(300 * EDSCALE, 150 * EDSCALE)); - comment_desc_change_edit->reset_size(); - comment_desc_change_popup->reset_size(); - comment_desc_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_comment_desc_confirm)); - comment_desc_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_comment_desc_popup_hide)); - Button *comment_desc_confirm_button = memnew(Button); - comment_desc_confirm_button->set_text(TTR("OK")); - comment_desc_vbox->add_child(comment_desc_confirm_button); - comment_desc_confirm_button->connect("pressed", callable_mp(this, &VisualShaderEditor::_comment_desc_confirm)); - add_child(comment_desc_change_popup); + frame_title_change_popup = memnew(PopupPanel); + frame_title_change_edit = memnew(LineEdit); + frame_title_change_edit->set_expand_to_text_length_enabled(true); + frame_title_change_edit->set_select_all_on_focus(true); + frame_title_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_frame_title_text_changed)); + frame_title_change_edit->connect("text_submitted", callable_mp(this, &VisualShaderEditor::_frame_title_text_submitted)); + frame_title_change_popup->add_child(frame_title_change_edit); + frame_title_change_edit->reset_size(); + frame_title_change_popup->reset_size(); + frame_title_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_frame_title_popup_focus_out)); + frame_title_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_frame_title_popup_hide)); + add_child(frame_title_change_popup); + + frame_tint_color_pick_popup = memnew(PopupPanel); + VBoxContainer *frame_popup_item_tint_color_editor = memnew(VBoxContainer); + frame_tint_color_pick_popup->add_child(frame_popup_item_tint_color_editor); + frame_tint_color_picker = memnew(ColorPicker); + frame_popup_item_tint_color_editor->add_child(frame_tint_color_picker); + frame_tint_color_picker->reset_size(); + frame_tint_color_picker->connect("color_changed", callable_mp(this, &VisualShaderEditor::_frame_color_changed)); + Button *frame_tint_color_confirm_button = memnew(Button); + frame_tint_color_confirm_button->set_text(TTR("OK")); + frame_popup_item_tint_color_editor->add_child(frame_tint_color_confirm_button); + frame_tint_color_confirm_button->connect("pressed", callable_mp(this, &VisualShaderEditor::_frame_color_confirm)); + + frame_tint_color_pick_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_frame_color_popup_hide)); + add_child(frame_tint_color_pick_popup); /////////////////////////////////////// // SHADER NODES TREE OPTIONS @@ -6519,7 +6953,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("Vector4Parameter", "Vector/Variables", "VisualShaderNodeVec4Parameter", TTR("4D vector parameter."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); // SPECIAL - + add_options.push_back(AddOption("Frame", "Special", "VisualShaderNodeFrame", TTR("A rectangular area with a description string for better graph organization."))); 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("GlobalExpression", "Special", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, parameters and constants."))); add_options.push_back(AddOption("ParameterRef", "Special", "VisualShaderNodeParameterRef", TTR("A reference to an existing parameter."))); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 683a6bc883..253ff264aa 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -42,6 +42,7 @@ class CodeEdit; class ColorPicker; class CurveEditor; class GraphElement; +class GraphFrame; class MenuButton; class PopupPanel; class RichTextLabel; @@ -120,11 +121,12 @@ public: bool is_preview_visible(int p_id) const; void update_node(VisualShader::Type p_type, int p_id); void update_node_deferred(VisualShader::Type p_type, int p_node_id); - void add_node(VisualShader::Type p_type, int p_id, bool p_just_update); + void add_node(VisualShader::Type p_type, int p_id, bool p_just_update, bool p_update_frames); void remove_node(VisualShader::Type p_type, int p_id, bool p_just_update); void connect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); void disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); void show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id, bool p_is_valid); + void update_frames(VisualShader::Type p_type, int p_node); void set_node_position(VisualShader::Type p_type, int p_id, const Vector2 &p_position); void refresh_node_ports(VisualShader::Type p_type, int p_node); void set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, const Variant &p_value); @@ -133,9 +135,13 @@ public: void update_curve(int p_node_id); void update_curve_xyz(int p_node_id); void set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression); + void attach_node_to_frame(VisualShader::Type p_type, int p_node_id, int p_frame_id); + void detach_node_from_frame(VisualShader::Type p_type, int p_node_id); + void set_frame_color_enabled(VisualShader::Type p_type, int p_node_id, bool p_enable); + void set_frame_color(VisualShader::Type p_type, int p_node_id, const Color &p_color); + void set_frame_autoshrink_enabled(VisualShader::Type p_type, int p_node_id, bool p_enable); int get_constant_index(float p_constant) const; Ref<Script> get_node_script(int p_node_id) const; - void update_node_size(int p_node_id); void update_theme(); bool is_node_has_parameter_instances_relatively(VisualShader::Type p_type, int p_node) const; VisualShader::Type get_shader_type() const; @@ -219,11 +225,11 @@ class VisualShaderEditor : public VBoxContainer { ConfirmationDialog *remove_varying_dialog = nullptr; Tree *varyings = nullptr; - PopupPanel *comment_title_change_popup = nullptr; - LineEdit *comment_title_change_edit = nullptr; + PopupPanel *frame_title_change_popup = nullptr; + LineEdit *frame_title_change_edit = nullptr; - PopupPanel *comment_desc_change_popup = nullptr; - TextEdit *comment_desc_change_edit = nullptr; + PopupPanel *frame_tint_color_pick_popup = nullptr; + ColorPicker *frame_tint_color_picker = nullptr; bool preview_first = true; bool preview_showed = false; @@ -281,9 +287,12 @@ class VisualShaderEditor : public VBoxContainer { FLOAT_CONSTANTS, CONVERT_CONSTANTS_TO_PARAMETERS, CONVERT_PARAMETERS_TO_CONSTANTS, + UNLINK_FROM_PARENT_FRAME, SEPARATOR3, // ignore - SET_COMMENT_TITLE, - SET_COMMENT_DESCRIPTION, + SET_FRAME_TITLE, + ENABLE_FRAME_COLOR, + SET_FRAME_COLOR, + ENABLE_FRAME_AUTOSHRINK, }; enum ConnectionMenuOptions { @@ -374,6 +383,9 @@ class VisualShaderEditor : public VBoxContainer { void _get_next_nodes_recursively(VisualShader::Type p_type, int p_node_id, LocalVector<int> &r_nodes) const; String _get_description(int p_idx); + Vector<int> nodes_link_to_frame_buffer; // Contains the nodes that are requested to be linked to a frame. This is used to perform one Undo/Redo operation for dragging nodes. + int frame_node_id_to_link_to = -1; + struct DragOp { VisualShader::Type type = VisualShader::Type::TYPE_MAX; int node = 0; @@ -381,6 +393,7 @@ class VisualShaderEditor : public VBoxContainer { Vector2 to; }; List<DragOp> drag_buffer; + bool drag_dirty = false; void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node); void _nodes_dragged(); @@ -398,6 +411,9 @@ class VisualShaderEditor : public VBoxContainer { void _node_changed(int p_id); + void _nodes_linked_to_frame_request(const TypedArray<StringName> &p_nodes, const StringName &p_frame); + void _frame_rect_changed(const GraphFrame *p_frame, const Rect2 &p_new_rect); + void _edit_port_default_input(Object *p_button, int p_node, int p_port); void _port_edited(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); @@ -411,29 +427,36 @@ class VisualShaderEditor : public VBoxContainer { HashSet<int> selected_constants; HashSet<int> selected_parameters; - int selected_comment = -1; + int selected_frame = -1; int selected_float_constant = -1; void _convert_constants_to_parameters(bool p_vice_versa); + void _detach_nodes_from_frame_request(); + void _detach_nodes_from_frame(int p_type, const List<int> &p_nodes); void _replace_node(VisualShader::Type p_type_id, int p_node_id, const StringName &p_from, const StringName &p_to); void _update_constant(VisualShader::Type p_type_id, int p_node_id, const Variant &p_var, int p_preview_port); void _update_parameter(VisualShader::Type p_type_id, int p_node_id, const Variant &p_var, int p_preview_port); + void _unlink_node_from_parent_frame(int p_node_id); + 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<GraphEdit::Connection> *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(); - void _comment_title_popup_focus_out(); - void _comment_title_text_changed(const String &p_new_text); - void _comment_title_text_submitted(const String &p_new_text); + void _frame_title_popup_show(const Point2 &p_position, int p_node_id); + void _frame_title_popup_hide(); + void _frame_title_popup_focus_out(); + void _frame_title_text_changed(const String &p_new_text); + void _frame_title_text_submitted(const String &p_new_text); + + void _frame_color_enabled_changed(int p_node_id); + void _frame_color_popup_show(const Point2 &p_position, int p_node_id); + void _frame_color_popup_hide(); + void _frame_color_confirm(); + void _frame_color_changed(const Color &p_color); - void _comment_desc_popup_show(const Point2 &p_position, int p_node_id); - void _comment_desc_popup_hide(); - void _comment_desc_confirm(); - void _comment_desc_text_changed(); + void _frame_autoshrink_enabled_changed(int p_node_id); void _parameter_line_edit_changed(const String &p_text, int p_node_id); void _parameter_line_edit_focus_out(Object *p_line_edit, int p_node_id); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index bb4dce808c..734fa415a3 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1528,7 +1528,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the p_theme->set_color("resizer_color", "GraphEditMinimap", minimap_resizer_color); } - // GraphElement & GraphNode. + // GraphElement, GraphNode & GraphFrame. { const int gn_margin_top = 2; const int gn_margin_side = 2; @@ -1619,6 +1619,41 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the p_theme->set_constant("shadow_offset_x", "GraphNodeTitleLabel", 0); p_theme->set_constant("shadow_offset_y", "GraphNodeTitleLabel", 1); p_theme->set_constant("line_spacing", "GraphNodeTitleLabel", 3 * EDSCALE); + + // GraphFrame. + + const int gf_corner_width = 7 * EDSCALE; + const int gf_border_width = 2 * MAX(1, EDSCALE); + + Ref<StyleBoxFlat> graphframe_sb = make_flat_stylebox(Color(0.0, 0.0, 0.0, 0.2), gn_margin_side, gn_margin_side, gn_margin_side, gn_margin_bottom, gf_corner_width); + graphframe_sb->set_expand_margin(SIDE_TOP, 38 * EDSCALE); + graphframe_sb->set_border_width_all(gf_border_width); + graphframe_sb->set_border_color(high_contrast_borders ? gn_bg_color.lightened(0.2) : gn_bg_color.darkened(0.3)); + graphframe_sb->set_shadow_size(8 * EDSCALE); + graphframe_sb->set_shadow_color(Color(p_config.shadow_color, p_config.shadow_color.a * 0.25)); + graphframe_sb->set_anti_aliased(true); + + Ref<StyleBoxFlat> graphframe_sb_selected = graphframe_sb->duplicate(); + graphframe_sb_selected->set_border_color(gn_selected_border_color); + + p_theme->set_stylebox("panel", "GraphFrame", graphframe_sb); + p_theme->set_stylebox("panel_selected", "GraphFrame", graphframe_sb_selected); + p_theme->set_stylebox("titlebar", "GraphFrame", make_empty_stylebox(4, 4, 4, 4)); + p_theme->set_stylebox("titlebar_selected", "GraphFrame", make_empty_stylebox(4, 4, 4, 4)); + p_theme->set_color("resizer_color", "GraphFrame", gn_decoration_color); + + // GraphFrame's title Label + p_theme->set_type_variation("GraphFrameTitleLabel", "Label"); + p_theme->set_stylebox("normal", "GraphFrameTitleLabel", memnew(StyleBoxEmpty)); + p_theme->set_font_size("font_size", "GraphFrameTitleLabel", 22); + p_theme->set_color("font_color", "GraphFrameTitleLabel", Color(1, 1, 1)); + p_theme->set_color("font_shadow_color", "GraphFrameTitleLabel", Color(0, 0, 0, 0)); + p_theme->set_color("font_outline_color", "GraphFrameTitleLabel", Color(1, 1, 1)); + p_theme->set_constant("shadow_offset_x", "GraphFrameTitleLabel", 1 * EDSCALE); + p_theme->set_constant("shadow_offset_y", "GraphFrameTitleLabel", 1 * EDSCALE); + p_theme->set_constant("outline_size", "GraphFrameTitleLabel", 0); + p_theme->set_constant("shadow_outline_size", "GraphFrameTitleLabel", 1 * EDSCALE); + p_theme->set_constant("line_spacing", "GraphFrameTitleLabel", 3 * EDSCALE); } } diff --git a/misc/extension_api_validation/4.2-stable.expected b/misc/extension_api_validation/4.2-stable.expected index 53a29fa660..2416f05ee4 100644 --- a/misc/extension_api_validation/4.2-stable.expected +++ b/misc/extension_api_validation/4.2-stable.expected @@ -259,3 +259,10 @@ Validate extension JSON: Error: Field 'classes/AStarGrid2D/methods/get_point_pat Added optional "allow_partial_path" argument to get_id_path and get_point_path methods in AStar classes. Compatibility methods registered. + + +GH-88014 +-------- +Validate extension JSON: API was removed: classes/VisualShaderNodeComment + +Removed VisualShaderNodeComment, which is replaced by VisualShaderNodeFrame. diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 2d0bd9c258..2df1e81e25 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -395,11 +395,11 @@ bool AnimationNode::is_filter_enabled() const { return filter_enabled; } -void AnimationNode::set_closable(bool p_closable) { +void AnimationNode::set_deletable(bool p_closable) { closable = p_closable; } -bool AnimationNode::is_closable() const { +bool AnimationNode::is_deletable() const { return closable; } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 2a58822275..62a01b758f 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -184,8 +184,8 @@ public: void set_filter_enabled(bool p_enable); bool is_filter_enabled() const; - void set_closable(bool p_closable); - bool is_closable() const; + void set_deletable(bool p_closable); + bool is_deletable() const; virtual bool has_filter() const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index c8909840c8..ef9c7e35ed 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -442,11 +442,42 @@ void GraphEdit::_update_scroll() { updating = false; } -void GraphEdit::_graph_element_moved_to_front(Node *p_node) { - GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); - ERR_FAIL_NULL(graph_element); +void GraphEdit::_ensure_node_order_from(Node *p_node) { + GraphElement *graph_node = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_node); + GraphFrame *frame = Object::cast_to<GraphFrame>(p_node); + + // Move a non-frame node directly to the front. + if (!frame) { + graph_node->move_to_front(); + return; + } + + // Reorder the frames behind the connection layer. + List<GraphFrame *> attached_nodes_to_move; + attached_nodes_to_move.push_back(frame); + + while (!attached_nodes_to_move.is_empty()) { + GraphFrame *attached_frame = attached_nodes_to_move.front()->get(); + attached_nodes_to_move.pop_front(); + + // Move the frame to the front of the background node index range. + attached_frame->get_parent()->call_deferred("move_child", attached_frame, background_nodes_separator_idx - 1); + + if (!frame_attached_nodes.has(attached_frame->get_name())) { + continue; + } + + for (const StringName &attached_node_name : frame_attached_nodes.get(attached_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node(NodePath(attached_node_name))); + + GraphFrame *attached_child_frame_node = Object::cast_to<GraphFrame>(attached_node); - graph_element->move_to_front(); + if (attached_child_frame_node && (attached_child_frame_node != frame)) { + attached_nodes_to_move.push_back(attached_child_frame_node); + } + } + } } void GraphEdit::_graph_element_selected(Node *p_node) { @@ -463,11 +494,42 @@ void GraphEdit::_graph_element_deselected(Node *p_node) { emit_signal(SNAME("node_deselected"), graph_element); } -void GraphEdit::_graph_element_resized(Vector2 p_new_minsize, Node *p_node) { +void GraphEdit::_graph_element_resize_request(const Vector2 &p_new_minsize, Node *p_node) { GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); ERR_FAIL_NULL(graph_element); - graph_element->set_size(p_new_minsize); + // Snap the new size to the grid if snapping is enabled. + Vector2 new_size = p_new_minsize; + if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + new_size = new_size.snapped(Vector2(snapping_distance, snapping_distance)); + } + + // Disallow resizing the frame to a size smaller than the minimum size of the attached nodes. + GraphFrame *frame = Object::cast_to<GraphFrame>(graph_element); + if (frame && !frame->is_autoshrink_enabled()) { + Rect2 frame_rect = _compute_shrinked_frame_rect(frame); + Vector2 computed_min_size = (frame_rect.position + frame_rect.size) - frame->get_position_offset(); + frame->set_size(new_size.max(computed_min_size)); + } else { + graph_element->set_size(new_size); + } + + // Update all parent frames recursively bottom-up. + if (linked_parent_map.has(graph_element->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node_or_null(NodePath(linked_parent_map[graph_element->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } +} + +void GraphEdit::_graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame) { + _update_graph_frame(p_frame); + + minimap->queue_redraw(); + queue_redraw(); + connections_layer->queue_redraw(); + callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred(); } void GraphEdit::_graph_element_moved(Node *p_node) { @@ -502,6 +564,26 @@ void GraphEdit::_graph_node_rect_changed(GraphNode *p_node) { connections_layer->queue_redraw(); callable_mp(this, &GraphEdit::_update_top_connection_layer).call_deferred(); + + // Update all parent frames recursively bottom-up. + if (linked_parent_map.has(p_node->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node(NodePath(linked_parent_map[p_node->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } +} + +void GraphEdit::_ensure_node_order_from_root(const StringName &p_node) { + // Find the root frame node of the frame tree starting from p_node. + GraphElement *root_frame = Object::cast_to<GraphElement>(get_node(NodePath(p_node))); + ERR_FAIL_NULL(root_frame); + + while (linked_parent_map.has(root_frame->get_name())) { + root_frame = Object::cast_to<GraphElement>(get_node(NodePath(linked_parent_map[root_frame->get_name()]))); + } + + _ensure_node_order_from(root_frame); } void GraphEdit::add_child_notify(Node *p_child) { @@ -520,10 +602,23 @@ void GraphEdit::add_child_notify(Node *p_child) { if (graph_node) { graph_node->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(graph_element)); graph_node->connect("item_rect_changed", callable_mp(this, &GraphEdit::_graph_node_rect_changed).bind(graph_node)); + _ensure_node_order_from(graph_node); } - graph_element->connect("raise_request", callable_mp(this, &GraphEdit::_graph_element_moved_to_front).bind(graph_element)); - graph_element->connect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resized).bind(graph_element)); + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(graph_element); + if (graph_frame) { + background_nodes_separator_idx++; + + callable_mp((Node *)this, &Node::move_child).call_deferred(graph_frame, 0); + callable_mp((Node *)this, &Node::move_child).call_deferred(connections_layer, background_nodes_separator_idx); + + _update_graph_frame(graph_frame); + + graph_frame->connect("autoshrink_changed", callable_mp(this, &GraphEdit::_graph_frame_autoshrink_changed).bind(graph_element)); + } + graph_element->connect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from).bind(graph_element)); + graph_element->connect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request).bind(graph_element)); + graph_element->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); graph_element->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); graph_element->set_scale(Vector2(zoom, zoom)); @@ -565,8 +660,35 @@ void GraphEdit::remove_child_notify(Node *p_child) { connections_layer->queue_redraw(); } - graph_element->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_element_moved_to_front)); - graph_element->disconnect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resized)); + GraphFrame *frame = Object::cast_to<GraphFrame>(graph_element); + if (frame) { + background_nodes_separator_idx--; + graph_element->disconnect("autoshrink_changed", callable_mp(this, &GraphEdit::_graph_frame_autoshrink_changed)); + } + + if (linked_parent_map.has(graph_element->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node(NodePath(linked_parent_map[graph_element->get_name()]))); + if (parent_frame) { + if (frame_attached_nodes.has(parent_frame->get_name())) { + frame_attached_nodes.get(parent_frame->get_name()).erase(graph_element->get_name()); + } + linked_parent_map.erase(graph_element->get_name()); + _update_graph_frame(parent_frame); + } + } + + if (frame_attached_nodes.has(graph_element->get_name())) { + for (const StringName &attached_node_name : frame_attached_nodes.get(graph_element->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node(NodePath(attached_node_name))); + if (attached_node) { + linked_parent_map.erase(attached_node->get_name()); + } + } + frame_attached_nodes.erase(graph_element->get_name()); + } + + graph_element->disconnect("raise_request", callable_mp(this, &GraphEdit::_ensure_node_order_from)); + graph_element->disconnect("resize_request", callable_mp(this, &GraphEdit::_graph_element_resize_request)); // In case of the whole GraphEdit being destroyed these references can already be freed. if (minimap != nullptr && minimap->is_inside_tree()) { @@ -620,6 +742,7 @@ void GraphEdit::_notification(int p_what) { _draw_grid(); } } break; + case NOTIFICATION_RESIZED: { _update_scroll(); minimap->queue_redraw(); @@ -628,6 +751,118 @@ void GraphEdit::_notification(int p_what) { } } +Rect2 GraphEdit::_compute_shrinked_frame_rect(const GraphFrame *p_frame) { + Vector2 min_point{ FLT_MAX, FLT_MAX }; + Vector2 max_point{ -FLT_MAX, -FLT_MAX }; + + if (!frame_attached_nodes.has(p_frame->get_name())) { + return Rect2(p_frame->get_position_offset(), Size2()); + } + + int autoshrink_margin = p_frame->get_autoshrink_margin(); + + for (const StringName &attached_node_name : frame_attached_nodes.get(p_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node_or_null(NodePath(attached_node_name))); + + if (!attached_node || attached_node == p_frame) { + if (!attached_node) { + frame_attached_nodes.get(p_frame->get_name()).erase(attached_node_name); + } + continue; + } + + Vector2 node_pos = attached_node->get_position_offset(); + Vector2 size = attached_node->get_size(); + min_point = min_point.min(node_pos); + max_point = max_point.max(node_pos + size); + } + + // It's sufficient to check only one value here. + if (min_point.x == FLT_MAX) { + return Rect2(p_frame->get_position_offset(), Size2()); + } + + min_point -= Size2(autoshrink_margin, autoshrink_margin); + max_point += Size2(autoshrink_margin, autoshrink_margin); + + return Rect2(min_point, max_point - min_point); +} + +void GraphEdit::_update_graph_frame(GraphFrame *p_frame) { + Rect2 frame_rect = _compute_shrinked_frame_rect(p_frame); + + Vector2 min_point = frame_rect.position; + Vector2 max_point = frame_rect.position + frame_rect.size; + + // Only update the size if there are attached nodes. + if (frame_attached_nodes.has(p_frame->get_name()) && frame_attached_nodes.get(p_frame->get_name()).size() > 0) { + if (!p_frame->is_autoshrink_enabled()) { + Vector2 old_offset = p_frame->get_position_offset(); + min_point = min_point.min(old_offset); + max_point = max_point.max(old_offset + p_frame->get_size()); + } + + Rect2 old_rect = p_frame->get_rect(); + + p_frame->set_position_offset(min_point); + p_frame->set_size(max_point - min_point); + + // Emit the signal only if the frame rect has changed. + if (old_rect != p_frame->get_rect()) { + emit_signal(SNAME("frame_rect_changed"), p_frame, p_frame->get_rect()); + } + } + + // Update all parent frames recursively bottom-up. + if (linked_parent_map.has(p_frame->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node_or_null(NodePath(linked_parent_map[p_frame->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } +} + +void GraphEdit::_set_drag_frame_attached_nodes(GraphFrame *p_frame, bool p_drag) { + if (!frame_attached_nodes.has(p_frame->get_name())) { + return; + } + + for (const StringName &attached_node_name : frame_attached_nodes.get(p_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node(NodePath(attached_node_name))); + + attached_node->set_drag(p_drag); + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(attached_node); + if (graph_frame) { + _set_drag_frame_attached_nodes(graph_frame, p_drag); + } + } +} + +void GraphEdit::_set_position_of_frame_attached_nodes(GraphFrame *p_frame, const Vector2 &p_pos) { + if (!frame_attached_nodes.has(p_frame->get_name())) { + return; + } + + for (const StringName &attached_node_name : frame_attached_nodes.get(p_frame->get_name())) { + GraphElement *attached_node = Object::cast_to<GraphElement>(get_node_or_null(NodePath(attached_node_name))); + if (!attached_node) { + continue; + } + + Vector2 pos = (attached_node->get_drag_from() * zoom + drag_accum) / zoom; + if (snapping_enabled ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) { + pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); + } + + // Recursively move graph frames. + attached_node->set_position_offset(pos); + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(attached_node); + if (graph_frame) { + _set_position_of_frame_attached_nodes(graph_frame, p_pos); + } + } +} + bool GraphEdit::_filter_input(const Point2 &p_point) { for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); @@ -1265,7 +1500,33 @@ void GraphEdit::_minimap_draw() { Vector2 graph_offset = minimap->_get_graph_offset(); Vector2 minimap_offset = minimap->minimap_offset; - // Draw graph nodes. + // Draw frame nodes. + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(get_child(i)); + if (!graph_frame || !graph_frame->is_visible()) { + continue; + } + + Vector2 node_position = minimap->_convert_from_graph_position(graph_frame->get_position_offset() * zoom - graph_offset) + minimap_offset; + Vector2 node_size = minimap->_convert_from_graph_position(graph_frame->get_size() * zoom); + Rect2 node_rect = Rect2(node_position, node_size); + + Ref<StyleBoxFlat> sb_minimap = minimap->theme_cache.node_style->duplicate(); + + // Override default values with colors provided by the GraphNode's stylebox, if possible. + Ref<StyleBoxFlat> sb_frame = graph_frame->get_theme_stylebox(graph_frame->is_selected() ? SNAME("panel_selected") : SNAME("panel")); + if (sb_frame.is_valid()) { + Color node_color = sb_frame->get_bg_color(); + if (graph_frame->is_tint_color_enabled()) { + node_color = graph_frame->get_tint_color(); + } + sb_minimap->set_bg_color(node_color); + } + + minimap->draw_style_box(sb_minimap, node_rect); + } + + // Draw regular graph nodes. for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); if (!graph_node || !graph_node->is_visible()) { @@ -1400,6 +1661,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { hovered_connection = new_highlighted_connection; } + // Logic for moving graph controls via mouse drag. if (mm.is_valid() && dragging) { if (!moving_selection) { emit_signal(SNAME("begin_node_move")); @@ -1420,6 +1682,19 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } graph_element->set_position_offset(pos); + + if (linked_parent_map.has(graph_element->get_name())) { + GraphFrame *parent_frame = Object::cast_to<GraphFrame>(get_node_or_null(NodePath(linked_parent_map[graph_element->get_name()]))); + if (parent_frame) { + _update_graph_frame(parent_frame); + } + } + + // Update all frame transforms recursively. + GraphFrame *graph_frame = Object::cast_to<GraphFrame>(get_child(i)); + if (graph_frame) { + _set_position_of_frame_attached_nodes(graph_frame, drag_accum); + } } } } @@ -1436,10 +1711,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { continue; } + // Only select frames when the box selection is fully enclosing them. + bool is_frame = Object::cast_to<GraphFrame>(graph_element); Rect2 r = graph_element->get_rect(); - bool in_box = r.intersects(box_selecting_rect); + bool should_be_selected = is_frame ? box_selecting_rect.encloses(r) : box_selecting_rect.intersects(r); - if (in_box) { + if (should_be_selected) { graph_element->set_selected(box_selection_mode_additive); } else { graph_element->set_selected(prev_selected.find(graph_element) != nullptr); @@ -1494,6 +1771,10 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); if (graph_element && graph_element->is_selected()) { graph_element->set_drag(false); + GraphFrame *frame = Object::cast_to<GraphFrame>(get_child(i)); + if (frame) { + _set_drag_frame_attached_nodes(frame, false); + } } } } @@ -1501,6 +1782,46 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (moving_selection) { emit_signal(SNAME("end_node_move")); moving_selection = false; + + Vector<GraphElement *> dragged_nodes; + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphElement *moved_node = Object::cast_to<GraphElement>(get_child(i)); + if (moved_node && moved_node->is_selected() && moved_node->is_draggable()) { + dragged_nodes.push_back(moved_node); + } + } + + GraphFrame *frame_dropped_on = nullptr; + + // Find frame on which the node(s) is/were dropped. + // Count down to find the topmost frame. + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphFrame *frame = Object::cast_to<GraphFrame>(get_child(i)); + + if (!frame || frame->is_resizing()) { + continue; + } + + Rect2 frame_rect = frame->get_rect(); + if (frame_rect.has_point(mb->get_position()) && !dragged_nodes.has(frame)) { + frame_dropped_on = frame; + break; + } + } + + if (frame_dropped_on) { + dragged_nodes.erase(frame_dropped_on); + + TypedArray<StringName> dragged_node_names; + for (GraphElement *moved_node : dragged_nodes) { + if (!linked_parent_map.has(moved_node->get_name())) { + dragged_node_names.push_back(moved_node->get_name()); + } + } + if (dragged_node_names.size() > 0) { + emit_signal(SNAME("graph_elements_linked_to_frame_request"), dragged_node_names, frame_dropped_on->get_name()); + } + } } dragging = false; @@ -1561,6 +1882,11 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } if (child_element->is_selected()) { child_element->set_drag(true); + GraphFrame *frame_node = Object::cast_to<GraphFrame>(get_child(i)); + if (frame_node) { + _ensure_node_order_from(frame_node); + _set_drag_frame_attached_nodes(frame_node, true); + } } } @@ -1636,12 +1962,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { TypedArray<StringName> nodes; for (int i = 0; i < get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(get_child(i)); - if (!gn) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } - if (gn->is_selected()) { - nodes.push_back(gn->get_name()); + if (graph_element->is_selected()) { + nodes.push_back(graph_element->get_name()); } } @@ -1926,6 +2252,58 @@ bool GraphEdit::is_valid_connection_type(int p_type, int p_with_type) const { return valid_connection_types.has(ct); } +void GraphEdit::attach_graph_element_to_frame(const StringName &p_graph_element, const StringName &p_parent_frame) { + GraphFrame *frame = Object::cast_to<GraphFrame>(get_node(NodePath(p_parent_frame))); + ERR_FAIL_NULL_MSG(frame, "Frame does not exist or is not of type GraphFrame."); + GraphElement *graph_element = Object::cast_to<GraphElement>(get_node(NodePath(p_graph_element))); + ERR_FAIL_NULL_MSG(graph_element, "Graph element to attach does not exist or is not of type GraphElement."); + ERR_FAIL_COND_MSG(frame == graph_element, "Cannot attach a frame to itself."); + + linked_parent_map.insert(p_graph_element, p_parent_frame); + frame_attached_nodes[p_parent_frame].insert(p_graph_element); + + _ensure_node_order_from_root(p_graph_element); + _update_graph_frame(frame); +} + +void GraphEdit::detach_graph_element_from_frame(const StringName &p_graph_element) { + if (!linked_parent_map.has(p_graph_element)) { + return; + } + GraphFrame *frame = Object::cast_to<GraphFrame>(get_node(NodePath(linked_parent_map[p_graph_element]))); + ERR_FAIL_NULL_MSG(frame, "Frame does not exist or is not of type GraphFrame."); + GraphElement *graph_element = Object::cast_to<GraphElement>(get_node(NodePath(p_graph_element))); + ERR_FAIL_NULL_MSG(graph_element, "Graph element to detach does not exist or is not of type GraphElement."); + + frame_attached_nodes.get(frame->get_name()).erase(p_graph_element); + linked_parent_map.erase(p_graph_element); + + _update_graph_frame(frame); +} + +GraphFrame *GraphEdit::get_element_frame(const StringName &p_attached_graph_element) { + if (!linked_parent_map.has(p_attached_graph_element)) { + return nullptr; + } + + Node *parent = get_node_or_null(NodePath(linked_parent_map[p_attached_graph_element])); + + return Object::cast_to<GraphFrame>(parent); +} + +TypedArray<StringName> GraphEdit::get_attached_nodes_of_frame(const StringName &p_graph_frame) { + if (!frame_attached_nodes.has(p_graph_frame)) { + return TypedArray<StringName>(); + } + + TypedArray<StringName> attached_nodes; + for (const StringName &node : frame_attached_nodes.get(p_graph_frame)) { + attached_nodes.push_back(node); + } + + return attached_nodes; +} + void GraphEdit::set_snapping_enabled(bool p_enable) { if (snapping_enabled == p_enable) { return; @@ -2191,6 +2569,11 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("is_valid_connection_type", "from_type", "to_type"), &GraphEdit::is_valid_connection_type); ClassDB::bind_method(D_METHOD("get_connection_line", "from_node", "to_node"), &GraphEdit::get_connection_line); + ClassDB::bind_method(D_METHOD("attach_graph_element_to_frame", "element", "frame"), &GraphEdit::attach_graph_element_to_frame); + ClassDB::bind_method(D_METHOD("detach_graph_element_from_frame", "element"), &GraphEdit::detach_graph_element_from_frame); + ClassDB::bind_method(D_METHOD("get_element_frame", "element"), &GraphEdit::get_element_frame); + ClassDB::bind_method(D_METHOD("get_attached_nodes_of_frame", "frame"), &GraphEdit::get_attached_nodes_of_frame); + ClassDB::bind_method(D_METHOD("set_panning_scheme", "scheme"), &GraphEdit::set_panning_scheme); ClassDB::bind_method(D_METHOD("get_panning_scheme"), &GraphEdit::get_panning_scheme); @@ -2314,11 +2697,13 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("node_selected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("frame_rect_changed", PropertyInfo(Variant::OBJECT, "frame", PROPERTY_HINT_RESOURCE_TYPE, "GraphFrame"), PropertyInfo(Variant::VECTOR2, "new_rect"))); ADD_SIGNAL(MethodInfo("popup_request", PropertyInfo(Variant::VECTOR2, "at_position"))); ADD_SIGNAL(MethodInfo("begin_node_move")); ADD_SIGNAL(MethodInfo("end_node_move")); + ADD_SIGNAL(MethodInfo("graph_elements_linked_to_frame_request", PropertyInfo(Variant::ARRAY, "elements"), PropertyInfo(Variant::STRING_NAME, "frame"))); ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset"))); BIND_ENUM_CONSTANT(SCROLL_ZOOMS); @@ -2374,7 +2759,7 @@ GraphEdit::GraphEdit() { top_layer->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); connections_layer = memnew(Control); - add_child(connections_layer, false, INTERNAL_MODE_FRONT); + add_child(connections_layer, false); connections_layer->connect("draw", callable_mp(this, &GraphEdit::_update_connections)); connections_layer->set_name("_connection_layer"); connections_layer->set_disable_visibility_clip(true); // Necessary, so it can draw freely and be offset. diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index e24f039e84..eeda9ae200 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -32,6 +32,7 @@ #define GRAPH_EDIT_H #include "scene/gui/box_container.h" +#include "scene/gui/graph_frame.h" #include "scene/gui/graph_node.h" class Button; @@ -294,6 +295,13 @@ private: float port_hotzone_outer_extent = 0.0; } theme_cache; + // This separates the children in two layers to ensure the order + // of both background nodes (e.g frame nodes) and foreground nodes (connectable nodes). + int background_nodes_separator_idx = 0; + + HashMap<StringName, HashSet<StringName>> frame_attached_nodes; + HashMap<StringName, StringName> linked_parent_map; + void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event); void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event); @@ -304,12 +312,15 @@ private: void _graph_element_selected(Node *p_node); void _graph_element_deselected(Node *p_node); - void _graph_element_moved_to_front(Node *p_node); - void _graph_element_resized(Vector2 p_new_minsize, Node *p_node); + void _graph_element_resize_request(const Vector2 &p_new_minsize, Node *p_node); + void _graph_frame_autoshrink_changed(const Vector2 &p_new_minsize, GraphFrame *p_frame); void _graph_element_moved(Node *p_node); void _graph_node_slot_updated(int p_index, Node *p_node); void _graph_node_rect_changed(GraphNode *p_node); + void _ensure_node_order_from_root(const StringName &p_node); + void _ensure_node_order_from(Node *p_node); + void _update_scroll(); void _update_scroll_offset(); void _scroll_moved(double); @@ -332,6 +343,10 @@ private: Dictionary _get_closest_connection_at_point(const Vector2 &p_point, float p_max_distance = 4.0) const; TypedArray<Dictionary> _get_connections_intersecting_with_rect(const Rect2 &p_rect) const; + Rect2 _compute_shrinked_frame_rect(const GraphFrame *p_frame); + void _set_drag_frame_attached_nodes(GraphFrame *p_frame, bool p_drag); + void _set_position_of_frame_attached_nodes(GraphFrame *p_frame, const Vector2 &p_pos); + friend class GraphEditFilter; bool _filter_input(const Point2 &p_point); void _snapping_toggled(); @@ -377,6 +392,11 @@ public: PackedStringArray get_configuration_warnings() const override; + // This method has to be public (for undo redo). + // TODO: Find a better way to do this. + void _update_graph_frame(GraphFrame *p_frame); + + // Connection related methods. Error connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); bool is_node_connected(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); void disconnect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port); @@ -397,6 +417,12 @@ public: void remove_valid_connection_type(int p_type, int p_with_type); bool is_valid_connection_type(int p_type, int p_with_type) const; + // GraphFrame related methods. + void attach_graph_element_to_frame(const StringName &p_graph_element, const StringName &p_parent_frame); + void detach_graph_element_from_frame(const StringName &p_graph_element); + GraphFrame *get_element_frame(const StringName &p_attached_graph_element); + TypedArray<StringName> get_attached_nodes_of_frame(const StringName &p_graph_frame); + void set_panning_scheme(PanningScheme p_scheme); PanningScheme get_panning_scheme() const; diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp index 9eb9578b2c..9c13a3ad9e 100644 --- a/scene/gui/graph_element.cpp +++ b/scene/gui/graph_element.cpp @@ -166,7 +166,11 @@ void GraphElement::gui_input(const Ref<InputEvent> &p_ev) { } if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - resizing = false; + if (resizing) { + resizing = false; + emit_signal(SNAME("resize_end"), get_size()); + return; + } } } @@ -237,7 +241,8 @@ void GraphElement::_bind_methods() { ADD_SIGNAL(MethodInfo("raise_request")); ADD_SIGNAL(MethodInfo("delete_request")); - ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_minsize"))); + ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_size"))); + ADD_SIGNAL(MethodInfo("resize_end", PropertyInfo(Variant::VECTOR2, "new_size"))); ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); ADD_SIGNAL(MethodInfo("position_offset_changed")); diff --git a/scene/gui/graph_frame.cpp b/scene/gui/graph_frame.cpp new file mode 100644 index 0000000000..288b8ba66d --- /dev/null +++ b/scene/gui/graph_frame.cpp @@ -0,0 +1,357 @@ +/**************************************************************************/ +/* graph_frame.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "graph_frame.h" + +#include "core/string/translation.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/resources/style_box_flat.h" +#include "scene/resources/style_box_texture.h" +#include "scene/theme/theme_db.h" + +void GraphFrame::gui_input(const Ref<InputEvent> &p_ev) { + ERR_FAIL_COND(p_ev.is_null()); + + Ref<InputEventMouseButton> mb = p_ev; + if (mb.is_valid()) { + ERR_FAIL_NULL_MSG(get_parent_control(), "GraphFrame must be the child of a GraphEdit node."); + + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + Vector2 mpos = mb->get_position(); + + Ref<Texture2D> resizer = theme_cache.resizer; + + if (resizable && mpos.x > get_size().x - resizer->get_width() && mpos.y > get_size().y - resizer->get_height()) { + resizing = true; + resizing_from = mpos; + resizing_from_size = get_size(); + accept_event(); + return; + } + + emit_signal(SNAME("raise_request")); + } + + if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + if (resizing) { + resizing = false; + emit_signal(SNAME("resize_end"), get_size()); + return; + } + } + } + + Ref<InputEventMouseMotion> mm = p_ev; + + // Only resize if the frame is not auto-resizing based on linked nodes. + if (resizing && !autoshrink_enabled && mm.is_valid()) { + Vector2 mpos = mm->get_position(); + + Vector2 diff = mpos - resizing_from; + + emit_signal(SNAME("resize_request"), resizing_from_size + diff); + } +} + +Control::CursorShape GraphFrame::get_cursor_shape(const Point2 &p_pos) const { + if (resizable && !autoshrink_enabled) { + if (resizing || (p_pos.x > get_size().x - theme_cache.resizer->get_width() && p_pos.y > get_size().y - theme_cache.resizer->get_height())) { + return CURSOR_FDIAGSIZE; + } + } + + return Control::get_cursor_shape(p_pos); +} + +void GraphFrame::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + // Used for layout calculations. + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + + // Used for drawing. + Ref<StyleBox> sb_to_draw_panel = selected ? theme_cache.panel_selected : sb_panel; + Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : sb_titlebar; + Ref<StyleBoxFlat> sb_panel_flat = sb_to_draw_panel; + Ref<StyleBoxTexture> sb_panel_texture = sb_to_draw_panel; + + Rect2 titlebar_rect(Point2(), titlebar_hbox->get_size() + sb_titlebar->get_minimum_size()); + Size2 body_size = get_size(); + body_size.y -= titlebar_rect.size.height; + Rect2 body_rect(Point2(0, titlebar_rect.size.height), body_size); + + // Draw body stylebox. + if (tint_color_enabled) { + if (sb_panel_flat.is_valid()) { + Color original_border_color = sb_panel_flat->get_border_color(); + sb_panel_flat = sb_panel_flat->duplicate(); + sb_panel_flat->set_bg_color(tint_color); + sb_panel_flat->set_border_color(selected ? original_border_color : tint_color.lightened(0.3)); + draw_style_box(sb_panel_flat, body_rect); + } else if (sb_panel_texture.is_valid()) { + sb_panel_texture = sb_panel_flat->duplicate(); + sb_panel_texture->set_modulate(tint_color); + draw_style_box(sb_panel_texture, body_rect); + } + } else { + draw_style_box(sb_panel_flat, body_rect); + } + + // Draw title bar stylebox above. + draw_style_box(sb_to_draw_titlebar, titlebar_rect); + + // Only draw the resize handle if the frame is not auto-resizing. + if (resizable && !autoshrink_enabled) { + Ref<Texture2D> resizer = theme_cache.resizer; + Color resizer_color = theme_cache.resizer_color; + if (resizable) { + draw_texture(resizer, get_size() - resizer->get_size(), resizer_color); + } + } + + } break; + } +} + +void GraphFrame::_resort() { + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + + // Resort titlebar first. + Size2 titlebar_size = Size2(get_size().width, titlebar_hbox->get_size().height); + titlebar_size -= sb_titlebar->get_minimum_size(); + Rect2 titlebar_rect = Rect2(sb_titlebar->get_offset(), titlebar_size); + + fit_child_in_rect(titlebar_hbox, titlebar_rect); + + // After resort the children of the titlebar container may have changed their height (e.g. Label autowrap). + Size2i titlebar_min_size = titlebar_hbox->get_combined_minimum_size(); + + Size2 size = get_size() - sb_panel->get_minimum_size() - Size2(0, titlebar_min_size.height + sb_titlebar->get_minimum_size().height); + Point2 offset = Point2(sb_panel->get_margin(SIDE_LEFT), sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height); + + for (int i = 0; i < get_child_count(false); i++) { + Control *child = Object::cast_to<Control>(get_child(i, false)); + if (!child || !child->is_visible_in_tree()) { + continue; + } + if (child->is_set_as_top_level()) { + continue; + } + + fit_child_in_rect(child, Rect2(offset, size)); + } +} + +void GraphFrame::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphFrame::set_title); + ClassDB::bind_method(D_METHOD("get_title"), &GraphFrame::get_title); + + ClassDB::bind_method(D_METHOD("get_titlebar_hbox"), &GraphFrame::get_titlebar_hbox); + + ClassDB::bind_method(D_METHOD("set_autoshrink_enabled", "shrink"), &GraphFrame::set_autoshrink_enabled); + ClassDB::bind_method(D_METHOD("is_autoshrink_enabled"), &GraphFrame::is_autoshrink_enabled); + + ClassDB::bind_method(D_METHOD("set_autoshrink_margin", "autoshrink_margin"), &GraphFrame::set_autoshrink_margin); + ClassDB::bind_method(D_METHOD("get_autoshrink_margin"), &GraphFrame::get_autoshrink_margin); + + ClassDB::bind_method(D_METHOD("set_drag_margin", "drag_margin"), &GraphFrame::set_drag_margin); + ClassDB::bind_method(D_METHOD("get_drag_margin"), &GraphFrame::get_drag_margin); + + ClassDB::bind_method(D_METHOD("set_tint_color_enabled", "enable"), &GraphFrame::set_tint_color_enabled); + ClassDB::bind_method(D_METHOD("is_tint_color_enabled"), &GraphFrame::is_tint_color_enabled); + + ClassDB::bind_method(D_METHOD("set_tint_color", "color"), &GraphFrame::set_tint_color); + ClassDB::bind_method(D_METHOD("get_tint_color"), &GraphFrame::get_tint_color); + + ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoshrink_enabled"), "set_autoshrink_enabled", "is_autoshrink_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "autoshrink_margin", PROPERTY_HINT_RANGE, "0,128,1"), "set_autoshrink_margin", "get_autoshrink_margin"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "drag_margin", PROPERTY_HINT_RANGE, "0,128,1"), "set_drag_margin", "get_drag_margin"); + + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tint_color_enabled"), "set_tint_color_enabled", "is_tint_color_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_color"), "set_tint_color", "get_tint_color"); + + ADD_SIGNAL(MethodInfo(SNAME("autoshrink_changed"))); + + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, panel); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, panel_selected); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, titlebar); + BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphFrame, titlebar_selected); + + BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphFrame, resizer); + BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphFrame, resizer_color); +} + +void GraphFrame::_validate_property(PropertyInfo &p_property) const { + if (p_property.name == "resizable") { + p_property.usage = PROPERTY_USAGE_NO_EDITOR; + } +} + +void GraphFrame::set_title(const String &p_title) { + if (title == p_title) { + return; + } + title = p_title; + if (title_label) { + title_label->set_text(title); + } + update_minimum_size(); +} + +String GraphFrame::get_title() const { + return title; +} + +void GraphFrame::set_autoshrink_enabled(bool p_shrink) { + if (autoshrink_enabled == p_shrink) { + return; + } + + autoshrink_enabled = p_shrink; + + emit_signal("autoshrink_changed", get_size()); + queue_redraw(); +} + +bool GraphFrame::is_autoshrink_enabled() const { + return autoshrink_enabled; +} + +void GraphFrame::set_autoshrink_margin(const int &p_margin) { + if (autoshrink_margin == p_margin) { + return; + } + + autoshrink_margin = p_margin; + + emit_signal("autoshrink_changed", get_size()); +} + +int GraphFrame::get_autoshrink_margin() const { + return autoshrink_margin; +} + +HBoxContainer *GraphFrame::get_titlebar_hbox() { + return titlebar_hbox; +} + +void GraphFrame::set_drag_margin(int p_margin) { + drag_margin = p_margin; +} + +int GraphFrame::get_drag_margin() const { + return drag_margin; +} + +void GraphFrame::set_tint_color_enabled(bool p_enable) { + tint_color_enabled = p_enable; + queue_redraw(); +} + +bool GraphFrame::is_tint_color_enabled() const { + return tint_color_enabled; +} + +void GraphFrame::set_tint_color(const Color &p_color) { + tint_color = p_color; + queue_redraw(); +} + +Color GraphFrame::get_tint_color() const { + return tint_color; +} + +bool GraphFrame::has_point(const Point2 &p_point) const { + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + Ref<Texture2D> resizer = theme_cache.resizer; + + if (Rect2(get_size() - resizer->get_size(), resizer->get_size()).has_point(p_point)) { + return true; + } + + // For grabbing on the titlebar. + int titlebar_height = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height; + if (Rect2(0, 0, get_size().width, titlebar_height).has_point(p_point)) { + return true; + } + + // Allow grabbing on all sides of the frame. + Rect2 frame_rect = Rect2(0, 0, get_size().width, get_size().height); + Rect2 no_drag_rect = frame_rect.grow(-drag_margin); + + if (frame_rect.has_point(p_point) && !no_drag_rect.has_point(p_point)) { + return true; + } + + return false; +} + +Size2 GraphFrame::get_minimum_size() const { + Ref<StyleBox> sb_panel = theme_cache.panel; + Ref<StyleBox> sb_titlebar = theme_cache.titlebar; + + Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size(); + + for (int i = 0; i < get_child_count(false); i++) { + Control *child = Object::cast_to<Control>(get_child(i, false)); + if (!child || !child->is_visible() || child->is_set_as_top_level()) { + continue; + } + + Size2i size = child->get_combined_minimum_size(); + size.width += sb_panel->get_minimum_size().width; + + minsize.x = MAX(minsize.x, size.x); + minsize.y += MAX(minsize.y, size.y); + } + + minsize.height += sb_panel->get_minimum_size().height; + + return minsize; +} + +GraphFrame::GraphFrame() { + titlebar_hbox = memnew(HBoxContainer); + titlebar_hbox->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(titlebar_hbox, false, INTERNAL_MODE_FRONT); + + title_label = memnew(Label); + title_label->set_theme_type_variation("GraphFrameTitleLabel"); + title_label->set_h_size_flags(SIZE_EXPAND_FILL); + title_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + titlebar_hbox->add_child(title_label); + + set_mouse_filter(MOUSE_FILTER_STOP); +} diff --git a/scene/gui/graph_frame.h b/scene/gui/graph_frame.h new file mode 100644 index 0000000000..21346586c8 --- /dev/null +++ b/scene/gui/graph_frame.h @@ -0,0 +1,108 @@ +/**************************************************************************/ +/* graph_frame.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef GRAPH_FRAME_H +#define GRAPH_FRAME_H + +#include "scene/gui/graph_element.h" + +class HBoxContainer; + +class GraphFrame : public GraphElement { + GDCLASS(GraphFrame, GraphElement); + + struct _MinSizeCache { + int min_size = 0; + bool will_stretch = false; + int final_size = 0; + }; + + struct ThemeCache { + Ref<StyleBox> panel; + Ref<StyleBox> panel_selected; + Ref<StyleBox> titlebar; + Ref<StyleBox> titlebar_selected; + + Ref<Texture2D> resizer; + Color resizer_color; + } theme_cache; + +private: + String title; + + HBoxContainer *titlebar_hbox = nullptr; + Label *title_label = nullptr; + + bool autoshrink_enabled = true; + int autoshrink_margin = 40; + int drag_margin = 16; + + bool tint_color_enabled = false; + Color tint_color = Color(0.3, 0.3, 0.3, 0.75); + +protected: + virtual void gui_input(const Ref<InputEvent> &p_event) override; + virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; + + void _notification(int p_what); + static void _bind_methods(); + + void _validate_property(PropertyInfo &p_property) const; + + virtual void _resort() override; + +public: + void set_title(const String &p_title); + String get_title() const; + + void set_autoshrink_enabled(bool p_enable); + bool is_autoshrink_enabled() const; + + void set_autoshrink_margin(const int &p_margin); + int get_autoshrink_margin() const; + + HBoxContainer *get_titlebar_hbox(); + + void set_drag_margin(int p_margin); + int get_drag_margin() const; + + void set_tint_color_enabled(bool p_enable); + bool is_tint_color_enabled() const; + + void set_tint_color(const Color &p_tint_color); + Color get_tint_color() const; + + virtual bool has_point(const Point2 &p_point) const override; + virtual Size2 get_minimum_size() const override; + + GraphFrame(); +}; + +#endif // GRAPH_FRAME_H diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index a0610b37fb..71cc322baa 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -62,9 +62,9 @@ class GraphNode : public GraphElement { }; struct _MinSizeCache { - int min_size; - bool will_stretch; - int final_size; + int min_size = 0; + bool will_stretch = false; + int final_size = 0; }; HBoxContainer *titlebar_hbox = nullptr; diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index b6efd30f63..f645bb5e73 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -104,6 +104,7 @@ #include "scene/gui/file_dialog.h" #include "scene/gui/flow_container.h" #include "scene/gui/graph_edit.h" +#include "scene/gui/graph_frame.h" #include "scene/gui/graph_node.h" #include "scene/gui/grid_container.h" #include "scene/gui/item_list.h" @@ -473,6 +474,7 @@ void register_scene_types() { GDREGISTER_CLASS(GraphElement); GDREGISTER_CLASS(GraphNode); + GDREGISTER_CLASS(GraphFrame); GDREGISTER_CLASS(GraphEdit); OS::get_singleton()->yield(); // may take time to init @@ -649,7 +651,7 @@ void register_scene_types() { GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeGroupBase); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeConstant); GDREGISTER_ABSTRACT_CLASS(VisualShaderNodeVectorBase); - GDREGISTER_CLASS(VisualShaderNodeComment); + GDREGISTER_CLASS(VisualShaderNodeFrame); GDREGISTER_CLASS(VisualShaderNodeFloatConstant); GDREGISTER_CLASS(VisualShaderNodeIntConstant); GDREGISTER_CLASS(VisualShaderNodeUIntConstant); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 5cd9ec7ad0..9ac899ad78 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -348,14 +348,22 @@ void VisualShaderNode::set_disabled(bool p_disabled) { disabled = p_disabled; } -bool VisualShaderNode::is_closable() const { +bool VisualShaderNode::is_deletable() const { return closable; } -void VisualShaderNode::set_closable(bool p_closable) { +void VisualShaderNode::set_deletable(bool p_closable) { closable = p_closable; } +void VisualShaderNode::set_frame(int p_node) { + linked_parent_graph_frame = p_node; +} + +int VisualShaderNode::get_frame() const { + return linked_parent_graph_frame; +} + Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { return Vector<VisualShader::DefaultTextureParam>(); } @@ -433,9 +441,13 @@ void VisualShaderNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_default_input_values", "values"), &VisualShaderNode::set_default_input_values); ClassDB::bind_method(D_METHOD("get_default_input_values"), &VisualShaderNode::get_default_input_values); + ClassDB::bind_method(D_METHOD("set_frame", "frame"), &VisualShaderNode::set_frame); + ClassDB::bind_method(D_METHOD("get_frame"), &VisualShaderNode::get_frame); + ADD_PROPERTY(PropertyInfo(Variant::INT, "output_port_for_preview"), "set_output_port_for_preview", "get_output_port_for_preview"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_default_input_values", "get_default_input_values"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "expanded_output_ports", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_output_ports_expanded", "_get_output_ports_expanded"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "linked_parent_graph_frame", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_frame", "get_frame"); BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR); BIND_ENUM_CONSTANT(PORT_TYPE_SCALAR_INT); @@ -937,6 +949,9 @@ Vector2 VisualShader::get_node_position(Type p_type, int p_id) const { 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]; + if (!g->nodes.has(p_id)) { + return Ref<VisualShaderNode>(); + } ERR_FAIL_COND_V(!g->nodes.has(p_id), Ref<VisualShaderNode>()); return g->nodes[p_id].node; } @@ -1134,6 +1149,36 @@ bool VisualShader::is_port_types_compatible(int p_a, int p_b) const { return MAX(0, p_a - (int)VisualShaderNode::PORT_TYPE_BOOLEAN) == (MAX(0, p_b - (int)VisualShaderNode::PORT_TYPE_BOOLEAN)); } +void VisualShader::attach_node_to_frame(Type p_type, int p_node, int p_frame) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + ERR_FAIL_COND(p_frame < 0); + Graph *g = &graph[p_type]; + + ERR_FAIL_COND(!g->nodes.has(p_node)); + + g->nodes[p_node].node->set_frame(p_frame); + + Ref<VisualShaderNodeFrame> vsnode_frame = g->nodes[p_frame].node; + if (vsnode_frame.is_valid()) { + vsnode_frame->add_attached_node(p_node); + } +} + +void VisualShader::detach_node_from_frame(Type p_type, int p_node) { + ERR_FAIL_INDEX(p_type, TYPE_MAX); + Graph *g = &graph[p_type]; + + ERR_FAIL_COND(!g->nodes.has(p_node)); + + int parent_frame_id = g->nodes[p_node].node->get_frame(); + Ref<VisualShaderNodeFrame> vsnode_frame = g->nodes[parent_frame_id].node; + if (vsnode_frame.is_valid()) { + vsnode_frame->remove_attached_node(p_node); + } + + g->nodes[p_node].node->set_frame(-1); +} + 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]; @@ -2797,6 +2842,9 @@ void VisualShader::_bind_methods() { ClassDB::bind_method(D_METHOD("set_graph_offset", "offset"), &VisualShader::set_graph_offset); ClassDB::bind_method(D_METHOD("get_graph_offset"), &VisualShader::get_graph_offset); + ClassDB::bind_method(D_METHOD("attach_node_to_frame", "type", "id", "frame"), &VisualShader::attach_node_to_frame); + ClassDB::bind_method(D_METHOD("detach_node_from_frame", "type", "id"), &VisualShader::detach_node_from_frame); + ClassDB::bind_method(D_METHOD("add_varying", "name", "mode", "type"), &VisualShader::add_varying); ClassDB::bind_method(D_METHOD("remove_varying", "name"), &VisualShader::remove_varying); ClassDB::bind_method(D_METHOD("has_varying", "name"), &VisualShader::has_varying); @@ -4160,66 +4208,119 @@ VisualShaderNodeResizableBase::VisualShaderNodeResizableBase() { ////////////// Comment -String VisualShaderNodeComment::get_caption() const { +String VisualShaderNodeFrame::get_caption() const { return title; } -int VisualShaderNodeComment::get_input_port_count() const { +int VisualShaderNodeFrame::get_input_port_count() const { return 0; } -VisualShaderNodeComment::PortType VisualShaderNodeComment::get_input_port_type(int p_port) const { +VisualShaderNodeFrame::PortType VisualShaderNodeFrame::get_input_port_type(int p_port) const { return PortType::PORT_TYPE_SCALAR; } -String VisualShaderNodeComment::get_input_port_name(int p_port) const { +String VisualShaderNodeFrame::get_input_port_name(int p_port) const { return String(); } -int VisualShaderNodeComment::get_output_port_count() const { +int VisualShaderNodeFrame::get_output_port_count() const { return 0; } -VisualShaderNodeComment::PortType VisualShaderNodeComment::get_output_port_type(int p_port) const { +VisualShaderNodeFrame::PortType VisualShaderNodeFrame::get_output_port_type(int p_port) const { return PortType::PORT_TYPE_SCALAR; } -String VisualShaderNodeComment::get_output_port_name(int p_port) const { +String VisualShaderNodeFrame::get_output_port_name(int p_port) const { return String(); } -void VisualShaderNodeComment::set_title(const String &p_title) { +void VisualShaderNodeFrame::set_title(const String &p_title) { title = p_title; } -String VisualShaderNodeComment::get_title() const { +String VisualShaderNodeFrame::get_title() const { return title; } -void VisualShaderNodeComment::set_description(const String &p_description) { - description = p_description; +void VisualShaderNodeFrame::set_tint_color_enabled(bool p_enabled) { + tint_color_enabled = p_enabled; +} + +bool VisualShaderNodeFrame::is_tint_color_enabled() const { + return tint_color_enabled; } -String VisualShaderNodeComment::get_description() const { - return description; +void VisualShaderNodeFrame::set_tint_color(const Color &p_color) { + tint_color = p_color; } -String VisualShaderNodeComment::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 { +Color VisualShaderNodeFrame::get_tint_color() const { + return tint_color; +} + +void VisualShaderNodeFrame::set_autoshrink_enabled(bool p_enable) { + autoshrink = p_enable; +} + +bool VisualShaderNodeFrame::is_autoshrink_enabled() const { + return autoshrink; +} + +String VisualShaderNodeFrame::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 String(); } -void VisualShaderNodeComment::_bind_methods() { - ClassDB::bind_method(D_METHOD("set_title", "title"), &VisualShaderNodeComment::set_title); - ClassDB::bind_method(D_METHOD("get_title"), &VisualShaderNodeComment::get_title); +void VisualShaderNodeFrame::add_attached_node(int p_node) { + attached_nodes.insert(p_node); +} + +void VisualShaderNodeFrame::remove_attached_node(int p_node) { + attached_nodes.erase(p_node); +} + +void VisualShaderNodeFrame::set_attached_nodes(const PackedInt32Array &p_attached_nodes) { + attached_nodes.clear(); + for (const int &node_id : p_attached_nodes) { + attached_nodes.insert(node_id); + } +} + +PackedInt32Array VisualShaderNodeFrame::get_attached_nodes() const { + PackedInt32Array attached_nodes_arr; + for (const int &node_id : attached_nodes) { + attached_nodes_arr.push_back(node_id); + } + return attached_nodes_arr; +} + +void VisualShaderNodeFrame::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_title", "title"), &VisualShaderNodeFrame::set_title); + ClassDB::bind_method(D_METHOD("get_title"), &VisualShaderNodeFrame::get_title); + + ClassDB::bind_method(D_METHOD("set_tint_color_enabled", "enable"), &VisualShaderNodeFrame::set_tint_color_enabled); + ClassDB::bind_method(D_METHOD("is_tint_color_enabled"), &VisualShaderNodeFrame::is_tint_color_enabled); + + ClassDB::bind_method(D_METHOD("set_tint_color", "color"), &VisualShaderNodeFrame::set_tint_color); + ClassDB::bind_method(D_METHOD("get_tint_color"), &VisualShaderNodeFrame::get_tint_color); + + ClassDB::bind_method(D_METHOD("set_autoshrink_enabled", "enable"), &VisualShaderNodeFrame::set_autoshrink_enabled); + ClassDB::bind_method(D_METHOD("is_autoshrink_enabled"), &VisualShaderNodeFrame::is_autoshrink_enabled); - ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description); - ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description); + ClassDB::bind_method(D_METHOD("add_attached_node", "node"), &VisualShaderNodeFrame::add_attached_node); + ClassDB::bind_method(D_METHOD("remove_attached_node", "node"), &VisualShaderNodeFrame::remove_attached_node); + ClassDB::bind_method(D_METHOD("set_attached_nodes", "attached_nodes"), &VisualShaderNodeFrame::set_attached_nodes); + ClassDB::bind_method(D_METHOD("get_attached_nodes"), &VisualShaderNodeFrame::get_attached_nodes); ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "tint_color_enabled"), "set_tint_color_enabled", "is_tint_color_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "tint_color"), "set_tint_color", "get_tint_color"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "autoshrink"), "set_autoshrink_enabled", "is_autoshrink_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "attached_nodes", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_attached_nodes", "get_attached_nodes"); } -VisualShaderNodeComment::VisualShaderNodeComment() { +VisualShaderNodeFrame::VisualShaderNodeFrame() { } ////////////// GroupBase diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index f02ada7ee8..3ef6dcd4f9 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -226,6 +226,9 @@ public: // internal methods void connect_nodes_forced(Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); bool is_port_types_compatible(int p_a, int p_b) const; + void attach_node_to_frame(Type p_type, int p_node, int p_frame); + void detach_node_from_frame(Type p_type, int p_node); + void rebuild(); void get_node_connections(Type p_type, List<Connection> *r_connections) const; @@ -287,6 +290,7 @@ public: private: int port_preview = -1; + int linked_parent_graph_frame = -1; HashMap<int, bool> connected_input_ports; HashMap<int, int> connected_output_ports; @@ -351,8 +355,11 @@ public: bool is_disabled() const; void set_disabled(bool p_disabled = true); - bool is_closable() const; - void set_closable(bool p_closable = true); + bool is_deletable() const; + void set_deletable(bool p_closable = true); + + void set_frame(int p_node); + int get_frame() const; virtual Vector<StringName> get_editable_properties() const; virtual HashMap<StringName, String> get_editable_properties_names() const; @@ -712,12 +719,15 @@ public: VisualShaderNodeResizableBase(); }; -class VisualShaderNodeComment : public VisualShaderNodeResizableBase { - GDCLASS(VisualShaderNodeComment, VisualShaderNodeResizableBase); +class VisualShaderNodeFrame : public VisualShaderNodeResizableBase { + GDCLASS(VisualShaderNodeFrame, VisualShaderNodeResizableBase); protected: - String title = "Comment"; - String description = ""; + String title = "Title"; + bool tint_color_enabled = false; + Color tint_color = Color(0.3, 0.3, 0.3, 0.75); + bool autoshrink = true; + HashSet<int> attached_nodes; protected: static void _bind_methods(); @@ -738,12 +748,23 @@ public: void set_title(const String &p_title); String get_title() const; - void set_description(const String &p_description); - String get_description() const; + void set_tint_color_enabled(bool p_enable); + bool is_tint_color_enabled() const; - virtual Category get_category() const override { return CATEGORY_SPECIAL; } + void set_tint_color(const Color &p_color); + Color get_tint_color() const; + + void set_autoshrink_enabled(bool p_enable); + bool is_autoshrink_enabled() const; + + void add_attached_node(int p_node); + void remove_attached_node(int p_node); + void set_attached_nodes(const PackedInt32Array &p_nodes); + PackedInt32Array get_attached_nodes() const; + + virtual Category get_category() const override { return CATEGORY_NONE; } - VisualShaderNodeComment(); + VisualShaderNodeFrame(); }; class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase { diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index c45f52ec9e..7ab1d399fb 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -757,6 +757,36 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("shadow_outline_size", "GraphNodeTitleLabel", Math::round(1 * scale)); theme->set_constant("line_spacing", "GraphNodeTitleLabel", Math::round(3 * scale)); + // GraphFrame + + Ref<StyleBoxFlat> graphframe_sb = make_flat_stylebox(style_pressed_color, 18, 12, 18, 12, 3, true, 2); + graphframe_sb->set_expand_margin(SIDE_TOP, 38 * scale); + graphframe_sb->set_border_color(style_pressed_color); + Ref<StyleBoxFlat> graphframe_sb_selected = graphframe_sb->duplicate(); + graphframe_sb_selected->set_border_color(style_hover_color); + + theme->set_stylebox("panel", "GraphFrame", graphframe_sb); + theme->set_stylebox("panel_selected", "GraphFrame", graphframe_sb_selected); + theme->set_stylebox("titlebar", "GraphFrame", make_empty_stylebox(4, 4, 4, 4)); + theme->set_stylebox("titlebar_selected", "GraphFrame", make_empty_stylebox(4, 4, 4, 4)); + theme->set_icon("resizer", "GraphFrame", icons["resizer_se"]); + theme->set_color("resizer_color", "GraphFrame", control_font_color); + + // GraphFrame's title Label + + theme->set_type_variation("GraphFrameTitleLabel", "Label"); + + theme->set_stylebox("normal", "GraphFrameTitleLabel", memnew(StyleBoxEmpty)); + theme->set_font_size("font_size", "GraphFrameTitleLabel", 22); + theme->set_color("font_color", "GraphFrameTitleLabel", Color(1, 1, 1)); + theme->set_color("font_shadow_color", "GraphFrameTitleLabel", Color(0, 0, 0, 0)); + theme->set_color("font_outline_color", "GraphFrameTitleLabel", Color(1, 1, 1)); + theme->set_constant("shadow_offset_x", "GraphFrameTitleLabel", 1 * scale); + theme->set_constant("shadow_offset_y", "GraphFrameTitleLabel", 1 * scale); + theme->set_constant("outline_size", "GraphFrameTitleLabel", 0); + theme->set_constant("shadow_outline_size", "GraphFrameTitleLabel", 1 * scale); + theme->set_constant("line_spacing", "GraphFrameTitleLabel", 3 * scale); + // Tree theme->set_stylebox("panel", "Tree", make_flat_stylebox(style_normal_color, 4, 4, 4, 5)); |