@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.
- package/CHANGELOG.md +41 -1
- package/lib/cjs/Markup.d.ts +323 -310
- package/lib/cjs/Markup.d.ts.map +1 -1
- package/lib/cjs/Markup.js +451 -420
- package/lib/cjs/Markup.js.map +1 -1
- package/lib/cjs/MarkupTool.d.ts +38 -38
- package/lib/cjs/MarkupTool.js +88 -88
- package/lib/cjs/MarkupTool.js.map +1 -1
- package/lib/cjs/RedlineTool.d.ts +145 -145
- package/lib/cjs/RedlineTool.d.ts.map +1 -1
- package/lib/cjs/RedlineTool.js +498 -512
- package/lib/cjs/RedlineTool.js.map +1 -1
- package/lib/cjs/SelectTool.d.ts +126 -126
- package/lib/cjs/SelectTool.js +741 -741
- package/lib/cjs/SelectTool.js.map +1 -1
- package/lib/cjs/SvgJsExt.d.ts +85 -85
- package/lib/cjs/SvgJsExt.js +185 -185
- package/lib/cjs/TextEdit.d.ts +43 -43
- package/lib/cjs/TextEdit.js +196 -196
- package/lib/cjs/TextEdit.js.map +1 -1
- package/lib/cjs/Undo.d.ts +46 -46
- package/lib/cjs/Undo.js +168 -168
- package/lib/cjs/core-markup.d.ts +18 -18
- package/lib/cjs/core-markup.js +38 -34
- package/lib/cjs/core-markup.js.map +1 -1
- package/lib/esm/Markup.d.ts +323 -310
- package/lib/esm/Markup.d.ts.map +1 -1
- package/lib/esm/Markup.js +447 -415
- package/lib/esm/Markup.js.map +1 -1
- package/lib/esm/MarkupTool.d.ts +38 -38
- package/lib/esm/MarkupTool.js +85 -84
- package/lib/esm/MarkupTool.js.map +1 -1
- package/lib/esm/RedlineTool.d.ts +145 -145
- package/lib/esm/RedlineTool.d.ts.map +1 -1
- package/lib/esm/RedlineTool.js +494 -498
- package/lib/esm/RedlineTool.js.map +1 -1
- package/lib/esm/SelectTool.d.ts +126 -126
- package/lib/esm/SelectTool.js +735 -734
- package/lib/esm/SelectTool.js.map +1 -1
- package/lib/esm/SvgJsExt.d.ts +85 -85
- package/lib/esm/SvgJsExt.js +180 -180
- package/lib/esm/TextEdit.d.ts +43 -43
- package/lib/esm/TextEdit.js +193 -191
- package/lib/esm/TextEdit.js.map +1 -1
- package/lib/esm/Undo.d.ts +46 -46
- package/lib/esm/Undo.js +164 -164
- package/lib/esm/core-markup.d.ts +18 -18
- package/lib/esm/core-markup.js +22 -22
- package/package.json +19 -19
package/lib/cjs/SelectTool.js
CHANGED
|
@@ -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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
this.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
|
|
740
|
-
SelectTool.
|
|
741
|
-
SelectTool
|
|
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
|