Compare commits

...

3 Commits

6 changed files with 99 additions and 88 deletions

View File

@ -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,12 +342,19 @@ 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, true)
elif draggable is StickyNote and not is_in_dropzone(draggable):
# Sticky dropped in panel area but no empty panel found - create one
add_sticky_note(draggable)
# Sticky dropped in panel area - reclaim to panel with animation
reclaim_sticky_to_panel(draggable, true)
else:
# Fallback: use default board drop
# Fallback: use default board drop (for cards)
handle_drop(draggable)
# Cleanup and state update
@ -334,38 +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
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
target_panel.attached_sticky_note = old_sticky
old_sticky.attached_to = target_panel
target_panel.attatch_sticky_note(old_sticky, self, false, true)
else:
# New sticky was loose - create new panel for old sticky
add_sticky_note(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()
## 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
@ -377,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:
@ -583,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()
@ -632,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:
@ -797,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
@ -812,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:

View File

@ -171,10 +171,12 @@ size_flags_horizontal = 3
mouse_filter = 1
[node name="ScrollContainer" type="ScrollContainer" parent="HBoxContainer"]
clip_contents = false
layout_mode = 2
horizontal_scroll_mode = 0
[node name="VBoxContainer" type="VBoxContainer" parent="HBoxContainer/ScrollContainer"]
z_index = 120
layout_mode = 2
[node name="instructions_panel" type="PanelContainer" parent="."]

View File

@ -141,7 +141,7 @@ func is_sticky_note_in_panel() -> bool:
var transform_tween: Tween
func tween_transform_to(target: Transform2D):
func tween_transform_to(target: Transform2D, duration: float = 0.25):
# Validate position to prevent teleporting
if not is_finite(target.origin.x) or not is_finite(target.origin.y):
push_warning("StickyNote.tween_transform_to: Invalid position, skipping tween")
@ -152,7 +152,7 @@ func tween_transform_to(target: Transform2D):
transform_tween.stop()
transform_tween = create_tween()
transform_tween.tween_property(self, "transform", target, 0.25)
transform_tween.tween_property(self, "transform", target, duration)
await transform_tween.finished
transform_tween_finished.emit()

View File

@ -10,6 +10,7 @@ height = 312.0
[node name="sticky-note" type="Area2D"]
z_index = 1
collision_layer = 2
priority = 100
script = ExtResource("1_yvh5n")
text = "card"

View File

@ -18,40 +18,55 @@ 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.position = ancor_position
is_attatching = false
attatchment.owner = custom_owner
attatchment.on_board = false
attatchment.attached_to = self
attatchment.current_handle = custom_owner
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 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)
@ -69,20 +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:
if is_empty() and attached_sticky_note.attached_to != Card:
is_attatching = true
attached_sticky_note.on_board = false
attached_sticky_note.tween_transform_to(Transform2D(0, get_screen_position() + ancor_position))
await attached_sticky_note.transform_tween_finished
await get_tree().process_frame
attached_sticky_note.reparent(self)
attached_sticky_note.attached_to = self
attached_sticky_note.owner = self.owner
is_attatching = false
return true
return false
var invalid: bool = false
func clear_if_empty():
if !is_empty(): return
@ -113,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():

View File

@ -4,11 +4,12 @@
[node name="Panel" type="Panel"]
self_modulate = Color(1, 1, 1, 0)
custom_minimum_size = Vector2(400, 0)
custom_minimum_size = Vector2(400, 100)
offset_right = 400.0
offset_bottom = 120.0
mouse_filter = 1
script = ExtResource("1_1dtc4")
[node name="sticky-note_anchor" type="Node2D" parent="."]
z_index = 110
position = Vector2(105, 57)