@@ -41,9 +41,42 @@ signal modified
4141## Whether the block can be deleted by the Delete key.
4242var can_delete : bool = true
4343
44+ # FIXME: Variable pinned should be saved with the scene
45+ ## Whether the block is pinned
46+ var pinned : bool :
47+ set (value ):
48+ if not can_delete :
49+ return
50+
51+ pinned = value
52+
53+ if not block_pinned_container :
54+ block_pinned_container = Container .new ()
55+ block_pinned_container .mouse_filter = Control .MOUSE_FILTER_IGNORE
56+
57+ var block_pinned_panel := Panel .new ()
58+ block_pinned_panel .custom_minimum_size = Vector2 (16 , 16 )
59+ block_pinned_panel .grow_horizontal = 2
60+ block_pinned_panel .grow_vertical = 2
61+ block_pinned_panel .self_modulate = Color (1 , 1 , 1 , 0.75 )
62+
63+ var block_pinned_icon := TextureRect .new ()
64+ block_pinned_icon .texture = _icon_pin
65+
66+ block_pinned_panel .add_child (block_pinned_icon )
67+ block_pinned_container .add_child (block_pinned_panel )
68+ add_child (block_pinned_container )
69+
70+ block_pinned_container .visible = pinned
71+
72+ var block_pinned_container : Container
73+
4474var _block_extension : BlockExtension
4575
76+ var _block_canvas : Node
77+
4678@onready var _context := BlockEditorContext .get_default ()
79+ @onready var _icon_pin := EditorInterface .get_editor_theme ().get_icon ("Pin" , "EditorIcons" )
4780
4881
4982func _ready ():
@@ -163,24 +196,62 @@ func _on_block_extension_changed():
163196
164197func _gui_input (event ):
165198 if event is InputEventKey :
166- if event .pressed and event .keycode == KEY_DELETE :
167- # Always accept the Delete key so it doesn't propagate to the
168- # BlockCode node in the scene tree.
169- accept_event ()
170-
171- if not can_delete :
172- return
173-
174- var dialog := ConfirmationDialog .new ()
175- var num_blocks = _count_child_blocks (self ) + 1
176- # FIXME: Maybe this should use block_name or label, but that
177- # requires one to be both unique and human friendly.
178- if num_blocks > 1 :
179- dialog .dialog_text = "Delete %d blocks?" % num_blocks
180- else :
181- dialog .dialog_text = "Delete block?"
182- dialog .confirmed .connect (remove_from_tree )
183- EditorInterface .popup_dialog_centered (dialog )
199+ if event .pressed :
200+ if event .keycode == KEY_DELETE :
201+ # Always accept the Delete key so it doesn't propagate to the
202+ # BlockCode node in the scene tree.
203+ accept_event ()
204+ confirm_delete ()
205+ elif event .ctrl_pressed and not event .shift_pressed and not event .alt_pressed and not event .meta_pressed :
206+ # Should not accept when other keys are pressed
207+ if event .keycode == KEY_D :
208+ accept_event ()
209+ confirm_duplicate ()
210+ elif event .keycode == KEY_P :
211+ accept_event ()
212+ pinned = not pinned
213+ _pin_snapped_blocks (self , pinned )
214+
215+
216+ func confirm_delete ():
217+ if not can_delete :
218+ return
219+
220+ var dialog := ConfirmationDialog .new ()
221+ var num_blocks = _count_child_blocks (self ) + 1
222+ # FIXME: Maybe this should use block_name or label, but that
223+ # requires one to be both unique and human friendly.
224+ if num_blocks > 1 :
225+ dialog .dialog_text = "Delete %d blocks?" % num_blocks
226+ else :
227+ dialog .dialog_text = "Delete block?"
228+ dialog .confirmed .connect (remove_from_tree )
229+ EditorInterface .popup_dialog_centered (dialog )
230+
231+
232+ func confirm_duplicate ():
233+ if not can_delete :
234+ return
235+
236+ var new_block : Block = _context .block_script .instantiate_block (definition )
237+
238+ var new_parent : Node = get_parent ()
239+ while not new_parent .name == "Window" :
240+ new_parent = new_parent .get_parent ()
241+
242+ if not _block_canvas :
243+ _block_canvas = get_parent ()
244+ while not _block_canvas .name == "BlockCanvas" :
245+ _block_canvas = _block_canvas .get_parent ()
246+
247+ new_parent .add_child (new_block )
248+ new_block .global_position = global_position + (Vector2 (100 , 50 ) * new_parent .scale )
249+
250+ _copy_snapped_blocks (self , new_block )
251+
252+ _block_canvas .reconnect_block .emit (new_block )
253+
254+ modified .emit ()
184255
185256
186257func remove_from_tree ():
@@ -200,7 +271,8 @@ static func get_scene_path():
200271
201272
202273func _drag_started (offset : Vector2 = Vector2 .ZERO ):
203- drag_started .emit (self , offset )
274+ if not pinned :
275+ drag_started .emit (self , offset )
204276
205277
206278func disconnect_signals ():
@@ -235,6 +307,41 @@ func _count_child_blocks(node: Node) -> int:
235307 for child in node .get_children ():
236308 if child is SnapPoint and child .has_snapped_block ():
237309 count += 1
238- count += _count_child_blocks (child )
310+
311+ if child is Container :
312+ count += _count_child_blocks (child )
239313
240314 return count
315+
316+
317+ func _copy_snapped_blocks (copy_from : Node , copy_to : Node ):
318+ var copy_to_child : Node
319+ var child_index := 0
320+ var maximum_count := copy_to .get_child_count ()
321+
322+ for copy_from_child in copy_from .get_children ():
323+ if child_index + 1 > maximum_count :
324+ return
325+
326+ copy_to_child = copy_to .get_child (child_index )
327+ child_index += 1
328+
329+ if copy_from_child is SnapPoint and copy_from_child .has_snapped_block ():
330+ copy_to_child .add_child (_context .block_script .instantiate_block (copy_from_child .snapped_block .definition ))
331+ _block_canvas .reconnect_block .emit (copy_to_child .snapped_block )
332+ elif copy_from_child .name .begins_with ("ParameterInput" ):
333+ var raw_input = copy_from_child .get_raw_input ()
334+
335+ if not raw_input is Block :
336+ copy_to_child .set_raw_input (copy_from_child .get_raw_input ())
337+
338+ _copy_snapped_blocks (copy_from_child , copy_to_child )
339+
340+
341+ func _pin_snapped_blocks (node : Node , _is_pinned : bool ):
342+ for child in node .get_children ():
343+ if child is SnapPoint and child .has_snapped_block ():
344+ child .snapped_block .pinned = _is_pinned
345+
346+ if child is Container :
347+ _pin_snapped_blocks (child , _is_pinned )
0 commit comments