import { ContextMenu, GraphicDrawAssistant, KeyListener, calculateMirrorPoint, convertToBezierParams, pointPolygon, linePoint, GraphicInteractionPlugin, getWaypointRangeIndex, addWayPoint, clearWayPoint, PolylineEditPlugin, VectorText, AppConsts, BezierCurveEditPlugin, AbsorbableLine, AbsorbablePoint } from 'jl-graphic'; import { SectionType, defaultDisplayConfig } from './Section.js'; import { Graphics, Point } from 'pixi.js'; import { Turnout } from '../../Turnout/Turnout.js'; import { Section } from '../bjrtss/Section.js'; class SectionDraw extends GraphicDrawAssistant { points = []; graphic = new Graphics(); keyQListener = new KeyListener({ value: 'KeyQ', global: true, onPress: () => { if (this.points.length === 0) ; }, }); keyZListener = new KeyListener({ value: 'KeyZ', global: true, onPress: () => { if (this.points.length === 0) ; }, }); constructor(app, template) { super(app, template, 'sym_o_timeline', '区段Section'); this.container.addChild(this.graphic); SectionPointEditPlugin.init(app, this); } bind() { super.bind(); this.app.addKeyboardListener(this.keyQListener, this.keyZListener); } unbind() { super.unbind(); this.app.removeKeyboardListener(this.keyQListener, this.keyZListener); } onLeftDown(e) { const { x, y } = this.toCanvasCoordinates(e.global); const p = new Point(x, y); if (this.graphicTemplate.isCurve) { if (this.points.length == 0) { this.points.push(p); } else { this.points.push(p, p.clone()); } } else { this.points.push(p); } } onLeftUp(e) { const template = this.graphicTemplate; if (template.isCurve) { const mp = this.toCanvasCoordinates(e.global); if (this.points.length === 1) { this.points.push(new Point(mp.x, mp.y)); } else if (this.points.length > 1) { const cp2 = this.points[this.points.length - 2]; const p = this.points[this.points.length - 1]; cp2.copyFrom(calculateMirrorPoint(p, mp)); this.points.push(mp); } } } onRightClick() { if (this.points.length < 2) { this.finish(); return; } this.createAndStore(true); } onEsc() { if (this.points.length < 2) { this.finish(); return; } this.createAndStore(true); } redraw(p) { if (this.points.length < 1) return; const template = this.graphicTemplate; this.graphic.clear(); this.graphic.lineStyle(3, '#555'); const ps = [...this.points]; if (template.isCurve) { if (ps.length === 1) { this.graphic.moveTo(ps[0].x, ps[0].y); this.graphic.lineTo(p.x, p.y); } else { if ((ps.length + 1) % 3 === 0) { ps.push(p.clone(), p.clone()); } else { const cp = ps[ps.length - 2]; const p1 = ps[ps.length - 1]; const mp = calculateMirrorPoint(p1, p); cp.copyFrom(mp); } const bps = convertToBezierParams(ps); bps.forEach((bp) => { this.graphic.drawBezierCurve(bp.p1, bp.p2, bp.cp1, bp.cp2, template.segmentsCount); }); } } else { ps.push(p); ps.forEach((p, i) => { if (i !== 0) { this.graphic.lineTo(p.x, p.y); } else { this.graphic.moveTo(p.x, p.y); } }); } } prepareData(data) { const template = this.graphicTemplate; if ((!template.isCurve && this.points.length < 2) || (template.isCurve && this.points.length < 4)) { console.log('Section绘制因点不够取消绘制'); return false; } if (template.isCurve) { this.points.pop(); } data.isCurve = template.isCurve; data.segmentsCount = template.segmentsCount; data.points = this.points; data.code = 'G000'; return true; } clearCache() { this.points = []; this.graphic.clear(); } } class SectionGraphicHitArea { section; constructor(section) { this.section = section; } contains(x, y) { if (this.section.datas.sectionType === SectionType.TurnoutPhysical) { return false; } if (this.section.datas.isCurve) { const bps = convertToBezierParams(this.section.datas.points); for (let i = 0; i < bps.length; i++) { const bp = bps[i]; if (pointPolygon({ x, y }, [bp.p1, bp.cp1, bp.cp2, bp.p2], defaultDisplayConfig.lineWidth)) { return true; } } } else { for (let i = 1; i < this.section.datas.points.length; i++) { const p1 = this.section.datas.points[i - 1]; const p2 = this.section.datas.points[i]; if (linePoint(p1, p2, { x, y }, defaultDisplayConfig.lineWidth)) { return true; } } } return false; } } function buildAbsorbablePositions(section) { const aps = []; const sections = section.queryStore.queryByType(Section.Type); sections.forEach((other) => { const [ps, pe] = [ other.localToCanvasPoint(other.getStartPoint()), other.localToCanvasPoint(other.getEndPoint()), ]; const { width, height } = section.getGraphicApp().canvas; const xs = new AbsorbableLine({ x: 0, y: ps.y }, { x: width, y: ps.y }); const ys = new AbsorbableLine({ x: ps.x, y: 0 }, { x: ps.x, y: height }); const xe = new AbsorbableLine({ x: 0, y: pe.y }, { x: width, y: pe.y }); const ye = new AbsorbableLine({ x: pe.x, y: 0 }, { x: pe.x, y: height }); aps.push(xs, ys, xe, ye); }); const turnouts = section.queryStore.queryByType(Turnout.Type); turnouts.forEach((turnout) => { turnout.getPortPoints().forEach((points) => { turnout.localToCanvasPoints(...points).forEach((p) => { aps.push(new AbsorbablePoint(p)); }); }); }); return aps; } function onEditPointCreate(g, dp) { const section = g; dp.on('transformstart', (e) => { if (e.isShift()) { section.getGraphicApp().setOptions({ absorbablePositions: buildAbsorbablePositions(section), }); } }); } class SectionPolylineEditPlugin extends PolylineEditPlugin { static Name = 'SectionPolylineEditPlugin'; labels = []; constructor(g, options) { super(g, options); this.name = SectionPolylineEditPlugin.Name; this.initLabels(); } initLabels() { this.labels = ['A', 'B'].map((str) => { const vc = new VectorText(str, { fill: AppConsts.assistantElementColor }); vc.setVectorFontSize(14); vc.anchor.set(0.5); return vc; }); this.addChild(...this.labels); this.updateEditedPointsPosition(); } updateEditedPointsPosition() { super.updateEditedPointsPosition(); this.labels[0]?.position.set(this.editedPoints[0].x, this.editedPoints[0].y + 10); this.labels[1]?.position.set(this.editedPoints[this.editedPoints.length - 1].x, this.editedPoints[this.editedPoints.length - 1].y + 10); } } class SectionBazierCurveEditPlugin extends BezierCurveEditPlugin { static Name = 'SectionBazierCurveEditPlugin'; labels = []; constructor(g, options) { super(g, options); this.name = SectionBazierCurveEditPlugin.Name; this.initLabels(); } initLabels() { this.labels = [new VectorText('A'), new VectorText('B')]; this.labels.forEach((label) => { label.setVectorFontSize(14); this.addChild(label); }); this.updateEditedPointsPosition(); } updateEditedPointsPosition() { super.updateEditedPointsPosition(); this.labels[0]?.position.set(this.editedPoints[0].x, this.editedPoints[0].y + 10); this.labels[1]?.position.set(this.editedPoints[this.editedPoints.length - 1].x, this.editedPoints[this.editedPoints.length - 1].y + 10); } } const addWaypointConfig = { name: '添加路径点', }; const clearWaypointsConfig = { name: '清除所有路径点', }; const splitSectionConfig = { name: '拆分', // disabled: true, }; const SectionEditMenu = ContextMenu.init({ name: '区段编辑菜单', groups: [ { items: [addWaypointConfig, clearWaypointsConfig], }, { items: [splitSectionConfig], }, ], }); class SectionPointEditPlugin extends GraphicInteractionPlugin { static Name = 'SectionPointDrag'; drawAssistant; constructor(app, da) { super(SectionPointEditPlugin.Name, app); this.drawAssistant = da; app.registerMenu(SectionEditMenu); } static init(app, da) { return new SectionPointEditPlugin(app, da); } filter(...grahpics) { return grahpics.filter((g) => g.type == Section.Type); } bind(g) { g.lineGraphic.eventMode = 'static'; g.lineGraphic.cursor = 'pointer'; g.lineGraphic.hitArea = new SectionGraphicHitArea(g); g.transformSave = true; g.labelGraphic.eventMode = 'static'; g.labelGraphic.cursor = 'pointer'; g.labelGraphic.selectable = true; g.labelGraphic.draggable = true; g.on('selected', this.onSelected, this); g.on('unselected', this.onUnselected, this); // g.on('_rightclick', this.onContextMenu, this); } unbind(g) { g.off('selected', this.onSelected, this); g.off('unselected', this.onUnselected, this); // g.off('_rightclick', this.onContextMenu, this); } onContextMenu(e) { const target = e.target; const section = target.getGraphic(); this.app.updateSelected(section); if (section.datas.sectionType === SectionType.TurnoutPhysical) { return; } const p = section.screenToLocalPoint(e.global); addWaypointConfig.handler = () => { const linePoints = section.linePoints; const { start, end } = getWaypointRangeIndex(linePoints, false, p, defaultDisplayConfig.lineWidth); addWayPoint(section, false, start, end, p); }; clearWaypointsConfig.handler = () => { clearWayPoint(section, false); }; // TODO /** * 区段拆分的菜单项和对话框之前写在这里,但引用了Quasar组件不是很合适,暂时先注释掉 * Section中已有获取拆分的数据的方法,考虑将生成对应目标区段的逻辑放业务层?或这里提供生成方法供业务层调用 */ // splitSectionConfig.disabled = false; // splitSectionConfig.handler = () => { // Dialog.create({ // title: '拆分区段', // message: '请选择生成数量和方向', // component: SectionSplitDialog, // cancel: true, // persistent: true, // }).onOk((data: { num: number; dir: 'ltr' | 'rtl' }) => { // const { num, dir } = data; // const sectionData = section.datas; // const points = section.getSplitPoints(num); // const children: LogicSection[] = []; // let codeAppend = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.slice(0, num); // if ( // (dir === 'ltr' && // sectionData.points[0].x > // sectionData.points[sectionData.points.length - 1].x) || // (dir === 'rtl' && // sectionData.points[0].x < // sectionData.points[sectionData.points.length - 1].x) // ) { // codeAppend = codeAppend.split('').reverse().join(''); // } // points.forEach((ps, i) => { // const data = new LogicSectionData(); // const logicSectionDraw = // this.drawAssistant.app.getDrawAssistant( // LogicSection.Type, // ); // data.id = logicSectionDraw.nextId(); // data.code = `${sectionData.code}-${codeAppend.charAt(i % 26)}`; // data.points = ps.map( // (p) => new graphicData.Point({ x: p.x, y: p.y }), // ); // data.id = this.drawAssistant.nextId(); // data.childTransforms = [ // new ChildTransform( // 'label', // new GraphicTransform( // { // x: // data.points[0].x + // (data.points[1].x - data.points[0].x) / 2, // y: // data.points[0].y + // (data.points[1].y - data.points[0].y) / 2 + // 20, // }, // { x: 0, y: 0 }, // 0, // { x: 0, y: 0 }, // ), // ), // ]; // const g = logicSectionDraw.graphicTemplate.new(); // g.loadData(data); // logicSectionDraw.storeGraphic(g); // children.push(g); // }); // // sectionData.children = children.map((g) => g.datas.id); // section.repaint(); // section.buildRelation(); // section.draggable = false; // children.forEach((c) => c.buildRelation()); // this.app.updateSelected(...children); // }); // }; SectionEditMenu.open(e.global); } onSelected(g) { const section = g; if (section.datas.sectionType === SectionType.TurnoutPhysical) { return; } if (section.datas.isCurve) { let lep = section.getAssistantAppend(SectionBazierCurveEditPlugin.Name); if (!lep) { lep = new SectionBazierCurveEditPlugin(section, { onEditPointCreate, }); section.addAssistantAppend(lep); } lep.showAll(); } else { let lep = section.getAssistantAppend(SectionPolylineEditPlugin.Name); if (!lep) { lep = new SectionPolylineEditPlugin(section, { onEditPointCreate }); section.addAssistantAppend(lep); } lep.showAll(); } } onUnselected(g) { const section = g; if (section.datas.isCurve) { const lep = section.getAssistantAppend(SectionBazierCurveEditPlugin.Name); if (lep) { lep.hideAll(); } } else { const lep = section.getAssistantAppend(SectionPolylineEditPlugin.Name); if (lep) { lep.hideAll(); } } } } export { SectionDraw, SectionGraphicHitArea, SectionPointEditPlugin, addWaypointConfig, clearWaypointsConfig, splitSectionConfig };