UNPKG

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