fix: various board edge conditions
This commit is contained in:
parent
f2ab1d7689
commit
de44d4aea7
|
|
@ -137,6 +137,11 @@ func _on_board_focused() -> void:
|
||||||
visible = true
|
visible = true
|
||||||
if is_node_ready():
|
if is_node_ready():
|
||||||
process_mode = Node.PROCESS_MODE_INHERIT
|
process_mode = Node.PROCESS_MODE_INHERIT
|
||||||
|
|
||||||
|
# Check board state and give lore feedback when presented
|
||||||
|
if is_board_complete():
|
||||||
|
board_was_completed = true
|
||||||
|
give_lore_feedback()
|
||||||
|
|
||||||
|
|
||||||
## Called when board loses focus
|
## Called when board loses focus
|
||||||
|
|
@ -204,91 +209,99 @@ func is_in_dropzone(to_check: Node) -> bool:
|
||||||
|
|
||||||
# Called by notes when a mouse event needs handling
|
# Called by notes when a mouse event needs handling
|
||||||
func handle_mouse_button(input: InputEventMouseButton, to_handle = currently_active_node) -> void:
|
func handle_mouse_button(input: InputEventMouseButton, to_handle = currently_active_node) -> void:
|
||||||
|
# Prevent dragging multiple nodes at once
|
||||||
# Makes sure that only the same area is dragged.
|
|
||||||
# Otherwise overlapping areas are dragged at the same time.
|
|
||||||
if current_context == DRAG and to_handle != currently_active_node:
|
if current_context == DRAG and to_handle != currently_active_node:
|
||||||
return
|
return
|
||||||
|
|
||||||
if input.button_index == MOUSE_BUTTON_MASK_LEFT and input.pressed:
|
# === DRAG START ===
|
||||||
currently_active_node = to_handle
|
if input.button_index == MOUSE_BUTTON_LEFT and input.pressed:
|
||||||
to_handle.is_dragged = true
|
_start_drag(to_handle)
|
||||||
if to_handle is StickyNote:
|
return
|
||||||
if not to_handle.on_board:
|
|
||||||
to_handle.reparent(dropzone)
|
# === DRAG END ===
|
||||||
to_handle.on_board = true
|
if input.button_index == MOUSE_BUTTON_LEFT and not input.pressed:
|
||||||
to_handle.attached_to = self
|
_end_drag(to_handle)
|
||||||
current_context = DRAG
|
return
|
||||||
|
|
||||||
|
|
||||||
# when Drag stops ...
|
## Starts a drag operation for the given draggable
|
||||||
if input.button_index == MOUSE_BUTTON_MASK_LEFT and not input.pressed:
|
func _start_drag(draggable: Draggable) -> void:
|
||||||
to_handle.is_dragged = false
|
currently_active_node = draggable
|
||||||
if to_handle is StickyNote:
|
current_context = DRAG
|
||||||
if is_in_dropzone(to_handle):
|
|
||||||
if to_handle.has_overlapping_areas():
|
var mouse_offset = get_viewport().get_mouse_position() - draggable.global_position
|
||||||
for area in to_handle.get_overlapping_areas():
|
draggable.start_drag(mouse_offset)
|
||||||
if area is Card:
|
|
||||||
focus_stickies = false
|
|
||||||
if area.has_sticky_note_attached():
|
|
||||||
to_handle = area.exchange_sticky_note_with(to_handle)
|
|
||||||
to_handle.reparent(dropzone)
|
|
||||||
to_handle.on_board = true
|
|
||||||
if sticky_note_container.get_child_count() > 0:
|
|
||||||
sticky_note_container.get_child(current_sticky_note_id).attached_sticky_note = to_handle
|
|
||||||
to_handle.attached_to = sticky_note_container.get_child(current_sticky_note_id)
|
|
||||||
else:
|
|
||||||
var new_panel = StickyNotePanel.new()
|
|
||||||
sticky_note_container.add_child(new_panel, true, Node.INTERNAL_MODE_DISABLED)
|
|
||||||
new_panel.owner = self
|
|
||||||
new_panel.attatch_sticky_note(to_handle, self, false)
|
|
||||||
current_sticky_note_id = 0
|
|
||||||
to_handle.reset_drag()
|
|
||||||
current_context = NAVIGATE
|
|
||||||
_return_sticky_notes_to_panels()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
area.attach_sticky_note(to_handle)
|
|
||||||
to_handle.z_index = 0
|
|
||||||
if sticky_note_container.get_child_count() > 0:
|
|
||||||
sticky_note_container.get_child(current_sticky_note_id).clear_if_empty()
|
|
||||||
current_context = NAVIGATE
|
|
||||||
check_board_comnpletion()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
var i: int = 0
|
|
||||||
for panel: StickyNotePanel in sticky_note_container.get_children():
|
|
||||||
i += 1
|
|
||||||
if panel.is_empty:
|
|
||||||
if panel.get_global_rect().intersects(Rect2(to_handle.global_position - Vector2(to_handle.diameter/2, 10), Vector2(to_handle.diameter/2, 10))):
|
|
||||||
panel.attatch_sticky_note(to_handle, self)
|
|
||||||
elif panel.is_gapped or i == sticky_note_container.get_child_count():
|
|
||||||
panel.collapse_gap()
|
|
||||||
var new_panel = StickyNotePanel.new()
|
|
||||||
sticky_note_container.add_child(new_panel)
|
|
||||||
sticky_note_container.move_child(new_panel, i)
|
|
||||||
new_panel.attatch_sticky_note(to_handle, self)
|
|
||||||
new_panel.owner = self
|
|
||||||
panel.clear_if_empty()
|
|
||||||
_return_sticky_notes_to_panels()
|
|
||||||
current_context = NAVIGATE
|
|
||||||
return
|
|
||||||
|
|
||||||
## Dropping Cards and Sticky Notes not causing a return condition above.
|
|
||||||
if not (to_handle is StickyNote and to_handle.is_sticky_note_attached()):
|
## Ends a drag operation and handles the drop
|
||||||
if to_handle.get_parent() is Card:
|
func _end_drag(draggable: Draggable) -> void:
|
||||||
insert_area(to_handle.get_parent().remove_sticky_note(), to_handle)
|
draggable.end_drag()
|
||||||
else:
|
|
||||||
insert_area(dropzone, to_handle)
|
# Let draggable find its own drop target
|
||||||
current_context = NAVIGATE
|
var drop_target = draggable.find_drop_target()
|
||||||
|
|
||||||
|
# Execute the drop
|
||||||
|
if drop_target and Draggable.is_drop_target(drop_target):
|
||||||
|
var result = drop_target.handle_drop(draggable)
|
||||||
|
|
||||||
|
# Handle exchange result (sticky swapped with card's sticky)
|
||||||
|
if result == Draggable.DropResult.EXCHANGED:
|
||||||
|
_handle_sticky_exchange(draggable, drop_target)
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
# Fallback: use default board drop
|
||||||
|
handle_drop(draggable)
|
||||||
|
|
||||||
|
# Cleanup and state update
|
||||||
|
_return_sticky_notes_to_panels()
|
||||||
|
current_context = NAVIGATE
|
||||||
|
_update_focus_after_drop(draggable)
|
||||||
|
|
||||||
|
# Check win condition if sticky was attached to card
|
||||||
|
if draggable is StickyNote and draggable.is_sticky_note_attached():
|
||||||
|
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 = old_sticky.base_rotation
|
||||||
|
old_sticky.scale = old_sticky.base_scale
|
||||||
|
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
|
||||||
|
if draggable is Card or (draggable is StickyNote and draggable.on_board):
|
||||||
focus_stickies = false
|
focus_stickies = false
|
||||||
current_dropzone_id = dropzone.get_children().find(to_handle)
|
current_dropzone_id = dropzone.get_children().find(draggable)
|
||||||
if to_handle is StickyNote:
|
|
||||||
to_handle.rotation = to_handle.base_rotation
|
|
||||||
to_handle.scale = to_handle.base_scale
|
|
||||||
|
|
||||||
if input.is_action_pressed("mouse_right") and current_context == DRAG:
|
|
||||||
to_handle.reset_drag()
|
|
||||||
|
|
||||||
|
|
||||||
func _return_sticky_notes_to_panels() -> void:
|
func _return_sticky_notes_to_panels() -> void:
|
||||||
|
|
@ -394,6 +407,35 @@ func insert_area(parent: Control, node: Area2D):
|
||||||
node.attached_to = self
|
node.attached_to = self
|
||||||
node.is_dragable = true
|
node.is_dragable = true
|
||||||
|
|
||||||
|
# === DROP TARGET PATTERN IMPLEMENTATION ===
|
||||||
|
|
||||||
|
## Checks if this board can accept the given draggable (always true for board)
|
||||||
|
func can_accept_drop(draggable: Draggable) -> bool:
|
||||||
|
return draggable is Card or draggable is StickyNote
|
||||||
|
|
||||||
|
## Handles dropping a draggable onto the board (into the dropzone)
|
||||||
|
func handle_drop(draggable: Draggable) -> int:
|
||||||
|
if not can_accept_drop(draggable):
|
||||||
|
return Draggable.DropResult.REJECTED
|
||||||
|
|
||||||
|
if draggable is StickyNote:
|
||||||
|
# Handle sticky note drop
|
||||||
|
var sticky = draggable as StickyNote
|
||||||
|
insert_area(dropzone, sticky)
|
||||||
|
sticky.attached_to = self
|
||||||
|
sticky.on_board = true
|
||||||
|
sticky.is_dragable = true
|
||||||
|
# Reset visual state
|
||||||
|
sticky.rotation = sticky.base_rotation
|
||||||
|
sticky.scale = sticky.base_scale
|
||||||
|
elif draggable is Card:
|
||||||
|
# Handle card drop
|
||||||
|
insert_area(dropzone, draggable)
|
||||||
|
draggable.is_dragable = true
|
||||||
|
|
||||||
|
return Draggable.DropResult.ACCEPTED
|
||||||
|
|
||||||
|
|
||||||
# Takes the inputs for control inputs
|
# Takes the inputs for control inputs
|
||||||
func _input(event) -> void:
|
func _input(event) -> void:
|
||||||
|
|
||||||
|
|
@ -630,21 +672,25 @@ func initialise_from_save(savegame: SaveGame) -> void:
|
||||||
# Add all cards
|
# Add all cards
|
||||||
print_debug(" Loading %d cards..." % card_pile["cards"].size())
|
print_debug(" Loading %d cards..." % card_pile["cards"].size())
|
||||||
for card: Card in card_pile["cards"]:
|
for card: Card in card_pile["cards"]:
|
||||||
# Set position BEFORE adding to scene tree to avoid reparent position issues
|
# Determine target position (will be set after adding to scene)
|
||||||
|
var target_position: Vector2
|
||||||
if savegame.board_positions.has(card.name):
|
if savegame.board_positions.has(card.name):
|
||||||
card.position = savegame.board_positions[card.name]
|
target_position = savegame.board_positions[card.name]
|
||||||
print_debug(" Card '%s' at %s" % [card.name, card.position])
|
print_debug(" Card '%s' loading at %s" % [card.name, target_position])
|
||||||
else:
|
else:
|
||||||
card.position = _generate_random_position()
|
target_position = _generate_random_position()
|
||||||
print_debug(" Card '%s' - generated random position: %s" % [card.name, card.position])
|
print_debug(" Card '%s' - generated random position: %s" % [card.name, target_position])
|
||||||
|
|
||||||
|
# Add to board first
|
||||||
add_child(card)
|
add_child(card)
|
||||||
insert_area(dropzone, card)
|
|
||||||
|
|
||||||
card.set_owner(self)
|
card.set_owner(self)
|
||||||
card.is_dragable = true
|
card.is_dragable = true
|
||||||
cards_by_name[card.name] = card
|
cards_by_name[card.name] = card
|
||||||
card.picked_random = savegame.board_randoms.has(card.card_id)
|
card.picked_random = savegame.board_randoms.has(card.card_id)
|
||||||
|
|
||||||
|
# Move to dropzone and set position (position must be set after adding to scene)
|
||||||
|
insert_area(dropzone, card)
|
||||||
|
card.position = target_position
|
||||||
|
|
||||||
# Add all sticky notes
|
# Add all sticky notes
|
||||||
print_debug(" Loading %d stickies..." % card_pile["sticky_notes"].size())
|
print_debug(" Loading %d stickies..." % card_pile["sticky_notes"].size())
|
||||||
|
|
@ -670,26 +716,32 @@ func initialise_from_save(savegame: SaveGame) -> void:
|
||||||
|
|
||||||
# Sticky is loose on board
|
# Sticky is loose on board
|
||||||
else:
|
else:
|
||||||
# Set position BEFORE adding to scene tree to avoid reparent position issues
|
# Determine target position (will be set after adding to scene)
|
||||||
|
var target_position: Vector2
|
||||||
if savegame.board_positions.has(sticky.name):
|
if savegame.board_positions.has(sticky.name):
|
||||||
sticky.position = savegame.board_positions[sticky.name]
|
target_position = savegame.board_positions[sticky.name]
|
||||||
print_debug(" Loose sticky '%s' at %s" % [sticky.name, sticky.position])
|
print_debug(" Loose sticky '%s' loading at %s" % [sticky.name, target_position])
|
||||||
else:
|
else:
|
||||||
sticky.position = _generate_random_position()
|
target_position = _generate_random_position()
|
||||||
print_debug(" Loose sticky '%s' - generated random position: %s" % [sticky.name, sticky.position])
|
print_debug(" Loose sticky '%s' - generated random position: %s" % [sticky.name, target_position])
|
||||||
|
|
||||||
|
# Add to board first
|
||||||
add_child(sticky)
|
add_child(sticky)
|
||||||
insert_area(dropzone, sticky)
|
|
||||||
|
|
||||||
sticky.set_owner(self)
|
sticky.set_owner(self)
|
||||||
sticky.current_handle = self # Required for input handling
|
sticky.current_handle = self # Required for input handling
|
||||||
sticky.on_board = true
|
sticky.on_board = true
|
||||||
sticky.attached_to = self
|
sticky.attached_to = self
|
||||||
sticky.is_dragable = true
|
sticky.is_dragable = true
|
||||||
|
|
||||||
|
# Move to dropzone and set position (position must be set after adding to scene)
|
||||||
|
insert_area(dropzone, sticky)
|
||||||
|
sticky.position = target_position
|
||||||
|
|
||||||
sticky.picked_random = savegame.board_randoms.has(sticky.sticky_id)
|
sticky.picked_random = savegame.board_randoms.has(sticky.sticky_id)
|
||||||
|
|
||||||
print_debug("CardBoard: Load complete!")
|
print_debug("CardBoard: Load complete!")
|
||||||
|
|
||||||
|
# Note: Lore feedback will be triggered when board is presented (in play())
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -235,8 +235,7 @@ func _on_input_event(_viewport, event, _shape_idx):
|
||||||
|
|
||||||
func _move_card():
|
func _move_card():
|
||||||
if is_dragged:
|
if is_dragged:
|
||||||
position = get_viewport().get_mouse_position() - mouse_offset
|
update_drag_position(get_viewport().get_mouse_position())
|
||||||
confine_to_screen()
|
|
||||||
|
|
||||||
func has_sticky_note_attached() -> bool:
|
func has_sticky_note_attached() -> bool:
|
||||||
return get_attached_sticky_note() != null
|
return get_attached_sticky_note() != null
|
||||||
|
|
@ -296,3 +295,49 @@ func reclaim_sticky_note():
|
||||||
await current_sticky_note.transform_tween_finished
|
await current_sticky_note.transform_tween_finished
|
||||||
current_sticky_note.reparent(self)
|
current_sticky_note.reparent(self)
|
||||||
current_sticky_note.owner = self.owner
|
current_sticky_note.owner = self.owner
|
||||||
|
|
||||||
|
|
||||||
|
# === DROP TARGET PATTERN IMPLEMENTATION ===
|
||||||
|
|
||||||
|
## Temporary storage for exchanged sticky during drop operation
|
||||||
|
var _last_exchanged_sticky: StickyNote = null
|
||||||
|
|
||||||
|
## Checks if this card can accept the given draggable
|
||||||
|
func can_accept_drop(draggable: Draggable) -> bool:
|
||||||
|
return draggable is StickyNote
|
||||||
|
|
||||||
|
## Handles dropping a sticky note onto this card
|
||||||
|
## Returns DropResult indicating success, rejection, or exchange
|
||||||
|
func handle_drop(draggable: StickyNote) -> int:
|
||||||
|
if not can_accept_drop(draggable):
|
||||||
|
return Draggable.DropResult.REJECTED
|
||||||
|
|
||||||
|
if has_sticky_note_attached():
|
||||||
|
# Exchange: remove current, attach new, store old for retrieval
|
||||||
|
_last_exchanged_sticky = exchange_sticky_note_with(draggable)
|
||||||
|
# Reset z_index for newly attached sticky
|
||||||
|
draggable.z_index = 0
|
||||||
|
return Draggable.DropResult.EXCHANGED
|
||||||
|
else:
|
||||||
|
# Simple attach
|
||||||
|
if attach_sticky_note(draggable):
|
||||||
|
# Reset z_index for newly attached sticky
|
||||||
|
draggable.z_index = 0
|
||||||
|
return Draggable.DropResult.ACCEPTED
|
||||||
|
else:
|
||||||
|
# Attach failed (shouldn't happen, but handle it)
|
||||||
|
return Draggable.DropResult.REJECTED
|
||||||
|
|
||||||
|
## Retrieves the sticky that was exchanged during last drop
|
||||||
|
## Clears the reference after retrieval
|
||||||
|
func get_last_exchanged_sticky() -> StickyNote:
|
||||||
|
var result = _last_exchanged_sticky
|
||||||
|
_last_exchanged_sticky = null
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# === DRAG LIFECYCLE OVERRIDES ===
|
||||||
|
|
||||||
|
## Cards always drop back to board dropzone
|
||||||
|
func find_drop_target() -> Node:
|
||||||
|
return owner if owner is CardBoard else get_parent()
|
||||||
|
|
|
||||||
|
|
@ -27,9 +27,9 @@ anchors_preset = 15
|
||||||
anchor_right = 1.0
|
anchor_right = 1.0
|
||||||
anchor_bottom = 1.0
|
anchor_bottom = 1.0
|
||||||
offset_left = -126.0
|
offset_left = -126.0
|
||||||
offset_top = -88.0
|
offset_top = -100.0
|
||||||
offset_right = 136.0
|
offset_right = 136.0
|
||||||
offset_bottom = 89.95834
|
offset_bottom = 77.95834
|
||||||
grow_horizontal = 2
|
grow_horizontal = 2
|
||||||
grow_vertical = 2
|
grow_vertical = 2
|
||||||
theme = ExtResource("3_mdi7r")
|
theme = ExtResource("3_mdi7r")
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,18 @@ extends Area2D
|
||||||
## Base class for draggable UI elements (Cards and StickyNotes)
|
## Base class for draggable UI elements (Cards and StickyNotes)
|
||||||
## Provides common dragging behavior and boundary protection
|
## Provides common dragging behavior and boundary protection
|
||||||
|
|
||||||
|
## Drop result codes for DropTarget pattern
|
||||||
|
enum DropResult {
|
||||||
|
ACCEPTED, # Drop successful, item is now owned by target
|
||||||
|
REJECTED, # Drop refused, item stays with previous owner
|
||||||
|
EXCHANGED # Swap occurred, exchanged item needs handling
|
||||||
|
}
|
||||||
|
|
||||||
|
## Static helper to check if a node implements DropTarget pattern
|
||||||
|
## DropTarget pattern requires: can_accept_drop(draggable) and handle_drop(draggable)
|
||||||
|
static func is_drop_target(node: Node) -> bool:
|
||||||
|
return node != null and node.has_method("can_accept_drop") and node.has_method("handle_drop")
|
||||||
|
|
||||||
var is_dragged: bool = false:
|
var is_dragged: bool = false:
|
||||||
set(dragged):
|
set(dragged):
|
||||||
is_dragged = dragged
|
is_dragged = dragged
|
||||||
|
|
@ -12,6 +24,38 @@ var is_dragged: bool = false:
|
||||||
## Margin from screen edges when confining to screen bounds
|
## Margin from screen edges when confining to screen bounds
|
||||||
@export var screen_margin: float = 50.0
|
@export var screen_margin: float = 50.0
|
||||||
|
|
||||||
|
## Drag state tracking
|
||||||
|
var _drag_start_position: Vector2
|
||||||
|
var _mouse_drag_offset: Vector2
|
||||||
|
var _drag_source: Node = null # Where the drag started from
|
||||||
|
|
||||||
|
## === DRAG LIFECYCLE METHODS ===
|
||||||
|
## Override these in Card and StickyNote for specific behavior
|
||||||
|
|
||||||
|
## Starts a drag operation
|
||||||
|
func start_drag(mouse_offset: Vector2) -> void:
|
||||||
|
_drag_start_position = global_position
|
||||||
|
_mouse_drag_offset = mouse_offset
|
||||||
|
_drag_source = get_parent()
|
||||||
|
is_dragged = true
|
||||||
|
|
||||||
|
## Updates position during drag (call from _process or manual update)
|
||||||
|
func update_drag_position(mouse_pos: Vector2) -> void:
|
||||||
|
global_position = mouse_pos - _mouse_drag_offset
|
||||||
|
confine_to_screen()
|
||||||
|
|
||||||
|
## Finds the best drop target for this draggable
|
||||||
|
## Override in subclasses for specific drop target logic
|
||||||
|
## Returns the node that should receive the drop, or null for no valid target
|
||||||
|
func find_drop_target() -> Node:
|
||||||
|
# Base implementation: return parent (board)
|
||||||
|
return get_parent()
|
||||||
|
|
||||||
|
## Called after drop to clean up drag state
|
||||||
|
func end_drag() -> void:
|
||||||
|
is_dragged = false
|
||||||
|
_drag_source = null
|
||||||
|
|
||||||
## Confines this draggable element to stay within screen or container bounds
|
## Confines this draggable element to stay within screen or container bounds
|
||||||
## Skip this check if a sticky note is attached to a card
|
## Skip this check if a sticky note is attached to a card
|
||||||
func confine_to_screen() -> void:
|
func confine_to_screen() -> void:
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,7 @@ _data = {
|
||||||
}
|
}
|
||||||
|
|
||||||
[node name="board" type="PanelContainer"]
|
[node name="board" type="PanelContainer"]
|
||||||
|
z_index = -100
|
||||||
material = SubResource("ShaderMaterial_ttqei")
|
material = SubResource("ShaderMaterial_ttqei")
|
||||||
clip_contents = true
|
clip_contents = true
|
||||||
anchors_preset = 15
|
anchors_preset = 15
|
||||||
|
|
|
||||||
|
|
@ -132,8 +132,7 @@ func _on_input_event(_viewport, event, _shape_idx):
|
||||||
|
|
||||||
func _move_sticky_note():
|
func _move_sticky_note():
|
||||||
if is_dragged:
|
if is_dragged:
|
||||||
global_position = get_viewport().get_mouse_position() - mouse_offset
|
update_drag_position(get_viewport().get_mouse_position())
|
||||||
confine_to_screen()
|
|
||||||
|
|
||||||
func is_sticky_note_attached() -> bool:
|
func is_sticky_note_attached() -> bool:
|
||||||
# FIXME: this breaks if attatched to is previousely freed because GODOT IS FUCKING STUPID
|
# FIXME: this breaks if attatched to is previousely freed because GODOT IS FUCKING STUPID
|
||||||
|
|
@ -155,3 +154,69 @@ func tween_transform_to(target: Transform2D):
|
||||||
await transform_tween.finished
|
await transform_tween.finished
|
||||||
transform_tween_finished.emit()
|
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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ radius = 48.0
|
||||||
height = 312.0
|
height = 312.0
|
||||||
|
|
||||||
[node name="sticky-note" type="Area2D"]
|
[node name="sticky-note" type="Area2D"]
|
||||||
|
z_index = 1
|
||||||
priority = 100
|
priority = 100
|
||||||
script = ExtResource("1_yvh5n")
|
script = ExtResource("1_yvh5n")
|
||||||
text = "card"
|
text = "card"
|
||||||
|
|
|
||||||
|
|
@ -100,3 +100,25 @@ func replace_sticky_note_with(new_sticky_note: StickyNote):
|
||||||
|
|
||||||
func is_empty() -> bool:
|
func is_empty() -> bool:
|
||||||
return get_child_count() == 0 and not is_attatching
|
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
|
||||||
|
attatch_sticky_note(draggable, owner, true, 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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue