1 | /*
|
2 | * Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
|
3 | */
|
4 | /*
|
5 | * This is an extension and not part of the main GoJS library.
|
6 | * Note that the API for this class may change with any version, even point releases.
|
7 | * If you intend to use an extension in production, you should copy the code to your own source directory.
|
8 | * Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
9 | * See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
10 | */
|
11 | import * as go from '../release/go-module.js';
|
12 | /**
|
13 | * The SnapLinkReshapingTool class lets the user snap link reshaping handles to the nearest grid point.
|
14 | * If {@link #avoidsNodes} is true and the link is orthogonal,
|
15 | * it also avoids reshaping the link so that any adjacent segments cross over any avoidable nodes.
|
16 | *
|
17 | * If you want to experiment with this extension, try the <a href="../../extensionsJSM/SnapLinkReshaping.html">Snap Link Reshaping</a> sample.
|
18 | * @category Tool Extension
|
19 | */
|
20 | export class SnapLinkReshapingTool extends go.LinkReshapingTool {
|
21 | constructor() {
|
22 | super(...arguments);
|
23 | this._gridCellSize = new go.Size(NaN, NaN);
|
24 | this._gridOrigin = new go.Point(NaN, NaN);
|
25 | this._isGridSnapEnabled = true;
|
26 | this._avoidsNodes = true;
|
27 | // internal state
|
28 | this._safePoint = new go.Point(NaN, NaN);
|
29 | this._prevSegHoriz = false;
|
30 | this._nextSegHoriz = false;
|
31 | }
|
32 | /**
|
33 | * Gets or sets the {@link Size} of each grid cell to which link points will be snapped.
|
34 | *
|
35 | * The default value is NaNxNaN, which means use the {@link Diagram#grid}'s {@link Panel#gridCellSize}.
|
36 | */
|
37 | get gridCellSize() { return this._gridCellSize; }
|
38 | set gridCellSize(val) {
|
39 | if (!(val instanceof go.Size))
|
40 | throw new Error('new value for SnapLinkReshapingTool.gridCellSize must be a Size, not: ' + val);
|
41 | this._gridCellSize = val.copy();
|
42 | }
|
43 | /**
|
44 | * Gets or sets the {@link Point} origin for the grid to which link points will be snapped.
|
45 | *
|
46 | * The default value is NaN,NaN, which means use the {@link Diagram#grid}'s {@link Panel#gridOrigin}.
|
47 | */
|
48 | get gridOrigin() { return this._gridOrigin; }
|
49 | set gridOrigin(val) {
|
50 | if (!(val instanceof go.Point))
|
51 | throw new Error('new value for SnapLinkReshapingTool.gridOrigin must be a Point, not: ' + val);
|
52 | this._gridOrigin = val.copy();
|
53 | }
|
54 | /**
|
55 | * Gets or sets whether a reshape handle's position should be snapped to a grid point.
|
56 | * This affects the behavior of {@link #computeReshape}.
|
57 | *
|
58 | * The default value is true.
|
59 | */
|
60 | get isGridSnapEnabled() { return this._isGridSnapEnabled; }
|
61 | set isGridSnapEnabled(val) {
|
62 | if (typeof val !== 'boolean')
|
63 | throw new Error('new value for SnapLinkReshapingTool.isGridSnapEnabled must be a boolean, not: ' + val);
|
64 | this._isGridSnapEnabled = val;
|
65 | }
|
66 | /**
|
67 | * Gets or sets whether a reshape handle's position should only be dragged where the
|
68 | * adjacent segments do not cross over any nodes.
|
69 | * This affects the behavior of {@link #computeReshape}.
|
70 | *
|
71 | * The default value is true.
|
72 | */
|
73 | get avoidsNodes() { return this._avoidsNodes; }
|
74 | set avoidsNodes(val) {
|
75 | if (typeof val !== 'boolean')
|
76 | throw new Error('new value for SnapLinkReshapingTool.avoidsNodes must be a boolean, not: ' + val);
|
77 | this._avoidsNodes = val;
|
78 | }
|
79 | /**
|
80 | * This override records information about the original point of the handle being dragged,
|
81 | * if the {@link #adornedLink} is Orthogonal and if {@link #avoidsNodes} is true.
|
82 | */
|
83 | doActivate() {
|
84 | super.doActivate();
|
85 | if (this.isActive && this.avoidsNodes && this.adornedLink !== null && this.adornedLink.isOrthogonal && this.handle !== null) {
|
86 | // assume the Link's route starts off correctly avoiding all nodes
|
87 | this._safePoint = this.diagram.lastInput.documentPoint.copy();
|
88 | const link = this.adornedLink;
|
89 | const idx = this.handle.segmentIndex;
|
90 | this._prevSegHoriz = Math.abs(link.getPoint(idx - 1).y - link.getPoint(idx).y) < 0.5;
|
91 | this._nextSegHoriz = Math.abs(link.getPoint(idx + 1).y - link.getPoint(idx).y) < 0.5;
|
92 | }
|
93 | }
|
94 | ;
|
95 | /**
|
96 | * Pretend while dragging a reshape handle the mouse point is at the nearest grid point, if {@link #isGridSnapEnabled} is true.
|
97 | * This uses {@link #gridCellSize} and {@link #gridOrigin}, unless those are not real values,
|
98 | * in which case this uses the {@link Diagram#grid}'s {@link Panel#gridCellSize} and {@link Panel#gridOrigin}.
|
99 | *
|
100 | * If {@link #avoidsNodes} is true and the adorned Link is {@link Link#isOrthogonal},
|
101 | * this method also avoids returning a Point that causes the adjacent segments, both before and after
|
102 | * the current handle's index, to cross over any Nodes that are {@link Node#avoidable}.
|
103 | */
|
104 | computeReshape(p) {
|
105 | let pt = p;
|
106 | const diagram = this.diagram;
|
107 | if (this.isGridSnapEnabled) {
|
108 | // first, find the grid to which we should snap
|
109 | let cell = this.gridCellSize;
|
110 | let orig = this.gridOrigin;
|
111 | if (!cell.isReal() || cell.width === 0 || cell.height === 0)
|
112 | cell = diagram.grid.gridCellSize;
|
113 | if (!orig.isReal())
|
114 | orig = diagram.grid.gridOrigin;
|
115 | // second, compute the closest grid point
|
116 | pt = p.copy().snapToGrid(orig.x, orig.y, cell.width, cell.height);
|
117 | }
|
118 | if (this.avoidsNodes && this.adornedLink !== null && this.adornedLink.isOrthogonal) {
|
119 | if (this._checkSegmentsOverlap(pt)) {
|
120 | this._safePoint = pt.copy();
|
121 | }
|
122 | else {
|
123 | pt = this._safePoint.copy();
|
124 | }
|
125 | }
|
126 | // then do whatever LinkReshapingTool would normally do as if the mouse were at that point
|
127 | return super.computeReshape(pt);
|
128 | }
|
129 | /**
|
130 | * @hidden @internal
|
131 | * Internal method for seeing whether a moved handle will cause any
|
132 | * adjacent orthogonal segments to cross over any avoidable nodes.
|
133 | * Returns true if everything would be OK.
|
134 | */
|
135 | _checkSegmentsOverlap(pt) {
|
136 | if (this.handle === null)
|
137 | return true;
|
138 | if (this.adornedLink === null)
|
139 | return true;
|
140 | const index = this.handle.segmentIndex;
|
141 | if (index >= 1) {
|
142 | const p1 = this.adornedLink.getPoint(index - 1);
|
143 | const r = new go.Rect(pt.x, pt.y, 0, 0);
|
144 | const q1 = p1.copy();
|
145 | if (this._prevSegHoriz) {
|
146 | q1.y = pt.y;
|
147 | }
|
148 | else {
|
149 | q1.x = pt.x;
|
150 | }
|
151 | r.unionPoint(q1);
|
152 | const overlaps = this.diagram.findPartsIn(r, true, false);
|
153 | if (overlaps.any(function (p) { return p instanceof go.Node && p.avoidable; }))
|
154 | return false;
|
155 | if (index >= 2) {
|
156 | const p0 = this.adornedLink.getPoint(index - 2);
|
157 | const r = new go.Rect(q1.x, q1.y, 0, 0);
|
158 | if (this._prevSegHoriz) {
|
159 | r.unionPoint(new go.Point(q1.x, p0.y));
|
160 | }
|
161 | else {
|
162 | r.unionPoint(new go.Point(p0.x, q1.y));
|
163 | }
|
164 | const overlaps = this.diagram.findPartsIn(r, true, false);
|
165 | if (overlaps.any(function (p) { return p instanceof go.Node && p.avoidable; }))
|
166 | return false;
|
167 | }
|
168 | }
|
169 | if (index < this.adornedLink.pointsCount - 1) {
|
170 | const p2 = this.adornedLink.getPoint(index + 1);
|
171 | const r = new go.Rect(pt.x, pt.y, 0, 0);
|
172 | const q2 = p2.copy();
|
173 | if (this._nextSegHoriz) {
|
174 | q2.y = pt.y;
|
175 | }
|
176 | else {
|
177 | q2.x = pt.x;
|
178 | }
|
179 | r.unionPoint(q2);
|
180 | const overlaps = this.diagram.findPartsIn(r, true, false);
|
181 | if (overlaps.any(function (p) { return p instanceof go.Node && p.avoidable; }))
|
182 | return false;
|
183 | if (index < this.adornedLink.pointsCount - 2) {
|
184 | const p3 = this.adornedLink.getPoint(index + 2);
|
185 | const r = new go.Rect(q2.x, q2.y, 0, 0);
|
186 | if (this._nextSegHoriz) {
|
187 | r.unionPoint(new go.Point(q2.x, p3.y));
|
188 | }
|
189 | else {
|
190 | r.unionPoint(new go.Point(p3.x, q2.y));
|
191 | }
|
192 | const overlaps = this.diagram.findPartsIn(r, true, false);
|
193 | if (overlaps.any(function (p) { return p instanceof go.Node && p.avoidable; }))
|
194 | return false;
|
195 | }
|
196 | }
|
197 | return true;
|
198 | }
|
199 | ;
|
200 | }
|