@itwin/core-markup 4.0.0-dev.8 → 4.0.0-dev.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +41 -1
  2. package/lib/cjs/Markup.d.ts +323 -310
  3. package/lib/cjs/Markup.d.ts.map +1 -1
  4. package/lib/cjs/Markup.js +451 -420
  5. package/lib/cjs/Markup.js.map +1 -1
  6. package/lib/cjs/MarkupTool.d.ts +38 -38
  7. package/lib/cjs/MarkupTool.js +88 -88
  8. package/lib/cjs/MarkupTool.js.map +1 -1
  9. package/lib/cjs/RedlineTool.d.ts +145 -145
  10. package/lib/cjs/RedlineTool.d.ts.map +1 -1
  11. package/lib/cjs/RedlineTool.js +498 -512
  12. package/lib/cjs/RedlineTool.js.map +1 -1
  13. package/lib/cjs/SelectTool.d.ts +126 -126
  14. package/lib/cjs/SelectTool.js +741 -741
  15. package/lib/cjs/SelectTool.js.map +1 -1
  16. package/lib/cjs/SvgJsExt.d.ts +85 -85
  17. package/lib/cjs/SvgJsExt.js +185 -185
  18. package/lib/cjs/TextEdit.d.ts +43 -43
  19. package/lib/cjs/TextEdit.js +196 -196
  20. package/lib/cjs/TextEdit.js.map +1 -1
  21. package/lib/cjs/Undo.d.ts +46 -46
  22. package/lib/cjs/Undo.js +168 -168
  23. package/lib/cjs/core-markup.d.ts +18 -18
  24. package/lib/cjs/core-markup.js +38 -34
  25. package/lib/cjs/core-markup.js.map +1 -1
  26. package/lib/esm/Markup.d.ts +323 -310
  27. package/lib/esm/Markup.d.ts.map +1 -1
  28. package/lib/esm/Markup.js +447 -415
  29. package/lib/esm/Markup.js.map +1 -1
  30. package/lib/esm/MarkupTool.d.ts +38 -38
  31. package/lib/esm/MarkupTool.js +85 -84
  32. package/lib/esm/MarkupTool.js.map +1 -1
  33. package/lib/esm/RedlineTool.d.ts +145 -145
  34. package/lib/esm/RedlineTool.d.ts.map +1 -1
  35. package/lib/esm/RedlineTool.js +494 -498
  36. package/lib/esm/RedlineTool.js.map +1 -1
  37. package/lib/esm/SelectTool.d.ts +126 -126
  38. package/lib/esm/SelectTool.js +735 -734
  39. package/lib/esm/SelectTool.js.map +1 -1
  40. package/lib/esm/SvgJsExt.d.ts +85 -85
  41. package/lib/esm/SvgJsExt.js +180 -180
  42. package/lib/esm/TextEdit.d.ts +43 -43
  43. package/lib/esm/TextEdit.js +193 -191
  44. package/lib/esm/TextEdit.js.map +1 -1
  45. package/lib/esm/Undo.d.ts +46 -46
  46. package/lib/esm/Undo.js +164 -164
  47. package/lib/esm/core-markup.d.ts +18 -18
  48. package/lib/esm/core-markup.js +22 -22
  49. package/package.json +19 -19
@@ -1,742 +1,742 @@
1
- "use strict";
2
- /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
- * See LICENSE.md in the project root for license terms and full copyright notice.
5
- *--------------------------------------------------------------------------------------------*/
6
- /** @packageDocumentation
7
- * @module MarkupTools
8
- */
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.SelectTool = exports.MarkupSelected = exports.Handles = exports.ModifyHandle = void 0;
11
- const core_bentley_1 = require("@itwin/core-bentley");
12
- const core_geometry_1 = require("@itwin/core-geometry");
13
- const core_frontend_1 = require("@itwin/core-frontend");
14
- const svg_js_1 = require("@svgdotjs/svg.js");
15
- const Markup_1 = require("./Markup");
16
- const MarkupTool_1 = require("./MarkupTool");
17
- const TextEdit_1 = require("./TextEdit");
18
- // cspell:ignore lmultiply untransform unFlash multiselect
19
- /** Classes added to HTMLElements so they can be customized in CSS by applications.
20
- * A "modify handle" is a visible position on the screen that provides UI to modify a MarkupElement.
21
- * @public
22
- */
23
- class ModifyHandle {
24
- constructor(handles) {
25
- this.handles = handles;
26
- }
27
- async onClick(_ev) { }
28
- /** the mouse just went down on this handle, begin modification. */
29
- startDrag(_ev, makeCopy = false) {
30
- this.vbToStartTrn = this.handles.vbToBoxTrn.clone(); // save the starting vp -> element box transform
31
- this.startModify(makeCopy);
32
- }
33
- startModify(makeCopy) {
34
- const handles = this.handles;
35
- const el = handles.el;
36
- const cloned = handles.el = el.cloneMarkup(); // make a clone of this element
37
- if (makeCopy) {
38
- el.after(cloned);
39
- }
40
- else {
41
- cloned.originalEl = el; // save original for undo
42
- el.replace(cloned); // put it into the DOM in place of the original
43
- }
44
- }
45
- setMouseHandler(target) {
46
- const node = target.node;
47
- node.addEventListener("mousedown", (event) => {
48
- const ev = event;
49
- if (0 === ev.button && undefined === this.handles.active)
50
- this.handles.active = this;
51
- });
52
- node.addEventListener("touchstart", () => {
53
- if (undefined === this.handles.active)
54
- this.handles.active = this;
55
- });
56
- }
57
- addTouchPadding(visible, handles) {
58
- if (core_frontend_1.InputSource.Touch !== core_frontend_1.IModelApp.toolAdmin.currentInputState.inputSource)
59
- return visible;
60
- const padding = visible.cloneMarkup().scale(3).attr("opacity", 0);
61
- const g = handles.group.group();
62
- padding.addTo(g);
63
- visible.addTo(g);
64
- return g;
65
- }
66
- }
67
- exports.ModifyHandle = ModifyHandle;
68
- /** A ModifyHandle that changes the size of the element
69
- * @public
70
- */
71
- class StretchHandle extends ModifyHandle {
72
- constructor(handles, xy, cursor) {
73
- super(handles);
74
- this.posNpc = new core_geometry_1.Point2d(xy[0], xy[1]);
75
- const props = Markup_1.MarkupApp.props.handles;
76
- this._circle = handles.group.circle(props.size).addClass(Markup_1.MarkupApp.stretchHandleClass).attr(props.stretch).attr("cursor", `${cursor}-resize`); // the visible "circle" for this handle
77
- this._circle = this.addTouchPadding(this._circle, handles);
78
- this.setMouseHandler(this._circle);
79
- }
80
- setPosition() {
81
- const pt = this.handles.npcToVb(this.posNpc); // convert to viewbox coords
82
- this._circle.center(pt.x, pt.y);
83
- }
84
- startDrag(_ev) {
85
- const handles = this.handles;
86
- this.startCtm = handles.el.screenCTM().lmultiplyO(Markup_1.MarkupApp.screenToVbMtx());
87
- this.startBox = handles.el.bbox(); // save starting size so we can preserve aspect ratio
88
- this.startPos = handles.npcToBox(this.posNpc);
89
- this.opposite = handles.npcToBox({ x: 1 - this.posNpc.x, y: 1 - this.posNpc.y });
90
- super.startDrag(_ev);
91
- }
92
- /** perform the stretch. Always stretch element with anchor at the opposite corner of the one being moved. */
93
- modify(ev) {
94
- const evPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // get cursor location in viewbox coords
95
- const diff = this.startPos.vectorTo(this.vbToStartTrn.multiplyPoint2d(evPt)); // movement of cursor from start, in viewbox coords
96
- const diag = this.startPos.vectorTo(this.opposite).normalize(); // vector from opposite corner to this handle
97
- let diagVec = diag.scaleToLength(diff.dotProduct(diag)); // projected distance along diagonal
98
- if (diagVec === undefined)
99
- diagVec = core_geometry_1.Vector2d.createZero();
100
- // if the shift key is down, don't preserve aspect ratio
101
- const adjusted = ev.isShiftKey ? { x: diff.x, y: diff.y } : { x: diagVec.x, y: diagVec.y };
102
- let { x, y, h, w } = this.startBox;
103
- if (this.posNpc.x === 0) {
104
- x += adjusted.x; // left edge
105
- w -= adjusted.x;
106
- }
107
- else if (this.posNpc.x === 1) {
108
- w += adjusted.x; // right edge
109
- }
110
- if (this.posNpc.y === 0) {
111
- y += adjusted.y; // top edge
112
- h -= adjusted.y;
113
- }
114
- else if (this.posNpc.y === 1) {
115
- h += adjusted.y; // bottom edge
116
- }
117
- const mtx = this.startCtm.inverse().scaleO(this.startBox.w / w, this.startBox.h / h, this.opposite.x, this.opposite.y).inverseO();
118
- const minSize = 10;
119
- if (w > minSize && h > minSize) // don't let element get too small
120
- this.handles.el.markupStretch(w, h, x, y, mtx);
121
- }
122
- }
123
- /** A ModifyHandle to rotate an element
124
- * @public
125
- */
126
- class RotateHandle extends ModifyHandle {
127
- constructor(handles) {
128
- super(handles);
129
- this.handles = handles;
130
- const props = Markup_1.MarkupApp.props.handles;
131
- this._line = handles.group.line(0, 0, 1, 1).attr(props.rotateLine).addClass(Markup_1.MarkupApp.rotateLineClass);
132
- this._circle = handles.group.circle(props.size * 1.25).attr(props.rotate).addClass(Markup_1.MarkupApp.rotateHandleClass);
133
- this._circle = this.addTouchPadding(this._circle, handles);
134
- this.setMouseHandler(this._circle);
135
- }
136
- get centerVb() { return this.handles.npcToVb({ x: .5, y: .5 }); }
137
- get anchorVb() { return this.handles.npcToVb({ x: .5, y: 0 }); }
138
- setPosition() {
139
- const anchor = this.anchorVb;
140
- const dir = this.centerVb.vectorTo(anchor).normalize();
141
- const loc = this.location = anchor.plusScaled(dir, Markup_1.MarkupApp.props.handles.size * 3);
142
- this._line.plot(anchor.x, anchor.y, loc.x, loc.y);
143
- this._circle.center(loc.x, loc.y);
144
- }
145
- modify(ev) {
146
- const centerVp = this.centerVb;
147
- const currDir = centerVp.vectorTo(Markup_1.MarkupApp.convertVpToVb(ev.viewPoint));
148
- const dir = centerVp.vectorTo(this.location);
149
- this.handles.el.rotate(dir.angleTo(currDir).degrees);
150
- }
151
- }
152
- /** A VertexHandle to move a point on a line
153
- * @public
154
- */
155
- class VertexHandle extends ModifyHandle {
156
- constructor(handles, index) {
157
- super(handles);
158
- this.handles = handles;
159
- const props = Markup_1.MarkupApp.props.handles;
160
- this._circle = handles.group.circle(props.size).attr(props.vertex).addClass(Markup_1.MarkupApp.vertexHandleClass);
161
- this._x = `x${index + 1}`;
162
- this._y = `y${index + 1}`;
163
- this._circle = this.addTouchPadding(this._circle, handles);
164
- this.setMouseHandler(this._circle);
165
- }
166
- setPosition() {
167
- let point = new svg_js_1.Point(this.handles.el.attr(this._x), this.handles.el.attr(this._y));
168
- const matrix = this.handles.el.screenCTM().lmultiplyO(Markup_1.MarkupApp.screenToVbMtx());
169
- point = point.transform(matrix);
170
- this._circle.center(point.x, point.y);
171
- }
172
- modify(ev) {
173
- let point = new svg_js_1.Point(ev.viewPoint.x, ev.viewPoint.y);
174
- const matrix = this.handles.el.screenCTM().inverseO().multiplyO(Markup_1.MarkupApp.getVpToScreenMtx());
175
- point = point.transform(matrix);
176
- const el = this.handles.el;
177
- el.attr(this._x, point.x);
178
- el.attr(this._y, point.y);
179
- }
180
- }
181
- /** A handle that moves (translates) an element.
182
- * @public
183
- */
184
- class MoveHandle extends ModifyHandle {
185
- constructor(handles, showBBox) {
186
- super(handles);
187
- this.handles = handles;
188
- const props = Markup_1.MarkupApp.props.handles;
189
- const clone = this.handles.el.cloneMarkup();
190
- clone.css(props.move);
191
- clone.forElementsOfGroup((child) => child.css(props.move));
192
- if (showBBox) {
193
- this._outline = handles.group.polygon().attr(props.moveOutline);
194
- const rect = this.handles.el.getOutline().attr(props.move).attr({ fill: "none" });
195
- const group = handles.group.group();
196
- group.add(this._outline);
197
- group.add(rect);
198
- group.add(clone);
199
- this._shape = group;
200
- }
201
- else {
202
- clone.addTo(handles.group);
203
- this._shape = clone;
204
- }
205
- this._shape.addClass(Markup_1.MarkupApp.moveHandleClass);
206
- this.setMouseHandler(this._shape);
207
- }
208
- async onClick(_ev) {
209
- const el = this.handles.el;
210
- // eslint-disable-next-line deprecation/deprecation
211
- if (el instanceof svg_js_1.Text || (el instanceof svg_js_1.G && el.node.className.baseVal === Markup_1.MarkupApp.boxedTextClass)) // if they click on the move handle of a text element, start the text editor
212
- await new TextEdit_1.EditTextTool(el).run();
213
- }
214
- /** draw the outline of the element's bbox (in viewbox coordinates) */
215
- setPosition() {
216
- if (undefined !== this._outline) {
217
- const pts = [new core_geometry_1.Point2d(0, 0), new core_geometry_1.Point2d(0, 1), new core_geometry_1.Point2d(1, 1), new core_geometry_1.Point2d(1, 0)];
218
- this._outline.plot(this.handles.npcToVbArray(pts).map((pt) => [pt.x, pt.y]));
219
- }
220
- }
221
- startDrag(ev) {
222
- super.startDrag(ev, ev.isShiftKey);
223
- this._lastPos = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // save stating position in viewbox coordinates
224
- }
225
- modify(ev) {
226
- const evPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
227
- const dist = evPt.minus(this._lastPos);
228
- this._lastPos = evPt;
229
- this.handles.el.translate(dist.x, dist.y); // move the element
230
- }
231
- }
232
- /** The set of ModifyHandles active. Only applies if there is a single element selected.
233
- * @public
234
- */
235
- class Handles {
236
- constructor(ss, el) {
237
- this.ss = ss;
238
- this.el = el;
239
- this.handles = [];
240
- this.dragging = false;
241
- this.group = ss.svg.group();
242
- if (el instanceof svg_js_1.Line) {
243
- this.handles.push(new MoveHandle(this, false));
244
- this.handles.push(new VertexHandle(this, 0));
245
- this.handles.push(new VertexHandle(this, 1));
246
- this.draw(); // show starting state
247
- return;
248
- }
249
- // move box is in the back
250
- this.handles.push(new MoveHandle(this, true));
251
- // then rotate handle
252
- this.handles.push(new RotateHandle(this));
253
- // then add all the stretch handles
254
- const pts = [[0, 0], [0, .5], [0, 1], [.5, 1], [1, 1], [1, .5], [1, 0], [.5, 0]];
255
- const cursors = ["nw", "w", "sw", "s", "se", "e", "ne", "n"];
256
- const order = [7, 3, 1, 5, 2, 6, 0, 4];
257
- const angle = el.screenCTM().decompose().rotate || 0;
258
- const start = Math.round(-angle / 45); // so that we rotate the cursors for rotated elements
259
- order.forEach((index) => this.handles.push(new StretchHandle(this, pts[index], cursors[(index + start + 8) % 8])));
260
- this.draw(); // show starting state
261
- }
262
- npcToBox(p) {
263
- const pt = this.npcToVb(p);
264
- return this.vbToBox(pt, pt);
265
- }
266
- npcToVb(p, result) { return this.npcToVbTrn.multiplyPoint2d(p, result); }
267
- vbToBox(p, result) { return this.vbToBoxTrn.multiplyPoint2d(p, result); }
268
- npcToVbArray(pts) {
269
- pts.forEach((pt) => this.npcToVb(pt, pt));
270
- return pts;
271
- }
272
- draw() {
273
- const el = this.el;
274
- const bb = el.bbox();
275
- const ctm = el.screenCTM().lmultiplyO(Markup_1.MarkupApp.screenToVbMtx());
276
- this.vbToBoxTrn = ctm.inverse().toIModelTransform();
277
- this.npcToVbTrn = new svg_js_1.Matrix().scaleO(bb.w, bb.h).translateO(bb.x, bb.y).lmultiplyO(ctm).toIModelTransform();
278
- this.handles.forEach((h) => h.setPosition());
279
- }
280
- remove() {
281
- if (this.dragging)
282
- this.cancelDrag();
283
- this.group.remove();
284
- }
285
- startDrag(ev) {
286
- if (this.active) {
287
- this.active.startDrag(ev);
288
- this.dragging = true;
289
- Markup_1.MarkupApp.markup.disablePick();
290
- core_frontend_1.IModelApp.toolAdmin.setCursor(core_frontend_1.IModelApp.viewManager.dynamicsCursor);
291
- }
292
- return core_frontend_1.EventHandled.Yes;
293
- }
294
- drag(ev) {
295
- if (this.dragging) {
296
- this.active.modify(ev);
297
- this.draw();
298
- }
299
- }
300
- /** complete the modification for the active handle. */
301
- endDrag(undo) {
302
- undo.performOperation(Markup_1.MarkupApp.getActionName("modify"), () => {
303
- const el = this.el;
304
- const original = el.originalEl; // save original element
305
- if (original === undefined) {
306
- this.ss.emptyAll();
307
- this.ss.add(el);
308
- undo.onAdded(el);
309
- }
310
- else {
311
- el.originalEl = undefined; // clear original element
312
- undo.onModified(el, original);
313
- }
314
- });
315
- this.draw();
316
- this.dragging = false;
317
- this.active = undefined;
318
- Markup_1.MarkupApp.markup.enablePick();
319
- return core_frontend_1.EventHandled.Yes;
320
- }
321
- /** called when the reset button is pressed. */
322
- cancelDrag() {
323
- if (!this.dragging)
324
- return;
325
- const el = this.el;
326
- const original = el.originalEl;
327
- if (original) {
328
- el.replace(original);
329
- this.el = original;
330
- }
331
- this.draw();
332
- this.active = undefined;
333
- Markup_1.MarkupApp.markup.enablePick();
334
- }
335
- }
336
- exports.Handles = Handles;
337
- /** The set of currently selected SVG elements. When elements are added to the set, they are hilited.
338
- * @public
339
- */
340
- class MarkupSelected {
341
- constructor(svg) {
342
- this.svg = svg;
343
- this.elements = new Set();
344
- /** Called whenever elements are added or removed from this SelectionSet */
345
- this.onChanged = new core_bentley_1.BeEvent();
346
- }
347
- get size() { return this.elements.size; }
348
- get isEmpty() { return this.size === 0; }
349
- has(el) { return this.elements.has(el); }
350
- emptyAll() {
351
- this.clearEditors();
352
- if (this.isEmpty)
353
- return; // Don't send onChanged if already empty.
354
- this.elements.forEach((el) => el.unHilite());
355
- this.elements.clear();
356
- this.onChanged.raiseEvent(this);
357
- }
358
- restart(el) {
359
- this.emptyAll();
360
- if (el)
361
- this.add(el);
362
- }
363
- clearEditors() {
364
- if (this.handles) {
365
- this.handles.remove();
366
- this.handles = undefined;
367
- }
368
- }
369
- sizeChanged() {
370
- this.clearEditors();
371
- if (this.elements.size === 1)
372
- this.handles = new Handles(this, this.elements.values().next().value);
373
- this.onChanged.raiseEvent(this);
374
- }
375
- /** Add a new element to the SS */
376
- add(el) {
377
- this.elements.add(el);
378
- el.hilite();
379
- this.sizeChanged();
380
- }
381
- /** Remove an element from the selection set and unhilite it.
382
- * @returns true if the element was in the SS and was removed.
383
- */
384
- drop(el) {
385
- el.unHilite();
386
- return this.elements.delete(el) ? (this.sizeChanged(), true) : false;
387
- }
388
- /** Replace an entry in the selection set with a different element. */
389
- replace(oldEl, newEl) {
390
- if (this.drop(oldEl))
391
- this.add(newEl);
392
- }
393
- deleteAll(undo) {
394
- undo.performOperation(Markup_1.MarkupApp.getActionName("delete"), () => this.elements.forEach((el) => {
395
- undo.onDelete(el);
396
- el.remove();
397
- }));
398
- this.emptyAll();
399
- }
400
- groupAll(undo) {
401
- if (this.size < 2)
402
- return;
403
- const first = this.elements.values().next().value;
404
- const parent = first.parent();
405
- const group = parent.group();
406
- const ordered = [];
407
- this.elements.forEach((el) => ordered.push(el));
408
- ordered.sort((lhs, rhs) => parent.index(lhs) - parent.index(rhs)); // Preserve relative z ordering
409
- undo.performOperation(Markup_1.MarkupApp.getActionName("group"), () => {
410
- ordered.forEach((el) => {
411
- const oldParent = el.parent();
412
- const oldPos = el.position();
413
- el.unHilite();
414
- undo.onRepositioned(el.addTo(group), oldPos, oldParent);
415
- }), undo.onAdded(group);
416
- });
417
- this.restart(group);
418
- }
419
- ungroupAll(undo) {
420
- const groups = new Set();
421
- this.elements.forEach((el) => {
422
- if (el instanceof svg_js_1.G)
423
- groups.add(el);
424
- });
425
- if (0 === groups.size)
426
- return;
427
- undo.performOperation(Markup_1.MarkupApp.getActionName("ungroup"), () => {
428
- groups.forEach((g) => {
429
- g.unHilite();
430
- this.elements.delete(g);
431
- undo.onDelete(g);
432
- g.each((index, children) => {
433
- const child = children[index];
434
- const oldPos = child.position();
435
- child.toParent(g.parent());
436
- undo.onRepositioned(child, oldPos, g);
437
- }, false);
438
- g.untransform(); // Don't want undo of ungroup to push the current group transform...
439
- g.remove();
440
- });
441
- });
442
- this.sizeChanged();
443
- }
444
- /** Move all of the entries to a new position in the DOM via a callback. */
445
- reposition(cmdName, undo, fn) {
446
- undo.performOperation(cmdName, () => this.elements.forEach((el) => {
447
- const oldParent = el.parent();
448
- const oldPos = el.position();
449
- fn(el);
450
- undo.onRepositioned(el, oldPos, oldParent);
451
- }));
452
- this.sizeChanged();
453
- }
454
- }
455
- exports.MarkupSelected = MarkupSelected;
456
- /** Provides UI for selection, delete, move, copy, bring-to-front, send-to-back, etc. for Markup SVG elements
457
- * @public
458
- */
459
- class SelectTool extends MarkupTool_1.MarkupTool {
460
- constructor() {
461
- super(...arguments);
462
- this._dragging = [];
463
- this._isBoxSelect = false;
464
- }
465
- get flashedElement() { return this._flashedElement; }
466
- set flashedElement(el) {
467
- if (el === this._flashedElement)
468
- return;
469
- if (undefined !== this._flashedElement)
470
- this._flashedElement.unFlash();
471
- if (undefined !== el)
472
- el.flash();
473
- this._flashedElement = el;
474
- }
475
- unflashSelected() {
476
- if (undefined !== this._flashedElement && this.markup.selected.has(this._flashedElement))
477
- this.flashedElement = undefined;
478
- }
479
- initSelect() {
480
- this.markup.setCursor("default");
481
- this.markup.enablePick();
482
- this.flashedElement = undefined;
483
- this.boxSelectInit();
484
- }
485
- clearSelect() {
486
- this.cancelDrag();
487
- this.markup.selected.emptyAll();
488
- }
489
- async onCleanup() { this.clearSelect(); }
490
- async onPostInstall() {
491
- this.initSelect();
492
- return super.onPostInstall();
493
- }
494
- async onRestartTool() { this.initSelect(); }
495
- showPrompt() {
496
- const mainInstruction = core_frontend_1.ToolAssistance.createInstruction(this.iconSpec, core_frontend_1.IModelApp.localization.getLocalizedString(`${MarkupTool_1.MarkupTool.toolKey}Select.Prompts.IdentifyMarkup`));
497
- const mouseInstructions = [];
498
- const touchInstructions = [];
499
- const acceptMsg = core_frontend_1.IModelApp.localization.getLocalizedString(`${MarkupTool_1.MarkupTool.toolKey}Select.Prompts.AcceptMarkup`);
500
- touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.OneTouchTap, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Touch));
501
- mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.LeftClick, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
502
- touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.OneTouchDrag, core_frontend_1.CoreTools.translate("ElementSet.Inputs.BoxCorners"), false, core_frontend_1.ToolAssistanceInputMethod.Touch));
503
- mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.LeftClickDrag, core_frontend_1.CoreTools.translate("ElementSet.Inputs.BoxCorners"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
504
- mouseInstructions.push(core_frontend_1.ToolAssistance.createModifierKeyInstruction(core_frontend_1.ToolAssistance.shiftKey, core_frontend_1.ToolAssistanceImage.LeftClickDrag, core_frontend_1.CoreTools.translate("ElementSet.Inputs.OverlapSelection"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
505
- mouseInstructions.push(core_frontend_1.ToolAssistance.createModifierKeyInstruction(core_frontend_1.ToolAssistance.ctrlKey, core_frontend_1.ToolAssistanceImage.LeftClick, core_frontend_1.CoreTools.translate("ElementSet.Inputs.InvertSelection"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
506
- mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.CursorClick, core_frontend_1.CoreTools.translate("ElementSet.Inputs.ClearSelection"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
507
- const sections = [];
508
- sections.push(core_frontend_1.ToolAssistance.createSection(mouseInstructions, core_frontend_1.ToolAssistance.inputsLabel));
509
- sections.push(core_frontend_1.ToolAssistance.createSection(touchInstructions, core_frontend_1.ToolAssistance.inputsLabel));
510
- const instructions = core_frontend_1.ToolAssistance.createInstructions(mainInstruction, sections);
511
- core_frontend_1.IModelApp.notifications.setToolAssistance(instructions);
512
- }
513
- /** When we start a drag operation, we add a new set of elements to the DOM and start modifying them.
514
- * If we cancel the operation, we need remove them from the DOM.
515
- */
516
- cancelDrag() {
517
- this._dragging.forEach((el) => el.remove()); // remove temporary elements from DOM
518
- this._dragging.length = 0;
519
- this.boxSelectInit();
520
- }
521
- async onResetButtonUp(_ev) {
522
- const selected = this.markup.selected;
523
- const handles = selected.handles;
524
- if (handles && handles.dragging)
525
- handles.cancelDrag();
526
- this.cancelDrag();
527
- selected.sizeChanged();
528
- return core_frontend_1.EventHandled.Yes;
529
- }
530
- /** Called when there is a mouse "click" (down+up without any motion) */
531
- async onDataButtonUp(ev) {
532
- const markup = this.markup;
533
- const selected = markup.selected;
534
- const handles = selected.handles;
535
- if (handles) {
536
- if (handles.dragging)
537
- return handles.endDrag(markup.undo);
538
- if (handles.active) { // clicked on a handle
539
- if (ev.isControlKey)
540
- selected.drop(handles.el);
541
- else
542
- await handles.active.onClick(ev);
543
- handles.active = undefined;
544
- return core_frontend_1.EventHandled.Yes;
545
- }
546
- }
547
- const el = this.flashedElement = this.pickElement(ev.viewPoint);
548
- if (ev.isControlKey) {
549
- if (el && selected.drop(el))
550
- return core_frontend_1.EventHandled.Yes;
551
- }
552
- else {
553
- selected.emptyAll();
554
- }
555
- if (el !== undefined)
556
- selected.add(el);
557
- return core_frontend_1.EventHandled.Yes;
558
- }
559
- async onTouchTap(ev) {
560
- // Allow tap with a second touch point to multiselect (similar functionality to control being held with mouse click).
561
- if (ev.isSingleTap && 2 === ev.touchEvent.touches.length) {
562
- const el = this.flashedElement = this.pickElement(ev.viewPoint);
563
- if (el) {
564
- const selected = this.markup.selected;
565
- if (!selected.drop(el))
566
- selected.add(el);
567
- return core_frontend_1.EventHandled.Yes;
568
- }
569
- }
570
- return super.onTouchTap(ev);
571
- }
572
- boxSelectInit() {
573
- this._isBoxSelect = false;
574
- this.markup.svgDynamics.clear();
575
- }
576
- boxSelectStart(ev) {
577
- if (!ev.isControlKey)
578
- this.markup.selected.emptyAll();
579
- this._anchorPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
580
- this._isBoxSelect = true;
581
- return true;
582
- }
583
- boxSelect(ev, isDynamics) {
584
- if (!this._isBoxSelect)
585
- return false;
586
- const start = this._anchorPt;
587
- const end = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
588
- const vec = start.vectorTo(end);
589
- const width = Math.abs(vec.x);
590
- const height = Math.abs(vec.y);
591
- if (width < 1 || height < 1)
592
- return true;
593
- const rightToLeft = (start.x > end.x);
594
- const overlapMode = (ev.isShiftKey ? !rightToLeft : rightToLeft); // Shift inverts inside/overlap selection...
595
- const offset = core_geometry_1.Point3d.create(vec.x < 0 ? end.x : start.x, vec.y < 0 ? end.y : start.y); // define location by corner points...
596
- this.markup.svgDynamics.clear();
597
- this.markup.svgDynamics.rect(width, height).move(offset.x, offset.y).css({ "stroke-width": 1, "stroke": "black", "stroke-opacity": 0.5, "fill": "lightBlue", "fill-opacity": 0.2 });
598
- const selectBox = this.markup.svgDynamics.rect(width, height).move(offset.x, offset.y).css({ "stroke-width": 1, "stroke": "white", "stroke-opacity": 1.0, "stroke-dasharray": overlapMode ? "5" : "2", "fill": "none" });
599
- const outlinesG = isDynamics ? this.markup.svgDynamics.group() : undefined;
600
- const selectRect = selectBox.node.getBoundingClientRect();
601
- this.markup.svgMarkup.forElementsOfGroup((child) => {
602
- const childRect = child.node.getBoundingClientRect();
603
- const inside = (childRect.left >= selectRect.left && childRect.top >= selectRect.top && childRect.right <= selectRect.right && childRect.bottom <= selectRect.bottom);
604
- const overlap = !inside && (childRect.left < selectRect.right && childRect.right > selectRect.left && childRect.bottom > selectRect.top && childRect.top < selectRect.bottom);
605
- const accept = inside || (overlap && overlapMode);
606
- if (undefined !== outlinesG) {
607
- if (inside || overlap) {
608
- const outline = child.getOutline().attr(Markup_1.MarkupApp.props.handles.moveOutline).addTo(outlinesG);
609
- if (accept)
610
- outline.attr({ "fill": Markup_1.MarkupApp.props.hilite.flash, "fill-opacity": 0.2 });
611
- }
612
- }
613
- else if (accept) {
614
- this.markup.selected.add(child);
615
- }
616
- });
617
- if (!isDynamics)
618
- this.boxSelectInit();
619
- return true;
620
- }
621
- /** called when the mouse moves while the data button is down. */
622
- async onMouseStartDrag(ev) {
623
- if (core_frontend_1.BeButton.Data !== ev.button)
624
- return core_frontend_1.EventHandled.No;
625
- const markup = this.markup;
626
- const selected = markup.selected;
627
- const handles = selected.handles;
628
- if (handles && handles.active) {
629
- this.flashedElement = undefined; // make sure there are no elements flashed while dragging
630
- return handles.startDrag(ev);
631
- }
632
- const flashed = this.flashedElement = this.pickElement(ev.viewPoint);
633
- if (undefined === flashed)
634
- return this.boxSelectStart(ev) ? core_frontend_1.EventHandled.Yes : core_frontend_1.EventHandled.No;
635
- if (!selected.has(flashed))
636
- selected.restart(flashed); // we clicked on an element not in the selection set, replace current selection with just this element
637
- selected.clearEditors();
638
- this._anchorPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // save the starting point. This is the point where the "down" occurred.
639
- this.cancelDrag();
640
- selected.elements.forEach((el) => {
641
- const cloned = el.cloneMarkup(); // make a clone of this element
642
- el.after(cloned); // put it into the DOM after its original
643
- cloned.originalEl = el; // save original element so we can remove it if this is a "move" command
644
- this._dragging.push(cloned); // add to dragging set
645
- });
646
- return core_frontend_1.EventHandled.Yes;
647
- }
648
- /** Called whenever the mouse moves while this tool is active. */
649
- async onMouseMotion(ev) {
650
- const markup = this.markup;
651
- const handles = markup.selected.handles;
652
- if (handles && handles.dragging) {
653
- this.receivedDownEvent = true; // necessary to tell ToolAdmin to send us the button up event
654
- return handles.drag(ev); // drag the handle
655
- }
656
- if (this._dragging.length === 0) {
657
- if (this.boxSelect(ev, true))
658
- return;
659
- if (core_frontend_1.InputSource.Touch !== ev.inputSource)
660
- this.flashedElement = this.pickElement(ev.viewPoint); // if we're not dragging, try to find an element under the cursor
661
- return;
662
- }
663
- // we have a set of elements being dragged. NOTE: coordinates are viewbox
664
- const vbPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
665
- const delta = vbPt.minus(this._anchorPt);
666
- this._dragging.forEach((el) => el.translate(delta.x, delta.y));
667
- this._anchorPt = vbPt; // translate moves from last mouse location
668
- }
669
- /** Called when the mouse goes up after dragging. */
670
- async onMouseEndDrag(ev) {
671
- const markup = this.markup;
672
- const selected = markup.selected;
673
- const handles = selected.handles;
674
- if (handles && handles.dragging) // if we have handles up, and if they're in the "dragging" state, send the event to them
675
- return handles.endDrag(markup.undo);
676
- if (this._dragging.length === 0)
677
- return this.boxSelect(ev, false) ? core_frontend_1.EventHandled.Yes : core_frontend_1.EventHandled.No;
678
- // NOTE: all units should be in viewbox coordinates
679
- const delta = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint).minus(this._anchorPt);
680
- const undo = markup.undo;
681
- if (ev.isShiftKey) // shift key means "add to existing," otherwise new selection replaces old
682
- selected.emptyAll();
683
- // move or copy all of the elements in dragged set
684
- undo.performOperation(Markup_1.MarkupApp.getActionName("copy"), () => this._dragging.forEach((el) => {
685
- el.translate(delta.x, delta.y); // move to final location
686
- const original = el.originalEl; // save original element
687
- el.originalEl = undefined; // clear original element
688
- if (ev.isShiftKey) {
689
- selected.add(el);
690
- undo.onAdded(el); // shift key means copy element
691
- }
692
- else {
693
- original.replace(el);
694
- undo.onModified(el, original);
695
- }
696
- }));
697
- this._dragging.length = 0; // empty dragging set
698
- selected.sizeChanged(); // notify that size of selection set changed
699
- return core_frontend_1.EventHandled.Yes;
700
- }
701
- /** called when a modifier key is pressed or released. Updates stretch handles, if present */
702
- async onModifierKeyTransition(_wentDown, modifier, _event) {
703
- if (modifier !== core_frontend_1.BeModifierKeys.Shift) // we only care about the shift key
704
- return core_frontend_1.EventHandled.No;
705
- const selected = this.markup.selected;
706
- const handles = selected.handles;
707
- if (undefined === handles || !handles.dragging) // and only if we're currently dragging
708
- return core_frontend_1.EventHandled.No;
709
- const ev = new core_frontend_1.BeButtonEvent(); // we need to simulate a mouse motion by sending a drag event at the last cursor position
710
- core_frontend_1.IModelApp.toolAdmin.fillEventFromCursorLocation(ev);
711
- return (undefined === ev.viewport) ? core_frontend_1.EventHandled.No : (handles.drag(ev), core_frontend_1.EventHandled.Yes);
712
- }
713
- /** called whenever a key is pressed while this tool is active. */
714
- async onKeyTransition(wentDown, key) {
715
- if (!wentDown)
716
- return core_frontend_1.EventHandled.No;
717
- const markup = this.markup;
718
- switch (key.key.toLowerCase()) {
719
- case "delete": // delete key or backspace = delete current selection set
720
- case "backspace":
721
- this.unflashSelected();
722
- markup.deleteSelected();
723
- return core_frontend_1.EventHandled.Yes;
724
- case "escape": // esc = cancel current operation
725
- await this.exitTool();
726
- return core_frontend_1.EventHandled.Yes;
727
- case "b": // alt-shift-b = send to back
728
- return (key.altKey && key.shiftKey) ? (markup.sendToBack(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
729
- case "f": // alt-shift-f = bring to front
730
- return (key.altKey && key.shiftKey) ? (markup.bringToFront(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
731
- case "g": // ctrl-g = create group
732
- return (key.ctrlKey) ? (this.unflashSelected(), markup.groupSelected(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
733
- case "u": // ctrl-u = ungroup
734
- return (key.ctrlKey) ? (this.unflashSelected(), markup.ungroupSelected(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
735
- }
736
- return core_frontend_1.EventHandled.No;
737
- }
738
- }
739
- exports.SelectTool = SelectTool;
740
- SelectTool.toolId = "Markup.Select";
741
- SelectTool.iconSpec = "icon-cursor";
1
+ "use strict";
2
+ /*---------------------------------------------------------------------------------------------
3
+ * Copyright (c) Bentley Systems, Incorporated. All rights reserved.
4
+ * See LICENSE.md in the project root for license terms and full copyright notice.
5
+ *--------------------------------------------------------------------------------------------*/
6
+ /** @packageDocumentation
7
+ * @module MarkupTools
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SelectTool = exports.MarkupSelected = exports.Handles = exports.ModifyHandle = void 0;
11
+ const core_bentley_1 = require("@itwin/core-bentley");
12
+ const core_geometry_1 = require("@itwin/core-geometry");
13
+ const core_frontend_1 = require("@itwin/core-frontend");
14
+ const svg_js_1 = require("@svgdotjs/svg.js");
15
+ const Markup_1 = require("./Markup");
16
+ const MarkupTool_1 = require("./MarkupTool");
17
+ const TextEdit_1 = require("./TextEdit");
18
+ // cspell:ignore lmultiply untransform unFlash multiselect
19
+ /** Classes added to HTMLElements so they can be customized in CSS by applications.
20
+ * A "modify handle" is a visible position on the screen that provides UI to modify a MarkupElement.
21
+ * @public
22
+ */
23
+ class ModifyHandle {
24
+ constructor(handles) {
25
+ this.handles = handles;
26
+ }
27
+ async onClick(_ev) { }
28
+ /** the mouse just went down on this handle, begin modification. */
29
+ startDrag(_ev, makeCopy = false) {
30
+ this.vbToStartTrn = this.handles.vbToBoxTrn.clone(); // save the starting vp -> element box transform
31
+ this.startModify(makeCopy);
32
+ }
33
+ startModify(makeCopy) {
34
+ const handles = this.handles;
35
+ const el = handles.el;
36
+ const cloned = handles.el = el.cloneMarkup(); // make a clone of this element
37
+ if (makeCopy) {
38
+ el.after(cloned);
39
+ }
40
+ else {
41
+ cloned.originalEl = el; // save original for undo
42
+ el.replace(cloned); // put it into the DOM in place of the original
43
+ }
44
+ }
45
+ setMouseHandler(target) {
46
+ const node = target.node;
47
+ node.addEventListener("mousedown", (event) => {
48
+ const ev = event;
49
+ if (0 === ev.button && undefined === this.handles.active)
50
+ this.handles.active = this;
51
+ });
52
+ node.addEventListener("touchstart", () => {
53
+ if (undefined === this.handles.active)
54
+ this.handles.active = this;
55
+ });
56
+ }
57
+ addTouchPadding(visible, handles) {
58
+ if (core_frontend_1.InputSource.Touch !== core_frontend_1.IModelApp.toolAdmin.currentInputState.inputSource)
59
+ return visible;
60
+ const padding = visible.cloneMarkup().scale(3).attr("opacity", 0);
61
+ const g = handles.group.group();
62
+ padding.addTo(g);
63
+ visible.addTo(g);
64
+ return g;
65
+ }
66
+ }
67
+ exports.ModifyHandle = ModifyHandle;
68
+ /** A ModifyHandle that changes the size of the element
69
+ * @public
70
+ */
71
+ class StretchHandle extends ModifyHandle {
72
+ constructor(handles, xy, cursor) {
73
+ super(handles);
74
+ this.posNpc = new core_geometry_1.Point2d(xy[0], xy[1]);
75
+ const props = Markup_1.MarkupApp.props.handles;
76
+ this._circle = handles.group.circle(props.size).addClass(Markup_1.MarkupApp.stretchHandleClass).attr(props.stretch).attr("cursor", `${cursor}-resize`); // the visible "circle" for this handle
77
+ this._circle = this.addTouchPadding(this._circle, handles);
78
+ this.setMouseHandler(this._circle);
79
+ }
80
+ setPosition() {
81
+ const pt = this.handles.npcToVb(this.posNpc); // convert to viewbox coords
82
+ this._circle.center(pt.x, pt.y);
83
+ }
84
+ startDrag(_ev) {
85
+ const handles = this.handles;
86
+ this.startCtm = handles.el.screenCTM().lmultiplyO(Markup_1.MarkupApp.screenToVbMtx());
87
+ this.startBox = handles.el.bbox(); // save starting size so we can preserve aspect ratio
88
+ this.startPos = handles.npcToBox(this.posNpc);
89
+ this.opposite = handles.npcToBox({ x: 1 - this.posNpc.x, y: 1 - this.posNpc.y });
90
+ super.startDrag(_ev);
91
+ }
92
+ /** perform the stretch. Always stretch element with anchor at the opposite corner of the one being moved. */
93
+ modify(ev) {
94
+ const evPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // get cursor location in viewbox coords
95
+ const diff = this.startPos.vectorTo(this.vbToStartTrn.multiplyPoint2d(evPt)); // movement of cursor from start, in viewbox coords
96
+ const diag = this.startPos.vectorTo(this.opposite).normalize(); // vector from opposite corner to this handle
97
+ let diagVec = diag.scaleToLength(diff.dotProduct(diag)); // projected distance along diagonal
98
+ if (diagVec === undefined)
99
+ diagVec = core_geometry_1.Vector2d.createZero();
100
+ // if the shift key is down, don't preserve aspect ratio
101
+ const adjusted = ev.isShiftKey ? { x: diff.x, y: diff.y } : { x: diagVec.x, y: diagVec.y };
102
+ let { x, y, h, w } = this.startBox;
103
+ if (this.posNpc.x === 0) {
104
+ x += adjusted.x; // left edge
105
+ w -= adjusted.x;
106
+ }
107
+ else if (this.posNpc.x === 1) {
108
+ w += adjusted.x; // right edge
109
+ }
110
+ if (this.posNpc.y === 0) {
111
+ y += adjusted.y; // top edge
112
+ h -= adjusted.y;
113
+ }
114
+ else if (this.posNpc.y === 1) {
115
+ h += adjusted.y; // bottom edge
116
+ }
117
+ const mtx = this.startCtm.inverse().scaleO(this.startBox.w / w, this.startBox.h / h, this.opposite.x, this.opposite.y).inverseO();
118
+ const minSize = 10;
119
+ if (w > minSize && h > minSize) // don't let element get too small
120
+ this.handles.el.markupStretch(w, h, x, y, mtx);
121
+ }
122
+ }
123
+ /** A ModifyHandle to rotate an element
124
+ * @public
125
+ */
126
+ class RotateHandle extends ModifyHandle {
127
+ constructor(handles) {
128
+ super(handles);
129
+ this.handles = handles;
130
+ const props = Markup_1.MarkupApp.props.handles;
131
+ this._line = handles.group.line(0, 0, 1, 1).attr(props.rotateLine).addClass(Markup_1.MarkupApp.rotateLineClass);
132
+ this._circle = handles.group.circle(props.size * 1.25).attr(props.rotate).addClass(Markup_1.MarkupApp.rotateHandleClass);
133
+ this._circle = this.addTouchPadding(this._circle, handles);
134
+ this.setMouseHandler(this._circle);
135
+ }
136
+ get centerVb() { return this.handles.npcToVb({ x: .5, y: .5 }); }
137
+ get anchorVb() { return this.handles.npcToVb({ x: .5, y: 0 }); }
138
+ setPosition() {
139
+ const anchor = this.anchorVb;
140
+ const dir = this.centerVb.vectorTo(anchor).normalize();
141
+ const loc = this.location = anchor.plusScaled(dir, Markup_1.MarkupApp.props.handles.size * 3);
142
+ this._line.plot(anchor.x, anchor.y, loc.x, loc.y);
143
+ this._circle.center(loc.x, loc.y);
144
+ }
145
+ modify(ev) {
146
+ const centerVp = this.centerVb;
147
+ const currDir = centerVp.vectorTo(Markup_1.MarkupApp.convertVpToVb(ev.viewPoint));
148
+ const dir = centerVp.vectorTo(this.location);
149
+ this.handles.el.rotate(dir.angleTo(currDir).degrees);
150
+ }
151
+ }
152
+ /** A VertexHandle to move a point on a line
153
+ * @public
154
+ */
155
+ class VertexHandle extends ModifyHandle {
156
+ constructor(handles, index) {
157
+ super(handles);
158
+ this.handles = handles;
159
+ const props = Markup_1.MarkupApp.props.handles;
160
+ this._circle = handles.group.circle(props.size).attr(props.vertex).addClass(Markup_1.MarkupApp.vertexHandleClass);
161
+ this._x = `x${index + 1}`;
162
+ this._y = `y${index + 1}`;
163
+ this._circle = this.addTouchPadding(this._circle, handles);
164
+ this.setMouseHandler(this._circle);
165
+ }
166
+ setPosition() {
167
+ let point = new svg_js_1.Point(this.handles.el.attr(this._x), this.handles.el.attr(this._y));
168
+ const matrix = this.handles.el.screenCTM().lmultiplyO(Markup_1.MarkupApp.screenToVbMtx());
169
+ point = point.transform(matrix);
170
+ this._circle.center(point.x, point.y);
171
+ }
172
+ modify(ev) {
173
+ let point = new svg_js_1.Point(ev.viewPoint.x, ev.viewPoint.y);
174
+ const matrix = this.handles.el.screenCTM().inverseO().multiplyO(Markup_1.MarkupApp.getVpToScreenMtx());
175
+ point = point.transform(matrix);
176
+ const el = this.handles.el;
177
+ el.attr(this._x, point.x);
178
+ el.attr(this._y, point.y);
179
+ }
180
+ }
181
+ /** A handle that moves (translates) an element.
182
+ * @public
183
+ */
184
+ class MoveHandle extends ModifyHandle {
185
+ constructor(handles, showBBox) {
186
+ super(handles);
187
+ this.handles = handles;
188
+ const props = Markup_1.MarkupApp.props.handles;
189
+ const clone = this.handles.el.cloneMarkup();
190
+ clone.css(props.move);
191
+ clone.forElementsOfGroup((child) => child.css(props.move));
192
+ if (showBBox) {
193
+ this._outline = handles.group.polygon().attr(props.moveOutline);
194
+ const rect = this.handles.el.getOutline().attr(props.move).attr({ fill: "none" });
195
+ const group = handles.group.group();
196
+ group.add(this._outline);
197
+ group.add(rect);
198
+ group.add(clone);
199
+ this._shape = group;
200
+ }
201
+ else {
202
+ clone.addTo(handles.group);
203
+ this._shape = clone;
204
+ }
205
+ this._shape.addClass(Markup_1.MarkupApp.moveHandleClass);
206
+ this.setMouseHandler(this._shape);
207
+ }
208
+ async onClick(_ev) {
209
+ const el = this.handles.el;
210
+ // eslint-disable-next-line deprecation/deprecation
211
+ if (el instanceof svg_js_1.Text || (el instanceof svg_js_1.G && el.node.className.baseVal === Markup_1.MarkupApp.boxedTextClass)) // if they click on the move handle of a text element, start the text editor
212
+ await new TextEdit_1.EditTextTool(el).run();
213
+ }
214
+ /** draw the outline of the element's bbox (in viewbox coordinates) */
215
+ setPosition() {
216
+ if (undefined !== this._outline) {
217
+ const pts = [new core_geometry_1.Point2d(0, 0), new core_geometry_1.Point2d(0, 1), new core_geometry_1.Point2d(1, 1), new core_geometry_1.Point2d(1, 0)];
218
+ this._outline.plot(this.handles.npcToVbArray(pts).map((pt) => [pt.x, pt.y]));
219
+ }
220
+ }
221
+ startDrag(ev) {
222
+ super.startDrag(ev, ev.isShiftKey);
223
+ this._lastPos = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // save stating position in viewbox coordinates
224
+ }
225
+ modify(ev) {
226
+ const evPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
227
+ const dist = evPt.minus(this._lastPos);
228
+ this._lastPos = evPt;
229
+ this.handles.el.translate(dist.x, dist.y); // move the element
230
+ }
231
+ }
232
+ /** The set of ModifyHandles active. Only applies if there is a single element selected.
233
+ * @public
234
+ */
235
+ class Handles {
236
+ constructor(ss, el) {
237
+ this.ss = ss;
238
+ this.el = el;
239
+ this.handles = [];
240
+ this.dragging = false;
241
+ this.group = ss.svg.group();
242
+ if (el instanceof svg_js_1.Line) {
243
+ this.handles.push(new MoveHandle(this, false));
244
+ this.handles.push(new VertexHandle(this, 0));
245
+ this.handles.push(new VertexHandle(this, 1));
246
+ this.draw(); // show starting state
247
+ return;
248
+ }
249
+ // move box is in the back
250
+ this.handles.push(new MoveHandle(this, true));
251
+ // then rotate handle
252
+ this.handles.push(new RotateHandle(this));
253
+ // then add all the stretch handles
254
+ const pts = [[0, 0], [0, .5], [0, 1], [.5, 1], [1, 1], [1, .5], [1, 0], [.5, 0]];
255
+ const cursors = ["nw", "w", "sw", "s", "se", "e", "ne", "n"];
256
+ const order = [7, 3, 1, 5, 2, 6, 0, 4];
257
+ const angle = el.screenCTM().decompose().rotate || 0;
258
+ const start = Math.round(-angle / 45); // so that we rotate the cursors for rotated elements
259
+ order.forEach((index) => this.handles.push(new StretchHandle(this, pts[index], cursors[(index + start + 8) % 8])));
260
+ this.draw(); // show starting state
261
+ }
262
+ npcToBox(p) {
263
+ const pt = this.npcToVb(p);
264
+ return this.vbToBox(pt, pt);
265
+ }
266
+ npcToVb(p, result) { return this.npcToVbTrn.multiplyPoint2d(p, result); }
267
+ vbToBox(p, result) { return this.vbToBoxTrn.multiplyPoint2d(p, result); }
268
+ npcToVbArray(pts) {
269
+ pts.forEach((pt) => this.npcToVb(pt, pt));
270
+ return pts;
271
+ }
272
+ draw() {
273
+ const el = this.el;
274
+ const bb = el.bbox();
275
+ const ctm = el.screenCTM().lmultiplyO(Markup_1.MarkupApp.screenToVbMtx());
276
+ this.vbToBoxTrn = ctm.inverse().toIModelTransform();
277
+ this.npcToVbTrn = new svg_js_1.Matrix().scaleO(bb.w, bb.h).translateO(bb.x, bb.y).lmultiplyO(ctm).toIModelTransform();
278
+ this.handles.forEach((h) => h.setPosition());
279
+ }
280
+ remove() {
281
+ if (this.dragging)
282
+ this.cancelDrag();
283
+ this.group.remove();
284
+ }
285
+ startDrag(ev) {
286
+ if (this.active) {
287
+ this.active.startDrag(ev);
288
+ this.dragging = true;
289
+ Markup_1.MarkupApp.markup.disablePick();
290
+ core_frontend_1.IModelApp.toolAdmin.setCursor(core_frontend_1.IModelApp.viewManager.dynamicsCursor);
291
+ }
292
+ return core_frontend_1.EventHandled.Yes;
293
+ }
294
+ drag(ev) {
295
+ if (this.dragging) {
296
+ this.active.modify(ev);
297
+ this.draw();
298
+ }
299
+ }
300
+ /** complete the modification for the active handle. */
301
+ endDrag(undo) {
302
+ undo.performOperation(Markup_1.MarkupApp.getActionName("modify"), () => {
303
+ const el = this.el;
304
+ const original = el.originalEl; // save original element
305
+ if (original === undefined) {
306
+ this.ss.emptyAll();
307
+ this.ss.add(el);
308
+ undo.onAdded(el);
309
+ }
310
+ else {
311
+ el.originalEl = undefined; // clear original element
312
+ undo.onModified(el, original);
313
+ }
314
+ });
315
+ this.draw();
316
+ this.dragging = false;
317
+ this.active = undefined;
318
+ Markup_1.MarkupApp.markup.enablePick();
319
+ return core_frontend_1.EventHandled.Yes;
320
+ }
321
+ /** called when the reset button is pressed. */
322
+ cancelDrag() {
323
+ if (!this.dragging)
324
+ return;
325
+ const el = this.el;
326
+ const original = el.originalEl;
327
+ if (original) {
328
+ el.replace(original);
329
+ this.el = original;
330
+ }
331
+ this.draw();
332
+ this.active = undefined;
333
+ Markup_1.MarkupApp.markup.enablePick();
334
+ }
335
+ }
336
+ exports.Handles = Handles;
337
+ /** The set of currently selected SVG elements. When elements are added to the set, they are hilited.
338
+ * @public
339
+ */
340
+ class MarkupSelected {
341
+ get size() { return this.elements.size; }
342
+ get isEmpty() { return this.size === 0; }
343
+ has(el) { return this.elements.has(el); }
344
+ emptyAll() {
345
+ this.clearEditors();
346
+ if (this.isEmpty)
347
+ return; // Don't send onChanged if already empty.
348
+ this.elements.forEach((el) => el.unHilite());
349
+ this.elements.clear();
350
+ this.onChanged.raiseEvent(this);
351
+ }
352
+ restart(el) {
353
+ this.emptyAll();
354
+ if (el)
355
+ this.add(el);
356
+ }
357
+ constructor(svg) {
358
+ this.svg = svg;
359
+ this.elements = new Set();
360
+ /** Called whenever elements are added or removed from this SelectionSet */
361
+ this.onChanged = new core_bentley_1.BeEvent();
362
+ }
363
+ clearEditors() {
364
+ if (this.handles) {
365
+ this.handles.remove();
366
+ this.handles = undefined;
367
+ }
368
+ }
369
+ sizeChanged() {
370
+ this.clearEditors();
371
+ if (this.elements.size === 1)
372
+ this.handles = new Handles(this, this.elements.values().next().value);
373
+ this.onChanged.raiseEvent(this);
374
+ }
375
+ /** Add a new element to the SS */
376
+ add(el) {
377
+ this.elements.add(el);
378
+ el.hilite();
379
+ this.sizeChanged();
380
+ }
381
+ /** Remove an element from the selection set and unhilite it.
382
+ * @returns true if the element was in the SS and was removed.
383
+ */
384
+ drop(el) {
385
+ el.unHilite();
386
+ return this.elements.delete(el) ? (this.sizeChanged(), true) : false;
387
+ }
388
+ /** Replace an entry in the selection set with a different element. */
389
+ replace(oldEl, newEl) {
390
+ if (this.drop(oldEl))
391
+ this.add(newEl);
392
+ }
393
+ deleteAll(undo) {
394
+ undo.performOperation(Markup_1.MarkupApp.getActionName("delete"), () => this.elements.forEach((el) => {
395
+ undo.onDelete(el);
396
+ el.remove();
397
+ }));
398
+ this.emptyAll();
399
+ }
400
+ groupAll(undo) {
401
+ if (this.size < 2)
402
+ return;
403
+ const first = this.elements.values().next().value;
404
+ const parent = first.parent();
405
+ const group = parent.group();
406
+ const ordered = [];
407
+ this.elements.forEach((el) => ordered.push(el));
408
+ ordered.sort((lhs, rhs) => parent.index(lhs) - parent.index(rhs)); // Preserve relative z ordering
409
+ undo.performOperation(Markup_1.MarkupApp.getActionName("group"), () => {
410
+ ordered.forEach((el) => {
411
+ const oldParent = el.parent();
412
+ const oldPos = el.position();
413
+ el.unHilite();
414
+ undo.onRepositioned(el.addTo(group), oldPos, oldParent);
415
+ }), undo.onAdded(group);
416
+ });
417
+ this.restart(group);
418
+ }
419
+ ungroupAll(undo) {
420
+ const groups = new Set();
421
+ this.elements.forEach((el) => {
422
+ if (el instanceof svg_js_1.G)
423
+ groups.add(el);
424
+ });
425
+ if (0 === groups.size)
426
+ return;
427
+ undo.performOperation(Markup_1.MarkupApp.getActionName("ungroup"), () => {
428
+ groups.forEach((g) => {
429
+ g.unHilite();
430
+ this.elements.delete(g);
431
+ undo.onDelete(g);
432
+ g.each((index, children) => {
433
+ const child = children[index];
434
+ const oldPos = child.position();
435
+ child.toParent(g.parent());
436
+ undo.onRepositioned(child, oldPos, g);
437
+ }, false);
438
+ g.untransform(); // Don't want undo of ungroup to push the current group transform...
439
+ g.remove();
440
+ });
441
+ });
442
+ this.sizeChanged();
443
+ }
444
+ /** Move all of the entries to a new position in the DOM via a callback. */
445
+ reposition(cmdName, undo, fn) {
446
+ undo.performOperation(cmdName, () => this.elements.forEach((el) => {
447
+ const oldParent = el.parent();
448
+ const oldPos = el.position();
449
+ fn(el);
450
+ undo.onRepositioned(el, oldPos, oldParent);
451
+ }));
452
+ this.sizeChanged();
453
+ }
454
+ }
455
+ exports.MarkupSelected = MarkupSelected;
456
+ /** Provides UI for selection, delete, move, copy, bring-to-front, send-to-back, etc. for Markup SVG elements
457
+ * @public
458
+ */
459
+ class SelectTool extends MarkupTool_1.MarkupTool {
460
+ constructor() {
461
+ super(...arguments);
462
+ this._dragging = [];
463
+ this._isBoxSelect = false;
464
+ }
465
+ get flashedElement() { return this._flashedElement; }
466
+ set flashedElement(el) {
467
+ if (el === this._flashedElement)
468
+ return;
469
+ if (undefined !== this._flashedElement)
470
+ this._flashedElement.unFlash();
471
+ if (undefined !== el)
472
+ el.flash();
473
+ this._flashedElement = el;
474
+ }
475
+ unflashSelected() {
476
+ if (undefined !== this._flashedElement && this.markup.selected.has(this._flashedElement))
477
+ this.flashedElement = undefined;
478
+ }
479
+ initSelect() {
480
+ this.markup.setCursor("default");
481
+ this.markup.enablePick();
482
+ this.flashedElement = undefined;
483
+ this.boxSelectInit();
484
+ }
485
+ clearSelect() {
486
+ this.cancelDrag();
487
+ this.markup.selected.emptyAll();
488
+ }
489
+ async onCleanup() { this.clearSelect(); }
490
+ async onPostInstall() {
491
+ this.initSelect();
492
+ return super.onPostInstall();
493
+ }
494
+ async onRestartTool() { this.initSelect(); }
495
+ showPrompt() {
496
+ const mainInstruction = core_frontend_1.ToolAssistance.createInstruction(this.iconSpec, core_frontend_1.IModelApp.localization.getLocalizedString(`${MarkupTool_1.MarkupTool.toolKey}Select.Prompts.IdentifyMarkup`));
497
+ const mouseInstructions = [];
498
+ const touchInstructions = [];
499
+ const acceptMsg = core_frontend_1.IModelApp.localization.getLocalizedString(`${MarkupTool_1.MarkupTool.toolKey}Select.Prompts.AcceptMarkup`);
500
+ touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.OneTouchTap, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Touch));
501
+ mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.LeftClick, acceptMsg, false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
502
+ touchInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.OneTouchDrag, core_frontend_1.CoreTools.translate("ElementSet.Inputs.BoxCorners"), false, core_frontend_1.ToolAssistanceInputMethod.Touch));
503
+ mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.LeftClickDrag, core_frontend_1.CoreTools.translate("ElementSet.Inputs.BoxCorners"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
504
+ mouseInstructions.push(core_frontend_1.ToolAssistance.createModifierKeyInstruction(core_frontend_1.ToolAssistance.shiftKey, core_frontend_1.ToolAssistanceImage.LeftClickDrag, core_frontend_1.CoreTools.translate("ElementSet.Inputs.OverlapSelection"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
505
+ mouseInstructions.push(core_frontend_1.ToolAssistance.createModifierKeyInstruction(core_frontend_1.ToolAssistance.ctrlKey, core_frontend_1.ToolAssistanceImage.LeftClick, core_frontend_1.CoreTools.translate("ElementSet.Inputs.InvertSelection"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
506
+ mouseInstructions.push(core_frontend_1.ToolAssistance.createInstruction(core_frontend_1.ToolAssistanceImage.CursorClick, core_frontend_1.CoreTools.translate("ElementSet.Inputs.ClearSelection"), false, core_frontend_1.ToolAssistanceInputMethod.Mouse));
507
+ const sections = [];
508
+ sections.push(core_frontend_1.ToolAssistance.createSection(mouseInstructions, core_frontend_1.ToolAssistance.inputsLabel));
509
+ sections.push(core_frontend_1.ToolAssistance.createSection(touchInstructions, core_frontend_1.ToolAssistance.inputsLabel));
510
+ const instructions = core_frontend_1.ToolAssistance.createInstructions(mainInstruction, sections);
511
+ core_frontend_1.IModelApp.notifications.setToolAssistance(instructions);
512
+ }
513
+ /** When we start a drag operation, we add a new set of elements to the DOM and start modifying them.
514
+ * If we cancel the operation, we need remove them from the DOM.
515
+ */
516
+ cancelDrag() {
517
+ this._dragging.forEach((el) => el.remove()); // remove temporary elements from DOM
518
+ this._dragging.length = 0;
519
+ this.boxSelectInit();
520
+ }
521
+ async onResetButtonUp(_ev) {
522
+ const selected = this.markup.selected;
523
+ const handles = selected.handles;
524
+ if (handles && handles.dragging)
525
+ handles.cancelDrag();
526
+ this.cancelDrag();
527
+ selected.sizeChanged();
528
+ return core_frontend_1.EventHandled.Yes;
529
+ }
530
+ /** Called when there is a mouse "click" (down+up without any motion) */
531
+ async onDataButtonUp(ev) {
532
+ const markup = this.markup;
533
+ const selected = markup.selected;
534
+ const handles = selected.handles;
535
+ if (handles) {
536
+ if (handles.dragging)
537
+ return handles.endDrag(markup.undo);
538
+ if (handles.active) { // clicked on a handle
539
+ if (ev.isControlKey)
540
+ selected.drop(handles.el);
541
+ else
542
+ await handles.active.onClick(ev);
543
+ handles.active = undefined;
544
+ return core_frontend_1.EventHandled.Yes;
545
+ }
546
+ }
547
+ const el = this.flashedElement = this.pickElement(ev.viewPoint);
548
+ if (ev.isControlKey) {
549
+ if (el && selected.drop(el))
550
+ return core_frontend_1.EventHandled.Yes;
551
+ }
552
+ else {
553
+ selected.emptyAll();
554
+ }
555
+ if (el !== undefined)
556
+ selected.add(el);
557
+ return core_frontend_1.EventHandled.Yes;
558
+ }
559
+ async onTouchTap(ev) {
560
+ // Allow tap with a second touch point to multiselect (similar functionality to control being held with mouse click).
561
+ if (ev.isSingleTap && 2 === ev.touchEvent.touches.length) {
562
+ const el = this.flashedElement = this.pickElement(ev.viewPoint);
563
+ if (el) {
564
+ const selected = this.markup.selected;
565
+ if (!selected.drop(el))
566
+ selected.add(el);
567
+ return core_frontend_1.EventHandled.Yes;
568
+ }
569
+ }
570
+ return super.onTouchTap(ev);
571
+ }
572
+ boxSelectInit() {
573
+ this._isBoxSelect = false;
574
+ this.markup.svgDynamics.clear();
575
+ }
576
+ boxSelectStart(ev) {
577
+ if (!ev.isControlKey)
578
+ this.markup.selected.emptyAll();
579
+ this._anchorPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
580
+ this._isBoxSelect = true;
581
+ return true;
582
+ }
583
+ boxSelect(ev, isDynamics) {
584
+ if (!this._isBoxSelect)
585
+ return false;
586
+ const start = this._anchorPt;
587
+ const end = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
588
+ const vec = start.vectorTo(end);
589
+ const width = Math.abs(vec.x);
590
+ const height = Math.abs(vec.y);
591
+ if (width < 1 || height < 1)
592
+ return true;
593
+ const rightToLeft = (start.x > end.x);
594
+ const overlapMode = (ev.isShiftKey ? !rightToLeft : rightToLeft); // Shift inverts inside/overlap selection...
595
+ const offset = core_geometry_1.Point3d.create(vec.x < 0 ? end.x : start.x, vec.y < 0 ? end.y : start.y); // define location by corner points...
596
+ this.markup.svgDynamics.clear();
597
+ this.markup.svgDynamics.rect(width, height).move(offset.x, offset.y).css({ "stroke-width": 1, "stroke": "black", "stroke-opacity": 0.5, "fill": "lightBlue", "fill-opacity": 0.2 });
598
+ const selectBox = this.markup.svgDynamics.rect(width, height).move(offset.x, offset.y).css({ "stroke-width": 1, "stroke": "white", "stroke-opacity": 1.0, "stroke-dasharray": overlapMode ? "5" : "2", "fill": "none" });
599
+ const outlinesG = isDynamics ? this.markup.svgDynamics.group() : undefined;
600
+ const selectRect = selectBox.node.getBoundingClientRect();
601
+ this.markup.svgMarkup.forElementsOfGroup((child) => {
602
+ const childRect = child.node.getBoundingClientRect();
603
+ const inside = (childRect.left >= selectRect.left && childRect.top >= selectRect.top && childRect.right <= selectRect.right && childRect.bottom <= selectRect.bottom);
604
+ const overlap = !inside && (childRect.left < selectRect.right && childRect.right > selectRect.left && childRect.bottom > selectRect.top && childRect.top < selectRect.bottom);
605
+ const accept = inside || (overlap && overlapMode);
606
+ if (undefined !== outlinesG) {
607
+ if (inside || overlap) {
608
+ const outline = child.getOutline().attr(Markup_1.MarkupApp.props.handles.moveOutline).addTo(outlinesG);
609
+ if (accept)
610
+ outline.attr({ "fill": Markup_1.MarkupApp.props.hilite.flash, "fill-opacity": 0.2 });
611
+ }
612
+ }
613
+ else if (accept) {
614
+ this.markup.selected.add(child);
615
+ }
616
+ });
617
+ if (!isDynamics)
618
+ this.boxSelectInit();
619
+ return true;
620
+ }
621
+ /** called when the mouse moves while the data button is down. */
622
+ async onMouseStartDrag(ev) {
623
+ if (core_frontend_1.BeButton.Data !== ev.button)
624
+ return core_frontend_1.EventHandled.No;
625
+ const markup = this.markup;
626
+ const selected = markup.selected;
627
+ const handles = selected.handles;
628
+ if (handles && handles.active) {
629
+ this.flashedElement = undefined; // make sure there are no elements flashed while dragging
630
+ return handles.startDrag(ev);
631
+ }
632
+ const flashed = this.flashedElement = this.pickElement(ev.viewPoint);
633
+ if (undefined === flashed)
634
+ return this.boxSelectStart(ev) ? core_frontend_1.EventHandled.Yes : core_frontend_1.EventHandled.No;
635
+ if (!selected.has(flashed))
636
+ selected.restart(flashed); // we clicked on an element not in the selection set, replace current selection with just this element
637
+ selected.clearEditors();
638
+ this._anchorPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint); // save the starting point. This is the point where the "down" occurred.
639
+ this.cancelDrag();
640
+ selected.elements.forEach((el) => {
641
+ const cloned = el.cloneMarkup(); // make a clone of this element
642
+ el.after(cloned); // put it into the DOM after its original
643
+ cloned.originalEl = el; // save original element so we can remove it if this is a "move" command
644
+ this._dragging.push(cloned); // add to dragging set
645
+ });
646
+ return core_frontend_1.EventHandled.Yes;
647
+ }
648
+ /** Called whenever the mouse moves while this tool is active. */
649
+ async onMouseMotion(ev) {
650
+ const markup = this.markup;
651
+ const handles = markup.selected.handles;
652
+ if (handles && handles.dragging) {
653
+ this.receivedDownEvent = true; // necessary to tell ToolAdmin to send us the button up event
654
+ return handles.drag(ev); // drag the handle
655
+ }
656
+ if (this._dragging.length === 0) {
657
+ if (this.boxSelect(ev, true))
658
+ return;
659
+ if (core_frontend_1.InputSource.Touch !== ev.inputSource)
660
+ this.flashedElement = this.pickElement(ev.viewPoint); // if we're not dragging, try to find an element under the cursor
661
+ return;
662
+ }
663
+ // we have a set of elements being dragged. NOTE: coordinates are viewbox
664
+ const vbPt = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint);
665
+ const delta = vbPt.minus(this._anchorPt);
666
+ this._dragging.forEach((el) => el.translate(delta.x, delta.y));
667
+ this._anchorPt = vbPt; // translate moves from last mouse location
668
+ }
669
+ /** Called when the mouse goes up after dragging. */
670
+ async onMouseEndDrag(ev) {
671
+ const markup = this.markup;
672
+ const selected = markup.selected;
673
+ const handles = selected.handles;
674
+ if (handles && handles.dragging) // if we have handles up, and if they're in the "dragging" state, send the event to them
675
+ return handles.endDrag(markup.undo);
676
+ if (this._dragging.length === 0)
677
+ return this.boxSelect(ev, false) ? core_frontend_1.EventHandled.Yes : core_frontend_1.EventHandled.No;
678
+ // NOTE: all units should be in viewbox coordinates
679
+ const delta = Markup_1.MarkupApp.convertVpToVb(ev.viewPoint).minus(this._anchorPt);
680
+ const undo = markup.undo;
681
+ if (ev.isShiftKey) // shift key means "add to existing," otherwise new selection replaces old
682
+ selected.emptyAll();
683
+ // move or copy all of the elements in dragged set
684
+ undo.performOperation(Markup_1.MarkupApp.getActionName("copy"), () => this._dragging.forEach((el) => {
685
+ el.translate(delta.x, delta.y); // move to final location
686
+ const original = el.originalEl; // save original element
687
+ el.originalEl = undefined; // clear original element
688
+ if (ev.isShiftKey) {
689
+ selected.add(el);
690
+ undo.onAdded(el); // shift key means copy element
691
+ }
692
+ else {
693
+ original.replace(el);
694
+ undo.onModified(el, original);
695
+ }
696
+ }));
697
+ this._dragging.length = 0; // empty dragging set
698
+ selected.sizeChanged(); // notify that size of selection set changed
699
+ return core_frontend_1.EventHandled.Yes;
700
+ }
701
+ /** called when a modifier key is pressed or released. Updates stretch handles, if present */
702
+ async onModifierKeyTransition(_wentDown, modifier, _event) {
703
+ if (modifier !== core_frontend_1.BeModifierKeys.Shift) // we only care about the shift key
704
+ return core_frontend_1.EventHandled.No;
705
+ const selected = this.markup.selected;
706
+ const handles = selected.handles;
707
+ if (undefined === handles || !handles.dragging) // and only if we're currently dragging
708
+ return core_frontend_1.EventHandled.No;
709
+ const ev = new core_frontend_1.BeButtonEvent(); // we need to simulate a mouse motion by sending a drag event at the last cursor position
710
+ core_frontend_1.IModelApp.toolAdmin.fillEventFromCursorLocation(ev);
711
+ return (undefined === ev.viewport) ? core_frontend_1.EventHandled.No : (handles.drag(ev), core_frontend_1.EventHandled.Yes);
712
+ }
713
+ /** called whenever a key is pressed while this tool is active. */
714
+ async onKeyTransition(wentDown, key) {
715
+ if (!wentDown)
716
+ return core_frontend_1.EventHandled.No;
717
+ const markup = this.markup;
718
+ switch (key.key.toLowerCase()) {
719
+ case "delete": // delete key or backspace = delete current selection set
720
+ case "backspace":
721
+ this.unflashSelected();
722
+ markup.deleteSelected();
723
+ return core_frontend_1.EventHandled.Yes;
724
+ case "escape": // esc = cancel current operation
725
+ await this.exitTool();
726
+ return core_frontend_1.EventHandled.Yes;
727
+ case "b": // alt-shift-b = send to back
728
+ return (key.altKey && key.shiftKey) ? (markup.sendToBack(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
729
+ case "f": // alt-shift-f = bring to front
730
+ return (key.altKey && key.shiftKey) ? (markup.bringToFront(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
731
+ case "g": // ctrl-g = create group
732
+ return (key.ctrlKey) ? (this.unflashSelected(), markup.groupSelected(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
733
+ case "u": // ctrl-u = ungroup
734
+ return (key.ctrlKey) ? (this.unflashSelected(), markup.ungroupSelected(), core_frontend_1.EventHandled.Yes) : core_frontend_1.EventHandled.No;
735
+ }
736
+ return core_frontend_1.EventHandled.No;
737
+ }
738
+ }
739
+ SelectTool.toolId = "Markup.Select";
740
+ SelectTool.iconSpec = "icon-cursor";
741
+ exports.SelectTool = SelectTool;
742
742
  //# sourceMappingURL=SelectTool.js.map