Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context menu, delete, duplicate, and pin function #293

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
150 changes: 130 additions & 20 deletions addons/block_code/ui/blocks/block/block.gd
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,43 @@ signal modified
## Whether the block can be deleted by the Delete key.
var can_delete: bool = true

# FIXME: Variable pinned should be saved with the scene
## Whether the block is pinned
var pinned: bool:
set(value):
if not can_delete:
return

pinned = value

if pinned:
block_pinned_container = Container.new()
block_pinned_container.mouse_filter = Control.MOUSE_FILTER_IGNORE

var block_pinned_panel := Panel.new()
block_pinned_panel.custom_minimum_size = Vector2(16, 16)
block_pinned_panel.grow_horizontal = 2
block_pinned_panel.grow_vertical = 2
block_pinned_panel.self_modulate = Color(1, 1, 1, 0.75)

var block_pinned_icon := TextureRect.new()
block_pinned_icon.texture = EditorInterface.get_editor_theme().get_icon("Pin", "EditorIcons")

block_pinned_panel.add_child(block_pinned_icon)
block_pinned_container.add_child(block_pinned_panel)
add_child(block_pinned_container)
else:
remove_child(block_pinned_container)
block_pinned_container.queue_free()

block_pinned_container.visible = pinned

var block_pinned_container: Container

var _block_extension: BlockExtension

var _block_canvas: Node

@onready var _context := BlockEditorContext.get_default()


Expand Down Expand Up @@ -163,24 +198,62 @@ func _on_block_extension_changed():

func _gui_input(event):
if event is InputEventKey:
if event.pressed and event.keycode == KEY_DELETE:
# Always accept the Delete key so it doesn't propagate to the
# BlockCode node in the scene tree.
accept_event()

if not can_delete:
return

var dialog := ConfirmationDialog.new()
var num_blocks = _count_child_blocks(self) + 1
# FIXME: Maybe this should use block_name or label, but that
# requires one to be both unique and human friendly.
if num_blocks > 1:
dialog.dialog_text = "Delete %d blocks?" % num_blocks
else:
dialog.dialog_text = "Delete block?"
dialog.confirmed.connect(remove_from_tree)
EditorInterface.popup_dialog_centered(dialog)
if event.pressed:
if event.keycode == KEY_DELETE:
# Always accept the Delete key so it doesn't propagate to the
# BlockCode node in the scene tree.
accept_event()
confirm_delete()
elif event.ctrl_pressed and not event.shift_pressed and not event.alt_pressed and not event.meta_pressed:
# Should not accept when other keys are pressed
if event.keycode == KEY_D:
accept_event()
confirm_duplicate()
elif event.keycode == KEY_P:
accept_event()
pinned = not pinned
_pin_snapped_blocks(self, pinned)


func confirm_delete():
if not can_delete:
return

var dialog := ConfirmationDialog.new()
var num_blocks = _count_child_blocks(self) + 1
# FIXME: Maybe this should use block_name or label, but that
# requires one to be both unique and human friendly.
if num_blocks > 1:
dialog.dialog_text = "Delete %d blocks?" % num_blocks
else:
dialog.dialog_text = "Delete block?"
dialog.confirmed.connect(remove_from_tree)
EditorInterface.popup_dialog_centered(dialog)


func confirm_duplicate():
if not can_delete:
return

var new_block: Block = _context.block_script.instantiate_block(definition)

var new_parent: Node = get_parent()
while not new_parent.name == "Window":
new_parent = new_parent.get_parent()

if not _block_canvas:
_block_canvas = get_parent()
while not _block_canvas.name == "BlockCanvas":
_block_canvas = _block_canvas.get_parent()

new_parent.add_child(new_block)
new_block.global_position = global_position + (Vector2(100, 50) * new_parent.scale)

_copy_snapped_blocks(self, new_block)

_block_canvas.reconnect_block.emit(new_block)

modified.emit()


func remove_from_tree():
Expand All @@ -200,7 +273,8 @@ static func get_scene_path():


func _drag_started(offset: Vector2 = Vector2.ZERO):
drag_started.emit(self, offset)
if not pinned:
drag_started.emit(self, offset)


func disconnect_signals():
Expand Down Expand Up @@ -235,6 +309,42 @@ func _count_child_blocks(node: Node) -> int:
for child in node.get_children():
if child is SnapPoint and child.has_snapped_block():
count += 1
count += _count_child_blocks(child)

if child is Container:
count += _count_child_blocks(child)

return count


func _copy_snapped_blocks(copy_from: Node, copy_to: Node):
var copy_to_child: Node
var child_index := 0
var maximum_count := copy_to.get_child_count()

for copy_from_child in copy_from.get_children():
if child_index + 1 > maximum_count:
return

copy_to_child = copy_to.get_child(child_index)
child_index += 1

if copy_from_child is SnapPoint and copy_from_child.has_snapped_block():
copy_to_child.add_child(_context.block_script.instantiate_block(copy_from_child.snapped_block.definition))
_block_canvas.reconnect_block.emit(copy_to_child.snapped_block)
elif copy_from_child.name.begins_with("ParameterInput"):
var raw_input = copy_from_child.get_raw_input()

if not raw_input is Block:
copy_to_child.set_raw_input(raw_input)

if copy_from_child is Container:
_copy_snapped_blocks(copy_from_child, copy_to_child)


func _pin_snapped_blocks(node: Node, _is_pinned: bool):
for child in node.get_children():
if child is SnapPoint and child.has_snapped_block():
child.snapped_block.pinned = _is_pinned

if child is Container:
_pin_snapped_blocks(child, _is_pinned)
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
extends Control

const Constants = preload("res://addons/block_code/ui/constants.gd")
const BlockTreeUtil = preload("res://addons/block_code/ui/block_tree_util.gd")

signal drag_started(offset: Vector2)

Expand All @@ -16,6 +17,7 @@ signal drag_started(offset: Vector2)
@export var drag_outside: bool = false

var _drag_start_position: Vector2 = Vector2.INF
var parent_block: Block


func _gui_input(event: InputEvent) -> void:
Expand All @@ -27,7 +29,7 @@ func _gui_input(event: InputEvent) -> void:

var button_event: InputEventMouseButton = event as InputEventMouseButton

if button_event.button_index != MOUSE_BUTTON_LEFT:
if button_event.button_index != MOUSE_BUTTON_LEFT and button_event.button_index != MOUSE_BUTTON_RIGHT:
return

if button_event.double_click:
Expand All @@ -37,7 +39,30 @@ func _gui_input(event: InputEvent) -> void:
elif button_event.pressed:
# Keep track of where the mouse click originated, but allow this
# event to propagate to other nodes.
_drag_start_position = event.global_position
if button_event.button_index == MOUSE_BUTTON_LEFT:
_drag_start_position = event.global_position
else:
if not parent_block:
parent_block = BlockTreeUtil.get_parent_block(self)

if parent_block and parent_block.can_delete:
# Accepts to avoid menu conflicts
accept_event()

# A new right-click menu with items
var _context_menu := PopupMenu.new()
_context_menu.add_icon_item(EditorInterface.get_editor_theme().get_icon("Duplicate", "EditorIcons"), "Duplicate")
_context_menu.add_icon_item(EditorInterface.get_editor_theme().get_icon("Info", "EditorIcons"), "Summary")
_context_menu.add_icon_item(EditorInterface.get_editor_theme().get_icon("Pin", "EditorIcons"), "Unpin" if parent_block.pinned else "Pin")
_context_menu.add_separator()
_context_menu.add_icon_item(EditorInterface.get_editor_theme().get_icon("Remove", "EditorIcons"), "Delete")
_context_menu.popup_hide.connect(_cleanup)
_context_menu.id_pressed.connect(_menu_pressed.bind(_context_menu))

_context_menu.position = DisplayServer.mouse_get_position()
add_child(_context_menu)

_context_menu.show()
else:
_drag_start_position = Vector2.INF

Expand All @@ -64,3 +89,33 @@ func _input(event: InputEvent) -> void:
get_viewport().set_input_as_handled()
drag_started.emit(_drag_start_position - motion_event.global_position)
_drag_start_position = Vector2.INF


func _menu_pressed(_index: int, _context_menu: PopupMenu):
# Getting which item was pressed and the corresponding function
var _pressed_label: String = _context_menu.get_item_text(_index)

if _pressed_label == "Duplicate":
parent_block.confirm_duplicate()
elif _pressed_label == "Unpin" or _pressed_label == "Pin":
parent_block.pinned = not parent_block.pinned
parent_block._pin_snapped_blocks(parent_block, parent_block.pinned)
elif _pressed_label == "Summary":
# TODO: Replace tooltip with full summary
var _tooltip := parent_block._make_custom_tooltip(parent_block.get_tooltip())
var _tooltip_window := Popup.new()

_tooltip_window.position = DisplayServer.mouse_get_position()
_tooltip_window.popup_hide.connect(_cleanup)
_tooltip_window.add_child(_tooltip)
add_child(_tooltip_window)

_tooltip_window.show()
elif _pressed_label == "Delete":
parent_block.confirm_delete()


func _cleanup():
for child in get_children():
remove_child(child)
child.queue_free()