import * as createjs from "createjs-module" import { DragLayer, DragLayerEvent, SelectState } from "./DragLayer" import { AnchorProp, Control, ControlData, ControlTypeInfo, DesignerApi, PositionAndSizeProperty } from "../types" import { EditorProperties } from "@iplusplus/y-model" import { max, min } from "../utils" import { GroupControl, GroupControlData } from "../internal-controls/GroupControl" import { ContainerControl, ContainerControlData, ContainerControlPointsTypeInfo } from "../internal-controls/ContainerControl" export class ControlLayer extends createjs.Container { rightMax: number bottomMax: number constructor( rightMax: number, bottomMax: number, public api: DesignerApi, ) { super() this.rightMax = rightMax this.bottomMax = bottomMax } changeSize(width: number, height: number) { this.rightMax = width; this.bottomMax = height; } addControl(control: Control, x?: number, y?: number, needSelect?: boolean) { control.initControl(this.api) const dragLayer = new DragLayer(control) dragLayer.x = x || 0 dragLayer.y = y || 0 dragLayer.on("select", evt => { const e = evt as DragLayerEvent this.confirmSelect(e.dragerLayer, e.isAdd!) }) dragLayer.on("selectSame", evt => { const e = evt as DragLayerEvent this.confirmSelectSame(e.dragerLayer, e.isAdd!) }) dragLayer.on("move", evt => { const e = evt as DragLayerEvent this.moveControls(e.movementX!, e.movementY!) }) dragLayer.on("resize", evt => { const e = evt as DragLayerEvent let newSizeX = e.stageX! - e.dragerLayer.x let newSizeY = e.stageY! - e.dragerLayer.y newSizeX = Math.max(newSizeX, 10) newSizeY = Math.max(newSizeY, 10) e.dragerLayer.resize(newSizeX, newSizeY) }) this.addChild(dragLayer) if (needSelect) { dragLayer.setSelectState("mulityselect") this.fireSelectedChanged() } } moveControl(dragerLayer: DragLayer, movementX: number, movementY: number) { dragerLayer.x += movementX dragerLayer.y += movementY } moveControls(movementX: number, movementY: number) { this.allControlExecute(d => { if (d.selectState === "select" || d.selectState === "mulityselect") { d.x += movementX d.y += movementY this.checkPosition(d) } }) } checkPosition = (d: DragLayer) => { d.assControl.size.width = Math.max(10, d.assControl.size.width) d.assControl.size.height = Math.max(10, d.assControl.size.height) d.x = Math.max(0, d.x) d.x = Math.min(d.x, this.rightMax - d.assControl.size.width) d.y = Math.max(0, d.y) d.y = Math.min(d.y, this.bottomMax - d.assControl.size.height) } checkControlTypeIsSame(control1: Control, control2: Control) { if (control1.controlTypeInfo.typeName !== control2.controlTypeInfo.typeName) { return false } if (control1.controlTypeInfo.typeName === "EmbeddedDivChart") { return false } if (control1.controlTypeInfo.typeName !== "Group" && control1.controlTypeInfo.typeName !== "Container") return true return control1.customName !== undefined && control1.customName === control2.customName } confirmSelectSame(dragLayer: DragLayer, isAdd: boolean) { this.drags().forEach(d => { if (this.checkControlTypeIsSame(dragLayer.assControl, d.assControl)) { d.setSelectState("mulityselect") } else if (!isAdd) { d.setSelectState("noselect") } }) dragLayer.setSelectState("select") this.fireSelectedChanged() } confirmSelect(dragLayer: DragLayer, isAdd: boolean) { const nowState = dragLayer.selectState if (isAdd) { if (nowState === "noselect") { dragLayer.setSelectState("mulityselect") } else { dragLayer.setSelectState("noselect") } } else if (nowState === "noselect") { this.allControlExecute(d => d.setSelectState("noselect")) dragLayer.setSelectState("select") } else if (nowState === "mulityselect") { this.allControlExecute(d => { if (d.selectState === "select") d.setSelectState("mulityselect") }) dragLayer.setSelectState("select") } this.fireSelectedChanged() } allControlExecute(fun: (dragLayer: DragLayer) => void) { for (let i = 0; i < this.numChildren; i++) { fun(this.getChildAt(i) as DragLayer) } } selectControl(x1: number, y1: number, x2: number, y2: number, isAdd: boolean) { const xx1 = Math.min(x1, x2) const xx2 = Math.max(x1, x2) const yy1 = Math.min(y1, y2) const yy2 = Math.max(y1, y2) this.allControlExecute(d => { const outb = xx1 > d.x + d.assControl.size.width || yy1 > d.y + d.assControl.size.height || d.x > xx2 || d.y > yy2 if (outb) { if (!isAdd) { d.setSelectState("noselect") } } else { d.setSelectState("mulityselect") } }) this.fireSelectedChanged() } fireSelectedChanged() { const selectedIds: number[] = [] let selectedId: number | undefined = undefined let lastSelected: DragLayer | undefined ; (this.children as DragLayer[]).forEach(d => { if (d.selectState === "mulityselect" || d.selectState === "select") { selectedIds.push(d.id) lastSelected = d } if (d.selectState === "select") { if (selectedId) { d.setSelectState("mulityselect") } else { selectedId = d.id } } }) if (!selectedId && lastSelected) { selectedId = lastSelected.id lastSelected.setSelectState("select") } this.dispatchEvent({ type: "selectedChanged", source: this, selectedIds, selectedId }) } getControlInfo(id: number): | { typeInfo: ControlTypeInfo properties: EditorProperties data: ControlData } | undefined { const drag = this.getDragById(id) return drag ? { typeInfo: drag.assControl.controlTypeInfo, properties: drag.assControl.getProperties(), data: drag.assControl.getControlData(), } : undefined } setSelectedControlProperty(newProperties: { [index: string]: unknown }) { this.selectedControls().forEach(c => { c.setProperty(newProperties) c.reDraw() }) } applyCustomOperationToSelectedControls(fun: (control: Control) => void) { this.selectedControls().forEach(fun) } getControlsData(): ControlData[] { return (this.children as DragLayer[]).map(d => { const result = d.assControl.getControlData() result.x = d.x result.y = d.y return result }) } // private controls(): Control[] { // return this.children.map(d => (d as DragLayer).assControl) // } private drags(): DragLayer[] { return this.children as DragLayer[] } private selectedDrags(): DragLayer[] { return (this.children as DragLayer[]).filter(d => d.selectState === "select" || d.selectState === "mulityselect") } private selectedDrag(): DragLayer | undefined { const d = (this.children as DragLayer[]).filter(d => d.selectState === "select") if (d.length === 1) return d[0] return undefined } getSelectedDragId() { const drag = this.selectedDrag() if (drag === undefined) return 0 return drag.id } private selectedControls(): Control[] { return this.selectedDrags().map(d => d.assControl) } getDragById(id: number) { return (this.children as DragLayer[]).find(d => d.id === id) } getDragsBySelectState(s: SelectState) { return (this.children as DragLayer[]).filter(d => d.selectState === s) } deleteControls() { this.selectedDrags().forEach(this.deleteDrag) } setControlsLayer(layer: "top" | "bottom") { let idx = -1 if (layer === "top") { for (let i = 0; i < this.numChildren; i++) { if (idx === -1) { const curctrl = this.getChildAt(i) as DragLayer if (curctrl.selectState === "select") { idx = i } } else { this.swapChildrenAt(i - 1, i) } } } else if (layer === "bottom") { for (let i = this.numChildren - 1; i > -1; i--) { if (idx === -1) { const curctrl = this.getChildAt(i) as DragLayer if (curctrl.selectState === "select") { idx = i } } else { this.swapChildrenAt(i + 1, i) } } } } alignControl(alignType: "left" | "right" | "top" | "bottom" | "hcenter" | "vcenter") { const selectedDrag = this.selectedDrag() if (!selectedDrag) return const asec = 400 const aEase = createjs.Ease.cubicInOut const drags = this.getDragsBySelectState("mulityselect") if (drags.length === 0) return drags.forEach(d => { let newX = d.x let newY = d.y switch (alignType) { case "left": newX = selectedDrag.x break case "right": newX = selectedDrag.x + selectedDrag.assControl.size.width - d.assControl.size.width break case "top": newY = selectedDrag.y break case "bottom": newY = selectedDrag.y + selectedDrag.assControl.size.height - d.assControl.size.height break case "hcenter": newX = selectedDrag.x + selectedDrag.assControl.size.width / 2 - d.assControl.size.width / 2 break case "vcenter": newY = selectedDrag.y + selectedDrag.assControl.size.height / 2 - d.assControl.size.height / 2 break } createjs.Tween.get(d).to( { x: newX, y: newY, }, asec, aEase, ) }) } resizeControls(resizeType: "width" | "height" | "both") { const selectedDrag = this.selectedDrag() if (!selectedDrag) return const drags = this.getDragsBySelectState("mulityselect") if (drags.length === 0) return drags.forEach(d => { if (resizeType === "width" || resizeType === "both") { d.assControl.size.width = selectedDrag.assControl.size.width } if (resizeType === "height" || resizeType === "both") { d.assControl.size.height = selectedDrag.assControl.size.height } d.reDraw() }) } distributeControls(dType: "h" | "v") { const asec = 400 const aEase = createjs.Ease.cubicInOut const selectControls = this.selectedDrags() if (selectControls.length < 3) return if (dType === "h") { selectControls.sort((a, b) => a.x + a.assControl.size.width / 2 - b.x + b.assControl.size.width / 2) let bMin = 99999 let bMax = 0 let cWidth = 0 selectControls.forEach(d => { bMin = Math.min(bMin, d.x) bMax = Math.max(bMax, d.x + d.assControl.size.width) cWidth += d.assControl.size.width }) const interval = (bMax - bMin - cWidth) / (selectControls.length - 1) selectControls.forEach(d => { createjs.Tween.get(d).to( { x: bMin, }, asec, aEase, ) bMin = bMin + d.assControl.size.width + interval }) } else if (dType === "v") { selectControls.sort((a, b) => a.y + a.assControl.size.height / 2 - b.y + b.assControl.size.height / 2) let bMin = 99999 let bMax = 0 let cHeight = 0 selectControls.forEach(d => { bMin = Math.min(bMin, d.y) bMax = Math.max(bMax, d.y + d.assControl.size.height) cHeight += d.assControl.size.height }) const interval = (bMax - bMin - cHeight) / (selectControls.length - 1) selectControls.forEach(d => { createjs.Tween.get(d).to( { y: bMin, }, asec, aEase, ) bMin = bMin + d.assControl.size.height + interval }) } } lockControls() { this.selectedDrags().forEach(d => { d.assControl.isLocked = true d.setSelectState("noselect") }) this.fireSelectedChanged() } getLockedDrags() { return this.drags().filter(d => d.assControl.isLocked) } getControlId(ctrl: Control) { return ctrl.getDisplayObject().id } getControlTitle(ctrl: Control) { const metaInfo = ctrl.getMeta() if (ctrl.customName) { return ctrl.customName } if (metaInfo) { return metaInfo.originTitle || metaInfo.controlType } else { return ctrl.controlTypeInfo.title } } getControlById(controlId: number) { return this.drags().find(d => this.getControlId(d.assControl) === controlId)?.assControl } unlockControl(ctrl: Control) { ctrl.isLocked = false } combinControls() { const selectedDrags = this.selectedDrags() if (selectedDrags.length < 1) return if (selectedDrags.length === 1 && selectedDrags[0].assControl.controlTypeInfo.typeName !== "EmbeddedDivChart") { return } // if(selectedDrags.length < 2) return; const x = min(selectedDrags.map(d => d.x)) const y = min(selectedDrags.map(d => d.y)) const originWidth = max(selectedDrags.map(d => d.x + d.assControl.size.width - x)) const originHeight = max(selectedDrags.map(d => d.y + d.assControl.size.height - y)) const subControls = selectedDrags.map(d => { const data = this.getControlData(d) data.x = data.x! - x data.y = data.y! - y return data }) const groupControlData: GroupControlData = { x, y, subControls, width: originWidth, height: originHeight, controlType: "Group", customProperties: {}, } const groupControl = this.api.createControl(groupControlData) selectedDrags.forEach(this.deleteDrag) this.addControl(groupControl, x, y, true) // groupControl.initControl(this.api); } private packageControlToContainerInternal = (d: DragLayer, pointsTypeInfo: ContainerControlPointsTypeInfo) => { const { x, y, assControl } = d; const contrlData = assControl.getControlData(); const containerControlData: ContainerControlData = { x: d.x, y: d.y, width: assControl.size.width, height: assControl.size.height, controlType: "Container", customProperties: { dataPoints: [contrlData.customProperties] }, sampleControlData: contrlData, pointsTypeInfo } const containerControl = this.api.createControl(containerControlData); this.deleteDrag(d); this.addControl(containerControl, x, y, true); } packageControlToContainer(pointsTypeInfoBuilder:(controlData:ControlData)=> ContainerControlPointsTypeInfo | undefined) { const selectedDrags = this.selectedDrags(); if (selectedDrags.length < 1) return; selectedDrags.forEach(d=>{ const pointsTypeInfo = pointsTypeInfoBuilder(d.assControl.getControlData()); if(pointsTypeInfo){ this.packageControlToContainerInternal(d,pointsTypeInfo); } }) } private deCombinGroupControls() { const drags = this.selectedDrags().filter(c => c.assControl.controlTypeInfo.typeName === "Group") drags.forEach(d => { const x = d.x const y = d.y const controlData = d.assControl.getControlData() as GroupControlData this.deleteDrag(d) controlData.subControls.forEach(c => { const sx = x + c.x! const sy = y + c.y! const newControl = this.api.createControl(c) this.addControl(newControl, sx, sy, true) }) }) } private deCombinContainerControls() { const drags = this.selectedDrags().filter(c => c.assControl.controlTypeInfo.typeName === "Container"); drags.forEach(d => { const x = d.x; const y = d.y; const subControls = (d.assControl as ContainerControl).getSubControlOwnShip() this.deleteDrag(d); subControls.forEach(c => { const sx = x + c.getDisplayObject().x; const sy = y + c.getDisplayObject().y; c.getDisplayObject().x = 0; c.getDisplayObject().y = 0; //const newControl= this.api.createControl(c); this.addControl(c, sx, sy, true); }) }) } deCombinControls() { this.deCombinGroupControls() this.deCombinContainerControls(); } getSelectGroupControlData() { const drag = this.selectedDrag() const allowControlTypes = ["Group", "Container", "EmbeddedDivChart"] if (drag === undefined || allowControlTypes.indexOf(drag.assControl.controlTypeInfo.typeName) === -1) return undefined // return drag.assControl.getControlData() as (GroupControlData | ContainerControlData); return drag.assControl.getControlData() as GroupControlData } deleteDrag = (d: DragLayer) => { d.assControl.destroy() this.removeChild(d) } clear() { const children = [...(this.children as DragLayer[])] children.forEach(this.deleteDrag) } destroy() { this.clear() // const } getControlData = (d: DragLayer) => { const data = d.assControl.getControlData() data.x = d.x data.y = d.y return data } serializeControls(): ControlData[] { return this.selectedDrags().map(this.getControlData) } loadControls(controlDataList: ControlData[], offset: number = 0) { this.allControlExecute(d => d.setSelectState("noselect")) controlDataList.forEach(c => { const newControl = this.api.createControl(c) this.addControl(newControl, c.x! + offset, c.y! + offset, true) }) } getSelectedPosition(): PositionAndSizeProperty | undefined { const drag = this.selectedDrag() if (drag) { return { customName: drag.assControl.customName || "", x: drag.x, y: drag.y, width: drag.assControl.size.width, height: drag.assControl.size.height, } } return undefined } setSelectedPosition(p: PositionAndSizeProperty) { const drag = this.selectedDrag() if (drag) { drag.x = p.x drag.y = p.y drag.resize(p.width, p.height) drag.assControl.customName = p.customName } } setSelectedPositionCustom(f: (old: PositionAndSizeProperty) => PositionAndSizeProperty) { const drag = this.selectedDrag() if (drag) { const old = { x: drag.x, y: drag.y, width: drag.assControl.size.width, height: drag.assControl.size.height, customName: "", } const p = f(old) drag.x = p.x drag.y = p.y drag.resize(p.width, p.height) this.checkPosition(drag) } } getSelectedAnchor(): AnchorProp | undefined { const drag = this.selectedDrag() if (drag) { return drag.assControl.anchored } return undefined } setSelectedAnchor(a: AnchorProp) { const drag = this.selectedDrag() if (drag) { drag.assControl.anchored = a } } }