From 999ce610a2f57ed29be8dd7dde69c24590251029 Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Fri, 6 Nov 2020 22:16:45 +0300 Subject: Add a minimap to the GraphEdit --- scene/gui/graph_edit.cpp | 359 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 352 insertions(+), 7 deletions(-) (limited to 'scene/gui/graph_edit.cpp') diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 96810e8707..84e2231919 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -31,6 +31,7 @@ #include "graph_edit.h" #include "core/input/input.h" +#include "core/math/math_funcs.h" #include "core/os/keyboard.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" @@ -44,6 +45,9 @@ #define MIN_ZOOM (((1 / ZOOM_SCALE) / ZOOM_SCALE) / ZOOM_SCALE) #define MAX_ZOOM (1 * ZOOM_SCALE * ZOOM_SCALE * ZOOM_SCALE) +#define MINIMAP_OFFSET 12 +#define MINIMAP_PADDING 5 + bool GraphEditFilter::has_point(const Point2 &p_point) const { return ge->_filter_input(p_point); } @@ -52,6 +56,141 @@ GraphEditFilter::GraphEditFilter(GraphEdit *p_edit) { ge = p_edit; } +void GraphEditMinimap::_bind_methods() { + ClassDB::bind_method(D_METHOD("_gui_input"), &GraphEditMinimap::_gui_input); +} + +GraphEditMinimap::GraphEditMinimap(GraphEdit *p_edit) { + ge = p_edit; + + graph_proportions = Vector2(1, 1); + graph_padding = Vector2(0, 0); + camera_position = Vector2(100, 50); + camera_size = Vector2(200, 200); + minimap_padding = Vector2(MINIMAP_PADDING, MINIMAP_PADDING); + minimap_offset = minimap_padding + _convert_from_graph_position(graph_padding); + + is_pressing = false; + is_resizing = false; +} + +void GraphEditMinimap::update_minimap() { + Vector2 graph_offset = _get_graph_offset(); + Vector2 graph_size = _get_graph_size(); + + camera_position = ge->get_scroll_ofs() - graph_offset; + camera_size = ge->get_size(); + + Vector2 render_size = _get_render_size(); + float target_ratio = render_size.x / render_size.y; + float graph_ratio = graph_size.x / graph_size.y; + + graph_proportions = graph_size; + graph_padding = Vector2(0, 0); + if (graph_ratio > target_ratio) { + graph_proportions.x = graph_size.x; + graph_proportions.y = graph_size.x / target_ratio; + graph_padding.y = Math::abs(graph_size.y - graph_proportions.y) / 2; + } else { + graph_proportions.x = graph_size.y * target_ratio; + graph_proportions.y = graph_size.y; + graph_padding.x = Math::abs(graph_size.x - graph_proportions.x) / 2; + } + + // This centers minimap inside the minimap rectangle. + minimap_offset = minimap_padding + _convert_from_graph_position(graph_padding); +} + +Rect2 GraphEditMinimap::get_camera_rect() { + Vector2 camera_center = _convert_from_graph_position(camera_position + camera_size / 2) + minimap_offset; + Vector2 camera_viewport = _convert_from_graph_position(camera_size); + Vector2 camera_position = (camera_center - camera_viewport / 2); + return Rect2(camera_position, camera_viewport); +} + +Vector2 GraphEditMinimap::_get_render_size() { + if (!is_inside_tree()) { + return Vector2(0, 0); + } + + return get_size() - 2 * minimap_padding; +} + +Vector2 GraphEditMinimap::_get_graph_offset() { + return Vector2(ge->h_scroll->get_min(), ge->v_scroll->get_min()); +} + +Vector2 GraphEditMinimap::_get_graph_size() { + Vector2 graph_size = Vector2(ge->h_scroll->get_max(), ge->v_scroll->get_max()) - Vector2(ge->h_scroll->get_min(), ge->v_scroll->get_min()); + + if (graph_size.x == 0) { + graph_size.x = 1; + } + if (graph_size.y == 0) { + graph_size.y = 1; + } + + return graph_size; +} + +Vector2 GraphEditMinimap::_convert_from_graph_position(const Vector2 &p_position) { + Vector2 map_position = Vector2(0, 0); + Vector2 render_size = _get_render_size(); + + map_position.x = p_position.x * render_size.x / graph_proportions.x; + map_position.y = p_position.y * render_size.y / graph_proportions.y; + + return map_position; +} + +Vector2 GraphEditMinimap::_convert_to_graph_position(const Vector2 &p_position) { + Vector2 graph_position = Vector2(0, 0); + Vector2 render_size = _get_render_size(); + + graph_position.x = p_position.x * graph_proportions.x / render_size.x; + graph_position.y = p_position.y * graph_proportions.y / render_size.y; + + return graph_position; +} + +void GraphEditMinimap::_gui_input(const Ref &p_ev) { + Ref mb = p_ev; + Ref mm = p_ev; + + if (mb.is_valid() && mb->get_button_index() == BUTTON_LEFT) { + if (mb->is_pressed()) { + is_pressing = true; + + Ref resizer = get_theme_icon("resizer"); + Rect2 resizer_hitbox = Rect2(Point2(), resizer->get_size()); + if (resizer_hitbox.has_point(mb->get_position())) { + is_resizing = true; + } else { + Vector2 click_position = _convert_to_graph_position(mb->get_position() - minimap_padding) - graph_padding; + _adjust_graph_scroll(click_position); + } + } else { + is_pressing = false; + is_resizing = false; + } + accept_event(); + } else if (mm.is_valid() && is_pressing) { + if (is_resizing) { + ge->set_minimap_size(ge->get_minimap_size() - mm->get_relative()); + update(); + } else { + Vector2 click_position = _convert_to_graph_position(mm->get_position() - minimap_padding) - graph_padding; + _adjust_graph_scroll(click_position); + } + accept_event(); + } +} + +void GraphEditMinimap::_adjust_graph_scroll(const Vector2 &p_offset) { + Vector2 graph_offset = _get_graph_offset(); + ge->set_scroll_ofs(p_offset + graph_offset - camera_size / 2); +} + Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const StringName &p_to, int p_to_port) { if (is_node_connected(p_from, p_from_port, p_to, p_to_port)) { return OK; @@ -64,6 +203,7 @@ Error GraphEdit::connect_node(const StringName &p_from, int p_from_port, const S c.activity = 0; connections.push_back(c); top_layer->update(); + minimap->update(); update(); connections_layer->update(); @@ -85,6 +225,7 @@ void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const if (E->get().from == p_from && E->get().from_port == p_from_port && E->get().to == p_to && E->get().to_port == p_to_port) { connections.erase(E); top_layer->update(); + minimap->update(); update(); connections_layer->update(); return; @@ -118,6 +259,7 @@ void GraphEdit::_scroll_moved(double) { awaiting_scroll_offset_update = true; } top_layer->update(); + minimap->update(); update(); if (!setting_scroll_ofs) { //in godot, signals on change value are avoided as a convention @@ -234,6 +376,7 @@ void GraphEdit::_graph_node_moved(Node *p_gn) { GraphNode *gn = Object::cast_to(p_gn); ERR_FAIL_COND(!gn); top_layer->update(); + minimap->update(); update(); connections_layer->update(); } @@ -241,7 +384,8 @@ void GraphEdit::_graph_node_moved(Node *p_gn) { void GraphEdit::add_child_notify(Node *p_child) { Control::add_child_notify(p_child); - top_layer->call_deferred("raise"); //top layer always on top! + top_layer->call_deferred("raise"); // Top layer always on top! + GraphNode *gn = Object::cast_to(p_child); if (gn) { gn->set_scale(Vector2(zoom, zoom)); @@ -255,9 +399,11 @@ void GraphEdit::add_child_notify(Node *p_child) { void GraphEdit::remove_child_notify(Node *p_child) { Control::remove_child_notify(p_child); + if (is_inside_tree()) { - top_layer->call_deferred("raise"); //top layer always on top! + top_layer->call_deferred("raise"); // Top layer always on top! } + GraphNode *gn = Object::cast_to(p_child); if (gn) { gn->disconnect("offset_changed", callable_mp(this, &GraphEdit::_graph_node_moved)); @@ -275,6 +421,7 @@ void GraphEdit::_notification(int p_what) { zoom_reset->set_icon(get_theme_icon("reset")); zoom_plus->set_icon(get_theme_icon("more")); snap_button->set_icon(get_theme_icon("snap")); + minimap_button->set_icon(get_theme_icon("minimap")); } if (p_what == NOTIFICATION_READY) { Size2 hmin = h_scroll->get_combined_minimum_size(); @@ -338,6 +485,7 @@ void GraphEdit::_notification(int p_what) { if (p_what == NOTIFICATION_RESIZED) { _update_scroll(); top_layer->update(); + minimap->update(); } } @@ -472,6 +620,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { connecting_to = mm->get_position(); connecting_target = false; top_layer->update(); + minimap->update(); connecting_valid = just_disconnected || click_pos.distance_to(connecting_to) > 20.0 * zoom; if (connecting_valid) { @@ -541,6 +690,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { connecting = false; top_layer->update(); + minimap->update(); update(); connections_layer->update(); } @@ -632,12 +782,12 @@ void GraphEdit::_bake_segment2d(Vector &points, Vector &colors, } } -void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color) { +void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color, float p_bezier_ratio) { //cubic bezier code float diff = p_to.x - p_from.x; float cp_offset; - int cp_len = get_theme_constant("bezier_len_pos"); - int cp_neg_len = get_theme_constant("bezier_len_neg"); + int cp_len = get_theme_constant("bezier_len_pos") * p_bezier_ratio; + int cp_neg_len = get_theme_constant("bezier_len_neg") * p_bezier_ratio; if (diff > 0) { cp_offset = MIN(cp_len, diff * 0.5); @@ -708,7 +858,7 @@ void GraphEdit::_connections_layer_draw() { color = color.lerp(activity_color, E->get().activity); tocolor = tocolor.lerp(activity_color, E->get().activity); } - _draw_cos_line(connections_layer, frompos, topos, color, tocolor); + _draw_cos_line(connections_layer, frompos, topos, color, tocolor, 1.0); } while (to_erase.size()) { @@ -747,7 +897,7 @@ void GraphEdit::_top_layer_draw() { if (!connecting_out) { SWAP(pos, topos); } - _draw_cos_line(top_layer, pos, topos, col, col); + _draw_cos_line(top_layer, pos, topos, col, col, 1.0); } if (box_selecting) { @@ -756,6 +906,114 @@ void GraphEdit::_top_layer_draw() { } } +void GraphEdit::_minimap_draw() { + if (!is_minimap_enabled()) { + return; + } + + minimap->update_minimap(); + + // Draw the minimap background. + Rect2 minimap_rect = Rect2(Point2(), minimap->get_size()); + minimap->draw_style_box(minimap->get_theme_stylebox("bg"), minimap_rect); + + Vector2 graph_offset = minimap->_get_graph_offset(); + Vector2 minimap_offset = minimap->minimap_offset; + + // Draw comment graph nodes. + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphNode *gn = Object::cast_to(get_child(i)); + if (!gn || !gn->is_comment()) { + continue; + } + + Vector2 node_position = minimap->_convert_from_graph_position(gn->get_offset() * zoom - graph_offset) + minimap_offset; + Vector2 node_size = minimap->_convert_from_graph_position(gn->get_size() * zoom); + Rect2 node_rect = Rect2(node_position, node_size); + + Ref sb_minimap = minimap->get_theme_stylebox("node")->duplicate(); + + // Override default values with colors provided by the GraphNode's stylebox, if possible. + Ref sbf = gn->get_theme_stylebox(gn->is_selected() ? "commentfocus" : "comment"); + if (sbf.is_valid()) { + Color node_color = sbf->get_bg_color(); + sb_minimap->set_bg_color(node_color); + } + + minimap->draw_style_box(sb_minimap, node_rect); + } + + // Draw regular graph nodes. + for (int i = get_child_count() - 1; i >= 0; i--) { + GraphNode *gn = Object::cast_to(get_child(i)); + if (!gn || gn->is_comment()) { + continue; + } + + Vector2 node_position = minimap->_convert_from_graph_position(gn->get_offset() * zoom - graph_offset) + minimap_offset; + Vector2 node_size = minimap->_convert_from_graph_position(gn->get_size() * zoom); + Rect2 node_rect = Rect2(node_position, node_size); + + Ref sb_minimap = minimap->get_theme_stylebox("node")->duplicate(); + + // Override default values with colors provided by the GraphNode's stylebox, if possible. + Ref sbf = gn->get_theme_stylebox(gn->is_selected() ? "selectedframe" : "frame"); + if (sbf.is_valid()) { + Color node_color = sbf->get_border_color(); + sb_minimap->set_bg_color(node_color); + } + + minimap->draw_style_box(sb_minimap, node_rect); + } + + // Draw node connections. + Color activity_color = get_theme_color("activity"); + for (List::Element *E = connections.front(); E; E = E->next()) { + NodePath fromnp(E->get().from); + + Node *from = get_node(fromnp); + if (!from) { + continue; + } + GraphNode *gfrom = Object::cast_to(from); + if (!gfrom) { + continue; + } + + NodePath tonp(E->get().to); + Node *to = get_node(tonp); + if (!to) { + continue; + } + GraphNode *gto = Object::cast_to(to); + if (!gto) { + continue; + } + + Vector2 from_slot_position = gfrom->get_offset() + gfrom->get_connection_output_position(E->get().from_port); + Vector2 from_position = minimap->_convert_from_graph_position(from_slot_position * zoom - graph_offset) + minimap_offset; + Color from_color = gfrom->get_connection_output_color(E->get().from_port); + Vector2 to_slot_position = gto->get_offset() + gto->get_connection_input_position(E->get().to_port); + Vector2 to_position = minimap->_convert_from_graph_position(to_slot_position * zoom - graph_offset) + minimap_offset; + Color to_color = gto->get_connection_input_color(E->get().to_port); + + if (E->get().activity > 0) { + from_color = from_color.lerp(activity_color, E->get().activity); + to_color = to_color.lerp(activity_color, E->get().activity); + } + _draw_cos_line(minimap, from_position, to_position, from_color, to_color, 0.5); + } + + // Draw the "camera" viewport. + Rect2 camera_rect = minimap->get_camera_rect(); + minimap->draw_style_box(minimap->get_theme_stylebox("camera"), camera_rect); + + // Draw the resizer control. + Ref resizer = minimap->get_theme_icon("resizer"); + Color resizer_color = minimap->get_theme_color("resizer_color"); + minimap->draw_texture(resizer, Point2(), resizer_color); +} + void GraphEdit::set_selected(Node *p_child) { for (int i = get_child_count() - 1; i >= 0; i--) { GraphNode *gn = Object::cast_to(get_child(i)); @@ -836,6 +1094,7 @@ void GraphEdit::_gui_input(const Ref &p_ev) { } top_layer->update(); + minimap->update(); } Ref b = p_ev; @@ -858,10 +1117,12 @@ void GraphEdit::_gui_input(const Ref &p_ev) { gn->set_selected(select); } top_layer->update(); + minimap->update(); } else { if (connecting) { connecting = false; top_layer->update(); + minimap->update(); } else { emit_signal("popup_request", b->get_global_position()); } @@ -902,6 +1163,7 @@ void GraphEdit::_gui_input(const Ref &p_ev) { dragging = false; top_layer->update(); + minimap->update(); update(); connections_layer->update(); } @@ -1012,6 +1274,7 @@ void GraphEdit::_gui_input(const Ref &p_ev) { box_selecting = false; previus_selected.clear(); top_layer->update(); + minimap->update(); } if (b->get_button_index() == BUTTON_WHEEL_UP && b->is_pressed()) { @@ -1079,6 +1342,7 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por if (Math::is_equal_approx(E->get().activity, p_activity)) { //update only if changed top_layer->update(); + minimap->update(); connections_layer->update(); } E->get().activity = p_activity; @@ -1089,6 +1353,7 @@ void GraphEdit::set_connection_activity(const StringName &p_from, int p_from_por void GraphEdit::clear_connections() { connections.clear(); + minimap->update(); update(); connections_layer->update(); } @@ -1112,6 +1377,7 @@ void GraphEdit::set_zoom_custom(float p_zoom, const Vector2 &p_center) { top_layer->update(); _update_scroll(); + minimap->update(); connections_layer->update(); if (is_visible_in_tree()) { @@ -1229,6 +1495,47 @@ void GraphEdit::_snap_value_changed(double) { update(); } +// _minimap_toggled + +void GraphEdit::set_minimap_size(Vector2 p_size) { + minimap->set_size(p_size); + Vector2 minimap_size = minimap->get_size(); // The size might've been adjusted by the minimum size. + + minimap->set_anchors_preset(Control::PRESET_BOTTOM_RIGHT); + minimap->set_margin(Margin::MARGIN_LEFT, -minimap_size.x - MINIMAP_OFFSET); + minimap->set_margin(Margin::MARGIN_TOP, -minimap_size.y - MINIMAP_OFFSET); + minimap->set_margin(Margin::MARGIN_RIGHT, -MINIMAP_OFFSET); + minimap->set_margin(Margin::MARGIN_BOTTOM, -MINIMAP_OFFSET); + minimap->update(); +} + +Vector2 GraphEdit::get_minimap_size() const { + return minimap->get_size(); +} + +void GraphEdit::set_minimap_opacity(float p_opacity) { + minimap->set_modulate(Color(1, 1, 1, p_opacity)); + minimap->update(); +} + +float GraphEdit::get_minimap_opacity() const { + Color minimap_modulate = minimap->get_modulate(); + return minimap_modulate.a; +} + +void GraphEdit::set_minimap_enabled(bool p_enable) { + minimap_button->set_pressed(p_enable); + minimap->update(); +} + +bool GraphEdit::is_minimap_enabled() const { + return minimap_button->is_pressed(); +} + +void GraphEdit::_minimap_toggled() { + minimap->update(); +} + HBoxContainer *GraphEdit::get_zoom_hbox() { return zoom_hb; } @@ -1260,6 +1567,14 @@ void GraphEdit::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_snap", "enable"), &GraphEdit::set_use_snap); ClassDB::bind_method(D_METHOD("is_using_snap"), &GraphEdit::is_using_snap); + ClassDB::bind_method(D_METHOD("set_minimap_size", "p_size"), &GraphEdit::set_minimap_size); + ClassDB::bind_method(D_METHOD("get_minimap_size"), &GraphEdit::get_minimap_size); + ClassDB::bind_method(D_METHOD("set_minimap_opacity", "p_opacity"), &GraphEdit::set_minimap_opacity); + ClassDB::bind_method(D_METHOD("get_minimap_opacity"), &GraphEdit::get_minimap_opacity); + + ClassDB::bind_method(D_METHOD("set_minimap_enabled", "enable"), &GraphEdit::set_minimap_enabled); + ClassDB::bind_method(D_METHOD("is_minimap_enabled"), &GraphEdit::is_minimap_enabled); + ClassDB::bind_method(D_METHOD("set_right_disconnects", "enable"), &GraphEdit::set_right_disconnects); ClassDB::bind_method(D_METHOD("is_right_disconnects_enabled"), &GraphEdit::is_right_disconnects_enabled); @@ -1275,6 +1590,10 @@ void GraphEdit::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::INT, "snap_distance"), "set_snap", "get_snap"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_snap"), "set_use_snap", "is_using_snap"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "zoom"), "set_zoom", "get_zoom"); + ADD_GROUP("Minimap", "minimap"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "minimap_enabled"), "set_minimap_enabled", "is_minimap_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "minimap_size"), "set_minimap_size", "get_minimap_size"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity"); ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING_NAME, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING_NAME, "to"), PropertyInfo(Variant::INT, "to_slot"))); @@ -1380,6 +1699,32 @@ GraphEdit::GraphEdit() { snap_amount->connect("value_changed", callable_mp(this, &GraphEdit::_snap_value_changed)); zoom_hb->add_child(snap_amount); + minimap_button = memnew(Button); + minimap_button->set_flat(true); + minimap_button->set_toggle_mode(true); + minimap_button->set_tooltip(RTR("Enable grid minimap.")); + minimap_button->connect("pressed", callable_mp(this, &GraphEdit::_minimap_toggled)); + minimap_button->set_pressed(true); + minimap_button->set_focus_mode(FOCUS_NONE); + zoom_hb->add_child(minimap_button); + + Vector2 minimap_size = Vector2(240, 160); + float minimap_opacity = 0.45; + + minimap = memnew(GraphEditMinimap(this)); + top_layer->add_child(minimap); + minimap->set_name("_minimap"); + minimap->set_modulate(Color(1, 1, 1, minimap_opacity)); + minimap->set_mouse_filter(MOUSE_FILTER_STOP); + minimap->set_custom_minimum_size(Vector2(50, 50)); + minimap->set_size(minimap_size); + minimap->set_anchors_preset(Control::PRESET_BOTTOM_RIGHT); + minimap->set_margin(Margin::MARGIN_LEFT, -minimap_size.x - MINIMAP_OFFSET); + minimap->set_margin(Margin::MARGIN_TOP, -minimap_size.y - MINIMAP_OFFSET); + minimap->set_margin(Margin::MARGIN_RIGHT, -MINIMAP_OFFSET); + minimap->set_margin(Margin::MARGIN_BOTTOM, -MINIMAP_OFFSET); + minimap->connect("draw", callable_mp(this, &GraphEdit::_minimap_draw)); + setting_scroll_ofs = false; just_disconnected = false; set_clip_contents(true); -- cgit v1.2.3