playful
This commit is contained in:
@@ -5,6 +5,21 @@ import { clamp, formatTransform } from './math';
|
||||
|
||||
const DRAG_HYSTERESIS_PX = 4;
|
||||
const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
|
||||
const MIN_TIMESTEP = 1 / 120;
|
||||
const MAX_TIMESTEP = 1 / 20;
|
||||
const MAX_DYNAMIC_ROTATION = 4;
|
||||
const MAX_ANGULAR_VELOCITY = 180;
|
||||
const ANGULAR_DAMPING = 11;
|
||||
const TORQUE_TO_ACCELERATION = 0.006;
|
||||
const SETTLE_ANGULAR_VELOCITY = 1.2;
|
||||
const EDGE_COLLISION_THRESHOLD = 0.5;
|
||||
const EDGE_ALIGNMENT_STIFFNESS = 30;
|
||||
const BASE_TORQUE_FACTOR = 0.4;
|
||||
const EDGE_COLLISION_TORQUE_FACTOR = 0.08;
|
||||
const EDGE_ALIGNMENT_TORQUE_MULTIPLIER = 35;
|
||||
const EDGE_COLLISION_EXTRA_DAMPING = 4;
|
||||
const EDGE_REST_REALIGN_RATE = 10;
|
||||
const EDGE_ALIGNMENT_EPSILON = 0.15;
|
||||
|
||||
const useDocumentDrag = () => {
|
||||
const {
|
||||
@@ -20,11 +35,121 @@ const useDocumentDrag = () => {
|
||||
openOverlayForDoc,
|
||||
recalcVisibleDocIds,
|
||||
settings,
|
||||
containerRef,
|
||||
} = useDesktopContext();
|
||||
|
||||
const dragStateRef = useRef(null);
|
||||
const inertiaAnimationsRef = useRef(new Map());
|
||||
const { canvasPadding, defaultCanvasWidth, defaultCanvasHeight, debugDrag } = settings;
|
||||
|
||||
const cancelInertiaAnimation = useCallback((docId) => {
|
||||
if (typeof window === 'undefined') {
|
||||
inertiaAnimationsRef.current.delete(docId);
|
||||
return;
|
||||
}
|
||||
const existing = inertiaAnimationsRef.current.get(docId);
|
||||
if (existing && typeof window.cancelAnimationFrame === 'function') {
|
||||
window.cancelAnimationFrame(existing.frameId);
|
||||
}
|
||||
inertiaAnimationsRef.current.delete(docId);
|
||||
}, []);
|
||||
|
||||
const integrateRotation = useCallback(
|
||||
(simulationState, dt, torque = 0, dampingOverride = null) => {
|
||||
const { docId } = simulationState;
|
||||
const entry = layoutRef.current.get(docId);
|
||||
if (!entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const centerX = Number(entry.centerX);
|
||||
const centerY = Number(entry.centerY);
|
||||
if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const torqueAcceleration = torque * TORQUE_TO_ACCELERATION;
|
||||
let angularVelocity = simulationState.angularVelocity + torqueAcceleration * dt;
|
||||
angularVelocity = clamp(angularVelocity, -MAX_ANGULAR_VELOCITY, MAX_ANGULAR_VELOCITY);
|
||||
|
||||
const dampingConstant = typeof dampingOverride === 'number' ? dampingOverride : ANGULAR_DAMPING;
|
||||
const dampingFactor = Math.exp(-dampingConstant * dt);
|
||||
angularVelocity *= dampingFactor;
|
||||
|
||||
let dynamicRotation = simulationState.dynamicRotation + angularVelocity * dt;
|
||||
if (dynamicRotation > MAX_DYNAMIC_ROTATION) {
|
||||
dynamicRotation = MAX_DYNAMIC_ROTATION;
|
||||
angularVelocity = Math.min(angularVelocity, 0);
|
||||
} else if (dynamicRotation < -MAX_DYNAMIC_ROTATION) {
|
||||
dynamicRotation = -MAX_DYNAMIC_ROTATION;
|
||||
angularVelocity = Math.max(angularVelocity, 0);
|
||||
}
|
||||
|
||||
simulationState.angularVelocity = angularVelocity;
|
||||
simulationState.dynamicRotation = dynamicRotation;
|
||||
simulationState.rotation = simulationState.restRotation + dynamicRotation;
|
||||
|
||||
const rotation = simulationState.rotation;
|
||||
layoutRef.current.set(docId, { ...entry, rotation });
|
||||
|
||||
const node = itemRefs.current.get(docId);
|
||||
if (node) {
|
||||
node.style.transform = formatTransform(
|
||||
centerX - simulationState.width / 2,
|
||||
centerY - simulationState.height / 2,
|
||||
rotation,
|
||||
simulationState.dragScale || 1,
|
||||
);
|
||||
}
|
||||
|
||||
const isSettled = Math.abs(angularVelocity) < SETTLE_ANGULAR_VELOCITY;
|
||||
return isSettled;
|
||||
},
|
||||
[itemRefs, layoutRef],
|
||||
);
|
||||
|
||||
const startInertiaAnimation = useCallback(
|
||||
(docId, baseState) => {
|
||||
if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function') {
|
||||
return;
|
||||
}
|
||||
cancelInertiaAnimation(docId);
|
||||
const now =
|
||||
typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
const simulationState = {
|
||||
...baseState,
|
||||
docId,
|
||||
dragScale: baseState.dragScale || 1,
|
||||
lastTimestamp: now,
|
||||
};
|
||||
|
||||
const step = (timestamp) => {
|
||||
const safeTimestamp = Number.isFinite(timestamp) ? timestamp : now + 16;
|
||||
const previous = simulationState.lastTimestamp;
|
||||
let dt = (safeTimestamp - previous) / 1000;
|
||||
if (!Number.isFinite(dt) || dt <= 0) {
|
||||
dt = MIN_TIMESTEP;
|
||||
}
|
||||
dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);
|
||||
simulationState.lastTimestamp = safeTimestamp;
|
||||
|
||||
const settled = integrateRotation(simulationState, dt, 0);
|
||||
if (settled) {
|
||||
inertiaAnimationsRef.current.delete(docId);
|
||||
syncLayoutSnapshot();
|
||||
return;
|
||||
}
|
||||
simulationState.frameId = window.requestAnimationFrame(step);
|
||||
};
|
||||
|
||||
simulationState.frameId = window.requestAnimationFrame(step);
|
||||
inertiaAnimationsRef.current.set(docId, simulationState);
|
||||
},
|
||||
[cancelInertiaAnimation, integrateRotation, syncLayoutSnapshot],
|
||||
);
|
||||
|
||||
const finishDrag = useCallback(
|
||||
(pointerId) => {
|
||||
const state = dragStateRef.current;
|
||||
@@ -63,6 +188,7 @@ const useDocumentDrag = () => {
|
||||
);
|
||||
}
|
||||
preventAll(event);
|
||||
cancelInertiaAnimation(docId);
|
||||
const docKey = docId != null ? String(docId) : null;
|
||||
const doc = docKey ? documentLookup.get(docKey) : null;
|
||||
const { width: docWidth, height: docHeight } = ensureDocumentSize(doc);
|
||||
@@ -90,6 +216,25 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
const containerRect = containerRef?.current?.getBoundingClientRect?.() || null;
|
||||
const containerLeft = containerRect?.left || 0;
|
||||
const containerTop = containerRect?.top || 0;
|
||||
const pointerCanvasX = event.clientX - containerLeft;
|
||||
const pointerCanvasY = event.clientY - containerTop;
|
||||
const pointerOffsetX = pointerCanvasX - centerX;
|
||||
const pointerOffsetY = pointerCanvasY - centerY;
|
||||
const initialRotationDeg = entry?.rotation ?? 0;
|
||||
const initialRotationRad = (initialRotationDeg * Math.PI) / 180;
|
||||
const cosInitial = Math.cos(-initialRotationRad);
|
||||
const sinInitial = Math.sin(-initialRotationRad);
|
||||
const localPointerOffsetX = pointerOffsetX * cosInitial - pointerOffsetY * sinInitial;
|
||||
const localPointerOffsetY = pointerOffsetX * sinInitial + pointerOffsetY * cosInitial;
|
||||
const eventTimestamp =
|
||||
typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp)
|
||||
? event.timeStamp
|
||||
: typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
dragStateRef.current = {
|
||||
docId,
|
||||
pointerId: event.pointerId,
|
||||
@@ -98,6 +243,9 @@ const useDocumentDrag = () => {
|
||||
startX: event.clientX,
|
||||
startY: event.clientY,
|
||||
rotation: entry?.rotation ?? 0,
|
||||
restRotation: entry?.rotation ?? 0,
|
||||
dynamicRotation: 0,
|
||||
angularVelocity: 0,
|
||||
moved: false,
|
||||
locked: false,
|
||||
width: docWidth,
|
||||
@@ -105,18 +253,27 @@ const useDocumentDrag = () => {
|
||||
dragScale: 1,
|
||||
baseScale: normalizedBaseScale,
|
||||
capturedTarget,
|
||||
lastClientX: event.clientX,
|
||||
lastClientY: event.clientY,
|
||||
lastTimestamp: eventTimestamp,
|
||||
localPointerOffsetX,
|
||||
localPointerOffsetY,
|
||||
containerRectLeft: containerLeft,
|
||||
containerRectTop: containerTop,
|
||||
};
|
||||
setDraggingId(docId);
|
||||
},
|
||||
[
|
||||
bringToFront,
|
||||
canvasPadding,
|
||||
cancelInertiaAnimation,
|
||||
documentLookup,
|
||||
ensureDocumentSize,
|
||||
layoutRef,
|
||||
resolveBaseMetrics,
|
||||
setDraggingId,
|
||||
debugDrag,
|
||||
containerRef,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -155,21 +312,47 @@ const useDocumentDrag = () => {
|
||||
|
||||
const deltaX = event.clientX - state.startX;
|
||||
const deltaY = event.clientY - state.startY;
|
||||
const nextCenterX = state.originCenterX + deltaX;
|
||||
const nextCenterY = state.originCenterY + deltaY;
|
||||
|
||||
const docWidth = state.width;
|
||||
const docHeight = state.height;
|
||||
const halfWidth = docWidth / 2;
|
||||
const halfHeight = docHeight / 2;
|
||||
|
||||
const containerRect = containerRef?.current?.getBoundingClientRect?.();
|
||||
if (containerRect) {
|
||||
state.containerRectLeft = containerRect.left;
|
||||
state.containerRectTop = containerRect.top;
|
||||
}
|
||||
|
||||
const containerLeft = state.containerRectLeft;
|
||||
const containerTop = state.containerRectTop;
|
||||
const pointerCanvasX = event.clientX - containerLeft;
|
||||
const pointerCanvasY = event.clientY - containerTop;
|
||||
|
||||
const rotationDeg = state.rotation || 0;
|
||||
const rotationRad = (rotationDeg * Math.PI) / 180;
|
||||
const cosRot = Math.cos(rotationRad);
|
||||
const sinRot = Math.sin(rotationRad);
|
||||
const rotatedOffsetX =
|
||||
state.localPointerOffsetX * cosRot - state.localPointerOffsetY * sinRot;
|
||||
const rotatedOffsetY =
|
||||
state.localPointerOffsetX * sinRot + state.localPointerOffsetY * cosRot;
|
||||
|
||||
const desiredCenterX = pointerCanvasX - rotatedOffsetX;
|
||||
const desiredCenterY = pointerCanvasY - rotatedOffsetY;
|
||||
|
||||
const absCos = Math.abs(cosRot);
|
||||
const absSin = Math.abs(sinRot);
|
||||
const rotatedHalfWidth = absCos * halfWidth + absSin * halfHeight;
|
||||
const rotatedHalfHeight = absSin * halfWidth + absCos * halfHeight;
|
||||
const canvasWidth = canvasSize.width || defaultCanvasWidth;
|
||||
const canvasHeight = canvasSize.height || defaultCanvasHeight;
|
||||
const minCenterX = canvasPadding + halfWidth;
|
||||
const maxCenterX = Math.max(minCenterX, canvasWidth - canvasPadding - halfWidth);
|
||||
const minCenterY = canvasPadding + halfHeight;
|
||||
const maxCenterY = Math.max(minCenterY, canvasHeight - canvasPadding - halfHeight);
|
||||
const clampedCenterX = clamp(nextCenterX, minCenterX, maxCenterX);
|
||||
const clampedCenterY = clamp(nextCenterY, minCenterY, maxCenterY);
|
||||
const minCenterX = canvasPadding + rotatedHalfWidth;
|
||||
const maxCenterX = Math.max(minCenterX, canvasWidth - canvasPadding - rotatedHalfWidth);
|
||||
const minCenterY = canvasPadding + rotatedHalfHeight;
|
||||
const maxCenterY = Math.max(minCenterY, canvasHeight - canvasPadding - rotatedHalfHeight);
|
||||
const clampedCenterX = clamp(desiredCenterX, minCenterX, maxCenterX);
|
||||
const clampedCenterY = clamp(desiredCenterY, minCenterY, maxCenterY);
|
||||
|
||||
if (!state.moved) {
|
||||
const distanceSquared = deltaX * deltaX + deltaY * deltaY;
|
||||
@@ -180,20 +363,167 @@ const useDocumentDrag = () => {
|
||||
state.moved = true;
|
||||
}
|
||||
|
||||
const updated = { ...entry, centerX: clampedCenterX, centerY: clampedCenterY };
|
||||
let currentCenterX = clampedCenterX;
|
||||
let currentCenterY = clampedCenterY;
|
||||
const updated = { ...entry, centerX: currentCenterX, centerY: currentCenterY };
|
||||
layoutRef.current.set(state.docId, updated);
|
||||
|
||||
const node = itemRefs.current.get(state.docId);
|
||||
if (node) {
|
||||
node.style.transform = formatTransform(
|
||||
clampedCenterX - state.width / 2,
|
||||
clampedCenterY - state.height / 2,
|
||||
state.rotation,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
const collidedWithHorizontalEdge =
|
||||
Math.abs(desiredCenterY - clampedCenterY) > EDGE_COLLISION_THRESHOLD;
|
||||
const collidedWithVerticalEdge =
|
||||
Math.abs(desiredCenterX - clampedCenterX) > EDGE_COLLISION_THRESHOLD;
|
||||
const collidedWithEdge = collidedWithHorizontalEdge || collidedWithVerticalEdge;
|
||||
|
||||
const offsetX = pointerCanvasX - currentCenterX;
|
||||
const offsetY = pointerCanvasY - currentCenterY;
|
||||
|
||||
const currentTimestamp =
|
||||
typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp)
|
||||
? event.timeStamp
|
||||
: typeof performance !== 'undefined' && typeof performance.now === 'function'
|
||||
? performance.now()
|
||||
: Date.now();
|
||||
const previousTimestamp = state.lastTimestamp ?? currentTimestamp;
|
||||
let dt = (currentTimestamp - previousTimestamp) / 1000;
|
||||
if (!Number.isFinite(dt) || dt <= 0) {
|
||||
dt = MIN_TIMESTEP;
|
||||
}
|
||||
dt = clamp(dt, MIN_TIMESTEP, MAX_TIMESTEP);
|
||||
|
||||
const previousClientX = state.lastClientX;
|
||||
const previousClientY = state.lastClientY;
|
||||
const velocityX = (event.clientX - previousClientX) / dt;
|
||||
const velocityY = (event.clientY - previousClientY) / dt;
|
||||
const rawTorque = offsetX * velocityY - offsetY * velocityX;
|
||||
|
||||
const rotationBeforeIntegration = state.rotation;
|
||||
|
||||
let torque = rawTorque * BASE_TORQUE_FACTOR;
|
||||
let dampingOverride = null;
|
||||
if (collidedWithEdge) {
|
||||
const currentRotation =
|
||||
typeof state.rotation === 'number'
|
||||
? state.rotation
|
||||
: state.restRotation + state.dynamicRotation;
|
||||
torque =
|
||||
(rawTorque * EDGE_COLLISION_TORQUE_FACTOR + currentRotation * EDGE_ALIGNMENT_STIFFNESS) *
|
||||
EDGE_ALIGNMENT_TORQUE_MULTIPLIER;
|
||||
const restBlend = 1 - Math.exp(-EDGE_REST_REALIGN_RATE * dt);
|
||||
if (restBlend > 0) {
|
||||
const previousRest = state.restRotation;
|
||||
const nextRest = previousRest + (0 - previousRest) * restBlend;
|
||||
state.restRotation = nextRest;
|
||||
}
|
||||
dampingOverride = ANGULAR_DAMPING + EDGE_COLLISION_EXTRA_DAMPING;
|
||||
}
|
||||
|
||||
state.lastClientX = event.clientX;
|
||||
state.lastClientY = event.clientY;
|
||||
state.lastTimestamp = currentTimestamp;
|
||||
|
||||
integrateRotation(state, dt, torque, dampingOverride);
|
||||
|
||||
if (!collidedWithEdge) {
|
||||
const rotationAfter = state.rotation || 0;
|
||||
if (rotationAfter !== rotationBeforeIntegration) {
|
||||
const rotationAfterRad = (rotationAfter * Math.PI) / 180;
|
||||
const cosAfter = Math.cos(rotationAfterRad);
|
||||
const sinAfter = Math.sin(rotationAfterRad);
|
||||
const rotatedOffsetXAfter =
|
||||
state.localPointerOffsetX * cosAfter - state.localPointerOffsetY * sinAfter;
|
||||
const rotatedOffsetYAfter =
|
||||
state.localPointerOffsetX * sinAfter + state.localPointerOffsetY * cosAfter;
|
||||
|
||||
const desiredCenterXAfter = pointerCanvasX - rotatedOffsetXAfter;
|
||||
const desiredCenterYAfter = pointerCanvasY - rotatedOffsetYAfter;
|
||||
|
||||
const absCosAfter = Math.abs(cosAfter);
|
||||
const absSinAfter = Math.abs(sinAfter);
|
||||
const rotatedHalfWidthAfter = absCosAfter * halfWidth + absSinAfter * halfHeight;
|
||||
const rotatedHalfHeightAfter = absSinAfter * halfWidth + absCosAfter * halfHeight;
|
||||
|
||||
const minCenterXAfter = canvasPadding + rotatedHalfWidthAfter;
|
||||
const maxCenterXAfter = Math.max(
|
||||
minCenterXAfter,
|
||||
canvasWidth - canvasPadding - rotatedHalfWidthAfter,
|
||||
);
|
||||
const minCenterYAfter = canvasPadding + rotatedHalfHeightAfter;
|
||||
const maxCenterYAfter = Math.max(
|
||||
minCenterYAfter,
|
||||
canvasHeight - canvasPadding - rotatedHalfHeightAfter,
|
||||
);
|
||||
|
||||
const correctedCenterX = clamp(
|
||||
desiredCenterXAfter,
|
||||
minCenterXAfter,
|
||||
maxCenterXAfter,
|
||||
);
|
||||
const correctedCenterY = clamp(
|
||||
desiredCenterYAfter,
|
||||
minCenterYAfter,
|
||||
maxCenterYAfter,
|
||||
);
|
||||
|
||||
if (
|
||||
Math.abs(correctedCenterX - currentCenterX) > 0.01 ||
|
||||
Math.abs(correctedCenterY - currentCenterY) > 0.01
|
||||
) {
|
||||
const entryAfter = layoutRef.current.get(state.docId);
|
||||
if (entryAfter) {
|
||||
const adjustedEntry = {
|
||||
...entryAfter,
|
||||
centerX: correctedCenterX,
|
||||
centerY: correctedCenterY,
|
||||
};
|
||||
layoutRef.current.set(state.docId, adjustedEntry);
|
||||
const nodeAfter = itemRefs.current.get(state.docId);
|
||||
if (nodeAfter) {
|
||||
nodeAfter.style.transform = formatTransform(
|
||||
correctedCenterX - state.width / 2,
|
||||
correctedCenterY - state.height / 2,
|
||||
state.rotation,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
currentCenterX = correctedCenterX;
|
||||
currentCenterY = correctedCenterY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (collidedWithEdge) {
|
||||
const rotationAfter = state.rotation;
|
||||
const crossedAlignment =
|
||||
rotationBeforeIntegration > EDGE_ALIGNMENT_EPSILON && rotationAfter < 0
|
||||
? rotationBeforeIntegration - rotationAfter > EDGE_ALIGNMENT_EPSILON
|
||||
: rotationBeforeIntegration < -EDGE_ALIGNMENT_EPSILON && rotationAfter > 0
|
||||
? rotationAfter - rotationBeforeIntegration > EDGE_ALIGNMENT_EPSILON
|
||||
: false;
|
||||
const nearAlignment = Math.abs(rotationAfter) <= EDGE_ALIGNMENT_EPSILON;
|
||||
if (crossedAlignment || nearAlignment) {
|
||||
state.restRotation = 0;
|
||||
state.dynamicRotation = 0;
|
||||
state.angularVelocity = 0;
|
||||
state.rotation = 0;
|
||||
const updatedEntry = layoutRef.current.get(state.docId);
|
||||
if (updatedEntry) {
|
||||
const alignedEntry = { ...updatedEntry, rotation: 0 };
|
||||
layoutRef.current.set(state.docId, alignedEntry);
|
||||
const node = itemRefs.current.get(state.docId);
|
||||
if (node) {
|
||||
node.style.transform = formatTransform(
|
||||
alignedEntry.centerX - state.width / 2,
|
||||
alignedEntry.centerY - state.height / 2,
|
||||
0,
|
||||
state.dragScale || 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (debugDrag) {
|
||||
console.log('[desk] handlePointerMove: moved doc', state.docId, 'to', clampedCenterX, clampedCenterY);
|
||||
console.log('[desk] handlePointerMove: moved doc', state.docId, 'to', currentCenterX, currentCenterY);
|
||||
}
|
||||
recalcVisibleDocIds();
|
||||
},
|
||||
@@ -204,7 +534,8 @@ const useDocumentDrag = () => {
|
||||
canvasSize.width,
|
||||
defaultCanvasHeight,
|
||||
defaultCanvasWidth,
|
||||
itemRefs,
|
||||
containerRef,
|
||||
integrateRotation,
|
||||
layoutRef,
|
||||
recalcVisibleDocIds,
|
||||
debugDrag,
|
||||
@@ -216,7 +547,18 @@ const useDocumentDrag = () => {
|
||||
const state = dragStateRef.current;
|
||||
if (state && state.pointerId === event.pointerId) {
|
||||
if (state.moved) {
|
||||
const inertiaState = {
|
||||
restRotation: state.restRotation,
|
||||
dynamicRotation: state.dynamicRotation,
|
||||
angularVelocity: state.angularVelocity,
|
||||
rotation: state.rotation,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
dragScale: state.dragScale || 1,
|
||||
};
|
||||
const docId = state.docId;
|
||||
finishDrag(event.pointerId);
|
||||
startInertiaAnimation(docId, inertiaState);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -234,14 +576,30 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[bringToFront, finishDrag, openOverlayForDoc],
|
||||
[bringToFront, finishDrag, openOverlayForDoc, startInertiaAnimation],
|
||||
);
|
||||
|
||||
const handlePointerCancel = useCallback(
|
||||
(event) => {
|
||||
const state = dragStateRef.current;
|
||||
if (state && state.pointerId === event.pointerId && state.moved) {
|
||||
const inertiaState = {
|
||||
restRotation: state.restRotation,
|
||||
dynamicRotation: state.dynamicRotation,
|
||||
angularVelocity: state.angularVelocity,
|
||||
rotation: state.rotation,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
dragScale: state.dragScale || 1,
|
||||
};
|
||||
const docId = state.docId;
|
||||
finishDrag(event.pointerId);
|
||||
startInertiaAnimation(docId, inertiaState);
|
||||
return;
|
||||
}
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[finishDrag],
|
||||
[finishDrag, startInertiaAnimation],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user