Merge pull request #88014 from Geometror/new-graph-frame

Implement GraphFrame and integrate it in VisualShader
This commit is contained in:
Rémi Verschelde 2024-04-04 17:08:57 +02:00
commit a144ef52f5
No known key found for this signature in database
GPG Key ID: C3336907360768E1
25 changed files with 1996 additions and 298 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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="&quot;&quot;">
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>

View File

@ -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" />

View File

@ -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>

View File

@ -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="&quot;&quot;">
An additional description which placed below the title.
</member>
<member name="title" type="String" setter="set_title" getter="get_title" default="&quot;Comment&quot;">
A title of the node.
</member>
</members>
</class>

View File

@ -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="&quot;Title&quot;">
The title of the node.
</member>
</members>
</class>

View File

@ -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>

After

Width:  |  Height:  |  Size: 413 B

View File

@ -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]);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 _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_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 _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);

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
graph_element->move_to_front();
// 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);
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.

View File

@ -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;

View File

@ -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"));

357
scene/gui/graph_frame.cpp Normal file
View File

@ -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);
}

108
scene/gui/graph_frame.h Normal file
View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
String VisualShaderNodeComment::get_description() const {
return description;
bool VisualShaderNodeFrame::is_tint_color_enabled() const {
return tint_color_enabled;
}
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 {
void VisualShaderNodeFrame::set_tint_color(const Color &p_color) {
tint_color = p_color;
}
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);
ClassDB::bind_method(D_METHOD("set_description", "description"), &VisualShaderNodeComment::set_description);
ClassDB::bind_method(D_METHOD("get_description"), &VisualShaderNodeComment::get_description);
ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "description"), "set_description", "get_description");
void VisualShaderNodeFrame::add_attached_node(int p_node) {
attached_nodes.insert(p_node);
}
VisualShaderNodeComment::VisualShaderNodeComment() {
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("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::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");
}
VisualShaderNodeFrame::VisualShaderNodeFrame() {
}
////////////// GroupBase

View File

@ -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;
VisualShaderNodeComment();
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; }
VisualShaderNodeFrame();
};
class VisualShaderNodeGroupBase : public VisualShaderNodeResizableBase {

View File

@ -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));