diff --git a/doc/classes/GraphEdit.xml b/doc/classes/GraphEdit.xml index 14ae0a31b6eb7..c722261e674bb 100644 --- a/doc/classes/GraphEdit.xml +++ b/doc/classes/GraphEdit.xml @@ -175,6 +175,15 @@ + + If [code]true[/code], the minimap is visible. + + + The opacity of the minimap rectangle. + + + The size of the minimap rectangle. The map itself is based on the size of the grid area and is scaled to fit this rectangle. + If [code]true[/code], enables disconnection of existing connections in the GraphEdit by dragging the right end. diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 90ac95f384077..5462f685de4de 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -84,6 +84,25 @@ static Ref make_line_stylebox(Color p_color, int p_thickness = 1, return style; } +static Ref flip_icon(Ref p_texture, bool p_flip_y = false, bool p_flip_x = false) { + if (!p_flip_y && !p_flip_x) { + return p_texture; + } + + Ref texture(memnew(ImageTexture)); + Ref img = p_texture->get_data(); + + if (p_flip_y) { + img->flip_y(); + } + if (p_flip_x) { + img->flip_x(); + } + + texture->create_from_image(img); + return texture; +} + #ifdef SVG_ENABLED static Ref editor_generate_icon(int p_index, bool p_convert_color, float p_scale = EDSCALE, bool p_force_filter = false) { @@ -1052,11 +1071,33 @@ Ref create_editor_theme(const Ref p_theme) { theme->set_icon("more", "GraphEdit", theme->get_icon("ZoomMore", "EditorIcons")); 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_constant("bezier_len_pos", "GraphEdit", 80 * EDSCALE); theme->set_constant("bezier_len_neg", "GraphEdit", 160 * EDSCALE); - // GraphNode + // GraphEditMinimap + theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(dark_color_1, 0, 0, 0, 0)); + Ref style_minimap_camera; + Ref style_minimap_node; + if (dark_theme) { + style_minimap_camera = make_flat_stylebox(Color(0.65, 0.65, 0.65, 0.2), 0, 0, 0, 0); + style_minimap_camera->set_border_color(Color(0.65, 0.65, 0.65, 0.45)); + style_minimap_node = make_flat_stylebox(Color(1, 1, 1), 0, 0, 0, 0); + } else { + style_minimap_camera = make_flat_stylebox(Color(0.38, 0.38, 0.38, 0.2), 0, 0, 0, 0); + style_minimap_camera->set_border_color(Color(0.38, 0.38, 0.38, 0.45)); + style_minimap_node = make_flat_stylebox(Color(0, 0, 0), 0, 0, 0, 0); + } + style_minimap_camera->set_border_width_all(1); + style_minimap_node->set_corner_radius_all(1); + theme->set_stylebox("camera", "GraphEditMinimap", style_minimap_camera); + theme->set_stylebox("node", "GraphEditMinimap", style_minimap_node); + + Ref resizer_icon = theme->get_icon("GuiResizer", "EditorIcons"); + theme->set_icon("resizer", "GraphEditMinimap", flip_icon(resizer_icon, true, true)); + theme->set_color("resizer_color", "GraphEditMinimap", Color(1, 1, 1, 0.65)); + // GraphNode const float mv = dark_theme ? 0.0 : 1.0; const float mv2 = 1.0 - mv; const int gn_margin_side = 28; diff --git a/editor/icons/icon_grid_minimap.svg b/editor/icons/icon_grid_minimap.svg new file mode 100644 index 0000000000000..72f107066daeb --- /dev/null +++ b/editor/icons/icon_grid_minimap.svg @@ -0,0 +1 @@ + diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp index 417da9ad16765..70c1c10cafb41 100644 --- a/scene/gui/graph_edit.cpp +++ b/scene/gui/graph_edit.cpp @@ -30,6 +30,7 @@ #include "graph_edit.h" +#include "core/math/math_funcs.h" #include "core/os/input.h" #include "core/os/keyboard.h" #include "scene/gui/box_container.h" @@ -43,6 +44,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); @@ -53,6 +57,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_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)) @@ -65,6 +204,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(); @@ -90,6 +230,7 @@ void GraphEdit::disconnect_node(const StringName &p_from, int p_from_port, const connections.erase(E); top_layer->update(); + minimap->update(); update(); connections_layer->update(); return; @@ -128,6 +269,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 @@ -245,6 +387,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(); } @@ -253,13 +396,15 @@ 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)); gn->connect("offset_changed", this, "_graph_node_moved", varray(gn)); gn->connect("raise_request", this, "_graph_node_raised", varray(gn)); gn->connect("item_rect_changed", connections_layer, "update"); + gn->connect("item_rect_changed", minimap, "update"); _graph_node_moved(gn); gn->set_mouse_filter(MOUSE_FILTER_PASS); } @@ -268,14 +413,17 @@ 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", this, "_graph_node_moved"); gn->disconnect("raise_request", this, "_graph_node_raised"); gn->disconnect("item_rect_changed", connections_layer, "update"); + gn->disconnect("item_rect_changed", minimap, "update"); } } @@ -289,6 +437,7 @@ void GraphEdit::_notification(int p_what) { zoom_reset->set_icon(get_icon("reset")); zoom_plus->set_icon(get_icon("more")); snap_button->set_icon(get_icon("snap")); + minimap_button->set_icon(get_icon("minimap")); } if (p_what == NOTIFICATION_READY) { Size2 hmin = h_scroll->get_combined_minimum_size(); @@ -353,6 +502,7 @@ void GraphEdit::_notification(int p_what) { if (p_what == NOTIFICATION_RESIZED) { _update_scroll(); top_layer->update(); + minimap->update(); } } @@ -501,6 +651,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { connecting_to = mm->get_position(); connecting_target = false; top_layer->update(); + minimap->update(); Ref port = get_icon("port", "GraphNode"); Vector2 mpos = mm->get_position(); @@ -572,6 +723,7 @@ void GraphEdit::_top_layer_input(const Ref &p_ev) { connecting = false; top_layer->update(); + minimap->update(); update(); connections_layer->update(); } @@ -662,13 +814,13 @@ 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_width = 2.0, float p_bezier_ratio = 1.0) { //cubic bezier code float diff = p_to.x - p_from.x; float cp_offset; - int cp_len = get_constant("bezier_len_pos"); - int cp_neg_len = get_constant("bezier_len_neg"); + int cp_len = get_constant("bezier_len_pos") * p_bezier_ratio; + int cp_neg_len = get_constant("bezier_len_neg") * p_bezier_ratio; if (diff > 0) { cp_offset = MIN(cp_len, diff * 0.5); @@ -690,9 +842,9 @@ void GraphEdit::_draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const colors.push_back(p_to_color); #ifdef TOOLS_ENABLED - p_where->draw_polyline_colors(points, colors, Math::floor(2 * EDSCALE), true); + p_where->draw_polyline_colors(points, colors, Math::floor(p_width * EDSCALE), true); #else - p_where->draw_polyline_colors(points, colors, 2, true); + p_where->draw_polyline_colors(points, colors, p_width, true); #endif } @@ -790,6 +942,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_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_stylebox("node")->duplicate(); + + // Override default values with colors provided by the GraphNode's stylebox, if possible. + Ref sbf = gn->get_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_stylebox("node")->duplicate(); + + // Override default values with colors provided by the GraphNode's stylebox, if possible. + Ref sbf = gn->get_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_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() * zoom + gfrom->get_connection_output_position(E->get().from_port); + Vector2 from_position = minimap->_convert_from_graph_position(from_slot_position - graph_offset) + minimap_offset; + Color from_color = gfrom->get_connection_output_color(E->get().from_port); + Vector2 to_slot_position = gto->get_offset() * zoom + gto->get_connection_input_position(E->get().to_port); + Vector2 to_position = minimap->_convert_from_graph_position(to_slot_position - 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.linear_interpolate(activity_color, E->get().activity); + to_color = to_color.linear_interpolate(activity_color, E->get().activity); + } + _draw_cos_line(minimap, from_position, to_position, from_color, to_color, 1.0, 0.5); + } + + // Draw the "camera" viewport. + Rect2 camera_rect = minimap->get_camera_rect(); + minimap->draw_style_box(minimap->get_stylebox("camera"), camera_rect); + + // Draw the resizer control. + Ref resizer = minimap->get_icon("resizer"); + Color resizer_color = minimap->get_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--) { @@ -873,6 +1133,7 @@ void GraphEdit::_gui_input(const Ref &p_ev) { } top_layer->update(); + minimap->update(); } Ref b = p_ev; @@ -896,10 +1157,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()); } @@ -939,6 +1202,7 @@ void GraphEdit::_gui_input(const Ref &p_ev) { dragging = false; top_layer->update(); + minimap->update(); update(); connections_layer->update(); } @@ -1046,6 +1310,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()) { @@ -1119,6 +1384,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; @@ -1130,6 +1396,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(); } @@ -1154,6 +1421,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()) { @@ -1288,6 +1556,45 @@ void GraphEdit::_snap_value_changed(double) { update(); } +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; } @@ -1320,6 +1627,16 @@ 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("_minimap_toggled"), &GraphEdit::_minimap_toggled); + ClassDB::bind_method(D_METHOD("_minimap_draw"), &GraphEdit::_minimap_draw); + 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); @@ -1348,6 +1665,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::REAL, "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::REAL, "minimap_opacity"), "set_minimap_opacity", "get_minimap_opacity"); ADD_SIGNAL(MethodInfo("connection_request", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"))); ADD_SIGNAL(MethodInfo("disconnection_request", PropertyInfo(Variant::STRING, "from"), PropertyInfo(Variant::INT, "from_slot"), PropertyInfo(Variant::STRING, "to"), PropertyInfo(Variant::INT, "to_slot"))); @@ -1375,7 +1696,6 @@ GraphEdit::GraphEdit() { top_layer->set_mouse_filter(MOUSE_FILTER_PASS); top_layer->set_anchors_and_margins_preset(Control::PRESET_WIDE); top_layer->connect("draw", this, "_top_layer_draw"); - top_layer->set_mouse_filter(MOUSE_FILTER_PASS); top_layer->connect("gui_input", this, "_top_layer_input"); connections_layer = memnew(Control); @@ -1450,6 +1770,32 @@ GraphEdit::GraphEdit() { snap_amount->connect("value_changed", this, "_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", this, "_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.65; + + 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", this, "_minimap_draw"); + setting_scroll_ofs = false; just_disconnected = false; set_clip_contents(true); diff --git a/scene/gui/graph_edit.h b/scene/gui/graph_edit.h index 7b810a423434a..5776defe92459 100644 --- a/scene/gui/graph_edit.h +++ b/scene/gui/graph_edit.h @@ -46,6 +46,7 @@ class GraphEditFilter : public Control { GDCLASS(GraphEditFilter, Control); friend class GraphEdit; + friend class GraphEditMinimap; GraphEdit *ge; virtual bool has_point(const Point2 &p_point) const; @@ -53,6 +54,45 @@ class GraphEditFilter : public Control { GraphEditFilter(GraphEdit *p_edit); }; +class GraphEditMinimap : public Control { + GDCLASS(GraphEditMinimap, Control); + + friend class GraphEdit; + friend class GraphEditFilter; + GraphEdit *ge; + +protected: + static void _bind_methods(); + +public: + GraphEditMinimap(GraphEdit *p_edit); + + void update_minimap(); + Rect2 get_camera_rect(); + +private: + Vector2 minimap_padding; + Vector2 minimap_offset; + Vector2 graph_proportions; + Vector2 graph_padding; + Vector2 camera_position; + Vector2 camera_size; + + bool is_pressing; + bool is_resizing; + + Vector2 _get_render_size(); + Vector2 _get_graph_offset(); + Vector2 _get_graph_size(); + + Vector2 _convert_from_graph_position(const Vector2 &p_position); + Vector2 _convert_to_graph_position(const Vector2 &p_position); + + void _gui_input(const Ref &p_ev); + + void _adjust_graph_scroll(const Vector2 &p_offset); +}; + class GraphEdit : public Control { GDCLASS(GraphEdit, Control); @@ -74,6 +114,8 @@ class GraphEdit : public Control { ToolButton *snap_button; SpinBox *snap_amount; + Button *minimap_button; + void _zoom_minus(); void _zoom_reset(); void _zoom_plus(); @@ -118,7 +160,7 @@ class GraphEdit : public Control { void _bake_segment2d(Vector &points, Vector &colors, float p_begin, float p_end, const Vector2 &p_a, const Vector2 &p_out, const Vector2 &p_b, const Vector2 &p_in, int p_depth, int p_min_depth, int p_max_depth, float p_tol, const Color &p_color, const Color &p_to_color, int &lines) const; - void _draw_cos_line(CanvasItem *p_where, const Vector2 &p_from, const Vector2 &p_to, const Color &p_color, const Color &p_to_color); + void _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_width, float p_bezier_ratio); void _graph_node_raised(Node *p_gn); void _graph_node_moved(Node *p_gn); @@ -129,12 +171,14 @@ class GraphEdit : public Control { Control *connections_layer; GraphEditFilter *top_layer; + GraphEditMinimap *minimap; void _top_layer_input(const Ref &p_ev); bool is_in_hot_zone(const Vector2 &pos, const Vector2 &p_mouse_pos); void _top_layer_draw(); void _connections_layer_draw(); + void _minimap_draw(); void _update_scroll_offset(); Array _get_connection_list() const; @@ -172,6 +216,9 @@ class GraphEdit : public Control { void _snap_toggled(); void _snap_value_changed(double); + friend class GraphEditMinimap; + void _minimap_toggled(); + bool _check_clickable_control(Control *p_control, const Vector2 &pos); protected: @@ -197,7 +244,16 @@ class GraphEdit : public Control { void set_zoom_custom(float p_zoom, const Vector2 &p_center); float get_zoom() const; + void set_minimap_size(Vector2 p_size); + Vector2 get_minimap_size() const; + void set_minimap_opacity(float p_opacity); + float get_minimap_opacity() const; + + void set_minimap_enabled(bool p_enable); + bool is_minimap_enabled() const; + GraphEditFilter *get_top_layer() const { return top_layer; } + GraphEditMinimap *get_minimap() const { return minimap; } void get_connection_list(List *r_connections) const; void set_right_disconnects(bool p_enable); diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 79ab782273286..11775843ac8b9 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -88,6 +88,17 @@ static Ref make_stylebox(T p_src, float p_left, float p_top, fl return style; } +static Ref make_flat_stylebox(Color p_color, float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { + Ref style(memnew(StyleBoxFlat)); + style->set_bg_color(p_color); + style->set_default_margin(MARGIN_LEFT, p_margin_left * scale); + style->set_default_margin(MARGIN_RIGHT, p_margin_right * scale); + style->set_default_margin(MARGIN_BOTTOM, p_margin_bottom * scale); + style->set_default_margin(MARGIN_TOP, p_margin_top * scale); + + return style; +} + static Ref sb_expand(Ref p_sbox, float p_left, float p_top, float p_right, float p_botton) { p_sbox->set_expand_margin_size(MARGIN_LEFT, p_left * scale); @@ -120,6 +131,25 @@ static Ref make_icon(T p_src) { return texture; } +static Ref flip_icon(Ref p_texture, bool p_flip_y = false, bool p_flip_x = false) { + if (!p_flip_y && !p_flip_x) { + return p_texture; + } + + Ref texture(memnew(ImageTexture)); + Ref img = p_texture->get_data(); + + if (p_flip_y) { + img->flip_y(); + } + if (p_flip_x) { + img->flip_x(); + } + + texture->create_from_image(img); + return texture; +} + static Ref make_font(int p_height, int p_ascent, int p_charcount, const int *p_char_rects, int p_kerning_count, const int *p_kernings, int p_w, int p_h, const unsigned char *p_img) { Ref font(memnew(BitmapFont)); @@ -851,6 +881,7 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_icon("reset", "GraphEdit", make_icon(icon_zoom_reset_png)); 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_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)); @@ -864,6 +895,19 @@ void fill_default_theme(Ref &theme, const Ref &default_font, const theme->set_constant("port_grab_distance_horizontal", "GraphEdit", 48 * scale); theme->set_constant("port_grab_distance_vertical", "GraphEdit", 6 * scale); + theme->set_stylebox("bg", "GraphEditMinimap", make_flat_stylebox(Color(0.24, 0.24, 0.24), 0, 0, 0, 0)); + Ref style_minimap_camera = make_flat_stylebox(Color(0.65, 0.65, 0.65, 0.2), 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); + theme->set_stylebox("camera", "GraphEditMinimap", style_minimap_camera); + Ref style_minimap_node = make_flat_stylebox(Color(1, 1, 1), 0, 0, 0, 0); + style_minimap_node->set_corner_radius_all(2); + theme->set_stylebox("node", "GraphEditMinimap", style_minimap_node); + + Ref resizer_icon = make_icon(window_resizer_png); + theme->set_icon("resizer", "GraphEditMinimap", flip_icon(resizer_icon, true, true)); + theme->set_color("resizer_color", "GraphEditMinimap", Color(1, 1, 1, 0.85)); + // Theme default_icon = make_icon(error_icon_png); diff --git a/scene/resources/default_theme/icon_grid_minimap.png b/scene/resources/default_theme/icon_grid_minimap.png new file mode 100644 index 0000000000000..00a6179d5e975 Binary files /dev/null and b/scene/resources/default_theme/icon_grid_minimap.png differ diff --git a/scene/resources/default_theme/theme_data.h b/scene/resources/default_theme/theme_data.h index edcdb90db9efa..8da6cdc3b611a 100644 --- a/scene/resources/default_theme/theme_data.h +++ b/scene/resources/default_theme/theme_data.h @@ -158,6 +158,10 @@ static const unsigned char icon_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, 0x2e, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x63, 0xa0, 0x6, 0x78, 0x70, 0xf4, 0xc1, 0x7f, 0x24, 0x78, 0x18, 0x53, 0xc1, 0x7f, 0x54, 0x48, 0x50, 0xc1, 0x43, 0x1b, 0xbc, 0xa, 0x50, 0xad, 0x23, 0xa4, 0xe0, 0xff, 0x70, 0x52, 0x70, 0x18, 0x97, 0xf4, 0xfd, 0x43, 0xd4, 0x88, 0x4a, 0x0, 0x5a, 0xcb, 0x18, 0xab, 0x5e, 0xd9, 0x1a, 0x53, 0x0, 0x0, 0x0, 0x0, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82 }; +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_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 };