Merge pull request #49343 from theoway/node_auto_arrangement_graph_edit

Node Auto Arrangement in GraphEdit/VisualScript/VisualShader
This commit is contained in:
K. S. Ernest (iFire) Lee 2021-08-10 15:42:04 -04:00 committed by GitHub
commit 18bd0fee5a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 542 additions and 1 deletions

View file

@ -32,6 +32,12 @@
Makes possible to disconnect nodes when dragging from the slot at the right if it has the specified type.
</description>
</method>
<method name="arrange_nodes">
<return type="void" />
<description>
Rearranges selected nodes in a layout with minimum crossings between connections and uniform horizontal and vertical gap between nodes.
</description>
</method>
<method name="clear_connections">
<return type="void" />
<description>
@ -283,6 +289,8 @@
<theme_item name="grid_minor" data_type="color" type="Color" default="Color(1, 1, 1, 0.05)">
Color of minor grid lines.
</theme_item>
<theme_item name="layout" data_type="icon" type="Texture2D">
</theme_item>
<theme_item name="minimap" data_type="icon" type="Texture2D">
</theme_item>
<theme_item name="minus" data_type="icon" type="Texture2D">

View file

@ -1232,6 +1232,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("reset", "GraphEdit", theme->get_icon("ZoomReset", "EditorIcons"));
theme->set_icon("snap", "GraphEdit", theme->get_icon("SnapGrid", "EditorIcons"));
theme->set_icon("minimap", "GraphEdit", theme->get_icon("GridMinimap", "EditorIcons"));
theme->set_icon("layout", "GraphEdit", theme->get_icon("GridLayout", "EditorIcons"));
theme->set_constant("bezier_len_pos", "GraphEdit", 80 * EDSCALE);
theme->set_constant("bezier_len_neg", "GraphEdit", 160 * EDSCALE);

View file

@ -0,0 +1 @@
<svg height="16" viewBox="0 0 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m14 2.1992188v2.6152343l-2.625 1.3125v-2.6152343zm-12 4.0644531 2.625 1.3125v2.5507811l-2.625-1.3124999zm12 0v2.5507812l-2.625 1.3124999v-2.5507811zm-8 1.4550781h4v2.640625h-4zm-4 2.560547 2.625 1.3125v2.521484l-2.625-1.3125zm12 0v2.521484l-2.625 1.3125v-2.521484zm-8 1.455078h4v2.640625h-4zm1.7014535-8.109375h2.2985465v2.734375h-4.15625s-.7487346.647119-.8746377.640625c-.1310411-.0067594-1.5097373-1.4558594-1.5097373-1.4558594l-1.459375-.7296875v-2.6152343l.068419.034223s.026411-.4573464.062111-.6760553c.0346282-.2121439.1970747-.59225724.1970747-.59225724l-1.0483078-.52372301c-.0795772-.04012218-.1668141-.06276382-.2558594-.06640625-.35427845-.01325803-.64865004.27047362-.6484375.625v12c.00021484.236623.13402736.45284.34570312.558594l3.99999998 2c.086686.043505.1823067.06624.2792969.066406h6c.09699-.000166.192611-.0229.279297-.06641l4-2c.211676-.10575.345488-.321967.345703-.55859v-12c-.000468-.46423753-.488958-.76598317-.904297-.55859375l-3.869141 1.93359375h-2.9709527s.033448.4166167.015891.625c-.029188.3464401-.1950466.625-.1950468.625z" fill="#b05b5b"/><path d="m5 6s-2.21875-2.1616704-2.21875-3.2425057c0-1.0808352 0-2.6072392 2.21875-2.6072392s2.21875 1.526404 2.21875 2.6072392c0 1.0808353-2.21875 3.2425057-2.21875 3.2425057z" fill="#fff" fill-opacity=".68627"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -60,7 +60,7 @@ class VisualScriptEditor : public ScriptEditorBase {
EDIT_CUT_NODES,
EDIT_PASTE_NODES,
EDIT_CREATE_FUNCTION,
REFRESH_GRAPH
REFRESH_GRAPH,
};
enum PortAction {

View file

@ -450,6 +450,7 @@ void GraphEdit::_notification(int p_what) {
zoom_plus->set_icon(get_theme_icon(SNAME("more")));
snap_button->set_icon(get_theme_icon(SNAME("snap")));
minimap_button->set_icon(get_theme_icon(SNAME("minimap")));
layout_button->set_icon(get_theme_icon(SNAME("layout")));
}
if (p_what == NOTIFICATION_READY) {
Size2 hmin = h_scroll->get_combined_minimum_size();
@ -1646,6 +1647,500 @@ HBoxContainer *GraphEdit::get_zoom_hbox() {
return zoom_hb;
}
int GraphEdit::_set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v) {
switch (p_operation) {
case GraphEdit::IS_EQUAL: {
for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
if (!r_v.has(E->get()))
return 0;
}
return r_u.size() == r_v.size();
} break;
case GraphEdit::IS_SUBSET: {
if (r_u.size() == r_v.size() && !r_u.size()) {
return 1;
}
for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
if (!r_v.has(E->get()))
return 0;
}
return 1;
} break;
case GraphEdit::DIFFERENCE: {
for (Set<StringName>::Element *E = r_u.front(); E; E = E->next()) {
if (r_v.has(E->get())) {
r_u.erase(E->get());
}
}
return r_u.size();
} break;
case GraphEdit::UNION: {
for (Set<StringName>::Element *E = r_v.front(); E; E = E->next()) {
if (!r_u.has(E->get())) {
r_u.insert(E->get());
}
}
return r_v.size();
} break;
default:
break;
}
return -1;
}
HashMap<int, Vector<StringName>> GraphEdit::_layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<StringName>> &r_upper_neighbours) {
HashMap<int, Vector<StringName>> l;
Set<StringName> p = r_selected_nodes, q = r_selected_nodes, u, z;
int current_layer = 0;
bool selected = false;
while (!_set_operations(GraphEdit::IS_EQUAL, q, u)) {
_set_operations(GraphEdit::DIFFERENCE, p, u);
for (const Set<StringName>::Element *E = p.front(); E; E = E->next()) {
Set<StringName> n = r_upper_neighbours[E->get()];
if (_set_operations(GraphEdit::IS_SUBSET, n, z)) {
Vector<StringName> t;
t.push_back(E->get());
if (!l.has(current_layer)) {
l.set(current_layer, Vector<StringName>{});
}
selected = true;
t.append_array(l[current_layer]);
l.set(current_layer, t);
Set<StringName> V;
V.insert(E->get());
_set_operations(GraphEdit::UNION, u, V);
}
}
if (!selected) {
current_layer++;
_set_operations(GraphEdit::UNION, z, u);
}
selected = false;
}
return l;
}
Vector<StringName> GraphEdit::_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 GraphEdit::_horizontal_alignment(Dictionary &r_root, Dictionary &r_align, const HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes) {
for (const Set<StringName>::Element *E = r_selected_nodes.front(); E; E = E->next()) {
r_root[E->get()] = E->get();
r_align[E->get()] = E->get();
}
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() / 2;
int end = up.size() % 2 ? start : start + 1;
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 GraphEdit::_crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<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.set(p, d);
}
r_layers.set(i, _split(lower_layer, c));
}
}
void GraphEdit::_calculate_inner_shifts(Dictionary &r_inner_shifts, const Dictionary &r_root, const Dictionary &r_node_names, const Dictionary &r_align, const Set<StringName> &r_block_heads, const HashMap<StringName, Pair<int, int>> &r_port_info) {
for (const Set<StringName>::Element *E = r_block_heads.front(); E; E = E->next()) {
real_t left = 0;
StringName u = E->get();
StringName v = r_align[u];
while (u != v && (StringName)r_root[u] != v) {
String _connection = String(u) + " " + String(v);
GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[u]);
GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[v]);
Pair<int, int> ports = r_port_info[_connection];
int pfrom = ports.first;
int pto = ports.second;
Vector2 frompos = gfrom->get_connection_output_position(pfrom);
Vector2 topos = gto->get_connection_input_position(pto);
real_t s = (real_t)r_inner_shifts[u] + (frompos.y - topos.y) / zoom;
r_inner_shifts[v] = s;
left = MIN(left, s);
u = v;
v = (StringName)r_align[v];
}
u = E->get();
do {
r_inner_shifts[u] = (real_t)r_inner_shifts[u] - left;
u = (StringName)r_align[u];
} while (u != E->get());
}
}
float GraphEdit::_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;
Connection incoming;
for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
if (E->get().to == p_w) {
ORDER(E->get().from, r_layers);
if (min_order > order) {
min_order = order;
incoming = E->get();
}
}
}
if (incoming.from != StringName()) {
GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[incoming.from]);
GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[p_w]);
Vector2 frompos = gfrom->get_connection_output_position(incoming.from_port);
Vector2 topos = gto->get_connection_input_position(incoming.to_port);
//If connected block node is selected, calculate thershold or add current block to list
if (gfrom->is_selected()) {
Vector2 connected_block_pos = r_node_positions[r_root[incoming.from]];
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] - (real_t)r_inner_shift[p_w] + frompos.y - topos.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;
Connection outgoing;
for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
if (E->get().from == p_w) {
ORDER(E->get().to, r_layers);
if (min_order > order) {
min_order = order;
outgoing = E->get();
}
}
}
if (outgoing.to != StringName()) {
GraphNode *gfrom = Object::cast_to<GraphNode>(r_node_names[p_w]);
GraphNode *gto = Object::cast_to<GraphNode>(r_node_names[outgoing.to]);
Vector2 frompos = gfrom->get_connection_output_position(outgoing.from_port);
Vector2 topos = gto->get_connection_input_position(outgoing.to_port);
//If connected block node is selected, calculate thershold or add current block to list
if (gto->is_selected()) {
Vector2 connected_block_pos = r_node_positions[r_root[outgoing.to]];
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] - (real_t)r_inner_shift[p_w] + frompos.y - topos.y;
}
}
}
}
#undef MAX_ORDER
#undef ORDER
return threshold;
}
void GraphEdit::_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.set(p_v, pos);
}
#undef PRED
}
void GraphEdit::arrange_nodes() {
if (!arranging_graph) {
arranging_graph = true;
} else {
return;
}
Dictionary node_names;
Set<StringName> selected_nodes;
for (int i = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (!gn) {
continue;
}
node_names[gn->get_name()] = gn;
}
HashMap<StringName, Set<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 = get_child_count() - 1; i >= 0; i--) {
GraphNode *gn = Object::cast_to<GraphNode>(get_child(i));
if (!gn) {
continue;
}
if (gn->is_selected()) {
selected_nodes.insert(gn->get_name());
origin = origin < gn->get_position_offset() ? origin : gn->get_position_offset();
Set<StringName> s;
for (List<Connection>::Element *E = connections.front(); E; E = E->next()) {
GraphNode *p_from = Object::cast_to<GraphNode>(node_names[E->get().from]);
if (E->get().to == gn->get_name() && p_from->is_selected()) {
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);
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.set(_connection, ports);
}
}
upper_neighbours.set(gn->get_name(), s);
}
}
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;
Set<StringName> block_heads;
for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) {
inner_shift[E->get()] = 0.0f;
sink[E->get()] = E->get();
shift[E->get()] = FLT_MAX;
new_positions.set(E->get(), default_position);
if ((StringName)root[E->get()] == E->get()) {
block_heads.insert(E->get());
}
}
_calculate_inner_shifts(inner_shift, root, node_names, align, block_heads, port_info);
for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) {
_place_block(E->get(), gap_v, layers, root, align, node_names, inner_shift, sink, shift, new_positions);
}
for (const Set<StringName>::Element *E = block_heads.front(); E; E = E->next()) {
StringName u = E->get();
StringName prev = u;
float start_from = origin.y + new_positions[E->get()].y;
do {
Vector2 cal_pos;
cal_pos.y = start_from + (real_t)inner_shift[u];
new_positions.set(u, cal_pos);
prev = u;
u = align[u];
} while (u != E->get());
}
//Compute horizontal co-ordinates 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;
if (current_node_size >= largest_node_size / 2) {
current_node_start_pos = start_from;
} else {
current_node_start_pos = start_from + largest_node_size - current_node_size;
}
cal_pos.x = current_node_start_pos;
}
new_positions.set(layer[j], cal_pos);
}
start_from += largest_node_size + gap_h;
largest_node_size = 0.0f;
}
emit_signal("begin_node_move");
for (const Set<StringName>::Element *E = selected_nodes.front(); E; E = E->next()) {
GraphNode *gn = Object::cast_to<GraphNode>(node_names[E->get()]);
gn->set_drag(true);
Vector2 pos = (new_positions[E->get()]);
if (is_using_snap()) {
const int snap = get_snap();
pos = pos.snapped(Vector2(snap, snap));
}
gn->set_position_offset(pos);
gn->set_drag(false);
}
emit_signal("end_node_move");
arranging_graph = false;
}
void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("connect_node", "from", "from_port", "to", "to_port"), &GraphEdit::connect_node);
ClassDB::bind_method(D_METHOD("is_node_connected", "from", "from_port", "to", "to_port"), &GraphEdit::is_node_connected);
@ -1707,6 +2202,8 @@ void GraphEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_zoom_hbox"), &GraphEdit::get_zoom_hbox);
ClassDB::bind_method(D_METHOD("arrange_nodes"), &GraphEdit::arrange_nodes);
ClassDB::bind_method(D_METHOD("set_selected", "node"), &GraphEdit::set_selected);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "right_disconnects"), "set_right_disconnects", "is_right_disconnects_enabled");
@ -1851,6 +2348,13 @@ GraphEdit::GraphEdit() {
minimap_button->set_focus_mode(FOCUS_NONE);
zoom_hb->add_child(minimap_button);
layout_button = memnew(Button);
layout_button->set_flat(true);
zoom_hb->add_child(layout_button);
layout_button->set_tooltip(RTR("Arrange nodes."));
layout_button->connect("pressed", callable_mp(this, &GraphEdit::arrange_nodes));
layout_button->set_focus_mode(FOCUS_NONE);
Vector2 minimap_size = Vector2(240, 160);
float minimap_opacity = 0.65;

View file

@ -116,6 +116,8 @@ private:
Button *minimap_button;
Button *layout_button;
HScrollBar *h_scroll;
VScrollBar *v_scroll;
@ -230,6 +232,24 @@ private:
bool _check_clickable_control(Control *p_control, const Vector2 &pos);
bool arranging_graph = false;
enum SET_OPERATIONS {
IS_EQUAL,
IS_SUBSET,
DIFFERENCE,
UNION,
};
int _set_operations(SET_OPERATIONS p_operation, Set<StringName> &r_u, const Set<StringName> &r_v);
HashMap<int, Vector<StringName>> _layering(const Set<StringName> &r_selected_nodes, const HashMap<StringName, Set<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, Set<StringName>> &r_upper_neighbours, const Set<StringName> &r_selected_nodes);
void _crossing_minimisation(HashMap<int, Vector<StringName>> &r_layers, const HashMap<StringName, Set<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 Set<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;
@ -304,6 +324,8 @@ public:
HBoxContainer *get_zoom_hbox();
void arrange_nodes();
GraphEdit();
};

View file

@ -955,6 +955,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_icon("more", "GraphEdit", make_icon(icon_zoom_more_png));
theme->set_icon("snap", "GraphEdit", make_icon(icon_snap_grid_png));
theme->set_icon("minimap", "GraphEdit", make_icon(icon_grid_minimap_png));
theme->set_icon("layout", "GraphEdit", make_icon(icon_grid_layout_png));
theme->set_stylebox("bg", "GraphEdit", make_stylebox(tree_bg_png, 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));

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

View file

@ -182,6 +182,10 @@ static const unsigned char icon_grid_minimap_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
static const unsigned char icon_grid_layout_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x6, 0x0, 0x0, 0x0, 0x1f, 0xf3, 0xff, 0x61, 0x0, 0x0, 0x0, 0x9, 0x70, 0x48, 0x59, 0x73, 0x0, 0x0, 0xe, 0xc3, 0x0, 0x0, 0xe, 0xc3, 0x1, 0xc7, 0x6f, 0xa8, 0x64, 0x0, 0x0, 0x0, 0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x0, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x6e, 0x6b, 0x73, 0x63, 0x61, 0x70, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x9b, 0xee, 0x3c, 0x1a, 0x0, 0x0, 0x2, 0xd, 0x49, 0x44, 0x41, 0x54, 0x38, 0x8d, 0x75, 0x93, 0x31, 0x68, 0x14, 0x51, 0x10, 0x86, 0xbf, 0xd9, 0xd, 0xbb, 0xde, 0x76, 0x82, 0x21, 0xf8, 0xe0, 0xbc, 0x5d, 0x8b, 0x80, 0x69, 0x6c, 0xd2, 0x5a, 0x6a, 0x91, 0xc3, 0xd2, 0x46, 0x22, 0x8, 0x9, 0x89, 0x70, 0x85, 0x10, 0x41, 0xd, 0x24, 0x45, 0xb0, 0xb, 0x68, 0x11, 0x14, 0x24, 0x10, 0x22, 0x62, 0x21, 0x41, 0xe, 0x4b, 0x21, 0xa4, 0xb7, 0x49, 0x17, 0xb1, 0x8, 0xb9, 0xdd, 0xc7, 0x86, 0x33, 0x21, 0xe1, 0x3a, 0x8f, 0x64, 0x61, 0x6f, 0x2c, 0xbc, 0x3b, 0x36, 0xb9, 0xdc, 0xc0, 0x2b, 0xde, 0xcc, 0xfc, 0xf3, 0xff, 0xfc, 0xcc, 0x48, 0xa3, 0xd1, 0x78, 0x20, 0x22, 0x13, 0xbe, 0xef, 0xaf, 0xdf, 0xac, 0xd7, 0x1f, 0xe1, 0x38, 0xd3, 0xa8, 0x2a, 0xf0, 0x45, 0x6a, 0xb5, 0xcf, 0x5c, 0x11, 0xcd, 0x66, 0x33, 0x38, 0x3f, 0x3f, 0x9f, 0x13, 0x91, 0x7d, 0xb1, 0xd6, 0x6e, 0xaa, 0xea, 0xd3, 0xe0, 0xe8, 0xe8, 0xde, 0xe8, 0xee, 0xee, 0x37, 0xc0, 0xe9, 0xf6, 0x75, 0xf0, 0xfd, 0x9, 0x99, 0x9d, 0x6d, 0x15, 0x81, 0x59, 0x96, 0x3d, 0x3, 0x5e, 0x2, 0x63, 0x22, 0xf2, 0x69, 0xa4, 0x57, 0x1c, 0xdd, 0xdb, 0xfb, 0x5b, 0x0, 0x3, 0x38, 0x67, 0x41, 0x30, 0x11, 0xc7, 0xf1, 0x13, 0x0, 0x11, 0x71, 0xb2, 0x2c, 0x7b, 0xd8, 0xad, 0xad, 0x2, 0x6f, 0xb9, 0x0, 0x38, 0x3c, 0xfc, 0x5, 0x9c, 0xf6, 0xff, 0x22, 0x27, 0x27, 0xe3, 0xe3, 0x7f, 0xa, 0x3, 0x67, 0x45, 0xe4, 0xbb, 0xe7, 0x79, 0xb7, 0xc3, 0x30, 0x7c, 0xd7, 0x67, 0xe9, 0xe3, 0x67, 0x66, 0x5c, 0x60, 0x1, 0x50, 0x40, 0x51, 0x7d, 0x71, 0x6b, 0x72, 0xf2, 0x20, 0x8a, 0xa2, 0xf9, 0x28, 0x8a, 0xe6, 0x1, 0x3a, 0x9d, 0xce, 0x4f, 0x63, 0x4c, 0x3b, 0x4d, 0xd3, 0xd2, 0xc0, 0x80, 0x3c, 0xcf, 0xf, 0x92, 0xa9, 0xa9, 0x31, 0x60, 0x5, 0x58, 0x91, 0x5a, 0xed, 0xc7, 0x15, 0xfe, 0x95, 0xac, 0xb5, 0xcf, 0xf3, 0x3c, 0x3f, 0xe8, 0x25, 0x46, 0xa, 0xc5, 0xd, 0x11, 0x59, 0xb3, 0xd5, 0xea, 0x1b, 0xa0, 0x95, 0x54, 0xab, 0x5b, 0x97, 0xd1, 0x22, 0xb2, 0xa6, 0xaa, 0x6d, 0x60, 0xd, 0x58, 0xba, 0xa0, 0x20, 0xc, 0xc3, 0x65, 0xd7, 0x75, 0x23, 0xe0, 0x2e, 0xb0, 0x1, 0x5c, 0xbf, 0xf4, 0x0, 0xbe, 0xba, 0xae, 0x1b, 0x85, 0x61, 0xb8, 0x3c, 0xa0, 0x20, 0x4d, 0xd3, 0x52, 0xb9, 0x5c, 0x6e, 0xc5, 0x71, 0xbc, 0x23, 0x22, 0xd3, 0x61, 0x18, 0xde, 0x2f, 0xb2, 0x27, 0x49, 0xa2, 0xaa, 0xba, 0x53, 0x2e, 0x97, 0x5b, 0x69, 0x9a, 0x96, 0xf2, 0x3c, 0x1f, 0xf0, 0xc0, 0x5a, 0x6b, 0x5f, 0x1, 0x25, 0x86, 0x84, 0xe3, 0x38, 0x9e, 0xb5, 0x76, 0x2e, 0xcf, 0xf3, 0xfd, 0x1, 0x5, 0x22, 0xb2, 0xa1, 0xaa, 0x4b, 0x22, 0x72, 0xad, 0xcb, 0x38, 0xe0, 0x81, 0xaa, 0x7e, 0x0, 0xce, 0x44, 0xe4, 0xbd, 0xaa, 0xbe, 0xbe, 0xa0, 0xa0, 0x52, 0xa9, 0x2c, 0x7a, 0x9e, 0x17, 0x1, 0x3d, 0xe0, 0x55, 0x1e, 0x6c, 0x79, 0x9e, 0x17, 0x55, 0x2a, 0x95, 0xc5, 0x1, 0x5, 0xcd, 0x66, 0x33, 0x30, 0xc6, 0x9c, 0xc6, 0x71, 0xbc, 0x2d, 0x22, 0x8f, 0x87, 0x78, 0xb0, 0x6d, 0x8c, 0x39, 0xed, 0xae, 0x74, 0xdf, 0x83, 0x3a, 0x70, 0x9c, 0x65, 0x59, 0x23, 0x49, 0x92, 0x5, 0x11, 0x9, 0x86, 0x79, 0x20, 0x22, 0x41, 0x92, 0x24, 0xb, 0x59, 0x96, 0x35, 0x80, 0x63, 0xa0, 0x2e, 0x3d, 0xf6, 0xc2, 0x91, 0xdc, 0x0, 0x5c, 0x55, 0x5d, 0xbf, 0x4, 0x9e, 0x3, 0x72, 0xfe, 0xaf, 0xfb, 0xaa, 0xe7, 0x79, 0x1f, 0x8d, 0x31, 0x6d, 0x29, 0x36, 0xf5, 0xce, 0x14, 0xb8, 0x33, 0x44, 0xc4, 0x6f, 0xdf, 0xf7, 0xd7, 0x8d, 0x31, 0xed, 0x5e, 0xe2, 0x1f, 0xb, 0x5c, 0xe2, 0xcb, 0xd, 0x9b, 0x69, 0xcb, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};
static const unsigned char icon_parent_folder_png[] = {
0x89, 0x50, 0x4e, 0x47, 0xd, 0xa, 0x1a, 0xa, 0x0, 0x0, 0x0, 0xd, 0x49, 0x48, 0x44, 0x52, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x10, 0x8, 0x4, 0x0, 0x0, 0x0, 0xb5, 0xfa, 0x37, 0xea, 0x0, 0x0, 0x0, 0x68, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x33, 0xb8, 0x27, 0xfe, 0xe0, 0xfc, 0x83, 0x73, 0xf7, 0xc4, 0x71, 0x48, 0xdf, 0x11, 0x7b, 0x78, 0xe9, 0xc1, 0x3f, 0x20, 0xbc, 0xfe, 0x40, 0x12, 0x8f, 0x34, 0x4c, 0x9, 0xa6, 0xe1, 0x57, 0x80, 0x12, 0x17, 0x81, 0xf8, 0x2f, 0x58, 0xe1, 0x15, 0x34, 0x8b, 0x1e, 0x9c, 0x5, 0xa, 0x5e, 0xb8, 0x23, 0x6, 0x52, 0x70, 0x5b, 0x14, 0xac, 0xf0, 0xc, 0xaa, 0x82, 0x7d, 0xf, 0x8e, 0xde, 0x14, 0xf9, 0xcf, 0x8, 0x52, 0xc0, 0xc0, 0x70, 0x5b, 0xf4, 0xe1, 0xc9, 0x7, 0x47, 0xb1, 0xb8, 0x3, 0xaa, 0x0, 0xa, 0x48, 0x52, 0x80, 0xb0, 0xea, 0xc8, 0xc3, 0x83, 0xc, 0x83, 0xe, 0x0, 0x0, 0xb8, 0x27, 0x55, 0x4c, 0xbe, 0xc0, 0xd2, 0xac, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};