UNPKG

9.07 kBJavaScriptView Raw
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*/
11import * 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 */
20export 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}