qcast-front/src/hooks/useEvent.js
hyojun.choi 4ec191dcb0 1. 외벽선그리기, 배치면그리기 에서 흡착점 동작
2. 외벽선그리기, 배치면그리기 에서 그리드, 흡착점 선택 불가
3. 이동, 복사 시 길이 줄어듬
4. 옵션 설정 닫을 때, 임의그리드,흡착점 선택  해제
2025-05-20 17:02:56 +09:00

405 lines
14 KiB
JavaScript

import { useRef } from 'react'
import { useRecoilValue, useSetRecoilState } from 'recoil'
import { canvasState, canvasZoomState, currentMenuState, textModeState } from '@/store/canvasAtom'
import { fabric } from 'fabric'
import { calculateDistance, calculateDistancePoint, calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util'
import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint'
import { useDotLineGrid } from '@/hooks/useDotLineGrid'
import { useTempGrid } from '@/hooks/useTempGrid'
import { gridColorState } from '@/store/gridAtom'
import { gridDisplaySelector } from '@/store/settingAtom'
import { POLYGON_TYPE } from '@/common/common'
export function useEvent() {
const canvas = useRecoilValue(canvasState)
const currentMenu = useRecoilValue(currentMenuState)
const documentEventListeners = useRef([])
const mouseEventListeners = useRef([])
const setCanvasZoom = useSetRecoilState(canvasZoomState)
const gridColor = useRecoilValue(gridColorState)
const isGridDisplay = useRecoilValue(gridDisplaySelector)
const { adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, getAdsorptionPoints, adsorptionPointAddModeStateEvent } = useAdsorptionPoint()
const { dotLineGridSetting, interval, getClosestLineGrid } = useDotLineGrid()
const { tempGridModeStateLeftClickEvent, tempGridMode } = useTempGrid()
const roofAdsorptionPoints = useRef([])
const intersectionPoints = useRef([])
const textMode = useRecoilValue(textModeState)
// 이벤트 초기화 위치 수정 -> useCanvasSetting에서 세팅값 불러오고 나서 초기화 함수 호출
// useEffect(() => {
// initEvent()
// }, [currentMenu, canvas, adsorptionPointAddMode, adsorptionPointMode, adsorptionRange, dotLineGridSetting])
// 임시 그리드 모드 변경 시 이벤트 초기화 호출 위치 수정 -> GridOption 컴포넌트에서 임시 그리드 모드 변경 시 이벤트 초기화 함수 호출
// useEffect(() => {
// initEvent()
// }, [tempGridMode])
const initEvent = () => {
if (!canvas) {
return
}
removeAllDocumentEventListeners()
removeAllMouseEventListeners()
/**
* wheelEvent
*/
canvas?.off('mouse:wheel')
canvas?.on('mouse:wheel', wheelEvent)
addDefaultEvent()
}
const addDefaultEvent = () => {
//default Event 추가
addCanvasMouseEventListener('mouse:move', defaultMouseMoveEvent)
addCanvasMouseEventListener('mouse:out', defaultMouseOutEvent)
addDocumentEventListener('contextmenu', document, defaultContextMenuEvent)
if (adsorptionPointAddMode) {
addCanvasMouseEventListener('mouse:down', adsorptionPointAddModeStateEvent)
}
if (tempGridMode) {
addCanvasMouseEventListener('mouse:down', tempGridModeStateLeftClickEvent)
addDocumentEventListener('contextmenu', document, tempGridRightClickEvent)
}
}
const defaultContextMenuEvent = (e) => {
e.preventDefault()
e.stopPropagation()
}
const wheelEvent = (opt) => {
const delta = opt.e.deltaY // 휠 이동 값 (양수면 축소, 음수면 확대)
let zoom = canvas.getZoom() // 현재 줌 값
zoom += delta > 0 ? -0.1 : 0.1
// 줌 값 제한 (최소 0.5배, 최대 5배)
if (zoom > 5) zoom = 5
if (zoom < 0.5) zoom = 0.5
setCanvasZoom(Number((zoom * 100).toFixed(0)))
// 마우스 위치 기준으로 확대/축소
canvas.zoomToPoint(new fabric.Point(opt.e.offsetX, opt.e.offsetY), zoom)
canvas.calcOffset()
canvas.setViewportTransform(canvas.viewportTransform)
canvas.requestRenderAll()
// 이벤트의 기본 동작 방지 (스크롤 방지)
opt.e.preventDefault()
opt.e.stopPropagation()
}
const defaultMouseOutEvent = (e) => {
removeMouseLine()
}
const defaultMouseMoveEvent = (e) => {
removeMouseLine()
const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF)
// 가로선
const pointer = canvas.getPointer(e.e)
let arrivalPoint = { x: pointer.x, y: pointer.y }
if (adsorptionPointMode) {
const roofsPoints = roofs.map((roof) => roof.points).flat()
roofAdsorptionPoints.current = [...roofsPoints]
const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine' && !obj.isFixed)
const otherAdsorptionPoints = []
auxiliaryLines.forEach((line1) => {
auxiliaryLines.forEach((line2) => {
if (line1 === line2) {
return
}
const intersectionPoint = calculateIntersection(line1, line2)
if (!intersectionPoint || intersectionPoints.current.some((point) => point.x === intersectionPoint.x && point.y === intersectionPoint.y)) {
return
}
otherAdsorptionPoints.push(intersectionPoint)
})
})
let innerLinePoints = []
canvas
.getObjects()
.filter((obj) => obj.innerLines)
.forEach((polygon) => {
polygon.innerLines.forEach((line) => {
innerLinePoints.push({ x: line.x1, y: line.y1 })
innerLinePoints.push({ x: line.x2, y: line.y2 })
})
})
const adsorptionPoints = [
...getAdsorptionPoints(),
...roofAdsorptionPoints.current,
...otherAdsorptionPoints,
...intersectionPoints.current,
...innerLinePoints,
]
if (dotLineGridSetting.LINE || canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name)).length > 0) {
const closestLine = getClosestLineGrid(pointer)
const horizonLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'horizontal')
const verticalLines = canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name) && obj.direction === 'vertical')
if (!horizonLines || !verticalLines) {
return
}
let closestHorizontalLine = null
let closestVerticalLine = null
if (horizonLines && horizonLines.length > 0) {
closestHorizontalLine = horizonLines.reduce((prev, curr) => {
const prevDistance = calculateDistance(pointer, prev)
const currDistance = calculateDistance(pointer, curr)
return prevDistance < currDistance ? prev : curr
})
}
if (verticalLines && verticalLines.length > 0) {
closestVerticalLine = verticalLines.reduce((prev, curr) => {
const prevDistance = calculateDistance(pointer, prev)
const currDistance = calculateDistance(pointer, curr)
return prevDistance < currDistance ? prev : curr
})
}
if (!closestVerticalLine || !closestHorizontalLine) {
return
}
const closestIntersectionPoint = calculateIntersection(closestHorizontalLine, closestVerticalLine)
if (closestLine) {
const distanceClosestLine = calculateDistance(pointer, closestLine)
let distanceClosestPoint = Infinity
if (closestIntersectionPoint) {
distanceClosestPoint = calculateDistancePoint(pointer, closestIntersectionPoint)
}
if (distanceClosestLine < adsorptionRange) {
arrivalPoint =
closestLine.direction === 'vertical'
? { x: closestLine.x1, y: pointer.y }
: {
x: pointer.x,
y: closestLine.y1,
}
if (distanceClosestPoint * 2 < adsorptionRange) {
arrivalPoint = { ...closestIntersectionPoint }
}
}
}
}
if (dotLineGridSetting.DOT) {
const horizontalInterval = interval.horizontalInterval
const verticalInterval = interval.verticalInterval
const x = pointer.x - horizontalInterval * Math.floor(pointer.x / horizontalInterval)
const y = pointer.y - verticalInterval * Math.floor(pointer.y / verticalInterval)
const xRate = (x / horizontalInterval) * 100
const yRate = (y / verticalInterval) * 100
let tempPoint
if (xRate <= adsorptionRange && yRate <= adsorptionRange) {
tempPoint = {
x: Math.round(pointer.x / horizontalInterval) * horizontalInterval + horizontalInterval / 2,
y: Math.round(pointer.y / verticalInterval) * verticalInterval + horizontalInterval / 2,
}
} else if (xRate <= adsorptionRange && yRate >= adsorptionRange) {
tempPoint = {
x: Math.round(pointer.x / horizontalInterval) * horizontalInterval + horizontalInterval / 2,
y: Math.round(pointer.y / verticalInterval) * verticalInterval - horizontalInterval / 2,
}
} else if (xRate >= adsorptionRange && yRate <= adsorptionRange) {
tempPoint = {
x: Math.round(pointer.x / horizontalInterval) * horizontalInterval - horizontalInterval / 2,
y: Math.round(pointer.y / verticalInterval) * verticalInterval + horizontalInterval / 2,
}
} else if (xRate >= adsorptionRange && yRate >= adsorptionRange) {
tempPoint = {
x: Math.round(pointer.x / horizontalInterval) * horizontalInterval - horizontalInterval / 2,
y: Math.round(pointer.y / verticalInterval) * verticalInterval - horizontalInterval / 2,
}
}
if (distanceBetweenPoints(pointer, tempPoint) <= adsorptionRange) {
arrivalPoint = tempPoint
}
}
// pointer와 adsorptionPoints의 거리를 계산하여 가장 가까운 점을 찾는다.
let adsorptionPoint = findClosestPoint(pointer, adsorptionPoints)
if (adsorptionPoint && distanceBetweenPoints(pointer, adsorptionPoint) <= adsorptionRange) {
arrivalPoint = { ...adsorptionPoint }
}
}
try {
const helpGuideLines = canvas.getObjects().filter((obj) => obj.name === 'helpGuideLine')
if (helpGuideLines.length === 2) {
const guideIntersectionPoint = calculateIntersection(helpGuideLines[0], helpGuideLines[1])
if (guideIntersectionPoint && distanceBetweenPoints(guideIntersectionPoint, pointer) <= adsorptionRange) {
arrivalPoint = guideIntersectionPoint
}
}
} catch (e) {}
const horizontalLine = new fabric.Line([-4 * canvas.width, arrivalPoint.y, 4 * canvas.width, arrivalPoint.y], {
stroke: 'red',
strokeWidth: 1,
selectable: false,
name: 'mouseLine',
})
// 세로선
const verticalLine = new fabric.Line([arrivalPoint.x, -4 * canvas.height, arrivalPoint.x, 4 * canvas.height], {
stroke: 'red',
strokeWidth: 1,
selectable: false,
name: 'mouseLine',
})
// 선들을 캔버스에 추가합니다.
canvas?.add(horizontalLine, verticalLine)
// 캔버스를 다시 그립니다.
canvas?.renderAll()
}
const removeMouseLine = () => {
// 캔버스에서 마우스 선을 찾아 제거합니다.
canvas
?.getObjects()
.filter((obj) => obj.name === 'mouseLine')
.forEach((line) => {
canvas?.remove(line)
})
}
const tempGridRightClickEvent = (e) => {
e.preventDefault()
e.stopPropagation()
//임의 그리드 모드일 경우
let pointer = { x: e.offsetX, y: e.offsetY }
const tempGrid = new fabric.Line([-1500, pointer.y, 2500, pointer.y], {
stroke: gridColor,
strokeWidth: 1,
selectable: true,
lockMovementX: true,
lockMovementY: true,
lockRotation: true,
lockScalingX: true,
lockScalingY: true,
strokeDashArray: [5, 2],
opacity: 0.3,
padding: 5,
name: 'tempGrid',
visible: isGridDisplay,
direction: 'horizontal',
})
canvas.add(tempGrid)
canvas.renderAll()
}
const defaultKeyboardEvent = (e) => {
if (e.key === 'Escape') {
console.log('defaultKeyboardEvent')
}
}
const addCanvasMouseEventListener = (eventType, handler) => {
canvas.off(eventType)
canvas.on(eventType, handler)
mouseEventListeners.current.push({ eventType, handler })
}
const removeAllMouseEventListeners = () => {
mouseEventListeners.current.forEach(({ eventType, handler }) => {
canvas.off(eventType)
})
mouseEventListeners.current.length = 0 // 배열 초기화
}
const addTargetMouseEventListener = (eventType, target, handler) => {
target.off(eventType)
target.on(eventType, handler)
mouseEventListeners.current.push({ eventType, handler })
}
/**
* document 이벤트의 경우 이 함수를 통해서만 등록
* @param eventType
* @param element
* @param handler
*/
const addDocumentEventListener = (eventType, element, handler) => {
removeDocumentEvent(eventType)
element.addEventListener(eventType, handler)
documentEventListeners.current.push({ eventType, element, handler })
}
/**
* document에 등록되는 event 제거
*/
const removeAllDocumentEventListeners = () => {
documentEventListeners.current.forEach(({ eventType, element, handler }) => {
element.removeEventListener(eventType, handler)
})
documentEventListeners.current.length = 0 // 배열 초기화
}
const removeMouseEvent = (type) => {
mouseEventListeners.current = mouseEventListeners.current.filter((event) => {
if (event.eventType === type) {
canvas.off(type, event.handler)
return false
}
return true
})
}
const removeDocumentEvent = (type) => {
documentEventListeners.current = documentEventListeners.current.filter((event) => {
if (event.eventType === type) {
document.removeEventListener(event.eventType, event.handler)
return false
}
return true
})
}
return {
addDocumentEventListener,
addCanvasMouseEventListener,
addTargetMouseEventListener,
removeAllMouseEventListeners,
removeAllDocumentEventListeners,
removeDocumentEvent,
removeMouseEvent,
removeMouseLine,
defaultMouseMoveEvent,
initEvent,
}
}