import { ModuleControl } from "queenjs"; import { reactive } from "vue"; import { EditorModule } from "../../module"; import { DesignComp } from "../../objects/DesignTemp/DesignComp"; import { Matrix } from "./matrix"; import { GroupActionCtrl } from "../TransferCtrl/GroupCtrl"; import { Point } from "./objects/point"; import { ObjsContainer } from "./ObjsContainer"; import Event from "./event"; import { DisplayObject } from "./objects/displayObject"; import { CompObject } from "./compObj"; import { each, eachRight } from "lodash"; import { string } from "vue-types"; import { Project, VectorLenth } from "./objects/mathUtils"; /** * 页面画布空间进行选择 */ const MODE_SEL_RECT = 1; const MODE_MOVING = 2; const MODE_ROTATE = 3; const MODE_SCALE = 4; const MODE_NONE = 0; export class SelectCtrl extends ModuleControl { transEvent = { startX: 0, startY: 0, offsetX: 0, offsetY: 0, width: 0, height: 0, }; transferStyle = reactive({ showGizmo: false, width: 0, height: 0, matrix: "matrix(1,0,0,1,0,0)", matrixInvert: "matrix(1,0,0,1,0,0)", }); selected: any[] = []; //选中的所有组件ids mouseDownSelects: any[] = []; //鼠标按下时选中的 pageEl?: HTMLElement; selCanvas = {} as HTMLCanvasElement; _downed = false; _selCtx = {} as CanvasRenderingContext2D; _state = MODE_SEL_RECT; _selDownX = 0; _selDownY = 0; _selBox = {} as DOMRect; _selCanvaseSize = { w: 0, h: 0 }; _downClientX = 0; _downClientY = 0; //groupCtrl = new GroupActionCtrl(this.module); bus = new Event(); viewport?:HTMLElement; initEvents(pageEl: HTMLElement, selCanvas: HTMLCanvasElement, viewport:HTMLElement) { this.viewport = viewport; this.pageEl = pageEl; this.selCanvas = selCanvas; const b = selCanvas.getBoundingClientRect(); selCanvas.width = b.width * 2; selCanvas.height = b.height * 2; this._selCtx = selCanvas.getContext("2d") as CanvasRenderingContext2D; this._selCanvaseSize.w = selCanvas.width * 2; this._selCanvaseSize.h = selCanvas.height * 2; document.addEventListener("mousedown", this.onDocMouseDown.bind(this)); document.addEventListener("mousemove", this.onDocMouseMove.bind(this)); document.addEventListener("mouseup", this.onDocMouseUp.bind(this)); window.addEventListener("resize", this.onResize.bind(this)); } _mouseDownFlag = ""; onDocMouseDown(e: MouseEvent) { if (!this.pageEl || !this.selCanvas) return; let box = this.pageEl.getBoundingClientRect(); const pageX = e.clientX - box?.left; const pageY = e.clientY - box?.top; const card = this.store.currStreamCard.$el; box = card.getBoundingClientRect(); const cardX = pageX; const cardY = e.clientY - box.top; const sel = this.selCanvas.getBoundingClientRect(); const selX = e.clientX - sel.left; const sely = e.clientY - sel.top; this._selDownX = selX; this._selDownY = sely; this._selBox = sel; this._downClientX = e.clientX; this._downClientY = e.clientY; console.log(cardX, selX, cardY, sely); this._downed = true; this._mouseDownFlag = this.getDivFlag(e.target as any); if (!this._mouseDownFlag) { //选框点击判断 let isClickSelRect = false; if (this.selected.length > 0) { isClickSelRect = this.objContainer?.testClick(cardX, cardY) as boolean; if (isClickSelRect) { this._state = MODE_MOVING; } } if (!isClickSelRect) { //判断是否有点击到card stream const comps = this.compClickTest(e); this.mouseDownSelects = comps; console.log("comps=>", comps); if (comps.length < 1) { const view = this.viewport?.getBoundingClientRect() as any; const isOut = (e.clientX < view.left || e.clientX > (view.right) || e.clientY < view.top || e.clientY > view.bottom) if (!isOut) { this._state = MODE_SEL_RECT; } } else { this._state = MODE_MOVING; const obj = this.compMap[comps[0].id]; this.selecteObjs([new CompObject(obj)]); } } } else if (this._mouseDownFlag == "rotate") { this._state = MODE_ROTATE; } else if (this._mouseDownFlag == "move") { this._state = MODE_MOVING; } else if (this._mouseDownFlag.indexOf("scale") > -1) { this._state = MODE_SCALE; } this._movePreClientX = this._downClientX; this._movePreClientY = this._downClientY; } getDivFlag(div: HTMLElement) { let c: any = div; if (!c) return; let i = 0; do { if (c.editable) return c.editable; c = div.parentElement; i += 1; if (i > 3) { return; } } while (c); } compClickTest(e: MouseEvent) { const cards = this.store.streamCardIds; let n = cards.length; const compMap = this.store.designData.compMap; //@ts-ignore const pbox = this.pageEl.getBoundingClientRect(); const pageX = e.clientX - pbox?.left; const Out = []; while (n--) { const cardComp = compMap[cards[n]]; const box = cardComp.$el.getBoundingClientRect(); const cardY = e.clientY - box.top; const cardChilds = cardComp.children.default || []; for (const key of cardChilds) { const c = compMap[key]; const m = Matrix.createFromDiv(c.$el); const localp = m.applyInverse(new Point(pageX, cardY)); const cw = this.helper.designSizeToPx(c.layout.size?.[0] as number); const ch = this.helper.designSizeToPx(c.layout.size?.[1] as number); const out = localp.x < 0 || localp.x > cw || localp.y < 0 || localp.y > ch; if (!out) { Out.push({ id: key, el: c.$el, cardX: pageX, cardY: cardY, cardId: cards[n], startMatrix: m, }); } } } return Out; } streamCardClickTest(e: MouseEvent) { const cards = this.store.streamCardIds; let n = cards.length; const compMap = this.store.designData.compMap; //@ts-ignore const pbox = this.pageEl.getBoundingClientRect(); const pageX = e.clientX - pbox?.left; if (pageX < 0 || pageX > pbox.width) return ""; while (n--) { const card = compMap[cards[n]]; const box = card.$el.getBoundingClientRect(); if (e.clientY >= box.top && e.clientY <= box.bottom) return { id: cards[n], x: pageX, y: e.clientY - box.top }; } return ""; } _moveSelectUpdated = false; updateSelects() { if (this._moveSelectUpdated) return; this._moveSelectUpdated = true; //鼠标按下并移动中 修正当前选中的对象 if (this.selected.length < 1) { //没有被选中的 this.selected = this.mouseDownSelects; } else { //当前有选中的 let findSeleted = false; let n = this.selected.length; if (this.mouseDownSelects.length > 0) { while (n--) { const item = this.mouseDownSelects.find( (item) => item.id == this.selected[n].id ); if (item) findSeleted = true; } } if (!findSeleted) { this.selected = this.mouseDownSelects; } } if (this.selected.length > 0) { this._state = MODE_MOVING; } } translate(xOffset:number, yOffset:number) { const objContainer = this.objContainer as ObjsContainer; objContainer.translate(xOffset, yOffset); this.upgateGizmoStyle(); } movingMousemove(e: MouseEvent) { const objContainer = this.objContainer as ObjsContainer; objContainer.translate( e.clientX - this._movePreClientX, e.clientY - this._movePreClientY ); this.upgateGizmoStyle(); } _movePreClientX = 0; _movePreClientY = 0; onDocMouseMove(e: MouseEvent) { if (!this.pageEl) return; if (!this._downed) { this.checkHover(); return; } switch (this._state) { case MODE_SEL_RECT: //选框模式 this.drawSelRect(e); break; case MODE_MOVING: this.movingMousemove(e); break; case MODE_ROTATE: this.rotateMousemove(e); break; case MODE_SCALE: this.scaleMousemove(e); } this._movePreClientY = e.clientY; this._movePreClientX = e.clientX; } get compMap() { return this.store.designData.compMap; } onDocMouseUp(e: MouseEvent) { let isClick = false; const dx = Math.abs(e.clientX - this._downClientX); const dy = Math.abs(e.clientY - this._downClientY); if (dx < 2 && dy < 2) { isClick = true; } if (isClick) { this._state = MODE_NONE; if (this.mouseDownSelects.length < 1) this.selecteObjs([]); else { const objs = this.mouseDownSelects.map( (item) => new CompObject(this.compMap[item.id]) ); this.selecteObjs(objs); } } console.log("up"); if (this._state == MODE_SEL_RECT && !isClick) { //选择空间转 streamCard空间 const card = this.store.currStreamCard; const box = card.$el.getBoundingClientRect(); this.rectSelect( this._lastSelRect[0] - box.left, this._lastSelRect[1] - box.top, this._lastSelRect[2], this._lastSelRect[3] ); } if (this._state == MODE_ROTATE) { this.rotateMouseUp(e); } if (this._state == MODE_SCALE) { this.scaleMouseUp(e); } this._state = MODE_NONE; this._downed = false; this._moveSelectUpdated = false; this._selCtx?.clearRect( 0, 0, this._selCanvaseSize.w, this._selCanvaseSize.h ); this.upgateGizmoStyle(); } rectSelect(x: number, y: number, width: number, height: number) { const childs = this.compMap[this.store.currStreamCardId].children.default || []; let n = childs.length; const outs = []; while (n--) { const o = new CompObject(this.compMap[childs[n]]); if (o.testRect({ x, y, w: width, h: height }, true)) { //相交 outs.push(o); } } console.log(outs); this.selecteObjs(outs); } upgateGizmoStyle() { if (this.selected.length < 1) { this.transferStyle.showGizmo = false; return; } this.transferStyle.showGizmo = false; const selector = this.objContainer as ObjsContainer; if (!selector) { return; } this.transferStyle.showGizmo = true; let obj = selector.parent; let w = selector.rect.width, h = selector.rect.height; let tmp = new Matrix(); tmp.copyFrom(obj.worldTransform); // tmp.scale(0.5 , 0.5); let matrix = `matrix(${tmp.a},${tmp.b},${tmp.c},${tmp.d},${tmp.tx},${tmp.ty})`; tmp.invert(); let matrixInvert = `matrix(${tmp.a},${tmp.b},${tmp.c},${tmp.d},0,0)`; this.transferStyle.width = w; this.transferStyle.height = h; this.transferStyle.matrix = matrix; this.transferStyle.matrixInvert = matrixInvert; } selectId(id: string) { //选中ids之前 id对应组件必须已经渲染 console.log("selectId=>", id); } _lastSelRect = [0, 0, 0, 0]; drawSelRect(e: MouseEvent) { const ctx = this._selCtx; const dx = this._selDownX; const dy = this._selDownY; const currX = e.clientX - this._selBox.left; const currY = e.clientY - this._selBox.top; const x = Math.min(currX, dx), y = Math.min(dy, currY); ctx.clearRect(0, 0, this._selCanvaseSize.w, this._selCanvaseSize.h); ctx.fillStyle = "rgba(232, 139, 0, 0.16)"; const w = Math.abs(currX - dx); const h = Math.abs(currY - dy); ctx.fillRect(x * 2, y * 2, w * 2, h * 2); ctx.lineWidth = 2; ctx.strokeStyle = "#E88B00"; ctx.strokeRect(x * 2, y * 2, w * 2, h * 2); this._lastSelRect[0] = x + this._selBox.left; this._lastSelRect[1] = y + this._selBox.top; this._lastSelRect[2] = w; this._lastSelRect[3] = h; } checkHover() { this.selCanvas; } onResize() { const b = this.selCanvas.getBoundingClientRect(); this.selCanvas.width = b.width * 2; this.selCanvas.height = b.height * 2; this._selCtx = this.selCanvas.getContext("2d") as CanvasRenderingContext2D; this._selCanvaseSize.w = b.width * 2; this._selCanvaseSize.h = b.height * 2; } // checkIntersect(compId: string, e: MouseEvent) { const currCard = this.store.currStreamCard.$el; const comp = this.store.designData.compMap[compId]; //排除坐标没有在streamCard空间内的坐标 //把当前的card坐标转为 组件的自己local坐标判断是否在方框外面 const cardBox = currCard.getBoundingClientRect(); const cardX = e.clientX - cardBox.left; const cardY = e.clientY - cardBox.top; //const m = Matrix.createFromComp(comp.layout.transform) } objContainer?: ObjsContainer; selecteObjs(objs: any[], ContainerBox?: ObjsContainer) { if (this.selected.length == 0 && objs.length == 0) return; if ( this.selected.length == 1 && objs.length == 1 && this.selected[0] == objs[0] ) return; if (objs.length == 1) { this.actions.pickComp(objs[0].comp.id); } // objs = this.getSceneObjOrderArr(objs); const preObjContainer = this.objContainer; if (this.objContainer) { this.objContainer.destroy(); this.objContainer = undefined; } let newObjContainer = undefined; if (objs.length > 0 && objs[0]) { newObjContainer = ContainerBox ? ContainerBox : new ObjsContainer(objs); if (ContainerBox) { objs.forEach((obj) => { ContainerBox.parent.addChildWorldNoChange(obj); }); ContainerBox.selected = objs; ContainerBox.parent.updateTransform(); } } this.objContainer = newObjContainer; const pre = this.selected.slice(0); this.selected = objs; // if (history) { // this.editor.history.record({ // undo: () => { // this.selected = pre; // if (preObjContainer) { // let parent = preObjContainer.parent; // pre.forEach(obj => { // parent.addChildWorldNoChange(obj); // }); // parent.updateTransform(); // this.objContainer = preObjContainer; // } else { // this.objContainer = null; // } // this.emitChange(); // }, redo: () => { // this.selected = objs; // if (preObjContainer) { // preObjContainer.destroy(); // } // if (newObjContainer) { // let parent = newObjContainer.parent; // objs.forEach(obj => { // parent.addChildWorldNoChange(obj); // }); // parent.updateTransform(); // this.objContainer = newObjContainer; // } else { // this.objContainer = null; // } // this.emitChange(); // } // }) // } this.emitChange(); this.upgateGizmoStyle(); return this.selected; } emitChange() { const selected = this.selected; if (selected.length && selected[0]) { this.bus.emit("showProps", selected[0].from); } else { this.bus.emit("showProps"); } this.bus.emit("selectedChange"); } rotateCenter?: { x: number; y: number }; ratatePre = 0; objinitAngleRad = 0; rotateCmd = false; lastRad = 0; rotateMousemove(e: MouseEvent) { const card = this.store.currStreamCard; const rect = card.$el.getBoundingClientRect(); let StartX = e.clientX - rect.left; let StartY = e.clientY - rect.top; const objContainer = this.objContainer as ObjsContainer; //获取当前屏幕坐标和选框中心点坐标,计算旋转值 if (!this.rotateCenter) { //let rect = this.objContainer.parent.getBounds(false); let center = objContainer.setPivot(4); this.rotateCenter = center; let vec = { x: StartX - center.x, y: StartY - center.y }; let angle = Math.atan2(vec.y, vec.x); if (angle < 0) angle += 2 * Math.PI; this.ratatePre = angle; this.objinitAngleRad = objContainer.parent.rotation; this.rotateCmd = true; return; } let center = this.rotateCenter; let vec = { x: StartX - center.x, y: StartY - center.y }; let angle = Math.atan2(vec.y, vec.x); if (angle < 0) angle += 2 * Math.PI; let dta = this.objinitAngleRad + angle - this.ratatePre; if (e.shiftKey) { //规整到0 90 180 270 if (dta < 0) dta += 2 * Math.PI; let Deg45 = Math.PI / 4.0; let Deg90 = Math.PI / 2.0; let Deg135 = Deg45 * 3; let Deg225 = Deg45 * 5; let Deg270 = Deg45 * 6; let Deg315 = Deg45 * 7; if (dta < Deg45) { dta = 0; } else if (dta < Deg135) { dta = Deg90; } else if (dta < Deg225) { dta = Math.PI; } else if (dta < Deg315) { dta = Deg270; } else { dta = 0; } } this.lastRad = dta; console.log("rotate=>", dta); objContainer.rotate(dta); // this.emit("translateChange", this.objContainer) this.upgateGizmoStyle(); } rotateMouseUp(e: MouseEvent) { this.rotateCenter = undefined; if (!this.rotateCmd) return; let scope = this; let last = this.lastRad; let initrad = scope.objinitAngleRad; // this.editor.history.record({undo:function(){ // scope.objContainer.setPivot(4); // scope.objContainer.rotate( initrad ); // scope.emitChange(); // }, redo:function(){ // scope.objContainer.setPivot(4); // scope.objContainer.rotate( last ); // scope.emitChange(); // }}); this.rotateCmd = false; } //缩放选中的对象 scalePivot?: any; scaleIndex = 0; mainAxisVector = { x: 0, y: 0 }; initScale = { x: 1, y: 1 }; mainAxisVectorLenth = 0; xAxisVector = { x: 1, y: 1 }; xAxisVectorLength = 0; yAxisVector = { x: 1, y: 1 }; yAxisVectorLength = 0; scaleCmd = false; lastScale = { x: 1, y: 1 }; scaleMousemove(event: MouseEvent) { let dirIndexs = [ "scaleBottomright", "scaleBottomleft", "scaleTopleft", "scaleTopright", ]; let dirOrth = ["scaleright", "scaleleft", "scalebottom", "scaletop"]; const rect = this.store.currStreamCard.$el.getBoundingClientRect(); let StartX = event.clientX - rect.left; let StartY = event.clientY - rect.top; const objContainer = this.objContainer as ObjsContainer; //获取当前屏幕坐标和选框中心点坐标,计算旋转值 if (!this.scalePivot) { let dir = this._mouseDownFlag; let scaleIndex = dirIndexs.indexOf(dir); if (scaleIndex == -1) { scaleIndex = dirOrth.indexOf(dir); if (scaleIndex == 2) scaleIndex = 0; } let pivot = objContainer.setPivot(scaleIndex); this.scaleIndex = scaleIndex; this.scalePivot = pivot; this.mainAxisVector = { x: StartX - pivot.x, y: StartY - pivot.y }; let scale = objContainer.parent.scale; this.initScale = { x: scale.x, y: scale.y }; this.mainAxisVectorLenth = VectorLenth( this.mainAxisVector.x, this.mainAxisVector.y ); let ret = objContainer.getPivotXY(scaleIndex); this.xAxisVector = ret.x; this.xAxisVectorLength = VectorLenth(ret.x.x, ret.x.y); this.yAxisVector = ret.y; this.yAxisVectorLength = VectorLenth(ret.y.x, ret.y.y); return; } this.scaleCmd = true; let center = this.scalePivot; let vec = { x: StartX - center.x, y: StartY - center.y }; if (event.shiftKey) { //按住shift 自由缩放 let dtaX = Project(vec, this.xAxisVector) / this.xAxisVectorLength; let dtaY = Project(vec, this.yAxisVector) / this.yAxisVectorLength; this.lastScale.x = dtaX * this.initScale.x; this.lastScale.y = dtaY * this.initScale.y; objContainer.scale(this.lastScale.x, this.lastScale.y); } else { let mainVec = this.mainAxisVector; let dtaScale = Project(vec, mainVec) / this.mainAxisVectorLenth; let i = dirOrth.indexOf(this._mouseDownFlag); if (i == -1) { this.lastScale.x = dtaScale * this.initScale.x; this.lastScale.y = dtaScale * this.initScale.y; objContainer.scale(this.lastScale.x, this.lastScale.y); } else if (i == 0 || i == 1) { this.lastScale.x = dtaScale * this.initScale.x; objContainer.scaleX(this.lastScale.x); } else if (i == 2 || i == 3) { this.lastScale.y = dtaScale * this.initScale.y; objContainer.scaleY(this.lastScale.y); } } this.upgateGizmoStyle(); } scaleMouseUp(event: MouseEvent) { this.scalePivot = undefined; if (this.scaleCmd) { let preScale = { x: this.initScale.x, y: this.initScale.y }; let scaleIndex = this.scaleIndex; let lastScale = { x: this.lastScale.x, y: this.lastScale.y }; // this.editor.history.record({ // undo:()=>{ // this.objContainer.setPivot( scaleIndex ); // this.objContainer.scale(preScale.x, preScale.y); // this.emitChange(); // }, // redo:()=>{ // this.objContainer.setPivot( scaleIndex ); // this.objContainer.scale(lastScale.x, lastScale.y); // this.emitChange(); // } // }); this.scaleCmd = false; // this.emit("objSizeChanged"); // this.editor.draw(); } // this.emitTransformed = false; } }