desktop selection
This commit is contained in:
@@ -2,6 +2,7 @@ import { useCallback, useRef } from 'react';
|
||||
import { useDesktopContext } from './context';
|
||||
import { preventAll } from './events';
|
||||
import { clamp, formatTransform } from './math';
|
||||
import usePointerTap from '../ui/usePointerTap';
|
||||
|
||||
const DRAG_HYSTERESIS_PX = 4;
|
||||
const DRAG_HYSTERESIS_SQUARED = DRAG_HYSTERESIS_PX * DRAG_HYSTERESIS_PX;
|
||||
@@ -36,8 +37,33 @@ const useDocumentDrag = () => {
|
||||
recalcVisibleDocIds,
|
||||
settings,
|
||||
containerRef,
|
||||
onDocumentOpen,
|
||||
onInspectDocument,
|
||||
} = useDesktopContext();
|
||||
|
||||
const tapHandler = usePointerTap({
|
||||
delay: 220,
|
||||
onSingle: ({ data, event }) => {
|
||||
if (!data || !data.docId) {
|
||||
return;
|
||||
}
|
||||
if (event && (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
|
||||
return;
|
||||
}
|
||||
openOverlayForDoc(data.docId, data.originInfo);
|
||||
},
|
||||
onDouble: ({ data }) => {
|
||||
if (!data || !data.docId) {
|
||||
return;
|
||||
}
|
||||
if (typeof onInspectDocument === 'function') {
|
||||
onInspectDocument(data.docId);
|
||||
return;
|
||||
}
|
||||
onDocumentOpen?.(data.docId);
|
||||
},
|
||||
});
|
||||
|
||||
const dragStateRef = useRef(null);
|
||||
const inertiaAnimationsRef = useRef(new Map());
|
||||
const { canvasPadding, defaultCanvasWidth, defaultCanvasHeight, debugDrag } = settings;
|
||||
@@ -197,7 +223,10 @@ const useDocumentDrag = () => {
|
||||
Number.isFinite(baseScale) && baseScale > 0 ? baseScale : 1;
|
||||
const defaultCenterX = canvasPadding + docWidth / 2;
|
||||
const defaultCenterY = canvasPadding + docHeight / 2;
|
||||
bringToFront(docId);
|
||||
const metaPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
if (!metaPressed) {
|
||||
bringToFront(docId);
|
||||
}
|
||||
const entry = layoutRef.current.get(docId) || null;
|
||||
const centerX = typeof entry?.centerX === 'number' ? entry.centerX : defaultCenterX;
|
||||
const centerY = typeof entry?.centerY === 'number' ? entry.centerY : defaultCenterY;
|
||||
@@ -338,8 +367,8 @@ const useDocumentDrag = () => {
|
||||
const rotatedOffsetY =
|
||||
state.localPointerOffsetX * sinRot + state.localPointerOffsetY * cosRot;
|
||||
|
||||
const desiredCenterX = pointerCanvasX - rotatedOffsetX;
|
||||
const desiredCenterY = pointerCanvasY - rotatedOffsetY;
|
||||
const previousCenterX = Number.isFinite(entry.centerX) ? entry.centerX : state.originCenterX;
|
||||
const previousCenterY = Number.isFinite(entry.centerY) ? entry.centerY : state.originCenterY;
|
||||
|
||||
const absCos = Math.abs(cosRot);
|
||||
const absSin = Math.abs(sinRot);
|
||||
@@ -351,6 +380,18 @@ const useDocumentDrag = () => {
|
||||
const maxCenterX = Math.max(minCenterX, canvasWidth - canvasPadding - rotatedHalfWidth);
|
||||
const minCenterY = canvasPadding + rotatedHalfHeight;
|
||||
const maxCenterY = Math.max(minCenterY, canvasHeight - canvasPadding - rotatedHalfHeight);
|
||||
|
||||
const pointerRelativePrevX = pointerCanvasX - previousCenterX;
|
||||
const pointerRelativePrevY = pointerCanvasY - previousCenterY;
|
||||
const cosInversePrev = Math.cos(-rotationRad);
|
||||
const sinInversePrev = Math.sin(-rotationRad);
|
||||
const pointerLocalPrevX = pointerRelativePrevX * cosInversePrev - pointerRelativePrevY * sinInversePrev;
|
||||
const pointerLocalPrevY = pointerRelativePrevX * sinInversePrev + pointerRelativePrevY * cosInversePrev;
|
||||
const pointerInsideRelativeToPrev =
|
||||
Math.abs(pointerLocalPrevX) <= halfWidth && Math.abs(pointerLocalPrevY) <= halfHeight;
|
||||
|
||||
const desiredCenterX = pointerCanvasX - rotatedOffsetX;
|
||||
const desiredCenterY = pointerCanvasY - rotatedOffsetY;
|
||||
const clampedCenterX = clamp(desiredCenterX, minCenterX, maxCenterX);
|
||||
const clampedCenterY = clamp(desiredCenterY, minCenterY, maxCenterY);
|
||||
|
||||
@@ -363,19 +404,30 @@ const useDocumentDrag = () => {
|
||||
state.moved = true;
|
||||
}
|
||||
|
||||
let currentCenterX = clampedCenterX;
|
||||
let currentCenterY = clampedCenterY;
|
||||
const updated = { ...entry, centerX: currentCenterX, centerY: currentCenterY };
|
||||
layoutRef.current.set(state.docId, updated);
|
||||
|
||||
const collidedWithHorizontalEdge =
|
||||
Math.abs(desiredCenterY - clampedCenterY) > EDGE_COLLISION_THRESHOLD;
|
||||
const collidedWithVerticalEdge =
|
||||
Math.abs(desiredCenterX - clampedCenterX) > EDGE_COLLISION_THRESHOLD;
|
||||
const collidedWithEdge = collidedWithHorizontalEdge || collidedWithVerticalEdge;
|
||||
|
||||
let currentCenterX = clampedCenterX;
|
||||
let currentCenterY = clampedCenterY;
|
||||
if (collidedWithEdge && !pointerInsideRelativeToPrev) {
|
||||
currentCenterX = previousCenterX;
|
||||
currentCenterY = previousCenterY;
|
||||
}
|
||||
|
||||
const updated = { ...entry, centerX: currentCenterX, centerY: currentCenterY };
|
||||
layoutRef.current.set(state.docId, updated);
|
||||
|
||||
const offsetX = pointerCanvasX - currentCenterX;
|
||||
const offsetY = pointerCanvasY - currentCenterY;
|
||||
const cosInverseCurrent = Math.cos(-rotationRad);
|
||||
const sinInverseCurrent = Math.sin(-rotationRad);
|
||||
const pointerLocalX = offsetX * cosInverseCurrent - offsetY * sinInverseCurrent;
|
||||
const pointerLocalY = offsetX * sinInverseCurrent + offsetY * cosInverseCurrent;
|
||||
const pointerInsideCard =
|
||||
Math.abs(pointerLocalX) <= halfWidth && Math.abs(pointerLocalY) <= halfHeight;
|
||||
|
||||
const currentTimestamp =
|
||||
typeof event.timeStamp === 'number' && Number.isFinite(event.timeStamp)
|
||||
@@ -415,6 +467,14 @@ const useDocumentDrag = () => {
|
||||
state.restRotation = nextRest;
|
||||
}
|
||||
dampingOverride = ANGULAR_DAMPING + EDGE_COLLISION_EXTRA_DAMPING;
|
||||
if (!pointerInsideCard) {
|
||||
torque = 0;
|
||||
currentCenterX = entry?.centerX ?? currentCenterX;
|
||||
currentCenterY = entry?.centerY ?? currentCenterY;
|
||||
if (updated.centerX !== currentCenterX || updated.centerY !== currentCenterY) {
|
||||
layoutRef.current.set(state.docId, { ...updated, centerX: currentCenterX, centerY: currentCenterY });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.lastClientX = event.clientX;
|
||||
@@ -522,6 +582,20 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rotationForOffsetDeg = state.rotation || 0;
|
||||
const rotationForOffsetRad = (rotationForOffsetDeg * Math.PI) / 180;
|
||||
const cosInverse = Math.cos(-rotationForOffsetRad);
|
||||
const sinInverse = Math.sin(-rotationForOffsetRad);
|
||||
const pointerRelativeX = pointerCanvasX - currentCenterX;
|
||||
const pointerRelativeY = pointerCanvasY - currentCenterY;
|
||||
const updatedLocalOffsetX = pointerRelativeX * cosInverse - pointerRelativeY * sinInverse;
|
||||
const updatedLocalOffsetY = pointerRelativeX * sinInverse + pointerRelativeY * cosInverse;
|
||||
if (!collidedWithEdge || pointerInsideCard) {
|
||||
state.localPointerOffsetX = updatedLocalOffsetX;
|
||||
state.localPointerOffsetY = updatedLocalOffsetY;
|
||||
}
|
||||
|
||||
if (debugDrag) {
|
||||
console.log('[desk] handlePointerMove: moved doc', state.docId, 'to', currentCenterX, currentCenterY);
|
||||
}
|
||||
@@ -563,20 +637,35 @@ const useDocumentDrag = () => {
|
||||
}
|
||||
|
||||
const docId = state.docId;
|
||||
bringToFront(docId);
|
||||
const metaPressed = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
if (!metaPressed) {
|
||||
bringToFront(docId);
|
||||
}
|
||||
const originInfo = {
|
||||
rotation: state.rotation || 0,
|
||||
scale: state.baseScale || 1,
|
||||
width: state.width,
|
||||
height: state.height,
|
||||
};
|
||||
const docKey = docId != null ? String(docId) : null;
|
||||
const doc = docKey ? documentLookup.get(docKey) : null;
|
||||
tapHandler(event, {
|
||||
docId,
|
||||
originInfo,
|
||||
docTitle: doc?.title || 'document',
|
||||
});
|
||||
finishDrag(event.pointerId);
|
||||
openOverlayForDoc(docId, originInfo);
|
||||
return;
|
||||
}
|
||||
finishDrag(event.pointerId);
|
||||
},
|
||||
[bringToFront, finishDrag, openOverlayForDoc, startInertiaAnimation],
|
||||
[
|
||||
bringToFront,
|
||||
documentLookup,
|
||||
finishDrag,
|
||||
startInertiaAnimation,
|
||||
tapHandler,
|
||||
],
|
||||
);
|
||||
|
||||
const handlePointerCancel = useCallback(
|
||||
|
||||
Reference in New Issue
Block a user