From e4a6bbafaf5e2a2173b08f8224abe391ae810f06 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 24 Jun 2024 09:51:41 -0700 Subject: [PATCH 1/6] Reset snap point variables in DragManager.drag_end In the `drag_end` function, make sure to reset `previewing_snap_point` in addition to `preview_block`. This ensures that block snapping will work correctly if the same block is dragged a second time. In addition, defer cleaning up preview_block and previewing_snap_point to the end of the function. This avoids some confusion related to nested if statements. https://phabricator.endlessm.com/T35524 --- addons/block_code/drag_manager/drag_manager.gd | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/addons/block_code/drag_manager/drag_manager.gd b/addons/block_code/drag_manager/drag_manager.gd index 5706f581..51a96afb 100644 --- a/addons/block_code/drag_manager/drag_manager.gd +++ b/addons/block_code/drag_manager/drag_manager.gd @@ -16,7 +16,6 @@ var dragging: Block = null var previewing_snap_point: SnapPoint = null var preview_block: Control = null -var preview_owner: Block = null var _picker: Picker var _block_canvas: BlockCanvas @@ -132,16 +131,15 @@ func drag_ended(): # Check if in BlockCanvas var block_canvas_rect: Rect2 = _block_canvas.get_global_rect() + if block_canvas_rect.encloses(block_rect): dragging.disconnect_signals() # disconnect previous on canvas signal connections connect_block_canvas_signals(dragging) remove_child(dragging) dragging.on_canvas = true - if preview_block: + if previewing_snap_point: # Can snap block - preview_block.free() - preview_block = null var orphaned_block = previewing_snap_point.set_snapped_block(dragging) if orphaned_block: # Place the orphan block somewhere outside the snap point @@ -156,7 +154,13 @@ func drag_ended(): else: dragging.queue_free() + if preview_block: + preview_block.queue_free() + preview_block = null + + previewing_snap_point = null dragging = null + block_dropped.emit() From af807afca8e7dbac686e89a0441447cfee6b35e8 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 24 Jun 2024 17:20:49 -0700 Subject: [PATCH 2/6] Reset block size when snap points are changed Previously, blocks were continuing to occupy space after their children were detached, making it difficult to interact with other blocks in the canvas. --- .../drag_drop_area/drag_drop_area.gd | 25 ++++++------------- .../drag_drop_area/drag_drop_area.tscn | 2 -- .../blocks/utilities/snap_point/snap_point.gd | 3 +++ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.gd b/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.gd index 52e2279a..1697c7e1 100644 --- a/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.gd +++ b/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.gd @@ -5,22 +5,13 @@ extends MarginContainer signal mouse_down signal mouse_up -var hovered := false - - -func _on_mouse_entered(): - hovered = true - - -func _on_mouse_exited(): - hovered = false - func _on_gui_input(event): - if hovered: - if event is InputEventMouseButton: - var mouse_event: InputEventMouseButton = event as InputEventMouseButton - if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed: - mouse_down.emit() - if mouse_event.button_index == MOUSE_BUTTON_LEFT and not mouse_event.pressed: - mouse_up.emit() + if event is InputEventMouseButton and get_global_rect().has_point(event.global_position): + var mouse_event: InputEventMouseButton = event as InputEventMouseButton + if mouse_event.button_index == MOUSE_BUTTON_LEFT and mouse_event.pressed: + mouse_down.emit() + get_viewport().set_input_as_handled() + if mouse_event.button_index == MOUSE_BUTTON_LEFT and not mouse_event.pressed: + mouse_up.emit() + get_viewport().set_input_as_handled() diff --git a/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn b/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn index a8579dd3..09f4d444 100644 --- a/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn +++ b/addons/block_code/ui/blocks/utilities/drag_drop_area/drag_drop_area.tscn @@ -12,5 +12,3 @@ mouse_default_cursor_shape = 2 script = ExtResource("1_5vdxp") [connection signal="gui_input" from="." to="." method="_on_gui_input"] -[connection signal="mouse_entered" from="." to="." method="_on_mouse_entered"] -[connection signal="mouse_exited" from="." to="." method="_on_mouse_exited"] diff --git a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd index 32d3ab89..2c7fb2b6 100644 --- a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd +++ b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd @@ -44,6 +44,9 @@ func set_snapped_block(snapped_block: Block) -> Block: snapped_block_changed.emit(snapped_block) + reset_size() + block.reset_size() + return orphaned_block From 3a8630ac47cfc5a005b3c1f6b8d46e9a66ba5c36 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 24 Jun 2024 17:26:33 -0700 Subject: [PATCH 3/6] 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. --- .../block_code/drag_manager/drag_manager.gd | 278 +++++++++++------- .../ui/block_canvas/block_canvas.gd | 10 +- .../blocks/utilities/snap_point/snap_point.gd | 1 + 3 files changed, 177 insertions(+), 112 deletions(-) diff --git a/addons/block_code/drag_manager/drag_manager.gd b/addons/block_code/drag_manager/drag_manager.gd index 51a96afb..aa7697be 100644 --- a/addons/block_code/drag_manager/drag_manager.gd +++ b/addons/block_code/drag_manager/drag_manager.gd @@ -9,17 +9,142 @@ signal block_modified @export var block_canvas_path: NodePath const Constants = preload("res://addons/block_code/ui/constants.gd") -const BLOCK_AUTO_PLACE_MARGIN: Vector2 = Vector2(16, 8) -var drag_offset: Vector2 -var dragging: Block = null +enum DragAction { NONE, PLACE, REMOVE } + + +class Drag: + extends Control + var _block: Block + var _block_canvas: BlockCanvas + var _preview_block: Control + var action: DragAction: + get: + return action + set(value): + action = value + + var target_snap_point: SnapPoint: + get: + return target_snap_point + set(value): + if target_snap_point != value: + target_snap_point = value + _update_preview() + + var snap_block: Block: + get: + return target_snap_point.block if target_snap_point else null + + func _init(block: Block, offset: Vector2, block_canvas: BlockCanvas): + assert(block.get_parent() == null) + + add_child(block) + block.on_canvas = false + block.position = -offset + + _block = block + _block_canvas = block_canvas + + func apply_drag() -> Block: + if action == DragAction.PLACE: + _place_block() + return _block + elif action == DragAction.REMOVE: + _remove_block() + return null + else: + return null + + func _remove_block(): + target_snap_point = null + _block.g() + + func _place_block(): + var canvas_rect: Rect2 = _block_canvas.get_global_rect() + + var position = _block.global_position - canvas_rect.position + + remove_child(_block) + + if target_snap_point: + # Snap the block to the point + var orphaned_block = target_snap_point.set_snapped_block(_block) + if orphaned_block: + # Place the orphan block somewhere outside the snap point + _block_canvas.arrange_block(orphaned_block, snap_block) + else: + # Block goes on screen somewhere + _block_canvas.add_block(_block, position) + + target_snap_point = null + + func snaps_to(node: Node) -> bool: + var _snap_point: SnapPoint = node as SnapPoint + + if not _snap_point: + push_error("Warning: node %s is not of class SnapPoint." % node) + return false + + if _snap_point.block == null: + push_error("Warning: snap point %s does not reference its parent block." % _snap_point) + return false + + if not _snap_point.block.on_canvas: + # We only snap to blocks on the canvas: + return false + + if _block.block_type != _snap_point.block_type: + # We only snap to the same block type: + return false + + if _block.block_type == Types.BlockType.VALUE and not Types.can_cast(_block.variant_type, _snap_point.variant_type): + # We only snap Value blocks to snaps that can cast to same variant: + return false + + if _get_distance_to_snap_point(_snap_point) > Constants.MINIMUM_SNAP_DISTANCE: + return false + + # Check if any parent node is this node + var parent = _snap_point + while parent is SnapPoint: + if parent.block == _block: + return false + + parent = parent.block.get_parent() + + return true + + func sort_snap_points_by_distance(a: SnapPoint, b: SnapPoint): + return _get_distance_to_snap_point(a) < _get_distance_to_snap_point(b) + + func _get_distance_to_snap_point(snap_point: SnapPoint) -> float: + var from_global: Vector2 = _block.global_position + return from_global.distance_to(snap_point.global_position) + + func _update_preview(): + if _preview_block: + _preview_block.queue_free() + _preview_block = null + + if target_snap_point: + # Make preview block + _preview_block = Control.new() + _preview_block.set_script(preload("res://addons/block_code/ui/blocks/utilities/background/background.gd")) + + _preview_block.color = Color(1, 1, 1, 0.5) + _preview_block.custom_minimum_size = _block.get_global_rect().size + _preview_block.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + _preview_block.size_flags_vertical = Control.SIZE_SHRINK_BEGIN + + target_snap_point.add_child(_preview_block) -var previewing_snap_point: SnapPoint = null -var preview_block: Control = null var _picker: Picker var _block_canvas: BlockCanvas +var drag: Drag = null + func _ready(): _picker = get_node(picker_path) @@ -27,92 +152,46 @@ func _ready(): func _process(_delta): - var mouse_pos: Vector2 = get_local_mouse_position() - if dragging: - dragging.position = mouse_pos - drag_offset - - var dragging_global_pos: Vector2 = dragging.get_global_rect().position - - # Find closest snap point not child of current node - var closest_snap_point: SnapPoint = null - var closest_dist: float = INF - var snap_points: Array[Node] = get_tree().get_nodes_in_group("snap_point") - for snap_point in snap_points: - if not snap_point is SnapPoint: - push_error('Warning: node %s in group "snap_point" is not of class SnapPoint.' % snap_point) - continue - if snap_point.block == null: - push_error("Warning: snap point %s does not reference it's parent block." % snap_point) - continue - if not snap_point.block.on_canvas: - # We only snap to blocks on the canvas: - continue - if dragging.block_type != snap_point.block_type: - # We only snap to the same block type: - continue - if dragging.block_type == Types.BlockType.VALUE and not Types.can_cast(dragging.variant_type, snap_point.variant_type): - # We only snap Value blocks to snaps that can cast to same variant: - continue - var snap_global_pos: Vector2 = snap_point.get_global_rect().position - var temp_dist: float = dragging_global_pos.distance_to(snap_global_pos) - if temp_dist <= Constants.MINIMUM_SNAP_DISTANCE and temp_dist < closest_dist: - # Check if any parent node is this node - var is_child: bool = false - var parent = snap_point - while parent is SnapPoint: - if parent.block == dragging: - is_child = true - - parent = parent.block.get_parent() - - if not is_child: - closest_dist = temp_dist - closest_snap_point = snap_point - - if closest_snap_point != previewing_snap_point: - _update_preview(closest_snap_point) - - -func _update_preview(snap_point: SnapPoint): - previewing_snap_point = snap_point - - if preview_block: - preview_block.free() - preview_block = null - - if previewing_snap_point: - # Make preview block - preview_block = Control.new() - preview_block.set_script(preload("res://addons/block_code/ui/blocks/utilities/background/background.gd")) - - preview_block.color = Color(1, 1, 1, 0.5) - preview_block.custom_minimum_size = dragging.get_global_rect().size - preview_block.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN - preview_block.size_flags_vertical = Control.SIZE_SHRINK_BEGIN - - previewing_snap_point.add_child(preview_block) + _update_drag_position() + + +func _update_drag_position(): + if not drag: + return + + drag.position = get_local_mouse_position() + + if _picker.get_global_rect().has_point(get_global_mouse_position()): + drag.action = DragAction.REMOVE + else: + drag.action = DragAction.PLACE + + # Find closest snap point not child of current node + var snap_points: Array[Node] = get_tree().get_nodes_in_group("snap_point").filter(drag.snaps_to) + snap_points.sort_custom(drag.sort_snap_points_by_distance) + + drag.target_snap_point = snap_points[0] if snap_points.size() > 0 else null func drag_block(block: Block, copied_from: Block = null): - var new_pos: Vector2 = -get_global_rect().position + var offset: Vector2 if copied_from: - new_pos += copied_from.get_global_rect().position + offset = get_global_mouse_position() - copied_from.global_position else: - new_pos += block.get_global_rect().position + offset = get_global_mouse_position() - block.global_position var parent = block.get_parent() + if parent is SnapPoint: parent.remove_snapped_block(block) elif parent: parent.remove_child(block) - block.position = new_pos - block.on_canvas = false - add_child(block) + block.disconnect_signals() - drag_offset = get_local_mouse_position() - block.position - dragging = block + drag = Drag.new(block, offset, _block_canvas) + add_child(drag) func copy_block(block: Block) -> Block: @@ -121,47 +200,24 @@ func copy_block(block: Block) -> Block: func copy_picked_block_and_drag(block: Block): var new_block: Block = copy_block(block) - drag_block(new_block, block) func drag_ended(): - if dragging: - var block_rect: Rect2 = dragging.get_global_rect() - - # Check if in BlockCanvas - var block_canvas_rect: Rect2 = _block_canvas.get_global_rect() - - if block_canvas_rect.encloses(block_rect): - dragging.disconnect_signals() # disconnect previous on canvas signal connections - connect_block_canvas_signals(dragging) - remove_child(dragging) - dragging.on_canvas = true - - if previewing_snap_point: - # Can snap block - var orphaned_block = previewing_snap_point.set_snapped_block(dragging) - if orphaned_block: - # Place the orphan block somewhere outside the snap point - orphaned_block.position = ( - (previewing_snap_point.block.global_position - block_canvas_rect.position) + (previewing_snap_point.block.get_size() * Vector2.RIGHT) + BLOCK_AUTO_PLACE_MARGIN - ) - _block_canvas.add_block(orphaned_block) - else: - # Block goes on screen somewhere - dragging.position = (get_global_mouse_position() - block_canvas_rect.position - drag_offset) - _block_canvas.add_block(dragging) - else: - dragging.queue_free() + if not drag: + return + + _update_drag_position() + + var block = drag.apply_drag() - if preview_block: - preview_block.queue_free() - preview_block = null + if block: + connect_block_canvas_signals(block) - previewing_snap_point = null - dragging = null + drag.queue_free() + drag = null - block_dropped.emit() + block_dropped.emit() func connect_block_canvas_signals(block: Block): diff --git a/addons/block_code/ui/block_canvas/block_canvas.gd b/addons/block_code/ui/block_canvas/block_canvas.gd index c37667cc..4ea0f278 100644 --- a/addons/block_code/ui/block_canvas/block_canvas.gd +++ b/addons/block_code/ui/block_canvas/block_canvas.gd @@ -3,6 +3,7 @@ class_name BlockCanvas extends MarginContainer const EXTEND_MARGIN: float = 800 +const BLOCK_AUTO_PLACE_MARGIN: Vector2 = Vector2(16, 8) @onready var _window: Control = %Window @onready var _window_scroll: ScrollContainer = %WindowScroll @@ -28,12 +29,19 @@ func _populate_block_scenes_by_class(): _block_scenes_by_class[_class.class] = _script.get_scene_path() -func add_block(block: Block) -> void: +func add_block(block: Block, position: Vector2 = Vector2.ZERO) -> void: + block.position = position block.position.y += _window_scroll.scroll_vertical + block.on_canvas = true _window.add_child(block) _window.custom_minimum_size.y = max(block.position.y + EXTEND_MARGIN, _window.custom_minimum_size.y) +func arrange_block(block: Block, nearby_block: Block) -> void: + add_block(block) + block.global_position = (nearby_block.global_position + (nearby_block.get_size() * Vector2.RIGHT) + BLOCK_AUTO_PLACE_MARGIN) + + func set_child(n: Node): n.owner = _window for c in n.get_children(): diff --git a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd index 2c7fb2b6..21d31223 100644 --- a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd +++ b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd @@ -35,6 +35,7 @@ func set_snapped_block(snapped_block: Block) -> Block: if snapped_block: add_child(snapped_block) + snapped_block.on_canvas = block.on_canvas if snapped_block and orphaned_block: var last_snap = _get_last_snap(snapped_block) From a6f93669ac935233521de96e5f99dfc0fca461c6 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 24 Jun 2024 17:51:06 -0700 Subject: [PATCH 4/6] Hint if a block will be deleted when it is dragged --- addons/block_code/drag_manager/drag_manager.gd | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/addons/block_code/drag_manager/drag_manager.gd b/addons/block_code/drag_manager/drag_manager.gd index aa7697be..97c71a4f 100644 --- a/addons/block_code/drag_manager/drag_manager.gd +++ b/addons/block_code/drag_manager/drag_manager.gd @@ -22,7 +22,9 @@ class Drag: get: return action set(value): - action = value + if action != value: + action = value + _update_action_hint() var target_snap_point: SnapPoint: get: @@ -122,6 +124,13 @@ class Drag: var from_global: Vector2 = _block.global_position return from_global.distance_to(snap_point.global_position) + func _update_action_hint(): + match action: + DragAction.REMOVE: + _block.modulate = Color(1.0, 1.0, 1.0, 0.5) + _: + _block.modulate = Color.WHITE + func _update_preview(): if _preview_block: _preview_block.queue_free() From 105247c9f01c5b52f973ba90e4cdeba65839d919 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 24 Jun 2024 17:52:26 -0700 Subject: [PATCH 5/6] Remove block.on_canvas Instead, check if a block is on the canvas using `Node.is_ancestor_of`. This avoids needing to keep the property in sync between multiple places. --- addons/block_code/drag_manager/drag_manager.gd | 3 +-- addons/block_code/ui/block_canvas/block_canvas.gd | 2 -- addons/block_code/ui/blocks/block/block.gd | 2 -- addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/addons/block_code/drag_manager/drag_manager.gd b/addons/block_code/drag_manager/drag_manager.gd index 97c71a4f..41f3b0e0 100644 --- a/addons/block_code/drag_manager/drag_manager.gd +++ b/addons/block_code/drag_manager/drag_manager.gd @@ -42,7 +42,6 @@ class Drag: assert(block.get_parent() == null) add_child(block) - block.on_canvas = false block.position = -offset _block = block @@ -92,7 +91,7 @@ class Drag: push_error("Warning: snap point %s does not reference its parent block." % _snap_point) return false - if not _snap_point.block.on_canvas: + if not _block_canvas.is_ancestor_of(_snap_point): # We only snap to blocks on the canvas: return false diff --git a/addons/block_code/ui/block_canvas/block_canvas.gd b/addons/block_code/ui/block_canvas/block_canvas.gd index 4ea0f278..b3e0857d 100644 --- a/addons/block_code/ui/block_canvas/block_canvas.gd +++ b/addons/block_code/ui/block_canvas/block_canvas.gd @@ -32,7 +32,6 @@ func _populate_block_scenes_by_class(): func add_block(block: Block, position: Vector2 = Vector2.ZERO) -> void: block.position = position block.position.y += _window_scroll.scroll_vertical - block.on_canvas = true _window.add_child(block) _window.custom_minimum_size.y = max(block.position.y + EXTEND_MARGIN, _window.custom_minimum_size.y) @@ -83,7 +82,6 @@ func load_tree(parent: Node, node: SerializedBlockTreeNode): for prop_pair in node.serialized_block.serialized_props: scene.set(prop_pair[0], prop_pair[1]) - scene.on_canvas = true parent.add_child(scene) var scene_block: Block = scene as Block diff --git a/addons/block_code/ui/blocks/block/block.gd b/addons/block_code/ui/blocks/block/block.gd index 79ec85b4..51e021b9 100644 --- a/addons/block_code/ui/blocks/block/block.gd +++ b/addons/block_code/ui/blocks/block/block.gd @@ -23,8 +23,6 @@ signal modified ## The next block in the line of execution (can be null if end) @export var bottom_snap_path: NodePath -var on_canvas: bool = false - var bottom_snap: SnapPoint diff --git a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd index 21d31223..2c7fb2b6 100644 --- a/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd +++ b/addons/block_code/ui/blocks/utilities/snap_point/snap_point.gd @@ -35,7 +35,6 @@ func set_snapped_block(snapped_block: Block) -> Block: if snapped_block: add_child(snapped_block) - snapped_block.on_canvas = block.on_canvas if snapped_block and orphaned_block: var last_snap = _get_last_snap(snapped_block) From 29ef166115c613ed1186bed6a002b27499c28d78 Mon Sep 17 00:00:00 2001 From: Dylan McCall Date: Mon, 24 Jun 2024 21:49:08 -0700 Subject: [PATCH 6/6] fixup! Use a control to manage each drag and drop interaction --- addons/block_code/drag_manager/drag_manager.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/block_code/drag_manager/drag_manager.gd b/addons/block_code/drag_manager/drag_manager.gd index 41f3b0e0..9b86eba0 100644 --- a/addons/block_code/drag_manager/drag_manager.gd +++ b/addons/block_code/drag_manager/drag_manager.gd @@ -59,7 +59,7 @@ class Drag: func _remove_block(): target_snap_point = null - _block.g() + _block.queue_free() func _place_block(): var canvas_rect: Rect2 = _block_canvas.get_global_rect()