Skip to content

Commit 5783f99

Browse files
authored
Merge pull request #227 from endlessm/T35648-node-drag
Allow dragging nodes to block canvas
2 parents 62c0a4b + 2714dde commit 5783f99

File tree

10 files changed

+151
-48
lines changed

10 files changed

+151
-48
lines changed

addons/block_code/block_code_plugin.gd

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ const BlockInspectorPlugin := preload("res://addons/block_code/inspector_plugin/
1313
var block_inspector_plugin: BlockInspectorPlugin
1414

1515
var editor_inspector: EditorInspector
16-
var editor_selection: EditorSelection
1716

1817
var _selected_block_code: BlockCode
1918

@@ -34,7 +33,6 @@ func _enter_tree():
3433
Types.init_cast_graph()
3534

3635
editor_inspector = EditorInterface.get_inspector()
37-
editor_selection = EditorInterface.get_selection()
3836

3937
main_panel = MainPanelScene.instantiate()
4038
main_panel.script_window_requested.connect(script_window_requested)
@@ -100,31 +98,14 @@ func _ready():
10098

10199
func _on_editor_inspector_edited_object_changed():
102100
var edited_object = editor_inspector.get_edited_object()
103-
#var edited_node = edited_object as Node
104-
var selected_nodes = editor_selection.get_selected_nodes()
105-
106-
if edited_object is BlockCode:
101+
var block_code_node = edited_object as BlockCode
102+
if block_code_node:
103+
# If a block code node was explicitly selected, activate the
104+
# Block Code panel.
107105
make_bottom_panel_item_visible(main_panel)
108-
109-
if edited_object is BlockCode and selected_nodes.size() == 1 and edited_object.owner and edited_object != _selected_block_code:
110-
# If a BlockCode node is being edited, and it was explicitly selected
111-
# (as opposed to edited in the Inspector alone), select its parent node
112-
# as well. This provides a clearer indication of what is being edited.
113-
# Changing the selection will cause edited_object_changed to fire again,
114-
# so we will return early to avoid duplicate work.
115-
var parent_node = edited_object.get_parent()
116-
if parent_node:
117-
editor_selection.add_node.call_deferred(parent_node)
118-
return
119-
120-
if edited_object and edited_object.get_class() == "MultiNodeEdit":
121-
# If multiple nodes are shown in the inspector, we will find the first
122-
# BlockCode node in the list of selected nodes and use that. This
123-
# occurs when the user selects multiple items in the Scene panel, or
124-
# when we select the parent of a BlockCode node.
125-
edited_object = selected_nodes.filter(func(node): return node is BlockCode).pop_front()
126-
127-
var block_code_node = list_block_code_nodes_for_node(edited_object as Node).pop_front()
106+
else:
107+
# Find the first block code child.
108+
block_code_node = list_block_code_nodes_for_node(edited_object as Node).pop_front()
128109
select_block_code_node(block_code_node)
129110

130111

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@tool
2+
extends BlockExtension
3+
4+
const OptionData = preload("res://addons/block_code/code_generation/option_data.gd")
5+
const Util = preload("res://addons/block_code/ui/util.gd")
6+
7+
8+
func _find_paths(paths: Array[NodePath], node: Node, path_root: Node, block_parent: Node):
9+
# Add any non-BlockCode nodes that aren't the parent of the current
10+
# BlockCode node.
11+
if not node is BlockCode:
12+
var node_path: NodePath = Util.node_scene_path(node, block_parent, path_root)
13+
if not node_path in [^"", ^"."]:
14+
paths.append(node_path)
15+
16+
for child in node.get_children():
17+
_find_paths(paths, child, path_root, block_parent)
18+
19+
20+
func get_defaults_for_node(context_node: Node) -> Dictionary:
21+
# The default paths are only needed in the editor.
22+
if not Engine.is_editor_hint():
23+
return {}
24+
25+
var scene_root: Node = EditorInterface.get_edited_scene_root()
26+
var path_root: Node = scene_root.get_parent()
27+
var paths: Array[NodePath]
28+
_find_paths(paths, scene_root, path_root, context_node)
29+
30+
if not paths:
31+
return {}
32+
33+
return {"path": OptionData.new(paths)}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[gd_resource type="Resource" load_steps=5 format=3 uid="uid://canpdkahokjqs"]
2+
3+
[ext_resource type="Script" path="res://addons/block_code/code_generation/option_data.gd" id="1_bk47y"]
4+
[ext_resource type="Script" path="res://addons/block_code/code_generation/block_definition.gd" id="1_d60g7"]
5+
[ext_resource type="Script" path="res://addons/block_code/blocks/communication/get_node.gd" id="1_we5wl"]
6+
7+
[sub_resource type="Resource" id="Resource_esr4a"]
8+
script = ExtResource("1_bk47y")
9+
selected = 0
10+
items = []
11+
12+
[resource]
13+
script = ExtResource("1_d60g7")
14+
name = &"get_node"
15+
target_node_class = ""
16+
description = "Get the node at the given path"
17+
category = "Communication | Nodes"
18+
type = 3
19+
variant_type = 24
20+
display_template = "{path: NIL}"
21+
code_template = "get_node(\"{path}\")"
22+
defaults = {
23+
"path": SubResource("Resource_esr4a")
24+
}
25+
signal_name = ""
26+
scope = ""
27+
extension_script = ExtResource("1_we5wl")

addons/block_code/ui/block_canvas/block_canvas.gd

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,45 @@ func _ready():
6060
_open_scene_button.icon = _open_scene_icon
6161

6262

63+
func _can_drop_data(at_position: Vector2, data: Variant) -> bool:
64+
if _context.block_code_node == null or _context.parent_node == null:
65+
return false
66+
if typeof(data) != TYPE_DICTIONARY:
67+
return false
68+
69+
var nodes: Array = data.get("nodes", [])
70+
if nodes.size() != 1:
71+
return false
72+
var abs_path: NodePath = nodes[0]
73+
74+
# Don't allow dropping BlockCode nodes or nodes that aren't part of the
75+
# edited scene.
76+
var node := get_tree().root.get_node(abs_path)
77+
if node is BlockCode or not Util.node_is_part_of_edited_scene(node):
78+
return false
79+
80+
# Don't allow dropping the BlockCode node's parent as that's already self.
81+
var parent_path: NodePath = _context.parent_node.get_path()
82+
return abs_path != parent_path
83+
84+
85+
func _drop_data(at_position: Vector2, data: Variant) -> void:
86+
var abs_path: NodePath = data.get("nodes", []).pop_back()
87+
if abs_path == null:
88+
return
89+
90+
# Figure out the best path to the node.
91+
var node := get_tree().root.get_node(abs_path)
92+
var node_path: NodePath = Util.node_scene_path(node, _context.parent_node)
93+
if node_path in [^"", ^"."]:
94+
return
95+
96+
var block = _context.block_script.instantiate_block_by_name(&"get_node")
97+
block.set_parameter_values_on_ready({"path": node_path})
98+
add_block(block, at_position)
99+
reconnect_block.emit(block)
100+
101+
63102
func add_block(block: Block, position: Vector2 = Vector2.ZERO) -> void:
64103
if block is EntryBlock:
65104
block.position = canvas_to_window(position).snapped(SNAP_GRID)

addons/block_code/ui/block_canvas/block_canvas.tscn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ script = ExtResource("1_tk8h2")
1515

1616
[node name="Panel" type="Panel" parent="."]
1717
layout_mode = 2
18+
mouse_filter = 2
1819

1920
[node name="WindowContainer" type="Control" parent="."]
2021
clip_contents = true
2122
layout_mode = 2
23+
mouse_filter = 2
2224

2325
[node name="Window" type="Control" parent="WindowContainer"]
2426
unique_name_in_owner = true

addons/block_code/ui/constants.gd

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ const BUILTIN_CATEGORIES_PROPS: Dictionary = {
8181
"color": Color("4b7bec"),
8282
"order": 110,
8383
},
84+
"Communication | Nodes":
85+
{
86+
"color": Color("4b7bec"),
87+
"order": 115,
88+
},
8489
"Communication | Groups":
8590
{
8691
"color": Color("4b7bec"),

addons/block_code/ui/main_panel.gd

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func _on_block_canvas_add_block_code():
217217

218218
undo_redo.add_do_method(edited_node, "add_child", block_code, true)
219219
undo_redo.add_do_property(block_code, "owner", scene_root)
220-
undo_redo.add_do_method(self, "_select_block_code_node", edited_node)
220+
undo_redo.add_do_property(_context, "block_code_node", block_code)
221221
undo_redo.add_do_reference(block_code)
222222
undo_redo.add_undo_method(edited_node, "remove_child", block_code)
223223
undo_redo.add_undo_property(block_code, "owner", null)
@@ -246,26 +246,11 @@ func _on_block_canvas_replace_block_code():
246246
# Ideally this should fix itself in a future version of Godot.
247247

248248
undo_redo.add_do_method(scene_root, "set_editable_instance", edited_node, true)
249-
undo_redo.add_do_method(self, "_select_block_code_node", edited_node)
250249
undo_redo.add_undo_method(scene_root, "set_editable_instance", edited_node, false)
251250

252251
undo_redo.commit_action()
253252

254253

255-
func _select_block_code_node(edited_node: Node):
256-
var block_code_nodes = BlockCodePlugin.list_block_code_nodes_for_node(edited_node)
257-
if block_code_nodes.size() > 0:
258-
_set_selection([block_code_nodes.pop_front()])
259-
else:
260-
_set_selection([edited_node])
261-
262-
263-
func _set_selection(nodes: Array[Node]):
264-
EditorInterface.get_selection().clear()
265-
for node in nodes:
266-
EditorInterface.get_selection().add_node(node)
267-
268-
269254
func _create_variable(variable: VariableDefinition):
270255
if _context.block_code_node == null:
271256
print("No script loaded to add variable to.")

addons/block_code/ui/title_bar/title_bar.gd

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ signal node_name_changed(node_name: String)
99

1010
@onready var _block_code_icon = load("res://addons/block_code/block_code_node/block_code_node.svg") as Texture2D
1111
@onready var _editor_inspector: EditorInspector = EditorInterface.get_inspector()
12-
@onready var _editor_selection: EditorSelection = EditorInterface.get_selection()
1312
@onready var _node_option_button: OptionButton = %NodeOptionButton
1413

1514

@@ -62,8 +61,4 @@ func _get_block_script_index(block_script: BlockScriptSerialization) -> int:
6261

6362
func _on_node_option_button_item_selected(index):
6463
var block_code_node = _node_option_button.get_item_metadata(index) as BlockCode
65-
var parent_node = block_code_node.get_parent() as Node
66-
_editor_selection.clear()
67-
_editor_selection.add_node(block_code_node)
68-
if parent_node:
69-
_editor_selection.add_node(parent_node)
64+
_context.block_code_node = block_code_node

addons/block_code/ui/util.gd

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,38 @@ static func node_is_part_of_edited_scene(node: Node) -> bool:
1212

1313
var edited_scene_parent := tree.edited_scene_root.get_parent()
1414
return edited_scene_parent and edited_scene_parent.is_ancestor_of(node)
15+
16+
17+
## Get the path from [param reference] to [param node] within a scene.
18+
##
19+
## Returns the path from [param reference] to [param node] without referencing
20+
## parent nodes. If [param node] is [param reference] or a child of it in the
21+
## scene tree, a relative path is returned. If [param node] is an ancestor of
22+
## [param reference] in the scene tree, an absolute path using [param
23+
## path_root] is returned.
24+
## [br]
25+
## Both [param node] and [param reference] must be ancestors of [param
26+
## path_root]. If [param path_root] is [constant null]
27+
## [method EditorInterface.get_edited_scene_root().get_parent] is used.
28+
static func node_scene_path(node: Node, reference: Node, path_root: Node = null) -> NodePath:
29+
if path_root == null:
30+
path_root = EditorInterface.get_edited_scene_root().get_parent()
31+
32+
if not path_root.is_ancestor_of(node):
33+
push_error("Node %s is not an ancestor of %s" % [path_root, node])
34+
return NodePath()
35+
if not path_root.is_ancestor_of(reference):
36+
push_error("Node %s is not an ancestor of %s" % [path_root, reference])
37+
return NodePath()
38+
39+
if node.unique_name_in_owner:
40+
# With unique_name_in_owner, just use the % prefixed name.
41+
return NodePath("%%%s" % node.name)
42+
elif node.is_ancestor_of(reference):
43+
# If the node is an ancestor of the reference, it would begin
44+
# with an ugly ../. Use an absolute path where /root is the
45+
# path_root node.
46+
return NodePath("/root/%s" % path_root.get_path_to(node))
47+
else:
48+
# The node is reference or a child of it. Use a relative path.
49+
return reference.get_path_to(node)

tests/test_category_factory.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func before_each():
3737
const default_category_names = [
3838
"Communication | Groups",
3939
"Communication | Methods",
40+
"Communication | Nodes",
4041
"Graphics | Viewport",
4142
"Input",
4243
"Lifecycle",

0 commit comments

Comments
 (0)