thu-maic--openmaic
274 行
9.6 KiB
TypeScript
274 行
9.6 KiB
TypeScript
import { useCallback } from 'react';
|
|
import { useKeyboardStore } from '@/lib/store/keyboard';
|
|
import { useCanvasStore } from '@/lib/store';
|
|
import type { PPTElement, PPTLineElement } from '@openmaic/dsl';
|
|
import { OperateLineHandlers } from '@/lib/types/edit';
|
|
import { useHistorySnapshot } from '@/lib/hooks/use-history-snapshot';
|
|
import { useCanvasOperations } from '@/lib/hooks/use-canvas-operations';
|
|
|
|
interface AdsorptionPoint {
|
|
x: number;
|
|
y: number;
|
|
}
|
|
|
|
/**
|
|
* Drag line element Hook
|
|
*
|
|
* @param elementListRef - Element list ref (used to read the latest value on mouseup)
|
|
* @param setElementList - Element list setter (used to trigger re-render)
|
|
*/
|
|
export function useDragLineElement(
|
|
elementListRef: React.RefObject<PPTElement[]>,
|
|
setElementList: React.Dispatch<React.SetStateAction<PPTElement[]>>,
|
|
) {
|
|
const updateSlide = useCanvasOperations().updateSlide;
|
|
const canvasScale = useCanvasStore.use.canvasScale();
|
|
const ctrlOrShiftKeyActive = useKeyboardStore((state) => state.ctrlOrShiftKeyActive());
|
|
const { addHistorySnapshot } = useHistorySnapshot();
|
|
|
|
// Drag line endpoint
|
|
const dragLineElement = useCallback(
|
|
(e: React.MouseEvent, element: PPTLineElement, command: OperateLineHandlers) => {
|
|
let isMouseDown = true;
|
|
|
|
const sorptionRange = 8;
|
|
|
|
const startPageX = e.pageX;
|
|
const startPageY = e.pageY;
|
|
|
|
const adsorptionPoints: AdsorptionPoint[] = [];
|
|
|
|
// Get the 8 scale points of all non-rotated, non-line elements as adsorption positions
|
|
for (let i = 0; i < elementListRef.current.length; i++) {
|
|
const _element = elementListRef.current[i];
|
|
if (_element.type === 'line' || _element.rotate) continue;
|
|
|
|
const left = _element.left;
|
|
const top = _element.top;
|
|
const width = _element.width;
|
|
const height = _element.height;
|
|
|
|
const right = left + width;
|
|
const bottom = top + height;
|
|
const centerX = top + height / 2;
|
|
const centerY = left + width / 2;
|
|
|
|
const topPoint = { x: centerY, y: top };
|
|
const bottomPoint = { x: centerY, y: bottom };
|
|
const leftPoint = { x: left, y: centerX };
|
|
const rightPoint = { x: right, y: centerX };
|
|
|
|
const leftTopPoint = { x: left, y: top };
|
|
const rightTopPoint = { x: right, y: top };
|
|
const leftBottomPoint = { x: left, y: bottom };
|
|
const rightBottomPoint = { x: right, y: bottom };
|
|
|
|
adsorptionPoints.push(
|
|
topPoint,
|
|
bottomPoint,
|
|
leftPoint,
|
|
rightPoint,
|
|
leftTopPoint,
|
|
rightTopPoint,
|
|
leftBottomPoint,
|
|
rightBottomPoint,
|
|
);
|
|
}
|
|
|
|
const handleMouseMove = (e: MouseEvent) => {
|
|
if (!isMouseDown) return;
|
|
|
|
const currentPageX = e.pageX;
|
|
const currentPageY = e.pageY;
|
|
|
|
const moveX = (currentPageX - startPageX) / canvasScale;
|
|
const moveY = (currentPageY - startPageY) / canvasScale;
|
|
|
|
// Position of line start and end points in the editing area
|
|
let startX = element.left + element.start[0];
|
|
let startY = element.top + element.start[1];
|
|
let endX = element.left + element.end[0];
|
|
let endY = element.top + element.end[1];
|
|
|
|
const mid = element.broken || element.broken2 || element.curve || [0, 0];
|
|
let midX = element.left + mid[0];
|
|
let midY = element.top + mid[1];
|
|
|
|
const [c1, c2] = element.cubic || [
|
|
[0, 0],
|
|
[0, 0],
|
|
];
|
|
let c1X = element.left + c1[0];
|
|
let c1Y = element.top + c1[1];
|
|
let c2X = element.left + c2[0];
|
|
let c2Y = element.top + c2[1];
|
|
|
|
// Drag start or end point position
|
|
// Horizontal and vertical snapping
|
|
if (command === OperateLineHandlers.START) {
|
|
startX = startX + moveX;
|
|
startY = startY + moveY;
|
|
|
|
if (Math.abs(startX - endX) < sorptionRange) startX = endX;
|
|
if (Math.abs(startY - endY) < sorptionRange) startY = endY;
|
|
|
|
for (const adsorptionPoint of adsorptionPoints) {
|
|
const { x, y } = adsorptionPoint;
|
|
if (Math.abs(x - startX) < sorptionRange && Math.abs(y - startY) < sorptionRange) {
|
|
startX = x;
|
|
startY = y;
|
|
break;
|
|
}
|
|
}
|
|
} else if (command === OperateLineHandlers.END) {
|
|
endX = endX + moveX;
|
|
endY = endY + moveY;
|
|
|
|
if (Math.abs(startX - endX) < sorptionRange) endX = startX;
|
|
if (Math.abs(startY - endY) < sorptionRange) endY = startY;
|
|
|
|
for (const adsorptionPoint of adsorptionPoints) {
|
|
const { x, y } = adsorptionPoint;
|
|
if (Math.abs(x - endX) < sorptionRange && Math.abs(y - endY) < sorptionRange) {
|
|
endX = x;
|
|
endY = y;
|
|
break;
|
|
}
|
|
}
|
|
} else if (command === OperateLineHandlers.C) {
|
|
midX = midX + moveX;
|
|
midY = midY + moveY;
|
|
|
|
if (Math.abs(midX - startX) < sorptionRange) midX = startX;
|
|
if (Math.abs(midY - startY) < sorptionRange) midY = startY;
|
|
if (Math.abs(midX - endX) < sorptionRange) midX = endX;
|
|
if (Math.abs(midY - endY) < sorptionRange) midY = endY;
|
|
if (
|
|
Math.abs(midX - (startX + endX) / 2) < sorptionRange &&
|
|
Math.abs(midY - (startY + endY) / 2) < sorptionRange
|
|
) {
|
|
midX = (startX + endX) / 2;
|
|
midY = (startY + endY) / 2;
|
|
}
|
|
} else if (command === OperateLineHandlers.C1) {
|
|
c1X = c1X + moveX;
|
|
c1Y = c1Y + moveY;
|
|
|
|
if (Math.abs(c1X - startX) < sorptionRange) c1X = startX;
|
|
if (Math.abs(c1Y - startY) < sorptionRange) c1Y = startY;
|
|
if (Math.abs(c1X - endX) < sorptionRange) c1X = endX;
|
|
if (Math.abs(c1Y - endY) < sorptionRange) c1Y = endY;
|
|
} else if (command === OperateLineHandlers.C2) {
|
|
c2X = c2X + moveX;
|
|
c2Y = c2Y + moveY;
|
|
|
|
if (Math.abs(c2X - startX) < sorptionRange) c2X = startX;
|
|
if (Math.abs(c2Y - startY) < sorptionRange) c2Y = startY;
|
|
if (Math.abs(c2X - endX) < sorptionRange) c2X = endX;
|
|
if (Math.abs(c2Y - endY) < sorptionRange) c2Y = endY;
|
|
}
|
|
|
|
// Calculate updated start and end coordinates relative to the element's own position
|
|
const minX = Math.min(startX, endX);
|
|
const minY = Math.min(startY, endY);
|
|
const maxX = Math.max(startX, endX);
|
|
const maxY = Math.max(startY, endY);
|
|
|
|
const start: [number, number] = [0, 0];
|
|
const end: [number, number] = [maxX - minX, maxY - minY];
|
|
if (startX > endX) {
|
|
start[0] = maxX - minX;
|
|
end[0] = 0;
|
|
}
|
|
if (startY > endY) {
|
|
start[1] = maxY - minY;
|
|
end[1] = 0;
|
|
}
|
|
|
|
// Update local element list during mousemove
|
|
const newElements = elementListRef.current.map((el) => {
|
|
if (el.id === element.id) {
|
|
const newEl: PPTLineElement = {
|
|
...(el as PPTLineElement),
|
|
left: minX,
|
|
top: minY,
|
|
start: start,
|
|
end: end,
|
|
};
|
|
if (command === OperateLineHandlers.START || command === OperateLineHandlers.END) {
|
|
if (ctrlOrShiftKeyActive) {
|
|
if (element.broken) newEl.broken = [midX - minX, midY - minY];
|
|
if (element.curve) newEl.curve = [midX - minX, midY - minY];
|
|
if (element.cubic)
|
|
newEl.cubic = [
|
|
[c1X - minX, c1Y - minY],
|
|
[c2X - minX, c2Y - minY],
|
|
];
|
|
} else {
|
|
if (element.broken)
|
|
newEl.broken = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];
|
|
if (element.curve) newEl.curve = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];
|
|
if (element.cubic)
|
|
newEl.cubic = [
|
|
[(start[0] + end[0]) / 2, (start[1] + end[1]) / 2],
|
|
[(start[0] + end[0]) / 2, (start[1] + end[1]) / 2],
|
|
];
|
|
}
|
|
if (element.broken2)
|
|
newEl.broken2 = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];
|
|
} else if (command === OperateLineHandlers.C) {
|
|
if (element.broken) newEl.broken = [midX - minX, midY - minY];
|
|
if (element.curve) newEl.curve = [midX - minX, midY - minY];
|
|
if (element.broken2) {
|
|
if (maxX - minX >= maxY - minY) newEl.broken2 = [midX - minX, newEl.broken2![1]];
|
|
else newEl.broken2 = [newEl.broken2![0], midY - minY];
|
|
}
|
|
} else {
|
|
if (element.cubic)
|
|
newEl.cubic = [
|
|
[c1X - minX, c1Y - minY],
|
|
[c2X - minX, c2Y - minY],
|
|
];
|
|
}
|
|
return newEl;
|
|
}
|
|
return el;
|
|
});
|
|
|
|
// Update both ref and state
|
|
elementListRef.current = newElements;
|
|
setElementList(newElements);
|
|
};
|
|
|
|
const handleMouseUp = (e: MouseEvent) => {
|
|
isMouseDown = false;
|
|
document.onmousemove = null;
|
|
document.onmouseup = null;
|
|
|
|
const currentPageX = e.pageX;
|
|
const currentPageY = e.pageY;
|
|
|
|
if (startPageX === currentPageX && startPageY === currentPageY) return;
|
|
|
|
updateSlide({ elements: elementListRef.current });
|
|
addHistorySnapshot();
|
|
};
|
|
|
|
document.onmousemove = handleMouseMove;
|
|
document.onmouseup = handleMouseUp;
|
|
},
|
|
[
|
|
elementListRef,
|
|
setElementList,
|
|
canvasScale,
|
|
ctrlOrShiftKeyActive,
|
|
updateSlide,
|
|
addHistorySnapshot,
|
|
],
|
|
);
|
|
|
|
return {
|
|
dragLineElement,
|
|
};
|
|
}
|