From 204ca31ba2cae6eb191db0bc312e3713015a31cc Mon Sep 17 00:00:00 2001 From: LMW Date: Sun, 8 Feb 2026 00:10:43 +0800 Subject: [PATCH] Added multi-reroute gesture --- material_maker/icons/cross.png | Bin 0 -> 281 bytes material_maker/icons/cross.png.import | 40 +++++ .../panels/graph_edit/graph_edit.gd | 137 +++++++++++++----- material_maker/theme/classic_base.tres | 2 +- material_maker/theme/default dark.tres | 2 +- material_maker/theme/default light.tres | 2 +- material_maker/theme/default.tres | 2 +- project.godot | 5 + 8 files changed, 152 insertions(+), 38 deletions(-) create mode 100644 material_maker/icons/cross.png create mode 100644 material_maker/icons/cross.png.import diff --git a/material_maker/icons/cross.png b/material_maker/icons/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..2e90c183fae02f05dd72d60bff7072388fee4701 GIT binary patch literal 281 zcmeAS@N?(olHy`uVBq!ia0vp^A|TAc0wmQNuC@Uw&H|6fVjv9yj0`Q6;vmjxPZ!6K zid%axIdU~QNU%Oo-?EY;hLv^R;i)UqGXmK&F1j6@;GlKMB_sazxr>*?p050qYAonv z%H4VI_TL(VGiUmraTcuF0t)|XBrmp_1=yY6U$0imQqs9r>Rj6$gQboYUdu}h4(xmt_}V void: for t in range(41): add_valid_connection_type(t, 42) add_valid_connection_type(42, t) - node_popup.about_to_popup.connect(func(): valid_drag_cut_entry = false) + node_popup.about_to_popup.connect(func(): drag_line_mode = DragLineGesture.NONE) func _exit_tree(): remove_crash_recovery_file() @@ -168,22 +170,45 @@ func _gui_input(event) -> void: accept_event() node_popup.position = Vector2i(get_screen_transform()*get_local_mouse_position()) node_popup.show_popup() - elif event.is_action_released("ui_cut_drag"): - var conns : Array[Dictionary] - for p in len(drag_cut_line) - 1: - var rect : Rect2 - rect.position = drag_cut_line[p] - rect.end = drag_cut_line[p + 1] - conns = get_connections_intersecting_with_rect(rect.abs()) - if conns.size(): - connections_to_cut.append_array(conns) - if connections_to_cut.size(): - on_cut_connections(connections_to_cut) - connections_to_cut.clear() - Input.set_custom_mouse_cursor(null) - drag_cut_line.clear() - conns.clear() - queue_redraw() + elif event.is_action_released("ui_cut_drag") or event.is_action_released("ui_reroute_drag"): + match drag_line_mode: + DragLineGesture.CUT: + var connections_to_cut : Array[Dictionary] + var links : Array[Dictionary] + for p in len(drag_line) - 1: + var rect : Rect2 + rect.position = drag_line[p] + rect.end = drag_line[p + 1] + links = get_connections_intersecting_with_rect(rect.abs()) + if links.size(): + connections_to_cut.append_array(links) + if connections_to_cut.size(): + on_cut_connections(connections_to_cut) + connections_to_cut.clear() + Input.set_custom_mouse_cursor(null) + drag_line.clear() + links.clear() + queue_redraw() + DragLineGesture.REROUTE: + if drag_line.size() >= 2: + var drag_reroute_line := Curve2D.new() + for p : Vector2 in drag_line: + drag_reroute_line.add_point(p) + var points := drag_reroute_line.tessellate_even_length(5, connection_lines_thickness) + var target_connections : Array[Dictionary] + for p : Vector2 in points: + var link := get_closest_connection_at_point(p, connection_lines_thickness) + if not link.is_empty() and target_connections.find_custom(func(d): + return (d.from_node == link.from_node and d.from_port == link.from_port + and d.to_node == link.to_node and d.to_port == link.to_port)) == -1: + link.position = p + target_connections.append(link) + if target_connections.size(): + on_reroute_connections(target_connections) + target_connections.clear() + Input.set_custom_mouse_cursor(null) + drag_line.clear() + queue_redraw() elif event.is_action_released("ui_lasso_select", true): for node in get_children(): if node is GraphElement: @@ -224,8 +249,10 @@ func _gui_input(event) -> void: event.control = true do_zoom(1.0/1.1) elif event.button_index == MOUSE_BUTTON_RIGHT and event.is_pressed(): - valid_drag_cut_entry = true - if event.is_command_or_control_pressed() and event.shift_pressed: + drag_line_mode = DragLineGesture.CUT + if event.shift_pressed: + add_reroute_under_mouse() + elif event.ctrl_pressed: create_portals() elif event.shift_pressed: add_reroute_under_mouse() @@ -326,15 +353,24 @@ func _gui_input(event) -> void: if rect.has_point(get_global_mouse_position()): mm_globals.set_tip_text("Space/#RMB: Nodes menu, Arrow keys: Pan, Mouse wheel: Zoom", 3) - if ((event.button_mask & MOUSE_BUTTON_MASK_RIGHT) != 0 and valid_drag_cut_entry - and event.relative.length() > 1.0): - if event.ctrl_pressed: - Input.set_custom_mouse_cursor( - drag_cut_cursor, Input.CURSOR_ARROW, CURSOR_HOT_SPOT) - drag_cut_line.append(get_local_mouse_position()) + # drag line gesture (cut/reroute) + if (event.button_mask & MOUSE_BUTTON_MASK_RIGHT) != 0 and event.relative.length() > 1.0: + if drag_line_mode != DragLineGesture.NONE: + drag_line.append(get_local_mouse_position()) queue_redraw() - elif drag_cut_line.size(): - drag_cut_line.append(get_local_mouse_position()) + if event.ctrl_pressed: + drag_line_mode = DragLineGesture.CUT + Input.set_custom_mouse_cursor(drag_cut_cursor, + Input.CURSOR_ARROW, DRAG_CUT_CURSOR_HOT_SPOT) + elif event.shift_pressed: + drag_line_mode = DragLineGesture.REROUTE + Input.set_custom_mouse_cursor(drag_reroute_cursor, + Input.CURSOR_ARROW, drag_reroute_cursor.get_size() * 0.5) + else: + drag_line_mode = DragLineGesture.NONE + Input.set_custom_mouse_cursor(null) + if drag_line.size(): + drag_line.clear() queue_redraw() # lasso selection @@ -355,8 +391,8 @@ func get_padded_node_rect(graph_node:GraphNode) -> Rect2: return Rect2(rect.position, rect.size) func _draw() -> void: - if drag_cut_line.size() > 1: - draw_polyline(drag_cut_line, get_theme_color("connection_knife", "GraphEdit"), 1.0) + if drag_line.size() > 1: + draw_polyline(drag_line, get_theme_color("line_gesture", "GraphEdit"), 1.0) if lasso_points.size() > 1: draw_polyline(lasso_points + PackedVector2Array([lasso_points[0]]), get_theme_color("lasso_stroke", "GraphEdit"), 1.0) @@ -479,6 +515,40 @@ func do_disconnect_node(from : String, from_slot : int, to : String, to_slot : i return true return false +func on_reroute_connections(target_connections : Array[Dictionary]) -> void: + var prev : Dictionary = generator.serialize() + var grouped_connections : Dictionary[String, Array] + + # group connections by their source node/port + for link : Dictionary in target_connections: + var key := "%s_%d" % [link.from_node, link.from_port] + if not grouped_connections.has(key): + grouped_connections[key] = [] + grouped_connections[key].append(link) + + for group : String in grouped_connections: + # find connection group center + var group_rect := Rect2(grouped_connections[group][0].position, Vector2.ZERO) + for group_link : Dictionary in grouped_connections[group]: + group_rect = group_rect.expand(group_link.position) + var group_center := (group_rect.abs().get_center() + scroll_offset) / zoom + + # create reroute node + var reroute : Array = await do_create_nodes({nodes=[{name ="reroute", type="reroute", + node_position={x=group_center.x, y=group_center.y}}], connections=[]}) + var group_reroute : GraphNode = reroute[0] + group_reroute.position_offset -= group_reroute.size * 0.5 + + # reroute connections + for link : Dictionary in grouped_connections[group]: + do_disconnect_node(link.from_node, link.from_port, link.to_node, link.to_port) + do_connect_node(link.from_node, link.from_port, group_reroute.name, 0) + do_connect_node(group_reroute.name, 0, link.to_node, link.to_port) + + # undo/redo + var next : Dictionary = generator.serialize() + undoredo_create_step("Reroute multiple connections", generator.get_hier_name(), prev, next) + func on_cut_connections(connections_to_be_cut : Array): var generator_hier_name : String = generator.get_hier_name() var conns : Array = [] @@ -495,7 +565,6 @@ func on_cut_connections(connections_to_be_cut : Array): ] undoredo.add("Cut node connections", undo_actions, redo_actions) - func on_disconnect_node(from : String, from_slot : int, to : String, to_slot : int) -> void: var from_gen = get_node(from).generator var to_gen = get_node(to).generator diff --git a/material_maker/theme/classic_base.tres b/material_maker/theme/classic_base.tres index 1541f9cb4..a34f5bb39 100644 --- a/material_maker/theme/classic_base.tres +++ b/material_maker/theme/classic_base.tres @@ -981,10 +981,10 @@ CodeEdit/colors/symbol_color = Color(1, 1, 1, 1) CodeEdit/colors/type_color = Color(1, 1, 0.878431, 1) CodeEdit/styles/normal = SubResource("StyleBoxFlat_meah8") FileDialog/styles/panel = SubResource("StyleBoxFlat_7b2nb") -GraphEdit/colors/connection_knife = Color(1, 1, 1, 1) GraphEdit/colors/grid_major = Color(0.321569, 0.337255, 0.384314, 1) GraphEdit/colors/grid_minor = Color(0.321569, 0.337255, 0.384314, 1) GraphEdit/colors/lasso_stroke = Color(1, 1, 1, 1) +GraphEdit/colors/line_gesture = Color(1, 1, 1, 1) GraphEdit/constants/port_hotzone_inner_extent = 8 GraphEdit/constants/port_hotzone_outer_extent = 50 GraphEdit/icons/grid_toggle = SubResource("AtlasTexture_m1w7u") diff --git a/material_maker/theme/default dark.tres b/material_maker/theme/default dark.tres index ab33d0ff5..919079646 100644 --- a/material_maker/theme/default dark.tres +++ b/material_maker/theme/default dark.tres @@ -205,7 +205,7 @@ metadata/_custom_type_script = "uid://3ga2k3abkk0d" [sub_resource type="Resource" id="Resource_0m7hk"] script = ExtResource("4_0efyb") -name = "GraphEditConnectionKnife" +name = "GraphEditLineGesture" orig = Color(0.99215686, 0.9843137, 1, 1) target = Color(1, 1, 1, 1) metadata/_custom_type_script = "uid://3ga2k3abkk0d" diff --git a/material_maker/theme/default light.tres b/material_maker/theme/default light.tres index 9b02c342c..07bb66e78 100644 --- a/material_maker/theme/default light.tres +++ b/material_maker/theme/default light.tres @@ -346,7 +346,7 @@ metadata/_custom_type_script = "uid://3ga2k3abkk0d" [sub_resource type="Resource" id="Resource_qqxbn"] script = ExtResource("4_rhf2q") -name = "GraphEditConnectionKnife" +name = "GraphEditLineGesture" orig = Color(0.99215686, 0.9843137, 1, 1) target = Color(1, 1, 1, 1) metadata/_custom_type_script = "uid://3ga2k3abkk0d" diff --git a/material_maker/theme/default.tres b/material_maker/theme/default.tres index f19d6072b..7fb7130a0 100644 --- a/material_maker/theme/default.tres +++ b/material_maker/theme/default.tres @@ -1252,10 +1252,10 @@ FileDialog/colors/file_disabled_color = Color(0.301961, 0.305882, 0.309804, 1) FileDialog/colors/file_icon_color = Color(0.698039, 0.698039, 0.698039, 1) FileDialog/colors/folder_icon_color = Color(0.698039, 0.698039, 0.698039, 1) FileDialog/styles/panel = SubResource("StyleBoxFlat_ck0hb") -GraphEdit/colors/connection_knife = Color(0.99215686, 0.9843137, 1, 1) GraphEdit/colors/grid_major = Color(0.137255, 0.141176, 0.152941, 1) GraphEdit/colors/grid_minor = Color(0.137255, 0.141176, 0.152941, 1) GraphEdit/colors/lasso_stroke = Color(0.9882353, 0.9882353, 0.9882353, 1) +GraphEdit/colors/line_gesture = Color(0.9882353, 0.9882353, 0.9882353, 1) GraphEdit/constants/port_hotzone_inner_extent = 8 GraphEdit/constants/port_hotzone_outer_extent = 50 GraphEdit/icons/grid_toggle = SubResource("AtlasTexture_m1w7u") diff --git a/project.godot b/project.godot index bdc58cb36..07571201f 100644 --- a/project.godot +++ b/project.godot @@ -132,6 +132,11 @@ left_click={ "events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(80, 13),"global_position":Vector2(89, 61),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null) ] } +ui_reroute_drag={ +"deadzone": 0.5, +"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":true,"ctrl_pressed":false,"meta_pressed":false,"button_mask":2,"position":Vector2(812, 27),"global_position":Vector2(828, 108),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null) +] +} [locale]