Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions material_maker/doc/user_interface_main_menu.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Edit menu

* *Frame selected nodes* creates a comment node that frames selected nodes

* *Detach Connections* detaches selection from existing connections

* *Remove with Reconnect* is similar to *Detach Connections* but removes the selection

* *Select all* selects all nodes in the current graph view

* *Select none* clears the selection in the current graph view
Expand Down
3 changes: 2 additions & 1 deletion material_maker/globals.gd
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ const DEFAULT_CONFIG : Dictionary = {
node_minimize_button = true,
node_close_button = true,
ui_field_sensitivity = 1.0,
color_picker_floating = false
color_picker_floating = false,
auto_node_insertion = true,
}


Expand Down
12 changes: 12 additions & 0 deletions material_maker/main_window.gd
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ const MENU : Array[Dictionary] = [
{ menu="Edit/Swap node inputs", command="edit_swap_node_inputs", shortcut="Alt+S"},
{ menu="Edit/-" },
{ menu="Edit/Frame selected nodes", command="frame_nodes", shortcut="Control+Shift+F" },
{ menu="Edit/Detach Connections", command="edit_detach_node_connections", shortcut="Control+Shift+C" },
{ menu="Edit/Remove with Reconnect", command="edit_remove_with_reconnections", shortcut="Control+Shift+X" },
{ menu="Edit/-" },
{ menu="Edit/Select All", command="edit_select_all", shortcut="Control+A" },
{ menu="Edit/Select None", command="edit_select_none", shortcut="Control+Shift+A" },
Expand Down Expand Up @@ -975,6 +977,16 @@ func edit_swap_node_inputs() -> void:
if graph_edit != null:
graph_edit.swap_node_inputs()

func edit_remove_with_reconnections() -> void:
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit != null:
graph_edit.remove_with_reconnections()

func edit_detach_node_connections() -> void:
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit != null:
graph_edit.remove_with_reconnections(true)

func edit_select_all() -> void:
var graph_edit : MMGraphEdit = get_current_graph_edit()
if graph_edit != null:
Expand Down
1 change: 1 addition & 0 deletions material_maker/nodes/reroute/reroute.gd
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
extends MMGraphNodeMinimal

class_name MMGraphReroute

const PREVIEW_SIZES : Array[int] = [ 0, 64, 128, 192]

Expand Down
191 changes: 184 additions & 7 deletions material_maker/panels/graph_edit/graph_edit.gd
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,14 @@ var need_save : bool = false
var save_crash_recovery_path = ""
var need_save_crash_recovery : bool = false


var top_generator = null
var generator = null

var grab_accumulation : Dictionary[String, Vector2]
var target_drop_connection : Dictionary
var target_drop_node : MMGraphNodeMinimal

@onready var grab_icon := preload("res://material_maker/icons/grab.svg")
var has_grab : bool = false:
set(v):
Expand All @@ -36,6 +40,9 @@ var has_grab : bool = false:
else:
Input.set_custom_mouse_cursor(null)
grab_accumulation.clear()
if not target_drop_connection.is_empty():
drop_node_on_connection(target_drop_node, target_drop_connection)
target_drop_connection.clear()

const PREVIEW_COUNT = 2
var current_preview : Array = [ null, null ]
Expand Down Expand Up @@ -144,7 +151,6 @@ func process_port_click(pressed : bool):
port_click_port_index = -1
return


func _input(event : InputEvent) -> void:
# Handle node grab
if has_grab:
Expand Down Expand Up @@ -282,17 +288,30 @@ func _gui_input(event) -> void:
if c.has_method("on_clicked_output"):
c.on_clicked_output(slot.index, Input.is_key_pressed(KEY_SHIFT))
return
# Avoid conflicting with:
# - drag-cut (Ctrl + RMB)
# - reroute insertion on connection lines (Shift + RMB)
# - lasso selection (Alt + LMB)
if not (event.ctrl_pressed or event.shift_pressed or event.alt_pressed):
var closest_connection : Dictionary = get_closest_connection_at_point(
get_local_mouse_position(), connection_lines_thickness + 2.0)
if not closest_connection.is_empty():
node_popup.target_connection = closest_connection
request_popup(closest_connection.from_node, closest_connection.from_port,
get_local_mouse_position(), false)
elif not (event.ctrl_pressed or event.shift_pressed or event.alt_pressed):
# Avoid conflicting with:
# - drag-cut (Ctrl + RMB)
# - reroute insertion on connection lines (Shift + RMB)
# - lasso selection (Alt + LMB)
node_popup.position = Vector2i(get_screen_transform()*get_local_mouse_position())
node_popup.show_popup()
else:
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
has_grab = false
else:
if not target_drop_connection.is_empty() and not Input.is_key_pressed(KEY_D):
drop_node_on_connection(target_drop_node, target_drop_connection)
target_drop_connection.clear()
remove_theme_color_override("activity")
remove_theme_color_override("connection_hover_tint_color")

if event.double_click:
if get_nodes_under_mouse().is_empty():
on_ButtonUp_pressed()
Expand Down Expand Up @@ -330,6 +349,20 @@ func _gui_input(event) -> void:
KEY_G:
if not get_selected_nodes().is_empty():
has_grab = true
KEY_D:
if (Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
and has_graph_node_selection()):
hint_node_drop_allowed(false)
highlight_connection(target_drop_connection)
KEY_G:
if not get_selected_nodes().is_empty():
has_grab = true
elif not event.pressed:
var scancode_with_modifiers = event.get_keycode_with_modifiers()
match scancode_with_modifiers:
KEY_D:
if has_graph_node_selection() and mm_globals.get_config("auto_node_insertion"):
hint_node_drop_allowed(true)
KEY_ESCAPE:
has_grab = false
_ when event.unicode >= KEY_0 and event.unicode <= KEY_9:
Expand Down Expand Up @@ -392,6 +425,105 @@ func _gui_input(event) -> void:
lasso_points.clear()
queue_redraw()

handle_node_detach(event)
handle_node_drop(event)

## Detaches node(s) from connections if D is held down while dragging a node.
## [br][br]Unsets [param target_drop_connection]
func handle_node_detach(event: InputEventMouseMotion) -> void:
if (Input.is_key_pressed(KEY_D) and event.button_mask & MOUSE_BUTTON_MASK_LEFT != 0
and get_selected_nodes().size() and event.relative.length() > 0.0):
if not get_nodes_under_mouse().is_empty():
remove_with_reconnections(true)
target_drop_connection.clear()

## Checks whether a node can be dropped onto a connection
## and sets [param target_drop_connection] and [param target_drop_node]
## [br][br]Node connections are performed by [method drop_node_on_connection]
func handle_node_drop(event : InputEventMouseMotion) -> void:
if not mm_globals.get_config("auto_node_insertion"):
return
var single_node_selected := get_selected_nodes().size() == 1
if (single_node_selected and event.button_mask & MOUSE_BUTTON_MASK_LEFT != 0
and event.relative.length() > 0.0) or (has_grab and single_node_selected):
hint_node_drop_allowed(not Input.is_key_pressed(KEY_D))
var node : GraphElement = get_selected_nodes()[0]
if node is not GraphNode or node is MMGraphPortal:
return
target_drop_node = node
for c in get_connection_list():
if node.name in c.values():
return
var active_conn : Dictionary
var node_rect := node.get_rect()
var conns := get_connections_intersecting_with_rect(node_rect)
if not conns.is_empty():
if conns.size() > 1:
conns.sort_custom(compare_connection_by_port_height)
active_conn = conns.front()
highlight_connection(active_conn)
if not Input.is_key_pressed(KEY_D):
target_drop_connection = active_conn
else:
target_drop_connection.clear()
for c in get_connection_list():
if c != active_conn:
highlight_connection(c, 0.0)
else:
remove_theme_color_override("activity")
remove_theme_color_override("connection_hover_tint_color")

func compare_connection_by_port_height(a : Dictionary, b : Dictionary) -> bool:
var target := target_drop_node
if not target:
return false
var from_node : MMGraphNodeMinimal = get_node(NodePath(a.from_node))
var to_node : MMGraphNodeMinimal = get_node(NodePath(a.to_node))
var from_node_dist_to_target := target.position_offset.distance_squared_to(from_node.position_offset)
var to_node_dist_to_target := target.position_offset.distance_squared_to(to_node.position_offset)
if from_node_dist_to_target < to_node_dist_to_target:
var upper : MMGraphNodeMinimal = get_node(NodePath(a.from_node))
var lower : MMGraphNodeMinimal = get_node(NodePath(b.from_node))
var upper_slot_pos := upper.get_output_port_position(a.from_port) + upper.position_offset
var lower_slot_pos := lower.get_output_port_position(b.from_port) + lower.position_offset
return upper_slot_pos.y < lower_slot_pos.y
else:
var upper : MMGraphNodeMinimal = get_node(NodePath(a.to_node))
var lower : MMGraphNodeMinimal = get_node(NodePath(b.to_node))
var upper_slot_pos := upper.get_input_port_position(a.to_port) + upper.position_offset
var lower_slot_pos := lower.get_input_port_position(b.to_port) + lower.position_offset
return upper_slot_pos.y < lower_slot_pos.y

func drop_node_on_connection(node : GraphNode, connection : Dictionary) -> void:
undoredo.start_group()
if node != null:
on_disconnect_node(connection.from_node, connection.from_port,
connection.to_node, connection.to_port)
for new_slot in node.get_input_port_count():
var slot_type : int = node.get_input_port_type(new_slot)
var from_node : MMGraphNodeMinimal = get_node(NodePath(connection.from_node))
var from_slot : int = from_node.get_output_port_type(connection.from_port)
if (from_slot == slot_type or slot_type == 42 or from_slot == 42):
on_connect_node(connection.from_node, connection.from_port, node.name, new_slot)
break
for new_slot in node.get_output_port_count():
var slot_type : int = node.get_output_port_type(new_slot)
var to_node : MMGraphNodeMinimal = get_node(NodePath(connection.to_node))
var to_slot : int = to_node.get_input_port_type(connection.to_port)
if (to_slot == slot_type or slot_type == 42 or to_slot == 42):
on_connect_node(node.name, new_slot, connection.to_node, connection.to_port)
break
undoredo.end_group()

func hint_node_drop_allowed(should_allow : bool) -> void:
add_theme_color_override("activity", Color(1.5,1.5,1.5,1.0) if should_allow else Color.BLACK)
add_theme_color_override("connection_hover_tint_color", Color.TRANSPARENT)
get_node("_connection_layer").queue_redraw()

func highlight_connection(connection: Dictionary, amount: float = 0.5) -> void:
if not connection.is_empty():
set_connection_activity(connection.from_node, connection.from_port,
connection.to_node, connection.to_port, amount)

func get_padded_node_rect(graph_node:GraphNode) -> Rect2:
var rect : Rect2 = graph_node.get_global_rect()
Expand All @@ -409,6 +541,10 @@ func _draw() -> void:
MMGraphPortal.draw_links(self)

# Misc. useful functions

func has_graph_node_selection() -> bool:
return get_selected_nodes().filter(func(n): return n is GraphNode).size()

func get_source(node, port) -> Dictionary:
for c in get_connection_list():
if c.to_node == node and c.to_port == port:
Expand Down Expand Up @@ -1063,6 +1199,45 @@ func duplicate_selected() -> void:
func duplicate_selected_with_inputs() -> void:
do_paste(serialize_selection([], true))

## Detaches a node from an existing connection.
## Detached node will not be removed if [param keep_nodes] is set
func remove_with_reconnections(keep_nodes: bool = false) -> void:
var selection := get_selected_nodes()
var from_node : MMGraphNodeMinimal
var to_node : MMGraphNodeMinimal
var from_slot : int = -1
var to_slot : int = -1
undoredo.start_group()
for node in selection:
if node is not GraphNode:
node.selected = false
var connection_list : Array[Dictionary] = get_connection_list()
connection_list.sort_custom(func(a, b): return a.to_port < b.to_port)
for c : Dictionary in connection_list:
var from : MMGraphNodeMinimal = get_node(NodePath(c.from_node))
var to : MMGraphNodeMinimal = get_node(NodePath(c.to_node))
if from == node or to == node:
on_disconnect_node(c.from_node, c.from_port, c.to_node, c.to_port)
if from == node:
to_node = to
to_slot = c.to_port
if to == node:
from_node = from
from_slot = c.from_port
if from_node and to_node and from_slot != -1 and to_slot != -1:
var from_slot_type := from_node.get_output_port_type(from_slot)
var to_slot_type := to_node.get_input_port_type(to_slot)
if (from_slot_type == to_slot_type
or from_slot_type == 42 or to_slot_type == 42):
on_connect_node(from_node.name, from_slot, to_node.name, to_slot)
from_node = null
to_node = null
from_slot = -1
to_slot = -1
if not keep_nodes:
remove_selection()
undoredo.end_group()

func select_all() -> void:
for c in get_children():
if c is GraphElement:
Expand Down Expand Up @@ -1172,7 +1347,9 @@ func highlight_connections() -> void:
while Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
await get_tree().process_frame
for c in get_connection_list():
set_connection_activity(c.from_node, c.from_port, c.to_node, c.to_port, 1.0 if get_node(NodePath(c.from_node)).selected or get_node(NodePath(c.to_node)).selected else 0.0)
set_connection_activity(c.from_node, c.from_port, c.to_node, c.to_port,
1.0 if get_node(NodePath(c.from_node)).selected
or get_node(NodePath(c.to_node)).selected else 0.0)
highlighting_connections = false

func _on_GraphEdit_node_selected(node : GraphElement) -> void:
Expand Down
Loading