diff options
author | Hendrik Brucker <hendrik.brucker@mail.de> | 2023-08-09 18:31:15 +0200 |
---|---|---|
committer | Hendrik Brucker <hendrik.brucker@mail.de> | 2023-09-07 17:29:06 +0200 |
commit | 5afe78bd9c7e619ebc2dd2fb43d549d16382b51d (patch) | |
tree | cdf9e6e0979a1726fc4fb801fdd1f9591cf48b4f | |
parent | 08c578c54c352edf85f750992b2156bae5685fb8 (diff) | |
download | redot-engine-5afe78bd9c7e619ebc2dd2fb43d549d16382b51d.tar.gz |
Clean up/refactor GraphNode and make it more flexible
Split GraphNode into GraphElement and GraphNode, add custom
titlebar, and adjust theming.
26 files changed, 1548 insertions, 1485 deletions
diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 0aea0c5727..baa591fd66 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -26,12 +26,12 @@ <param index="2" name="mouse_position" type="Vector2" /> <description> Returns whether the [param mouse_position] is in the input hot zone. - By default, a hot zone is a [Rect2] positioned such that its center is at [param in_node].[method GraphNode.get_connection_input_position]([param in_port]) (For output's case, call [method GraphNode.get_connection_output_position] instead). The hot zone's width is twice the Theme Property [code]port_grab_distance_horizontal[/code], and its height is twice the [code]port_grab_distance_vertical[/code]. + By default, a hot zone is a [Rect2] positioned such that its center is at [param in_node].[method GraphNode.get_input_port_position]([param in_port]) (For output's case, call [method GraphNode.get_output_port_position] instead). The hot zone's width is twice the Theme Property [code]port_grab_distance_horizontal[/code], and its height is twice the [code]port_grab_distance_vertical[/code]. Below is a sample code to help get started: [codeblock] func _is_in_input_hotzone(in_node, in_port, mouse_position): var port_size: Vector2 = Vector2(get_theme_constant("port_grab_distance_horizontal"), get_theme_constant("port_grab_distance_vertical")) - var port_pos: Vector2 = in_node.get_position() + in_node.get_connection_input_position(in_port) - port_size / 2 + var port_pos: Vector2 = in_node.get_position() + in_node.get_input_port_position(in_port) - port_size / 2 var rect = Rect2(port_pos, port_size) return rect.has_point(mouse_position) @@ -49,7 +49,7 @@ [codeblock] func _is_in_output_hotzone(in_node, in_port, mouse_position): var port_size: Vector2 = Vector2(get_theme_constant("port_grab_distance_horizontal"), get_theme_constant("port_grab_distance_vertical")) - var port_pos: Vector2 = in_node.get_position() + in_node.get_connection_output_position(in_port) - port_size / 2 + var port_pos: Vector2 = in_node.get_position() + in_node.get_output_port_position(in_port) - port_size / 2 var rect = Rect2(port_pos, port_size) return rect.has_point(mouse_position) @@ -289,6 +289,12 @@ Emitted at the beginning of a GraphNode movement. </description> </signal> + <signal name="close_nodes_request"> + <param index="0" name="nodes" type="StringName[]" /> + <description> + Emitted when attempting to remove a GraphNode from the GraphEdit. Provides a list of node names to be removed (all selected nodes, excluding nodes without closing button). + </description> + </signal> <signal name="connection_drag_ended"> <description> Emitted at the end of a connection drag. @@ -332,12 +338,6 @@ Emitted when the user presses [kbd]Ctrl + C[/kbd]. </description> </signal> - <signal name="delete_nodes_request"> - <param index="0" name="nodes" type="StringName[]" /> - <description> - Emitted when a GraphNode is attempted to be removed from the GraphEdit. Provides a list of node names to be removed (all selected nodes, excluding nodes without closing button). - </description> - </signal> <signal name="disconnection_request"> <param index="0" name="from_node" type="StringName" /> <param index="1" name="from_port" type="int" /> diff --git a/doc/classes/GraphElement.xml b/doc/classes/GraphElement.xml new file mode 100644 index 0000000000..6c3b6b727a --- /dev/null +++ b/doc/classes/GraphElement.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<class name="GraphElement" inherits="Container" is_experimental="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> + <brief_description> + A container that represents a basic element that can be placed inside a [GraphEdit] control. + </brief_description> + <description> + [GraphElement] allows to create custom elements for a [GraphEdit] graph. By default such elements can be selected, resized, and repositioned, but they cannot be connected. For a graph element that allows for connections see [GraphNode]. + </description> + <tutorials> + </tutorials> + <members> + <member name="draggable" type="bool" setter="set_draggable" getter="is_draggable" default="true"> + If [code]true[/code], the user can drag the GraphElement. + </member> + <member name="position_offset" type="Vector2" setter="set_position_offset" getter="get_position_offset" default="Vector2(0, 0)"> + The offset of the GraphElement, relative to the scroll offset of the [GraphEdit]. + </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. + </member> + <member name="selectable" type="bool" setter="set_selectable" getter="is_selectable" default="true"> + If [code]true[/code], the user can select the GraphElement. + </member> + <member name="selected" type="bool" setter="set_selected" getter="is_selected" default="false"> + If [code]true[/code], the GraphElement is selected. + </member> + </members> + <signals> + <signal name="close_request"> + <description> + Emitted when closing the GraphElement is requested. + </description> + </signal> + <signal name="dragged"> + <param index="0" name="from" type="Vector2" /> + <param index="1" name="to" type="Vector2" /> + <description> + Emitted when the GraphElement is dragged. + </description> + </signal> + <signal name="node_deselected"> + <description> + Emitted when the GraphElement is deselected. + </description> + </signal> + <signal name="node_selected"> + <description> + Emitted when the GraphElement is selected. + </description> + </signal> + <signal name="position_offset_changed"> + <description> + Emitted when the GraphElement is moved. + </description> + </signal> + <signal name="raise_request"> + <description> + Emitted when displaying the GraphElement over other ones is requested. Happens on focusing (clicking into) the GraphElement. + </description> + </signal> + <signal name="resize_request"> + <param index="0" name="new_minsize" type="Vector2" /> + <description> + Emitted when resizing the GraphElement is requested. Happens on dragging the resizer handle (see [member resizable]). + </description> + </signal> + </signals> +</class> diff --git a/doc/classes/GraphNode.xml b/doc/classes/GraphNode.xml index d160141842..5d52ea17e2 100644 --- a/doc/classes/GraphNode.xml +++ b/doc/classes/GraphNode.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="GraphNode" inherits="Container" is_experimental="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> +<class name="GraphNode" inherits="GraphElement" is_experimental="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> A container with connection ports, representing a node in a [GraphEdit]. </brief_description> @@ -7,153 +7,154 @@ [GraphNode] allows to create nodes for a [GraphEdit] graph with customizable content based on its child controls. [GraphNode] is derived from [Container] and it is responsible for placing its children on screen. This works similar to [VBoxContainer]. Children, in turn, provide [GraphNode] with so-called slots, each of which can have a connection port on either side. Each [GraphNode] slot is defined by its index and can provide the node with up to two ports: one on the left, and one on the right. By convention the left port is also referred to as the [b]input port[/b] and the right port is referred to as the [b]output port[/b]. Each port can be enabled and configured individually, using different type and color. The type is an arbitrary value that you can define using your own considerations. The parent [GraphEdit] will receive this information on each connect and disconnect request. Slots can be configured in the Inspector dock once you add at least one child [Control]. The properties are grouped by each slot's index in the "Slot" section. - [b]Note:[/b] While GraphNode is set up using slots and slot indices, connections are made between the ports which are enabled. Because of that, [GraphEdit] uses the port's index and not the slot's index. You can use [method get_connection_input_slot] and [method get_connection_output_slot] to get the slot index from the port index. + [b]Note:[/b] While GraphNode is set up using slots and slot indices, connections are made between the ports which are enabled. Because of that [GraphEdit] uses the port's index and not the slot's index. You can use [method get_input_port_slot] and [method get_output_port_slot] to get the slot index from the port index. </description> <tutorials> </tutorials> <methods> + <method name="_draw_port" qualifiers="virtual"> + <return type="void" /> + <param index="0" name="slot_index" type="int" /> + <param index="1" name="position" type="Vector2i" /> + <param index="2" name="left" type="bool" /> + <param index="3" name="color" type="Color" /> + <description> + </description> + </method> <method name="clear_all_slots"> <return type="void" /> <description> - Disables all input and output slots of the GraphNode. + Disables all slots of the GraphNode. This will remove all input/output ports from the GraphNode. </description> </method> <method name="clear_slot"> <return type="void" /> <param index="0" name="slot_index" type="int" /> <description> - Disables input and output slot whose index is [param slot_index]. + Disables the slot with the given [param slot_index]. This will remove the corresponding input and output port from the GraphNode. </description> </method> - <method name="get_connection_input_color"> + <method name="get_input_port_color"> <return type="Color" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the [Color] of the input connection [param port]. + Returns the [Color] of the input port with the given [param port_idx]. </description> </method> - <method name="get_connection_input_count"> + <method name="get_input_port_count"> <return type="int" /> <description> - Returns the number of enabled input slots (connections) to the GraphNode. + Returns the number of slots with an enabled input port. </description> </method> - <method name="get_connection_input_height"> - <return type="int" /> - <param index="0" name="port" type="int" /> - <description> - Returns the height of the input connection [param port]. - </description> - </method> - <method name="get_connection_input_position"> + <method name="get_input_port_position"> <return type="Vector2" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the position of the input connection [param port]. + Returns the position of the input port with the given [param port_idx]. </description> </method> - <method name="get_connection_input_slot"> + <method name="get_input_port_slot"> <return type="int" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the corresponding slot index of the input connection [param port]. + Returns the corresponding slot index of the input port with the given [param port_idx]. </description> </method> - <method name="get_connection_input_type"> + <method name="get_input_port_type"> <return type="int" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the type of the input connection [param port]. + Returns the type of the input port with the given [param port_idx]. </description> </method> - <method name="get_connection_output_color"> + <method name="get_output_port_color"> <return type="Color" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the [Color] of the output connection [param port]. + Returns the [Color] of the output port with the given [param port_idx]. </description> </method> - <method name="get_connection_output_count"> + <method name="get_output_port_count"> <return type="int" /> <description> - Returns the number of enabled output slots (connections) of the GraphNode. + Returns the number of slots with an enabled output port. </description> </method> - <method name="get_connection_output_height"> - <return type="int" /> - <param index="0" name="port" type="int" /> - <description> - Returns the height of the output connection [param port]. - </description> - </method> - <method name="get_connection_output_position"> + <method name="get_output_port_position"> <return type="Vector2" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the position of the output connection [param port]. + Returns the position of the output port with the given [param port_idx]. </description> </method> - <method name="get_connection_output_slot"> + <method name="get_output_port_slot"> <return type="int" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the corresponding slot index of the output connection [param port]. + Returns the corresponding slot index of the output port with the given [param port_idx]. </description> </method> - <method name="get_connection_output_type"> + <method name="get_output_port_type"> <return type="int" /> - <param index="0" name="port" type="int" /> + <param index="0" name="port_idx" type="int" /> <description> - Returns the type of the output connection [param port]. + Returns the type of the output port with the given [param port_idx]. </description> </method> <method name="get_slot_color_left" qualifiers="const"> <return type="Color" /> <param index="0" name="slot_index" type="int" /> <description> - Returns the left (input) [Color] of the slot [param slot_index]. + Returns the left (input) [Color] of the slot with the given [param slot_index]. </description> </method> <method name="get_slot_color_right" qualifiers="const"> <return type="Color" /> <param index="0" name="slot_index" type="int" /> <description> - Returns the right (output) [Color] of the slot [param slot_index]. + Returns the right (output) [Color] of the slot with the given [param slot_index]. </description> </method> <method name="get_slot_type_left" qualifiers="const"> <return type="int" /> <param index="0" name="slot_index" type="int" /> <description> - Returns the left (input) type of the slot [param slot_index]. + Returns the left (input) type of the slot with the given [param slot_index]. </description> </method> <method name="get_slot_type_right" qualifiers="const"> <return type="int" /> <param index="0" name="slot_index" type="int" /> <description> - Returns the right (output) type of the slot [param slot_index]. + Returns the right (output) type of the slot with the given [param slot_index]. + </description> + </method> + <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> <method name="is_slot_draw_stylebox" qualifiers="const"> <return type="bool" /> <param index="0" name="slot_index" type="int" /> <description> - Returns true if the background [StyleBox] of the slot [param slot_index] is drawn. + Returns true if the background [StyleBox] of the slot with the given [param slot_index] is drawn. </description> </method> <method name="is_slot_enabled_left" qualifiers="const"> <return type="bool" /> <param index="0" name="slot_index" type="int" /> <description> - Returns [code]true[/code] if left (input) side of the slot [param slot_index] is enabled. + Returns [code]true[/code] if left (input) side of the slot with the given [param slot_index] is enabled. </description> </method> <method name="is_slot_enabled_right" qualifiers="const"> <return type="bool" /> <param index="0" name="slot_index" type="int" /> <description> - Returns [code]true[/code] if right (output) side of the slot [param slot_index] is enabled. + Returns [code]true[/code] if right (output) side of the slot with the given [param slot_index] is enabled. </description> </method> <method name="set_slot"> @@ -169,7 +170,7 @@ <param index="8" name="custom_icon_right" type="Texture2D" default="null" /> <param index="9" name="draw_stylebox" type="bool" default="true" /> <description> - Sets properties of the slot with the [param slot_index] index. + Sets properties of the slot with the given [param slot_index]. If [param enable_left_port]/[param enable_right_port] is [code]true[/code], a port will appear and the slot will be able to be connected from this side. With [param type_left]/[param type_right] an arbitrary type can be assigned to each port. Two ports can be connected if they share the same type, or if the connection between their types is allowed in the parent [GraphEdit] (see [method GraphEdit.add_valid_connection_type]). Keep in mind that the [GraphEdit] has the final say in accepting the connection. Type compatibility simply allows the [signal GraphEdit.connection_request] signal to be emitted. Ports can be further customized using [param color_left]/[param color_right] and [param custom_icon_left]/[param custom_icon_right]. The color parameter adds a tint to the icon. The custom icon can be used to override the default port dot. @@ -183,7 +184,7 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="color" type="Color" /> <description> - Sets the [Color] of the left (input) side of the slot [param slot_index] to [param color]. + Sets the [Color] of the left (input) side of the slot with the given [param slot_index] to [param color]. </description> </method> <method name="set_slot_color_right"> @@ -191,7 +192,7 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="color" type="Color" /> <description> - Sets the [Color] of the right (output) side of the slot [param slot_index] to [param color]. + Sets the [Color] of the right (output) side of the slot with the given [param slot_index] to [param color]. </description> </method> <method name="set_slot_draw_stylebox"> @@ -199,7 +200,7 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="enable" type="bool" /> <description> - Toggles the background [StyleBox] of the slot [param slot_index]. + Toggles the background [StyleBox] of the slot with the given [param slot_index]. </description> </method> <method name="set_slot_enabled_left"> @@ -207,7 +208,7 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="enable" type="bool" /> <description> - Toggles the left (input) side of the slot [param slot_index]. If [param enable] is [code]true[/code], a port will appear on the left side and the slot will be able to be connected from this side. + Toggles the left (input) side of the slot with the given [param slot_index]. If [param enable] is [code]true[/code], a port will appear on the left side and the slot will be able to be connected from this side. </description> </method> <method name="set_slot_enabled_right"> @@ -215,7 +216,7 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="enable" type="bool" /> <description> - Toggles the right (output) side of the slot [param slot_index]. If [param enable] is [code]true[/code], a port will appear on the right side and the slot will be able to be connected from this side. + Toggles the right (output) side of the slot with the given [param slot_index]. If [param enable] is [code]true[/code], a port will appear on the right side and the slot will be able to be connected from this side. </description> </method> <method name="set_slot_type_left"> @@ -223,7 +224,7 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="type" type="int" /> <description> - Sets the left (input) type of the slot [param slot_index] to [param type]. If the value is negative, all connections will be disallowed to be created via user inputs. + Sets the left (input) type of the slot with the given [param slot_index] to [param type]. If the value is negative, all connections will be disallowed to be created via user inputs. </description> </method> <method name="set_slot_type_right"> @@ -231,156 +232,54 @@ <param index="0" name="slot_index" type="int" /> <param index="1" name="type" type="int" /> <description> - Sets the right (output) type of the slot [param slot_index] to [param type]. If the value is negative, all connections will be disallowed to be created via user inputs. + Sets the right (output) type of the slot with the given [param slot_index] to [param type]. If the value is negative, all connections will be disallowed to be created via user inputs. </description> </method> </methods> <members> - <member name="draggable" type="bool" setter="set_draggable" getter="is_draggable" default="true"> - If [code]true[/code], the user can drag the GraphNode. - </member> - <member name="language" type="String" setter="set_language" getter="get_language" default=""""> - Language code used for line-breaking and text shaping algorithms, if left empty current locale is used instead. - </member> <member name="mouse_filter" type="int" setter="set_mouse_filter" getter="get_mouse_filter" overrides="Control" enum="Control.MouseFilter" default="0" /> - <member name="overlay" type="int" setter="set_overlay" getter="get_overlay" enum="GraphNode.Overlay" default="0"> - Sets the overlay shown above the GraphNode. See [enum Overlay]. - </member> - <member name="position_offset" type="Vector2" setter="set_position_offset" getter="get_position_offset" default="Vector2(0, 0)"> - The offset of the GraphNode, relative to the scroll offset of the [GraphEdit]. - [b]Note:[/b] You cannot use position offset directly, as [GraphEdit] is a [Container]. - </member> - <member name="resizable" type="bool" setter="set_resizable" getter="is_resizable" default="false"> - If [code]true[/code], the user can resize the GraphNode. - [b]Note:[/b] Dragging the handle will only emit the [signal resize_request] signal, the GraphNode 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 GraphNode. - </member> - <member name="selected" type="bool" setter="set_selected" getter="is_selected" default="false"> - If [code]true[/code], the GraphNode is selected. - </member> - <member name="show_close" type="bool" setter="set_show_close_button" getter="is_close_button_visible" default="false"> - If [code]true[/code], the close button will be visible. - [b]Note:[/b] Pressing it will only emit the [signal close_request] signal, the GraphNode needs to be removed manually. - </member> - <member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0"> - Base text writing direction. - </member> <member name="title" type="String" setter="set_title" getter="get_title" default=""""> The text displayed in the GraphNode's title bar. </member> </members> <signals> - <signal name="close_request"> - <description> - Emitted when the GraphNode is requested to be closed. Happens on clicking the close button (see [member show_close]). - </description> - </signal> - <signal name="dragged"> - <param index="0" name="from" type="Vector2" /> - <param index="1" name="to" type="Vector2" /> - <description> - Emitted when the GraphNode is dragged. - </description> - </signal> - <signal name="node_deselected"> - <description> - Emitted when the GraphNode is deselected. - </description> - </signal> - <signal name="node_selected"> - <description> - Emitted when the GraphNode is selected. - </description> - </signal> - <signal name="position_offset_changed"> - <description> - Emitted when the GraphNode is moved. - </description> - </signal> - <signal name="raise_request"> - <description> - Emitted when the GraphNode is requested to be displayed over other ones. Happens on focusing (clicking into) the GraphNode. - </description> - </signal> - <signal name="resize_request"> - <param index="0" name="new_minsize" type="Vector2" /> - <description> - Emitted when the GraphNode is requested to be resized. Happens on dragging the resizer handle (see [member resizable]). - </description> - </signal> <signal name="slot_updated"> - <param index="0" name="idx" type="int" /> + <param index="0" name="slot_index" type="int" /> <description> Emitted when any GraphNode's slot is updated. </description> </signal> </signals> - <constants> - <constant name="OVERLAY_DISABLED" value="0" enum="Overlay"> - No overlay is shown. - </constant> - <constant name="OVERLAY_BREAKPOINT" value="1" enum="Overlay"> - Show overlay set in the [theme_item breakpoint] theme property. - </constant> - <constant name="OVERLAY_POSITION" value="2" enum="Overlay"> - Show overlay set in the [theme_item position] theme property. - </constant> - </constants> <theme_items> - <theme_item name="close_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - The color modulation applied to the close button icon. - </theme_item> <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="title_color" data_type="color" type="Color" default="Color(0.875, 0.875, 0.875, 1)"> - Color of the title text. - </theme_item> - <theme_item name="close_h_offset" data_type="constant" type="int" default="12"> - </theme_item> - <theme_item name="close_offset" data_type="constant" type="int" default="22"> - The vertical offset of the close button. - </theme_item> - <theme_item name="port_offset" data_type="constant" type="int" default="0"> + <theme_item name="port_h_offset" data_type="constant" type="int" default="0"> Horizontal offset for the ports. </theme_item> <theme_item name="separation" data_type="constant" type="int" default="2"> The vertical distance between ports. </theme_item> - <theme_item name="title_h_offset" data_type="constant" type="int" default="0"> - Horizontal offset of the title text. - </theme_item> - <theme_item name="title_offset" data_type="constant" type="int" default="26"> - Vertical offset of the title text. - </theme_item> - <theme_item name="title_font" data_type="font" type="Font"> - Font used for the title text. - </theme_item> - <theme_item name="close" data_type="icon" type="Texture2D"> - The icon for the close button, visible when [member show_close] is enabled. - </theme_item> <theme_item name="port" data_type="icon" type="Texture2D"> The icon used for representing ports. </theme_item> <theme_item name="resizer" data_type="icon" type="Texture2D"> - The icon used for resizer, visible when [member resizable] is enabled. - </theme_item> - <theme_item name="breakpoint" data_type="style" type="StyleBox"> - The background used when [member overlay] is set to [constant OVERLAY_BREAKPOINT]. + The icon used for the resizer, visible when [member GraphElement.resizable] is enabled. </theme_item> - <theme_item name="frame" data_type="style" type="StyleBox"> - The default background for [GraphNode]. + <theme_item name="panel" data_type="style" type="StyleBox"> + The default background for the slot area of the [GraphNode]. </theme_item> - <theme_item name="position" data_type="style" type="StyleBox"> - The background used when [member overlay] is set to [constant OVERLAY_POSITION]. - </theme_item> - <theme_item name="selected_frame" data_type="style" type="StyleBox"> - The background used when the [GraphNode] is selected. + <theme_item name="panel_selected" data_type="style" type="StyleBox"> + The [StyleBox] used for the slot area when selected. </theme_item> <theme_item name="slot" data_type="style" type="StyleBox"> The [StyleBox] used for each slot of the [GraphNode]. </theme_item> + <theme_item name="titlebar" data_type="style" type="StyleBox"> + The [StyleBox] used for the title bar of the [GraphNode]. + </theme_item> + <theme_item name="titlebar_selected" data_type="style" type="StyleBox"> + The [StyleBox] used for the title bar of the [GraphNode] when it is selected. + </theme_item> </theme_items> </class> diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index f605c44dd0..311e532e63 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1830,8 +1830,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { // GraphEdit theme->set_stylebox("panel", "GraphEdit", style_tree_bg); if (dark_theme) { - theme->set_color("grid_major", "GraphEdit", Color(1.0, 1.0, 1.0, 0.15)); - theme->set_color("grid_minor", "GraphEdit", Color(1.0, 1.0, 1.0, 0.07)); + theme->set_color("grid_major", "GraphEdit", Color(1.0, 1.0, 1.0, 0.1)); + theme->set_color("grid_minor", "GraphEdit", Color(1.0, 1.0, 1.0, 0.05)); } else { theme->set_color("grid_major", "GraphEdit", Color(0.0, 0.0, 0.0, 0.15)); theme->set_color("grid_minor", "GraphEdit", Color(0.0, 0.0, 0.0, 0.07)); @@ -1866,6 +1866,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { style_minimap_node = make_flat_stylebox(Color(0, 0, 0), 0, 0, 0, 0); } style_minimap_camera->set_border_width_all(1); + style_minimap_node->set_anti_aliased(false); theme->set_stylebox("camera", "GraphEditMinimap", style_minimap_camera); theme->set_stylebox("node", "GraphEditMinimap", style_minimap_node); @@ -1879,38 +1880,43 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_color("resizer_color", "GraphEditMinimap", minimap_resizer_color); // GraphNode + + const int gn_margin_top = 2; const int gn_margin_side = 2; const int gn_margin_bottom = 2; - // StateMachine - const int sm_margin_side = 10; - Color graphnode_bg = dark_color_3; if (!dark_theme) { graphnode_bg = prop_section_color; } + const Color graph_node_selected_border_color = graphnode_bg.lerp(accent_color, 0.275); + + const Color graphnode_frame_bg = graphnode_bg.lerp(style_tree_bg->get_bg_color(), 0.3); + + Ref<StyleBoxFlat> graphn_sb_panel = make_flat_stylebox(graphnode_frame_bg, gn_margin_side, gn_margin_top, gn_margin_side, gn_margin_bottom, corner_width); + graphn_sb_panel->set_border_width_all(border_width); + graphn_sb_panel->set_border_color(graphnode_bg); + graphn_sb_panel->set_corner_radius_individual(0, 0, corner_radius * EDSCALE, corner_radius * EDSCALE); + graphn_sb_panel->set_expand_margin(SIDE_TOP, 17 * EDSCALE); - Ref<StyleBoxFlat> graphsb = make_flat_stylebox(graphnode_bg.lerp(style_tree_bg->get_bg_color(), 0.3), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width); - graphsb->set_border_width_all(border_width); - graphsb->set_border_color(graphnode_bg); - Ref<StyleBoxFlat> graphsbselected = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 1), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width); - graphsbselected->set_border_width_all(2 * EDSCALE + border_width); - graphsbselected->set_border_color(Color(accent_color.r, accent_color.g, accent_color.b, 0.6)); - Ref<StyleBoxFlat> graphsbcomment = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 0.3), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width); - graphsbcomment->set_border_width_all(border_width); - graphsbcomment->set_border_color(graphnode_bg); - Ref<StyleBoxFlat> graphsbcommentselected = make_flat_stylebox(graphnode_bg * Color(1, 1, 1, 0.4), gn_margin_side, 24, gn_margin_side, gn_margin_bottom, corner_width); - graphsbcommentselected->set_border_width_all(border_width); - graphsbcommentselected->set_border_color(graphnode_bg); - Ref<StyleBoxFlat> graphsbbreakpoint = graphsbselected->duplicate(); - graphsbbreakpoint->set_draw_center(false); - graphsbbreakpoint->set_border_color(warning_color); - graphsbbreakpoint->set_shadow_color(warning_color * Color(1.0, 1.0, 1.0, 0.1)); - Ref<StyleBoxFlat> graphsbposition = graphsbselected->duplicate(); - graphsbposition->set_draw_center(false); - graphsbposition->set_border_color(error_color); - graphsbposition->set_shadow_color(error_color * Color(1.0, 1.0, 1.0, 0.2)); - Ref<StyleBoxEmpty> graphsbslot = make_empty_stylebox(12, 0, 12, 0); + Ref<StyleBoxFlat> graphn_sb_panel_selected = make_flat_stylebox(graphnode_frame_bg, gn_margin_side, gn_margin_top, gn_margin_side, gn_margin_bottom, corner_width); + graphn_sb_panel_selected->set_border_width_all(2 * EDSCALE + border_width); + graphn_sb_panel_selected->set_border_color(graph_node_selected_border_color); + graphn_sb_panel_selected->set_corner_radius_individual(0, 0, corner_radius * EDSCALE, corner_radius * EDSCALE); + graphn_sb_panel_selected->set_expand_margin(SIDE_TOP, 17 * EDSCALE); + + const int gn_titlebar_margin_side = 12; + Ref<StyleBoxFlat> graphn_sb_titlebar = make_flat_stylebox(graphnode_bg, gn_titlebar_margin_side, gn_margin_top, gn_titlebar_margin_side, 0, corner_width); + graphn_sb_titlebar->set_expand_margin(SIDE_TOP, 2 * EDSCALE); + graphn_sb_titlebar->set_corner_radius_individual(corner_radius * EDSCALE, corner_radius * EDSCALE, 0, 0); + + Ref<StyleBoxFlat> graphn_sb_titlebar_selected = make_flat_stylebox(graph_node_selected_border_color, gn_titlebar_margin_side, gn_margin_top, gn_titlebar_margin_side, 0, corner_width); + graphn_sb_titlebar_selected->set_corner_radius_individual(corner_radius * EDSCALE, corner_radius * EDSCALE, 0, 0); + graphn_sb_titlebar_selected->set_expand_margin(SIDE_TOP, 2 * EDSCALE); + Ref<StyleBoxEmpty> graphn_sb_slot = make_empty_stylebox(12, 0, 12, 0); + + // StateMachine. + const int sm_margin_side = 10; Ref<StyleBoxFlat> smgraphsb = make_flat_stylebox(dark_color_3 * Color(1, 1, 1, 0.7), sm_margin_side, 24, sm_margin_side, gn_margin_bottom, corner_width); smgraphsb->set_border_width_all(border_width); smgraphsb->set_border_color(graphnode_bg); @@ -1920,45 +1926,42 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { smgraphsbselected->set_shadow_size(8 * EDSCALE); smgraphsbselected->set_shadow_color(shadow_color); - graphsb->set_border_width(SIDE_TOP, 24 * EDSCALE); - graphsbselected->set_border_width(SIDE_TOP, 24 * EDSCALE); - graphsbcomment->set_border_width(SIDE_TOP, 24 * EDSCALE); - graphsbcommentselected->set_border_width(SIDE_TOP, 24 * EDSCALE); - - graphsb->set_corner_detail(corner_radius * EDSCALE); - graphsbselected->set_corner_detail(corner_radius * EDSCALE); - graphsbcomment->set_corner_detail(corner_radius * EDSCALE); - graphsbcommentselected->set_corner_detail(corner_radius * EDSCALE); - - theme->set_stylebox("frame", "GraphNode", graphsb); - theme->set_stylebox("selected_frame", "GraphNode", graphsbselected); - theme->set_stylebox("breakpoint", "GraphNode", graphsbbreakpoint); - theme->set_stylebox("position", "GraphNode", graphsbposition); - theme->set_stylebox("slot", "GraphNode", graphsbslot); - theme->set_stylebox("state_machine_frame", "GraphNode", smgraphsb); - theme->set_stylebox("state_machine_selected_frame", "GraphNode", smgraphsbselected); + theme->set_stylebox("panel", "GraphElement", graphn_sb_panel); + theme->set_stylebox("panel_selected", "GraphElement", graphn_sb_panel_selected); + theme->set_stylebox("titlebar", "GraphElement", graphn_sb_titlebar); + theme->set_stylebox("titlebar_selected", "GraphElement", graphn_sb_titlebar_selected); + + // GraphNode's title Label. + theme->set_type_variation("GraphNodeTitleLabel", "Label"); + + theme->set_stylebox("normal", "GraphNodeTitleLabel", make_empty_stylebox(0, 0, 0, 0)); + theme->set_color("font_color", "GraphNodeTitleLabel", font_color); + theme->set_constant("line_spacing", "GraphNodeTitleLabel", 3 * EDSCALE); + + Color graphnode_decoration_color = dark_color_1.inverted(); + + theme->set_color("resizer_color", "GraphElement", graphnode_decoration_color); + theme->set_icon("resizer", "GraphElement", theme->get_icon(SNAME("GuiResizer"), EditorStringName(EditorIcons))); - Color node_decoration_color = dark_color_1.inverted(); - theme->set_color("title_color", "GraphNode", node_decoration_color); - node_decoration_color.a = 0.7; - theme->set_color("close_color", "GraphNode", node_decoration_color); - theme->set_color("resizer_color", "GraphNode", node_decoration_color); - - theme->set_constant("port_offset", "GraphNode", 0); - theme->set_constant("title_h_offset", "GraphNode", 12 * EDSCALE); - theme->set_constant("title_offset", "GraphNode", 21 * EDSCALE); - theme->set_constant("close_h_offset", "GraphNode", -2 * EDSCALE); - theme->set_constant("close_offset", "GraphNode", 20 * EDSCALE); + // GraphNode. + theme->set_stylebox("panel", "GraphNode", graphn_sb_panel); + theme->set_stylebox("panel_selected", "GraphNode", graphn_sb_panel_selected); + theme->set_stylebox("titlebar", "GraphNode", graphn_sb_titlebar); + theme->set_stylebox("titlebar_selected", "GraphNode", graphn_sb_titlebar_selected); + theme->set_stylebox("slot", "GraphNode", graphn_sb_slot); + + theme->set_color("resizer_color", "GraphNode", graphnode_decoration_color); + + theme->set_constant("port_h_offset", "GraphNode", 0); theme->set_constant("separation", "GraphNode", 1 * EDSCALE); - theme->set_icon("close", "GraphNode", theme->get_icon(SNAME("GuiCloseCustomizable"), EditorStringName(EditorIcons))); - theme->set_icon("resizer", "GraphNode", theme->get_icon(SNAME("GuiResizer"), EditorStringName(EditorIcons))); Ref<ImageTexture> port_icon = theme->get_icon(SNAME("GuiGraphNodePort"), EditorStringName(EditorIcons)); // The true size is 24x24 This is necessary for sharp port icons at high zoom levels in GraphEdit (up to ~200%). port_icon->set_size_override(Size2(12, 12)); theme->set_icon("port", "GraphNode", port_icon); - theme->set_font("title_font", "GraphNode", theme->get_font(SNAME("main_bold_msdf"), EditorStringName(EditorFonts))); + theme->set_stylebox("state_machine_frame", "GraphNode", smgraphsb); + theme->set_stylebox("state_machine_selected_frame", "GraphNode", smgraphsbselected); // GridContainer theme->set_constant("v_separation", "GridContainer", Math::round(widget_default_margin.y - 2 * EDSCALE)); diff --git a/editor/icons/GraphEdit.svg b/editor/icons/GraphEdit.svg index b5b21a084e..fd633a4662 100644 --- a/editor/icons/GraphEdit.svg +++ b/editor/icons/GraphEdit.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a2 2 0 0 0-1 3.73v6.541a2 2 0 1 0 2 0V5.414l4.793 4.793 1.414-1.414L5.414 4h5.857a2 2 0 1 0 0-2H4.729A2 2 0 0 0 3 1zm10.656 6.93-.707.707 1.414 1.414.707-.707zm-1.414 1.414-3.889 3.889L8 15l1.767-.353 3.889-3.89-1.414-1.413z" fill="#8eef97"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M11 1c-.554 0-1 .446-1 1v3c0 .554.446 1 1 1h3c.554 0 1-.446 1-1V2c0-.554-.446-1-1-1h-3zM9 3.883L6.732 5.016c.17.292.268.628.268.984v1.117l2.268-1.133A1.964 1.964 0 019 5V3.883zM2 5c-.554 0-1 .446-1 1v4c0 .554.446 1 1 1h3c.554 0 1-.446 1-1V6c0-.554-.446-1-1-1H2zm5 3.883V10c0 .356-.099.692-.268.984L9 12.117V11c0-.356.099-.692.268-.984L7 8.883zM11 10c-.554 0-1 .446-1 1v3c0 .554.446 1 1 1h3c.554 0 1-.446 1-1v-3c0-.554-.446-1-1-1h-3z" color="#000" fill="#8eef97"/><path d="M5 3h6" fill="none"/><rect width="3" height="1" x="-5" y="6.5" ry=".5" fill="#4a4a4a"/></svg> diff --git a/editor/icons/GraphElement.svg b/editor/icons/GraphElement.svg new file mode 100644 index 0000000000..5629993c25 --- /dev/null +++ b/editor/icons/GraphElement.svg @@ -0,0 +1 @@ +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M8 2a6 6 0 00-6 6 6 6 0 006 6 6 6 0 006-6 6 6 0 00-6-6zm0 2a4 4 0 014 4 4 4 0 01-4 4 4 4 0 01-4-4 4 4 0 014-4zm0 2a2 2 0 100 4 2 2 0 000-4z" fill="#8eef97"/></svg> diff --git a/editor/icons/GraphNode.svg b/editor/icons/GraphNode.svg index 31316ea32c..9bef701ffd 100644 --- a/editor/icons/GraphNode.svg +++ b/editor/icons/GraphNode.svg @@ -1 +1 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 1a2 2 0 0 0-1 3.73v6.541a2 2 0 1 0 2 0V5.414L8.086 9.5 9.5 8.086 5.414 4h5.857a2 2 0 1 0 0-2H4.729A2 2 0 0 0 3 1zm9.5 9a2.5 2.5 0 0 0 0 5 2.5 2.5 0 0 0 0-5z" fill="#8eef97"/></svg> +<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M4.846 2A1.841 1.841 0 003 3.846V4h10v-.154A1.841 1.841 0 0011.154 2H4.846zM3 5v1a3 3 0 013 3 3 3 0 01-3 3v.154C3 13.177 3.823 14 4.846 14h6.308A1.841 1.841 0 0013 12.154V12a3 3 0 01-3-3 3 3 0 013-3V5H3zm0 2a2 2 0 00-2 2 2 2 0 002 2 2 2 0 002-2 2 2 0 00-2-2zm10 0a2 2 0 00-2 2 2 2 0 002 2 2 2 0 002-2 2 2 0 00-2-2z" fill="#8eef97"/></svg> diff --git a/editor/icons/GuiCloseCustomizable.svg b/editor/icons/GuiCloseCustomizable.svg deleted file mode 100644 index 81822a3aaf..0000000000 --- a/editor/icons/GuiCloseCustomizable.svg +++ /dev/null @@ -1 +0,0 @@ -<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m3 3 10 10M3 13 13 3" fill="none" stroke="#fff" stroke-width="2" stroke-opacity=".898"/></svg> diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index f45b160944..ac3be4ba84 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -169,8 +169,8 @@ 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; - node->set_show_close_button(true); - node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_request).bind(E), CONNECT_DEFERRED); + agnode->set_closable(true); + node->connect("close_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_close_request).bind(E), CONNECT_DEFERRED); } for (int i = 0; i < agnode->get_input_count(); i++) { @@ -263,7 +263,7 @@ void AnimationNodeBlendTreeEditor::update_graph() { mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected).bind(options, E), CONNECT_DEFERRED); } - Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SNAME("frame"), SNAME("GraphNode")); + Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SNAME("panel"), SNAME("GraphNode")); Color c = sb->get_border_color(); Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); mono_color.a = 0.85; @@ -498,7 +498,7 @@ void AnimationNodeBlendTreeEditor::_anim_selected(int p_index, Array p_options, undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { +void AnimationNodeBlendTreeEditor::_close_request(const String &p_which) { if (read_only) { return; } @@ -522,7 +522,7 @@ void AnimationNodeBlendTreeEditor::_delete_request(const String &p_which) { undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<StringName> &p_nodes) { +void AnimationNodeBlendTreeEditor::_close_nodes_request(const TypedArray<StringName> &p_nodes) { if (read_only) { return; } @@ -532,15 +532,19 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<String if (p_nodes.is_empty()) { for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - if (gn->is_selected() && gn->is_close_button_visible()) { + if (gn && gn->is_selected()) { + Ref<AnimationNode> anode = blend_tree->get_node(gn->get_name()); + if (anode->is_closable()) { to_erase.push_back(gn->get_name()); } } } } else { for (int i = 0; i < p_nodes.size(); i++) { - to_erase.push_back(p_nodes[i]); + Ref<AnimationNode> anode = blend_tree->get_node(p_nodes[i]); + if (anode->is_closable()) { + to_erase.push_back(p_nodes[i]); + } } } @@ -552,7 +556,7 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request(const TypedArray<String undo_redo->create_action(TTR("Delete Node(s)")); for (const StringName &F : to_erase) { - _delete_request(F); + _close_request(F); } undo_redo->commit_action(); @@ -1083,7 +1087,7 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { graph->connect("disconnection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_disconnection_request), CONNECT_DEFERRED); graph->connect("node_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_selected)); graph->connect("scroll_offset_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_scroll_changed)); - graph->connect("delete_nodes_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_nodes_request)); + graph->connect("close_nodes_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_close_nodes_request)); graph->connect("popup_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_request)); graph->connect("connection_to_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_to_empty)); graph->connect("connection_from_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_from_empty)); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index 90fb42cd7a..49c8d951ef 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -109,8 +109,8 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _node_selected(Object *p_node); void _open_in_editor(const String &p_which); void _anim_selected(int p_index, Array p_options, const String &p_node); - void _delete_request(const String &p_which); - void _delete_nodes_request(const TypedArray<StringName> &p_nodes); + void _close_request(const String &p_which); + void _close_nodes_request(const TypedArray<StringName> &p_nodes); bool _update_filters(const Ref<AnimationNode> &anode); void _inspect_filters(const String &p_which); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index b6330bdf75..8e1862d88b 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -144,22 +144,26 @@ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p bool is_dirty = link.preview_pos < 0; if (!is_dirty && link.preview_visible && link.preview_box != nullptr) { - link.graph_node->remove_child(link.preview_box); + link.graph_element->remove_child(link.preview_box); memdelete(link.preview_box); link.preview_box = nullptr; - link.graph_node->reset_size(); + link.graph_element->reset_size(); link.preview_visible = false; } if (p_port_id != -1 && link.output_ports[p_port_id].preview_button != nullptr) { if (is_dirty) { - link.preview_pos = link.graph_node->get_child_count(); + link.preview_pos = link.graph_element->get_child_count(); } VBoxContainer *vbox = memnew(VBoxContainer); - link.graph_node->add_child(vbox); - link.graph_node->move_child(vbox, link.preview_pos); - link.graph_node->set_slot_draw_stylebox(vbox->get_index(), false); + link.graph_element->add_child(vbox); + link.graph_element->move_child(vbox, link.preview_pos); + + GraphNode *graph_node = Object::cast_to<GraphNode>(link.graph_element); + if (graph_node) { + graph_node->set_slot_draw_stylebox(vbox->get_index(false), false); + } Control *offset = memnew(Control); offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); @@ -293,7 +297,7 @@ void VisualShaderGraphPlugin::update_node_size(int p_node_id) { if (!links.has(p_node_id)) { return; } - links[p_node_id].graph_node->reset_size(); + 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) { @@ -324,7 +328,7 @@ VisualShader::Type VisualShaderGraphPlugin::get_shader_type() const { void VisualShaderGraphPlugin::set_node_position(VisualShader::Type p_type, int p_id, const Vector2 &p_position) { if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { - links[p_id].graph_node->set_position_offset(p_position); + links[p_id].graph_element->set_position_offset(p_position); } } @@ -336,8 +340,8 @@ void VisualShaderGraphPlugin::clear_links() { links.clear(); } -void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node) { - links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, HashMap<int, InputPort>(), HashMap<int, Port>(), nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } }); +void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphElement *p_graph_element) { + links.insert(p_id, { p_type, p_visual_node, p_graph_element, p_visual_node->get_output_port_for_preview() != -1, -1, HashMap<int, InputPort>(), HashMap<int, Port>(), nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } }); } void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) { @@ -415,42 +419,49 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool // Visual shader specific theme for MSDF font. Ref<Theme> vstheme; vstheme.instantiate(); - Ref<Font> label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", EditorStringName(EditorFonts)); + Ref<Font> label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", EditorStringName(EditorIcons)); + Ref<Font> label_bold_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_bold_msdf", EditorStringName(EditorIcons)); vstheme->set_font("font", "Label", label_font); + vstheme->set_font("font", "GraphNodeTitleLabel", label_bold_font); vstheme->set_font("font", "LineEdit", label_font); vstheme->set_font("font", "Button", label_font); Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id); - Ref<VisualShaderNodeResizableBase> resizable_node = Object::cast_to<VisualShaderNodeResizableBase>(vsnode.ptr()); - bool is_resizable = !resizable_node.is_null(); + Ref<VisualShaderNodeResizableBase> resizable_node = vsnode; + bool is_resizable = resizable_node.is_valid(); Size2 size = Size2(0, 0); - Ref<VisualShaderNodeGroupBase> group_node = Object::cast_to<VisualShaderNodeGroupBase>(vsnode.ptr()); - bool is_group = !group_node.is_null(); + Ref<VisualShaderNodeGroupBase> group_node = vsnode; + bool is_group = group_node.is_valid(); - Ref<VisualShaderNodeComment> comment_node = Object::cast_to<VisualShaderNodeComment>(vsnode.ptr()); + Ref<VisualShaderNodeComment> comment_node = vsnode; bool is_comment = comment_node.is_valid(); - Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(group_node.ptr()); - bool is_expression = !expression_node.is_null(); + Ref<VisualShaderNodeExpression> expression_node = group_node; + bool is_expression = expression_node.is_valid(); String expression = ""; - VisualShaderNodeCustom *custom_node = Object::cast_to<VisualShaderNodeCustom>(vsnode.ptr()); - if (custom_node) { + Ref<VisualShaderNodeCustom> custom_node = vsnode; + if (custom_node.is_valid()) { custom_node->_set_initialized(true); } - // Create graph node. GraphNode *node = memnew(GraphNode); + node->set_title(vsnode->get_caption()); + + // All nodes are closable except the output node. + if (p_id >= 2) { + vsnode->set_closable(true); + node->connect("close_request", callable_mp(editor, &VisualShaderEditor::_close_node_request).bind(p_type, p_id), CONNECT_DEFERRED); + } graph->add_child(node); node->set_theme(vstheme); - editor->_update_created_node(node); if (p_just_update) { Link &link = links[p_id]; - link.graph_node = node; + link.graph_element = node; link.preview_box = nullptr; link.preview_pos = -1; link.output_ports.clear(); @@ -474,19 +485,18 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool } node->set_position_offset(visual_shader->get_node_position(p_type, p_id)); - node->set_title(vsnode->get_caption()); - node->set_name(itos(p_id)); - if (p_id >= 2) { - node->set_show_close_button(true); - node->connect("close_request", callable_mp(editor, &VisualShaderEditor::_delete_node_request).bind(p_type, p_id), CONNECT_DEFERRED); - } + node->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); + } + Control *content_offset = memnew(Control); content_offset->set_custom_minimum_size(Size2(0, 5 * EDSCALE)); node->add_child(content_offset); @@ -495,10 +505,6 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool port_offset += 1; } - if (is_resizable) { - editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); - } - Ref<VisualShaderNodeParticleEmit> emit = vsnode; if (emit.is_valid()) { node->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); @@ -608,7 +614,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool if (custom_editor) { if (is_curve || (hb == nullptr && !vsnode->is_use_prop_slots() && (vsnode->get_output_port_count() == 0 || vsnode->get_output_port_name(0) == "") && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == ""))) { - //will be embedded in first port + // Will be embedded in first port. } else { port_offset++; node->add_child(custom_editor); @@ -896,93 +902,97 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool if (!is_first_hbox) { idx = i + port_offset; } - node->set_slot(idx, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); + if (!is_comment) { + GraphNode *graph_node = Object::cast_to<GraphNode>(node); - if (vsnode->_is_output_port_expanded(i)) { - switch (vsnode->get_output_port_type(i)) { - case VisualShaderNode::PORT_TYPE_VECTOR_2D: { - port_offset++; - valid_left = (i + 1) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 1); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); - port_offset++; + graph_node->set_slot(idx, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); - valid_left = (i + 2) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 2); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); - - expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_2D; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_3D: { - port_offset++; - valid_left = (i + 1) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 1); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); - port_offset++; + if (vsnode->_is_output_port_expanded(i)) { + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 2) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 2); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); - port_offset++; + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_2D; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 3) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 3); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; - expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_3D; - } break; - case VisualShaderNode::PORT_TYPE_VECTOR_4D: { - port_offset++; - valid_left = (i + 1) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 1); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); - port_offset++; + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_3D; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 2) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 2); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); - port_offset++; + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; - valid_left = (i + 3) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 3); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); - port_offset++; + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + port_offset++; - valid_left = (i + 4) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 4); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[3]); + valid_left = (i + 4) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 4); + } + graph_node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[3]); - expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_4D; - } break; - default: - break; + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_4D; + } break; + default: + break; + } } } } @@ -1005,6 +1015,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool Label *error_label = memnew(Label); error_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("error_color"), EditorStringName(Editor))); error_label->set_text(error); + error_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD); node->add_child(error_label); } @@ -1061,16 +1072,13 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id, bool expression_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_expression_focus_out).bind(expression_box, p_id)); } - - if (is_comment) { - graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content - } } void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id, bool p_just_update) { if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { - links[p_id].graph_node->get_parent()->remove_child(links[p_id].graph_node); - memdelete(links[p_id].graph_node); + Node *graph_edit_node = links[p_id].graph_element->get_parent(); + graph_edit_node->remove_child(links[p_id].graph_element); + memdelete(links[p_id].graph_element); if (!p_just_update) { links.erase(p_id); } @@ -1374,7 +1382,7 @@ void VisualShaderEditor::_update_custom_script(const Ref<Script> &p_script) { if (vsnode.is_null()) { continue; } - Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); + Ref<VisualShaderNodeCustom> custom_node = vsnode; if (custom_node.is_null() || custom_node->get_script() != p_script) { continue; } @@ -1486,7 +1494,7 @@ void VisualShaderEditor::_resources_removed() { if (vsnode.is_null()) { continue; } - Ref<VisualShaderNodeCustom> custom_node = Ref<VisualShaderNodeCustom>(vsnode.ptr()); + Ref<VisualShaderNodeCustom> custom_node = vsnode; if (custom_node.is_null() || custom_node->get_script() != scr) { continue; } @@ -1916,8 +1924,8 @@ Size2 VisualShaderEditor::get_minimum_size() const { return Size2(10, 200); } -void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) { - Button *button = Object::cast_to<Button>(obj); +void VisualShaderEditor::_draw_color_over_button(Object *p_obj, Color p_color) { + Button *button = Object::cast_to<Button>(p_obj); if (!button) { return; } @@ -1926,18 +1934,6 @@ void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) { button->draw_rect(Rect2(normal->get_offset(), button->get_size() - normal->get_minimum_size()), p_color); } -void VisualShaderEditor::_update_created_node(GraphNode *node) { - const Ref<StyleBoxFlat> sb = node->get_theme_stylebox(SNAME("frame"), SNAME("GraphNode")); - Color c = sb->get_border_color(); - const Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0, 0.85) : Color(0.0, 0.0, 0.0, 0.85); - c = mono_color; - - node->add_theme_color_override("title_color", c); - c.a = 0.7; - node->add_theme_color_override("close_color", c); - node->add_theme_color_override("resizer_color", c); -} - void VisualShaderEditor::_update_parameters(bool p_update_refs) { VisualShaderNodeParameterRef::clear_parameters(visual_shader->get_rid()); @@ -2025,9 +2021,9 @@ void VisualShaderEditor::_update_graph() { VisualShader::Type type = get_current_shader_type(); graph->clear_connections(); - //erase all nodes + // Remove all nodes. for (int i = 0; i < graph->get_child_count(); i++) { - if (Object::cast_to<GraphNode>(graph->get_child(i))) { + if (Object::cast_to<GraphElement>(graph->get_child(i))) { Node *node = graph->get_child(i); graph->remove_child(node); memdelete(node); @@ -2406,14 +2402,14 @@ void VisualShaderEditor::_remove_output_port(int p_node, int p_port) { undo_redo->commit_action(); } -void VisualShaderEditor::_expression_focus_out(Object *code_edit, int p_node) { +void VisualShaderEditor::_expression_focus_out(Object *p_code_edit, int p_node) { VisualShader::Type type = get_current_shader_type(); Ref<VisualShaderNodeExpression> node = visual_shader->get_node(type, p_node); if (node.is_null()) { return; } - CodeEdit *expression_box = Object::cast_to<CodeEdit>(code_edit); + CodeEdit *expression_box = Object::cast_to<CodeEdit>(p_code_edit); if (node->get_expression() == expression_box->get_text()) { return; @@ -2452,20 +2448,20 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p } } - GraphNode *gn = nullptr; + GraphElement *graph_element = nullptr; Node *node2 = graph->get_node(itos(p_node)); - gn = Object::cast_to<GraphNode>(node2); - if (!gn) { + graph_element = Object::cast_to<GraphElement>(node2); + if (!graph_element) { return; } - gn->set_custom_minimum_size(size); - gn->reset_size(); + 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 = gn->get_size().x; + box_size.x = graph_element->get_size().x; } box_size.x -= text_box->get_offset(SIDE_LEFT); box_size.x -= 28 * EDSCALE; @@ -2483,9 +2479,14 @@ void VisualShaderEditor::_node_resized(const Vector2 &p_new_size, int p_type, in 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())); + } + 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, p_new_size); + undo_redo->add_do_method(this, "_set_node_size", p_type, p_node, new_size); undo_redo->add_undo_method(this, "_set_node_size", p_type, p_node, node->get_size()); undo_redo->commit_action(); } @@ -2547,7 +2548,7 @@ void VisualShaderEditor::_comment_title_popup_hide() { return; // nothing changed - ignored } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Set Comment Node Title")); + 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->add_undo_method(node.ptr(), "set_title", node->get_title()); undo_redo->add_do_method(graph_plugin.ptr(), "update_node", (int)type, node_id); @@ -2590,7 +2591,7 @@ void VisualShaderEditor::_comment_desc_popup_hide() { return; // nothing changed - ignored } EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); - undo_redo->create_action(TTR("Set Comment Node Description")); + 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); @@ -3544,12 +3545,12 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { } } - // delete nodes from the graph + // Delete nodes from the graph. for (const int &F : p_nodes) { undo_redo->add_do_method(graph_plugin.ptr(), "remove_node", type, F, false); } - // update parameter refs if any parameter has been deleted + // Update parameter refs if any parameter has been deleted. if (parameter_names.size() > 0) { undo_redo->add_do_method(this, "_update_parameters", true); undo_redo->add_undo_method(this, "_update_parameters", true); @@ -3789,7 +3790,12 @@ void VisualShaderEditor::_convert_constants_to_parameters(bool p_vice_versa) { undo_redo->commit_action(); } -void VisualShaderEditor::_delete_node_request(int p_type, int p_node) { +void VisualShaderEditor::_close_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()) { + return; + } + List<int> to_erase; to_erase.push_back(p_node); @@ -3799,22 +3805,30 @@ void VisualShaderEditor::_delete_node_request(int p_type, int p_node) { undo_redo->commit_action(); } -void VisualShaderEditor::_delete_nodes_request(const TypedArray<StringName> &p_nodes) { +void VisualShaderEditor::_close_nodes_request(const TypedArray<StringName> &p_nodes) { List<int> to_erase; if (p_nodes.is_empty()) { // Called from context menu. for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - if (gn->is_selected() && gn->is_close_button_visible()) { - to_erase.push_back(gn->get_name().operator String().to_int()); + 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()); } } } } else { + VisualShader::Type type = get_current_shader_type(); for (int i = 0; i < p_nodes.size(); i++) { - to_erase.push_back(p_nodes[i].operator String().to_int()); + int id = p_nodes[i].operator String().to_int(); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); + if (vsnode->is_closable()) { + to_erase.push_back(id); + } } } @@ -3831,59 +3845,62 @@ void VisualShaderEditor::_delete_nodes_request(const TypedArray<StringName> &p_n void VisualShaderEditor::_node_selected(Object *p_node) { VisualShader::Type type = get_current_shader_type(); - GraphNode *gn = Object::cast_to<GraphNode>(p_node); - ERR_FAIL_COND(!gn); + GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_COND(!graph_element); - int id = String(gn->get_name()).to_int(); + int id = String(graph_element->get_name()).to_int(); Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, id); ERR_FAIL_COND(!vsnode.is_valid()); - - //do not rely on this, makes editor more complex - //EditorNode::get_singleton()->push_item(vsnode.ptr(), "", true); } void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; VisualShader::Type type = get_current_shader_type(); + Ref<VisualShaderNode> selected_vsnode; + // Right click actions. if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { selected_constants.clear(); selected_parameters.clear(); selected_comment = -1; selected_float_constant = -1; - List<int> to_change; + List<int> selected_closable_graph_elements; for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - if (gn->is_selected() && gn->is_close_button_visible()) { - int id = gn->get_name().operator String().to_int(); - to_change.push_back(id); + 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; + } - Ref<VisualShaderNode> node = visual_shader->get_node(type, id); + selected_closable_graph_elements.push_back(id); - VisualShaderNodeComment *comment_node = Object::cast_to<VisualShaderNodeComment>(node.ptr()); - if (comment_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); - } + Ref<VisualShaderNode> node = visual_shader->get_node(type, id); + selected_vsnode = node; + + 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); } } } - if (to_change.size() > 1) { + if (selected_closable_graph_elements.size() > 1) { selected_comment = -1; selected_float_constant = -1; } @@ -3896,14 +3913,14 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } } - if (to_change.is_empty() && copy_buffer_empty) { + if (selected_closable_graph_elements.is_empty() && copy_buffer_empty) { _show_members_dialog(true); } else { - popup_menu->set_item_disabled(NodeMenuOptions::CUT, to_change.is_empty()); - popup_menu->set_item_disabled(NodeMenuOptions::COPY, to_change.is_empty()); + 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::PASTE, copy_buffer_empty); - popup_menu->set_item_disabled(NodeMenuOptions::DELETE, to_change.is_empty()); - popup_menu->set_item_disabled(NodeMenuOptions::DUPLICATE, to_change.is_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::CLEAR_COPY_BUFFER, copy_buffer_empty); int temp = popup_menu->get_item_index(NodeMenuOptions::SEPARATOR2); @@ -4184,9 +4201,9 @@ void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, Li HashSet<int> nodes; for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - int id = String(gn->get_name()).to_int(); + GraphElement *graph_element = Object::cast_to<GraphElement>(graph->get_child(i)); + if (graph_element) { + int id = String(graph_element->get_name()).to_int(); Ref<VisualShaderNode> node = visual_shader->get_node(type, id); Ref<VisualShaderNodeOutput> output = node; @@ -4194,7 +4211,7 @@ void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, Li continue; } - if (node.is_valid() && gn->is_selected()) { + if (node.is_valid() && graph_element->is_selected()) { Vector2 pos = visual_shader->get_node_position(type, id); selection_center += pos; @@ -4320,13 +4337,13 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, c // reselect nodes by excluding the other ones for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - int id = String(gn->get_name()).to_int(); + GraphElement *graph_element = Object::cast_to<GraphElement>(graph->get_child(i)); + if (graph_element) { + int id = String(graph_element->get_name()).to_int(); if (added_set.has(id)) { - gn->set_selected(true); + graph_element->set_selected(true); } else { - gn->set_selected(false); + graph_element->set_selected(false); } } } @@ -4819,7 +4836,7 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { _paste_nodes(true, menu_point); break; case NodeMenuOptions::DELETE: - _delete_nodes_request(TypedArray<StringName>()); + _close_nodes_request(TypedArray<StringName>()); break; case NodeMenuOptions::DUPLICATE: _duplicate_nodes(); @@ -5119,7 +5136,7 @@ VisualShaderEditor::VisualShaderEditor() { graph->connect("duplicate_nodes_request", callable_mp(this, &VisualShaderEditor::_duplicate_nodes)); graph->connect("copy_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes).bind(false)); graph->connect("paste_nodes_request", callable_mp(this, &VisualShaderEditor::_paste_nodes).bind(false, Point2())); - graph->connect("delete_nodes_request", callable_mp(this, &VisualShaderEditor::_delete_nodes_request)); + graph->connect("close_nodes_request", callable_mp(this, &VisualShaderEditor::_close_nodes_request)); graph->connect("gui_input", callable_mp(this, &VisualShaderEditor::_graph_gui_input)); graph->connect("connection_to_empty", callable_mp(this, &VisualShaderEditor::_connection_to_empty)); graph->connect("connection_from_empty", callable_mp(this, &VisualShaderEditor::_connection_from_empty)); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index bdb23afa0f..e0a0f3a096 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -38,9 +38,10 @@ #include "scene/resources/visual_shader.h" class CodeEdit; +class ColorPicker; class CurveEditor; class GraphEdit; -class GraphNode; +class GraphElement; class MenuButton; class PopupPanel; class RichTextLabel; @@ -81,7 +82,7 @@ private: struct Link { VisualShader::Type type = VisualShader::Type::TYPE_MAX; VisualShaderNode *visual_node = nullptr; - GraphNode *graph_node = nullptr; + GraphElement *graph_element = nullptr; bool preview_visible = false; int preview_pos = 0; HashMap<int, InputPort> input_ports; @@ -105,7 +106,7 @@ public: void set_editor(VisualShaderEditor *p_editor); void register_shader(VisualShader *p_visual_shader); void set_connections(const List<VisualShader::Connection> &p_connections); - void register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node); + void register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphElement *p_graph_element); void register_output_port(int p_id, int p_port, TextureButton *p_button); void register_parameter_name(int p_id, LineEdit *p_parameter_name); void register_default_input_button(int p_node_id, int p_port_id, Button *p_button); @@ -343,7 +344,7 @@ class VisualShaderEditor : public VBoxContainer { List<VisualShaderNodeParameterRef> uniform_refs; - void _draw_color_over_button(Object *obj, Color p_color); + void _draw_color_over_button(Object *p_obj, Color p_color); void _setup_node(VisualShaderNode *p_node, const Vector<Variant> &p_ops); void _add_node(int p_idx, const Vector<Variant> &p_ops, String p_resource_path = "", int p_node_idx = -1); @@ -379,8 +380,8 @@ class VisualShaderEditor : public VBoxContainer { void _node_selected(Object *p_node); void _delete_nodes(int p_type, const List<int> &p_nodes); - void _delete_node_request(int p_type, int p_node); - void _delete_nodes_request(const TypedArray<StringName> &p_nodes); + void _close_node_request(int p_type, int p_node); + void _close_nodes_request(const TypedArray<StringName> &p_nodes); void _node_changed(int p_id); @@ -417,9 +418,9 @@ class VisualShaderEditor : public VBoxContainer { void _comment_desc_text_changed(); void _parameter_line_edit_changed(const String &p_text, int p_node_id); - void _parameter_line_edit_focus_out(Object *line_edit, int p_node_id); + void _parameter_line_edit_focus_out(Object *p_line_edit, int p_node_id); - void _port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output); + void _port_name_focus_out(Object *p_line_edit, int p_node_id, int p_port_id, bool p_output); struct CopyItem { int id; @@ -451,7 +452,7 @@ class VisualShaderEditor : public VBoxContainer { void _mode_selected(int p_id); void _custom_mode_toggled(bool p_enabled); - void _input_select_item(Ref<VisualShaderNodeInput> input, String name); + void _input_select_item(Ref<VisualShaderNodeInput> p_input, String p_name); void _parameter_ref_select_item(Ref<VisualShaderNodeParameterRef> p_parameter_ref, String p_name); void _varying_select_item(Ref<VisualShaderNodeVarying> p_varying, String p_name); @@ -470,7 +471,7 @@ class VisualShaderEditor : public VBoxContainer { void _change_output_port_name(const String &p_text, Object *p_line_edit, int p_node, int p_port); void _expand_output_port(int p_node, int p_port, bool p_expand); - void _expression_focus_out(Object *code_edit, int p_node); + void _expression_focus_out(Object *p_code_edit, int p_node); void _set_node_size(int p_type, int p_node, const Size2 &p_size); void _node_resized(const Vector2 &p_new_size, int p_type, int p_node); @@ -500,7 +501,6 @@ class VisualShaderEditor : public VBoxContainer { void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); bool _is_available(int p_mode); - void _update_created_node(GraphNode *node); void _update_parameters(bool p_update_refs); void _update_parameter_refs(HashSet<String> &p_names); void _update_varyings(); diff --git a/misc/extension_api_validation/4.1-stable.expected b/misc/extension_api_validation/4.1-stable.expected index ff66f9d51d..274b458d56 100644 --- a/misc/extension_api_validation/4.1-stable.expected +++ b/misc/extension_api_validation/4.1-stable.expected @@ -116,3 +116,56 @@ Validate extension JSON: API was removed: classes/GLTFDocumentExtensionPhysics Validate extension JSON: API was removed: classes/GLTFDocumentExtensionTextureWebP Excluded unexposed classes from extension_api.json. + +GH-79311 +-------- + +Validate extension JSON: API was removed: classes/GraphEdit/signals/delete_nodes_request +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_input_color +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_input_count +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_input_height +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_input_position +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_input_slot +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_input_type +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_output_color +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_output_count +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_output_height +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_output_position +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_output_slot +Validate extension JSON: API was removed: classes/GraphNode/methods/get_connection_output_type +Validate extension JSON: API was removed: classes/GraphNode/methods/get_language +Validate extension JSON: API was removed: classes/GraphNode/methods/get_overlay +Validate extension JSON: API was removed: classes/GraphNode/methods/get_position_offset +Validate extension JSON: API was removed: classes/GraphNode/methods/get_text_direction +Validate extension JSON: API was removed: classes/GraphNode/methods/is_close_button_visible +Validate extension JSON: API was removed: classes/GraphNode/methods/is_draggable +Validate extension JSON: API was removed: classes/GraphNode/methods/is_resizable +Validate extension JSON: API was removed: classes/GraphNode/methods/is_selectable +Validate extension JSON: API was removed: classes/GraphNode/methods/is_selected +Validate extension JSON: API was removed: classes/GraphNode/methods/set_draggable +Validate extension JSON: API was removed: classes/GraphNode/methods/set_language +Validate extension JSON: API was removed: classes/GraphNode/methods/set_overlay +Validate extension JSON: API was removed: classes/GraphNode/methods/set_position_offset +Validate extension JSON: API was removed: classes/GraphNode/methods/set_resizable +Validate extension JSON: API was removed: classes/GraphNode/methods/set_selectable +Validate extension JSON: API was removed: classes/GraphNode/methods/set_selected +Validate extension JSON: API was removed: classes/GraphNode/methods/set_show_close_button +Validate extension JSON: API was removed: classes/GraphNode/methods/set_text_direction +Validate extension JSON: API was removed: classes/GraphNode/properties/draggable +Validate extension JSON: API was removed: classes/GraphNode/properties/language +Validate extension JSON: API was removed: classes/GraphNode/properties/overlay +Validate extension JSON: API was removed: classes/GraphNode/properties/position_offset +Validate extension JSON: API was removed: classes/GraphNode/properties/resizable +Validate extension JSON: API was removed: classes/GraphNode/properties/selectable +Validate extension JSON: API was removed: classes/GraphNode/properties/selected +Validate extension JSON: API was removed: classes/GraphNode/properties/show_close +Validate extension JSON: API was removed: classes/GraphNode/properties/text_direction +Validate extension JSON: API was removed: classes/GraphNode/signals/close_request +Validate extension JSON: API was removed: classes/GraphNode/signals/dragged +Validate extension JSON: API was removed: classes/GraphNode/signals/node_deselected +Validate extension JSON: API was removed: classes/GraphNode/signals/node_selected +Validate extension JSON: API was removed: classes/GraphNode/signals/position_offset_changed +Validate extension JSON: API was removed: classes/GraphNode/signals/raise_request +Validate extension JSON: API was removed: classes/GraphNode/signals/resize_request + +Refactor GraphNode (splitup in GraphElement and GraphNode) diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp index 2364032863..df6e4fed73 100644 --- a/scene/animation/animation_tree.cpp +++ b/scene/animation/animation_tree.cpp @@ -384,6 +384,14 @@ bool AnimationNode::is_filter_enabled() const { return filter_enabled; } +void AnimationNode::set_closable(bool p_closable) { + closable = p_closable; +} + +bool AnimationNode::is_closable() const { + return closable; +} + bool AnimationNode::is_path_filtered(const NodePath &p_path) const { return filter.has(p_path); } diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h index 422bd0abb1..93ca20f8f5 100644 --- a/scene/animation/animation_tree.h +++ b/scene/animation/animation_tree.h @@ -101,6 +101,8 @@ public: HashMap<NodePath, bool> filter; bool filter_enabled = false; + bool closable = false; + Array _get_filters() const; void _set_filters(const Array &p_filters); friend class AnimationNodeBlendTree; @@ -160,6 +162,9 @@ public: void set_filter_enabled(bool p_enable); bool is_filter_enabled() const; + void set_closable(bool p_closable); + bool is_closable() const; + virtual bool has_filter() const; virtual Ref<AnimationNode> get_child_by_name(const StringName &p_name) const; diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index cb9fba44e5..be0ae45e7e 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -295,16 +295,16 @@ void GraphEdit::_update_scroll_offset() { set_block_minimum_size_adjust(true); for (int i = 0; i < get_child_count(); i++) { - GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); - if (!graph_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } - Point2 pos = graph_node->get_position_offset() * zoom; + Point2 pos = graph_element->get_position_offset() * zoom; pos -= Point2(h_scrollbar->get_value(), v_scrollbar->get_value()); - graph_node->set_position(pos); - if (graph_node->get_scale() != Vector2(zoom, zoom)) { - graph_node->set_scale(Vector2(zoom, zoom)); + graph_element->set_position(pos); + if (graph_element->get_scale() != Vector2(zoom, zoom)) { + graph_element->set_scale(Vector2(zoom, zoom)); } } @@ -328,14 +328,14 @@ void GraphEdit::_update_scroll() { Rect2 screen_rect; for (int i = 0; i < get_child_count(); i++) { - GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); - if (!graph_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } Rect2 node_rect; - node_rect.position = graph_node->get_position_offset() * zoom; - node_rect.size = graph_node->get_size() * zoom; + node_rect.position = graph_element->get_position_offset() * zoom; + node_rect.size = graph_element->get_size() * zoom; screen_rect = screen_rect.merge(node_rect); } @@ -378,46 +378,48 @@ void GraphEdit::_update_scroll() { updating = false; } -void GraphEdit::_graph_node_moved_to_front(Node *p_gn) { - GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(graph_node); +void GraphEdit::_graph_element_moved_to_front(Node *p_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_element); - graph_node->move_to_front(); + graph_element->move_to_front(); } -void GraphEdit::_graph_node_selected(Node *p_gn) { - GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(graph_node); +void GraphEdit::_graph_element_selected(Node *p_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_element); - emit_signal(SNAME("node_selected"), graph_node); + emit_signal(SNAME("node_selected"), graph_element); } -void GraphEdit::_graph_node_deselected(Node *p_gn) { - GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(graph_node); +void GraphEdit::_graph_element_deselected(Node *p_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_element); - emit_signal(SNAME("node_deselected"), graph_node); + emit_signal(SNAME("node_deselected"), graph_element); } -void GraphEdit::_graph_node_resized(Vector2 p_new_minsize, Node *p_gn) { - GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(graph_node); +void GraphEdit::_graph_element_resized(Vector2 p_new_minsize, Node *p_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_element); - graph_node->set_custom_minimum_size(p_new_minsize); + graph_element->set_size(p_new_minsize); } -void GraphEdit::_graph_node_moved(Node *p_gn) { - GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); - ERR_FAIL_NULL(graph_node); +void GraphEdit::_graph_element_moved(Node *p_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(p_node); + ERR_FAIL_NULL(graph_element); + top_layer->queue_redraw(); minimap->queue_redraw(); queue_redraw(); connections_layer->queue_redraw(); } -void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_gn) { - GraphNode *graph_node = Object::cast_to<GraphNode>(p_gn); +void GraphEdit::_graph_node_slot_updated(int p_index, Node *p_node) { + GraphNode *graph_node = Object::cast_to<GraphNode>(p_node); ERR_FAIL_NULL(graph_node); + top_layer->queue_redraw(); minimap->queue_redraw(); queue_redraw(); @@ -430,19 +432,25 @@ void GraphEdit::add_child_notify(Node *p_child) { // Keep the top layer always on top! callable_mp((CanvasItem *)top_layer, &CanvasItem::move_to_front).call_deferred(); - GraphNode *graph_node = Object::cast_to<GraphNode>(p_child); - if (graph_node) { - graph_node->set_scale(Vector2(zoom, zoom)); - graph_node->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved).bind(graph_node)); - graph_node->connect("node_selected", callable_mp(this, &GraphEdit::_graph_node_selected).bind(graph_node)); - graph_node->connect("node_deselected", callable_mp(this, &GraphEdit::_graph_node_deselected).bind(graph_node)); - graph_node->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(graph_node)); - graph_node->connect("raise_request", callable_mp(this, &GraphEdit::_graph_node_moved_to_front).bind(graph_node)); - graph_node->connect("resize_request", callable_mp(this, &GraphEdit::_graph_node_resized).bind(graph_node)); - graph_node->connect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); - graph_node->connect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); - _graph_node_moved(graph_node); - graph_node->set_mouse_filter(MOUSE_FILTER_PASS); + GraphElement *graph_element = Object::cast_to<GraphElement>(p_child); + if (graph_element) { + graph_element->connect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_element_moved).bind(graph_element)); + graph_element->connect("node_selected", callable_mp(this, &GraphEdit::_graph_element_selected).bind(graph_element)); + graph_element->connect("node_deselected", callable_mp(this, &GraphEdit::_graph_element_deselected).bind(graph_element)); + + GraphNode *graph_node = Object::cast_to<GraphNode>(graph_element); + if (graph_node) { + graph_element->connect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated).bind(graph_element)); + } + + 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)); + 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)); + _graph_element_moved(graph_element); + graph_element->set_mouse_filter(MOUSE_FILTER_PASS); } } @@ -461,20 +469,26 @@ void GraphEdit::remove_child_notify(Node *p_child) { callable_mp((CanvasItem *)top_layer, &CanvasItem::move_to_front).call_deferred(); } - GraphNode *graph_node = Object::cast_to<GraphNode>(p_child); - if (graph_node) { - graph_node->disconnect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved)); - graph_node->disconnect("node_selected", callable_mp(this, &GraphEdit::_graph_node_selected)); - graph_node->disconnect("node_deselected", callable_mp(this, &GraphEdit::_graph_node_deselected)); - graph_node->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated)); - graph_node->disconnect("raise_request", callable_mp(this, &GraphEdit::_graph_node_moved_to_front)); + GraphElement *graph_element = Object::cast_to<GraphElement>(p_child); + if (graph_element) { + graph_element->disconnect("position_offset_changed", callable_mp(this, &GraphEdit::_graph_element_moved)); + graph_element->disconnect("node_selected", callable_mp(this, &GraphEdit::_graph_element_selected)); + graph_element->disconnect("node_deselected", callable_mp(this, &GraphEdit::_graph_element_deselected)); + + GraphNode *graph_node = Object::cast_to<GraphNode>(graph_element); + if (graph_node) { + graph_element->disconnect("slot_updated", callable_mp(this, &GraphEdit::_graph_node_slot_updated)); + } + + 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)); // In case of the whole GraphEdit being destroyed these references can already be freed. if (connections_layer != nullptr && connections_layer->is_inside_tree()) { - graph_node->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); + graph_element->disconnect("item_rect_changed", callable_mp((CanvasItem *)connections_layer, &CanvasItem::queue_redraw)); } if (minimap != nullptr && minimap->is_inside_tree()) { - graph_node->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); + graph_element->disconnect("item_rect_changed", callable_mp((CanvasItem *)minimap, &GraphEditMinimap::queue_redraw)); } } } @@ -572,11 +586,11 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { continue; } - for (int j = 0; j < graph_node->get_connection_input_count(); j++) { + for (int j = 0; j < graph_node->get_input_port_count(); j++) { Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); // Determine slot height. - int slot_index = graph_node->get_connection_input_slot(j); + int slot_index = graph_node->get_input_port_slot(j); Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); @@ -586,11 +600,11 @@ bool GraphEdit::_filter_input(const Point2 &p_point) { } } - for (int j = 0; j < graph_node->get_connection_output_count(); j++) { + for (int j = 0; j < graph_node->get_output_port_count(); j++) { Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); // Determine slot height. - int slot_index = graph_node->get_connection_output_slot(j); + int slot_index = graph_node->get_output_port_slot(j); Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); @@ -616,17 +630,17 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { continue; } - for (int j = 0; j < graph_node->get_connection_output_count(); j++) { - Vector2 pos = graph_node->get_connection_output_position(j) * zoom + graph_node->get_position(); + for (int j = 0; j < graph_node->get_output_port_count(); j++) { + Vector2 pos = graph_node->get_output_port_position(j) * zoom + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); // Determine slot height. - int slot_index = graph_node->get_connection_output_slot(j); + int slot_index = graph_node->get_output_port_slot(j); Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); if (is_in_output_hotzone(graph_node, j, click_pos, port_size)) { - if (valid_left_disconnect_types.has(graph_node->get_connection_output_type(j))) { + if (valid_left_disconnect_types.has(graph_node->get_output_port_type(j))) { // Check disconnect. for (const Connection &E : connections) { if (E.from_node == graph_node->get_name() && E.from_port == j) { @@ -635,8 +649,8 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_from = E.to_node; connecting_index = E.to_port; connecting_out = false; - connecting_type = Object::cast_to<GraphNode>(to)->get_connection_input_type(E.to_port); - connecting_color = Object::cast_to<GraphNode>(to)->get_connection_input_color(E.to_port); + connecting_type = Object::cast_to<GraphNode>(to)->get_input_port_type(E.to_port); + connecting_color = Object::cast_to<GraphNode>(to)->get_input_port_color(E.to_port); connecting_target = false; connecting_to = pos; @@ -659,8 +673,8 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_from = graph_node->get_name(); connecting_index = j; connecting_out = true; - connecting_type = graph_node->get_connection_output_type(j); - connecting_color = graph_node->get_connection_output_color(j); + connecting_type = graph_node->get_output_port_type(j); + connecting_color = graph_node->get_output_port_color(j); connecting_target = false; connecting_to = pos; if (connecting_type >= 0) { @@ -672,18 +686,18 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } - for (int j = 0; j < graph_node->get_connection_input_count(); j++) { - Vector2 pos = graph_node->get_connection_input_position(j) + graph_node->get_position(); + for (int j = 0; j < graph_node->get_input_port_count(); j++) { + Vector2 pos = graph_node->get_input_port_position(j) * zoom + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); // Determine slot height. - int slot_index = graph_node->get_connection_input_slot(j); + int slot_index = graph_node->get_input_port_slot(j); Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); if (is_in_input_hotzone(graph_node, j, click_pos, port_size)) { - if (right_disconnects || valid_right_disconnect_types.has(graph_node->get_connection_input_type(j))) { + if (right_disconnects || valid_right_disconnect_types.has(graph_node->get_input_port_type(j))) { // Check disconnect. for (const Connection &E : connections) { if (E.to_node == graph_node->get_name() && E.to_port == j) { @@ -692,8 +706,8 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_from = E.from_node; connecting_index = E.from_port; connecting_out = true; - connecting_type = Object::cast_to<GraphNode>(fr)->get_connection_output_type(E.from_port); - connecting_color = Object::cast_to<GraphNode>(fr)->get_connection_output_color(E.from_port); + connecting_type = Object::cast_to<GraphNode>(fr)->get_output_port_type(E.from_port); + connecting_color = Object::cast_to<GraphNode>(fr)->get_output_port_color(E.from_port); connecting_target = false; connecting_to = pos; just_disconnected = true; @@ -715,8 +729,8 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { connecting_from = graph_node->get_name(); connecting_index = j; connecting_out = false; - connecting_type = graph_node->get_connection_input_type(j); - connecting_color = graph_node->get_connection_input_color(j); + connecting_type = graph_node->get_input_port_type(j); + connecting_color = graph_node->get_input_port_color(j); connecting_target = false; connecting_to = pos; if (connecting_type >= 0) { @@ -749,16 +763,16 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } if (!connecting_out) { - for (int j = 0; j < graph_node->get_connection_output_count(); j++) { - Vector2 pos = graph_node->get_connection_output_position(j) + graph_node->get_position(); + for (int j = 0; j < graph_node->get_output_port_count(); j++) { + Vector2 pos = graph_node->get_output_port_position(j) * zoom + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); // Determine slot height. - int slot_index = graph_node->get_connection_output_slot(j); + int slot_index = graph_node->get_output_port_slot(j); Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); - int type = graph_node->get_connection_output_type(j); + int type = graph_node->get_output_port_type(j); if ((type == connecting_type || valid_connection_types.has(ConnectionType(connecting_type, type))) && is_in_output_hotzone(graph_node, j, mpos, port_size)) { @@ -773,16 +787,16 @@ void GraphEdit::_top_layer_input(const Ref<InputEvent> &p_ev) { } } } else { - for (int j = 0; j < graph_node->get_connection_input_count(); j++) { - Vector2 pos = graph_node->get_connection_input_position(j) + graph_node->get_position(); + for (int j = 0; j < graph_node->get_input_port_count(); j++) { + Vector2 pos = graph_node->get_input_port_position(j) * zoom + graph_node->get_position(); Vector2i port_size = Vector2i(port_icon->get_width(), port_icon->get_height()); // Determine slot height. - int slot_index = graph_node->get_connection_input_slot(j); + int slot_index = graph_node->get_input_port_slot(j); Control *child = Object::cast_to<Control>(graph_node->get_child(slot_index)); port_size.height = MAX(port_size.height, child ? child->get_size().y : 0); - int type = graph_node->get_connection_input_type(j); + int type = graph_node->get_input_port_type(j); if ((type == connecting_type || valid_connection_types.has(ConnectionType(connecting_type, type))) && is_in_input_hotzone(graph_node, j, mpos, port_size)) { if (!is_node_hover_valid(connecting_from, connecting_index, graph_node->get_name(), j)) { @@ -851,17 +865,17 @@ bool GraphEdit::_check_clickable_control(Control *p_control, const Vector2 &mpos } } -bool GraphEdit::is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { +bool GraphEdit::is_in_input_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { bool success; - if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_graph_node, p_port, p_mouse_pos, success)) { + if (GDVIRTUAL_CALL(_is_in_input_hotzone, p_graph_node, p_port_idx, p_mouse_pos, success)) { return success; } else { - Vector2 pos = p_graph_node->get_connection_input_position(p_port) + p_graph_node->get_position(); + Vector2 pos = p_graph_node->get_input_port_position(p_port_idx) * zoom + p_graph_node->get_position(); return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, true); } } -bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { +bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size) { if (p_graph_node->is_resizable()) { Ref<Texture2D> resizer = p_graph_node->get_theme_icon(SNAME("resizer")); Rect2 resizer_rect = Rect2(p_graph_node->get_position() / zoom + p_graph_node->get_size() - resizer->get_size(), resizer->get_size()); @@ -871,10 +885,10 @@ bool GraphEdit::is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const } bool success; - if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_graph_node, p_port, p_mouse_pos, success)) { + if (GDVIRTUAL_CALL(_is_in_output_hotzone, p_graph_node, p_port_idx, p_mouse_pos, success)) { return success; } else { - Vector2 pos = p_graph_node->get_connection_output_position(p_port) + p_graph_node->get_position(); + Vector2 pos = p_graph_node->get_output_port_position(p_port_idx) * zoom + p_graph_node->get_position(); return is_in_port_hotzone(pos / zoom, p_mouse_pos, p_port_size, false); } } @@ -978,10 +992,10 @@ void GraphEdit::_connections_layer_draw() { continue; } - Vector2 frompos = gnode_from->get_connection_output_position(c.from_port) + gnode_from->get_position_offset() * zoom; - Color color = gnode_from->get_connection_output_color(c.from_port); - Vector2 topos = gnode_to->get_connection_input_position(c.to_port) + gnode_to->get_position_offset() * zoom; - Color tocolor = gnode_to->get_connection_input_color(c.to_port); + Vector2 frompos = gnode_from->get_output_port_position(c.from_port) * zoom + gnode_from->get_position_offset() * zoom; + Color color = gnode_from->get_output_port_color(c.from_port); + Vector2 topos = gnode_to->get_input_port_position(c.to_port) * zoom + gnode_to->get_position_offset() * zoom; + Color tocolor = gnode_to->get_input_port_color(c.to_port); if (c.activity > 0) { color = color.lerp(activity_color, c.activity); @@ -1005,9 +1019,9 @@ void GraphEdit::_top_layer_draw() { ERR_FAIL_NULL(graph_node_from); Vector2 pos; if (connecting_out) { - pos = graph_node_from->get_connection_output_position(connecting_index); + pos = graph_node_from->get_output_port_position(connecting_index) * zoom; } else { - pos = graph_node_from->get_connection_input_position(connecting_index); + pos = graph_node_from->get_input_port_position(connecting_index) * zoom; } pos += graph_node_from->get_position(); @@ -1061,7 +1075,7 @@ void GraphEdit::_minimap_draw() { Ref<StyleBoxFlat> sb_minimap = minimap->get_theme_stylebox(SNAME("node"))->duplicate(); // Override default values with colors provided by the GraphNode's stylebox, if possible. - Ref<StyleBoxFlat> sb_frame = graph_node->get_theme_stylebox(graph_node->is_selected() ? "selected_frame" : "frame"); + Ref<StyleBoxFlat> sb_frame = graph_node->get_theme_stylebox(graph_node->is_selected() ? "panel_selected" : "panel"); if (sb_frame.is_valid()) { Color node_color = sb_frame->get_bg_color(); sb_minimap->set_bg_color(node_color); @@ -1085,12 +1099,12 @@ void GraphEdit::_minimap_draw() { continue; } - Vector2 from_port_position = graph_node_from->get_position_offset() + graph_node_from->get_connection_output_position(E.from_port); + Vector2 from_port_position = graph_node_from->get_position_offset() * zoom + graph_node_from->get_output_port_position(E.from_port) * zoom; Vector2 from_position = minimap->_convert_from_graph_position(from_port_position - graph_offset) + minimap_offset; - Color from_color = graph_node_from->get_connection_output_color(E.from_port); - Vector2 to_port_position = graph_node_to->get_position_offset() + graph_node_to->get_connection_input_position(E.to_port); + Color from_color = graph_node_from->get_output_port_color(E.from_port); + Vector2 to_port_position = graph_node_to->get_position_offset() * zoom + graph_node_to->get_input_port_position(E.to_port) * zoom; Vector2 to_position = minimap->_convert_from_graph_position(to_port_position - graph_offset) + minimap_offset; - Color to_color = graph_node_to->get_connection_input_color(E.to_port); + Color to_color = graph_node_to->get_input_port_color(E.to_port); if (E.activity > 0) { from_color = from_color.lerp(activity_color, E.activity); @@ -1137,9 +1151,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { just_selected = true; drag_accum += mm->get_relative(); 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_selected() && graph_node->is_draggable()) { - Vector2 pos = (graph_node->get_drag_from() * zoom + drag_accum) / zoom; + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (graph_element && graph_element->is_selected() && graph_element->is_draggable()) { + Vector2 pos = (graph_element->get_drag_from() * zoom + drag_accum) / zoom; // Snapping can be toggled temporarily by holding down Ctrl. // This is done here as to not toggle the grid when holding down Ctrl. @@ -1147,7 +1161,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { pos = pos.snapped(Vector2(snapping_distance, snapping_distance)); } - graph_node->set_position_offset(pos); + graph_element->set_position_offset(pos); } } } @@ -1158,18 +1172,18 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { box_selecting_rect = Rect2(box_selecting_from.min(box_selecting_to), (box_selecting_from - box_selecting_to).abs()); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); - if (!graph_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } - Rect2 r = graph_node->get_rect(); + Rect2 r = graph_element->get_rect(); bool in_box = r.intersects(box_selecting_rect); if (in_box) { - graph_node->set_selected(box_selection_mode_additive); + graph_element->set_selected(box_selection_mode_additive); } else { - graph_node->set_selected(prev_selected.find(graph_node) != nullptr); + graph_element->set_selected(prev_selected.find(graph_element) != nullptr); } } @@ -1183,12 +1197,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (box_selecting) { box_selecting = false; for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); - if (!graph_node) { + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (!graph_element) { continue; } - graph_node->set_selected(prev_selected.find(graph_node) != nullptr); + graph_element->set_selected(prev_selected.find(graph_element) != nullptr); } top_layer->queue_redraw(); minimap->queue_redraw(); @@ -1205,12 +1219,12 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (!just_selected && drag_accum == Vector2() && Input::get_singleton()->is_key_pressed(Key::CTRL)) { // Deselect current node. for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *graph_node = Object::cast_to<GraphNode>(get_child(i)); + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); - if (graph_node) { - Rect2 r = graph_node->get_rect(); + if (graph_element) { + Rect2 r = graph_element->get_rect(); if (r.has_point(mb->get_position())) { - graph_node->set_selected(false); + graph_element->set_selected(false); } } } @@ -1218,9 +1232,9 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { if (drag_accum != Vector2()) { 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_selected()) { - graph_node->set_drag(false); + GraphElement *graph_element = Object::cast_to<GraphElement>(get_child(i)); + if (graph_element && graph_element->is_selected()) { + graph_element->set_drag(false); } } } @@ -1240,27 +1254,27 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Node selection logic. if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { - GraphNode *graph_node = nullptr; + GraphElement *graph_element = nullptr; // Find node which was clicked on. for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *selected_gcontrol = Object::cast_to<GraphNode>(get_child(i)); + GraphElement *selected_element = Object::cast_to<GraphElement>(get_child(i)); - if (!selected_gcontrol) { + if (!selected_element) { continue; } - if (selected_gcontrol->is_resizing()) { + if (selected_element->is_resizing()) { continue; } - if (selected_gcontrol->has_point((mb->get_position() - selected_gcontrol->get_position()) / zoom)) { - graph_node = selected_gcontrol; + if (selected_element->has_point((mb->get_position() - selected_element->get_position()) / zoom)) { + graph_element = selected_element; break; } } - if (graph_node) { + if (graph_element) { if (_filter_input(mb->get_position())) { return; } @@ -1268,26 +1282,26 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { // Left-clicked on a node, select it. dragging = true; drag_accum = Vector2(); - just_selected = !graph_node->is_selected(); - if (!graph_node->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { + just_selected = !graph_element->is_selected(); + if (!graph_element->is_selected() && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { for (int i = 0; i < get_child_count(); i++) { - GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); - if (!o_gn) { + GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i)); + if (!child_element) { continue; } - o_gn->set_selected(o_gn == graph_node); + child_element->set_selected(child_element == graph_element); } } - graph_node->set_selected(true); + graph_element->set_selected(true); for (int i = 0; i < get_child_count(); i++) { - GraphNode *o_gn = Object::cast_to<GraphNode>(get_child(i)); - if (!o_gn) { + GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i)); + if (!child_element) { continue; } - if (o_gn->is_selected()) { - o_gn->set_drag(true); + if (child_element->is_selected()) { + child_element->set_drag(true); } } @@ -1306,34 +1320,34 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { box_selection_mode_additive = true; prev_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); - if (!gn2 || !gn2->is_selected()) { + GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i)); + if (!child_element || !child_element->is_selected()) { continue; } - prev_selected.push_back(gn2); + prev_selected.push_back(child_element); } } else if (mb->is_shift_pressed()) { box_selection_mode_additive = false; prev_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); - if (!gn2 || !gn2->is_selected()) { + GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i)); + if (!child_element || !child_element->is_selected()) { continue; } - prev_selected.push_back(gn2); + prev_selected.push_back(child_element); } } else { box_selection_mode_additive = true; prev_selected.clear(); for (int i = get_child_count() - 1; i >= 0; i--) { - GraphNode *gn2 = Object::cast_to<GraphNode>(get_child(i)); - if (!gn2) { + GraphElement *child_element = Object::cast_to<GraphElement>(get_child(i)); + if (!child_element) { continue; } - gn2->set_selected(false); + child_element->set_selected(false); } } } @@ -1372,7 +1386,7 @@ void GraphEdit::gui_input(const Ref<InputEvent> &p_ev) { } } - emit_signal(SNAME("delete_nodes_request"), nodes); + emit_signal(SNAME("close_nodes_request"), nodes); accept_event(); } } @@ -1886,7 +1900,7 @@ void GraphEdit::_bind_methods() { ADD_SIGNAL(MethodInfo("node_deselected", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); ADD_SIGNAL(MethodInfo("connection_to_empty", PropertyInfo(Variant::STRING_NAME, "from_node"), PropertyInfo(Variant::INT, "from_port"), PropertyInfo(Variant::VECTOR2, "release_position"))); ADD_SIGNAL(MethodInfo("connection_from_empty", PropertyInfo(Variant::STRING_NAME, "to_node"), PropertyInfo(Variant::INT, "to_port"), PropertyInfo(Variant::VECTOR2, "release_position"))); - ADD_SIGNAL(MethodInfo("delete_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName"))); + ADD_SIGNAL(MethodInfo("close_nodes_request", PropertyInfo(Variant::ARRAY, "nodes", PROPERTY_HINT_ARRAY_TYPE, "StringName"))); ADD_SIGNAL(MethodInfo("begin_node_move")); ADD_SIGNAL(MethodInfo("end_node_move")); ADD_SIGNAL(MethodInfo("scroll_offset_changed", PropertyInfo(Variant::VECTOR2, "offset"))); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index d86cc4a5f9..5e97ea353d 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -195,7 +195,7 @@ private: Point2 box_selecting_from; Point2 box_selecting_to; Rect2 box_selecting_rect; - List<GraphNode *> prev_selected; + List<GraphElement *> prev_selected; bool setting_scroll_offset = false; bool right_disconnects = false; @@ -229,12 +229,12 @@ private: void _draw_connection_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_width, float p_zoom); - void _graph_node_selected(Node *p_gn); - void _graph_node_deselected(Node *p_gn); - void _graph_node_moved_to_front(Node *p_gn); - void _graph_node_resized(Vector2 p_new_minsize, Node *p_gn); - void _graph_node_moved(Node *p_gn); - void _graph_node_slot_updated(int p_index, Node *p_gn); + 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_moved(Node *p_node); + void _graph_node_slot_updated(int p_index, Node *p_node); void _update_scroll(); void _update_scroll_offset(); @@ -269,8 +269,8 @@ protected: void _notification(int p_what); - virtual bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); - virtual bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + virtual bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); + virtual bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port_idx, const Vector2 &p_mouse_pos, const Vector2i &p_port_size); GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2) GDVIRTUAL3R(bool, _is_in_input_hotzone, Object *, int, Vector2) diff --git a/scene/gui/graph_edit_arranger.cpp b/scene/gui/graph_edit_arranger.cpp index f4d9dcbf95..c1750a7b0f 100644 --- a/scene/gui/graph_edit_arranger.cpp +++ b/scene/gui/graph_edit_arranger.cpp @@ -408,8 +408,8 @@ void GraphEditArranger::_calculate_inner_shifts(Dictionary &r_inner_shifts, cons int port_from = ports.first; int port_to = ports.second; - Vector2 pos_from = gnode_from->get_connection_output_position(port_from) * graph_edit->get_zoom(); - Vector2 pos_to = gnode_to->get_connection_input_position(port_to) * graph_edit->get_zoom(); + Vector2 pos_from = gnode_from->get_output_port_position(port_from) * graph_edit->get_zoom(); + Vector2 pos_to = gnode_to->get_input_port_position(port_to) * graph_edit->get_zoom(); real_t s = (real_t)r_inner_shifts[u] + (pos_from.y - pos_to.y) / graph_edit->get_zoom(); r_inner_shifts[v] = s; @@ -459,8 +459,8 @@ float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, co if (incoming.from_node != StringName()) { GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[incoming.from_node]); GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[p_w]); - Vector2 pos_from = gnode_from->get_connection_output_position(incoming.from_port) * graph_edit->get_zoom(); - Vector2 pos_to = gnode_to->get_connection_input_position(incoming.to_port) * graph_edit->get_zoom(); + Vector2 pos_from = gnode_from->get_output_port_position(incoming.from_port) * graph_edit->get_zoom(); + Vector2 pos_to = gnode_to->get_input_port_position(incoming.to_port) * graph_edit->get_zoom(); // If connected block node is selected, calculate thershold or add current block to list. if (gnode_from->is_selected()) { @@ -491,8 +491,8 @@ float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, co if (outgoing.to_node != StringName()) { GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[p_w]); GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[outgoing.to_node]); - Vector2 pos_from = gnode_from->get_connection_output_position(outgoing.from_port) * graph_edit->get_zoom(); - Vector2 pos_to = gnode_to->get_connection_input_position(outgoing.to_port) * graph_edit->get_zoom(); + Vector2 pos_from = gnode_from->get_output_port_position(outgoing.from_port) * graph_edit->get_zoom(); + Vector2 pos_to = gnode_to->get_input_port_position(outgoing.to_port) * graph_edit->get_zoom(); // If connected block node is selected, calculate thershold or add current block to list. if (gnode_to->is_selected()) { diff --git a/scene/gui/graph_element.cpp b/scene/gui/graph_element.cpp new file mode 100644 index 0000000000..04c4aa6ce8 --- /dev/null +++ b/scene/gui/graph_element.cpp @@ -0,0 +1,244 @@ +/**************************************************************************/ +/* graph_element.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_element.h" + +#include "core/string/translation.h" +#include "scene/gui/graph_edit.h" + +#ifdef TOOLS_ENABLED +void GraphElement::_edit_set_position(const Point2 &p_position) { + GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); + if (graph) { + Point2 offset = (p_position + graph->get_scroll_offset()) * graph->get_zoom(); + set_position_offset(offset); + } + set_position(p_position); +} +#endif + +void GraphElement::_resort() { + Size2 size = get_size(); + + for (int i = 0; i < get_child_count(); i++) { + Control *child = Object::cast_to<Control>(get_child(i)); + if (!child || !child->is_visible_in_tree()) { + continue; + } + if (child->is_set_as_top_level()) { + continue; + } + + fit_child_in_rect(child, Rect2(Point2(), size)); + } +} + +Size2 GraphElement::get_minimum_size() const { + Size2 minsize; + for (int i = 0; i < get_child_count(); i++) { + Control *child = Object::cast_to<Control>(get_child(i)); + if (!child) { + continue; + } + if (child->is_set_as_top_level()) { + continue; + } + + Size2i size = child->get_combined_minimum_size(); + + minsize.width = MAX(minsize.width, size.width); + minsize.height = MAX(minsize.height, size.height); + } + + return minsize; +} + +void GraphElement::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_SORT_CHILDREN: { + _resort(); + } break; + + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: + case NOTIFICATION_THEME_CHANGED: { + update_minimum_size(); + queue_redraw(); + } break; + } +} + +void GraphElement::_validate_property(PropertyInfo &p_property) const { + Control::_validate_property(p_property); + GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); + if (graph) { + if (p_property.name == "position") { + p_property.usage |= PROPERTY_USAGE_READ_ONLY; + } + } +} + +void GraphElement::set_position_offset(const Vector2 &p_offset) { + if (position_offset == p_offset) { + return; + } + + position_offset = p_offset; + emit_signal(SNAME("position_offset_changed")); + queue_redraw(); +} + +Vector2 GraphElement::get_position_offset() const { + return position_offset; +} + +void GraphElement::set_selected(bool p_selected) { + if (!is_selectable() || selected == p_selected) { + return; + } + selected = p_selected; + emit_signal(p_selected ? SNAME("node_selected") : SNAME("node_deselected")); + queue_redraw(); +} + +bool GraphElement::is_selected() { + return selected; +} + +void GraphElement::set_drag(bool p_drag) { + if (p_drag) { + drag_from = get_position_offset(); + } else { + emit_signal(SNAME("dragged"), drag_from, get_position_offset()); // Required for undo/redo. + } +} + +Vector2 GraphElement::get_drag_from() { + return drag_from; +} + +void GraphElement::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_COND_MSG(get_parent_control() == nullptr, "GraphElement 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 = get_theme_icon(SNAME("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) { + resizing = false; + } + } + + Ref<InputEventMouseMotion> mm = p_ev; + if (resizing && mm.is_valid()) { + Vector2 mpos = mm->get_position(); + Vector2 diff = mpos - resizing_from; + + emit_signal(SNAME("resize_request"), resizing_from_size + diff); + } +} + +void GraphElement::set_resizable(bool p_enable) { + if (resizable == p_enable) { + return; + } + resizable = p_enable; + queue_redraw(); +} + +bool GraphElement::is_resizable() const { + return resizable; +} + +void GraphElement::set_draggable(bool p_draggable) { + draggable = p_draggable; +} + +bool GraphElement::is_draggable() { + return draggable; +} + +void GraphElement::set_selectable(bool p_selectable) { + if (!p_selectable) { + set_selected(false); + } + selectable = p_selectable; +} + +bool GraphElement::is_selectable() { + return selectable; +} + +void GraphElement::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_resizable", "resizable"), &GraphElement::set_resizable); + ClassDB::bind_method(D_METHOD("is_resizable"), &GraphElement::is_resizable); + + ClassDB::bind_method(D_METHOD("set_draggable", "draggable"), &GraphElement::set_draggable); + ClassDB::bind_method(D_METHOD("is_draggable"), &GraphElement::is_draggable); + + ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &GraphElement::set_selectable); + ClassDB::bind_method(D_METHOD("is_selectable"), &GraphElement::is_selectable); + + ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphElement::set_selected); + ClassDB::bind_method(D_METHOD("is_selected"), &GraphElement::is_selected); + + ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphElement::set_position_offset); + ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphElement::get_position_offset); + + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset"), "set_position_offset", "get_position_offset"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draggable"), "set_draggable", "is_draggable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected"); + + ADD_SIGNAL(MethodInfo("position_offset_changed")); + ADD_SIGNAL(MethodInfo("node_selected")); + ADD_SIGNAL(MethodInfo("node_deselected")); + ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); + ADD_SIGNAL(MethodInfo("raise_request")); + ADD_SIGNAL(MethodInfo("close_request")); + ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_minsize"))); +} diff --git a/scene/gui/graph_element.h b/scene/gui/graph_element.h new file mode 100644 index 0000000000..2c0a4760d8 --- /dev/null +++ b/scene/gui/graph_element.h @@ -0,0 +1,93 @@ +/**************************************************************************/ +/* graph_element.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_ELEMENT_H +#define GRAPH_ELEMENT_H + +#include "scene/gui/container.h" + +class GraphElement : public Container { + GDCLASS(GraphElement, Container); + +protected: + bool selected = false; + bool resizable = false; + bool resizing = false; + bool draggable = true; + bool selectable = true; + + Vector2 drag_from; + Vector2 resizing_from; + Vector2 resizing_from_size; + + Vector2 position_offset; + +#ifdef TOOLS_ENABLED + void _edit_set_position(const Point2 &p_position) override; +#endif + +protected: + virtual void gui_input(const Ref<InputEvent> &p_ev) override; + void _notification(int p_what); + static void _bind_methods(); + + virtual void _resort(); + + void _validate_property(PropertyInfo &p_property) const; + +public: + void set_position_offset(const Vector2 &p_offset); + Vector2 get_position_offset() const; + + void set_selected(bool p_selected); + bool is_selected(); + + void set_drag(bool p_drag); + Vector2 get_drag_from(); + + void set_resizable(bool p_enable); + bool is_resizable() const; + + void set_draggable(bool p_draggable); + bool is_draggable(); + + void set_selectable(bool p_selectable); + bool is_selectable(); + + virtual Size2 get_minimum_size() const override; + + bool is_resizing() const { + return resizing; + } + + GraphElement() {} +}; + +#endif // GRAPH_ELEMENT_H diff --git a/scene/gui/graph_node.cpp b/scene/gui/graph_node.cpp index 2223beafda..385b564b7c 100644 --- a/scene/gui/graph_node.cpp +++ b/scene/gui/graph_node.cpp @@ -31,8 +31,8 @@ #include "graph_node.h" #include "core/string/translation.h" - -#include "graph_edit.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { String str = p_name; @@ -42,72 +42,83 @@ bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { } int idx = str.get_slice("/", 1).to_int(); - String what = str.get_slice("/", 2); - - Slot si; - if (slot_info.has(idx)) { - si = slot_info[idx]; - } - - if (what == "left_enabled") { - si.enable_left = p_value; - } else if (what == "left_type") { - si.type_left = p_value; - } else if (what == "left_icon") { - si.custom_slot_left = p_value; - } else if (what == "left_color") { - si.color_left = p_value; - } else if (what == "right_enabled") { - si.enable_right = p_value; - } else if (what == "right_type") { - si.type_right = p_value; - } else if (what == "right_color") { - si.color_right = p_value; - } else if (what == "right_icon") { - si.custom_slot_right = p_value; - } else if (what == "draw_stylebox") { - si.draw_stylebox = p_value; + String slot_property_name = str.get_slice("/", 2); + + Slot slot; + if (slot_table.has(idx)) { + slot = slot_table[idx]; + } + + if (slot_property_name == "left_enabled") { + slot.enable_left = p_value; + } else if (slot_property_name == "left_type") { + slot.type_left = p_value; + } else if (slot_property_name == "left_icon") { + slot.custom_port_icon_left = p_value; + } else if (slot_property_name == "left_color") { + slot.color_left = p_value; + } else if (slot_property_name == "right_enabled") { + slot.enable_right = p_value; + } else if (slot_property_name == "right_type") { + slot.type_right = p_value; + } else if (slot_property_name == "right_color") { + slot.color_right = p_value; + } else if (slot_property_name == "right_icon") { + slot.custom_port_icon_right = p_value; + } else if (slot_property_name == "draw_stylebox") { + slot.draw_stylebox = p_value; } else { return false; } - set_slot(idx, si.enable_left, si.type_left, si.color_left, si.enable_right, si.type_right, si.color_right, si.custom_slot_left, si.custom_slot_right, si.draw_stylebox); + set_slot(idx, + slot.enable_left, + slot.type_left, + slot.color_left, + slot.enable_right, + slot.type_right, + slot.color_right, + slot.custom_port_icon_left, + slot.custom_port_icon_right, + slot.draw_stylebox); + queue_redraw(); return true; } bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { String str = p_name; + if (!str.begins_with("slot/")) { return false; } int idx = str.get_slice("/", 1).to_int(); - String what = str.get_slice("/", 2); - - Slot si; - if (slot_info.has(idx)) { - si = slot_info[idx]; - } - - if (what == "left_enabled") { - r_ret = si.enable_left; - } else if (what == "left_type") { - r_ret = si.type_left; - } else if (what == "left_color") { - r_ret = si.color_left; - } else if (what == "left_icon") { - r_ret = si.custom_slot_left; - } else if (what == "right_enabled") { - r_ret = si.enable_right; - } else if (what == "right_type") { - r_ret = si.type_right; - } else if (what == "right_color") { - r_ret = si.color_right; - } else if (what == "right_icon") { - r_ret = si.custom_slot_right; - } else if (what == "draw_stylebox") { - r_ret = si.draw_stylebox; + StringName slot_property_name = str.get_slice("/", 2); + + Slot slot; + if (slot_table.has(idx)) { + slot = slot_table[idx]; + } + + if (slot_property_name == "left_enabled") { + r_ret = slot.enable_left; + } else if (slot_property_name == "left_type") { + r_ret = slot.type_left; + } else if (slot_property_name == "left_color") { + r_ret = slot.color_left; + } else if (slot_property_name == "left_icon") { + r_ret = slot.custom_port_icon_left; + } else if (slot_property_name == "right_enabled") { + r_ret = slot.enable_right; + } else if (slot_property_name == "right_type") { + r_ret = slot.type_right; + } else if (slot_property_name == "right_color") { + r_ret = slot.color_right; + } else if (slot_property_name == "right_icon") { + r_ret = slot.custom_port_icon_right; + } else if (slot_property_name == "draw_stylebox") { + r_ret = slot.draw_stylebox; } else { return false; } @@ -117,9 +128,9 @@ bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { int idx = 0; - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || c->is_set_as_top_level()) { + for (int i = 0; i < get_child_count(false); i++) { + Control *child = Object::cast_to<Control>(get_child(i, false)); + if (!child || child->is_set_as_top_level()) { continue; } @@ -139,43 +150,51 @@ void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { } void GraphNode::_resort() { - /** First pass, determine minimum size AND amount of stretchable elements */ + Size2 new_size = get_size(); + Ref<StyleBox> sb_panel = get_theme_stylebox(SNAME("panel")); + Ref<StyleBox> sb_titlebar = get_theme_stylebox(SNAME("titlebar")); - Size2i new_size = get_size(); - Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); - Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); + // Resort titlebar first. + Size2 titlebar_size = Size2(new_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); - int sep = get_theme_constant(SNAME("separation")); + // 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(); + + // First pass, determine minimum size AND amount of stretchable elements. + Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); + int separation = get_theme_constant(SNAME("separation")); - bool first = true; int children_count = 0; int stretch_min = 0; - int stretch_avail = 0; + int available_stretch_space = 0; float stretch_ratio_total = 0; HashMap<Control *, _MinSizeCache> min_size_cache; - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + 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() || child->is_set_as_top_level()) { continue; } - Size2i size = c->get_combined_minimum_size() + (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2()); - _MinSizeCache msc; + Size2i size = child->get_combined_minimum_size() + (slot_table[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2()); stretch_min += size.height; + + _MinSizeCache msc; msc.min_size = size.height; - msc.will_stretch = c->get_v_size_flags().has_flag(SIZE_EXPAND); + msc.will_stretch = child->get_v_size_flags().has_flag(SIZE_EXPAND); + msc.final_size = msc.min_size; + min_size_cache[child] = msc; if (msc.will_stretch) { - stretch_avail += msc.min_size; - stretch_ratio_total += c->get_stretch_ratio(); + available_stretch_space += msc.min_size; + stretch_ratio_total += child->get_stretch_ratio(); } - msc.final_size = msc.min_size; - min_size_cache[c] = msc; + children_count++; } @@ -183,43 +202,38 @@ void GraphNode::_resort() { return; } - int stretch_max = new_size.height - (children_count - 1) * sep; + int stretch_max = new_size.height - (children_count - 1) * separation; int stretch_diff = stretch_max - stretch_min; - if (stretch_diff < 0) { - //avoid negative stretch space - stretch_diff = 0; - } - stretch_avail += stretch_diff - sb->get_margin(SIDE_BOTTOM) - sb->get_margin(SIDE_TOP); //available stretch space. - /** Second, pass successively to discard elements that can't be stretched, this will run while stretchable - elements exist */ + // Avoid negative stretch space. + stretch_diff = MAX(stretch_diff, 0); - while (stretch_ratio_total > 0) { // first of all, don't even be here if no stretchable objects exist - bool refit_successful = true; //assume refit-test will go well + available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP); - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + // Second pass, discard elements that can't be stretched, this will run while stretchable elements exist. + + while (stretch_ratio_total > 0) { + // First of all, don't even be here if no stretchable objects exist. + bool refit_successful = true; + + 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() || child->is_set_as_top_level()) { continue; } - ERR_FAIL_COND(!min_size_cache.has(c)); - _MinSizeCache &msc = min_size_cache[c]; - - if (msc.will_stretch) { //wants to stretch - //let's see if it can really stretch + ERR_FAIL_COND(!min_size_cache.has(child)); + _MinSizeCache &msc = min_size_cache[child]; - int final_pixel_size = stretch_avail * c->get_stretch_ratio() / stretch_ratio_total; + if (msc.will_stretch) { + int final_pixel_size = available_stretch_space * child->get_stretch_ratio() / stretch_ratio_total; if (final_pixel_size < msc.min_size) { - //if available stretching area is too small for widget, - //then remove it from stretching area + // If the available stretching area is too small for a Control, + // then remove it from stretching area. msc.will_stretch = false; - stretch_ratio_total -= c->get_stretch_ratio(); + stretch_ratio_total -= child->get_stretch_ratio(); refit_successful = false; - stretch_avail -= msc.min_size; + available_stretch_space -= msc.min_size; msc.final_size = msc.min_size; break; } else { @@ -228,786 +242,514 @@ void GraphNode::_resort() { } } - if (refit_successful) { //uf refit went well, break + if (refit_successful) { break; } } - /** Final pass, draw and stretch elements **/ + // Final pass, draw and stretch elements. - int ofs = sb->get_margin(SIDE_TOP); - - first = true; - int idx = 0; - cache_y.clear(); - int w = new_size.width - sb->get_minimum_size().x; + int ofs_y = sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height; - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible_in_tree()) { + slot_y_cache.clear(); + int width = new_size.width - sb_panel->get_minimum_size().width; + int valid_children_idx = 0; + 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() || child->is_set_as_top_level()) { continue; } - if (c->is_set_as_top_level()) { - continue; + + _MinSizeCache &msc = min_size_cache[child]; + + if (valid_children_idx > 0) { + ofs_y += separation; } - _MinSizeCache &msc = min_size_cache[c]; + int from_y_pos = ofs_y; + int to_y_pos = ofs_y + msc.final_size; - if (first) { - first = false; - } else { - ofs += sep; + // Adjust so the last valid child always fits perfect, compensating for numerical imprecision. + if (msc.will_stretch && valid_children_idx == children_count - 1) { + to_y_pos = new_size.height - sb_panel->get_margin(SIDE_BOTTOM); } - int from = ofs; - int to = ofs + msc.final_size; + int height = to_y_pos - from_y_pos; + float margin = sb_panel->get_margin(SIDE_LEFT) + (slot_table[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0); + float final_width = width - (slot_table[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0); + Rect2 rect(margin, from_y_pos, final_width, height); + fit_child_in_rect(child, rect); - if (msc.will_stretch && idx == children_count - 1) { - //adjust so the last one always fits perfect - //compensating for numerical imprecision + slot_y_cache.push_back(from_y_pos - sb_panel->get_margin(SIDE_TOP) + height * 0.5); - to = new_size.height - sb->get_margin(SIDE_BOTTOM); - } + ofs_y = to_y_pos; + valid_children_idx++; + } - int size = to - from; + queue_redraw(); + port_pos_dirty = true; +} - float margin = sb->get_margin(SIDE_LEFT) + (slot_info[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0); - float width = w - (slot_info[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0); - Rect2 rect(margin, from, width, size); +void GraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) { + if (GDVIRTUAL_CALL(_draw_port, p_slot_index, p_pos, p_left, p_color)) { + return; + } - fit_child_in_rect(c, rect); - cache_y.push_back(from - sb->get_margin(SIDE_TOP) + size * 0.5); + Slot slot = slot_table[p_slot_index]; + Ref<Texture2D> port_icon = p_left ? slot.custom_port_icon_left : slot.custom_port_icon_right; - ofs = to; - idx++; + Point2 icon_offset; + if (!port_icon.is_valid()) { + port_icon = get_theme_icon(SNAME("port")); } - queue_redraw(); - connpos_dirty = true; + icon_offset = -port_icon->get_size() * 0.5; + port_icon->draw(get_canvas_item(), p_pos + icon_offset, p_color); } void GraphNode::_notification(int p_what) { switch (p_what) { case NOTIFICATION_DRAW: { - Ref<StyleBox> sb; - - sb = get_theme_stylebox(selected ? SNAME("selected_frame") : SNAME("frame")); + // Used for layout calculations. + Ref<StyleBox> sb_panel = get_theme_stylebox(SNAME("panel")); + Ref<StyleBox> sb_titlebar = get_theme_stylebox(SNAME("titlebar")); + // Used for drawing. + Ref<StyleBox> sb_to_draw_panel = get_theme_stylebox(selected ? SNAME("panel_selected") : SNAME("panel")); + Ref<StyleBox> sb_to_draw_titlebar = get_theme_stylebox(selected ? SNAME("titlebar_selected") : SNAME("titlebar")); Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); - Ref<Texture2D> port = get_theme_icon(SNAME("port")); - Ref<Texture2D> close = get_theme_icon(SNAME("close")); - Ref<Texture2D> resizer = get_theme_icon(SNAME("resizer")); - int close_offset = get_theme_constant(SNAME("close_offset")); - int close_h_offset = get_theme_constant(SNAME("close_h_offset")); - Color close_color = get_theme_color(SNAME("close_color")); + int port_h_offset = get_theme_constant(SNAME("port_h_offset")); + + Ref<Texture2D> resizer_icon = get_theme_icon(SNAME("resizer")); + Color resizer_color = get_theme_color(SNAME("resizer_color")); - int title_offset = get_theme_constant(SNAME("title_offset")); - int title_h_offset = get_theme_constant(SNAME("title_h_offset")); - Color title_color = get_theme_color(SNAME("title_color")); - Point2i icofs = -port->get_size() * 0.5; - int edgeofs = get_theme_constant(SNAME("port_offset")); - icofs.y += sb->get_margin(SIDE_TOP); - - draw_style_box(sb, Rect2(Point2(), get_size())); - - switch (overlay) { - case OVERLAY_DISABLED: { - } break; - case OVERLAY_BREAKPOINT: { - draw_style_box(get_theme_stylebox(SNAME("breakpoint")), Rect2(Point2(), get_size())); - } break; - case OVERLAY_POSITION: { - draw_style_box(get_theme_stylebox(SNAME("position")), Rect2(Point2(), get_size())); - - } break; - } - int w = get_size().width - sb->get_minimum_size().x; + Rect2 titlebar_rect(Point2(), titlebar_hbox->get_size() + sb_titlebar->get_minimum_size()); + Size2 body_size = get_size(); + titlebar_rect.size.width = body_size.width; + body_size.height -= titlebar_rect.size.height; + Rect2 body_rect(0, titlebar_rect.size.height, body_size.width, body_size.height); - title_buf->draw(get_canvas_item(), Point2(sb->get_margin(SIDE_LEFT) + title_h_offset, -title_buf->get_size().y + title_offset), title_color); - if (show_close) { - Vector2 cpos = Point2(w + sb->get_margin(SIDE_LEFT) + close_h_offset - close->get_width(), -close->get_height() + close_offset); - draw_texture(close, cpos, close_color); - close_rect.position = cpos; - close_rect.size = close->get_size(); - } else { - close_rect = Rect2(); - } + // Draw body (slots area) stylebox. + draw_style_box(sb_to_draw_panel, body_rect); + + // Draw title bar stylebox above. + draw_style_box(sb_to_draw_titlebar, titlebar_rect); + + int width = get_size().width - sb_panel->get_minimum_size().x; if (get_child_count() > 0) { - for (const KeyValue<int, Slot> &E : slot_info) { - if (E.key < 0 || E.key >= cache_y.size()) { + int slot_index = 0; + for (const KeyValue<int, Slot> &E : slot_table) { + if (E.key < 0 || E.key >= slot_y_cache.size()) { continue; } - if (!slot_info.has(E.key)) { + if (!slot_table.has(E.key)) { continue; } - const Slot &s = slot_info[E.key]; + const Slot &slot = slot_table[E.key]; + // Left port. - if (s.enable_left) { - Ref<Texture2D> p = port; - if (s.custom_slot_left.is_valid()) { - p = s.custom_slot_left; - } - p->draw(get_canvas_item(), icofs + Point2(edgeofs, cache_y[E.key]), s.color_left); + if (slot.enable_left) { + draw_port(slot_index, Point2i(port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), true, slot.color_left); } + // Right port. - if (s.enable_right) { - Ref<Texture2D> p = port; - if (s.custom_slot_right.is_valid()) { - p = s.custom_slot_right; - } - p->draw(get_canvas_item(), icofs + Point2(get_size().x - edgeofs, cache_y[E.key]), s.color_right); + if (slot.enable_right) { + draw_port(slot_index, Point2i(get_size().x - port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), false, slot.color_right); } // Draw slot stylebox. - if (s.draw_stylebox) { - Control *c = Object::cast_to<Control>(get_child(E.key)); - if (!c || !c->is_visible_in_tree()) { - continue; - } - if (c->is_set_as_top_level()) { + if (slot.draw_stylebox) { + Control *child = Object::cast_to<Control>(get_child(E.key, false)); + if (!child || !child->is_visible_in_tree()) { continue; } - Rect2 c_rect = c->get_rect(); - c_rect.position.x = sb->get_margin(SIDE_LEFT); - c_rect.size.width = w; - draw_style_box(sb_slot, c_rect); + Rect2 child_rect = child->get_rect(); + child_rect.position.x = sb_panel->get_margin(SIDE_LEFT); + child_rect.size.width = width; + draw_style_box(sb_slot, child_rect); } + + slot_index++; } } if (resizable) { - draw_texture(resizer, get_size() - resizer->get_size(), resizer_color); + draw_texture(resizer_icon, get_size() - resizer_icon->get_size(), resizer_color); } } break; - - case NOTIFICATION_SORT_CHILDREN: { - _resort(); - } break; - - case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: - case NOTIFICATION_TRANSLATION_CHANGED: - case NOTIFICATION_THEME_CHANGED: { - _shape(); - - update_minimum_size(); - queue_redraw(); - } break; } } -void GraphNode::_shape() { - Ref<Font> font = get_theme_font(SNAME("title_font")); - int font_size = get_theme_font_size(SNAME("title_font_size")); - - title_buf->clear(); - if (text_direction == Control::TEXT_DIRECTION_INHERITED) { - title_buf->set_direction(is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR); - } else { - title_buf->set_direction((TextServer::Direction)text_direction); - } - title_buf->add_string(title, font, font_size, language); -} - -#ifdef TOOLS_ENABLED -void GraphNode::_edit_set_position(const Point2 &p_position) { - GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); - if (graph) { - Point2 offset = (p_position + graph->get_scroll_offset()) * graph->get_zoom(); - set_position_offset(offset); - } - set_position(p_position); -} -#endif - -void GraphNode::_validate_property(PropertyInfo &p_property) const { - GraphEdit *graph = Object::cast_to<GraphEdit>(get_parent()); - if (graph) { - if (p_property.name == "position") { - p_property.usage |= PROPERTY_USAGE_READ_ONLY; - } - } -} - -void GraphNode::set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) { - ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set slot with p_idx (%d) lesser than zero.", p_idx)); +void GraphNode::set_slot(int p_slot_index, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) { + ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set slot with index (%d) lesser than zero.", p_slot_index)); if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) && !p_enable_right && p_type_right == 0 && p_color_right == Color(1, 1, 1, 1) && !p_custom_left.is_valid() && !p_custom_right.is_valid()) { - slot_info.erase(p_idx); + slot_table.erase(p_slot_index); return; } - Slot s; - s.enable_left = p_enable_left; - s.type_left = p_type_left; - s.color_left = p_color_left; - s.enable_right = p_enable_right; - s.type_right = p_type_right; - s.color_right = p_color_right; - s.custom_slot_left = p_custom_left; - s.custom_slot_right = p_custom_right; - s.draw_stylebox = p_draw_stylebox; - slot_info[p_idx] = s; + Slot slot; + slot.enable_left = p_enable_left; + slot.type_left = p_type_left; + slot.color_left = p_color_left; + slot.enable_right = p_enable_right; + slot.type_right = p_type_right; + slot.color_right = p_color_right; + slot.custom_port_icon_left = p_custom_left; + slot.custom_port_icon_right = p_custom_right; + slot.draw_stylebox = p_draw_stylebox; + slot_table[p_slot_index] = slot; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -void GraphNode::clear_slot(int p_idx) { - slot_info.erase(p_idx); +void GraphNode::clear_slot(int p_slot_index) { + slot_table.erase(p_slot_index); queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; } void GraphNode::clear_all_slots() { - slot_info.clear(); + slot_table.clear(); queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; } -bool GraphNode::is_slot_enabled_left(int p_idx) const { - if (!slot_info.has(p_idx)) { +bool GraphNode::is_slot_enabled_left(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return false; } - return slot_info[p_idx].enable_left; + return slot_table[p_slot_index].enable_left; } -void GraphNode::set_slot_enabled_left(int p_idx, bool p_enable_left) { - ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_left for the slot with p_idx (%d) lesser than zero.", p_idx)); +void GraphNode::set_slot_enabled_left(int p_slot_index, bool p_enable) { + ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set enable_left for the slot with index (%d) lesser than zero.", p_slot_index)); - if (slot_info[p_idx].enable_left == p_enable_left) { + if (slot_table[p_slot_index].enable_left == p_enable) { return; } - slot_info[p_idx].enable_left = p_enable_left; + slot_table[p_slot_index].enable_left = p_enable; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -void GraphNode::set_slot_type_left(int p_idx, int p_type_left) { - ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_left for the slot '%d' because it hasn't been enabled.", p_idx)); +void GraphNode::set_slot_type_left(int p_slot_index, int p_type) { + ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set type_left for the slot with index '%d' because it hasn't been enabled.", p_slot_index)); - if (slot_info[p_idx].type_left == p_type_left) { + if (slot_table[p_slot_index].type_left == p_type) { return; } - slot_info[p_idx].type_left = p_type_left; + slot_table[p_slot_index].type_left = p_type; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -int GraphNode::get_slot_type_left(int p_idx) const { - if (!slot_info.has(p_idx)) { +int GraphNode::get_slot_type_left(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return 0; } - return slot_info[p_idx].type_left; + return slot_table[p_slot_index].type_left; } -void GraphNode::set_slot_color_left(int p_idx, const Color &p_color_left) { - ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_left for the slot '%d' because it hasn't been enabled.", p_idx)); +void GraphNode::set_slot_color_left(int p_slot_index, const Color &p_color) { + ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set color_left for the slot with index '%d' because it hasn't been enabled.", p_slot_index)); - if (slot_info[p_idx].color_left == p_color_left) { + if (slot_table[p_slot_index].color_left == p_color) { return; } - slot_info[p_idx].color_left = p_color_left; + slot_table[p_slot_index].color_left = p_color; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -Color GraphNode::get_slot_color_left(int p_idx) const { - if (!slot_info.has(p_idx)) { +Color GraphNode::get_slot_color_left(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return Color(1, 1, 1, 1); } - return slot_info[p_idx].color_left; + return slot_table[p_slot_index].color_left; } -bool GraphNode::is_slot_enabled_right(int p_idx) const { - if (!slot_info.has(p_idx)) { +bool GraphNode::is_slot_enabled_right(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return false; } - return slot_info[p_idx].enable_right; + return slot_table[p_slot_index].enable_right; } -void GraphNode::set_slot_enabled_right(int p_idx, bool p_enable_right) { - ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set enable_right for the slot with p_idx (%d) lesser than zero.", p_idx)); +void GraphNode::set_slot_enabled_right(int p_slot_index, bool p_enable) { + ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set enable_right for the slot with index (%d) lesser than zero.", p_slot_index)); - if (slot_info[p_idx].enable_right == p_enable_right) { + if (slot_table[p_slot_index].enable_right == p_enable) { return; } - slot_info[p_idx].enable_right = p_enable_right; + slot_table[p_slot_index].enable_right = p_enable; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -void GraphNode::set_slot_type_right(int p_idx, int p_type_right) { - ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set type_right for the slot '%d' because it hasn't been enabled.", p_idx)); +void GraphNode::set_slot_type_right(int p_slot_index, int p_type) { + ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set type_right for the slot with index '%d' because it hasn't been enabled.", p_slot_index)); - if (slot_info[p_idx].type_right == p_type_right) { + if (slot_table[p_slot_index].type_right == p_type) { return; } - slot_info[p_idx].type_right = p_type_right; + slot_table[p_slot_index].type_right = p_type; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -int GraphNode::get_slot_type_right(int p_idx) const { - if (!slot_info.has(p_idx)) { +int GraphNode::get_slot_type_right(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return 0; } - return slot_info[p_idx].type_right; + return slot_table[p_slot_index].type_right; } -void GraphNode::set_slot_color_right(int p_idx, const Color &p_color_right) { - ERR_FAIL_COND_MSG(!slot_info.has(p_idx), vformat("Cannot set color_right for the slot '%d' because it hasn't been enabled.", p_idx)); +void GraphNode::set_slot_color_right(int p_slot_index, const Color &p_color) { + ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set color_right for the slot with index '%d' because it hasn't been enabled.", p_slot_index)); - if (slot_info[p_idx].color_right == p_color_right) { + if (slot_table[p_slot_index].color_right == p_color) { return; } - slot_info[p_idx].color_right = p_color_right; + slot_table[p_slot_index].color_right = p_color; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } -Color GraphNode::get_slot_color_right(int p_idx) const { - if (!slot_info.has(p_idx)) { +Color GraphNode::get_slot_color_right(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return Color(1, 1, 1, 1); } - return slot_info[p_idx].color_right; + return slot_table[p_slot_index].color_right; } -bool GraphNode::is_slot_draw_stylebox(int p_idx) const { - if (!slot_info.has(p_idx)) { +bool GraphNode::is_slot_draw_stylebox(int p_slot_index) const { + if (!slot_table.has(p_slot_index)) { return false; } - return slot_info[p_idx].draw_stylebox; + return slot_table[p_slot_index].draw_stylebox; } -void GraphNode::set_slot_draw_stylebox(int p_idx, bool p_enable) { - ERR_FAIL_COND_MSG(p_idx < 0, vformat("Cannot set draw_stylebox for the slot with p_idx (%d) lesser than zero.", p_idx)); +void GraphNode::set_slot_draw_stylebox(int p_slot_index, bool p_enable) { + ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set draw_stylebox for the slot with p_index (%d) lesser than zero.", p_slot_index)); - slot_info[p_idx].draw_stylebox = p_enable; + slot_table[p_slot_index].draw_stylebox = p_enable; queue_redraw(); - connpos_dirty = true; + port_pos_dirty = true; - emit_signal(SNAME("slot_updated"), p_idx); + emit_signal(SNAME("slot_updated"), p_slot_index); } Size2 GraphNode::get_minimum_size() const { - Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); + Ref<StyleBox> sb_panel = get_theme_stylebox(SNAME("panel")); + Ref<StyleBox> sb_titlebar = get_theme_stylebox(SNAME("titlebar")); Ref<StyleBox> sb_slot = get_theme_stylebox(SNAME("slot")); - int sep = get_theme_constant(SNAME("separation")); - int title_h_offset = get_theme_constant(SNAME("title_h_offset")); - - bool first = true; + int separation = get_theme_constant(SNAME("separation")); + Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size(); - Size2 minsize; - minsize.x = title_buf->get_size().x + title_h_offset; - if (show_close) { - int close_h_offset = get_theme_constant(SNAME("close_h_offset")); - Ref<Texture2D> close = get_theme_icon(SNAME("close")); - //TODO: Remove this magic number after GraphNode rework. - minsize.x += 12 + close->get_width() + close_h_offset; - } - - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c || !c->is_visible()) { - continue; - } - if (c->is_set_as_top_level()) { + 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 = c->get_combined_minimum_size(); - if (slot_info.has(i)) { - size += slot_info[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2(); + Size2i size = child->get_combined_minimum_size(); + size.width += sb_panel->get_minimum_size().width; + if (slot_table.has(i)) { + size += slot_table[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2(); } - minsize.y += size.y; - minsize.x = MAX(minsize.x, size.x); + minsize.height += size.height; + minsize.width = MAX(minsize.width, size.width); - if (first) { - first = false; - } else { - minsize.y += sep; + if (i > 0) { + minsize.height += separation; } } - return minsize + sb->get_minimum_size(); -} - -void GraphNode::set_title(const String &p_title) { - if (title == p_title) { - return; - } - title = p_title; - _shape(); - - queue_redraw(); - update_minimum_size(); -} - -String GraphNode::get_title() const { - return title; -} - -void GraphNode::set_text_direction(Control::TextDirection p_text_direction) { - ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3); - if (text_direction != p_text_direction) { - text_direction = p_text_direction; - _shape(); - queue_redraw(); - } -} + minsize.height += sb_panel->get_minimum_size().height; -Control::TextDirection GraphNode::get_text_direction() const { - return text_direction; + return minsize; } -void GraphNode::set_language(const String &p_language) { - if (language != p_language) { - language = p_language; - _shape(); - queue_redraw(); - } -} +void GraphNode::_port_pos_update() { + int edgeofs = get_theme_constant(SNAME("port_h_offset")); + int separation = get_theme_constant(SNAME("separation")); -String GraphNode::get_language() const { - return language; -} + Ref<StyleBox> sb_panel = get_theme_stylebox(SNAME("panel")); + Ref<StyleBox> sb_titlebar = get_theme_stylebox(SNAME("titlebar")); -void GraphNode::set_position_offset(const Vector2 &p_offset) { - if (position_offset == p_offset) { - return; - } - - position_offset = p_offset; - emit_signal(SNAME("position_offset_changed")); - queue_redraw(); -} - -Vector2 GraphNode::get_position_offset() const { - return position_offset; -} - -void GraphNode::set_selected(bool p_selected) { - if (!is_selectable() || selected == p_selected) { - return; - } - - selected = p_selected; - emit_signal(p_selected ? SNAME("node_selected") : SNAME("node_deselected")); - queue_redraw(); -} - -bool GraphNode::is_selected() { - return selected; -} - -void GraphNode::set_drag(bool p_drag) { - if (p_drag) { - drag_from = get_position_offset(); - } else { - emit_signal(SNAME("dragged"), drag_from, get_position_offset()); //useful for undo/redo - } -} - -Vector2 GraphNode::get_drag_from() { - return drag_from; -} - -void GraphNode::set_show_close_button(bool p_enable) { - if (show_close == p_enable) { - return; - } - - show_close = p_enable; - queue_redraw(); -} - -bool GraphNode::is_close_button_visible() const { - return show_close; -} - -void GraphNode::_connpos_update() { - int edgeofs = get_theme_constant(SNAME("port_offset")); - int sep = get_theme_constant(SNAME("separation")); - - Ref<StyleBox> sb = get_theme_stylebox(SNAME("frame")); left_port_cache.clear(); right_port_cache.clear(); - int vofs = 0; - - int idx = 0; + int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP); - for (int i = 0; i < get_child_count(); i++) { - Control *c = Object::cast_to<Control>(get_child(i)); - if (!c) { + for (int i = 0; i < get_child_count(false); i++) { + Control *child = Object::cast_to<Control>(get_child(i, false)); + if (!child || child->is_set_as_top_level()) { continue; } - if (c->is_set_as_top_level()) { - continue; - } - - Size2i size = c->get_rect().size; - int y = sb->get_margin(SIDE_TOP) + vofs; - int h = size.height; + Size2i size = child->get_rect().size; - if (slot_info.has(idx)) { - if (slot_info[idx].enable_left) { - PortCache cc; - cc.position = Point2i(edgeofs, y + h / 2); - cc.height = h; - - cc.slot_idx = idx; - cc.type = slot_info[idx].type_left; - cc.color = slot_info[idx].color_left; - - left_port_cache.push_back(cc); + if (slot_table.has(i)) { + if (slot_table[i].enable_left) { + PortCache port_cache; + port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2); + port_cache.type = slot_table[i].type_left; + port_cache.color = slot_table[i].color_left; + port_cache.slot_index = child->get_index(); // Index with internal nodes included. + left_port_cache.push_back(port_cache); } - if (slot_info[idx].enable_right) { - PortCache cc; - cc.position = Point2i(get_size().width - edgeofs, y + h / 2); - cc.height = h; - - cc.slot_idx = idx; - cc.type = slot_info[idx].type_right; - cc.color = slot_info[idx].color_right; - - right_port_cache.push_back(cc); + if (slot_table[i].enable_right) { + PortCache port_cache; + port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2); + port_cache.type = slot_table[i].type_right; + port_cache.color = slot_table[i].color_right; + port_cache.slot_index = child->get_index(); // Index with internal nodes included. + right_port_cache.push_back(port_cache); } } - vofs += sep; - vofs += h; - idx++; + vertical_ofs += separation; + vertical_ofs += size.height; } - connpos_dirty = false; + port_pos_dirty = false; } -int GraphNode::get_connection_input_count() { - if (connpos_dirty) { - _connpos_update(); +int GraphNode::get_input_port_count() { + if (port_pos_dirty) { + _port_pos_update(); } return left_port_cache.size(); } -int GraphNode::get_connection_input_height(int p_port) { - if (connpos_dirty) { - _connpos_update(); +int GraphNode::get_output_port_count() { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), 0); - return left_port_cache[p_port].height; + return right_port_cache.size(); } -Vector2 GraphNode::get_connection_input_position(int p_port) { - if (connpos_dirty) { - _connpos_update(); +Vector2 GraphNode::get_input_port_position(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), Vector2()); - Vector2 pos = left_port_cache[p_port].position; - pos.x *= get_scale().x; - pos.y *= get_scale().y; + ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), Vector2()); + Vector2 pos = left_port_cache[p_port_idx].pos; return pos; } -int GraphNode::get_connection_input_type(int p_port) { - if (connpos_dirty) { - _connpos_update(); - } - - ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), 0); - return left_port_cache[p_port].type; -} - -Color GraphNode::get_connection_input_color(int p_port) { - if (connpos_dirty) { - _connpos_update(); - } - - ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), Color()); - return left_port_cache[p_port].color; -} - -int GraphNode::get_connection_input_slot(int p_port) { - if (connpos_dirty) { - _connpos_update(); +int GraphNode::get_input_port_type(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, left_port_cache.size(), -1); - return left_port_cache[p_port].slot_idx; + ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), 0); + return left_port_cache[p_port_idx].type; } -int GraphNode::get_connection_output_count() { - if (connpos_dirty) { - _connpos_update(); +Color GraphNode::get_input_port_color(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - return right_port_cache.size(); + ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), Color()); + return left_port_cache[p_port_idx].color; } -int GraphNode::get_connection_output_height(int p_port) { - if (connpos_dirty) { - _connpos_update(); +int GraphNode::get_input_port_slot(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), 0); - return right_port_cache[p_port].height; + ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), -1); + return left_port_cache[p_port_idx].slot_index; } -Vector2 GraphNode::get_connection_output_position(int p_port) { - if (connpos_dirty) { - _connpos_update(); +Vector2 GraphNode::get_output_port_position(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), Vector2()); - Vector2 pos = right_port_cache[p_port].position; - pos.x *= get_scale().x; - pos.y *= get_scale().y; + ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), Vector2()); + Vector2 pos = right_port_cache[p_port_idx].pos; return pos; } -int GraphNode::get_connection_output_type(int p_port) { - if (connpos_dirty) { - _connpos_update(); +int GraphNode::get_output_port_type(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), 0); - return right_port_cache[p_port].type; + ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), 0); + return right_port_cache[p_port_idx].type; } -Color GraphNode::get_connection_output_color(int p_port) { - if (connpos_dirty) { - _connpos_update(); +Color GraphNode::get_output_port_color(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), Color()); - return right_port_cache[p_port].color; + ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), Color()); + return right_port_cache[p_port_idx].color; } -int GraphNode::get_connection_output_slot(int p_port) { - if (connpos_dirty) { - _connpos_update(); +int GraphNode::get_output_port_slot(int p_port_idx) { + if (port_pos_dirty) { + _port_pos_update(); } - ERR_FAIL_INDEX_V(p_port, right_port_cache.size(), -1); - return right_port_cache[p_port].slot_idx; + ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), -1); + return right_port_cache[p_port_idx].slot_index; } -void GraphNode::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_COND_MSG(get_parent_control() == nullptr, "GraphNode must be the child of a GraphEdit node."); - - if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { - Vector2 mpos = mb->get_position(); - if (close_rect.size != Size2() && close_rect.has_point(mpos)) { - //send focus to parent - get_parent_control()->grab_focus(); - emit_signal(SNAME("close_request")); - accept_event(); - return; - } - - Ref<Texture2D> resizer = get_theme_icon(SNAME("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) { - resizing = false; - } - } - - Ref<InputEventMouseMotion> mm = p_ev; - if (resizing && mm.is_valid()) { - Vector2 mpos = mm->get_position(); - Vector2 diff = mpos - resizing_from; - - emit_signal(SNAME("resize_request"), resizing_from_size + diff); - } -} - -void GraphNode::set_overlay(Overlay p_overlay) { - if (overlay == p_overlay) { +void GraphNode::set_title(const String &p_title) { + if (title == p_title) { return; } - - overlay = p_overlay; - queue_redraw(); -} - -GraphNode::Overlay GraphNode::get_overlay() const { - return overlay; -} - -void GraphNode::set_resizable(bool p_enable) { - if (resizable == p_enable) { - return; + title = p_title; + if (title_label) { + title_label->set_text(title); } - - resizable = p_enable; - queue_redraw(); -} - -bool GraphNode::is_resizable() const { - return resizable; -} - -void GraphNode::set_draggable(bool p_draggable) { - draggable = p_draggable; -} - -bool GraphNode::is_draggable() { - return draggable; + update_minimum_size(); } -void GraphNode::set_selectable(bool p_selectable) { - if (!p_selectable) { - set_selected(false); - } - selectable = p_selectable; +String GraphNode::get_title() const { + return title; } -bool GraphNode::is_selectable() { - return selectable; +HBoxContainer *GraphNode::get_titlebar_hbox() { + return titlebar_hbox; } Control::CursorShape GraphNode::get_cursor_shape(const Point2 &p_pos) const { @@ -1044,17 +786,15 @@ Vector<int> GraphNode::get_allowed_size_flags_vertical() const { void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_title", "title"), &GraphNode::set_title); ClassDB::bind_method(D_METHOD("get_title"), &GraphNode::get_title); - ClassDB::bind_method(D_METHOD("set_text_direction", "direction"), &GraphNode::set_text_direction); - ClassDB::bind_method(D_METHOD("get_text_direction"), &GraphNode::get_text_direction); - ClassDB::bind_method(D_METHOD("set_language", "language"), &GraphNode::set_language); - ClassDB::bind_method(D_METHOD("get_language"), &GraphNode::get_language); + + ClassDB::bind_method(D_METHOD("get_titlebar_hbox"), &GraphNode::get_titlebar_hbox); ClassDB::bind_method(D_METHOD("set_slot", "slot_index", "enable_left_port", "type_left", "color_left", "enable_right_port", "type_right", "color_right", "custom_icon_left", "custom_icon_right", "draw_stylebox"), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true)); ClassDB::bind_method(D_METHOD("clear_slot", "slot_index"), &GraphNode::clear_slot); ClassDB::bind_method(D_METHOD("clear_all_slots"), &GraphNode::clear_all_slots); - ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "slot_index", "enable"), &GraphNode::set_slot_enabled_left); ClassDB::bind_method(D_METHOD("is_slot_enabled_left", "slot_index"), &GraphNode::is_slot_enabled_left); + ClassDB::bind_method(D_METHOD("set_slot_enabled_left", "slot_index", "enable"), &GraphNode::set_slot_enabled_left); ClassDB::bind_method(D_METHOD("set_slot_type_left", "slot_index", "type"), &GraphNode::set_slot_type_left); ClassDB::bind_method(D_METHOD("get_slot_type_left", "slot_index"), &GraphNode::get_slot_type_left); @@ -1062,8 +802,8 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("set_slot_color_left", "slot_index", "color"), &GraphNode::set_slot_color_left); ClassDB::bind_method(D_METHOD("get_slot_color_left", "slot_index"), &GraphNode::get_slot_color_left); - ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "slot_index", "enable"), &GraphNode::set_slot_enabled_right); ClassDB::bind_method(D_METHOD("is_slot_enabled_right", "slot_index"), &GraphNode::is_slot_enabled_right); + ClassDB::bind_method(D_METHOD("set_slot_enabled_right", "slot_index", "enable"), &GraphNode::set_slot_enabled_right); ClassDB::bind_method(D_METHOD("set_slot_type_right", "slot_index", "type"), &GraphNode::set_slot_type_right); ClassDB::bind_method(D_METHOD("get_slot_type_right", "slot_index"), &GraphNode::get_slot_type_right); @@ -1074,70 +814,33 @@ void GraphNode::_bind_methods() { ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox", "slot_index"), &GraphNode::is_slot_draw_stylebox); ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox", "slot_index", "enable"), &GraphNode::set_slot_draw_stylebox); - ClassDB::bind_method(D_METHOD("set_position_offset", "offset"), &GraphNode::set_position_offset); - ClassDB::bind_method(D_METHOD("get_position_offset"), &GraphNode::get_position_offset); - - ClassDB::bind_method(D_METHOD("set_resizable", "resizable"), &GraphNode::set_resizable); - ClassDB::bind_method(D_METHOD("is_resizable"), &GraphNode::is_resizable); - - ClassDB::bind_method(D_METHOD("set_draggable", "draggable"), &GraphNode::set_draggable); - ClassDB::bind_method(D_METHOD("is_draggable"), &GraphNode::is_draggable); + ClassDB::bind_method(D_METHOD("get_input_port_count"), &GraphNode::get_input_port_count); + ClassDB::bind_method(D_METHOD("get_input_port_position", "port_idx"), &GraphNode::get_input_port_position); + ClassDB::bind_method(D_METHOD("get_input_port_type", "port_idx"), &GraphNode::get_input_port_type); + ClassDB::bind_method(D_METHOD("get_input_port_color", "port_idx"), &GraphNode::get_input_port_color); + ClassDB::bind_method(D_METHOD("get_input_port_slot", "port_idx"), &GraphNode::get_input_port_slot); - ClassDB::bind_method(D_METHOD("set_selectable", "selectable"), &GraphNode::set_selectable); - ClassDB::bind_method(D_METHOD("is_selectable"), &GraphNode::is_selectable); + ClassDB::bind_method(D_METHOD("get_output_port_count"), &GraphNode::get_output_port_count); + ClassDB::bind_method(D_METHOD("get_output_port_position", "port_idx"), &GraphNode::get_output_port_position); + ClassDB::bind_method(D_METHOD("get_output_port_type", "port_idx"), &GraphNode::get_output_port_type); + ClassDB::bind_method(D_METHOD("get_output_port_color", "port_idx"), &GraphNode::get_output_port_color); + ClassDB::bind_method(D_METHOD("get_output_port_slot", "port_idx"), &GraphNode::get_output_port_slot); - ClassDB::bind_method(D_METHOD("set_selected", "selected"), &GraphNode::set_selected); - ClassDB::bind_method(D_METHOD("is_selected"), &GraphNode::is_selected); - - ClassDB::bind_method(D_METHOD("get_connection_input_count"), &GraphNode::get_connection_input_count); - ClassDB::bind_method(D_METHOD("get_connection_input_height", "port"), &GraphNode::get_connection_input_height); - ClassDB::bind_method(D_METHOD("get_connection_input_position", "port"), &GraphNode::get_connection_input_position); - ClassDB::bind_method(D_METHOD("get_connection_input_type", "port"), &GraphNode::get_connection_input_type); - ClassDB::bind_method(D_METHOD("get_connection_input_color", "port"), &GraphNode::get_connection_input_color); - ClassDB::bind_method(D_METHOD("get_connection_input_slot", "port"), &GraphNode::get_connection_input_slot); - - ClassDB::bind_method(D_METHOD("get_connection_output_count"), &GraphNode::get_connection_output_count); - ClassDB::bind_method(D_METHOD("get_connection_output_height", "port"), &GraphNode::get_connection_output_height); - ClassDB::bind_method(D_METHOD("get_connection_output_position", "port"), &GraphNode::get_connection_output_position); - ClassDB::bind_method(D_METHOD("get_connection_output_type", "port"), &GraphNode::get_connection_output_type); - ClassDB::bind_method(D_METHOD("get_connection_output_color", "port"), &GraphNode::get_connection_output_color); - ClassDB::bind_method(D_METHOD("get_connection_output_slot", "port"), &GraphNode::get_connection_output_slot); - - ClassDB::bind_method(D_METHOD("set_show_close_button", "show"), &GraphNode::set_show_close_button); - ClassDB::bind_method(D_METHOD("is_close_button_visible"), &GraphNode::is_close_button_visible); - - ClassDB::bind_method(D_METHOD("set_overlay", "overlay"), &GraphNode::set_overlay); - ClassDB::bind_method(D_METHOD("get_overlay"), &GraphNode::get_overlay); + GDVIRTUAL_BIND(_draw_port, "slot_index", "position", "left", "color") ADD_PROPERTY(PropertyInfo(Variant::STRING, "title"), "set_title", "get_title"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position_offset", PROPERTY_HINT_NONE, "suffix:px"), "set_position_offset", "get_position_offset"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "show_close"), "set_show_close_button", "is_close_button_visible"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "resizable"), "set_resizable", "is_resizable"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "draggable"), "set_draggable", "is_draggable"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selectable"), "set_selectable", "is_selectable"); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "selected"), "set_selected", "is_selected"); - ADD_PROPERTY(PropertyInfo(Variant::INT, "overlay", PROPERTY_HINT_ENUM, "Disabled,Breakpoint,Position"), "set_overlay", "get_overlay"); - - ADD_GROUP("BiDi", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction"); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language"); - ADD_GROUP("", ""); - - ADD_SIGNAL(MethodInfo("position_offset_changed")); - ADD_SIGNAL(MethodInfo("node_selected")); - ADD_SIGNAL(MethodInfo("node_deselected")); - ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "idx"))); - ADD_SIGNAL(MethodInfo("dragged", PropertyInfo(Variant::VECTOR2, "from"), PropertyInfo(Variant::VECTOR2, "to"))); - ADD_SIGNAL(MethodInfo("raise_request")); - ADD_SIGNAL(MethodInfo("close_request")); - ADD_SIGNAL(MethodInfo("resize_request", PropertyInfo(Variant::VECTOR2, "new_minsize"))); - - BIND_ENUM_CONSTANT(OVERLAY_DISABLED); - BIND_ENUM_CONSTANT(OVERLAY_BREAKPOINT); - BIND_ENUM_CONSTANT(OVERLAY_POSITION); + ADD_SIGNAL(MethodInfo("slot_updated", PropertyInfo(Variant::INT, "slot_index"))); } GraphNode::GraphNode() { - title_buf.instantiate(); + 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("GraphNodeTitleLabel"); + title_label->set_h_size_flags(SIZE_EXPAND_FILL); + titlebar_hbox->add_child(title_label); + set_mouse_filter(MOUSE_FILTER_STOP); } diff --git a/scene/gui/graph_node.h b/scene/gui/graph_node.h index 873683bc62..1178421819 100644 --- a/scene/gui/graph_node.h +++ b/scene/gui/graph_node.h @@ -31,170 +31,111 @@ #ifndef GRAPH_NODE_H #define GRAPH_NODE_H -#include "scene/gui/container.h" -#include "scene/resources/text_line.h" +#include "scene/gui/graph_element.h" -class GraphNode : public Container { - GDCLASS(GraphNode, Container); +class HBoxContainer; - struct _MinSizeCache { - int min_size; - bool will_stretch; - int final_size; - }; +class GraphNode : public GraphElement { + GDCLASS(GraphNode, GraphElement); -public: - enum Overlay { - OVERLAY_DISABLED, - OVERLAY_BREAKPOINT, - OVERLAY_POSITION - }; - -private: struct Slot { bool enable_left = false; int type_left = 0; Color color_left = Color(1, 1, 1, 1); + Ref<Texture2D> custom_port_icon_left; + bool enable_right = false; int type_right = 0; Color color_right = Color(1, 1, 1, 1); - Ref<Texture2D> custom_slot_left; - Ref<Texture2D> custom_slot_right; + Ref<Texture2D> custom_port_icon_right; + bool draw_stylebox = true; }; - String title; - Ref<TextLine> title_buf; - - String language; - TextDirection text_direction = TEXT_DIRECTION_AUTO; - - bool show_close = false; - Vector2 position_offset; - bool comment = false; - bool resizable = false; - bool draggable = true; - bool selectable = true; - - bool resizing = false; - Vector2 resizing_from; - Vector2 resizing_from_size; - - Rect2 close_rect; - - Vector<int> cache_y; - struct PortCache { - Vector2 position; - int height; - - int slot_idx; + Vector2 pos; + int slot_index; int type = 0; Color color; }; - Vector<PortCache> left_port_cache; - Vector<PortCache> right_port_cache; + struct _MinSizeCache { + int min_size; + bool will_stretch; + int final_size; + }; - HashMap<int, Slot> slot_info; + HBoxContainer *titlebar_hbox = nullptr; + Label *title_label = nullptr; - bool connpos_dirty = true; + String title; - void _connpos_update(); - void _resort(); - void _shape(); + Vector<PortCache> left_port_cache; + Vector<PortCache> right_port_cache; - Vector2 drag_from; - bool selected = false; + HashMap<int, Slot> slot_table; - Overlay overlay = OVERLAY_DISABLED; + Vector<int> slot_y_cache; -#ifdef TOOLS_ENABLED - void _edit_set_position(const Point2 &p_position) override; -#endif + bool port_pos_dirty = true; + + void _port_pos_update(); protected: - virtual void gui_input(const Ref<InputEvent> &p_ev) override; void _notification(int p_what); static void _bind_methods(); + virtual void _resort() override; + + virtual void draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color); + GDVIRTUAL4(_draw_port, int, Point2i, bool, const Color &); + bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - void _validate_property(PropertyInfo &p_property) const; public: - void set_slot(int p_idx, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true); - void clear_slot(int p_idx); - void clear_all_slots(); - - bool is_slot_enabled_left(int p_idx) const; - void set_slot_enabled_left(int p_idx, bool p_enable_left); - - void set_slot_type_left(int p_idx, int p_type_left); - int get_slot_type_left(int p_idx) const; - - void set_slot_color_left(int p_idx, const Color &p_color_left); - Color get_slot_color_left(int p_idx) const; - - bool is_slot_enabled_right(int p_idx) const; - void set_slot_enabled_right(int p_idx, bool p_enable_right); - - void set_slot_type_right(int p_idx, int p_type_right); - int get_slot_type_right(int p_idx) const; - - void set_slot_color_right(int p_idx, const Color &p_color_right); - Color get_slot_color_right(int p_idx) const; - - bool is_slot_draw_stylebox(int p_idx) const; - void set_slot_draw_stylebox(int p_idx, bool p_enable); - void set_title(const String &p_title); String get_title() const; - void set_text_direction(TextDirection p_text_direction); - TextDirection get_text_direction() const; - - void set_language(const String &p_language); - String get_language() const; + HBoxContainer *get_titlebar_hbox(); - void set_position_offset(const Vector2 &p_offset); - Vector2 get_position_offset() const; + void set_slot(int p_slot_index, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left = Ref<Texture2D>(), const Ref<Texture2D> &p_custom_right = Ref<Texture2D>(), bool p_draw_stylebox = true); + void clear_slot(int p_slot_index); + void clear_all_slots(); - void set_selected(bool p_selected); - bool is_selected(); + bool is_slot_enabled_left(int p_slot_index) const; + void set_slot_enabled_left(int p_slot_index, bool p_enable); - void set_drag(bool p_drag); - Vector2 get_drag_from(); + void set_slot_type_left(int p_slot_index, int p_type); + int get_slot_type_left(int p_slot_index) const; - void set_show_close_button(bool p_enable); - bool is_close_button_visible() const; + void set_slot_color_left(int p_slot_index, const Color &p_color); + Color get_slot_color_left(int p_slot_index) const; - int get_connection_input_count(); - int get_connection_input_height(int p_port); - Vector2 get_connection_input_position(int p_port); - int get_connection_input_type(int p_port); - Color get_connection_input_color(int p_port); - int get_connection_input_slot(int p_port); + bool is_slot_enabled_right(int p_slot_index) const; + void set_slot_enabled_right(int p_slot_index, bool p_enable); - int get_connection_output_count(); - int get_connection_output_height(int p_port); - Vector2 get_connection_output_position(int p_port); - int get_connection_output_type(int p_port); - Color get_connection_output_color(int p_port); - int get_connection_output_slot(int p_port); + void set_slot_type_right(int p_slot_index, int p_type); + int get_slot_type_right(int p_slot_index) const; - void set_overlay(Overlay p_overlay); - Overlay get_overlay() const; + void set_slot_color_right(int p_slot_index, const Color &p_color); + Color get_slot_color_right(int p_slot_index) const; - void set_resizable(bool p_enable); - bool is_resizable() const; + bool is_slot_draw_stylebox(int p_slot_index) const; + void set_slot_draw_stylebox(int p_slot_index, bool p_enable); - void set_draggable(bool p_draggable); - bool is_draggable(); + int get_input_port_count(); + Vector2 get_input_port_position(int p_port_idx); + int get_input_port_type(int p_port_idx); + Color get_input_port_color(int p_port_idx); + int get_input_port_slot(int p_port_idx); - void set_selectable(bool p_selectable); - bool is_selectable(); + int get_output_port_count(); + Vector2 get_output_port_position(int p_port_idx); + int get_output_port_type(int p_port_idx); + Color get_output_port_color(int p_port_idx); + int get_output_port_slot(int p_port_idx); virtual Size2 get_minimum_size() const override; @@ -203,13 +144,7 @@ public: virtual Vector<int> get_allowed_size_flags_horizontal() const override; virtual Vector<int> get_allowed_size_flags_vertical() const override; - bool is_resizing() const { - return resizing; - } - GraphNode(); }; -VARIANT_ENUM_CAST(GraphNode::Overlay) - #endif // GRAPH_NODE_H diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 5a29e5ff37..19c4b62eaf 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -439,6 +439,7 @@ void register_scene_types() { GDREGISTER_CLASS(HSplitContainer); GDREGISTER_CLASS(VSplitContainer); + GDREGISTER_CLASS(GraphElement); GDREGISTER_CLASS(GraphNode); GDREGISTER_CLASS(GraphEdit); diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp index 7f1c322c8f..99f9418027 100644 --- a/scene/resources/visual_shader.cpp +++ b/scene/resources/visual_shader.cpp @@ -330,6 +330,14 @@ void VisualShaderNode::set_disabled(bool p_disabled) { disabled = p_disabled; } +bool VisualShaderNode::is_closable() const { + return closable; +} + +void VisualShaderNode::set_closable(bool p_closable) { + closable = p_closable; +} + Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const { return Vector<VisualShader::DefaultTextureParam>(); } diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h index d3657eae07..1d23b80839 100644 --- a/scene/resources/visual_shader.h +++ b/scene/resources/visual_shader.h @@ -265,6 +265,7 @@ class VisualShaderNode : public Resource { protected: bool simple_decl = true; bool disabled = false; + bool closable = false; static void _bind_methods(); @@ -331,6 +332,9 @@ public: bool is_disabled() const; void set_disabled(bool p_disabled = true); + bool is_closable() const; + void set_closable(bool p_closable = true); + virtual Vector<StringName> get_editable_properties() const; virtual HashMap<StringName, String> get_editable_properties_names() const; diff --git a/scene/theme/default_theme.cpp b/scene/theme/default_theme.cpp index eef46a6798..3597eb3f78 100644 --- a/scene/theme/default_theme.cpp +++ b/scene/theme/default_theme.cpp @@ -699,40 +699,43 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_constant("icon_max_width", "PopupMenu", 0); // GraphNode - Ref<StyleBoxFlat> graphnode_normal = make_flat_stylebox(style_normal_color, 18, 42, 18, 12); - graphnode_normal->set_border_width(SIDE_TOP, 30); + + Ref<StyleBoxFlat> graphnode_normal = make_flat_stylebox(style_normal_color, 18, 12, 18, 12); graphnode_normal->set_border_color(Color(0.325, 0.325, 0.325, 0.6)); Ref<StyleBoxFlat> graphnode_selected = graphnode_normal->duplicate(); graphnode_selected->set_border_color(Color(0.625, 0.625, 0.625, 0.6)); - Ref<StyleBoxFlat> graphnode_comment_normal = make_flat_stylebox(style_pressed_color, 18, 42, 18, 12, 3, true, 2); - graphnode_comment_normal->set_border_color(style_pressed_color); - Ref<StyleBoxFlat> graphnode_comment_selected = graphnode_comment_normal->duplicate(); - graphnode_comment_selected->set_border_color(style_hover_color); - Ref<StyleBoxFlat> graphnode_breakpoint = make_flat_stylebox(style_pressed_color, 18, 42, 18, 12, 6, true, 4); - graphnode_breakpoint->set_border_color(Color(0.9, 0.29, 0.3)); - Ref<StyleBoxFlat> graphnode_position = make_flat_stylebox(style_pressed_color, 18, 42, 18, 12, 6, true, 4); - graphnode_position->set_border_color(Color(0.98, 0.89, 0.27)); + + Ref<StyleBoxFlat> graphn_sb_titlebar = make_flat_stylebox(style_normal_color.lightened(0.3), 4, 4, 4, 4); + Ref<StyleBoxFlat> graphn_sb_titlebar_selected = graphnode_normal->duplicate(); + graphn_sb_titlebar_selected->set_bg_color(Color(1.0, 0.625, 0.625, 0.6)); Ref<StyleBoxEmpty> graphnode_slot = make_empty_stylebox(0, 0, 0, 0); - theme->set_stylebox("frame", "GraphNode", graphnode_normal); - theme->set_stylebox("selected_frame", "GraphNode", graphnode_selected); - theme->set_stylebox("breakpoint", "GraphNode", graphnode_breakpoint); - theme->set_stylebox("position", "GraphNode", graphnode_position); + theme->set_stylebox("panel", "GraphNode", graphnode_normal); + theme->set_stylebox("panel_selected", "GraphNode", graphnode_selected); + theme->set_stylebox("titlebar", "GraphNode", graphn_sb_titlebar); + theme->set_stylebox("titlebar_selected", "GraphNode", graphn_sb_titlebar_selected); theme->set_stylebox("slot", "GraphNode", graphnode_slot); - theme->set_icon("port", "GraphNode", icons["graph_port"]); - theme->set_icon("close", "GraphNode", icons["close"]); theme->set_icon("resizer", "GraphNode", icons["resizer_se"]); - theme->set_font("title_font", "GraphNode", Ref<Font>()); - theme->set_color("title_color", "GraphNode", control_font_color); - theme->set_color("close_color", "GraphNode", control_font_color); theme->set_color("resizer_color", "GraphNode", control_font_color); theme->set_constant("separation", "GraphNode", Math::round(2 * scale)); - theme->set_constant("title_offset", "GraphNode", Math::round(26 * scale)); - theme->set_constant("title_h_offset", "GraphNode", 0); - theme->set_constant("close_offset", "GraphNode", Math::round(22 * scale)); - theme->set_constant("close_h_offset", "GraphNode", Math::round(12 * scale)); - theme->set_constant("port_offset", "GraphNode", 0); + theme->set_constant("port_h_offset", "GraphNode", 0); + + // GraphNodes's title Label. + + theme->set_type_variation("GraphNodeTitleLabel", "Label"); + + theme->set_stylebox("normal", "GraphNodeTitleLabel", make_empty_stylebox(0, 0, 0, 0)); + theme->set_font("font", "GraphNodeTitleLabel", Ref<Font>()); + theme->set_font_size("font_size", "GraphNodeTitleLabel", -1); + theme->set_color("font_color", "GraphNodeTitleLabel", control_font_color); + theme->set_color("font_shadow_color", "GraphNodeTitleLabel", Color(0, 0, 0, 0)); + theme->set_color("font_outline_color", "GraphNodeTitleLabel", control_font_color); + theme->set_constant("shadow_offset_x", "GraphNodeTitleLabel", Math::round(1 * scale)); + theme->set_constant("shadow_offset_y", "GraphNodeTitleLabel", Math::round(1 * scale)); + theme->set_constant("outline_size", "GraphNodeTitleLabel", 0); + theme->set_constant("shadow_outline_size", "GraphNodeTitleLabel", Math::round(1 * scale)); + theme->set_constant("line_spacing", "GraphNodeTitleLabel", Math::round(3 * scale)); // Tree |