| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- import { useCallback } from 'react';
- import { useKeyboardStore } from '@/lib/store/keyboard';
- import { useCanvasStore } from '@/lib/store';
- import type { PPTElement, PPTLineElement } from '@/lib/types/slides';
- 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,
- };
- }
|