Add drop shadow system with animated lift/drop during drag

DropShadowFilter applied to all ImageSprites for photos-on-a-desk feel.
SelectionManager now handles object dragging with shadow lift/drop and
spring-animated scale (1.0 <-> 1.03). Replaced incompatible @pixi/filter-drop-shadow
v5 with pixi-filters v6 for PixiJS v8 compatibility.
This commit is contained in:
Hiren Kangad
2026-03-09 23:35:02 +05:30
parent 7f6c9cd409
commit 9e4a427f97
4 changed files with 148 additions and 297 deletions
+98 -4
View File
@@ -9,6 +9,8 @@ import { Container, Graphics, FederatedPointerEvent } from 'pixi.js';
import type { Viewport } from 'pixi-viewport';
import { SceneManager, SceneItem } from './SceneManager';
import { TransformBox } from './TransformBox';
import { ImageSprite } from './sprites/ImageSprite';
import { Spring, PRESETS } from './spring';
// ---------------------------------------------------------------------------
// Constants
@@ -20,6 +22,8 @@ const BAND_STROKE = 0x4a90d9;
const BAND_STROKE_ALPHA = 0.6;
const BAND_STROKE_WIDTH = 1;
const RUBBER_BAND_THRESHOLD = 5; // px in screen space before rubber band activates
const DRAG_THRESHOLD = 5; // px in screen space before object drag activates
const DRAG_LIFT_SCALE = 1.03; // scale during drag
// ---------------------------------------------------------------------------
// SelectionManager
@@ -44,6 +48,11 @@ export class SelectionManager {
private _rubberBanding = false;
private _hitItemOnDown: SceneItem | null = null;
// Object drag state
private _objectDragging = false;
private _lastDragWorldX = 0;
private _lastDragWorldY = 0;
constructor(viewport: Viewport, scene: SceneManager) {
this._viewport = viewport;
this._scene = scene;
@@ -177,13 +186,46 @@ export class SelectionManager {
private _onPointerMove(e: FederatedPointerEvent): void {
if (!this._pointerDown) return;
// If we clicked on an object, don't start rubber banding
if (this._hitItemOnDown) return;
const dx = e.global.x - this._downScreenX;
const dy = e.global.y - this._downScreenY;
const dist = Math.sqrt(dx * dx + dy * dy);
// Object drag: clicked on a selected item
if (this._hitItemOnDown && this.selectedIds.has(this._hitItemOnDown.id)) {
if (!this._objectDragging && dist >= DRAG_THRESHOLD) {
this._objectDragging = true;
this._lastDragWorldX = this._downWorldX;
this._lastDragWorldY = this._downWorldY;
// Pause viewport drag so object drag works
this._viewport.plugins.pause('drag');
// Lift shadow + spring scale on all selected image sprites
this._applyLift();
}
if (this._objectDragging) {
const currentWorld = this._viewport.toWorld(e.global.x, e.global.y);
const ddx = currentWorld.x - this._lastDragWorldX;
const ddy = currentWorld.y - this._lastDragWorldY;
this._lastDragWorldX = currentWorld.x;
this._lastDragWorldY = currentWorld.y;
// Move all selected items by delta
for (const item of this.getSelectedItems()) {
item.displayObject.x += ddx;
item.displayObject.y += ddy;
item.data.x = item.displayObject.x;
item.data.y = item.displayObject.y;
}
this.transformBox.update(this.getSelectedItems());
}
return;
}
// Rubber band: clicked on empty space
if (this._hitItemOnDown) return;
if (!this._rubberBanding && dist >= RUBBER_BAND_THRESHOLD) {
this._rubberBanding = true;
// Pause viewport drag so rubber band works
@@ -200,7 +242,17 @@ export class SelectionManager {
if (!this._pointerDown) return;
this._pointerDown = false;
if (this._rubberBanding) {
if (this._objectDragging) {
// End object drag — drop shadow + spring scale back
this._applyDrop();
this._objectDragging = false;
// Resume viewport drag
this._viewport.plugins.resume('drag');
// Notify change (positions updated)
this._emitChange();
} else if (this._rubberBanding) {
// Finish rubber band selection
const currentWorld = this._viewport.toWorld(e.global.x, e.global.y);
this._finishRubberBand(this._downWorldX, this._downWorldY, currentWorld.x, currentWorld.y, e.shiftKey);
@@ -220,6 +272,48 @@ export class SelectionManager {
this._hitItemOnDown = null;
}
// -- Drag Shadow Lift / Drop ----------------------------------------------
/** Lift shadows and spring-scale selected items up for drag. */
private _applyLift(): void {
const items = this.getSelectedItems();
for (const item of items) {
const dobj = item.displayObject;
// Lift shadow on ImageSprites
if (dobj instanceof ImageSprite) {
dobj.liftShadow();
}
// Spring scale 1.0 → 1.03
const spring = new Spring(1.0, DRAG_LIFT_SCALE, PRESETS.snappy);
spring.onUpdate = (v) => {
dobj.scale.set(v, v);
};
this._scene.springs.add(spring);
}
}
/** Drop shadows and spring-scale selected items back to rest. */
private _applyDrop(): void {
const items = this.getSelectedItems();
for (const item of items) {
const dobj = item.displayObject;
// Drop shadow on ImageSprites
if (dobj instanceof ImageSprite) {
dobj.dropShadow();
}
// Spring scale 1.03 → 1.0
const spring = new Spring(DRAG_LIFT_SCALE, 1.0, PRESETS.snappy);
spring.onUpdate = (v) => {
dobj.scale.set(v, v);
};
this._scene.springs.add(spring);
}
}
// -- Rubber Band ----------------------------------------------------------
private _drawRubberBand(x1: number, y1: number, x2: number, y2: number): void {