UNPKG

10.5 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 LinkShiftingTool class lets the user shift the end of a link to be anywhere along the edges of the port;
14 * use it in a diagram.toolManager.mouseDownTools list:
15 * ```js
16 * myDiagram.toolManager.mouseDownTools.add(new LinkShiftingTool());
17 * ```
18 *
19 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/LinkShifting.html">Link Shifting</a> sample.
20 * @category Tool Extension
21 */
22export class LinkShiftingTool extends go.Tool {
23 /**
24 * Constructs a LinkShiftingTool and sets the handles and name of the tool.
25 */
26 constructor() {
27 super();
28 // transient state
29 this._handle = null;
30 const h = new go.Shape();
31 h.geometryString = 'F1 M0 0 L8 0 M8 4 L0 4';
32 h.fill = null;
33 h.stroke = 'dodgerblue';
34 h.background = 'lightblue';
35 h.cursor = 'pointer';
36 h.segmentIndex = 0;
37 h.segmentFraction = 1;
38 h.segmentOrientation = go.Link.OrientAlong;
39 const g = new go.Shape();
40 g.geometryString = 'F1 M0 0 L8 0 M8 4 L0 4';
41 g.fill = null;
42 g.stroke = 'dodgerblue';
43 g.background = 'lightblue';
44 g.cursor = 'pointer';
45 g.segmentIndex = -1;
46 g.segmentFraction = 1;
47 g.segmentOrientation = go.Link.OrientAlong;
48 this._fromHandleArchetype = h;
49 this._toHandleArchetype = g;
50 this._originalPoints = null;
51 this.name = 'LinkShifting';
52 }
53 /**
54 * A small GraphObject used as a shifting handle.
55 */
56 get fromHandleArchetype() { return this._fromHandleArchetype; }
57 set fromHandleArchetype(value) { this._fromHandleArchetype = value; }
58 /**
59 * A small GraphObject used as a shifting handle.
60 */
61 get toHandleArchetype() { return this._toHandleArchetype; }
62 set toHandleArchetype(value) { this._toHandleArchetype = value; }
63 /**
64 * Show an {@link Adornment} with a reshape handle at each end of the link which allows for shifting of the end points.
65 */
66 updateAdornments(part) {
67 if (part === null || !(part instanceof go.Link))
68 return; // this tool only applies to Links
69 const link = part;
70 // show handles if link is selected, remove them if no longer selected
71 let category = 'LinkShiftingFrom';
72 let adornment = null;
73 if (link.isSelected && !this.diagram.isReadOnly && link.fromPort) {
74 const selelt = link.selectionObject;
75 if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
76 selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
77 const spot = link.computeSpot(true);
78 if (spot.isSide() || spot.isSpot()) {
79 adornment = link.findAdornment(category);
80 if (adornment === null) {
81 adornment = this.makeAdornment(selelt, false);
82 adornment.category = category;
83 link.addAdornment(category, adornment);
84 }
85 else {
86 // This is just to invalidate the measure, so it recomputes itself based on the adorned link
87 adornment.segmentFraction = Math.random();
88 }
89 }
90 }
91 }
92 if (adornment === null)
93 link.removeAdornment(category);
94 category = 'LinkShiftingTo';
95 adornment = null;
96 if (link.isSelected && !this.diagram.isReadOnly && link.toPort) {
97 const selelt = link.selectionObject;
98 if (selelt !== null && link.actualBounds.isReal() && link.isVisible() &&
99 selelt.actualBounds.isReal() && selelt.isVisibleObject()) {
100 const spot = link.computeSpot(false);
101 if (spot.isSide() || spot.isSpot()) {
102 adornment = link.findAdornment(category);
103 if (adornment === null) {
104 adornment = this.makeAdornment(selelt, true);
105 adornment.category = category;
106 link.addAdornment(category, adornment);
107 }
108 else {
109 // This is just to invalidate the measure, so it recomputes itself based on the adorned link
110 adornment.segmentFraction = Math.random();
111 }
112 }
113 }
114 }
115 if (adornment === null)
116 link.removeAdornment(category);
117 }
118 /**
119 * @hidden @internal
120 * @param {GraphObject} selelt the {@link GraphObject} of the {@link Link} being shifted.
121 * @param {boolean} toend
122 * @return {Adornment}
123 */
124 makeAdornment(selelt, toend) {
125 const adornment = new go.Adornment();
126 adornment.type = go.Panel.Link;
127 const h = (toend ? this.toHandleArchetype : this.fromHandleArchetype);
128 if (h !== null) {
129 // add a single handle for shifting at one end
130 adornment.add(h.copy());
131 }
132 adornment.adornedObject = selelt;
133 return adornment;
134 }
135 /**
136 * This tool may run when there is a mouse-down event on a reshaping handle.
137 */
138 canStart() {
139 if (!this.isEnabled)
140 return false;
141 const diagram = this.diagram;
142 if (diagram.isReadOnly || diagram.isModelReadOnly)
143 return false;
144 if (!diagram.lastInput.left)
145 return false;
146 let h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingFrom');
147 if (h === null)
148 h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingTo');
149 return (h !== null);
150 }
151 /**
152 * Start shifting, if {@link #findToolHandleAt} finds a reshaping handle at the mouse down point.
153 *
154 * If successful this sets the handle to be the reshape handle that it finds.
155 * It also remembers the original points in case this tool is cancelled.
156 * And it starts a transaction.
157 */
158 doActivate() {
159 const diagram = this.diagram;
160 let h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingFrom');
161 if (h === null)
162 h = this.findToolHandleAt(diagram.firstInput.documentPoint, 'LinkShiftingTo');
163 if (h === null)
164 return;
165 const ad = h.part;
166 if (ad === null || ad.adornedObject === null)
167 return;
168 const link = ad.adornedObject.part;
169 if (!(link instanceof go.Link))
170 return;
171 this._handle = h;
172 this._originalPoints = link.points.copy();
173 this.startTransaction(this.name);
174 diagram.isMouseCaptured = true;
175 diagram.currentCursor = 'pointer';
176 this.isActive = true;
177 }
178 /**
179 * This stops the current shifting operation with the link as it is.
180 */
181 doDeactivate() {
182 this.isActive = false;
183 const diagram = this.diagram;
184 diagram.isMouseCaptured = false;
185 diagram.currentCursor = '';
186 this.stopTransaction();
187 }
188 /**
189 * Perform cleanup of tool state.
190 */
191 doStop() {
192 this._handle = null;
193 this._originalPoints = null;
194 }
195 /**
196 * Restore the link route to be the original points and stop this tool.
197 */
198 doCancel() {
199 if (this._handle !== null) {
200 const ad = this._handle.part;
201 if (ad.adornedObject === null)
202 return;
203 const link = ad.adornedObject.part;
204 if (this._originalPoints !== null)
205 link.points = this._originalPoints;
206 }
207 this.stopTool();
208 }
209 /**
210 * Call {@link #doReshape} with a new point determined by the mouse
211 * to change the end point of the link.
212 */
213 doMouseMove() {
214 if (this.isActive) {
215 this.doReshape(this.diagram.lastInput.documentPoint);
216 }
217 }
218 /**
219 * Reshape the link's end with a point based on the most recent mouse point by calling {@link #doReshape},
220 * and then stop this tool.
221 */
222 doMouseUp() {
223 if (this.isActive) {
224 this.doReshape(this.diagram.lastInput.documentPoint);
225 this.transactionResult = this.name;
226 }
227 this.stopTool();
228 }
229 /**
230 * Find the closest point along the edge of the link's port and shift the end of the link to that point.
231 */
232 doReshape(pt) {
233 if (this._handle === null)
234 return;
235 const ad = this._handle.part;
236 if (ad.adornedObject === null)
237 return;
238 const link = ad.adornedObject.part;
239 const fromend = ad.category === 'LinkShiftingFrom';
240 let port = null;
241 if (fromend) {
242 port = link.fromPort;
243 }
244 else {
245 port = link.toPort;
246 }
247 if (port === null)
248 return;
249 // support rotated ports
250 const portang = port.getDocumentAngle();
251 const center = port.getDocumentPoint(go.Spot.Center);
252 const portb = new go.Rect(port.getDocumentPoint(go.Spot.TopLeft).subtract(center).rotate(-portang).add(center), port.getDocumentPoint(go.Spot.BottomRight).subtract(center).rotate(-portang).add(center));
253 let lp = link.getLinkPointFromPoint(port.part, port, center, pt, fromend);
254 lp = lp.copy().subtract(center).rotate(-portang).add(center);
255 const spot = new go.Spot(Math.max(0, Math.min(1, (lp.x - portb.x) / (portb.width || 1))), Math.max(0, Math.min(1, (lp.y - portb.y) / (portb.height || 1))));
256 if (fromend) {
257 link.fromSpot = spot;
258 }
259 else {
260 link.toSpot = spot;
261 }
262 }
263}