439 lines
16 KiB
JavaScript
439 lines
16 KiB
JavaScript
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 '../GPSection.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<LogicSectionDraw>(
|
||
// 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 };
|