From aca35b34a06f6dd3ea948de271592e563a94a5e5 Mon Sep 17 00:00:00 2001 From: betalars Date: Sat, 6 Jun 2026 20:24:08 +0200 Subject: [PATCH] WIP: implement sideboard with scrolling and capability to accept both card and sticky drops depending on context --- src/logic-scenes/board/card-board.gd | 248 +++++++++++++------- src/logic-scenes/board/card.gd | 38 ++- src/logic-scenes/board/card.tscn | 3 +- src/logic-scenes/board/draggable.gd | 15 +- src/logic-scenes/board/physics-board.tscn | 33 ++- src/logic-scenes/board/side_board.gd | 136 +++++++++++ src/logic-scenes/board/side_board.gd.uid | 1 + src/logic-scenes/board/side_boundary.gd | 1 + src/logic-scenes/board/side_boundary.gd.uid | 1 + src/logic-scenes/board/sticky-note.gd | 14 +- src/logic-scenes/board/sticky-note.tscn | 9 +- 11 files changed, 398 insertions(+), 101 deletions(-) create mode 100644 src/logic-scenes/board/side_board.gd create mode 100644 src/logic-scenes/board/side_board.gd.uid create mode 100644 src/logic-scenes/board/side_boundary.gd create mode 100644 src/logic-scenes/board/side_boundary.gd.uid diff --git a/src/logic-scenes/board/card-board.gd b/src/logic-scenes/board/card-board.gd index 75508972..71418362 100644 --- a/src/logic-scenes/board/card-board.gd +++ b/src/logic-scenes/board/card-board.gd @@ -20,20 +20,46 @@ var cards : Array[Card] = [] var board_was_completed: bool = false -var current_context : int = NAVIGATE +var current_context : Context = Context.NAVIGATE var selection_state : SelectionState +var last_card_selected: Card +var last_note_selected: StickyNote +var selection: Draggable = null: + set(value): + if selection == value: return + + if selection: + if selection is Card: + last_card_selected = selection + elif selection is StickyNote: + last_note_selected = selection + + # Select & highlight new + if selection: selection.highlighted = false + selection = value + if selection: selection.highlighted = true + + # Are we selecting cards or stickies? + if selection is Card: + selection_state = SelectionState.CARDS + if current_context == Context.ASSIGN: + selection.preview_sticky_note(last_note_selected) + + if selection is StickyNote: + selection_state = SelectionState.STICKIES + #@onready var instructions := $instructions_panel/HBoxContainer/cards_remaining @onready var dropzone : Control = %CardZone -@onready var notezone : Control = %NoteZone +@onready var sideboard : SideBoard = %SideBoard enum SelectionState {FREE,STICKIES,CARDS} -enum {NAVIGATE, ASSIGN, DRAG} +enum Context {NAVIGATE, ASSIGN, DRAG} func _ready() -> void: - prints("card-board.gd:", "_ready()", self, "room:", State.room, owner) + prints("card-board.gd:", "_ready()", self, "room:", State.active_room, owner) super._ready() _delayed_ready.call_deferred() @@ -42,9 +68,10 @@ func _ready() -> void: # We need to wait for our room further up our parent hierarchy to actually make itself known # for interactables to do things with it, e.g. CardBoard needs to register itself func _delayed_ready() ->void: - var board_room := State.room as RoomWithBoard - assert(board_room, "CardBoard spawned in room that's not a RoomWithboard.") - board_room.card_board = self + var board_room := State.active_room as RoomWithBoard + if not get_tree().root == get_parent(): + assert(board_room, "CardBoard spawned in room that's not a RoomWithboard.") + board_room.card_board = self is_memory_board = is_memory_board @@ -75,23 +102,6 @@ var mementos_collected: int = 0: mementos_collected = mementos -var selection: Draggable = null: - set(value): - if selection == value: return - - # Select & highlight new - if selection: selection.highlighted = false - selection = value - if selection: selection.highlighted = true - - # Are we selecting cards or stickies? - if selection is Card: - selection_state = SelectionState.CARDS - - if selection is StickyNote: - selection_state = SelectionState.STICKIES - - func _navigate_next(): var candidates := _selection_candidates var index := maxi(0, candidates.find(selection)) @@ -111,34 +121,6 @@ func _smooth(current: Vector2, goal: Vector2, delta: float) -> Vector2: return (k) * current + (1.0-k) * goal -func _process(delta: float): - var zone_position := Vector2(notezone.get_screen_position().x + sticky_width / 3.0, sticky_height) - - var dragging := notes.any(func (n : Draggable): return n.is_dragged) - - if dragging: - # Y-sort the nodes, this lets us fill the gap more nicely. - notes.sort_custom(func (a:Draggable, b:Draggable): return a.global_position.y < b.global_position.y) - - for note in notes: - # Skip all dragged and already attached notes - if note.is_attached: continue - if note.is_dragged: continue - - # Magnetically move all notes to where they ought to be on screen - note.home = zone_position - zone_position.y += sticky_height - - # Only if not already in transit / animated or user holding on to one - if not dragging and not note.tween: - note.animate_home() - else: - # do adjustment with FIR filter - note.position = _smooth(note.position, note.home, delta) - note.z_index = 0 - - - func _check_completion() -> void: if is_board_complete(): board_was_completed = true @@ -148,7 +130,7 @@ func _check_completion() -> void: ## Finalizes board state before closing (ends drags, cleans up transitions) func _finalize_board_state() -> void: # End any active drag operations - if current_context == DRAG: + if current_context == Context.DRAG: _end_drag(selection) for item in notes: item.is_dragged = false @@ -157,7 +139,7 @@ func _finalize_board_state() -> void: item.is_dragged = false # Reset context to NAVIGATE - current_context = NAVIGATE + current_context = Context.NAVIGATE print("CardBoard: Board state finalized") @@ -181,8 +163,8 @@ func populate_board(names: Array[StringName]): for new_card: Card in all_new["cards"]: add_card(new_card) - for new_sticky_note: StickyNote in all_new["sticky_notes"]: - add_note(new_sticky_note) + + notes += sideboard.populate(all_new["sticky_notes"]) # FIXME: This can be made even simpler. @@ -243,14 +225,14 @@ func add_note(note: StickyNote) -> void: note.is_draggable = true func appear(): - await Main.curtain.close() + #await Main.curtain.close() show() - await Main.curtain.open() + #await Main.curtain.open() func vanish(): - await Main.curtain.close() + #await Main.curtain.close() hide() - await Main.curtain.open() + #await Main.curtain.open() # Called by notes when a mouse event needs handling @@ -265,13 +247,15 @@ func handle_mouse_button(input: InputEventMouseButton, target: Draggable) -> voi _end_drag(target) return - +var parent_before_drag: Node ## Starts a drag operation for the given draggable func _start_drag(draggable: Draggable) -> void: + parent_before_drag = draggable.get_parent() selection = draggable - current_context = DRAG + current_context = Context.DRAG var mouse_offset := get_viewport().get_mouse_position() - draggable.global_position draggable.start_drag(mouse_offset) + draggable.reparent(self) ## Ends a drag operation and handles the drop @@ -279,26 +263,30 @@ func _end_drag(draggable: Draggable) -> void: selection = draggable # Cleanup and state update - current_context = NAVIGATE + current_context = Context.NAVIGATE var destination := draggable.end_drag() + + if destination: + if destination is Card: + # If dropped on a card, attach it + if draggable is StickyNote: + destination.attach_or_exchange_note(draggable) + + elif destination is SideBoundary: + if not sideboard.accept_drop(draggable): + #FIXME remove if handle_drop and can_accept_drop is properly implemented. + destination = null + + # If dropped on board (no destination), ensure it's a child of the board + if not destination: + if draggable is StickyNote: + reclaim_sticky(draggable) - # Handle sticky note drops - if draggable is StickyNote: - var sticky := draggable as StickyNote + # Check win condition after any sticky movement + check_board_completion() - # If dropped on a card, attach it - if destination and destination is Card: - var target_card := destination as Card - target_card.attach_or_exchange_note(sticky) - - # If dropped on board (no destination), ensure it's a child of the board - elif not destination: - if sticky.is_attached: - reclaim_sticky(sticky) - - # Check win condition after any sticky movement - check_board_completion() + parent_before_drag = null func reclaim_sticky(note: StickyNote): @@ -384,8 +372,6 @@ func _nearest_hovered(candidates: Array[Draggable]) -> Draggable: return null - - func _by_spatial(a: Draggable, b: Draggable) -> bool: return a.position.x + a.position.y * 10000 > b.position.x + b.position.y * 10000 @@ -408,6 +394,7 @@ func _sort_by_positions() -> void: func can_accept_drop(draggable: Draggable) -> bool: return draggable is Card or draggable is StickyNote +# FIXME: this function seems to not be used. ## Handles dropping a draggable onto the board (into the dropzone) func handle_drop(draggable: Draggable) -> int: if not can_accept_drop(draggable): @@ -423,12 +410,49 @@ func _perform(action : StringName) -> void: # Takes the inputs for control inputs func _input(event : InputEvent) -> void: - if selection and not selection.is_dragged and event is InputEventMouseMotion and not event.is_action_pressed("mouse_left"): + # trying to not do the double check on the line below. + var is_mouse_left:= false + if event is InputEventMouseButton: + is_mouse_left = event.button_index == MOUSE_BUTTON_LEFT + + if selection and not selection.is_dragged and event is InputEventMouseMotion and not is_mouse_left: var candidate := _nearest_hovered(_sort_by_proximity_and_depth(notes)) if not candidate: candidate = _nearest_hovered(_sort_by_proximity_and_depth(cards)) selection = candidate + if event.is_action_pressed("ui_left"): + _handle_direction_input(Vector2.LEFT) + if event.is_action_pressed("ui_right"): + _handle_direction_input(Vector2.RIGHT) + if event.is_action_pressed("ui_up"): + _handle_direction_input(Vector2.UP) + if event.is_action_pressed("ui_down"): + _handle_direction_input(Vector2.DOWN) + + #TODO: make this work with the prompter once more. + if event.is_action_pressed("ui_cancel"): + get_viewport().set_input_as_handled() + closed.emit() +func _handle_direction_input(direction: Vector2): + + if current_context == Context.NAVIGATE and _assure_selection(): + try_select_nearest_card(selection.global_position, direction, (current_context != Context.ASSIGN)) + +func _assure_selection() -> bool: + if selection: + return true + else: + if selection_state == SelectionState.STICKIES or selection_state == SelectionState.FREE: + if dropzone.get_child_count() > 0: + selection = dropzone.get_child(0) + return true + ## Attempting to still select a Card if no sticky could be found. + if sideboard.get_child_count() > 0: + selection = sideboard.get_child(0) + return true + + return false ## Saves board state directly to SaveGame resource func save_to_resource(savegame: SaveGame) -> void: @@ -572,3 +596,65 @@ func _debug_mode() -> void: populate_board(["c_out_of_world", 'c_confusion', "p_outer_conflict", "p_unique"]) await get_tree().process_frame play() + + + +func try_select_nearest_card(from: Vector2, towards: Vector2, include_stickies: bool = false) -> bool: + var selection_transform = Transform2D(0, from).looking_at(from+towards) + + var scores: Dictionary[int, Area2D] = {-1: null} + for child:Area2D in dropzone.get_children(): + if not (child is StickyNote and include_stickies): + scores[get_distance_score(child.global_position, selection_transform)] = child + scores.erase(-1) + scores.sort() + + if include_stickies: + var panel_scores: Dictionary[int, Control] = {-1: null} + for child:Control in sideboard.get_children(): + if not child.is_empty(): + panel_scores[get_distance_score(child.attached_sticky_note.global_position, selection_transform)] = child + panel_scores.erase(-1) + panel_scores.sort() + + if panel_scores != {}: + if scores != {}: + if panel_scores.keys()[0] < scores.keys()[0]: + if current_context == Context.ASSIGN: return false + selection = panel_scores.values()[0] + selection_state = SelectionState.STICKIES + return true + else: + if current_context == Context.ASSIGN: return false + selection = panel_scores.values()[0] + selection_state = SelectionState.STICKIES + return true + + + if scores != {}: + selection = scores.values()[0] + return true + return false + +func try_select_nearest_empty_card(from: Vector2) -> bool: + var scores: Dictionary[int, Area2D] = {} + + for card in dropzone.get_children(): + if card is Card: + if not card.has_note_attached(): + scores[int((from-card.global_position).length())] = card + + scores.sort() + + if scores != {}: + selection = scores.values()[0] + return true + return false + +func get_distance_score(from: Vector2, to: Transform2D) -> int: + var diff = from * to + var dir = diff.normalized() + if dir.x > 0.5 and diff.length() > 0: + return int((abs(dir.y) + 0.5) * diff.length()) + else: + return -1 diff --git a/src/logic-scenes/board/card.gd b/src/logic-scenes/board/card.gd index 6c89df7d..e4707f76 100644 --- a/src/logic-scenes/board/card.gd +++ b/src/logic-scenes/board/card.gd @@ -43,7 +43,7 @@ var transfor_arr: Array[Transform2D] = [ @export_range(1, 2) var scale_bump: float = 1.05 @export_range(1.0, 10.0) var bounce_speed: float = 5 -@export_color_no_alpha var highlight_color: Color = Color(1.4, 1.4, 1.4) +@export_color_no_alpha var highlight_color: Color = Color(1.2, 1.2, 1.2) ## Override set_highlight to add visual feedback for cards func set_highlight(value: bool) -> void: @@ -231,6 +231,10 @@ func get_attached_note() -> StickyNote: return null +func get_size() -> Vector2: + return $CollisionShape2D.shape.size + + func preview_sticky_note(sticky_note: StickyNote): if not is_instance_valid(sticky_note): return @@ -252,7 +256,7 @@ func attach_or_exchange_note(note: StickyNote, instant: bool = false) -> void: old.reparent(note.attached_to) old.animate_home() else: - remove_note_if_present() # just kick out our old note + return_old_child(note) # just kick out our old note # ... in with the new note.reparent(self) @@ -268,12 +272,18 @@ func attach_or_exchange_note(note: StickyNote, instant: bool = false) -> void: Steam.storeStats() -func remove_note_if_present() -> void: +func return_old_child(new_note: StickyNote = null) -> void: var former_child: StickyNote = get_attached_note() if not former_child: return - - former_child.reparent(get_parent()) + former_child.tween = null # the positioning logic in card-board will pick that one up and calc a nice slot. + + if new_note: + former_child.reparent(new_note.last_parent) + former_child.animate_home(new_note.home) + else: + former_child.reparent(get_parent()) + # === DROP TARGET PATTERN IMPLEMENTATION === @@ -282,6 +292,7 @@ func remove_note_if_present() -> void: func can_accept_drop(draggable: Draggable) -> bool: return draggable is StickyNote +# FIXME: this function seems to not be used. ## Handles dropping a sticky note onto this card ## Returns DropResult indicating success, rejection, or exchange func handle_drop(draggable: StickyNote) -> int: @@ -292,6 +303,23 @@ func handle_drop(draggable: StickyNote) -> int: draggable.z_index = 0 return Draggable.DropResult.ACCEPTED + +## End drag operation and return the node we were dropped on +func end_drag() -> Node: + super.end_drag() + return _find_drop_target() + + +## Find best drop target: Card > Panel > Board (in priority order) +func _find_drop_target() -> Node: + print(get_overlapping_areas()) + # Priority 1: Check for overlapping cards in dropzone + for area in get_overlapping_areas(): + if area is SideBoundary: + return area + + return null + # === DRAG LIFECYCLE OVERRIDES === ## Cards always drop back to board dropzone diff --git a/src/logic-scenes/board/card.tscn b/src/logic-scenes/board/card.tscn index 06e70238..7ea41478 100644 --- a/src/logic-scenes/board/card.tscn +++ b/src/logic-scenes/board/card.tscn @@ -9,7 +9,7 @@ size = Vector2(277, 231) [node name="Card" type="Area2D" unique_id=5263467] collision_layer = 4 -collision_mask = 0 +collision_mask = 8 priority = 50 script = ExtResource("1_emip0") text = "card" @@ -34,6 +34,7 @@ offset_right = 136.0 offset_bottom = 77.95834 grow_horizontal = 2 grow_vertical = 2 +mouse_default_cursor_shape = 13 theme = ExtResource("3_mdi7r") theme_type_variation = &"card_text" text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. " diff --git a/src/logic-scenes/board/draggable.gd b/src/logic-scenes/board/draggable.gd index b6728d78..692c0b37 100644 --- a/src/logic-scenes/board/draggable.gd +++ b/src/logic-scenes/board/draggable.gd @@ -40,6 +40,7 @@ func set_highlight(value: bool) -> void: var _drag_start_position: Vector2 var _mouse_drag_offset: Vector2 var _drag_source: Node = null # Where the drag started from +var last_parent: Node = null # Where they were last dragged from. Used by return_old_child() in Card ## === SETUP ### func _ready() -> void: @@ -69,11 +70,15 @@ func _get_button_handler() -> Node: var tween : Tween = null -func animate_home() -> void: +func animate_home(target: Vector2 = home) -> void: z_index = 100 if tween: tween.kill() tween = create_tween().set_ease(Tween.EASE_IN_OUT).set_trans(Tween.TRANS_QUART) - tween.tween_property(self, "position", home, 0.5) + tween.tween_property(self, "position", target, 0.5) + +func get_size() -> Vector2: + assert(false, "This function needs to be overridden!") + return Vector2.ZERO func _on_mouse_entered() -> void: #prints("Draggable[base]._on_mouse_entered", self, self.name) @@ -110,6 +115,7 @@ func start_drag(mouse_offset: Vector2) -> void: _drag_start_position = global_position _mouse_drag_offset = mouse_offset _drag_source = get_parent() + last_parent = _drag_source z_index = 60 is_dragged = true @@ -132,6 +138,11 @@ func end_drag() -> Node: _drag_source = null return null +func _set_home() -> void: + var parent = get_parent() + if parent is Control: + confine_to_screen() + home = position ## Confines this draggable element to stay within screen or container bounds ## Skip this check if a sticky note is attached to a card diff --git a/src/logic-scenes/board/physics-board.tscn b/src/logic-scenes/board/physics-board.tscn index 6f208e3c..72f0a750 100644 --- a/src/logic-scenes/board/physics-board.tscn +++ b/src/logic-scenes/board/physics-board.tscn @@ -4,10 +4,12 @@ [ext_resource type="Shader" uid="uid://kyd37e0s6fdu" path="res://logic-scenes/board/physics-board.gdshader" id="1_ggnth"] [ext_resource type="Script" uid="uid://cqsor57nvowni" path="res://logic-scenes/board/card-board.gd" id="3_8v4c4"] [ext_resource type="AudioStream" uid="uid://bywmf3patoe56" path="res://base-environments/youth_room/audio/board_completed.wav" id="5_qjqy3"] +[ext_resource type="Script" uid="uid://cwc0k3mtj4k1f" path="res://logic-scenes/board/side_board.gd" id="6_kvxnu"] [ext_resource type="AudioStream" uid="uid://bgtohhyd8whbm" path="res://base-environments/youth_room/audio/board_completed_de.wav" id="6_ni75f"] [ext_resource type="AudioStream" uid="uid://dj8fpajqhj4k7" path="res://base-environments/youth_room/audio/board_incomplete.wav" id="6_vtvtf"] [ext_resource type="AudioStream" uid="uid://brolrc3lhaeid" path="res://base-environments/youth_room/audio/board_unfitting.wav" id="7_0phgc"] [ext_resource type="AudioStream" uid="uid://swlo6elqs4vx" path="res://base-environments/youth_room/audio/board_incomplete_de.wav" id="7_2qppy"] +[ext_resource type="Script" uid="uid://c7gbr6rdk843f" path="res://logic-scenes/board/side_boundary.gd" id="7_k5h0q"] [ext_resource type="Script" uid="uid://c1oub0cs7cph6" path="res://dev-util/stereo-switch.gd" id="8_ni75f"] [ext_resource type="AudioStream" uid="uid://y8fg3wjscvci" path="res://base-environments/youth_room/audio/board_unfitting_de.wav" id="10_kvxnu"] [ext_resource type="Texture2D" uid="uid://diviesbhf6p77" path="res://logic-scenes/board/board-texture/cardbord-box.png" id="11_ni75f"] @@ -20,6 +22,9 @@ shader_parameter/magic_scale_factor = 1500.0 [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_m1g7s"] +[sub_resource type="WorldBoundaryShape2D" id="WorldBoundaryShape2D_htay1"] +normal = Vector2(-1, 0) + [sub_resource type="Animation" id="Animation_qjqy3"] length = 0.001 @@ -164,7 +169,6 @@ script = ExtResource("3_8v4c4") [node name="CardboardBox" type="TextureRect" parent="." unique_id=450836053] unique_name_in_owner = true -visible = false clip_contents = true layout_direction = 3 layout_mode = 2 @@ -173,8 +177,10 @@ expand_mode = 2 [node name="Label" type="Label" parent="CardboardBox" unique_id=1585163137] layout_mode = 0 -offset_right = 274.0 -offset_bottom = 99.416664 +offset_left = 14.0 +offset_top = 13.0 +offset_right = 288.0 +offset_bottom = 112.416664 size_flags_horizontal = 8 size_flags_vertical = 0 theme_override_colors/font_color = Color(0, 0, 0, 1) @@ -192,11 +198,28 @@ layout_mode = 2 size_flags_horizontal = 3 mouse_filter = 2 -[node name="NoteZone" type="Control" parent="HBoxContainer" unique_id=92501430] +[node name="SideBoard" type="ScrollContainer" parent="HBoxContainer" unique_id=738913213] unique_name_in_owner = true +clip_contents = false custom_minimum_size = Vector2(400, 0) layout_mode = 2 -mouse_filter = 1 +horizontal_scroll_mode = 0 +vertical_scroll_mode = 3 +script = ExtResource("6_kvxnu") + +[node name="Panel" type="Control" parent="HBoxContainer/SideBoard" unique_id=1893108919] +custom_minimum_size = Vector2(400, 1500) +layout_mode = 2 +mouse_filter = 2 + +[node name="SideBoundary" type="Area2D" parent="HBoxContainer/SideBoard" unique_id=1180680912] +position = Vector2(155, 0) +collision_layer = 8 +collision_mask = 0 +script = ExtResource("7_k5h0q") + +[node name="CollisionShape2D" type="CollisionShape2D" parent="HBoxContainer/SideBoard/SideBoundary" unique_id=1692634692] +shape = SubResource("WorldBoundaryShape2D_htay1") [node name="Timer" type="Timer" parent="." unique_id=1859393351] diff --git a/src/logic-scenes/board/side_board.gd b/src/logic-scenes/board/side_board.gd new file mode 100644 index 00000000..2b79978c --- /dev/null +++ b/src/logic-scenes/board/side_board.gd @@ -0,0 +1,136 @@ +class_name SideBoard extends ScrollContainer + +@export var h_margin: = 8 + +# Panel is a bit easier to understand as a name, while Control is easier to use. +var panel: Control +var board: CardBoard + +func _ready() -> void: + panel = $Panel + panel.custom_minimum_size = Vector2(size.x, 0) + board = owner + +func _start_drag(draggable: Draggable) -> void: + assert(false) + +func get_nearest_legal_position(from: Vector2, target: Draggable) -> Vector2: + var children := get_children_sorted() + + var nearest_child_below: Draggable + var nearest_child_above: Draggable + var min_distance_above: float + var min_distance_below: float + + + for i in range(children.size()): + if children[i].home.y < from.y: + nearest_child_above = children[i] + min_distance_above = children[i].get_size().y/2 + h_margin + target.get_size().y/2 + if i > 0: + nearest_child_below = children[i-1] + min_distance_below = children[i-1].get_size().y/2 + h_margin + target.get_size().y/2 + + if nearest_child_above: + from.y = minf(from.y, nearest_child_above.home.y - min_distance_above) + if nearest_child_below: + from.y = maxf(from.y, nearest_child_below.home.y - min_distance_below) + + from.x = _get_x_pos(target) + + return from + +func can_accept_drop(draggable: Draggable) -> bool: + return (draggable is Card and board.is_memory_board) or draggable is StickyNote + +# FIXME: this function seems to not be used. +func handle_drop(draggable: Draggable) -> int: + if not can_accept_drop(draggable): + return Draggable.DropResult.REJECTED + return Draggable.DropResult.ACCEPTED + +# FIXME: integrate this into functions mentioned above! +func accept_drop(draggable: Draggable) -> bool: + if can_accept_drop(draggable): + draggable.home = get_nearest_legal_position(draggable.global_position, draggable) + draggable.reparent(panel) + _scoot_children() + return true + return false + +func populate(with_notes: Array) -> Array[StickyNote]: + var notes: Array[StickyNote] = [] + + for note: StickyNote in with_notes: + notes.append(note) + panel.add_child(note) + + _scoot_children(true) + + return notes + + +func _scoot_children(force_packing: bool = false): + #FIXME: the non-force-packing algorythm is borked right now + force_packing = true + var children := get_children_sorted() + + if children.size() < 2: return + + var min_size := h_margin + + for child in children: + min_size += child.get_size().y + h_margin + + # If space is too tight, children will be spaced optimally. + if maxf(min_size, children[-1].home.y + children[-1].get_size().y) > (size.y - h_margin) or force_packing: + var top :float = h_margin + + for child in children: + child.home = Vector2(_get_x_pos(child), top + child.get_size().y/2) + top += h_margin + child.get_size().y + child.animate_home() + + else: + children[1].home.y = maxf(children[0].get_size().y + h_margin, children[1].home.y) + var last_distance := 0.0 + for i in range(1, children.size()-1): + var min_distance_bottom: float = children[i+1].get_size().y/2 + h_margin + children[i].get_size().y/2 + + var bottom_distance: float = children[i+1].home.y - children[i].home.y + + if bottom_distance < min_distance_bottom: + children[i-1].home.y = children[i-1].home.y - min(last_distance, bottom_distance) + + children[i].home.y = children[i-1].home.y + min_distance_bottom + + last_distance = maxf(children[i].home.y - children[i-1].home.y - min_distance_bottom, 0) + + for child in children: + child.home.x = _get_x_pos(child) + child.animate_home() + + if children[-1]: + panel.custom_minimum_size.y = children[-1].home.y + children[-1].get_size().y + +func get_children_sorted() -> Array[Draggable]: + var children: Array[Draggable] = [] + for child in panel.get_children(true): + if child is Draggable: + children.append(child) + + children.sort_custom(_by_position) + + return children + +func _get_x_pos(target: Draggable) -> float: + if target is StickyNote: + return size.x - 256 + if target is Card: + return size.x - 160 + + return 42 + + +func _by_position(a: Draggable, b: Draggable) -> bool: + return a.position.y < b.position.y diff --git a/src/logic-scenes/board/side_board.gd.uid b/src/logic-scenes/board/side_board.gd.uid new file mode 100644 index 00000000..a8938e5f --- /dev/null +++ b/src/logic-scenes/board/side_board.gd.uid @@ -0,0 +1 @@ +uid://cwc0k3mtj4k1f diff --git a/src/logic-scenes/board/side_boundary.gd b/src/logic-scenes/board/side_boundary.gd new file mode 100644 index 00000000..f1fa2760 --- /dev/null +++ b/src/logic-scenes/board/side_boundary.gd @@ -0,0 +1 @@ +class_name SideBoundary extends Area2D diff --git a/src/logic-scenes/board/side_boundary.gd.uid b/src/logic-scenes/board/side_boundary.gd.uid new file mode 100644 index 00000000..3c5a8313 --- /dev/null +++ b/src/logic-scenes/board/side_boundary.gd.uid @@ -0,0 +1 @@ +uid://c7gbr6rdk843f diff --git a/src/logic-scenes/board/sticky-note.gd b/src/logic-scenes/board/sticky-note.gd index aaa0748a..8998ff65 100644 --- a/src/logic-scenes/board/sticky-note.gd +++ b/src/logic-scenes/board/sticky-note.gd @@ -71,8 +71,7 @@ func init(sticky_name: String = "sticky_note", card_id: StringName = "-1") -> vo parent_id = StringName(card_id.rsplit(".", false, 1)[0]) sticky_id = card_id - # first digit of the card id is 0-3 for youth cards. - from_youth = (card_id as String)[0] as int < 4 + from_youth = int((card_id as String)[0]) < (Scenes.id.TRANSITION as int) func _ready() -> void: super._ready() @@ -112,14 +111,19 @@ func end_drag() -> Node: ## Find best drop target: Card > Panel > Board (in priority order) func _find_drop_target() -> Node: + # Priority 1: Check for overlapping cards in dropzone - var closest : Card = null + var closest : Node = null for area in get_overlapping_areas(): if area is StickyNote and not area.is_attached: continue # Can only drop on attached stickies if area is Card: if (not closest) or ((area.position - position).length() < (closest.position - position).length()): closest = area + + if area is SideBoundary: + if not closest: + closest = area return closest @@ -127,3 +131,7 @@ func _find_drop_target() -> Node: ## Sticky notes are exempt from confinement if stuck to a card func confine_to_screen() -> void: if attached_to is not Card: super.confine_to_screen() + + +func get_size() -> Vector2: + return Vector2($CollisionShape2D.shape.height, $CollisionShape2D.shape.radius*2) diff --git a/src/logic-scenes/board/sticky-note.tscn b/src/logic-scenes/board/sticky-note.tscn index 1dc9dae2..71a7e8e1 100644 --- a/src/logic-scenes/board/sticky-note.tscn +++ b/src/logic-scenes/board/sticky-note.tscn @@ -10,7 +10,7 @@ height = 312.0 [node name="sticky-note" type="Area2D" unique_id=1136333559] collision_layer = 2 -collision_mask = 6 +collision_mask = 14 priority = 100 script = ExtResource("1_yvh5n") text = "card" @@ -37,12 +37,13 @@ anchor_left = 0.5 anchor_top = 0.5 anchor_right = 0.5 anchor_bottom = 0.5 -offset_left = -52.0 -offset_top = -50.0 +offset_left = -43.0 +offset_top = -46.0 offset_right = 243.0 -offset_bottom = 47.0 +offset_bottom = 42.0 grow_horizontal = 2 grow_vertical = 2 +mouse_default_cursor_shape = 13 theme = ExtResource("3_qmm0h") theme_type_variation = &"card_text" text = "sticksum ipsum dolor sit amet met post-it sulcum dulce est 3M, et tesa est."