extends Draggable class_name StickyNote var sticky_id var parent_id var sibling: StickyNote var shift_tween: Tween var modulate_tween: Tween var attached_to: Node = null: set(new_attatchement): attached_to = new_attatchement # cannot be explicitly typed, as this can be both handled by picker and physics-board var current_handle: Node var position_locked: bool = false signal transform_tween_finished @onready var background_sprite: AnimatedSprite2D = %BackgroundSprite @export var text: String = "" : set (value): if is_node_ready(): _on_text_updated.call_deferred() text = value var content: Node2D var label: Label @export var picked_random: bool = false @export var shift_by: Vector2 = Vector2(-32, 0) @export_color_no_alpha var highlight_color: Color = Color(1.5, 1.5, 1.5) @export var highlighted: bool = false: set(highlight): if highlight != highlighted: highlighted = highlight if is_inside_tree() and is_node_ready(): if modulate_tween: modulate_tween.kill() if shift_tween: shift_tween.kill() if highlighted: modulate_tween = get_tree().create_tween() modulate_tween.tween_property(self, "modulate", highlight_color, 0.1) shift_tween = get_tree().create_tween() shift_tween.tween_property(content, "position", shift_by, 0.2) else: modulate_tween = get_tree().create_tween() modulate_tween.tween_property(self, "modulate", Color(1, 1, 1), 0.3) shift_tween = get_tree().create_tween() shift_tween.tween_property(content, "position", Vector2.ZERO, 0.5) else: if highlighted: modulate = Color(1, 1, 1) else: modulate = Color(1, 1, 1) @export var voice_line: AudioStream = null @export var is_dragable: bool = false @onready var base_rotation := rotation @onready var base_scale := scale var mouse_offset: Vector2 @onready var diameter := 312.0 @export_range(1.0, 10.0) var bounce_speed: float = 8 var on_board: bool = false func init(sticky_name: String = "sticky_note", card_id: StringName = "-1") -> void: name = sticky_name text = sticky_name parent_id = StringName(card_id.rsplit(".", false, 1)[0]) sticky_id = card_id func _ready() -> void: label = $Content/Label background_sprite = $Content/BackgroundSprite content = $Content _on_text_updated.call_deferred() input_event.connect(_on_input_event) mouse_entered.connect(_on_mouse_entered) mouse_exited.connect(_on_mouse_exited) area_entered.connect(_on_area_enter) area_exited.connect(_on_area_exit) func _on_text_updated(): label.text = text background_sprite.frame = text.hash() % background_sprite.sprite_frames.get_frame_count(background_sprite.animation) func _process(delta: float) -> void: if get_overlapping_areas().size() > 0 and is_dragable and on_board: for area in get_overlapping_areas(): if area is Card or area is CardCollider: if area is CardCollider: position += area.direction * delta elif not area.highlighted or self.highlighted: var diff:Vector2 = position - area.position position -= diff.normalized() * ((diff.length()-diameter)/diameter) * bounce_speed * (delta/(1.0/60)) _move_sticky_note() func _on_mouse_entered(): if not Input.is_action_pressed("mouse_left") and "handle_hover" in current_handle: current_handle.handle_hover(self) func _on_mouse_exited(): highlighted = false # Let parent card re-check hover state if this sticky is attached to it if is_sticky_note_attached() and "check_hover" in attached_to: attached_to.check_hover() func _on_area_enter(area: Area2D): # Handle sticky note panel gap creation if area is StickyNote and is_sticky_note_in_panel() and not is_dragged: attached_to.create_gap() func _on_area_exit(area: Area2D): # Handle sticky note panel gap collapse if area is StickyNote and is_sticky_note_in_panel(): attached_to.collapse_gap() func _on_input_event(_viewport, event, _shape_idx): if event is InputEventMouseButton and "handle_mouse_button" in current_handle: if (event.button_index == MOUSE_BUTTON_LEFT and event.pressed) or event.button_index == MOUSE_BUTTON_RIGHT: mouse_offset = get_viewport().get_mouse_position() - global_position current_handle.handle_mouse_button(event, self) func _move_sticky_note(): if is_dragged: update_drag_position(get_viewport().get_mouse_position()) func is_sticky_note_attached() -> bool: # FIXME: this breaks if attatched to is previousely freed because GODOT IS FUCKING STUPID return attached_to is Card func is_sticky_note_in_panel() -> bool: ## fixme ~> see above return attached_to is StickyNotePanel var transform_tween: Tween func tween_transform_to(target: Transform2D): if transform_tween and transform_tween.is_running(): transform_tween.stop() transform_tween = create_tween() transform_tween.tween_property(self, "transform", target, 0.25) await transform_tween.finished transform_tween_finished.emit() # === DRAG LIFECYCLE OVERRIDES === ## Track whether this sticky came from a panel (for exchange logic) var _came_from_panel: bool = false ## Start drag: if in panel, immediately move to board func start_drag(offset: Vector2) -> void: super.start_drag(offset) _came_from_panel = is_sticky_note_in_panel() # If attached to a card, detach it first if is_sticky_note_attached(): var card := attached_to as Card if card and card.has_method("remove_sticky_note"): card.remove_sticky_note() # If in panel, immediately reparent to board for dragging if _came_from_panel and current_handle: var board := current_handle var dropzone := board.get_node_or_null("HBoxContainer/dropzone") if dropzone: reparent(dropzone) else: reparent(board) on_board = true attached_to = board ## Find best drop target: Card > Panel > Board (in priority order) func find_drop_target() -> Node: # Priority 1: Check for overlapping cards in dropzone for area in get_overlapping_areas(): if area is Card and Draggable.is_drop_target(area): return area # Priority 2: Check if dropped outside dropzone (over panel area) if current_handle and not current_handle.is_in_dropzone(self): var target_panel := _find_nearest_panel() if target_panel: return target_panel # Priority 3: Default to board (stay loose in dropzone) return current_handle ## Find the nearest panel that can accept this sticky func _find_nearest_panel() -> StickyNotePanel: if not current_handle or not current_handle.has_node("HBoxContainer/ScrollContainer/VBoxContainer"): return null var panel_container := current_handle.get_node("HBoxContainer/ScrollContainer/VBoxContainer") var sticky_rect := Rect2(global_position - Vector2(diameter/2, 10), Vector2(diameter/2, 10)) # First pass: look for empty panels we're hovering over for panel in panel_container.get_children(): if panel is StickyNotePanel: if panel.is_empty() and panel.get_global_rect().intersects(sticky_rect): return panel # Second pass: if no empty panel found, find first empty panel for panel in panel_container.get_children(): if panel is StickyNotePanel and panel.is_empty(): return panel # No empty panels found - will need to create one (handled by board) return null