From 0b1c726c2edc99d028713b5400ddfc067060c5cb Mon Sep 17 00:00:00 2001 From: Tiger Jove Date: Fri, 16 Jan 2026 23:55:24 +0100 Subject: [PATCH] refactor: sticky reclaim state unified into fewer functions --- src/logic-scenes/board/card-board.gd | 150 +++++++------------- src/logic-scenes/board/sticky_note_panel.gd | 78 +++++----- 2 files changed, 88 insertions(+), 140 deletions(-) diff --git a/src/logic-scenes/board/card-board.gd b/src/logic-scenes/board/card-board.gd index a8152df..5dbec4d 100644 --- a/src/logic-scenes/board/card-board.gd +++ b/src/logic-scenes/board/card-board.gd @@ -206,7 +206,7 @@ func populate_board(card_names: Array[StringName]): # marking the first card as random picks new_card.picked_random = new_card.name == card_names[1] for new_sticky_note: StickyNote in all_new["sticky_notes"]: # spawning a sticky note - add_sticky_note(new_sticky_note, false) + reclaim_sticky_to_panel(new_sticky_note, false) # marking the first sticky as random picks new_sticky_note.picked_random = new_sticky_note.name == card_names[3] @@ -265,13 +265,39 @@ func add_card(card: Card, re_parent:bool = true): card.set_owner(self) card.is_dragable = true -func add_sticky_note(sticky: StickyNote, re_parent:bool = true): - var new_panel := StickyNotePanel.new() - sticky_note_container.add_child(new_panel, true, Node.INTERNAL_MODE_DISABLED) - #WARNING this for some reason would break the tweens - new_panel.set_owner(self) - sticky.current_handle = self - new_panel.attatch_sticky_note(sticky, self, false, re_parent) +## Unified function to reclaim any sticky note to a panel +## Handles ALL scenarios: initial setup, drag-drop, exchanges, keyboard navigation +func reclaim_sticky_to_panel(sticky: StickyNote, animate: bool = true, prefer_panel_index: int = -1) -> void: + # Find or create target panel + var target_panel: StickyNotePanel = null + + # Try preferred panel first (for exchanges) + if prefer_panel_index >= 0 and prefer_panel_index < sticky_note_container.get_child_count(): + var panel = sticky_note_container.get_child(prefer_panel_index) + if panel is StickyNotePanel and panel.is_empty(): + target_panel = panel + + # Find any empty panel + if not target_panel: + for panel in sticky_note_container.get_children(): + if panel is StickyNotePanel and panel.is_empty(): + target_panel = panel + break + + # Create new panel if needed + if not target_panel: + target_panel = StickyNotePanel.new() + sticky_note_container.add_child(target_panel, true, Node.INTERNAL_MODE_DISABLED) + target_panel.set_owner(self) + + # Attach sticky to panel (handles all state setup and animation) + target_panel.attatch_sticky_note(sticky, self, animate) + + # Clean up other empty panels + if animate: # Only clean up during interactive use, not initial setup + for panel in sticky_note_container.get_children(): + if panel is StickyNotePanel and panel != target_panel: + panel.clear_if_empty() # Checks if a Node is currently inside the dropzone func is_in_dropzone(to_check: Node) -> bool: @@ -316,13 +342,17 @@ func _end_drag(draggable: Draggable) -> void: # Handle exchange result (sticky swapped with card's sticky) if result == Draggable.DropResult.EXCHANGED: - _handle_sticky_exchange(draggable, drop_target) + var old_sticky = (drop_target as Card).get_last_exchanged_sticky() + if old_sticky: + # Return exchanged sticky to panel where new one came from (if applicable) + var prefer_panel = current_sticky_note_id if (draggable as StickyNote)._came_from_panel else -1 + reclaim_sticky_to_panel(old_sticky, true, prefer_panel) # If sticky was dropped on board (not card), reclaim it to panel with animation elif result == Draggable.DropResult.ACCEPTED and draggable is StickyNote and drop_target == self: - _reclaim_sticky_to_panel(draggable) + reclaim_sticky_to_panel(draggable, true) elif draggable is StickyNote and not is_in_dropzone(draggable): - # Sticky dropped in panel area but no empty panel found - reclaim to panel - _reclaim_sticky_to_panel(draggable) + # Sticky dropped in panel area - reclaim to panel with animation + reclaim_sticky_to_panel(draggable, true) else: # Fallback: use default board drop (for cards) handle_drop(draggable) @@ -337,85 +367,6 @@ func _end_drag(draggable: Draggable) -> void: check_board_comnpletion() -## Handles the exchange when a sticky is dropped on a card that already has one -## The exchanged sticky always goes to the sticky_note_container (panel zone) -func _handle_sticky_exchange(new_sticky: StickyNote, card: Card) -> void: - var old_sticky = card.get_last_exchanged_sticky() - - if not old_sticky: - push_warning("CardBoard: Exchange occurred but no sticky returned") - return - - # Reset visual state for old sticky - old_sticky.rotation = 0.0 - old_sticky.scale = Vector2.ONE - old_sticky.z_index = 0 - - # Exchanged sticky always goes to sticky_note_container with smooth animation - if new_sticky._came_from_panel and sticky_note_container.get_child_count() > 0: - # New sticky came from panel - return old sticky to that panel (swap positions) - var target_panel = sticky_note_container.get_child(current_sticky_note_id) - old_sticky.reparent(dropzone) - old_sticky.on_board = true - old_sticky.attached_to = dropzone.get_parent() # Detach from card, attach to board temporarily - target_panel.attached_sticky_note = old_sticky - # Use reclaim to smoothly animate the sticky back to the panel - target_panel.reclaim_sticky_note() - else: - # New sticky was loose - reclaim old sticky to new panel with animation - _reclaim_sticky_to_panel(old_sticky) - - # Clean up empty panel if the new sticky came from one - if new_sticky._came_from_panel and sticky_note_container.get_child_count() > 0: - sticky_note_container.get_child(current_sticky_note_id).clear_if_empty() - - -## Smoothly reclaims a sticky note to a panel (creates one if needed) -func _reclaim_sticky_to_panel(sticky: StickyNote) -> void: - var target_panel: StickyNotePanel = null - - # Try to find or create an appropriate panel - if sticky._came_from_panel and current_sticky_note_id < sticky_note_container.get_child_count(): - # Try to use the panel the sticky came from - var original_panel = sticky_note_container.get_child(current_sticky_note_id) - if original_panel is StickyNotePanel and original_panel.is_empty(): - target_panel = original_panel - - # If no reusable panel, find any empty one - if not target_panel: - for panel in sticky_note_container.get_children(): - if panel is StickyNotePanel and panel.is_empty(): - target_panel = panel - break - - # Create new panel if needed - if not target_panel: - target_panel = StickyNotePanel.new() - sticky_note_container.add_child(target_panel, true, Node.INTERNAL_MODE_DISABLED) - target_panel.set_owner(self) - - # Ensure sticky is in dropzone temporarily (for smooth animation from board to panel) - if sticky.get_parent() != dropzone: - sticky.reparent(dropzone) - sticky.on_board = true - sticky.attached_to = self - sticky.current_handle = self - - # Reset visual state - sticky.rotation = 0.0 - sticky.scale = Vector2.ONE - sticky.z_index = 0 - - # Set panel reference and trigger smooth reclaim animation - target_panel.attached_sticky_note = sticky - target_panel.reclaim_sticky_note() - - # Clean up other empty panels - for panel in sticky_note_container.get_children(): - if panel is StickyNotePanel and panel != target_panel: - panel.clear_if_empty() - - ## Updates focus and navigation state after a drop func _update_focus_after_drop(draggable: Draggable) -> void: # Update focus based on where the item ended up @@ -427,7 +378,10 @@ func _update_focus_after_drop(draggable: Draggable) -> void: func _return_sticky_notes_to_panels() -> void: if not (current_context == ASSIGN and focus_stickies): return #FIXME this is an early return to prevent race conditions. Check if it is save to be removed. for panel:StickyNotePanel in sticky_note_container.get_children(): - panel.reclaim_sticky_note() + if panel is StickyNotePanel and panel.attached_sticky_note: + # Reclaim if sticky is not already in the panel + if panel.attached_sticky_note.get_parent() != panel: + panel.attatch_sticky_note(panel.attached_sticky_note, self, true) for node in dropzone.get_children(): if node is StickyNote: @@ -633,7 +587,9 @@ func _input(event) -> void: if current_context == NAVIGATE: focus_stickies = true elif current_context == ASSIGN: - sticky_note_container.get_children()[current_sticky_note_id].reclaim_sticky_note() + var panel = sticky_note_container.get_children()[current_sticky_note_id] + if panel is StickyNotePanel and panel.attached_sticky_note: + reclaim_sticky_to_panel(panel.attached_sticky_note, true, current_sticky_note_id) current_context = NAVIGATE get_viewport().set_input_as_handled() @@ -682,14 +638,14 @@ func _input(event) -> void: current_dropzone_id = find_first_free_card() else: if currently_active_node is StickyNote: - add_sticky_note(currently_active_node) + reclaim_sticky_to_panel(currently_active_node, true) current_sticky_note_id = sticky_note_container.get_child_count()-1 current_context = ASSIGN focus_stickies = false if currently_active_node is Card: if currently_active_node.has_sticky_note_attached(): currently_active_node = currently_active_node.remove_sticky_note() - add_sticky_note(currently_active_node) + reclaim_sticky_to_panel(currently_active_node, true) current_sticky_note_id = sticky_note_container.get_child_count()-1 focus_stickies = true else: @@ -847,7 +803,7 @@ func initialise_from_save(savegame: SaveGame) -> void: for sticky: StickyNote in card_pile["sticky_notes"]: # Check if sticky is in panel if savegame.board_in_panel.has(sticky.name): - add_sticky_note(sticky, false) + reclaim_sticky_to_panel(sticky, false) print_debug(" Sticky '%s' added to panel" % sticky.name) # Check if sticky is attached to a card @@ -862,7 +818,7 @@ func initialise_from_save(savegame: SaveGame) -> void: print_debug(" Sticky '%s' attached to card '%s'" % [sticky.name, card_name]) else: push_warning("CardBoard: Sticky '%s' attached to non-existent card '%s', adding to panel" % [sticky.name, card_name]) - add_sticky_note(sticky, false) + reclaim_sticky_to_panel(sticky, false) # Sticky is loose on board else: diff --git a/src/logic-scenes/board/sticky_note_panel.gd b/src/logic-scenes/board/sticky_note_panel.gd index e362239..5544ef0 100644 --- a/src/logic-scenes/board/sticky_note_panel.gd +++ b/src/logic-scenes/board/sticky_note_panel.gd @@ -18,35 +18,50 @@ 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, tween:bool = true, re_parent:bool = true): +func attatch_sticky_note(attatchment: StickyNote, custom_owner: Node, animate:bool = true): is_attatching = true - attatchment.on_board = false attached_sticky_note = attatchment - attatchment.attached_to = null - if tween: - await get_tree().process_frame + 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) - var target_post := get_global_transform().origin+ancor_position - for panel: StickyNotePanel in get_parent().get_children(): - if panel.attached_sticky_note == attatchment and panel.get_index() < get_index(): - target_post = get_global_transform().origin+ancor_position - Vector2(0, minimum_size.y) - attatchment.tween_transform_to(Transform2D(0, target_post)) - await attatchment.transform_tween_finished - await get_tree().process_frame - attatchment.reparent(self) - attatchment.position = ancor_position else: custom_minimum_size = minimum_size - if re_parent: + + # 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 - attatchment.owner = custom_owner - attatchment.attached_to = self - attatchment.current_handle = custom_owner var is_gapped: bool = false @@ -69,29 +84,6 @@ func collapse_gap(): var height_tween: Tween = create_tween() height_tween.tween_property(self, "custom_minimum_size", minimum_size, 0.1) -func reclaim_sticky_note() -> bool: - # Don't reclaim if sticky is already attached to this panel (prevents double reclaim) - if is_empty() and attached_sticky_note.attached_to != self and attached_sticky_note.attached_to is not Card: - is_attatching = true - attached_sticky_note.on_board = false - - attached_sticky_note.z_index = 125 # Make sure it's on top of all other stickies' - # Reparent while keeping world position (global transform) - attached_sticky_note.reparent(self, true) - attached_sticky_note.attached_to = self - attached_sticky_note.owner = self.owner - - # Tween from current position to target 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(attached_sticky_note, "position", ancor_position, 0.7) - await tween.finished - - attached_sticky_note.z_index = 0 - - is_attatching = false - return true - return false - var invalid: bool = false func clear_if_empty(): if !is_empty(): return @@ -122,8 +114,8 @@ func handle_drop(draggable: StickyNote) -> int: if not can_accept_drop(draggable): return Draggable.DropResult.REJECTED - # Attach sticky to this panel - attatch_sticky_note(draggable, owner, true, true) + # 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():