UNPKG

9.17 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 DragCreatingTool lets the user create a new node by dragging in the background
14 * to indicate its size and position.
15 *
16 * The default drag selection box is a magenta rectangle.
17 * You can modify the {@link #box} to customize its appearance.
18 *
19 * This tool will not be able to start running unless you have set the
20 * {@link #archetypeNodeData} property to an object that can be copied and added to the diagram's model.
21 *
22 * You can use this tool in a modal manner by executing:
23 * ```js
24 * diagram.currentTool = new DragCreatingTool();
25 * ```
26 *
27 * Use this tool in a mode-less manner by executing:
28 * ```js
29 * myDiagram.toolManager.mouseMoveTools.insertAt(2, new DragCreatingTool());
30 * ```
31 *
32 * However when used mode-lessly as a mouse-move tool, in {@link ToolManager#mouseMoveTools},
33 * this cannot start running unless there has been a motionless delay
34 * after the mouse-down event of at least {@link #delay} milliseconds.
35 *
36 * This tool does not utilize any {@link Adornment}s or tool handles,
37 * but it does temporarily add the {@link #box} Part to the diagram.
38 * This tool does conduct a transaction when inserting the new node.
39 *
40 * If you want to experiment with this extension, try the <a href="../../extensionsJSM/DragCreating.html">Drag Creating</a> sample.
41 * @category Tool Extension
42 */
43export class DragCreatingTool extends go.Tool {
44 /**
45 * Constructs a DragCreatingTool, sets {@link #box} to a magenta rectangle, and sets name of the tool.
46 */
47 constructor() {
48 super();
49 this._archetypeNodeData = null;
50 this._delay = 175;
51 const b = new go.Part();
52 const r = new go.Shape();
53 b.layerName = 'Tool';
54 b.selectable = false;
55 r.name = 'SHAPE';
56 r.figure = 'Rectangle';
57 r.fill = null;
58 r.stroke = 'magenta';
59 r.position = new go.Point(0, 0);
60 b.add(r);
61 this._box = b;
62 this.name = 'DragCreating';
63 }
64 /**
65 * Gets or sets the {@link Part} used as the "rubber-band box"
66 * that is stretched to follow the mouse, as feedback for what area will
67 * be passed to {@link #insertPart} upon a mouse-up.
68 *
69 * Initially this is a {@link Part} containing only a simple magenta rectangular {@link Shape}.
70 * The object to be resized should be named "SHAPE".
71 * Setting this property does not raise any events.
72 *
73 * Modifying this property while this tool {@link Tool#isActive} might have no effect.
74 */
75 get box() { return this._box; }
76 set box(val) { this._box = val; }
77 /**
78 * Gets or sets the time in milliseconds for which the mouse must be stationary
79 * before this tool can be started.
80 *
81 * The default value is 175 milliseconds.
82 * A value of zero will allow this tool to run without any wait after the mouse down.
83 * Setting this property does not raise any events.
84 */
85 get delay() { return this._delay; }
86 set delay(val) { this._delay = val; }
87 /**
88 * Gets or sets a data object that will be copied and added to the diagram's model each time this tool executes.
89 *
90 * The default value is null.
91 * The value must be non-null for this tool to be able to run.
92 * Setting this property does not raise any events.
93 */
94 get archetypeNodeData() { return this._archetypeNodeData; }
95 set archetypeNodeData(val) { this._archetypeNodeData = val; }
96 /**
97 * This tool can run when there has been a mouse-drag, far enough away not to be a click,
98 * and there has been delay of at least {@link #delay} milliseconds
99 * after the mouse-down before a mouse-move.
100 */
101 canStart() {
102 if (!this.isEnabled)
103 return false;
104 // gotta have some node data that can be copied
105 if (this.archetypeNodeData === null)
106 return false;
107 const diagram = this.diagram;
108 // heed IsReadOnly & AllowInsert
109 if (diagram.isReadOnly || diagram.isModelReadOnly)
110 return false;
111 if (!diagram.allowInsert)
112 return false;
113 const e = diagram.lastInput;
114 // require left button & that it has moved far enough away from the mouse down point, so it isn't a click
115 if (!e.left)
116 return false;
117 // don't include the following checks when this tool is running modally
118 if (diagram.currentTool !== this) {
119 if (!this.isBeyondDragSize())
120 return false;
121 // must wait for "delay" milliseconds before that tool can run
122 if (e.timestamp - diagram.firstInput.timestamp < this.delay)
123 return false;
124 }
125 return true;
126 }
127 /**
128 * Capture the mouse and show the {@link #box}.
129 */
130 doActivate() {
131 const diagram = this.diagram;
132 this.isActive = true;
133 diagram.isMouseCaptured = true;
134 diagram.add(this.box);
135 this.doMouseMove();
136 }
137 /**
138 * Release the mouse and remove any {@link #box}.
139 */
140 doDeactivate() {
141 const diagram = this.diagram;
142 diagram.remove(this.box);
143 diagram.isMouseCaptured = false;
144 this.isActive = false;
145 }
146 /**
147 * Update the {@link #box}'s position and size according to the value
148 * of {@link #computeBoxBounds}.
149 */
150 doMouseMove() {
151 if (this.isActive && this.box !== null) {
152 const r = this.computeBoxBounds();
153 let shape = this.box.findObject('SHAPE');
154 if (shape === null)
155 shape = this.box.findMainElement();
156 if (shape !== null)
157 shape.desiredSize = r.size;
158 this.box.position = r.position;
159 }
160 }
161 /**
162 * Call {@link #insertPart} with the value of a call to {@link #computeBoxBounds}.
163 */
164 doMouseUp() {
165 if (this.isActive) {
166 const diagram = this.diagram;
167 diagram.remove(this.box);
168 try {
169 diagram.currentCursor = 'wait';
170 this.insertPart(this.computeBoxBounds());
171 }
172 finally {
173 diagram.currentCursor = '';
174 }
175 }
176 this.stopTool();
177 }
178 /**
179 * This just returns a {@link Rect} stretching from the mouse-down point to the current mouse point.
180 * @return {Rect} a {@link Rect} in document coordinates.
181 */
182 computeBoxBounds() {
183 const diagram = this.diagram;
184 const start = diagram.firstInput.documentPoint;
185 const latest = diagram.lastInput.documentPoint;
186 return new go.Rect(start, latest);
187 }
188 /**
189 * Create a node by adding a copy of the {@link #archetypeNodeData} object
190 * to the diagram's model, assign its {@link GraphObject#position} and {@link GraphObject#desiredSize}
191 * according to the given bounds, and select the new part.
192 *
193 * The actual part that is added to the diagram may be a {@link Part}, a {@link Node},
194 * or even a {@link Group}, depending on the properties of the {@link #archetypeNodeData}
195 * and the type of the template that is copied to create the part.
196 * @param {Rect} bounds a Point in document coordinates.
197 * @return {Part} the newly created Part, or null if it failed.
198 */
199 insertPart(bounds) {
200 const diagram = this.diagram;
201 const arch = this.archetypeNodeData;
202 if (arch === null)
203 return null;
204 diagram.raiseDiagramEvent('ChangingSelection', diagram.selection);
205 this.startTransaction(this.name);
206 let part = null;
207 if (arch !== null) {
208 const data = diagram.model.copyNodeData(arch);
209 if (data) {
210 diagram.model.addNodeData(data);
211 part = diagram.findPartForData(data);
212 }
213 }
214 if (part !== null) {
215 part.position = bounds.position;
216 part.resizeObject.desiredSize = bounds.size;
217 if (diagram.allowSelect) {
218 diagram.clearSelection();
219 part.isSelected = true;
220 }
221 }
222 // set the TransactionResult before raising event, in case it changes the result or cancels the tool
223 this.transactionResult = this.name;
224 this.stopTransaction();
225 diagram.raiseDiagramEvent('ChangedSelection', diagram.selection);
226 return part;
227 }
228}