diff --git a/components/packages/Turnout/common/TurnoutDrawAssistant.d.ts b/components/packages/Turnout/common/TurnoutDrawAssistant.d.ts new file mode 100644 index 0000000..902e65f --- /dev/null +++ b/components/packages/Turnout/common/TurnoutDrawAssistant.d.ts @@ -0,0 +1,55 @@ +import { AbsorbablePosition, DraggablePoint, IGraphicApp, GraphicDrawAssistant, GraphicInteractionPlugin, GraphicTransformEvent, IDrawApp, JlGraphic, VectorText, GraphicEditPlugin, GraphicState } from 'jl-graphic'; +import { JlTurnout, TurnoutSection } from './JlTurnout'; +import { DisplayObject, FederatedMouseEvent, IHitArea, Point } from 'pixi.js'; +import { ITurnoutData } from './TurnoutConfig'; +import { TurnoutTemplate } from './TurnoutTemplate'; +import { JlSection } from 'src/packages/Section/common/JlSection'; +export declare class TurnoutDraw extends GraphicDrawAssistant, ITurnoutData> { + turnout: JlTurnout; + constructor(app: IDrawApp, template: TurnoutTemplate); + bind(): void; + onLeftUp(e: FederatedMouseEvent): void; + prepareData(data: ITurnoutData): boolean; + redraw(cp: Point): void; +} +export declare class ForkHitArea implements IHitArea { + turnout: JlTurnout; + constructor(turnout: JlTurnout); + contains(x: number, y: number): boolean; +} +export declare class TurnoutSectionHitArea implements IHitArea { + section: TurnoutSection; + constructor(section: TurnoutSection); + contains(x: number, y: number): boolean; +} +type dragType = JlTurnout | JlSection; +export declare function buildDragMoveAbsorbablePositions(target: dragType): AbsorbablePosition[]; +export declare class TurnoutPointsInteractionPlugin extends GraphicInteractionPlugin { + static Name: string; + static init(app: IDrawApp): TurnoutPointsInteractionPlugin; + constructor(app: IGraphicApp); + onSectionContextMenu(e: FederatedMouseEvent, section: TurnoutSection): void; + bind(g: JlTurnout): void; + unbind(g: JlTurnout): void; + onSelected(g: DisplayObject): void; + onUnSelected(g: DisplayObject): void; + filter(...grahpics: JlGraphic[]): JlTurnout[] | undefined; + onDragMove(e: GraphicTransformEvent): void; +} +type onTurnoutEditPointCreate = (turnout: JlTurnout, dp: DraggablePoint) => void; +export interface ITurnoutEditOptions { + onEditPointCreate?: onTurnoutEditPointCreate; +} +export declare class TurnoutEditPlugin extends GraphicEditPlugin { + static Name: string; + options: ITurnoutEditOptions; + editPoints: DraggablePoint[][]; + labels: VectorText[]; + constructor(graphic: JlTurnout, options?: ITurnoutEditOptions); + reset(): void; + hideAll(): void; + initEditPoints(): void; + destoryEditPoints(): void; + updateEditedPointsPosition(): void; +} +export {}; diff --git a/components/packages/Turnout/common/TurnoutDrawAssistant.js b/components/packages/Turnout/common/TurnoutDrawAssistant.js new file mode 100644 index 0000000..772aae6 --- /dev/null +++ b/components/packages/Turnout/common/TurnoutDrawAssistant.js @@ -0,0 +1,389 @@ +import { ContextMenu, GraphicDrawAssistant, linePoint, polylinePoint, GraphicInteractionPlugin, getWaypointRangeIndex, GraphicEditPlugin, DraggablePoint, VectorText, AppConsts, AbsorbablePoint, distance, AbsorbableLine } from 'jl-graphic'; +import { getForkPoint, JlTurnout } from './JlTurnout.js'; +import { Point } from 'pixi.js'; +import { DevicePort } from '../../../common/common.js'; +import { JlSection } from '../../Section/common/JlSection.js'; + +const commonTurnoutConsts = { + lineWidth: 5, + forkLenth: 20, +}; +class TurnoutDraw extends GraphicDrawAssistant { + turnout; + constructor(app, template) { + super(app, template, 'sym_o_ramp_left', '道岔Turnout'); + this.turnout = this.graphicTemplate.new(); + this.container.addChild(this.turnout); + TurnoutPointsInteractionPlugin.init(app); + } + bind() { + super.bind(); + this.turnout.loadData(this.graphicTemplate.datas); + this.turnout.doRepaint(); + } + onLeftUp(e) { + this.turnout.position.copyFrom(this.toCanvasCoordinates(e.global)); + this.createAndStore(true); + } + prepareData(data) { + data.transform = this.turnout.saveTransform(); + data.code = 'A000000'; + return true; + } + redraw(cp) { + this.turnout.position.copyFrom(cp); + } +} +class ForkHitArea { + turnout; + constructor(turnout) { + this.turnout = turnout; + } + contains(x, y) { + const intersectPointB = getForkPoint(commonTurnoutConsts.forkLenth, this.turnout.datas.pointB[0]); + const intersectPointC = getForkPoint(commonTurnoutConsts.forkLenth, this.turnout.datas.pointC[0]); + return (linePoint(intersectPointB, { x: 0, y: 0 }, { x, y }, commonTurnoutConsts.lineWidth) || + linePoint(intersectPointC, { x: 0, y: 0 }, { x, y }, commonTurnoutConsts.lineWidth)); + } +} +class TurnoutSectionHitArea { + section; + constructor(section) { + this.section = section; + } + contains(x, y) { + let points; + let start; + switch (this.section.port) { + case DevicePort.A: + points = this.section.turnout.datas.pointA; + start = { x: 0, y: 0 }; + break; + case DevicePort.B: + points = this.section.turnout.datas.pointB; + start = getForkPoint(commonTurnoutConsts.forkLenth, points[0]); + break; + case DevicePort.C: + points = this.section.turnout.datas.pointC; + start = getForkPoint(commonTurnoutConsts.forkLenth, points[0]); + break; + } + return polylinePoint([start, ...points], { x, y }, commonTurnoutConsts.lineWidth); + } +} +function buildAbsorbablePositions(turnout) { + const aps = []; + const sections = turnout.queryStore.queryByType(JlSection.Type); + sections.forEach((section) => { + const ps = new AbsorbablePoint(section.localToCanvasPoint(section.getStartPoint())); + const pe = new AbsorbablePoint(section.localToCanvasPoint(section.getEndPoint())); + aps.push(ps, pe); //区段端点 + }); + const turnouts = turnout.queryStore.queryByType(JlTurnout.Type); + turnouts.forEach((otherTurnout) => { + const { pointA: [A], pointB: [B], pointC: [C], } = otherTurnout.datas; + [A, B, C].forEach((p) => { + aps.push(new AbsorbablePoint(otherTurnout.localToCanvasPoint(p)), //道岔端点 + new AbsorbableLine(otherTurnout.localToCanvasPoint({ x: -5 * p.x, y: -5 * p.y }), otherTurnout.localToCanvasPoint({ x: 5 * p.x, y: 5 * p.y }))); + }); + aps.push(new AbsorbableLine(otherTurnout.localToCanvasPoint({ x: 0, y: -500 }), otherTurnout.localToCanvasPoint({ x: 0, y: 500 })), //岔心垂直线 + new AbsorbableLine(otherTurnout.localToCanvasPoint({ x: -500, y: 0 }), otherTurnout.localToCanvasPoint({ x: 500, y: 0 })), //岔心水平线 + new AbsorbableLine(otherTurnout.localToCanvasPoint({ x: -500, y: 500 }), otherTurnout.localToCanvasPoint({ x: 500, y: -500 })), //岔心/ + new AbsorbableLine(otherTurnout.localToCanvasPoint({ x: -500, y: -500 }), otherTurnout.localToCanvasPoint({ x: 500, y: 500 }))); + }); + return aps; +} +class DragMoveAbsorbablePoint extends AbsorbablePoint { + moveTarget; + constructor(point, absorbRange = 15) { + super(point, absorbRange); + } + tryAbsorb(...dragTargets) { + const dragTarget = dragTargets[0]; + if (dragTarget instanceof JlTurnout) { + if (this.moveTarget == undefined) { + const { pointA: [A], pointB: [B], pointC: [C], } = dragTarget.datas; + this.moveTarget = { + position: dragTarget.getGlobalPosition(), + portPos: [ + dragTarget.localToCanvasPoint(A), + dragTarget.localToCanvasPoint(B), + dragTarget.localToCanvasPoint(C), + ], + }; + } + const { pointA: [A], pointB: [B], pointC: [C], } = dragTarget.datas; + [A, B, C].forEach((p, i) => { + const changePos = dragTarget.localToCanvasPoint(p); + if (distance(this._point.x, this._point.y, changePos.x, changePos.y) < + this.absorbRange && + this.moveTarget) { + dragTarget.updatePositionByCanvasPosition(new Point(this.moveTarget.position.x + + this._point.x - + this.moveTarget.portPos[i].x, this.moveTarget.position.y + + this._point.y - + this.moveTarget.portPos[i].y)); + } + }); + } + else { + if (this.moveTarget == undefined) { + this.moveTarget = { + position: dragTarget.getGlobalPosition(), + portPos: [ + dragTarget.localToCanvasPoint(dragTarget.getStartPoint()), + dragTarget.localToCanvasPoint(dragTarget.getEndPoint()), + ], + }; + } + dragTarget + .localToCanvasPoints(...dragTarget.datas.points) + .forEach((p, i) => { + if (distance(this._point.x, this._point.y, p.x, p.y) < + this.absorbRange && + this.moveTarget) { + dragTarget.updatePositionByCanvasPosition(new Point(this.moveTarget.position.x + + this._point.x - + this.moveTarget.portPos[i].x, this.moveTarget.position.y + + this._point.y - + this.moveTarget.portPos[i].y)); + } + }); + } + } +} +function buildDragMoveAbsorbablePositions(target) { + const aps = []; + const sections = target.queryStore.queryByType(JlSection.Type); + sections.forEach((section) => { + if (section.id !== target.id) { + section.localToCanvasPoints(...section.datas.points).forEach((p) => { + aps.push(new DragMoveAbsorbablePoint(p)); //区段端点 + }); + } + }); + const turnouts = target.queryStore.queryByType(JlTurnout.Type); + turnouts.forEach((otherTurnout) => { + if (otherTurnout.id !== target.id) { + const { pointA: [A], pointB: [B], pointC: [C], } = otherTurnout.datas; + [A, B, C].forEach((p) => { + aps.push(new DragMoveAbsorbablePoint(otherTurnout.localToCanvasPoint(p))); + }); + } + }); + return aps; +} +function onEditPointCreate(turnout, dp) { + dp.on('transformstart', (e) => { + if (e.isShift()) { + turnout.getGraphicApp().setOptions({ + absorbablePositions: buildAbsorbablePositions(turnout), + }); + } + }); +} +const addPointConfig = { name: '添加路径点' }; +const clearPointConfig = { name: '清除路径点' }; +const turnoutSectionEditMenu = ContextMenu.init({ + name: '道岔区段路径编辑', + groups: [ + { + items: [addPointConfig, clearPointConfig], + }, + ], +}); +class TurnoutPointsInteractionPlugin extends GraphicInteractionPlugin { + static Name = 'TurnoutPointsDrag'; + static init(app) { + return new TurnoutPointsInteractionPlugin(app); + } + constructor(app) { + super(TurnoutPointsInteractionPlugin.Name, app); + app.registerMenu(turnoutSectionEditMenu); + } + onSectionContextMenu(e, section) { + const p = section.turnout.screenToLocalPoint(e.global); + addPointConfig.handler = () => { + if (section.port === DevicePort.A) { + const { start } = getWaypointRangeIndex([{ x: 0, y: 0 }, ...section.turnout.datas.pointA], false, p, commonTurnoutConsts.lineWidth); + const points = section.turnout.datas.pointA; + const ps = points.slice(0, start); + ps.push(new Point(p.x, p.y)); + ps.push(...points.slice(start)); + section.turnout.datas.pointA = ps; + } + if (section.port === DevicePort.B) { + const { start } = getWaypointRangeIndex([{ x: 0, y: 0 }, ...section.turnout.datas.pointB], false, p, commonTurnoutConsts.lineWidth); + const points = section.turnout.datas.pointB; + const ps = points.slice(0, start); + ps.push(new Point(p.x, p.y)); + ps.push(...points.slice(start)); + section.turnout.datas.pointB = ps; + } + if (section.port === DevicePort.C) { + const { start } = getWaypointRangeIndex([{ x: 0, y: 0 }, ...section.turnout.datas.pointC], false, p, commonTurnoutConsts.lineWidth); + const points = section.turnout.datas.pointC; + const ps = points.slice(0, start); + ps.push(new Point(p.x, p.y)); + ps.push(...points.slice(start)); + section.turnout.datas.pointC = ps; + } + this.onSelected(section.turnout); + }; + clearPointConfig.handler = () => { + if (section.port === DevicePort.A) + section.turnout.datas.pointA = [ + section.turnout.datas.pointA[section.turnout.datas.pointA.length - 1], + ]; + if (section.port === DevicePort.B) + section.turnout.datas.pointB = [ + section.turnout.datas.pointB[section.turnout.datas.pointB.length - 1], + ]; + if (section.port === DevicePort.C) + section.turnout.datas.pointC = [ + section.turnout.datas.pointC[section.turnout.datas.pointC.length - 1], + ]; + const tep = section.turnout.getAssistantAppend(TurnoutEditPlugin.Name); + if (tep) { + tep.reset(); + } + section.turnout.repaint(); + }; + turnoutSectionEditMenu.open(e.global); + } + bind(g) { + g.graphics.fork.eventMode = 'static'; + g.graphics.fork.cursor = 'pointer'; + g.graphics.fork.hitArea = new ForkHitArea(g); + g.graphics.sections.forEach((sectionGraphic) => { + sectionGraphic.eventMode = 'static'; + sectionGraphic.cursor = 'pointer'; + sectionGraphic.hitArea = new TurnoutSectionHitArea(sectionGraphic); + sectionGraphic.on('rightclick', (e) => this.onSectionContextMenu(e, sectionGraphic), sectionGraphic); + }); + g.graphics.label.eventMode = 'static'; + g.graphics.label.cursor = 'pointer'; + g.graphics.label.draggable = true; + g.graphics.label.selectable = true; + g.graphics.label.name = 'label'; + g.graphics.label.transformSave = true; + g.transformSave = true; + g.on('selected', this.onSelected, this); + g.on('unselected', this.onUnSelected, this); + g.on('transformstart', this.onDragMove, this); + } + unbind(g) { + g.off('selected', this.onSelected, this); + g.off('unselected', this.onUnSelected, this); + g.graphics.sections.forEach((sectionGraphic) => { + sectionGraphic.off('rightclick'); + }); + g.off('transformstart', this.onDragMove, this); + } + onSelected(g) { + const turnout = g; + let tep = turnout.getAssistantAppend(TurnoutEditPlugin.Name); + if (!tep) { + tep = new TurnoutEditPlugin(turnout, { onEditPointCreate }); + turnout.addAssistantAppend(tep); + } + tep.reset(); + tep.showAll(); + } + onUnSelected(g) { + const turnout = g; + const tep = turnout.getAssistantAppend(TurnoutEditPlugin.Name); + if (tep) { + tep.hideAll(); + } + } + filter(...grahpics) { + return grahpics.filter((g) => g.type == JlTurnout.Type); + } + onDragMove(e) { + const turnout = e.target; + this.app.setOptions({ + absorbablePositions: buildDragMoveAbsorbablePositions(turnout), + }); + } +} +class TurnoutEditPlugin extends GraphicEditPlugin { + static Name = 'TurnoutEdit'; + options; + editPoints = [[], [], []]; + labels = []; + constructor(graphic, options) { + super(graphic); + this.name = TurnoutEditPlugin.Name; + this.options = Object.assign({}, options); + this.initEditPoints(); + } + reset() { + this.destoryEditPoints(); + this.removeChildren(); + this.initEditPoints(); + } + hideAll() { + super.hideAll(); + } + initEditPoints() { + const cpA = this.graphic.localToCanvasPoints(...this.graphic.datas.pointA); + const cpB = this.graphic.localToCanvasPoints(...this.graphic.datas.pointB); + const cpC = this.graphic.localToCanvasPoints(...this.graphic.datas.pointC); + const cpMap = new Map([ + [cpA, this.graphic.datas.pointA], + [cpB, this.graphic.datas.pointB], + [cpC, this.graphic.datas.pointC], + ]); + Array.from(cpMap.entries()).forEach(([cpDatas, dataPoints], i) => { + cpDatas.forEach((cpData, j) => { + const dp = new DraggablePoint(cpData); + dp.on('transforming', () => { + const localPoint = this.graphic.canvasToLocalPoint(dp.position); + dataPoints[j].x = localPoint.x; + dataPoints[j].y = localPoint.y; + this.graphic.repaint(); + }); + if (this.options.onEditPointCreate) { + this.options.onEditPointCreate(this.graphic, dp); + } + this.editPoints[i].push(dp); + }); + }); + this.editPoints.forEach((cps) => { + this.addChild(...cps); + }); + this.labels = ['A', 'B', 'C'].map((str) => { + const vc = new VectorText(str, { fill: AppConsts.assistantElementColor }); + vc.setVectorFontSize(14); + vc.anchor.set(0.5); + return vc; + }); + this.addChild(...this.labels); + } + destoryEditPoints() { + this.editPoints.forEach((dps) => { + dps.forEach((dp) => { + dp.off('transforming'); + dp.destroy(); + this.removeChild(dp); + }); + }); + this.editPoints = [[], [], []]; + } + updateEditedPointsPosition() { + const cpA = this.graphic.localToCanvasPoints(...this.graphic.datas.pointA); + const cpB = this.graphic.localToCanvasPoints(...this.graphic.datas.pointB); + const cpC = this.graphic.localToCanvasPoints(...this.graphic.datas.pointC); + [cpA, cpB, cpC].forEach((cps, i) => { + cps.forEach((cp, j) => { + this.editPoints[i][j].position.copyFrom(cp); + if (j === cps.length - 1) { + this.labels[i].position.copyFrom({ x: cp.x, y: cp.y + 12 }); + } + }); + }); + } +} + +export { ForkHitArea, TurnoutDraw, TurnoutEditPlugin, TurnoutPointsInteractionPlugin, TurnoutSectionHitArea, buildDragMoveAbsorbablePositions }; diff --git a/components/packages/Turnout/common/TurnoutTemplate.d.ts b/components/packages/Turnout/common/TurnoutTemplate.d.ts new file mode 100644 index 0000000..1410714 --- /dev/null +++ b/components/packages/Turnout/common/TurnoutTemplate.d.ts @@ -0,0 +1,9 @@ +import { GraphicState, JlGraphicTemplate } from 'jl-graphic'; +import { JlTurnout } from './JlTurnout'; +import { ITurnoutData } from './TurnoutConfig'; +import { StyleType } from 'common/common'; +export declare class TurnoutTemplate extends JlGraphicTemplate { + styleType: StyleType; + constructor(dataTemplate: ITurnoutData, stateTemplate: S, styleType: StyleType); + new(): JlTurnout; +} diff --git a/components/packages/Turnout/common/TurnoutTemplate.js b/components/packages/Turnout/common/TurnoutTemplate.js new file mode 100644 index 0000000..e4856f5 --- /dev/null +++ b/components/packages/Turnout/common/TurnoutTemplate.js @@ -0,0 +1,32 @@ +import { JlGraphicTemplate } from 'jl-graphic'; +import { JlTurnout } from './JlTurnout.js'; +import { StyleType } from '../../../common/common.js'; +import { GPTurnout } from '../GPTurnout.js'; +import { THTurnout } from '../THTurnout.js'; + +class TurnoutTemplate extends JlGraphicTemplate { + styleType; + constructor(dataTemplate, stateTemplate, styleType) { + super(JlTurnout.Type, { + dataTemplate, + stateTemplate, + }); + this.styleType = styleType; + } + new() { + let turnout; + switch (this.styleType) { + case StyleType.GP: + turnout = new GPTurnout(); + break; + default: + turnout = new THTurnout(); + break; + } + turnout.loadData(this.datas); + turnout.loadState(this.states); + return turnout; + } +} + +export { TurnoutTemplate }; diff --git a/src/packages/Turnout/common/TurnoutDrawAssistant.ts b/src/packages/Turnout/common/TurnoutDrawAssistant.ts new file mode 100644 index 0000000..a271bcc --- /dev/null +++ b/src/packages/Turnout/common/TurnoutDrawAssistant.ts @@ -0,0 +1,575 @@ +import { + AbsorbablePosition, + DraggablePoint, + IGraphicApp, + GraphicDrawAssistant, + GraphicInteractionPlugin, + GraphicTransformEvent, + IDrawApp, + JlGraphic, + VectorText, + linePoint, + polylinePoint, + AppConsts, + GraphicEditPlugin, + getWaypointRangeIndex, + ContextMenu, + MenuItemOptions, + AbsorbablePoint, + AbsorbableLine, + distance, + GraphicState, +} from 'jl-graphic'; +import { JlTurnout, TurnoutSection, getForkPoint } from './JlTurnout'; +import { + DisplayObject, + FederatedMouseEvent, + IHitArea, + IPointData, + Point, +} from 'pixi.js'; +import { ITurnoutData } from './TurnoutConfig'; +import { TurnoutTemplate } from './TurnoutTemplate'; +import { DevicePort } from 'common/common'; +import { JlSection } from 'src/packages/Section/common/JlSection'; + +const commonTurnoutConsts = { + lineWidth: 5, + forkLenth: 20, +}; + +export class TurnoutDraw extends GraphicDrawAssistant< + TurnoutTemplate, + ITurnoutData +> { + turnout: JlTurnout; + constructor(app: IDrawApp, template: TurnoutTemplate) { + super(app, template, 'sym_o_ramp_left', '道岔Turnout'); + + this.turnout = this.graphicTemplate.new(); + this.container.addChild(this.turnout); + + TurnoutPointsInteractionPlugin.init(app); + } + + bind(): void { + super.bind(); + this.turnout.loadData(this.graphicTemplate.datas); + this.turnout.doRepaint(); + } + + onLeftUp(e: FederatedMouseEvent): void { + this.turnout.position.copyFrom(this.toCanvasCoordinates(e.global)); + this.createAndStore(true); + } + + prepareData(data: ITurnoutData): boolean { + data.transform = this.turnout.saveTransform(); + data.code = 'A000000'; + return true; + } + + redraw(cp: Point): void { + this.turnout.position.copyFrom(cp); + } +} + +export class ForkHitArea implements IHitArea { + turnout: JlTurnout; + constructor(turnout: JlTurnout) { + this.turnout = turnout; + } + contains(x: number, y: number): boolean { + const intersectPointB = getForkPoint( + commonTurnoutConsts.forkLenth, + this.turnout.datas.pointB[0], + ); + const intersectPointC = getForkPoint( + commonTurnoutConsts.forkLenth, + this.turnout.datas.pointC[0], + ); + + return ( + linePoint( + intersectPointB, + { x: 0, y: 0 }, + { x, y }, + commonTurnoutConsts.lineWidth, + ) || + linePoint( + intersectPointC, + { x: 0, y: 0 }, + { x, y }, + commonTurnoutConsts.lineWidth, + ) + ); + } +} + +export class TurnoutSectionHitArea implements IHitArea { + section: TurnoutSection; + constructor(section: TurnoutSection) { + this.section = section; + } + contains(x: number, y: number): boolean { + let points: IPointData[]; + let start: IPointData; + switch (this.section.port) { + case DevicePort.A: + points = this.section.turnout.datas.pointA; + start = { x: 0, y: 0 }; + break; + case DevicePort.B: + points = this.section.turnout.datas.pointB; + start = getForkPoint(commonTurnoutConsts.forkLenth, points[0]); + break; + case DevicePort.C: + points = this.section.turnout.datas.pointC; + start = getForkPoint(commonTurnoutConsts.forkLenth, points[0]); + break; + } + return polylinePoint( + [start, ...points], + { x, y }, + commonTurnoutConsts.lineWidth, + ); + } +} + +function buildAbsorbablePositions(turnout: JlTurnout): AbsorbablePosition[] { + const aps: AbsorbablePosition[] = []; + + const sections = turnout.queryStore.queryByType(JlSection.Type); + sections.forEach((section) => { + const ps = new AbsorbablePoint( + section.localToCanvasPoint(section.getStartPoint()), + ); + const pe = new AbsorbablePoint( + section.localToCanvasPoint(section.getEndPoint()), + ); + aps.push(ps, pe); //区段端点 + }); + + const turnouts = turnout.queryStore.queryByType(JlTurnout.Type); + turnouts.forEach((otherTurnout) => { + const { + pointA: [A], + pointB: [B], + pointC: [C], + } = otherTurnout.datas; + + [A, B, C].forEach((p) => { + aps.push( + new AbsorbablePoint(otherTurnout.localToCanvasPoint(p)), //道岔端点 + new AbsorbableLine( + otherTurnout.localToCanvasPoint({ x: -5 * p.x, y: -5 * p.y }), + otherTurnout.localToCanvasPoint({ x: 5 * p.x, y: 5 * p.y }), + ), //道岔延长线 + ); + }); + aps.push( + new AbsorbableLine( + otherTurnout.localToCanvasPoint({ x: 0, y: -500 }), + otherTurnout.localToCanvasPoint({ x: 0, y: 500 }), + ), //岔心垂直线 + new AbsorbableLine( + otherTurnout.localToCanvasPoint({ x: -500, y: 0 }), + otherTurnout.localToCanvasPoint({ x: 500, y: 0 }), + ), //岔心水平线 + new AbsorbableLine( + otherTurnout.localToCanvasPoint({ x: -500, y: 500 }), + otherTurnout.localToCanvasPoint({ x: 500, y: -500 }), + ), //岔心/ + new AbsorbableLine( + otherTurnout.localToCanvasPoint({ x: -500, y: -500 }), + otherTurnout.localToCanvasPoint({ x: 500, y: 500 }), + ), //岔心\ + ); + }); + + return aps; +} + +type dragType = JlTurnout | JlSection; +class DragMoveAbsorbablePoint extends AbsorbablePoint { + moveTarget: + | { + position: IPointData; + portPos: IPointData[]; + } + | undefined; + constructor(point: IPointData, absorbRange = 15) { + super(point, absorbRange); + } + tryAbsorb(...dragTargets: dragType[]): void { + const dragTarget = dragTargets[0]; + if (dragTarget instanceof JlTurnout) { + if (this.moveTarget == undefined) { + const { + pointA: [A], + pointB: [B], + pointC: [C], + } = dragTarget.datas; + this.moveTarget = { + position: dragTarget.getGlobalPosition(), + portPos: [ + dragTarget.localToCanvasPoint(A), + dragTarget.localToCanvasPoint(B), + dragTarget.localToCanvasPoint(C), + ], + }; + } + const { + pointA: [A], + pointB: [B], + pointC: [C], + } = dragTarget.datas; + [A, B, C].forEach((p, i) => { + const changePos = dragTarget.localToCanvasPoint(p); + if ( + distance(this._point.x, this._point.y, changePos.x, changePos.y) < + this.absorbRange && + this.moveTarget + ) { + dragTarget.updatePositionByCanvasPosition( + new Point( + this.moveTarget.position.x + + this._point.x - + this.moveTarget.portPos[i].x, + this.moveTarget.position.y + + this._point.y - + this.moveTarget.portPos[i].y, + ), + ); + } + }); + } else { + if (this.moveTarget == undefined) { + this.moveTarget = { + position: dragTarget.getGlobalPosition(), + portPos: [ + dragTarget.localToCanvasPoint(dragTarget.getStartPoint()), + dragTarget.localToCanvasPoint(dragTarget.getEndPoint()), + ], + }; + } + dragTarget + .localToCanvasPoints(...dragTarget.datas.points) + .forEach((p, i) => { + if ( + distance(this._point.x, this._point.y, p.x, p.y) < + this.absorbRange && + this.moveTarget + ) { + dragTarget.updatePositionByCanvasPosition( + new Point( + this.moveTarget.position.x + + this._point.x - + this.moveTarget.portPos[i].x, + this.moveTarget.position.y + + this._point.y - + this.moveTarget.portPos[i].y, + ), + ); + } + }); + } + } +} + +export function buildDragMoveAbsorbablePositions( + target: dragType, +): AbsorbablePosition[] { + const aps: AbsorbablePosition[] = []; + + const sections = target.queryStore.queryByType(JlSection.Type); + sections.forEach((section) => { + if (section.id !== target.id) { + section.localToCanvasPoints(...section.datas.points).forEach((p) => { + aps.push(new DragMoveAbsorbablePoint(p)); //区段端点 + }); + } + }); + + const turnouts = target.queryStore.queryByType(JlTurnout.Type); + turnouts.forEach((otherTurnout) => { + if (otherTurnout.id !== target.id) { + const { + pointA: [A], + pointB: [B], + pointC: [C], + } = otherTurnout.datas; + [A, B, C].forEach((p) => { + aps.push( + new DragMoveAbsorbablePoint(otherTurnout.localToCanvasPoint(p)), //道岔端点 + ); + }); + } + }); + + return aps; +} + +function onEditPointCreate(turnout: JlTurnout, dp: DraggablePoint) { + dp.on('transformstart', (e: GraphicTransformEvent) => { + if (e.isShift()) { + turnout.getGraphicApp().setOptions({ + absorbablePositions: buildAbsorbablePositions(turnout), + }); + } + }); +} + +const addPointConfig: MenuItemOptions = { name: '添加路径点' }; +const clearPointConfig: MenuItemOptions = { name: '清除路径点' }; + +const turnoutSectionEditMenu: ContextMenu = ContextMenu.init({ + name: '道岔区段路径编辑', + groups: [ + { + items: [addPointConfig, clearPointConfig], + }, + ], +}); + +export class TurnoutPointsInteractionPlugin extends GraphicInteractionPlugin { + static Name = 'TurnoutPointsDrag'; + static init(app: IDrawApp) { + return new TurnoutPointsInteractionPlugin(app); + } + + constructor(app: IGraphicApp) { + super(TurnoutPointsInteractionPlugin.Name, app); + app.registerMenu(turnoutSectionEditMenu); + } + + onSectionContextMenu(e: FederatedMouseEvent, section: TurnoutSection) { + const p = section.turnout.screenToLocalPoint(e.global); + addPointConfig.handler = () => { + if (section.port === DevicePort.A) { + const { start } = getWaypointRangeIndex( + [{ x: 0, y: 0 }, ...section.turnout.datas.pointA], + false, + p, + commonTurnoutConsts.lineWidth, + ); + const points = section.turnout.datas.pointA; + const ps = points.slice(0, start); + ps.push(new Point(p.x, p.y)); + ps.push(...points.slice(start)); + section.turnout.datas.pointA = ps; + } + if (section.port === DevicePort.B) { + const { start } = getWaypointRangeIndex( + [{ x: 0, y: 0 }, ...section.turnout.datas.pointB], + false, + p, + commonTurnoutConsts.lineWidth, + ); + const points = section.turnout.datas.pointB; + const ps = points.slice(0, start); + ps.push(new Point(p.x, p.y)); + ps.push(...points.slice(start)); + section.turnout.datas.pointB = ps; + } + if (section.port === DevicePort.C) { + const { start } = getWaypointRangeIndex( + [{ x: 0, y: 0 }, ...section.turnout.datas.pointC], + false, + p, + commonTurnoutConsts.lineWidth, + ); + const points = section.turnout.datas.pointC; + const ps = points.slice(0, start); + ps.push(new Point(p.x, p.y)); + ps.push(...points.slice(start)); + section.turnout.datas.pointC = ps; + } + this.onSelected(section.turnout); + }; + clearPointConfig.handler = () => { + if (section.port === DevicePort.A) + section.turnout.datas.pointA = [ + section.turnout.datas.pointA[section.turnout.datas.pointA.length - 1], + ]; + if (section.port === DevicePort.B) + section.turnout.datas.pointB = [ + section.turnout.datas.pointB[section.turnout.datas.pointB.length - 1], + ]; + if (section.port === DevicePort.C) + section.turnout.datas.pointC = [ + section.turnout.datas.pointC[section.turnout.datas.pointC.length - 1], + ]; + const tep = section.turnout.getAssistantAppend( + TurnoutEditPlugin.Name, + ); + if (tep) { + tep.reset(); + } + section.turnout.repaint(); + }; + turnoutSectionEditMenu.open(e.global); + } + + bind(g: JlTurnout): void { + g.graphics.fork.eventMode = 'static'; + g.graphics.fork.cursor = 'pointer'; + g.graphics.fork.hitArea = new ForkHitArea(g); + g.graphics.sections.forEach((sectionGraphic) => { + sectionGraphic.eventMode = 'static'; + sectionGraphic.cursor = 'pointer'; + sectionGraphic.hitArea = new TurnoutSectionHitArea(sectionGraphic); + sectionGraphic.on( + 'rightclick', + (e) => this.onSectionContextMenu(e, sectionGraphic), + sectionGraphic, + ); + }); + g.graphics.label.eventMode = 'static'; + g.graphics.label.cursor = 'pointer'; + g.graphics.label.draggable = true; + g.graphics.label.selectable = true; + g.graphics.label.name = 'label'; + g.graphics.label.transformSave = true; + g.transformSave = true; + g.on('selected', this.onSelected, this); + g.on('unselected', this.onUnSelected, this); + g.on('transformstart', this.onDragMove, this); + } + + unbind(g: JlTurnout): void { + g.off('selected', this.onSelected, this); + g.off('unselected', this.onUnSelected, this); + g.graphics.sections.forEach((sectionGraphic) => { + sectionGraphic.off('rightclick'); + }); + g.off('transformstart', this.onDragMove, this); + } + + onSelected(g: DisplayObject) { + const turnout = g as JlTurnout; + let tep = turnout.getAssistantAppend( + TurnoutEditPlugin.Name, + ); + if (!tep) { + tep = new TurnoutEditPlugin(turnout, { onEditPointCreate }); + turnout.addAssistantAppend(tep); + } + tep.reset(); + tep.showAll(); + } + + onUnSelected(g: DisplayObject) { + const turnout = g as JlTurnout; + const tep = turnout.getAssistantAppend( + TurnoutEditPlugin.Name, + ); + if (tep) { + tep.hideAll(); + } + } + + filter(...grahpics: JlGraphic[]): JlTurnout[] | undefined { + return grahpics.filter((g) => g.type == JlTurnout.Type) as JlTurnout[]; + } + + onDragMove(e: GraphicTransformEvent) { + const turnout = e.target as JlTurnout; + this.app.setOptions({ + absorbablePositions: buildDragMoveAbsorbablePositions(turnout), + }); + } +} + +type onTurnoutEditPointCreate = ( + turnout: JlTurnout, + dp: DraggablePoint, +) => void; + +export interface ITurnoutEditOptions { + onEditPointCreate?: onTurnoutEditPointCreate; +} + +export class TurnoutEditPlugin extends GraphicEditPlugin { + static Name = 'TurnoutEdit'; + options: ITurnoutEditOptions; + editPoints: DraggablePoint[][] = [[], [], []]; + labels: VectorText[] = []; + + constructor(graphic: JlTurnout, options?: ITurnoutEditOptions) { + super(graphic); + this.name = TurnoutEditPlugin.Name; + this.options = Object.assign({}, options); + this.initEditPoints(); + } + reset(): void { + this.destoryEditPoints(); + this.removeChildren(); + this.initEditPoints(); + } + hideAll(): void { + super.hideAll(); + } + + initEditPoints() { + const cpA = this.graphic.localToCanvasPoints(...this.graphic.datas.pointA); + const cpB = this.graphic.localToCanvasPoints(...this.graphic.datas.pointB); + const cpC = this.graphic.localToCanvasPoints(...this.graphic.datas.pointC); + const cpMap: Map = new Map([ + [cpA, this.graphic.datas.pointA], + [cpB, this.graphic.datas.pointB], + [cpC, this.graphic.datas.pointC], + ]); + Array.from(cpMap.entries()).forEach(([cpDatas, dataPoints], i) => { + cpDatas.forEach((cpData, j) => { + const dp = new DraggablePoint(cpData); + dp.on('transforming', () => { + const localPoint = this.graphic.canvasToLocalPoint(dp.position); + dataPoints[j].x = localPoint.x; + dataPoints[j].y = localPoint.y; + + this.graphic.repaint(); + }); + if (this.options.onEditPointCreate) { + this.options.onEditPointCreate(this.graphic, dp); + } + this.editPoints[i].push(dp); + }); + }); + this.editPoints.forEach((cps) => { + this.addChild(...cps); + }); + this.labels = ['A', 'B', 'C'].map((str) => { + const vc = new VectorText(str, { fill: AppConsts.assistantElementColor }); + vc.setVectorFontSize(14); + vc.anchor.set(0.5); + return vc; + }); + this.addChild(...this.labels); + } + + destoryEditPoints() { + this.editPoints.forEach((dps) => { + dps.forEach((dp) => { + dp.off('transforming'); + dp.destroy(); + this.removeChild(dp); + }); + }); + this.editPoints = [[], [], []]; + } + + updateEditedPointsPosition() { + const cpA = this.graphic.localToCanvasPoints(...this.graphic.datas.pointA); + const cpB = this.graphic.localToCanvasPoints(...this.graphic.datas.pointB); + const cpC = this.graphic.localToCanvasPoints(...this.graphic.datas.pointC); + [cpA, cpB, cpC].forEach((cps, i) => { + cps.forEach((cp, j) => { + this.editPoints[i][j].position.copyFrom(cp); + if (j === cps.length - 1) { + this.labels[i].position.copyFrom({ x: cp.x, y: cp.y + 12 }); + } + }); + }); + } +} diff --git a/src/packages/Turnout/common/TurnoutTemplate.ts b/src/packages/Turnout/common/TurnoutTemplate.ts new file mode 100644 index 0000000..673d2c1 --- /dev/null +++ b/src/packages/Turnout/common/TurnoutTemplate.ts @@ -0,0 +1,38 @@ +import { GraphicState, JlGraphicTemplate } from 'jl-graphic'; +import { JlTurnout } from './JlTurnout'; +import { ITurnoutData } from './TurnoutConfig'; +import { StyleType } from 'common/common'; +import { GPTurnout } from '../GPTurnout'; +import { THTurnout } from '../THTurnout'; + +export class TurnoutTemplate< + S extends GraphicState, +> extends JlGraphicTemplate { + styleType: StyleType; + constructor( + dataTemplate: ITurnoutData, + stateTemplate: S, + styleType: StyleType, + ) { + super(JlTurnout.Type, { + dataTemplate, + stateTemplate, + }); + this.styleType = styleType; + } + + new() { + let turnout: JlTurnout; + switch (this.styleType) { + case StyleType.GP: + turnout = new GPTurnout(); + break; + default: + turnout = new THTurnout(); + break; + } + turnout.loadData(this.datas); + turnout.loadState(this.states); + return turnout; + } +}