1 | /*
|
2 | * Copyright (C) 1998-2021 by Northwoods Software Corporation. All Rights Reserved.
|
3 | */
|
4 |
|
5 | import * as go from '../release/go-module.js';
|
6 |
|
7 | // A custom Tool to change the scale of an object in a Part.
|
8 |
|
9 | /*
|
10 | * This is an extension and not part of the main GoJS library.
|
11 | * Note that the API for this class may change with any version, even point releases.
|
12 | * If you intend to use an extension in production, you should copy the code to your own source directory.
|
13 | * Extensions can be found in the GoJS kit under the extensions or extensionsTS folders.
|
14 | * See the Extensions intro page (https://gojs.net/latest/intro/extensions.html) for more information.
|
15 | */
|
16 |
|
17 | /**
|
18 | * A custom tool for rescaling an object.
|
19 | *
|
20 | * Install the RescalingTool as a mouse-down tool by calling:
|
21 | * myDiagram.toolManager.mouseDownTools.add(new RescalingTool());
|
22 | *
|
23 | * Normally it would not make sense for the same object to be both resizable and rescalable.
|
24 | *
|
25 | * Note that there is no <code>Part.rescaleObjectName</code> property and there is no <code>Part.rescalable</code> property.
|
26 | * So although you cannot customize any Node to affect this tool, you can set
|
27 | * <a>RescalingTool.rescaleObjectName</a> and set <a>RescalingTool.isEnabled</a> to control
|
28 | * whether objects are rescalable and when.
|
29 | *
|
30 | * If you want to experiment with this extension, try the <a href="../../extensionsJSM/Rescaling.html">Rescaling</a> sample.
|
31 | * @category Tool Extension
|
32 | */
|
33 | export class RescalingTool extends go.Tool {
|
34 | private _rescaleObjectName: string = "";
|
35 | private _handleArchetype: go.GraphObject;
|
36 |
|
37 | // internal state
|
38 | private _adornedObject: go.GraphObject | null = null;
|
39 |
|
40 | private _handle: go.GraphObject | null = null;
|
41 |
|
42 | private originalPoint = new go.Point();
|
43 | private originalTopLeft = new go.Point();
|
44 | private originalScale = 1.0;
|
45 |
|
46 | constructor() {
|
47 | super();
|
48 | this.name = "Rescaling";
|
49 |
|
50 | var h = new go.Shape();
|
51 | h.desiredSize = new go.Size(8, 8);
|
52 | h.fill = "lightblue";
|
53 | h.stroke = "dodgerblue";
|
54 | h.strokeWidth = 1;
|
55 | h.cursor = "nwse-resize";
|
56 | this._handleArchetype = h;
|
57 | }
|
58 | /**
|
59 | * Gets the {@link GraphObject} that is being rescaled.
|
60 | * This may be the same object as the selected {@link Part} or it may be contained within that Part.
|
61 | *
|
62 | * This property is also settable, but should only be set when overriding functions
|
63 | * in RescalingTool, and not during normal operation.
|
64 | */
|
65 | get adornedObject(): go.GraphObject | null { return this._adornedObject; }
|
66 | set adornedObject(val: go.GraphObject | null) { this._adornedObject = val; }
|
67 |
|
68 | /**
|
69 | * Gets or sets a small GraphObject that is copied as a rescale handle for the selected part.
|
70 | * By default this is a {@link Shape} that is a small blue square.
|
71 | * Setting this property does not raise any events.
|
72 | *
|
73 | * Here is an example of changing the default handle to be green "X":
|
74 | * ```js
|
75 | * tool.handleArchetype =
|
76 | * $(go.Shape, "XLine",
|
77 | * { width: 8, height: 8, stroke: "green", fill: "transparent" });
|
78 | * ```
|
79 | */
|
80 | get handleArchetype(): go.GraphObject { return this._handleArchetype; }
|
81 | set handleArchetype(val: go.GraphObject ) { this._handleArchetype = val; }
|
82 |
|
83 | /**
|
84 | * This property returns the {@link GraphObject} that is the tool handle being dragged by the user.
|
85 | * This will be contained by an {@link Adornment} whose category is "RescalingTool".
|
86 | * Its {@link Adornment#adornedObject} is the same as the {@link #adornedObject}.
|
87 | *
|
88 | * This property is also settable, but should only be set either within an override of {@link #doActivate}
|
89 | * or prior to calling {@link #doActivate}.
|
90 | */
|
91 | get handle(): go.GraphObject | null { return this._handle; }
|
92 | set handle(val: go.GraphObject | null) { this._handle = val; }
|
93 |
|
94 | /**
|
95 | * This property returns the name of the GraphObject that identifies the object to be rescaled by this tool.
|
96 | *
|
97 | * The default value is the empty string, resulting in the whole Node being rescaled.
|
98 | * This property is used by findRescaleObject when calling {@link Panel#findObject}.
|
99 | */
|
100 | get rescaleObjectName(): string { return this._rescaleObjectName; }
|
101 | set rescaleObjectName(val: string) { this._rescaleObjectName = val; }
|
102 |
|
103 | /**
|
104 | * @this {RescalingTool}
|
105 | * @param {Part} part
|
106 | */
|
107 | updateAdornments(part: go.Part | null) {
|
108 | if (part === null || part instanceof go.Link) return;
|
109 | if (part.isSelected && !this.diagram.isReadOnly) {
|
110 | var rescaleObj = this.findRescaleObject(part);
|
111 | if (rescaleObj !== null && part.actualBounds.isReal() && part.isVisible() &&
|
112 | rescaleObj.actualBounds.isReal() && rescaleObj.isVisibleObject()) {
|
113 | var adornment = part.findAdornment(this.name);
|
114 | if (adornment === null || adornment.adornedObject !== rescaleObj) {
|
115 | adornment = this.makeAdornment(rescaleObj);
|
116 | }
|
117 | if (adornment !== null) {
|
118 | adornment.location = rescaleObj.getDocumentPoint(go.Spot.BottomRight);
|
119 | part.addAdornment(this.name, adornment);
|
120 | return;
|
121 | }
|
122 | }
|
123 | }
|
124 | part.removeAdornment(this.name);
|
125 | }
|
126 |
|
127 | /**
|
128 | * @this {RescalingTool}
|
129 | * @param {GraphObject} rescaleObj
|
130 | * @return {Adornment}
|
131 | */
|
132 | makeAdornment(rescaleObj: go.GraphObject | null): go.Adornment {
|
133 | var adornment = new go.Adornment();
|
134 | adornment.type = go.Panel.Position;
|
135 | adornment.locationSpot = go.Spot.Center;
|
136 | adornment.add(this._handleArchetype.copy());
|
137 | adornment.adornedObject = rescaleObj;
|
138 | return adornment;
|
139 | }
|
140 |
|
141 | /**
|
142 | * Return the GraphObject to be rescaled by the user.
|
143 | * @this {RescalingTool}
|
144 | * @return {GraphObject}
|
145 | */
|
146 | findRescaleObject(part: go.Part): go.GraphObject {
|
147 | var obj = part.findObject(this.rescaleObjectName);
|
148 | if (obj) return obj;
|
149 | return part;
|
150 | }
|
151 |
|
152 | /**
|
153 | * This tool can start running if the mouse-down happens on a "Rescaling" handle.
|
154 | * @this {RescalingTool}
|
155 | * @return {boolean}
|
156 | */
|
157 | canStart(): boolean {
|
158 | var diagram = this.diagram;
|
159 | if (diagram === null || diagram.isReadOnly) return false;
|
160 | if (!diagram.lastInput.left) return false;
|
161 | var h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
162 | return (h !== null);
|
163 | }
|
164 |
|
165 | /**
|
166 | * Activating this tool remembers the {@link #handle} that was dragged,
|
167 | * the {@link #adornedObject} that is being rescaled,
|
168 | * starts a transaction, and captures the mouse.
|
169 | * @this {RescalingTool}
|
170 | */
|
171 | doActivate(): void {
|
172 | var diagram = this.diagram;
|
173 | if (diagram === null) return;
|
174 | this._handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
175 | if (this._handle === null) return;
|
176 | var ad = this._handle.part;
|
177 | this._adornedObject = (ad instanceof go.Adornment) ? ad.adornedObject : null;
|
178 | if (!this._adornedObject) return;
|
179 | this.originalPoint = this._handle.getDocumentPoint(go.Spot.Center);
|
180 | this.originalTopLeft = this._adornedObject.getDocumentPoint(go.Spot.TopLeft);
|
181 | this.originalScale = this._adornedObject.scale;
|
182 | diagram.isMouseCaptured = true;
|
183 | diagram.delaysLayout = true;
|
184 | this.startTransaction(this.name);
|
185 | this.isActive = true;
|
186 | }
|
187 |
|
188 | /**
|
189 | * Stop the current transaction, forget the {@link #handle} and {@link #adornedObject}, and release the mouse.
|
190 | * @this {RescalingTool}
|
191 | */
|
192 | doDeactivate(): void {
|
193 | var diagram = this.diagram;
|
194 | if (diagram === null) return;
|
195 | this.stopTransaction();
|
196 | this._handle = null;
|
197 | this._adornedObject = null;
|
198 | diagram.isMouseCaptured = false;
|
199 | this.isActive = false;
|
200 | };
|
201 |
|
202 | /**
|
203 | * Restore the original {@link GraphObject#scale} of the adorned object.
|
204 | * @this {RescalingTool}
|
205 | */
|
206 | doCancel(): void {
|
207 | var diagram = this.diagram;
|
208 | if (diagram !== null) diagram.delaysLayout = false;
|
209 | this.scale(this.originalScale);
|
210 | this.stopTool();
|
211 | }
|
212 |
|
213 | /**
|
214 | * Call {@link #scale} with a new scale determined by the current mouse point.
|
215 | * This determines the new scale by calling {@link #computeScale}.
|
216 | * @this {RescalingTool}
|
217 | */
|
218 | doMouseMove(): void {
|
219 | var diagram = this.diagram;
|
220 | if (this.isActive && diagram !== null) {
|
221 | var newScale = this.computeScale(diagram.lastInput.documentPoint);
|
222 | this.scale(newScale);
|
223 | }
|
224 | }
|
225 |
|
226 | /**
|
227 | * Call {@link #scale} with a new scale determined by the most recent mouse point,
|
228 | * and commit the transaction.
|
229 | * @this {RescalingTool}
|
230 | */
|
231 | doMouseUp(): void {
|
232 | var diagram = this.diagram;
|
233 | if (this.isActive && diagram !== null) {
|
234 | diagram.delaysLayout = false;
|
235 | var newScale = this.computeScale(diagram.lastInput.documentPoint);
|
236 | this.scale(newScale);
|
237 | this.transactionResult = this.name;
|
238 | }
|
239 | this.stopTool();
|
240 | }
|
241 |
|
242 | /**
|
243 | * Set the {@link GraphObject#scale} of the {@link #findRescaleObject}.
|
244 | * @this {RescalingTool}
|
245 | * @param {number} newScale
|
246 | */
|
247 | scale(newScale: number): void {
|
248 | if (this._adornedObject !== null) {
|
249 | this._adornedObject.scale = newScale;
|
250 | }
|
251 | }
|
252 |
|
253 | /**
|
254 | * Compute the new scale given a point.
|
255 | *
|
256 | * This method is called by both {@link #doMouseMove} and {@link #doMouseUp}.
|
257 | * This method may be overridden.
|
258 | * Please read the Introduction page on <a href="../../intro/extensions.html">Extensions</a> for how to override methods and how to call this base method.
|
259 | * @this {RescalingTool}
|
260 | * @param {Point} newPoint in document coordinates
|
261 | */
|
262 | computeScale(newPoint: go.Point): number {
|
263 | var scale = this.originalScale;
|
264 | var origdist = Math.sqrt(this.originalPoint.distanceSquaredPoint(this.originalTopLeft));
|
265 | var newdist = Math.sqrt(newPoint.distanceSquaredPoint(this.originalTopLeft));
|
266 | return scale * (newdist/origdist);
|
267 | }
|
268 | }
|