import { defineComponent, onMounted , ref, effect} from "vue"; import { string } from "vue-types"; import { useCompData } from "."; import { useEditor } from "../../../.."; import { View } from "../View"; import { CompUI } from "../.."; import { values } from "lodash"; import { Angle, VectorLenth } from "@/modules/editor/controllers/SelectCtrl/objects/mathUtils"; import { CompObject } from "@/modules/editor/controllers/SelectCtrl/compObj"; import { ObjsContainer } from "@/modules/editor/controllers/SelectCtrl/ObjsContainer"; function findNearestPoint(points: number[][], w:number, h:number, sx:number, sy:number) { const n = points.length; let minv = 100000; let minIndex = -1; const positions = [] for(let i=0; i (x + 10) || sy < (y -10) || sy > (y+10) ) ) { return {clicked: true, index: i}; } } } let preIndex = minIndex -1; if (preIndex < 0 ) preIndex = n-1; let afterIndex = minIndex + 1; if ( afterIndex >= n) afterIndex = 0; const currV = {x:sx-positions[minIndex][0], y:sy-positions[minIndex][1]}; const preV = {x:positions[preIndex][0]-positions[minIndex][0], y:positions[preIndex][1]-positions[minIndex][1]} const afterV = {x:positions[afterIndex][0]-positions[minIndex][0], y:positions[afterIndex][1]-positions[minIndex][1]} const a1 = Angle(currV, preV) const a2 = Angle(currV, afterV); if (a1 < a2) { return {index: preIndex}; } return {index: minIndex}; } function normalize(v1:{x:number,y:number}) { let x = v1.x; let y = v1.y; const s = Math.sqrt(x*x + y*y); v1.x = x / s; v1.y = y / s; return v1; } type PointItem = { x: number; y: number; cx: number; cy: number; preLineX: number; //界面显示控制点 preLineY: number; //界面显示控制点 aftLineX: number; //界面显示控制点 aftLineY: number; //界面显示控制点 preCirleX: number; //界面显示控制点 preCirleY: number; //界面显示控制点 aftCirleX: number; //界面显示控制点 aftCirleY: number; //界面显示控制点 isLine: boolean; } function parsePoints( points:number[][], width:number, height:number) { let out :PointItem[] = []; let n = points.length; for(let i=0; i (px + rect) || y < (py -rect) || y > (py+rect) ) ) { pIndex = n; break; } if(!(x < (p.aftCirleX -rect) || x > (p.aftCirleX + rect) || y < (p.aftCirleY -rect) || y > (p.aftCirleY+rect) ) ) { pIndex = n; clickCurve = true; break; } if(!(x < (p.preLineX -rect) || x > (p.preLineX + rect) || y < (p.preLineY -rect) || y > (p.preLineY+rect) ) ) { pIndex = n; clickLine = true; break; } } if (pIndex == -1) { //ming const dta = 5 if(!(x < (20 - dta) || x > (20 + dta) || y < (20 -dta) || y > (20 + dta) ) ) { moving = true; } } return {clickLine, clickCurve, pIndex, moving, x, y}; } export const Component = defineComponent({ props: { compId: string().isRequired, }, setup(props) { const { helper, controls , store} = useEditor(); const data = useCompData(props.compId); const canvasRef = ref(); onMounted(()=>{ canvasRef.value?.addEventListener("dblclick", function(e:MouseEvent){ const x = helper.pxToDesignSize(e.offsetX ) const y = helper.pxToDesignSize(e.offsetY) const points = data.value.points as number[][]; const w = data.layout.size[0]; const h = data.layout.size[1]; //判断直线相交求解点到直线的距离 const ret = findNearestPoint(data.value.points, w , h, x, y); //判断是否 console.log("dbclick=>xxxxx", ret); if (ret.clicked) {//点击删除 points.splice(ret.index, 1); if (points.length < 2) { return; } } else { let next = ret.index + 1; if (next >= points.length ) { next = 0; } //计算 const x1 = x / w, y1= y / h; points.splice(ret.index+1, 0, [x1, y1 , (x1 + points[next][0])/ 2.0 , (y1 + points[next][1]) / 2.0 ]); } draw(); }) canvasRef.value?.addEventListener("mousedown", function(e:MouseEvent){ if (store.currCompId != props.compId) return; const el = canvasRef.value as HTMLCanvasElement; const width = data.layout.size[0]; const height = data.layout.size[1]; const ps = parsePoints(data.value.points as number[][], width, height) const ret = clickTest(ps, e, helper.pxToDesignSize); if (ret.pIndex == -1 && !ret.moving) return; const dragingX = ret.x; const dragingY = ret.y; const points = data.value.points as number[][]; const initValues = ret.pIndex != -1 ? points[ret.pIndex].slice(0) : {} as any; const initPoint = ret.pIndex != -1 ? {...ps[ret.pIndex]} : {} as any; const initPoints = points.map(item=>item.slice(0)); e.preventDefault(); e.stopPropagation(); const isDragingIndex = ret.pIndex; const box = controls.selectCtrl.getCurrCardViewPortBox(); const mvingPoint = !ret.clickCurve && !ret.clickLine let initControlpLen :number | undefined = undefined; const move = function (e:MouseEvent){ e.preventDefault(); e.stopPropagation(); const obj = controls.selectCtrl.objContainer as ObjsContainer; const cardX = e.clientX - box.left, cardY = e.clientY - box.top; const p = {x: cardX, y:cardY} as any; obj.parent.worldTransform.applyInverse(p, p); obj.parent.updateTransform(); const x = helper.pxToDesignSize(p.x), y = helper.pxToDesignSize(p.y); const offx = ( x - dragingX) / width; const offy = ( y - dragingY) / height; if (ret.moving) {//移动所有 points.forEach((p,i)=>{ p[0] = initPoints[i][0] + offx; p[1] = initPoints[i][1] + offy; p[2] = initPoints[i][2] + offx; p[3] = initPoints[i][3] + offy; }) draw(); return; } if (mvingPoint) { points[isDragingIndex][0] = initValues[0] + offx; points[isDragingIndex][1] = initValues[1] + offy; points[isDragingIndex][2] = initValues[2] + offx; points[isDragingIndex][3] = initValues[3] + offy; } else if (ret.clickCurve) {//移动曲线 if (initControlpLen == undefined) { //初始控制点的长度 initControlpLen = VectorLenth( initPoint.cx - initPoint.x, initPoint.cy - initPoint.y); } const half = initControlpLen / 2.0; //当前鼠标到远点的长度 const len = half + VectorLenth( x - initPoint.x, y - initPoint.y) / 90.0 * half; let v = normalize({x: x - initPoint.x, y: y - initPoint.y}); if (e.shiftKey) {//一条直线 let nextIndex = ret.pIndex + 1; if (nextIndex >= ps.length) { nextIndex = 0; } v = normalize({x: ps[nextIndex].x - initPoint.x, y: ps[nextIndex].y - initPoint.y}); } points[isDragingIndex][2] = (initPoint.x + v.x * len) / width; points[isDragingIndex][3] = (initPoint.y + v.y * len) / height; } draw(); } if ( !ret.clickCurve && !ret.clickLine && !ret.moving) { el.addEventListener("mouseleave", function(){ document.removeEventListener("mousemove", move); }, {once: true}) } document.addEventListener("mousemove", move) document.addEventListener("mouseup", function(e:MouseEvent){ document.removeEventListener("mousemove", move); }, {once: true}); }, ) effect(draw); }) function draw() { const canvas = canvasRef.value as HTMLCanvasElement; if (!canvas) return; const ctx = canvasRef.value?.getContext("2d") as CanvasRenderingContext2D; const width = data.layout.size[0]; const height = data.layout.size[1]; canvas.width = Math.ceil(Math.max(1, width)); canvas.height = Math.ceil(Math.max(1, height)); ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.lineWidth = data.value.lineWidth; if (data.value.reverseFill) { ctx.fillStyle = data.value.fillColor; ctx.fillRect(0, 0, canvas.width, canvas.height); } ctx.setLineDash([]); ctx.strokeStyle = data.value.lineColor; const padding = 0.2; ctx.lineJoin = "round"; ctx.beginPath(); let points = data.value.points as number[][]; if (data.value.points.length == 0) { data.value.points = [[padding, 0.5, 0.4, 0.1], [1 - padding, 0.5, 0.5, 0.85]]; points = data.value.points; } const ps = parsePoints(data.value.points, width, height); ctx.save(); ps.forEach((p, index)=>{ const x = p.x const y = p.y if (index == 0) { ctx.moveTo(x, y) } else { const preIndex = index -1; const prep = ps[preIndex]; if (!prep.isLine) { ctx.quadraticCurveTo( prep.cx, prep.cy, x, y); } else { ctx.lineTo(x, y); } } }) if (data.value.isClose) { const prep = ps[points.length-1]; const lastPoint = ps[0]; if (!prep.isLine) { ctx.quadraticCurveTo( prep.cx, prep.cy, lastPoint.x, lastPoint.y); } ctx.closePath(); } if (data.value.lineWidth !== 0) { ctx.stroke(); } if (data.value.isFill) { let bColor = data.value.fillColor; if (!bColor) bColor = data.value.lineColor; ctx.fillStyle = bColor; if (data.value.reverseFill) { ctx.fillStyle = "red"; // 设置剪切区域 ctx.clip(); // 将当前路径指定的区域颜色扣除 ctx.globalCompositeOperation = "destination-out"; } ctx.fill(); } ctx.restore(); //绘制辅助gizmo if (store.isEditMode && store.currCompId == props.compId) { ctx.lineWidth = 2; ctx.strokeStyle = "orange"; ctx.setLineDash([10, 5]) ps.forEach((p, index)=>{ if (index == 0) { ctx.beginPath(); ctx.moveTo(p.x, p.y); } else { ctx.lineTo(p.x, p.y); } }); if (data.value.isClose && ps.length > 2) { ctx.lineTo(ps[0].x, ps[0].y); } ctx.stroke(); ctx.setLineDash([]) ps.forEach((p)=>{ const x = p.x const y = p.y ctx.fillStyle = "red" ctx.beginPath(); ctx.arc(x, y, 5, 0, Math.PI*2); ctx.fill(); ctx.beginPath() // ctx.setLineDash([5,5]) ctx.moveTo(p.preLineX, p.preLineY); ctx.lineTo(p.aftLineX, p.aftLineY); ctx.stroke(); // ctx.beginPath() // ctx.arc(p.preCirleX, p.preCirleY, r, 0, Math.PI*2) // ctx.stroke(); const r = 10 ctx.beginPath() ctx.arc(p.aftCirleX, p.aftCirleY, r, 0, Math.PI*2) ctx.fill(); }) ctx.beginPath() ctx.arc(20, 20 , 5, 0, Math.PI*2) ctx.fill(); } } return () => ( ); }, });