class_name StickyNotePanel extends Panel var minimum_size:Vector2 = Vector2(400, 100): set(size): minimum_size = size custom_minimum_size = size var attached_sticky_note: StickyNote var ancor_position: Vector2 func _init(cstm_minimum_size: Vector2 = minimum_size, note_position: Vector2 = Vector2(105, 57)) -> void: minimum_size = cstm_minimum_size ancor_position = note_position mouse_filter = MOUSE_FILTER_PASS self_modulate = Color(1, 1, 1, 0) func _ready(): custom_minimum_size = Vector2(custom_minimum_size.x, 0) var is_attatching: bool = false func attatch_sticky_note(attatchment: StickyNote, custom_owner: Node, animate:bool = true): is_attatching = true attached_sticky_note = attatchment attatchment.current_handle = custom_owner attatchment.owner = custom_owner # Expand panel height if animate: var height_tween: Tween = create_tween() height_tween.tween_property(self, "custom_minimum_size", minimum_size, 0.1) else: custom_minimum_size = minimum_size # Position sticky if animate: await get_tree().process_frame attatchment.on_board = false attatchment.z_index = 125 # On top during animation # Reparent while keeping world position for smooth animation attatchment.reparent(self, true) attatchment.attached_to = self # Tween to anchor position in panel's coordinate space var tween := create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_BACK) tween.tween_property(attatchment, "position", ancor_position, 0.7) tween.tween_property(attatchment, "rotation", 0.0, 0.7) tween.parallel().tween_property(attatchment, "scale", Vector2.ONE, 0.7) await tween.finished attatchment.z_index = 0 else: # Immediate placement (for initial board setup) if attatchment.get_parent(): attatchment.reparent(self) else: add_child(attatchment) attatchment.on_board = false attatchment.attached_to = self attatchment.position = ancor_position attatchment.rotation = 0.0 attatchment.scale = Vector2.ONE is_attatching = false var is_gapped: bool = false func create_gap(): var self_id := get_parent().get_children().find(self) var next_id = min(self_id + 1, get_parent().get_child_count() - 1) var previous_id = max(self_id - 1, 0) if not (is_gapped or get_parent().get_child(next_id).attached_sticky_note.is_dragged or get_parent().get_child(previous_id).attached_sticky_note.is_dragged) and owner.current_context == CardBoard.DRAG: is_gapped = true var height_tween: Tween = create_tween() height_tween.tween_property(self, "custom_minimum_size", minimum_size*Vector2(1.0, 1.8), 0.1) get_parent().get_child(next_id).collapse_gap() if not get_parent().get_children().find(self) == 0: get_parent().get_child(previous_id).collapse_gap() func collapse_gap(): if is_gapped: is_gapped = false var height_tween: Tween = create_tween() height_tween.tween_property(self, "custom_minimum_size", minimum_size, 0.1) var invalid: bool = false func clear_if_empty(): if !is_empty(): return invalid = true if attached_sticky_note.attached_to == self: attached_sticky_note.attached_to = null var height_tween: Tween = create_tween() height_tween.tween_property(self, "custom_minimum_size", Vector2.ZERO, 0.3) await height_tween.finished owner.on_sticky_panel_cleared(get_parent().get_children().find(self)) self.queue_free() func replace_sticky_note_with(new_sticky_note: StickyNote): if is_empty(): attached_sticky_note = new_sticky_note func is_empty() -> bool: return get_child_count() == 0 and not is_attatching # === DROP TARGET PATTERN IMPLEMENTATION === ## Checks if this panel can accept the given draggable func can_accept_drop(draggable: Draggable) -> bool: return draggable is StickyNote and is_empty() ## Handles dropping a sticky note onto this panel func handle_drop(draggable: StickyNote) -> int: if not can_accept_drop(draggable): return Draggable.DropResult.REJECTED # Attach sticky to this panel with animation attatch_sticky_note(draggable, owner, true) # Clean up other empty panels for panel in get_parent().get_children(): if panel is StickyNotePanel and panel != self: panel.clear_if_empty() return Draggable.DropResult.ACCEPTED