@eclipse-glsp/client 2.7.0-next.1 → 2.7.0-next.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/base/auto-complete/auto-complete-widget.d.ts +1 -1
- package/lib/base/auto-complete/auto-complete-widget.js +3 -5
- package/lib/base/auto-complete/auto-complete-widget.js.map +1 -1
- package/lib/base/auto-complete/validation-decorator.d.ts +1 -1
- package/lib/base/auto-complete/validation-decorator.d.ts.map +1 -1
- package/lib/base/auto-complete/validation-decorator.js +1 -4
- package/lib/base/auto-complete/validation-decorator.js.map +1 -1
- package/lib/base/default.module.d.ts +1 -1
- package/lib/base/default.module.js +2 -2
- package/lib/base/default.module.js.map +1 -1
- package/lib/base/feedback/feedback-action-dispatcher.d.ts +1 -1
- package/lib/base/feedback/feedback-action-dispatcher.js +2 -4
- package/lib/base/feedback/feedback-action-dispatcher.js.map +1 -1
- package/lib/base/feedback/feedback-command.d.ts +1 -1
- package/lib/base/feedback/feedback-command.js +2 -2
- package/lib/base/feedback/feedback-command.js.map +1 -1
- package/lib/base/focus/focus-tracker.d.ts +1 -1
- package/lib/base/focus/focus-tracker.js +1 -1
- package/lib/base/model/model-change-service.d.ts +1 -1
- package/lib/base/model/model-change-service.js +1 -1
- package/lib/base/ui-extension/ui-extension.d.ts +1 -1
- package/lib/base/ui-extension/ui-extension.js +1 -1
- package/lib/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.d.ts +1 -1
- package/lib/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.js +1 -1
- package/lib/features/bounds/bounds-module.d.ts +1 -1
- package/lib/features/bounds/bounds-module.js +2 -2
- package/lib/features/bounds/bounds-module.js.map +1 -1
- package/lib/features/bounds/glsp-hidden-bounds-updater.d.ts +1 -1
- package/lib/features/bounds/glsp-hidden-bounds-updater.js +1 -1
- package/lib/features/bounds/hbox-layout.d.ts +1 -1
- package/lib/features/bounds/hbox-layout.js +1 -2
- package/lib/features/bounds/hbox-layout.js.map +1 -1
- package/lib/features/bounds/vbox-layout.d.ts +1 -1
- package/lib/features/bounds/vbox-layout.js +1 -2
- package/lib/features/bounds/vbox-layout.js.map +1 -1
- package/lib/features/change-bounds/model.d.ts +1 -1
- package/lib/features/change-bounds/model.d.ts.map +1 -1
- package/lib/features/change-bounds/model.js +1 -2
- package/lib/features/change-bounds/model.js.map +1 -1
- package/lib/features/change-bounds/point-position-updater.d.ts +1 -1
- package/lib/features/change-bounds/point-position-updater.js +3 -4
- package/lib/features/change-bounds/point-position-updater.js.map +1 -1
- package/lib/features/change-bounds/snap.d.ts +1 -1
- package/lib/features/change-bounds/snap.js +15 -0
- package/lib/features/change-bounds/snap.js.map +1 -1
- package/lib/features/command-palette/command-palette.d.ts +1 -1
- package/lib/features/command-palette/command-palette.js +1 -1
- package/lib/features/context-menu/context-menu-module.d.ts +1 -1
- package/lib/features/context-menu/context-menu-module.js +1 -2
- package/lib/features/context-menu/context-menu-module.js.map +1 -1
- package/lib/features/context-menu/delete-element-context-menu.d.ts +1 -1
- package/lib/features/context-menu/delete-element-context-menu.js +1 -1
- package/lib/features/copy-paste/copy-paste-context-menu.d.ts +1 -1
- package/lib/features/copy-paste/copy-paste-context-menu.js +2 -2
- package/lib/features/copy-paste/copy-paste-context-menu.js.map +1 -1
- package/lib/features/debug/debug-bounds-decorator.d.ts +1 -1
- package/lib/features/debug/debug-bounds-decorator.js +12 -13
- package/lib/features/debug/debug-bounds-decorator.js.map +1 -1
- package/lib/features/label-edit-ui/label-edit-ui.d.ts +4 -1
- package/lib/features/label-edit-ui/label-edit-ui.d.ts.map +1 -1
- package/lib/features/label-edit-ui/label-edit-ui.js +38 -1
- package/lib/features/label-edit-ui/label-edit-ui.js.map +1 -1
- package/lib/features/navigation/navigation-action-handler.d.ts +1 -1
- package/lib/features/navigation/navigation-action-handler.js +1 -1
- package/lib/features/routing/edge-router.d.ts +2 -1
- package/lib/features/routing/edge-router.d.ts.map +1 -1
- package/lib/features/routing/edge-router.js +2 -1
- package/lib/features/routing/edge-router.js.map +1 -1
- package/lib/features/routing/routing-module.d.ts +1 -1
- package/lib/features/routing/routing-module.d.ts.map +1 -1
- package/lib/features/routing/routing-module.js +7 -1
- package/lib/features/routing/routing-module.js.map +1 -1
- package/lib/features/routing/sticky-manhattan-anchors.d.ts +29 -0
- package/lib/features/routing/sticky-manhattan-anchors.d.ts.map +1 -0
- package/lib/features/routing/sticky-manhattan-anchors.js +62 -0
- package/lib/features/routing/sticky-manhattan-anchors.js.map +1 -0
- package/lib/features/routing/sticky-manhattan-edge-router.d.ts +107 -0
- package/lib/features/routing/sticky-manhattan-edge-router.d.ts.map +1 -0
- package/lib/features/routing/sticky-manhattan-edge-router.js +626 -0
- package/lib/features/routing/sticky-manhattan-edge-router.js.map +1 -0
- package/lib/features/tool-palette/tool-palette.d.ts +1 -1
- package/lib/features/tool-palette/tool-palette.d.ts.map +1 -1
- package/lib/features/tool-palette/tool-palette.js +1 -3
- package/lib/features/tool-palette/tool-palette.js.map +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +5 -1
- package/lib/index.js.map +1 -1
- package/lib/views/gedge-view-with-gaps-on-intersections.d.ts +39 -0
- package/lib/views/gedge-view-with-gaps-on-intersections.d.ts.map +1 -0
- package/lib/views/gedge-view-with-gaps-on-intersections.js +85 -0
- package/lib/views/gedge-view-with-gaps-on-intersections.js.map +1 -0
- package/lib/views/issue-marker-view.d.ts +1 -1
- package/lib/views/issue-marker-view.js +9 -10
- package/lib/views/issue-marker-view.js.map +1 -1
- package/lib/views/rounded-corner-manhattan-edge-view.d.ts +58 -0
- package/lib/views/rounded-corner-manhattan-edge-view.d.ts.map +1 -0
- package/lib/views/rounded-corner-manhattan-edge-view.js +170 -0
- package/lib/views/rounded-corner-manhattan-edge-view.js.map +1 -0
- package/package.json +4 -4
- package/src/base/action-dispatcher.spec.ts +2 -2
- package/src/base/action-dispatcher.ts +1 -1
- package/src/base/auto-complete/auto-complete-widget.ts +3 -3
- package/src/base/auto-complete/validation-decorator.ts +3 -4
- package/src/base/default.module.ts +2 -2
- package/src/base/feedback/feedback-action-dispatcher.ts +2 -2
- package/src/base/feedback/feedback-command.ts +2 -2
- package/src/base/focus/focus-tracker.ts +1 -1
- package/src/base/model/model-change-service.ts +1 -1
- package/src/base/model/model-initialization-constraint.spec.ts +2 -2
- package/src/base/selection-service.spec.ts +3 -4
- package/src/base/ui-extension/ui-extension.ts +1 -1
- package/src/default-modules.spec.ts +2 -2
- package/src/features/accessibility/keyboard-tool-palette/keyboard-tool-palette.ts +1 -1
- package/src/features/bounds/bounds-module.ts +2 -2
- package/src/features/bounds/glsp-hidden-bounds-updater.ts +1 -1
- package/src/features/bounds/hbox-layout.spec.ts +2 -2
- package/src/features/bounds/hbox-layout.ts +2 -2
- package/src/features/bounds/vbox-layout.spec.ts +2 -2
- package/src/features/bounds/vbox-layout.ts +2 -2
- package/src/features/change-bounds/model.ts +1 -2
- package/src/features/change-bounds/point-position-updater.spec.ts +3 -3
- package/src/features/change-bounds/point-position-updater.ts +4 -4
- package/src/features/change-bounds/snap.ts +2 -2
- package/src/features/command-palette/command-palette.ts +1 -1
- package/src/features/context-menu/context-menu-module.ts +2 -2
- package/src/features/context-menu/delete-element-context-menu.ts +2 -2
- package/src/features/copy-paste/copy-paste-context-menu.ts +2 -2
- package/src/features/debug/debug-bounds-decorator.tsx +2 -2
- package/src/features/label-edit-ui/label-edit-ui.ts +36 -3
- package/src/features/layout/layout-elements-action.spec.ts +2 -2
- package/src/features/navigation/navigation-action-handler.ts +3 -3
- package/src/features/routing/edge-router.ts +2 -2
- package/src/features/routing/routing-module.ts +8 -1
- package/src/features/routing/sticky-manhattan-anchors.ts +53 -0
- package/src/features/routing/sticky-manhattan-edge-router.spec.ts +212 -0
- package/src/features/routing/sticky-manhattan-edge-router.ts +713 -0
- package/src/features/tool-palette/tool-palette.ts +1 -3
- package/src/index.ts +5 -1
- package/src/views/gedge-view-with-gaps-on-intersections.tsx +99 -0
- package/src/views/issue-marker-view.tsx +2 -2
- package/src/views/rounded-corner-manhattan-edge-view.ts +171 -0
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2022-2026 Imixs Software Solutions GmbH.
|
|
3
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
4
|
+
*
|
|
5
|
+
* This program and the accompanying materials are made available under the
|
|
6
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
7
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
8
|
+
*
|
|
9
|
+
* This Source Code may also be made available under the following Secondary
|
|
10
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
11
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
12
|
+
* with the GNU Classpath Exception which is available at
|
|
13
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
import {
|
|
18
|
+
Bounds,
|
|
19
|
+
DefaultAnchors,
|
|
20
|
+
GRoutableElement,
|
|
21
|
+
GRoutingHandle,
|
|
22
|
+
LinearRouteOptions,
|
|
23
|
+
Point,
|
|
24
|
+
ResolvedHandleMove,
|
|
25
|
+
RoutedPoint,
|
|
26
|
+
Side
|
|
27
|
+
} from '@eclipse-glsp/sprotty';
|
|
28
|
+
import { injectable } from 'inversify';
|
|
29
|
+
import { GLSPAbstractEdgeRouter, ensureBounds } from './edge-router';
|
|
30
|
+
|
|
31
|
+
export interface StickyManhattanRouterOptions extends LinearRouteOptions {
|
|
32
|
+
/**
|
|
33
|
+
* Tolerance in pixels for deciding whether a segment is strictly vertical or
|
|
34
|
+
* horizontal. Defaults to `1` to accommodate the sub-pixel drift typical for
|
|
35
|
+
* Manhattan-routed coordinates.
|
|
36
|
+
*/
|
|
37
|
+
axisTolerance: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* An alternative Manhattan-style edge router that preserves existing bend points.
|
|
42
|
+
*
|
|
43
|
+
* In contrast to the standard {@link GLSPManhattanEdgeRouter} which recomputes the
|
|
44
|
+
* full route whenever a connected node moves, this router only adjusts the corner
|
|
45
|
+
* adjacent to the moved endpoint. Intermediate bend points are kept in place, which
|
|
46
|
+
* results in more predictable routing when users reposition nodes in complex
|
|
47
|
+
* diagrams.
|
|
48
|
+
*
|
|
49
|
+
* Opt-in per edge by setting `edge.routerKind = 'sticky-manhattan'`.
|
|
50
|
+
*
|
|
51
|
+
* @experimental The API surface and implementation details of this router may
|
|
52
|
+
* change in future releases, potentially in breaking ways, as we iterate on the
|
|
53
|
+
* initial implementation.
|
|
54
|
+
*
|
|
55
|
+
* Based on the original `BPMNManhattanRouter` by Ralph Soika (Imixs Software
|
|
56
|
+
* Solutions GmbH), contributed via https://github.com/eclipse-glsp/glsp/discussions/1642.
|
|
57
|
+
*/
|
|
58
|
+
@injectable()
|
|
59
|
+
export class GLSPStickyManhattanEdgeRouter extends GLSPAbstractEdgeRouter {
|
|
60
|
+
static readonly KIND = 'sticky-manhattan';
|
|
61
|
+
|
|
62
|
+
/** Move-detection baseline: source/target bounds at the last persistence. */
|
|
63
|
+
protected readonly elementPositions = new WeakMap<
|
|
64
|
+
GRoutableElement,
|
|
65
|
+
{ sourceX: number; sourceY: number; targetX: number; targetY: number }
|
|
66
|
+
>();
|
|
67
|
+
|
|
68
|
+
get kind(): string {
|
|
69
|
+
return GLSPStickyManhattanEdgeRouter.KIND;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected getOptions(edge: GRoutableElement): StickyManhattanRouterOptions {
|
|
73
|
+
return {
|
|
74
|
+
standardDistance: 20,
|
|
75
|
+
minimalPointDistance: 3,
|
|
76
|
+
selfEdgeOffset: 0.25,
|
|
77
|
+
axisTolerance: 1
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override route(edge: GRoutableElement): RoutedPoint[] {
|
|
82
|
+
if (!edge.source || !edge.target || !edge.source.bounds || !edge.target.bounds) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const routedCorners = this.createRoutedCorners(edge);
|
|
87
|
+
const sourceRefPoint = routedCorners[0] ?? Bounds.center(edge.target.bounds);
|
|
88
|
+
const targetRefPoint = routedCorners[routedCorners.length - 1] ?? Bounds.center(edge.source.bounds);
|
|
89
|
+
|
|
90
|
+
const sourceAnchor = this.getTranslatedAnchor(edge.source, sourceRefPoint, edge.parent, edge, edge.sourceAnchorCorrection);
|
|
91
|
+
const targetAnchor = this.getTranslatedAnchor(edge.target, targetRefPoint, edge.parent, edge, edge.targetAnchorCorrection);
|
|
92
|
+
|
|
93
|
+
if (!sourceAnchor || !targetAnchor) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result: RoutedPoint[] = [];
|
|
98
|
+
result.push({ kind: 'source', ...sourceAnchor });
|
|
99
|
+
routedCorners.forEach(corner => result.push(corner));
|
|
100
|
+
result.push({ kind: 'target', ...targetAnchor });
|
|
101
|
+
|
|
102
|
+
// Guard against NaN coordinates which would break path rendering downstream.
|
|
103
|
+
const sanitized = result.filter(p => !isNaN(p.x) && !isNaN(p.y));
|
|
104
|
+
if (sanitized.length < 2) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return sanitized;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
protected override commitRoute(edge: GRoutableElement, routedPoints: RoutedPoint[]): void {
|
|
111
|
+
super.commitRoute(edge, routedPoints);
|
|
112
|
+
this.captureBaseline(edge);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Snapshots current source/target bounds as the move-detection baseline. */
|
|
116
|
+
protected captureBaseline(edge: GRoutableElement): void {
|
|
117
|
+
if (edge.source?.bounds && edge.target?.bounds) {
|
|
118
|
+
this.elementPositions.set(edge, {
|
|
119
|
+
sourceX: edge.source.bounds.x,
|
|
120
|
+
sourceY: edge.source.bounds.y,
|
|
121
|
+
targetX: edge.target.bounds.x,
|
|
122
|
+
targetY: edge.target.bounds.y
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Computes the intermediate corner points between source and target anchors.
|
|
129
|
+
*
|
|
130
|
+
* If the edge already has routing points and the connected nodes have moved
|
|
131
|
+
* since the previous route, the existing corners are preserved and only the
|
|
132
|
+
* endpoints closest to the moved node(s) slide along their constrained axis.
|
|
133
|
+
* Otherwise the corners are recomputed from scratch based on the current node
|
|
134
|
+
* geometry.
|
|
135
|
+
*/
|
|
136
|
+
protected createRoutedCorners(edge: GRoutableElement): RoutedPoint[] {
|
|
137
|
+
if (!edge.source?.bounds || !edge.target?.bounds) {
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (edge.routingPoints && edge.routingPoints.length > 0) {
|
|
142
|
+
const srcBounds = edge.source.bounds;
|
|
143
|
+
const tgtBounds = edge.target.bounds;
|
|
144
|
+
const lastPos = this.elementPositions.get(edge);
|
|
145
|
+
const elementMoved =
|
|
146
|
+
lastPos !== undefined &&
|
|
147
|
+
(lastPos.sourceX !== srcBounds.x ||
|
|
148
|
+
lastPos.sourceY !== srcBounds.y ||
|
|
149
|
+
lastPos.targetX !== tgtBounds.x ||
|
|
150
|
+
lastPos.targetY !== tgtBounds.y);
|
|
151
|
+
|
|
152
|
+
// Seed the baseline on first sight; later refreshes happen in
|
|
153
|
+
// commitRoute / cleanupRoutingPoints to keep route() pure.
|
|
154
|
+
if (lastPos === undefined) {
|
|
155
|
+
this.elementPositions.set(edge, {
|
|
156
|
+
sourceX: srcBounds.x,
|
|
157
|
+
sourceY: srcBounds.y,
|
|
158
|
+
targetX: tgtBounds.x,
|
|
159
|
+
targetY: tgtBounds.y
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const points = edge.routingPoints.slice();
|
|
164
|
+
const sourceAnchors = new DefaultAnchors(edge.source, edge.parent, 'source');
|
|
165
|
+
const targetAnchors = new DefaultAnchors(edge.target, edge.parent, 'target');
|
|
166
|
+
|
|
167
|
+
if (elementMoved) {
|
|
168
|
+
// Follow-adjust endpoints; rendering-only, edge.routingPoints
|
|
169
|
+
// is persisted via commitRoute.
|
|
170
|
+
this.applyFollowLogic(points, edge, sourceAnchors);
|
|
171
|
+
this.cleanupRoutingPoints(edge, points, false, false, sourceAnchors, targetAnchors);
|
|
172
|
+
} else {
|
|
173
|
+
this.cleanupRoutingPoints(edge, points, false, true, sourceAnchors, targetAnchors);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (points.length > 0) {
|
|
177
|
+
return points.map((rp, index) => ({
|
|
178
|
+
kind: 'linear' as const,
|
|
179
|
+
pointIndex: index,
|
|
180
|
+
x: rp.x,
|
|
181
|
+
y: rp.y
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Fresh-default fallback: drop the baseline so the next real route re-seeds.
|
|
187
|
+
this.elementPositions.delete(edge);
|
|
188
|
+
|
|
189
|
+
const sourceAnchors = new DefaultAnchors(edge.source, edge.parent, 'source');
|
|
190
|
+
const targetAnchors = new DefaultAnchors(edge.target, edge.parent, 'target');
|
|
191
|
+
const options = this.getOptions(edge);
|
|
192
|
+
const bestAnchors = this.getBestConnectionAnchors(sourceAnchors, targetAnchors, options);
|
|
193
|
+
const corners = this.calculateCorners(sourceAnchors, targetAnchors, bestAnchors, options);
|
|
194
|
+
return corners.map((corner, index) => ({
|
|
195
|
+
kind: 'linear' as const,
|
|
196
|
+
pointIndex: index,
|
|
197
|
+
x: corner.x,
|
|
198
|
+
y: corner.y
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Adjusts the endpoints of the existing route so they follow the moved node(s).
|
|
204
|
+
* The corner adjacent to a moved endpoint slides along its constrained axis so
|
|
205
|
+
* that the route stays orthogonal without discarding intermediate bend points.
|
|
206
|
+
*/
|
|
207
|
+
protected applyFollowLogic(points: Point[], edge: GRoutableElement, sourceAnchors?: DefaultAnchors): void {
|
|
208
|
+
const sourceAnchor = this.getTranslatedAnchor(edge.source!, points[0], edge.parent, edge, edge.sourceAnchorCorrection);
|
|
209
|
+
const targetAnchor = this.getTranslatedAnchor(
|
|
210
|
+
edge.target!,
|
|
211
|
+
points[points.length - 1],
|
|
212
|
+
edge.parent,
|
|
213
|
+
edge,
|
|
214
|
+
edge.targetAnchorCorrection
|
|
215
|
+
);
|
|
216
|
+
if (!sourceAnchor || !targetAnchor) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const { axisTolerance } = this.getOptions(edge);
|
|
221
|
+
|
|
222
|
+
if (points.length === 1) {
|
|
223
|
+
// Classify the first segment via the source side closest to the bend — this
|
|
224
|
+
// matches how the Manhattan anchor computer picks the source anchor.
|
|
225
|
+
sourceAnchors ??= new DefaultAnchors(edge.source!, edge.parent, 'source');
|
|
226
|
+
const side = sourceAnchors.getNearestSide(points[0]);
|
|
227
|
+
const isVertical = side === Side.TOP || side === Side.BOTTOM;
|
|
228
|
+
if (isVertical) {
|
|
229
|
+
points[0] = { x: sourceAnchor.x, y: Bounds.center(edge.target!.bounds).y };
|
|
230
|
+
} else {
|
|
231
|
+
points[0] = { x: Bounds.center(edge.target!.bounds).x, y: sourceAnchor.y };
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// First corner follows the source element.
|
|
237
|
+
if (Point.isVerticalAligned(points[0], points[1], axisTolerance)) {
|
|
238
|
+
points[0] = { x: points[0].x, y: Bounds.center(edge.source!.bounds).y };
|
|
239
|
+
} else {
|
|
240
|
+
points[0] = { x: Bounds.center(edge.source!.bounds).x, y: points[0].y };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Last corner follows the target element.
|
|
244
|
+
const last = points.length - 1;
|
|
245
|
+
if (Point.isVerticalAligned(points[last], points[last - 1], axisTolerance)) {
|
|
246
|
+
points[last] = { x: points[last].x, y: Bounds.center(edge.target!.bounds).y };
|
|
247
|
+
} else {
|
|
248
|
+
points[last] = { x: Bounds.center(edge.target!.bounds).x, y: points[last].y };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
override createRoutingHandles(edge: GRoutableElement): void {
|
|
253
|
+
const routedPoints = this.route(edge);
|
|
254
|
+
this.commitRoute(edge, routedPoints);
|
|
255
|
+
if (routedPoints.length > 0) {
|
|
256
|
+
this.addHandle(edge, 'source', 'routing-point', -2);
|
|
257
|
+
for (let i = 0; i < routedPoints.length - 1; i++) {
|
|
258
|
+
this.addHandle(edge, 'manhattan-50%', 'volatile-routing-point', i - 1);
|
|
259
|
+
}
|
|
260
|
+
this.addHandle(edge, 'target', 'routing-point', routedPoints.length - 2);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
protected getInnerHandlePosition(edge: GRoutableElement, route: RoutedPoint[], handle: GRoutingHandle): Point | undefined {
|
|
265
|
+
if (handle.kind === 'manhattan-50%') {
|
|
266
|
+
const { start, end } = this.findRouteSegment(edge, route, handle.pointIndex);
|
|
267
|
+
if (start !== undefined && end !== undefined) {
|
|
268
|
+
return Point.linear(start, end, 0.5);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
protected applyInnerHandleMoves(edge: GRoutableElement, moves: ResolvedHandleMove[]): void {
|
|
275
|
+
const route = this.route(edge);
|
|
276
|
+
const routingPoints = edge.routingPoints;
|
|
277
|
+
const { minimalPointDistance, axisTolerance } = this.getOptions(edge);
|
|
278
|
+
|
|
279
|
+
moves.forEach(move => {
|
|
280
|
+
const handle = move.handle;
|
|
281
|
+
const index = handle.pointIndex;
|
|
282
|
+
|
|
283
|
+
if (handle.kind !== 'manhattan-50%') {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const correctedX = this.correctX(routingPoints, index, move.toPosition.x, minimalPointDistance);
|
|
288
|
+
const correctedY = this.correctY(routingPoints, index, move.toPosition.y, minimalPointDistance);
|
|
289
|
+
|
|
290
|
+
if (index < 0) {
|
|
291
|
+
// First segment: source anchor → first corner.
|
|
292
|
+
if (routingPoints.length === 0) {
|
|
293
|
+
routingPoints.push({ x: correctedX, y: correctedY });
|
|
294
|
+
handle.pointIndex = 0;
|
|
295
|
+
} else if (Point.isVerticalAligned(route[0], route[1], axisTolerance)) {
|
|
296
|
+
this.alignX(routingPoints, 0, correctedX);
|
|
297
|
+
} else {
|
|
298
|
+
this.alignY(routingPoints, 0, correctedY);
|
|
299
|
+
}
|
|
300
|
+
} else if (index < routingPoints.length - 1) {
|
|
301
|
+
// Inner segment: move both endpoints of the segment in lockstep.
|
|
302
|
+
if (Point.isVerticalAligned(routingPoints[index], routingPoints[index + 1], axisTolerance)) {
|
|
303
|
+
this.alignX(routingPoints, index, correctedX);
|
|
304
|
+
this.alignX(routingPoints, index + 1, correctedX);
|
|
305
|
+
} else {
|
|
306
|
+
this.alignY(routingPoints, index, correctedY);
|
|
307
|
+
this.alignY(routingPoints, index + 1, correctedY);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
// Last segment: last corner → target anchor.
|
|
311
|
+
if (routingPoints.length === 0) {
|
|
312
|
+
routingPoints.push({ x: correctedX, y: correctedY });
|
|
313
|
+
handle.pointIndex = 0;
|
|
314
|
+
} else if (Point.isVerticalAligned(route[route.length - 2], route[route.length - 1], axisTolerance)) {
|
|
315
|
+
this.alignX(routingPoints, routingPoints.length - 1, correctedX);
|
|
316
|
+
} else {
|
|
317
|
+
this.alignY(routingPoints, routingPoints.length - 1, correctedY);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
override cleanupRoutingPoints(
|
|
324
|
+
edge: GRoutableElement,
|
|
325
|
+
routingPoints: Point[],
|
|
326
|
+
updateHandles: boolean,
|
|
327
|
+
addRoutingPoints: boolean,
|
|
328
|
+
sourceAnchors?: DefaultAnchors,
|
|
329
|
+
targetAnchors?: DefaultAnchors
|
|
330
|
+
): void {
|
|
331
|
+
if (!ensureBounds(edge.source) || !ensureBounds(edge.target)) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
sourceAnchors ??= new DefaultAnchors(edge.source!, edge.parent, 'source');
|
|
335
|
+
targetAnchors ??= new DefaultAnchors(edge.target!, edge.parent, 'target');
|
|
336
|
+
const options = this.getOptions(edge);
|
|
337
|
+
|
|
338
|
+
// Persistence-style cleanup (applyHandleMoves / applyReconnect): refresh
|
|
339
|
+
// baseline so route() doesn't re-follow the committed coordinates.
|
|
340
|
+
if (updateHandles) {
|
|
341
|
+
this.captureBaseline(edge);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (this.resetRoutingPointsOnReconnect(edge, routingPoints, updateHandles, sourceAnchors, targetAnchors)) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Remove leading routing points that fall inside the source bounds.
|
|
349
|
+
for (let i = 0; i < routingPoints.length; i++) {
|
|
350
|
+
if (Bounds.includes(sourceAnchors.bounds, routingPoints[i])) {
|
|
351
|
+
routingPoints.splice(0, 1);
|
|
352
|
+
if (updateHandles) {
|
|
353
|
+
this.removeHandle(edge, -1);
|
|
354
|
+
}
|
|
355
|
+
i--;
|
|
356
|
+
} else {
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Remove trailing routing points that fall inside the target bounds.
|
|
362
|
+
for (let i = routingPoints.length - 1; i >= 0; i--) {
|
|
363
|
+
if (Bounds.includes(targetAnchors.bounds, routingPoints[i])) {
|
|
364
|
+
routingPoints.splice(i, 1);
|
|
365
|
+
if (updateHandles) {
|
|
366
|
+
this.removeHandle(edge, i);
|
|
367
|
+
}
|
|
368
|
+
} else {
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Collapse degenerate segments shorter than minimalPointDistance.
|
|
374
|
+
if (routingPoints.length >= 2) {
|
|
375
|
+
for (let i = routingPoints.length - 2; i >= 0; i--) {
|
|
376
|
+
if (Point.manhattanDistance(routingPoints[i], routingPoints[i + 1]) < options.minimalPointDistance) {
|
|
377
|
+
routingPoints.splice(i, 2);
|
|
378
|
+
i--;
|
|
379
|
+
if (updateHandles) {
|
|
380
|
+
this.removeHandle(edge, i - 1);
|
|
381
|
+
this.removeHandle(edge, i);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (addRoutingPoints) {
|
|
388
|
+
this.addAdditionalCorner(edge, routingPoints, sourceAnchors, targetAnchors, updateHandles);
|
|
389
|
+
this.addAdditionalCorner(edge, routingPoints, targetAnchors, sourceAnchors, updateHandles);
|
|
390
|
+
this.manhattanify(routingPoints, edge);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
protected removeHandle(edge: GRoutableElement, pointIndex: number): void {
|
|
395
|
+
const toBeRemoved: GRoutingHandle[] = [];
|
|
396
|
+
edge.children.forEach(child => {
|
|
397
|
+
if (child instanceof GRoutingHandle) {
|
|
398
|
+
if (child.pointIndex > pointIndex) {
|
|
399
|
+
child.pointIndex--;
|
|
400
|
+
} else if (child.pointIndex === pointIndex) {
|
|
401
|
+
toBeRemoved.push(child);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
toBeRemoved.forEach(child => edge.remove(child));
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Inserts an additional corner when the first/last routing point is outside
|
|
410
|
+
* the node bounds in the direction of the adjacent segment. This keeps the
|
|
411
|
+
* route orthogonal after routing points have been dragged outside the node.
|
|
412
|
+
*/
|
|
413
|
+
protected addAdditionalCorner(
|
|
414
|
+
edge: GRoutableElement,
|
|
415
|
+
points: Point[],
|
|
416
|
+
currentAnchors: DefaultAnchors,
|
|
417
|
+
otherAnchors: DefaultAnchors,
|
|
418
|
+
updateHandles: boolean
|
|
419
|
+
): void {
|
|
420
|
+
if (points.length === 0) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const isSource = currentAnchors.kind === 'source';
|
|
425
|
+
const refPoint = isSource ? points[0] : points[points.length - 1];
|
|
426
|
+
const insertIndex = isSource ? 0 : points.length;
|
|
427
|
+
const shiftIndex = insertIndex - (isSource ? 1 : 0);
|
|
428
|
+
const { axisTolerance } = this.getOptions(edge);
|
|
429
|
+
|
|
430
|
+
let isHorizontal: boolean;
|
|
431
|
+
if (points.length > 1) {
|
|
432
|
+
isHorizontal = isSource
|
|
433
|
+
? Point.isVerticalAligned(points[0], points[1], axisTolerance)
|
|
434
|
+
: Point.isVerticalAligned(points[points.length - 1], points[points.length - 2], axisTolerance);
|
|
435
|
+
} else {
|
|
436
|
+
const nearestSide = otherAnchors.getNearestSide(refPoint);
|
|
437
|
+
isHorizontal = nearestSide === Side.TOP || nearestSide === Side.BOTTOM;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if (isHorizontal) {
|
|
441
|
+
const topY = currentAnchors.get(Side.TOP).y;
|
|
442
|
+
const bottomY = currentAnchors.get(Side.BOTTOM).y;
|
|
443
|
+
if (refPoint.y < topY || refPoint.y > bottomY) {
|
|
444
|
+
const newPoint: Point = { x: currentAnchors.get(Side.TOP).x, y: refPoint.y };
|
|
445
|
+
points.splice(insertIndex, 0, newPoint);
|
|
446
|
+
if (updateHandles) {
|
|
447
|
+
edge.children.forEach(child => {
|
|
448
|
+
if (child instanceof GRoutingHandle && child.pointIndex >= shiftIndex) {
|
|
449
|
+
child.pointIndex++;
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
this.addHandle(edge, 'manhattan-50%', 'volatile-routing-point', shiftIndex);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
const leftX = currentAnchors.get(Side.LEFT).x;
|
|
457
|
+
const rightX = currentAnchors.get(Side.RIGHT).x;
|
|
458
|
+
if (refPoint.x < leftX || refPoint.x > rightX) {
|
|
459
|
+
const newPoint: Point = { x: refPoint.x, y: currentAnchors.get(Side.LEFT).y };
|
|
460
|
+
points.splice(insertIndex, 0, newPoint);
|
|
461
|
+
if (updateHandles) {
|
|
462
|
+
edge.children.forEach(child => {
|
|
463
|
+
if (child instanceof GRoutingHandle && child.pointIndex >= shiftIndex) {
|
|
464
|
+
child.pointIndex++;
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
this.addHandle(edge, 'manhattan-50%', 'volatile-routing-point', shiftIndex);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/** Inserts intermediate corner points so every segment is strictly orthogonal. */
|
|
474
|
+
protected manhattanify(points: Point[], edge: GRoutableElement): void {
|
|
475
|
+
const { axisTolerance } = this.getOptions(edge);
|
|
476
|
+
for (let i = 1; i < points.length; i++) {
|
|
477
|
+
if (!Point.isAxisAligned(points[i - 1], points[i], axisTolerance)) {
|
|
478
|
+
points.splice(i, 0, { x: points[i - 1].x, y: points[i].y });
|
|
479
|
+
i++;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Picks the sides on source and target for a fresh default route, preferring
|
|
486
|
+
* (in order) direct, one-corner, and finally two-corner connections.
|
|
487
|
+
*/
|
|
488
|
+
protected getBestConnectionAnchors(
|
|
489
|
+
sourceAnchors: DefaultAnchors,
|
|
490
|
+
targetAnchors: DefaultAnchors,
|
|
491
|
+
options: StickyManhattanRouterOptions
|
|
492
|
+
): { source: Side; target: Side } {
|
|
493
|
+
const sd = options.standardDistance;
|
|
494
|
+
|
|
495
|
+
// Direct connections (no corners).
|
|
496
|
+
if (targetAnchors.get(Side.LEFT).x - sourceAnchors.get(Side.RIGHT).x > sd) {
|
|
497
|
+
return { source: Side.RIGHT, target: Side.LEFT };
|
|
498
|
+
}
|
|
499
|
+
if (sourceAnchors.get(Side.LEFT).x - targetAnchors.get(Side.RIGHT).x > sd) {
|
|
500
|
+
return { source: Side.LEFT, target: Side.RIGHT };
|
|
501
|
+
}
|
|
502
|
+
if (sourceAnchors.get(Side.TOP).y - targetAnchors.get(Side.BOTTOM).y > sd) {
|
|
503
|
+
return { source: Side.TOP, target: Side.BOTTOM };
|
|
504
|
+
}
|
|
505
|
+
if (targetAnchors.get(Side.TOP).y - sourceAnchors.get(Side.BOTTOM).y > sd) {
|
|
506
|
+
return { source: Side.BOTTOM, target: Side.TOP };
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// One-corner connections.
|
|
510
|
+
if (
|
|
511
|
+
targetAnchors.get(Side.TOP).x - sourceAnchors.get(Side.RIGHT).x > 0.5 * sd &&
|
|
512
|
+
targetAnchors.get(Side.TOP).y - sourceAnchors.get(Side.RIGHT).y > sd
|
|
513
|
+
) {
|
|
514
|
+
return { source: Side.RIGHT, target: Side.TOP };
|
|
515
|
+
}
|
|
516
|
+
if (
|
|
517
|
+
targetAnchors.get(Side.BOTTOM).x - sourceAnchors.get(Side.RIGHT).x > 0.5 * sd &&
|
|
518
|
+
sourceAnchors.get(Side.RIGHT).y - targetAnchors.get(Side.BOTTOM).y > sd
|
|
519
|
+
) {
|
|
520
|
+
return { source: Side.RIGHT, target: Side.BOTTOM };
|
|
521
|
+
}
|
|
522
|
+
if (
|
|
523
|
+
sourceAnchors.get(Side.LEFT).x - targetAnchors.get(Side.BOTTOM).x > 0.5 * sd &&
|
|
524
|
+
sourceAnchors.get(Side.LEFT).y - targetAnchors.get(Side.BOTTOM).y > sd
|
|
525
|
+
) {
|
|
526
|
+
return { source: Side.LEFT, target: Side.BOTTOM };
|
|
527
|
+
}
|
|
528
|
+
if (
|
|
529
|
+
sourceAnchors.get(Side.LEFT).x - targetAnchors.get(Side.TOP).x > 0.5 * sd &&
|
|
530
|
+
targetAnchors.get(Side.TOP).y - sourceAnchors.get(Side.LEFT).y > sd
|
|
531
|
+
) {
|
|
532
|
+
return { source: Side.LEFT, target: Side.TOP };
|
|
533
|
+
}
|
|
534
|
+
if (
|
|
535
|
+
targetAnchors.get(Side.RIGHT).y - sourceAnchors.get(Side.BOTTOM).y > 0.5 * sd &&
|
|
536
|
+
sourceAnchors.get(Side.BOTTOM).x - targetAnchors.get(Side.RIGHT).x > sd
|
|
537
|
+
) {
|
|
538
|
+
return { source: Side.BOTTOM, target: Side.RIGHT };
|
|
539
|
+
}
|
|
540
|
+
if (
|
|
541
|
+
targetAnchors.get(Side.LEFT).y - sourceAnchors.get(Side.BOTTOM).y > 0.5 * sd &&
|
|
542
|
+
targetAnchors.get(Side.LEFT).x - sourceAnchors.get(Side.BOTTOM).x > sd
|
|
543
|
+
) {
|
|
544
|
+
return { source: Side.BOTTOM, target: Side.LEFT };
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Two-corner fallback. Simpler than sprotty's equivalent: no distance
|
|
548
|
+
// gate on same-side candidates, no diagonal fallbacks.
|
|
549
|
+
const srcTop = sourceAnchors.get(Side.TOP);
|
|
550
|
+
const tgtTop = targetAnchors.get(Side.TOP);
|
|
551
|
+
if (!Bounds.includes(targetAnchors.bounds, srcTop) && !Bounds.includes(sourceAnchors.bounds, tgtTop)) {
|
|
552
|
+
return { source: Side.TOP, target: Side.TOP };
|
|
553
|
+
}
|
|
554
|
+
const srcRight = sourceAnchors.get(Side.RIGHT);
|
|
555
|
+
const tgtRight = targetAnchors.get(Side.RIGHT);
|
|
556
|
+
if (!Bounds.includes(targetAnchors.bounds, srcRight) && !Bounds.includes(sourceAnchors.bounds, tgtRight)) {
|
|
557
|
+
return { source: Side.RIGHT, target: Side.RIGHT };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { source: Side.RIGHT, target: Side.LEFT };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** Computes 0, 1, or 2 corner points for a fresh default route between the given sides. */
|
|
564
|
+
protected calculateCorners(
|
|
565
|
+
sourceAnchors: DefaultAnchors,
|
|
566
|
+
targetAnchors: DefaultAnchors,
|
|
567
|
+
sides: { source: Side; target: Side },
|
|
568
|
+
options: StickyManhattanRouterOptions
|
|
569
|
+
): Point[] {
|
|
570
|
+
const src = sourceAnchors.get(sides.source);
|
|
571
|
+
const tgt = targetAnchors.get(sides.target);
|
|
572
|
+
const sd = options.standardDistance;
|
|
573
|
+
|
|
574
|
+
switch (sides.source) {
|
|
575
|
+
case Side.RIGHT:
|
|
576
|
+
switch (sides.target) {
|
|
577
|
+
case Side.LEFT: {
|
|
578
|
+
if (src.y !== tgt.y) {
|
|
579
|
+
const midX = (src.x + tgt.x) / 2;
|
|
580
|
+
return [
|
|
581
|
+
{ x: midX, y: src.y },
|
|
582
|
+
{ x: midX, y: tgt.y }
|
|
583
|
+
];
|
|
584
|
+
}
|
|
585
|
+
return [];
|
|
586
|
+
}
|
|
587
|
+
case Side.TOP:
|
|
588
|
+
case Side.BOTTOM:
|
|
589
|
+
return [{ x: tgt.x, y: src.y }];
|
|
590
|
+
case Side.RIGHT: {
|
|
591
|
+
const maxX = Math.max(src.x, tgt.x) + 1.5 * sd;
|
|
592
|
+
return [
|
|
593
|
+
{ x: maxX, y: src.y },
|
|
594
|
+
{ x: maxX, y: tgt.y }
|
|
595
|
+
];
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
break;
|
|
599
|
+
|
|
600
|
+
case Side.LEFT:
|
|
601
|
+
switch (sides.target) {
|
|
602
|
+
case Side.RIGHT: {
|
|
603
|
+
if (src.y !== tgt.y) {
|
|
604
|
+
const midX = (src.x + tgt.x) / 2;
|
|
605
|
+
return [
|
|
606
|
+
{ x: midX, y: src.y },
|
|
607
|
+
{ x: midX, y: tgt.y }
|
|
608
|
+
];
|
|
609
|
+
}
|
|
610
|
+
return [];
|
|
611
|
+
}
|
|
612
|
+
case Side.TOP:
|
|
613
|
+
case Side.BOTTOM:
|
|
614
|
+
return [{ x: tgt.x, y: src.y }];
|
|
615
|
+
default: {
|
|
616
|
+
const minX = Math.min(src.x, tgt.x) - 1.5 * sd;
|
|
617
|
+
return [
|
|
618
|
+
{ x: minX, y: src.y },
|
|
619
|
+
{ x: minX, y: tgt.y }
|
|
620
|
+
];
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
case Side.TOP:
|
|
625
|
+
switch (sides.target) {
|
|
626
|
+
case Side.BOTTOM: {
|
|
627
|
+
if (src.x !== tgt.x) {
|
|
628
|
+
const midY = (src.y + tgt.y) / 2;
|
|
629
|
+
return [
|
|
630
|
+
{ x: src.x, y: midY },
|
|
631
|
+
{ x: tgt.x, y: midY }
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
case Side.TOP: {
|
|
637
|
+
const minY = Math.min(src.y, tgt.y) - 1.5 * sd;
|
|
638
|
+
return [
|
|
639
|
+
{ x: src.x, y: minY },
|
|
640
|
+
{ x: tgt.x, y: minY }
|
|
641
|
+
];
|
|
642
|
+
}
|
|
643
|
+
case Side.RIGHT:
|
|
644
|
+
case Side.LEFT:
|
|
645
|
+
return [{ x: src.x, y: tgt.y }];
|
|
646
|
+
}
|
|
647
|
+
break;
|
|
648
|
+
|
|
649
|
+
case Side.BOTTOM:
|
|
650
|
+
switch (sides.target) {
|
|
651
|
+
case Side.TOP: {
|
|
652
|
+
if (src.x !== tgt.x) {
|
|
653
|
+
const midY = (src.y + tgt.y) / 2;
|
|
654
|
+
return [
|
|
655
|
+
{ x: src.x, y: midY },
|
|
656
|
+
{ x: tgt.x, y: midY }
|
|
657
|
+
];
|
|
658
|
+
}
|
|
659
|
+
return [];
|
|
660
|
+
}
|
|
661
|
+
case Side.BOTTOM: {
|
|
662
|
+
const maxY = Math.max(src.y, tgt.y) + 1.5 * sd;
|
|
663
|
+
return [
|
|
664
|
+
{ x: src.x, y: maxY },
|
|
665
|
+
{ x: tgt.x, y: maxY }
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
case Side.RIGHT:
|
|
669
|
+
case Side.LEFT:
|
|
670
|
+
return [{ x: src.x, y: tgt.y }];
|
|
671
|
+
}
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const midX = (src.x + tgt.x) / 2;
|
|
676
|
+
return [
|
|
677
|
+
{ x: midX, y: src.y },
|
|
678
|
+
{ x: midX, y: tgt.y }
|
|
679
|
+
];
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
protected correctX(routingPoints: Point[], index: number, x: number, minimalPointDistance: number): number {
|
|
683
|
+
if (index > 0 && Math.abs(x - routingPoints[index - 1].x) < minimalPointDistance) {
|
|
684
|
+
return routingPoints[index - 1].x;
|
|
685
|
+
}
|
|
686
|
+
if (index < routingPoints.length - 2 && Math.abs(x - routingPoints[index + 2].x) < minimalPointDistance) {
|
|
687
|
+
return routingPoints[index + 2].x;
|
|
688
|
+
}
|
|
689
|
+
return x;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
protected correctY(routingPoints: Point[], index: number, y: number, minimalPointDistance: number): number {
|
|
693
|
+
if (index > 0 && Math.abs(y - routingPoints[index - 1].y) < minimalPointDistance) {
|
|
694
|
+
return routingPoints[index - 1].y;
|
|
695
|
+
}
|
|
696
|
+
if (index < routingPoints.length - 2 && Math.abs(y - routingPoints[index + 2].y) < minimalPointDistance) {
|
|
697
|
+
return routingPoints[index + 2].y;
|
|
698
|
+
}
|
|
699
|
+
return y;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
protected alignX(points: Point[], index: number, x: number): void {
|
|
703
|
+
if (index >= 0 && index < points.length) {
|
|
704
|
+
points[index] = { x, y: points[index].y };
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
protected alignY(points: Point[], index: number, y: number): void {
|
|
709
|
+
if (index >= 0 && index < points.length) {
|
|
710
|
+
points[index] = { x: points[index].x, y };
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|