UNPKG

9.21 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 DragZoomingTool lets the user zoom into a diagram by stretching a box
14 * to indicate the new contents of the diagram's viewport (the area of the
15 * model shown by the Diagram).
16 * Hold down the Shift key in order to zoom out.
17 *
18 * The default drag selection box is a magenta rectangle.
19 * You can modify the {@link #box} to customize its appearance.
20 *
21 * The diagram that is zoomed by this tool is specified by the {@link #zoomedDiagram} property.
22 * If the value is null, the tool zooms its own {@link Tool#diagram}.
23 *
24 * You can use this tool in a modal manner by executing:
25 * ```js
26 * diagram.currentTool = new DragZoomingTool();
27 * ```
28 *
29 * Use this tool in a mode-less manner by executing:
30 * ```js
31 * myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragZoomingTool());
32 * ```
33 *
34 * However when used mode-lessly as a mouse-move tool, in {@link ToolManager#mouseMoveTools},
35 * this cannot start running unless there has been a motionless delay
36 * after the mouse-down event of at least {@link #delay} milliseconds.
37 *
38 * This tool does not utilize any {@link Adornment}s or tool handles,
39 * but it does temporarily add the {@link #box} part to the diagram.
40 * This tool does not modify the model or conduct any transaction.
41 *
42 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/DragZooming.html">Drag Zooming</a> sample.
43 * @category Tool Extension
44 */
45export class DragZoomingTool extends go.Tool {
46 /**
47 * Constructs a DragZoomingTool, sets {@link #box} to a magenta rectangle, and sets name of the tool.
48 */
49 constructor() {
50 super();
51 this._delay = 175;
52 this._zoomedDiagram = null;
53 const b = new go.Part();
54 const r = new go.Shape();
55 b.layerName = 'Tool';
56 b.selectable = false;
57 r.name = 'SHAPE';
58 r.figure = 'Rectangle';
59 r.fill = null;
60 r.stroke = 'magenta';
61 r.position = new go.Point(0, 0);
62 b.add(r);
63 this._box = b;
64 this.name = 'DragZooming';
65 }
66 /**
67 * Gets or sets the {@link Part} used as the "rubber-band zoom box"
68 * that is stretched to follow the mouse, as feedback for what area will
69 * be passed to {@link #zoomToRect} upon a mouse-up.
70 *
71 * Initially this is a {@link Part} containing only a simple magenta rectangular {@link Shape}.
72 * The object to be resized should be named "SHAPE".
73 * Setting this property does not raise any events.
74 *
75 * Modifying this property while this tool {@link Tool#isActive} might have no effect.
76 */
77 get box() { return this._box; }
78 set box(val) { this._box = val; }
79 /**
80 * Gets or sets the time in milliseconds for which the mouse must be stationary
81 * before this tool can be started.
82 *
83 * The default value is 175 milliseconds.
84 * Setting this property does not raise any events.
85 */
86 get delay() { return this._delay; }
87 set delay(val) { this._delay = val; }
88 /**
89 * Gets or sets the {@link Diagram} whose {@link Diagram#position} and {@link Diagram#scale}
90 * should be set to display the drawn {@link #box} rectangular bounds.
91 *
92 * The default value is null, which causes {@link #zoomToRect} to modify this tool's {@link Tool#diagram}.
93 * Setting this property does not raise any events.
94 */
95 get zoomedDiagram() { return this._zoomedDiagram; }
96 set zoomedDiagram(val) { this._zoomedDiagram = val; }
97 /**
98 * This tool can run when there has been a mouse-drag, far enough away not to be a click,
99 * and there has been delay of at least {@link #delay} milliseconds
100 * after the mouse-down before a mouse-move.
101 */
102 canStart() {
103 if (!this.isEnabled)
104 return false;
105 const diagram = this.diagram;
106 const e = diagram.lastInput;
107 // require left button & that it has moved far enough away from the mouse down point, so it isn't a click
108 if (!e.left)
109 return false;
110 // don't include the following checks when this tool is running modally
111 if (diagram.currentTool !== this) {
112 if (!this.isBeyondDragSize())
113 return false;
114 // must wait for "delay" milliseconds before that tool can run
115 if (e.timestamp - diagram.firstInput.timestamp < this.delay)
116 return false;
117 }
118 return true;
119 }
120 /**
121 * Capture the mouse and show the {@link #box}.
122 */
123 doActivate() {
124 const diagram = this.diagram;
125 this.isActive = true;
126 diagram.isMouseCaptured = true;
127 diagram.skipsUndoManager = true;
128 diagram.add(this.box);
129 this.doMouseMove();
130 }
131 /**
132 * Release the mouse and remove any {@link #box}.
133 */
134 doDeactivate() {
135 const diagram = this.diagram;
136 diagram.remove(this.box);
137 diagram.skipsUndoManager = false;
138 diagram.isMouseCaptured = false;
139 this.isActive = false;
140 }
141 /**
142 * Update the {@link #box}'s position and size according to the value
143 * of {@link #computeBoxBounds}.
144 */
145 doMouseMove() {
146 const diagram = this.diagram;
147 if (this.isActive && this.box !== null) {
148 const r = this.computeBoxBounds();
149 let shape = this.box.findObject('SHAPE');
150 if (shape === null)
151 shape = this.box.findMainElement();
152 if (shape !== null)
153 shape.desiredSize = r.size;
154 this.box.position = r.position;
155 }
156 }
157 /**
158 * Call {@link #zoomToRect} with the value of a call to {@link #computeBoxBounds}.
159 */
160 doMouseUp() {
161 if (this.isActive) {
162 const diagram = this.diagram;
163 diagram.remove(this.box);
164 try {
165 diagram.currentCursor = 'wait';
166 this.zoomToRect(this.computeBoxBounds());
167 }
168 finally {
169 diagram.currentCursor = '';
170 }
171 }
172 this.stopTool();
173 }
174 /**
175 * This just returns a {@link Rect} stretching from the mouse-down point to the current mouse point
176 * while maintaining the aspect ratio of the {@link #zoomedDiagram}.
177 * @return {Rect} a {@link Rect} in document coordinates.
178 */
179 computeBoxBounds() {
180 const diagram = this.diagram;
181 const start = diagram.firstInput.documentPoint;
182 const latest = diagram.lastInput.documentPoint;
183 const adx = latest.x - start.x;
184 const ady = latest.y - start.y;
185 let observed = this.zoomedDiagram;
186 if (observed === null)
187 observed = diagram;
188 if (observed === null) {
189 return new go.Rect(start, latest);
190 }
191 const vrect = observed.viewportBounds;
192 if (vrect.height === 0 || ady === 0) {
193 return new go.Rect(start, latest);
194 }
195 const vratio = vrect.width / vrect.height;
196 let lx;
197 let ly;
198 if (Math.abs(adx / ady) < vratio) {
199 lx = start.x + adx;
200 ly = start.y + Math.ceil(Math.abs(adx) / vratio) * (ady < 0 ? -1 : 1);
201 }
202 else {
203 lx = start.x + Math.ceil(Math.abs(ady) * vratio) * (adx < 0 ? -1 : 1);
204 ly = start.y + ady;
205 }
206 return new go.Rect(start, new go.Point(lx, ly));
207 }
208 /**
209 * This method is called to change the {@link #zoomedDiagram}'s viewport to match the given rectangle.
210 * @param {Rect} r a rectangular bounds in document coordinates.
211 */
212 zoomToRect(r) {
213 if (r.width < 0.1)
214 return;
215 const diagram = this.diagram;
216 let observed = this.zoomedDiagram;
217 if (observed === null)
218 observed = diagram;
219 if (observed === null)
220 return;
221 // zoom out when using the Shift modifier
222 if (diagram.lastInput.shift) {
223 observed.scale = Math.max(observed.scale * r.width / observed.viewportBounds.width, observed.minScale);
224 observed.centerRect(r);
225 }
226 else {
227 // do scale first, so the Diagram's position normalization isn't constrained unduly when increasing scale
228 observed.scale = Math.min(observed.viewportBounds.width * observed.scale / r.width, observed.maxScale);
229 observed.position = new go.Point(r.x, r.y);
230 }
231 }
232}