Skip to content

Commit 3a8630a

Browse files
committed
Use a control to manage each drag and drop interaction
This change moves several tightly coupled variables in DragManager into a separate class, which makes it easier to organize resources associated with a particular interaction.
1 parent af807af commit 3a8630a

File tree

3 files changed

+177
-112
lines changed

3 files changed

+177
-112
lines changed

addons/block_code/drag_manager/drag_manager.gd

Lines changed: 167 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -9,110 +9,189 @@ signal block_modified
99
@export var block_canvas_path: NodePath
1010

1111
const Constants = preload("res://addons/block_code/ui/constants.gd")
12-
const BLOCK_AUTO_PLACE_MARGIN: Vector2 = Vector2(16, 8)
1312

14-
var drag_offset: Vector2
15-
var dragging: Block = null
13+
enum DragAction { NONE, PLACE, REMOVE }
14+
15+
16+
class Drag:
17+
extends Control
18+
var _block: Block
19+
var _block_canvas: BlockCanvas
20+
var _preview_block: Control
21+
var action: DragAction:
22+
get:
23+
return action
24+
set(value):
25+
action = value
26+
27+
var target_snap_point: SnapPoint:
28+
get:
29+
return target_snap_point
30+
set(value):
31+
if target_snap_point != value:
32+
target_snap_point = value
33+
_update_preview()
34+
35+
var snap_block: Block:
36+
get:
37+
return target_snap_point.block if target_snap_point else null
38+
39+
func _init(block: Block, offset: Vector2, block_canvas: BlockCanvas):
40+
assert(block.get_parent() == null)
41+
42+
add_child(block)
43+
block.on_canvas = false
44+
block.position = -offset
45+
46+
_block = block
47+
_block_canvas = block_canvas
48+
49+
func apply_drag() -> Block:
50+
if action == DragAction.PLACE:
51+
_place_block()
52+
return _block
53+
elif action == DragAction.REMOVE:
54+
_remove_block()
55+
return null
56+
else:
57+
return null
58+
59+
func _remove_block():
60+
target_snap_point = null
61+
_block.g()
62+
63+
func _place_block():
64+
var canvas_rect: Rect2 = _block_canvas.get_global_rect()
65+
66+
var position = _block.global_position - canvas_rect.position
67+
68+
remove_child(_block)
69+
70+
if target_snap_point:
71+
# Snap the block to the point
72+
var orphaned_block = target_snap_point.set_snapped_block(_block)
73+
if orphaned_block:
74+
# Place the orphan block somewhere outside the snap point
75+
_block_canvas.arrange_block(orphaned_block, snap_block)
76+
else:
77+
# Block goes on screen somewhere
78+
_block_canvas.add_block(_block, position)
79+
80+
target_snap_point = null
81+
82+
func snaps_to(node: Node) -> bool:
83+
var _snap_point: SnapPoint = node as SnapPoint
84+
85+
if not _snap_point:
86+
push_error("Warning: node %s is not of class SnapPoint." % node)
87+
return false
88+
89+
if _snap_point.block == null:
90+
push_error("Warning: snap point %s does not reference its parent block." % _snap_point)
91+
return false
92+
93+
if not _snap_point.block.on_canvas:
94+
# We only snap to blocks on the canvas:
95+
return false
96+
97+
if _block.block_type != _snap_point.block_type:
98+
# We only snap to the same block type:
99+
return false
100+
101+
if _block.block_type == Types.BlockType.VALUE and not Types.can_cast(_block.variant_type, _snap_point.variant_type):
102+
# We only snap Value blocks to snaps that can cast to same variant:
103+
return false
104+
105+
if _get_distance_to_snap_point(_snap_point) > Constants.MINIMUM_SNAP_DISTANCE:
106+
return false
107+
108+
# Check if any parent node is this node
109+
var parent = _snap_point
110+
while parent is SnapPoint:
111+
if parent.block == _block:
112+
return false
113+
114+
parent = parent.block.get_parent()
115+
116+
return true
117+
118+
func sort_snap_points_by_distance(a: SnapPoint, b: SnapPoint):
119+
return _get_distance_to_snap_point(a) < _get_distance_to_snap_point(b)
120+
121+
func _get_distance_to_snap_point(snap_point: SnapPoint) -> float:
122+
var from_global: Vector2 = _block.global_position
123+
return from_global.distance_to(snap_point.global_position)
124+
125+
func _update_preview():
126+
if _preview_block:
127+
_preview_block.queue_free()
128+
_preview_block = null
129+
130+
if target_snap_point:
131+
# Make preview block
132+
_preview_block = Control.new()
133+
_preview_block.set_script(preload("res://addons/block_code/ui/blocks/utilities/background/background.gd"))
134+
135+
_preview_block.color = Color(1, 1, 1, 0.5)
136+
_preview_block.custom_minimum_size = _block.get_global_rect().size
137+
_preview_block.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
138+
_preview_block.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
139+
140+
target_snap_point.add_child(_preview_block)
16141

17-
var previewing_snap_point: SnapPoint = null
18-
var preview_block: Control = null
19142

20143
var _picker: Picker
21144
var _block_canvas: BlockCanvas
22145

146+
var drag: Drag = null
147+
23148

24149
func _ready():
25150
_picker = get_node(picker_path)
26151
_block_canvas = get_node(block_canvas_path)
27152

28153

29154
func _process(_delta):
30-
var mouse_pos: Vector2 = get_local_mouse_position()
31-
if dragging:
32-
dragging.position = mouse_pos - drag_offset
33-
34-
var dragging_global_pos: Vector2 = dragging.get_global_rect().position
35-
36-
# Find closest snap point not child of current node
37-
var closest_snap_point: SnapPoint = null
38-
var closest_dist: float = INF
39-
var snap_points: Array[Node] = get_tree().get_nodes_in_group("snap_point")
40-
for snap_point in snap_points:
41-
if not snap_point is SnapPoint:
42-
push_error('Warning: node %s in group "snap_point" is not of class SnapPoint.' % snap_point)
43-
continue
44-
if snap_point.block == null:
45-
push_error("Warning: snap point %s does not reference it's parent block." % snap_point)
46-
continue
47-
if not snap_point.block.on_canvas:
48-
# We only snap to blocks on the canvas:
49-
continue
50-
if dragging.block_type != snap_point.block_type:
51-
# We only snap to the same block type:
52-
continue
53-
if dragging.block_type == Types.BlockType.VALUE and not Types.can_cast(dragging.variant_type, snap_point.variant_type):
54-
# We only snap Value blocks to snaps that can cast to same variant:
55-
continue
56-
var snap_global_pos: Vector2 = snap_point.get_global_rect().position
57-
var temp_dist: float = dragging_global_pos.distance_to(snap_global_pos)
58-
if temp_dist <= Constants.MINIMUM_SNAP_DISTANCE and temp_dist < closest_dist:
59-
# Check if any parent node is this node
60-
var is_child: bool = false
61-
var parent = snap_point
62-
while parent is SnapPoint:
63-
if parent.block == dragging:
64-
is_child = true
65-
66-
parent = parent.block.get_parent()
67-
68-
if not is_child:
69-
closest_dist = temp_dist
70-
closest_snap_point = snap_point
71-
72-
if closest_snap_point != previewing_snap_point:
73-
_update_preview(closest_snap_point)
74-
75-
76-
func _update_preview(snap_point: SnapPoint):
77-
previewing_snap_point = snap_point
78-
79-
if preview_block:
80-
preview_block.free()
81-
preview_block = null
82-
83-
if previewing_snap_point:
84-
# Make preview block
85-
preview_block = Control.new()
86-
preview_block.set_script(preload("res://addons/block_code/ui/blocks/utilities/background/background.gd"))
87-
88-
preview_block.color = Color(1, 1, 1, 0.5)
89-
preview_block.custom_minimum_size = dragging.get_global_rect().size
90-
preview_block.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
91-
preview_block.size_flags_vertical = Control.SIZE_SHRINK_BEGIN
92-
93-
previewing_snap_point.add_child(preview_block)
155+
_update_drag_position()
156+
157+
158+
func _update_drag_position():
159+
if not drag:
160+
return
161+
162+
drag.position = get_local_mouse_position()
163+
164+
if _picker.get_global_rect().has_point(get_global_mouse_position()):
165+
drag.action = DragAction.REMOVE
166+
else:
167+
drag.action = DragAction.PLACE
168+
169+
# Find closest snap point not child of current node
170+
var snap_points: Array[Node] = get_tree().get_nodes_in_group("snap_point").filter(drag.snaps_to)
171+
snap_points.sort_custom(drag.sort_snap_points_by_distance)
172+
173+
drag.target_snap_point = snap_points[0] if snap_points.size() > 0 else null
94174

95175

96176
func drag_block(block: Block, copied_from: Block = null):
97-
var new_pos: Vector2 = -get_global_rect().position
177+
var offset: Vector2
98178

99179
if copied_from:
100-
new_pos += copied_from.get_global_rect().position
180+
offset = get_global_mouse_position() - copied_from.global_position
101181
else:
102-
new_pos += block.get_global_rect().position
182+
offset = get_global_mouse_position() - block.global_position
103183

104184
var parent = block.get_parent()
185+
105186
if parent is SnapPoint:
106187
parent.remove_snapped_block(block)
107188
elif parent:
108189
parent.remove_child(block)
109190

110-
block.position = new_pos
111-
block.on_canvas = false
112-
add_child(block)
191+
block.disconnect_signals()
113192

114-
drag_offset = get_local_mouse_position() - block.position
115-
dragging = block
193+
drag = Drag.new(block, offset, _block_canvas)
194+
add_child(drag)
116195

117196

118197
func copy_block(block: Block) -> Block:
@@ -121,47 +200,24 @@ func copy_block(block: Block) -> Block:
121200

122201
func copy_picked_block_and_drag(block: Block):
123202
var new_block: Block = copy_block(block)
124-
125203
drag_block(new_block, block)
126204

127205

128206
func drag_ended():
129-
if dragging:
130-
var block_rect: Rect2 = dragging.get_global_rect()
131-
132-
# Check if in BlockCanvas
133-
var block_canvas_rect: Rect2 = _block_canvas.get_global_rect()
134-
135-
if block_canvas_rect.encloses(block_rect):
136-
dragging.disconnect_signals() # disconnect previous on canvas signal connections
137-
connect_block_canvas_signals(dragging)
138-
remove_child(dragging)
139-
dragging.on_canvas = true
140-
141-
if previewing_snap_point:
142-
# Can snap block
143-
var orphaned_block = previewing_snap_point.set_snapped_block(dragging)
144-
if orphaned_block:
145-
# Place the orphan block somewhere outside the snap point
146-
orphaned_block.position = (
147-
(previewing_snap_point.block.global_position - block_canvas_rect.position) + (previewing_snap_point.block.get_size() * Vector2.RIGHT) + BLOCK_AUTO_PLACE_MARGIN
148-
)
149-
_block_canvas.add_block(orphaned_block)
150-
else:
151-
# Block goes on screen somewhere
152-
dragging.position = (get_global_mouse_position() - block_canvas_rect.position - drag_offset)
153-
_block_canvas.add_block(dragging)
154-
else:
155-
dragging.queue_free()
207+
if not drag:
208+
return
209+
210+
_update_drag_position()
211+
212+
var block = drag.apply_drag()
156213

157-
if preview_block:
158-
preview_block.queue_free()
159-
preview_block = null
214+
if block:
215+
connect_block_canvas_signals(block)
160216

161-
previewing_snap_point = null
162-
dragging = null
217+
drag.queue_free()
218+
drag = null
163219

164-
block_dropped.emit()
220+
block_dropped.emit()
165221

166222

167223
func connect_block_canvas_signals(block: Block):

addons/block_code/ui/block_canvas/block_canvas.gd

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ class_name BlockCanvas
33
extends MarginContainer
44

55
const EXTEND_MARGIN: float = 800
6+
const BLOCK_AUTO_PLACE_MARGIN: Vector2 = Vector2(16, 8)
67

78
@onready var _window: Control = %Window
89
@onready var _window_scroll: ScrollContainer = %WindowScroll
@@ -28,12 +29,19 @@ func _populate_block_scenes_by_class():
2829
_block_scenes_by_class[_class.class] = _script.get_scene_path()
2930

3031

31-
func add_block(block: Block) -> void:
32+
func add_block(block: Block, position: Vector2 = Vector2.ZERO) -> void:
33+
block.position = position
3234
block.position.y += _window_scroll.scroll_vertical
35+
block.on_canvas = true
3336
_window.add_child(block)
3437
_window.custom_minimum_size.y = max(block.position.y + EXTEND_MARGIN, _window.custom_minimum_size.y)
3538

3639

40+
func arrange_block(block: Block, nearby_block: Block) -> void:
41+
add_block(block)
42+
block.global_position = (nearby_block.global_position + (nearby_block.get_size() * Vector2.RIGHT) + BLOCK_AUTO_PLACE_MARGIN)
43+
44+
3745
func set_child(n: Node):
3846
n.owner = _window
3947
for c in n.get_children():

addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func set_snapped_block(snapped_block: Block) -> Block:
3535

3636
if snapped_block:
3737
add_child(snapped_block)
38+
snapped_block.on_canvas = block.on_canvas
3839

3940
if snapped_block and orphaned_block:
4041
var last_snap = _get_last_snap(snapped_block)

0 commit comments

Comments
 (0)