| 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; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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(); |
|
|
| |
| 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[] = []; |
|
|
| |
| 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; |
|
|
| |
| 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]; |
|
|
| |
| |
| 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; |
| } |
|
|
| |
| 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; |
| } |
|
|
| |
| 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; |
| }); |
|
|
| |
| 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, |
| }; |
| } |
|
|