diff --git a/frontend/src/desktop/CardDragLogic.ts b/frontend/src/desktop/CardDragLogic.ts index 957c341..85e9be0 100644 --- a/frontend/src/desktop/CardDragLogic.ts +++ b/frontend/src/desktop/CardDragLogic.ts @@ -4,8 +4,11 @@ export const handleDragStart = (store: LayoutStore, selection: string[], leading const leadingCard = store.items.get(leadingId); if (!leadingCard) return; - // 1. Snap all followers to leader's center - // 2. Attach them as followers to the leader's physics + // 1. Begin drag on the leader (clears old followers) + leadingCard.physics.beginDrag(offset, pointerId); + + // 2. Snap all followers to leader's center + // 3. Attach them as followers to the leader's physics selection.forEach(id => { if (id === leadingId) return; const card = store.items.get(id); @@ -28,9 +31,6 @@ export const handleDragStart = (store: LayoutStore, selection: string[], leading leadingCard.physics.addFollower(card); } }); - - // 3. Begin drag ONLY on the leader - leadingCard.physics.beginDrag(offset, pointerId); }; export const attachToDragGroup = (store: LayoutStore, selection: string[], leadingId: string, pointerId: number) => { diff --git a/frontend/src/desktop/CardPhysics.ts b/frontend/src/desktop/CardPhysics.ts index c17d366..134b225 100644 --- a/frontend/src/desktop/CardPhysics.ts +++ b/frontend/src/desktop/CardPhysics.ts @@ -46,6 +46,14 @@ export class CardPhysics { } beginDrag(offset: { x: number, y: number }, pointerId: number) { + // If I am a follower of someone else, detach first! + if (this.leader) { + this.leader.removeFollower(this.card); + } + + // Ensure I don't have stale followers from a previous session + this.stopPhysicsLoop(); + this._isDragging = true; this.dragPointerId = pointerId; @@ -113,6 +121,7 @@ export class CardPhysics { private maxLimit: number = 5; private followers: { card: LayoutCard, offsetRotation: number }[] = []; + public leader: CardPhysics | null = null; addFollower(card: LayoutCard) { // Calculate relative rotation @@ -120,25 +129,53 @@ export class CardPhysics { const offset = this.normalizeAngle(card.rotation - this.card.rotation); this.followers.push({ card, offsetRotation: offset }); + // Set back-reference + card.physics.leader = this; + // Add follower mass to leader this.mass += card.physics.mass; this.updateMassScale(); // Constrain leader limits to ensure follower stays within [-5, 5] - // -5 <= leader + offset <= 5 - // -5 - offset <= leader <= 5 - offset this.minLimit = Math.max(this.minLimit, -this.ROTATION_LIMIT - offset); this.maxLimit = Math.min(this.maxLimit, this.ROTATION_LIMIT - offset); } + removeFollower(card: LayoutCard) { + const index = this.followers.findIndex(f => f.card === card); + if (index !== -1) { + const follower = this.followers[index]; + follower.card.physics.leader = null; + this.followers.splice(index, 1); + + // Recalculate mass and limits + this.recalculateStackProperties(); + } + } + clearFollowers() { + // Clear back-references + this.followers.forEach(f => { + f.card.physics.leader = null; + }); this.followers = []; - // Reset mass to base mass + this.recalculateStackProperties(); + } + + private recalculateStackProperties() { this.mass = this.baseMass; - this.updateMassScale(); - // Reset limits this.minLimit = -this.ROTATION_LIMIT; this.maxLimit = this.ROTATION_LIMIT; + + for (const f of this.followers) { + this.mass += f.card.physics.mass; + + // Re-apply limits + const offset = f.offsetRotation; + this.minLimit = Math.max(this.minLimit, -this.ROTATION_LIMIT - offset); + this.maxLimit = Math.min(this.maxLimit, this.ROTATION_LIMIT - offset); + } + this.updateMassScale(); } private updatePhysics(dt: number) {