Merge pull request #79308 from Geometror/refactor-graphedit

Clean up/refactor GraphEdit
This commit is contained in:
Yuri Sizov 2023-07-24 19:32:33 +02:00
commit fb2c3ae8f7
12 changed files with 1274 additions and 1059 deletions

View file

@ -157,7 +157,7 @@
Returns an Array containing the list of connections. A connection consists in a structure of the form [code]{ from_port: 0, from: "GraphNode name 0", to_port: 1, to: "GraphNode name 1" }[/code].
</description>
</method>
<method name="get_zoom_hbox">
<method name="get_menu_hbox">
<return type="HBoxContainer" />
<description>
Gets the [HBoxContainer] that contains the zooming and grid snap controls in the top left of the graph. You can use this method to reposition the toolbar or to add your own custom controls to it.
@ -255,16 +255,19 @@
<member name="right_disconnects" type="bool" setter="set_right_disconnects" getter="is_right_disconnects_enabled" default="false">
If [code]true[/code], enables disconnection of existing connections in the GraphEdit by dragging the right end.
</member>
<member name="scroll_offset" type="Vector2" setter="set_scroll_ofs" getter="get_scroll_ofs" default="Vector2(0, 0)">
<member name="scroll_offset" type="Vector2" setter="set_scroll_offset" getter="get_scroll_offset" default="Vector2(0, 0)">
The scroll offset.
</member>
<member name="show_grid" type="bool" setter="set_show_grid" getter="is_showing_grid" default="true">
If [code]true[/code], the grid is visible.
</member>
<member name="show_zoom_label" type="bool" setter="set_show_zoom_label" getter="is_showing_zoom_label" default="false">
If [code]true[/code], makes a label with the current zoom level visible. The zoom value is displayed in percents.
</member>
<member name="snap_distance" type="int" setter="set_snap" getter="get_snap" default="20">
The snapping distance in pixels.
<member name="snapping_distance" type="int" setter="set_snapping_distance" getter="get_snapping_distance" default="20">
The snapping distance in pixels, also determines the grid line distance.
</member>
<member name="use_snap" type="bool" setter="set_use_snap" getter="is_using_snap" default="true">
<member name="snapping_enabled" type="bool" setter="set_snapping_enabled" getter="is_snapping_enabled" default="true">
If [code]true[/code], enables snapping.
</member>
<member name="zoom" type="float" setter="set_zoom" getter="get_zoom" default="1.0">
@ -412,23 +415,28 @@
<theme_item name="port_hotzone_outer_extent" data_type="constant" type="int" default="26">
The horizontal range within which a port can be grabbed (outer side).
</theme_item>
<theme_item name="grid_toggle" data_type="icon" type="Texture2D">
The icon for the grid toggle button.
</theme_item>
<theme_item name="layout" data_type="icon" type="Texture2D">
The icon for the layout button for auto-arranging the graph.
</theme_item>
<theme_item name="minimap" data_type="icon" type="Texture2D">
<theme_item name="minimap_toggle" data_type="icon" type="Texture2D">
The icon for the minimap toggle button.
</theme_item>
<theme_item name="minus" data_type="icon" type="Texture2D">
The icon for the zoom out button.
<theme_item name="snapping_toggle" data_type="icon" type="Texture2D">
The icon for the snapping toggle button.
</theme_item>
<theme_item name="more" data_type="icon" type="Texture2D">
<theme_item name="zoom_in" data_type="icon" type="Texture2D">
The icon for the zoom in button.
</theme_item>
<theme_item name="reset" data_type="icon" type="Texture2D">
<theme_item name="zoom_out" data_type="icon" type="Texture2D">
The icon for the zoom out button.
</theme_item>
<theme_item name="zoom_reset" data_type="icon" type="Texture2D">
The icon for the zoom reset button.
</theme_item>
<theme_item name="snap" data_type="icon" type="Texture2D">
The icon for the snap toggle button.
</theme_item>
<theme_item name="bg" data_type="style" type="StyleBox">
<theme_item name="panel" data_type="style" type="StyleBox">
The background drawn under the grid.
</theme_item>
</theme_items>

View file

@ -1793,7 +1793,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_constant("outline_size", "ProgressBar", 0);
// GraphEdit
theme->set_stylebox("bg", "GraphEdit", style_tree_bg);
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));
@ -1804,18 +1804,20 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_color("selection_fill", "GraphEdit", theme->get_color(SNAME("box_selection_fill_color"), SNAME("Editor")));
theme->set_color("selection_stroke", "GraphEdit", theme->get_color(SNAME("box_selection_stroke_color"), SNAME("Editor")));
theme->set_color("activity", "GraphEdit", accent_color);
theme->set_icon("minus", "GraphEdit", theme->get_icon(SNAME("ZoomLess"), SNAME("EditorIcons")));
theme->set_icon("more", "GraphEdit", theme->get_icon(SNAME("ZoomMore"), SNAME("EditorIcons")));
theme->set_icon("reset", "GraphEdit", theme->get_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
theme->set_icon("snap", "GraphEdit", theme->get_icon(SNAME("SnapGrid"), SNAME("EditorIcons")));
theme->set_icon("minimap", "GraphEdit", theme->get_icon(SNAME("GridMinimap"), SNAME("EditorIcons")));
theme->set_icon("zoom_out", "GraphEdit", theme->get_icon(SNAME("ZoomLess"), SNAME("EditorIcons")));
theme->set_icon("zoom_in", "GraphEdit", theme->get_icon(SNAME("ZoomMore"), SNAME("EditorIcons")));
theme->set_icon("zoom_reset", "GraphEdit", theme->get_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
theme->set_icon("grid_toggle", "GraphEdit", theme->get_icon(SNAME("GridToggle"), SNAME("EditorIcons")));
theme->set_icon("minimap_toggle", "GraphEdit", theme->get_icon(SNAME("GridMinimap"), SNAME("EditorIcons")));
theme->set_icon("snapping_toggle", "GraphEdit", theme->get_icon(SNAME("SnapGrid"), SNAME("EditorIcons")));
theme->set_icon("layout", "GraphEdit", theme->get_icon(SNAME("GridLayout"), SNAME("EditorIcons")));
// GraphEditMinimap
Ref<StyleBoxFlat> style_minimap_bg = make_flat_stylebox(dark_color_1, 0, 0, 0, 0);
style_minimap_bg->set_border_color(dark_color_3);
style_minimap_bg->set_border_width_all(1);
theme->set_stylebox("bg", "GraphEditMinimap", style_minimap_bg);
theme->set_stylebox("panel", "GraphEditMinimap", style_minimap_bg);
Ref<StyleBoxFlat> style_minimap_camera;
Ref<StyleBoxFlat> style_minimap_node;

View file

@ -0,0 +1 @@
<svg viewBox="0 0 16 16" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M2 0v2H0v1h2v4H0v1h2v4H0v1h2v2h1v-2h3.125A5.5 5 0 016 12H3V8h4v1.143a5.5 5 0 011-.99V8h.223A5.5 5 0 0111.5 7H8V3h4v4h-.5a5.5 5 0 013.297 1H15V7h-2V3h2V2h-2V0h-1v2H8V0H7v2H3V0H2zm1 3h4v4H3V3zm4 11.857V15h.117A5.5 5 0 017 14.857z" fill="#e0e0e0"/><path d="M11.485 8.261c-1.648 0-3.734 1.256-4.485 3.68a.645.645 0 00-.004.367C7.721 14.846 9.873 16 11.486 16c1.612 0 3.764-1.154 4.489-3.692a.645.645 0 000-.356c-.71-2.443-2.842-3.691-4.49-3.691zm0 1.29a2.58 2.58 0 012.58 2.58 2.58 2.58 0 01-2.58 2.58 2.58 2.58 0 01-2.58-2.58 2.58 2.58 0 012.58-2.58zm0 1.29a1.29 1.29 0 00-1.29 1.29 1.29 1.29 0 001.29 1.29 1.29 1.29 0 001.29-1.29 1.29 1.29 0 00-1.29-1.29z" fill="#e0e0e0" /></svg>

After

Width:  |  Height:  |  Size: 771 B

View file

@ -126,7 +126,7 @@ void AnimationNodeBlendTreeEditor::update_graph() {
visible_properties.clear();
graph->set_scroll_ofs(blend_tree->get_graph_offset() * EDSCALE);
graph->set_scroll_offset(blend_tree->get_graph_offset() * EDSCALE);
graph->clear_connections();
//erase all nodes
@ -348,7 +348,7 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) {
return;
}
Point2 instance_pos = graph->get_scroll_ofs();
Point2 instance_pos = graph->get_scroll_offset();
if (use_position_from_popup_menu) {
instance_pos += position_from_popup_menu;
} else {
@ -1092,13 +1092,13 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() {
graph->set_connection_lines_curvature(graph_lines_curvature);
VSeparator *vs = memnew(VSeparator);
graph->get_zoom_hbox()->add_child(vs);
graph->get_zoom_hbox()->move_child(vs, 0);
graph->get_menu_hbox()->add_child(vs);
graph->get_menu_hbox()->move_child(vs, 0);
add_node = memnew(MenuButton);
graph->get_zoom_hbox()->add_child(add_node);
graph->get_menu_hbox()->add_child(add_node);
add_node->set_text(TTR("Add Node..."));
graph->get_zoom_hbox()->move_child(add_node, 0);
graph->get_menu_hbox()->move_child(add_node, 0);
add_node->get_popup()->connect("id_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_add_node));
add_node->get_popup()->connect("popup_hide", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_hide), CONNECT_DEFERRED);
add_node->connect("about_to_popup", callable_mp(this, &AnimationNodeBlendTreeEditor::_update_options_menu).bind(false));

View file

@ -1154,7 +1154,7 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) {
graph_plugin->register_shader(visual_shader.ptr());
visual_shader->connect_changed(callable_mp(this, &VisualShaderEditor::_update_preview));
visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE);
visual_shader->set_graph_offset(graph->get_scroll_offset() / EDSCALE);
_set_mode(visual_shader->get_mode());
_update_nodes();
@ -2019,7 +2019,7 @@ void VisualShaderEditor::_update_graph() {
return;
}
graph->set_scroll_ofs(visual_shader->get_graph_offset() * EDSCALE);
graph->set_scroll_offset(visual_shader->get_graph_offset() * EDSCALE);
VisualShader::Type type = get_current_shader_type();
@ -3090,7 +3090,7 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, Stri
bool is_curve_xyz = (Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()) != nullptr);
bool is_parameter = (Object::cast_to<VisualShaderNodeParameter>(vsnode.ptr()) != nullptr);
Point2 position = graph->get_scroll_ofs();
Point2 position = graph->get_scroll_offset();
if (saved_node_pos_dirty) {
position += saved_node_pos;
@ -4387,7 +4387,7 @@ void VisualShaderEditor::_paste_nodes(bool p_use_custom_position, const Vector2
mpos = graph->get_local_mouse_position();
}
_dup_paste_nodes(type, copy_items_buffer, copy_connections_buffer, graph->get_scroll_ofs() / scale + mpos / scale - selection_center, false);
_dup_paste_nodes(type, copy_items_buffer, copy_connections_buffer, graph->get_scroll_offset() / scale + mpos / scale - selection_center, false);
}
void VisualShaderEditor::_mode_selected(int p_id) {
@ -5090,7 +5090,7 @@ VisualShaderEditor::VisualShaderEditor() {
FileSystemDock::get_singleton()->connect("resource_removed", callable_mp(this, &VisualShaderEditor::_resource_removed));
graph = memnew(GraphEdit);
graph->get_zoom_hbox()->set_h_size_flags(SIZE_EXPAND_FILL);
graph->get_menu_hbox()->set_h_size_flags(SIZE_EXPAND_FILL);
graph->set_v_size_flags(SIZE_EXPAND_FILL);
graph->set_h_size_flags(SIZE_EXPAND_FILL);
graph->set_show_zoom_label(true);
@ -5183,8 +5183,8 @@ VisualShaderEditor::VisualShaderEditor() {
graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SAMPLER, VisualShaderNode::PORT_TYPE_SAMPLER);
VSeparator *vs = memnew(VSeparator);
graph->get_zoom_hbox()->add_child(vs);
graph->get_zoom_hbox()->move_child(vs, 0);
graph->get_menu_hbox()->add_child(vs);
graph->get_menu_hbox()->move_child(vs, 0);
custom_mode_box = memnew(CheckBox);
custom_mode_box->set_text(TTR("Custom"));
@ -5218,28 +5218,28 @@ VisualShaderEditor::VisualShaderEditor() {
edit_type = edit_type_standard;
graph->get_zoom_hbox()->add_child(custom_mode_box);
graph->get_zoom_hbox()->move_child(custom_mode_box, 0);
graph->get_zoom_hbox()->add_child(edit_type_standard);
graph->get_zoom_hbox()->move_child(edit_type_standard, 0);
graph->get_zoom_hbox()->add_child(edit_type_particles);
graph->get_zoom_hbox()->move_child(edit_type_particles, 0);
graph->get_zoom_hbox()->add_child(edit_type_sky);
graph->get_zoom_hbox()->move_child(edit_type_sky, 0);
graph->get_zoom_hbox()->add_child(edit_type_fog);
graph->get_zoom_hbox()->move_child(edit_type_fog, 0);
graph->get_menu_hbox()->add_child(custom_mode_box);
graph->get_menu_hbox()->move_child(custom_mode_box, 0);
graph->get_menu_hbox()->add_child(edit_type_standard);
graph->get_menu_hbox()->move_child(edit_type_standard, 0);
graph->get_menu_hbox()->add_child(edit_type_particles);
graph->get_menu_hbox()->move_child(edit_type_particles, 0);
graph->get_menu_hbox()->add_child(edit_type_sky);
graph->get_menu_hbox()->move_child(edit_type_sky, 0);
graph->get_menu_hbox()->add_child(edit_type_fog);
graph->get_menu_hbox()->move_child(edit_type_fog, 0);
add_node = memnew(Button);
add_node->set_flat(true);
add_node->set_text(TTR("Add Node..."));
graph->get_zoom_hbox()->add_child(add_node);
graph->get_zoom_hbox()->move_child(add_node, 0);
graph->get_menu_hbox()->add_child(add_node);
graph->get_menu_hbox()->move_child(add_node, 0);
add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog).bind(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX));
varying_button = memnew(MenuButton);
varying_button->set_text(TTR("Manage Varyings"));
varying_button->set_switch_on_hover(true);
graph->get_zoom_hbox()->add_child(varying_button);
graph->get_menu_hbox()->add_child(varying_button);
PopupMenu *varying_menu = varying_button->get_popup();
varying_menu->add_item(TTR("Add Varying"), int(VaryingMenuOptions::ADD));
@ -5250,7 +5250,7 @@ VisualShaderEditor::VisualShaderEditor() {
preview_shader->set_flat(true);
preview_shader->set_toggle_mode(true);
preview_shader->set_tooltip_text(TTR("Show generated shader code."));
graph->get_zoom_hbox()->add_child(preview_shader);
graph->get_menu_hbox()->add_child(preview_shader);
preview_shader->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_preview_text));
///////////////////////////////////////

File diff suppressed because it is too large Load diff

View file

@ -39,6 +39,7 @@
#include "scene/gui/spin_box.h"
class GraphEdit;
class GraphEditArranger;
class ViewPanner;
class GraphEditFilter : public Control {
@ -47,6 +48,7 @@ class GraphEditFilter : public Control {
friend class GraphEdit;
friend class GraphEditMinimap;
GraphEdit *ge = nullptr;
virtual bool has_point(const Point2 &p_point) const override;
public:
@ -58,9 +60,9 @@ class GraphEditMinimap : public Control {
friend class GraphEdit;
friend class GraphEditFilter;
GraphEdit *ge = nullptr;
protected:
public:
GraphEditMinimap(GraphEdit *p_edit);
@ -97,8 +99,8 @@ class GraphEdit : public Control {
public:
struct Connection {
StringName from;
StringName to;
StringName from_node;
StringName to_node;
int from_port = 0;
int to_port = 0;
float activity = 0.0;
@ -111,33 +113,57 @@ public:
};
private:
struct ConnectionType {
union {
struct {
uint32_t type_a;
uint32_t type_b;
};
uint64_t key = 0;
};
static uint32_t hash(const ConnectionType &p_conn) {
return hash_one_uint64(p_conn.key);
}
bool operator==(const ConnectionType &p_type) const {
return key == p_type.key;
}
ConnectionType(uint32_t a = 0, uint32_t b = 0) {
type_a = a;
type_b = b;
}
};
Label *zoom_label = nullptr;
Button *zoom_minus = nullptr;
Button *zoom_reset = nullptr;
Button *zoom_plus = nullptr;
Button *snap_button = nullptr;
SpinBox *snap_amount = nullptr;
Button *zoom_minus_button = nullptr;
Button *zoom_reset_button = nullptr;
Button *zoom_plus_button = nullptr;
Button *toggle_snapping_button = nullptr;
SpinBox *snapping_distance_spinbox = nullptr;
Button *show_grid_button = nullptr;
Button *minimap_button = nullptr;
Button *layout_button = nullptr;
HScrollBar *h_scroll = nullptr;
VScrollBar *v_scroll = nullptr;
HScrollBar *h_scrollbar = nullptr;
VScrollBar *v_scrollbar = nullptr;
float port_hotzone_inner_extent = 0.0;
float port_hotzone_outer_extent = 0.0;
Ref<ViewPanner> panner;
bool warped_panning = true;
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
bool arrange_nodes_button_hidden = false;
bool snapping_enabled = true;
int snapping_distance = 20;
bool show_grid = true;
bool connecting = false;
StringName connecting_from;
String connecting_from;
bool connecting_out = false;
int connecting_index = 0;
int connecting_type = 0;
@ -146,8 +172,10 @@ private:
Vector2 connecting_to;
StringName connecting_target_to;
int connecting_target_index = 0;
bool just_disconnected = false;
bool connecting_valid = false;
Vector2 click_pos;
PanningScheme panning_scheme = SCROLL_ZOOMS;
@ -162,19 +190,14 @@ private:
float zoom_min = 0.0;
float zoom_max = 0.0;
void _zoom_minus();
void _zoom_reset();
void _zoom_plus();
void _update_zoom_label();
bool box_selecting = false;
bool box_selection_mode_additive = false;
Point2 box_selecting_from;
Point2 box_selecting_to;
Rect2 box_selecting_rect;
List<GraphNode *> previous_selected;
List<GraphNode *> prev_selected;
bool setting_scroll_ofs = false;
bool setting_scroll_offset = false;
bool right_disconnects = false;
bool updating = false;
bool awaiting_scroll_offset_update = false;
@ -184,97 +207,69 @@ private:
float lines_curvature = 0.5f;
bool lines_antialiased = true;
HBoxContainer *menu_hbox = nullptr;
Control *connections_layer = nullptr;
GraphEditFilter *top_layer = nullptr;
GraphEditMinimap *minimap = nullptr;
Ref<GraphEditArranger> arranger;
HashSet<ConnectionType, ConnectionType> valid_connection_types;
HashSet<int> valid_left_disconnect_types;
HashSet<int> valid_right_disconnect_types;
void _scroll_callback(Vector2 p_scroll_vec, bool p_alt);
void _pan_callback(Vector2 p_scroll_vec, Ref<InputEvent> p_event);
void _zoom_callback(float p_zoom_factor, Vector2 p_origin, Ref<InputEvent> p_event);
void _zoom_minus();
void _zoom_reset();
void _zoom_plus();
void _update_zoom_label();
PackedVector2Array get_connection_line(const Vector2 &p_from, const Vector2 &p_to);
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_raised(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 _update_scroll();
void _update_scroll_offset();
void _scroll_moved(double);
virtual void gui_input(const Ref<InputEvent> &p_ev) override;
Control *connections_layer = nullptr;
GraphEditFilter *top_layer = nullptr;
GraphEditMinimap *minimap = nullptr;
void _top_layer_input(const Ref<InputEvent> &p_ev);
bool is_in_input_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
bool is_in_output_hotzone(GraphNode *p_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
bool is_in_port_hotzone(const Vector2 &pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left);
bool is_in_input_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
bool is_in_output_hotzone(GraphNode *p_graph_node, int p_port, const Vector2 &p_mouse_pos, const Vector2i &p_port_size);
bool is_in_port_hotzone(const Vector2 &p_pos, const Vector2 &p_mouse_pos, const Vector2i &p_port_size, bool p_left);
void _top_layer_draw();
void _connections_layer_draw();
void _minimap_draw();
void _update_scroll_offset();
TypedArray<Dictionary> _get_connection_list() const;
bool lines_on_bg = false;
struct ConnType {
union {
struct {
uint32_t type_a;
uint32_t type_b;
};
uint64_t key = 0;
};
static uint32_t hash(const ConnType &p_conn) {
return hash_one_uint64(p_conn.key);
}
bool operator==(const ConnType &p_type) const {
return key == p_type.key;
}
ConnType(uint32_t a = 0, uint32_t b = 0) {
type_a = a;
type_b = b;
}
};
HashSet<ConnType, ConnType> valid_connection_types;
HashSet<int> valid_left_disconnect_types;
HashSet<int> valid_right_disconnect_types;
HBoxContainer *zoom_hb = nullptr;
friend class GraphEditFilter;
bool _filter_input(const Point2 &p_point);
void _snap_toggled();
void _snap_value_changed(double);
void _snapping_toggled();
void _snapping_distance_changed(double);
void _show_grid_toggled();
friend class GraphEditMinimap;
void _minimap_toggled();
bool _check_clickable_control(Control *p_control, const Vector2 &r_mouse_pos, const Vector2 &p_offset);
bool arranging_graph = false;
enum SET_OPERATIONS {
IS_EQUAL,
IS_SUBSET,
DIFFERENCE,
UNION,
};
int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v);
HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings);
void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes);
void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions);
void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions);
protected:
static void _bind_methods();
virtual void add_child_notify(Node *p_child) override;
virtual void remove_child_notify(Node *p_child) override;
void _notification(int p_what);
GDVIRTUAL2RC(Vector<Vector2>, _get_connection_line, Vector2, Vector2)
@ -332,6 +327,7 @@ public:
GraphEditFilter *get_top_layer() const { return top_layer; }
GraphEditMinimap *get_minimap() const { return minimap; }
void get_connection_list(List<Connection> *r_connections) const;
void set_right_disconnects(bool p_enable);
@ -343,16 +339,19 @@ public:
void add_valid_left_disconnect_type(int p_type);
void remove_valid_left_disconnect_type(int p_type);
void set_scroll_ofs(const Vector2 &p_ofs);
Vector2 get_scroll_ofs() const;
void set_scroll_offset(const Vector2 &p_ofs);
Vector2 get_scroll_offset() const;
void set_selected(Node *p_child);
void set_use_snap(bool p_enable);
bool is_using_snap() const;
void set_snapping_enabled(bool p_enable);
bool is_snapping_enabled() const;
int get_snap() const;
void set_snap(int p_snap);
void set_snapping_distance(int p_snapping_distance);
int get_snapping_distance() const;
void set_show_grid(bool p_enable);
bool is_showing_grid() const;
void set_connection_lines_curvature(float p_curvature);
float get_connection_lines_curvature() const;
@ -363,7 +362,7 @@ public:
void set_connection_lines_antialiased(bool p_antialiased);
bool is_connection_lines_antialiased() const;
HBoxContainer *get_zoom_hbox();
HBoxContainer *get_menu_hbox();
Ref<ViewPanner> get_panner();
void set_warped_panning(bool p_warped);

View file

@ -0,0 +1,565 @@
/**************************************************************************/
/* graph_edit_arranger.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_edit_arranger.h"
#include "scene/gui/graph_edit.h"
void GraphEditArranger::arrange_nodes() {
ERR_FAIL_COND(!graph_edit);
if (!arranging_graph) {
arranging_graph = true;
} else {
return;
}
Dictionary node_names;
HashSet<StringName> selected_nodes;
bool arrange_entire_graph = true;
for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
if (!graph_element) {
continue;
}
node_names[graph_element->get_name()] = graph_element;
if (graph_element->is_selected()) {
arrange_entire_graph = false;
}
}
HashMap<StringName, HashSet<StringName>> upper_neighbours;
HashMap<StringName, Pair<int, int>> port_info;
Vector2 origin(FLT_MAX, FLT_MAX);
float gap_v = 100.0f;
float gap_h = 100.0f;
for (int i = graph_edit->get_child_count() - 1; i >= 0; i--) {
GraphNode *graph_element = Object::cast_to<GraphNode>(graph_edit->get_child(i));
if (!graph_element) {
continue;
}
if (graph_element->is_selected() || arrange_entire_graph) {
selected_nodes.insert(graph_element->get_name());
HashSet<StringName> s;
List<GraphEdit::Connection> connection_list;
graph_edit->get_connection_list(&connection_list);
for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from_node]);
if (E->get().to_node == graph_element->get_name() && (p_from->is_selected() || arrange_entire_graph) && E->get().to_node != E->get().from_node) {
if (!s.has(p_from->get_name())) {
s.insert(p_from->get_name());
}
String s_connection = String(p_from->get_name()) + " " + String(E->get().to_node);
StringName _connection(s_connection);
Pair<int, int> ports(E->get().from_port, E->get().to_port);
if (port_info.has(_connection)) {
Pair<int, int> p_ports = port_info[_connection];
if (p_ports.first < ports.first) {
ports = p_ports;
}
}
port_info.insert(_connection, ports);
}
}
upper_neighbours.insert(graph_element->get_name(), s);
}
}
if (!selected_nodes.size()) {
arranging_graph = false;
return;
}
HashMap<int, Vector<StringName>> layers = _layering(selected_nodes, upper_neighbours);
_crossing_minimisation(layers, upper_neighbours);
Dictionary root, align, sink, shift;
_horizontal_alignment(root, align, layers, upper_neighbours, selected_nodes);
HashMap<StringName, Vector2> new_positions;
Vector2 default_position(FLT_MAX, FLT_MAX);
Dictionary inner_shift;
HashSet<StringName> block_heads;
for (const StringName &E : selected_nodes) {
inner_shift[E] = 0.0f;
sink[E] = E;
shift[E] = FLT_MAX;
new_positions.insert(E, default_position);
if ((StringName)root[E] == E) {
block_heads.insert(E);
}
}
_calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info);
for (const StringName &E : block_heads) {
_place_block(E, gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
}
origin.y = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().y - (new_positions[layers[0][0]].y + (float)inner_shift[layers[0][0]]);
origin.x = Object::cast_to<GraphNode>(node_names[layers[0][0]])->get_position_offset().x;
for (const StringName &E : block_heads) {
StringName u = E;
float start_from = origin.y + new_positions[E].y;
do {
Vector2 cal_pos;
cal_pos.y = start_from + (real_t)inner_shift[u];
new_positions.insert(u, cal_pos);
u = align[u];
} while (u != E);
}
// Compute horizontal coordinates individually for layers to get uniform gap.
float start_from = origin.x;
float largest_node_size = 0.0f;
for (unsigned int i = 0; i < layers.size(); i++) {
Vector<StringName> layer = layers[i];
for (int j = 0; j < layer.size(); j++) {
float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x;
largest_node_size = MAX(largest_node_size, current_node_size);
}
for (int j = 0; j < layer.size(); j++) {
float current_node_size = Object::cast_to<GraphNode>(node_names[layer[j]])->get_size().x;
Vector2 cal_pos = new_positions[layer[j]];
if (current_node_size == largest_node_size) {
cal_pos.x = start_from;
} else {
float current_node_start_pos = start_from;
if (current_node_size < largest_node_size / 2) {
if (!(i || j)) {
start_from -= (largest_node_size - current_node_size);
}
current_node_start_pos = start_from + largest_node_size - current_node_size;
}
cal_pos.x = current_node_start_pos;
}
new_positions.insert(layer[j], cal_pos);
}
start_from += largest_node_size + gap_h;
largest_node_size = 0.0f;
}
graph_edit->emit_signal(SNAME("begin_node_move"));
for (const StringName &E : selected_nodes) {
GraphNode *graph_node = Object::cast_to<GraphNode>(node_names[E]);
graph_node->set_drag(true);
Vector2 pos = (new_positions[E]);
if (graph_edit->is_snapping_enabled()) {
float snapping_distance = graph_edit->get_snapping_distance();
pos = pos.snapped(Vector2(snapping_distance, snapping_distance));
}
graph_node->set_position_offset(pos);
graph_node->set_drag(false);
}
graph_edit->emit_signal(SNAME("end_node_move"));
arranging_graph = false;
}
int GraphEditArranger::_set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v) {
switch (p_operation) {
case GraphEditArranger::IS_EQUAL: {
for (const StringName &E : r_u) {
if (!r_v.has(E)) {
return 0;
}
}
return r_u.size() == r_v.size();
} break;
case GraphEditArranger::IS_SUBSET: {
if (r_u.size() == r_v.size() && !r_u.size()) {
return 1;
}
for (const StringName &E : r_u) {
if (!r_v.has(E)) {
return 0;
}
}
return 1;
} break;
case GraphEditArranger::DIFFERENCE: {
for (HashSet<StringName>::Iterator E = r_u.begin(); E;) {
HashSet<StringName>::Iterator N = E;
++N;
if (r_v.has(*E)) {
r_u.remove(E);
}
E = N;
}
return r_u.size();
} break;
case GraphEditArranger::UNION: {
for (const StringName &E : r_v) {
if (!r_u.has(E)) {
r_u.insert(E);
}
}
return r_u.size();
} break;
default:
break;
}
return -1;
}
HashMap<int, Vector<StringName>> GraphEditArranger::_layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
HashMap<int, Vector<StringName>> l;
HashSet<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
int current_layer = 0;
bool selected = false;
while (!_set_operations(GraphEditArranger::IS_EQUAL, q, u)) {
_set_operations(GraphEditArranger::DIFFERENCE, p, u);
for (const StringName &E : p) {
HashSet<StringName> n = r_upper_neighbours[E];
if (_set_operations(GraphEditArranger::IS_SUBSET, n, z)) {
Vector<StringName> t;
t.push_back(E);
if (!l.has(current_layer)) {
l.insert(current_layer, Vector<StringName>{});
}
selected = true;
t.append_array(l[current_layer]);
l.insert(current_layer, t);
HashSet<StringName> V;
V.insert(E);
_set_operations(GraphEditArranger::UNION, u, V);
}
}
if (!selected) {
current_layer++;
uint32_t previous_size_z = z.size();
_set_operations(GraphEditArranger::UNION, z, u);
if (z.size() == previous_size_z) {
WARN_PRINT("Graph contains cycle(s). The cycle(s) will not be rearranged accurately.");
Vector<StringName> t;
if (l.has(0)) {
t.append_array(l[0]);
}
for (const StringName &E : p) {
t.push_back(E);
}
l.insert(0, t);
break;
}
}
selected = false;
}
return l;
}
Vector<StringName> GraphEditArranger::_split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings) {
if (!r_layer.size()) {
return Vector<StringName>();
}
StringName p = r_layer[Math::random(0, r_layer.size() - 1)];
Vector<StringName> left;
Vector<StringName> right;
for (int i = 0; i < r_layer.size(); i++) {
if (p != r_layer[i]) {
StringName q = r_layer[i];
int cross_pq = r_crossings[p][q];
int cross_qp = r_crossings[q][p];
if (cross_pq > cross_qp) {
left.push_back(q);
} else {
right.push_back(q);
}
}
}
left.push_back(p);
left.append_array(right);
return left;
}
void GraphEditArranger::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes) {
for (const StringName &E : r_selected_nodes) {
r_root[E] = E;
r_align[E] = E;
}
if (r_layers.size() == 1) {
return;
}
for (unsigned int i = 1; i < r_layers.size(); i++) {
Vector<StringName> lower_layer = r_layers[i];
Vector<StringName> upper_layer = r_layers[i - 1];
int r = -1;
for (int j = 0; j < lower_layer.size(); j++) {
Vector<Pair<int, StringName>> up;
StringName current_node = lower_layer[j];
for (int k = 0; k < upper_layer.size(); k++) {
StringName adjacent_neighbour = upper_layer[k];
if (r_upper_neighbours[current_node].has(adjacent_neighbour)) {
up.push_back(Pair<int, StringName>(k, adjacent_neighbour));
}
}
int start = (up.size() - 1) / 2;
int end = (up.size() - 1) % 2 ? start + 1 : start;
for (int p = start; p <= end; p++) {
StringName Align = r_align[current_node];
if (Align == current_node && r < up[p].first) {
r_align[up[p].second] = lower_layer[j];
r_root[current_node] = r_root[up[p].second];
r_align[current_node] = r_root[up[p].second];
r = up[p].first;
}
}
}
}
}
void GraphEditArranger::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours) {
if (r_layers.size() == 1) {
return;
}
for (unsigned int i = 1; i < r_layers.size(); i++) {
Vector<StringName> upper_layer = r_layers[i - 1];
Vector<StringName> lower_layer = r_layers[i];
HashMap<StringName, Dictionary> c;
for (int j = 0; j < lower_layer.size(); j++) {
StringName p = lower_layer[j];
Dictionary d;
for (int k = 0; k < lower_layer.size(); k++) {
unsigned int crossings = 0;
StringName q = lower_layer[k];
if (j != k) {
for (int h = 1; h < upper_layer.size(); h++) {
if (r_upper_neighbours[p].has(upper_layer[h])) {
for (int g = 0; g < h; g++) {
if (r_upper_neighbours[q].has(upper_layer[g])) {
crossings++;
}
}
}
}
}
d[q] = crossings;
}
c.insert(p, d);
}
r_layers.insert(i, _split(lower_layer, c));
}
}
void GraphEditArranger::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
for (const StringName &E : r_block_heads) {
real_t left = 0;
StringName u = E;
StringName v = r_align[u];
while (u != v && (StringName)r_root[u] != v) {
String _connection = String(u) + " " + String(v);
GraphNode *gnode_from = Object::cast_to<GraphNode>(r_node_names[u]);
GraphNode *gnode_to = Object::cast_to<GraphNode>(r_node_names[v]);
Pair<int, int> ports = r_port_info[_connection];
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();
real_t s = (real_t)r_inner_shifts[u] + (pos_from.y - pos_to.y) / graph_edit->get_zoom();
r_inner_shifts[v] = s;
left = MIN(left, s);
u = v;
v = (StringName)r_align[v];
}
u = E;
do {
r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left;
u = (StringName)r_align[u];
} while (u != E);
}
}
float GraphEditArranger::_calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions) {
#define MAX_ORDER 2147483647
#define ORDER(node, layers) \
for (unsigned int i = 0; i < layers.size(); i++) { \
int index = layers[i].find(node); \
if (index > 0) { \
order = index; \
break; \
} \
order = MAX_ORDER; \
}
int order = MAX_ORDER;
float threshold = p_current_threshold;
if (p_v == p_w) {
int min_order = MAX_ORDER;
GraphEdit::Connection incoming;
List<GraphEdit::Connection> connection_list;
graph_edit->get_connection_list(&connection_list);
for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
if (E->get().to_node == p_w) {
ORDER(E->get().from_node, r_layers);
if (min_order > order) {
min_order = order;
incoming = E->get();
}
}
}
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();
// If connected block node is selected, calculate thershold or add current block to list.
if (gnode_from->is_selected()) {
Vector2 connected_block_pos = r_node_positions[r_root[incoming.from_node]];
if (connected_block_pos.y != FLT_MAX) {
//Connected block is placed, calculate threshold.
threshold = connected_block_pos.y + (real_t)r_inner_shift[incoming.from_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y;
}
}
}
}
if (threshold == FLT_MIN && (StringName)r_align[p_w] == p_v) {
// This time, pick an outgoing edge and repeat as above!
int min_order = MAX_ORDER;
GraphEdit::Connection outgoing;
List<GraphEdit::Connection> connection_list;
graph_edit->get_connection_list(&connection_list);
for (List<GraphEdit::Connection>::Element *E = connection_list.front(); E; E = E->next()) {
if (E->get().from_node == p_w) {
ORDER(E->get().to_node, r_layers);
if (min_order > order) {
min_order = order;
outgoing = E->get();
}
}
}
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();
// If connected block node is selected, calculate thershold or add current block to list.
if (gnode_to->is_selected()) {
Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to_node]];
if (connected_block_pos.y != FLT_MAX) {
//Connected block is placed. Calculate threshold
threshold = connected_block_pos.y + (real_t)r_inner_shift[outgoing.to_node] - (real_t)r_inner_shift[p_w] + pos_from.y - pos_to.y;
}
}
}
}
#undef MAX_ORDER
#undef ORDER
return threshold;
}
void GraphEditArranger::_place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions) {
#define PRED(node, layers) \
for (unsigned int i = 0; i < layers.size(); i++) { \
int index = layers[i].find(node); \
if (index > 0) { \
predecessor = layers[i][index - 1]; \
break; \
} \
predecessor = StringName(); \
}
StringName predecessor;
StringName successor;
Vector2 pos = r_node_positions[p_v];
if (pos.y == FLT_MAX) {
pos.y = 0;
bool initial = false;
StringName w = p_v;
real_t threshold = FLT_MIN;
do {
PRED(w, r_layers);
if (predecessor != StringName()) {
StringName u = r_root[predecessor];
_place_block(u, p_delta, r_layers, r_root, r_align, r_node_name, r_inner_shift, r_sink, r_shift, r_node_positions);
threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
if ((StringName)r_sink[p_v] == p_v) {
r_sink[p_v] = r_sink[u];
}
Vector2 predecessor_root_pos = r_node_positions[u];
Vector2 predecessor_node_size = Object::cast_to<GraphNode>(r_node_name[predecessor])->get_size();
if (r_sink[p_v] != r_sink[u]) {
real_t sc = pos.y + (real_t)r_inner_shift[w] - predecessor_root_pos.y - (real_t)r_inner_shift[predecessor] - predecessor_node_size.y - p_delta;
r_shift[r_sink[u]] = MIN(sc, (real_t)r_shift[r_sink[u]]);
} else {
real_t sb = predecessor_root_pos.y + (real_t)r_inner_shift[predecessor] + predecessor_node_size.y - (real_t)r_inner_shift[w] + p_delta;
sb = MAX(sb, threshold);
if (initial) {
pos.y = sb;
} else {
pos.y = MAX(pos.y, sb);
}
initial = false;
}
}
threshold = _calculate_threshold(p_v, w, r_node_name, r_layers, r_root, r_align, r_inner_shift, threshold, r_node_positions);
w = r_align[w];
} while (w != p_v);
r_node_positions.insert(p_v, pos);
}
#undef PRED
}

View file

@ -0,0 +1,67 @@
/**************************************************************************/
/* graph_edit_arranger.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_EDIT_ARRANGER_H
#define GRAPH_EDIT_ARRANGER_H
#include "core/object/ref_counted.h"
#include "core/templates/hash_map.h"
#include "core/templates/hash_set.h"
class GraphEdit;
class GraphEditArranger : public RefCounted {
enum SET_OPERATIONS {
IS_EQUAL,
IS_SUBSET,
DIFFERENCE,
UNION,
};
GraphEdit *graph_edit = nullptr;
bool arranging_graph = false;
int _set_operations(SET_OPERATIONS p_operation, HashSet<StringName> &r_u, const HashSet<StringName> &r_v);
HashMap<int, Vector<StringName>> _layering(const HashSet<StringName> &r_selected_nodes, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
Vector<StringName> _split(const Vector<StringName> &r_layer, const HashMap<StringName, Dictionary> &r_crossings);
void _horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours, const HashSet<StringName> &r_selected_nodes);
void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, HashSet<StringName>> &r_upper_neighbours);
void _calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const HashSet<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info);
float _calculate_threshold(StringName p_v, StringName p_w, const Dictionary &r_node_names, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_inner_shift, real_t p_current_threshold, const HashMap<StringName, Vector2> &r_node_positions);
void _place_block(StringName p_v, float p_delta, const HashMap<int, Vector<StringName>> &r_layers, const Dictionary &r_root, const Dictionary &r_align, const Dictionary &r_node_name, const Dictionary &r_inner_shift, Dictionary &r_sink, Dictionary &r_shift, HashMap<StringName, Vector2> &r_node_positions);
public:
void arrange_nodes();
GraphEditArranger(GraphEdit *p_graph_edit) :
graph_edit(p_graph_edit) {}
};
#endif // GRAPH_EDIT_ARRANGER_H

View file

@ -415,7 +415,7 @@ void GraphNode::_shape() {
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_ofs()) * graph->get_zoom();
Point2 offset = (p_position + graph->get_scroll_offset()) * graph->get_zoom();
set_position_offset(offset);
}
set_position(p_position);

View file

@ -1110,13 +1110,16 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_stylebox("panel", "PanelContainer", make_flat_stylebox(style_normal_color, 0, 0, 0, 0));
theme->set_icon("minus", "GraphEdit", icons["zoom_less"]);
theme->set_icon("reset", "GraphEdit", icons["zoom_reset"]);
theme->set_icon("more", "GraphEdit", icons["zoom_more"]);
theme->set_icon("snap", "GraphEdit", icons["grid_snap"]);
theme->set_icon("minimap", "GraphEdit", icons["grid_minimap"]);
theme->set_icon("zoom_out", "GraphEdit", icons["zoom_less"]);
theme->set_icon("zoom_in", "GraphEdit", icons["zoom_more"]);
theme->set_icon("zoom_reset", "GraphEdit", icons["zoom_reset"]);
theme->set_icon("grid_toggle", "GraphEdit", icons["grid_toggle"]);
theme->set_icon("minimap_toggle", "GraphEdit", icons["grid_minimap"]);
theme->set_icon("snapping_toggle", "GraphEdit", icons["grid_snap"]);
theme->set_icon("layout", "GraphEdit", icons["grid_layout"]);
theme->set_stylebox("bg", "GraphEdit", make_flat_stylebox(style_normal_color, 4, 4, 4, 5));
theme->set_stylebox("panel", "GraphEdit", make_flat_stylebox(style_normal_color, 4, 4, 4, 5));
theme->set_color("grid_minor", "GraphEdit", Color(1, 1, 1, 0.05));
theme->set_color("grid_major", "GraphEdit", Color(1, 1, 1, 0.2));
theme->set_color("selection_fill", "GraphEdit", Color(1, 1, 1, 0.3));
@ -1128,7 +1131,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("port_hotzone_inner_extent", "GraphEdit", 22 * scale);
theme->set_constant("port_hotzone_outer_extent", "GraphEdit", 26 * scale);
theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0));
theme->set_stylebox("panel", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0));
Ref<StyleBoxFlat> style_minimap_camera = make_flat_stylebox(Color(0.65, 0.65, 0.65, 0.2), 0, 0, 0, 0, 0);
style_minimap_camera->set_border_color(Color(0.65, 0.65, 0.65, 0.45));
style_minimap_camera->set_border_width_all(1);

View file

@ -0,0 +1 @@
<svg viewBox="0 0 16 16" height="16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="M3 0v3H0v2h3v4H0v2h3v3h2V5h9V3h-3V0H9v3H5V0z" fill="#b2b2b2" fill-opacity=".65"/><path d="M11 6.62c-1.747 0-3.957 1.344-4.752 3.936a.683.69 0 00-.004.394C7.012 13.665 9.292 14.9 11 14.9c1.708 0 3.988-1.235 4.756-3.95a.683.69 0 000-.382C15.004 7.955 12.746 6.62 11 6.62zM11 8a2.733 2.76 0 012.733 2.76A2.733 2.76 0 0111 13.52a2.733 2.76 0 01-2.733-2.76A2.733 2.76 0 0111 8zm0 1.38a1.367 1.38 0 00-1.367 1.38A1.367 1.38 0 0011 12.14a1.367 1.38 0 001.367-1.38A1.367 1.38 0 0011 9.38z" fill="#e0e0e0" /></svg>

After

Width:  |  Height:  |  Size: 598 B