@quinninc/pixi-transformer 0.0.4 → 0.0.6

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.
@@ -0,0 +1,668 @@
1
+ import { Pixi } from "./pixi";
2
+ import { calcAngleRadians, calcDistance, getDirection, rotatePoint, snapToEdgesAndCorners, } from "./graphics";
3
+ export class Transformer extends Pixi.Container {
4
+ /**
5
+ *
6
+ * @param p.lineColor - border line color
7
+ * @param p.handleColor - handles' fill color
8
+ * @param p.controlsSize - handles' size in pixels
9
+ * @param p.debug - show scaling distance debug lines
10
+ * @param p.generateAnchorMark - a function to generate your custom anchor mark
11
+ *
12
+ * @example
13
+ * const selectTool = new FreeTransformTool({
14
+ * lineColor: 0x66cfff,
15
+ * handleColor: 0xffffff,
16
+ * controlsSize: 10,
17
+ * debug: false,
18
+ * generateAnchorMark: (g) => g.clear().circle(0,0,10).fill(0xff0000); // please use clear() to clear the geometry first every time. leave undefined for default anchor mark. set false to disable.
19
+ * });
20
+ *
21
+ * root.addChild(selectTool); // by default the tool has a z-index of 1e7 (10000000). you can change it according to your requirement
22
+ *
23
+ * selectTool.select(obj);
24
+ *
25
+ * selectTool.unselect();
26
+ */
27
+ constructor({ id = "transformer", lineColor = 0x66cfff, handleColor = 0xffffff, controlsSize = 10, debug = false, generateAnchorMark, } = {}) {
28
+ super();
29
+ // target Pixi.Container
30
+ this.target = null;
31
+ // position state
32
+ this.left = 0;
33
+ this.top = 0;
34
+ this.bottom = 0;
35
+ this.right = 0;
36
+ this._anchor = new Pixi.Point(0, 0);
37
+ this.dragging = false;
38
+ this.onUpdate = () => { };
39
+ const that = this;
40
+ this._id = id;
41
+ this.debug = debug;
42
+ this.lineColor = lineColor;
43
+ this.handleColor = handleColor;
44
+ this.handleOpacity = 1;
45
+ this.controlsSize = controlsSize;
46
+ this.controlsDimOpacity = 1;
47
+ this.controlsStrokeThickness = 2;
48
+ this.movedThreshold = 10;
49
+ if (typeof generateAnchorMark == "function")
50
+ this.generateAnchorMark = generateAnchorMark;
51
+ else if (generateAnchorMark === false)
52
+ this.generateAnchorMark = () => { };
53
+ this.zIndex = 1e7;
54
+ // Pixi.Container Properties
55
+ this.visible = false;
56
+ // create border
57
+ this.border = new Pixi.Graphics({ label: "Border" });
58
+ this.addChild(this.border);
59
+ // Anchor mark
60
+ this.anchorMark = new Pixi.Graphics({
61
+ label: "AnchorMark",
62
+ eventMode: "none",
63
+ });
64
+ this.anchorMark.alpha = this.handleOpacity;
65
+ this.addChild(this.anchorMark);
66
+ // debug indicators
67
+ this.fromPoint = new Pixi.Graphics({
68
+ label: "fromPoint",
69
+ alpha: this.handleOpacity * 0.75,
70
+ });
71
+ this.toPoint = new Pixi.Graphics({
72
+ label: "toPoint",
73
+ alpha: this.handleOpacity * 0.75,
74
+ });
75
+ this.addChild(this.fromPoint, this.toPoint);
76
+ // scaling handles
77
+ this.scaleTHandle = this.createHandle({
78
+ name: "scaleTHandle",
79
+ cursor: "n-resize",
80
+ });
81
+ this.scaleRHandle = this.createHandle({
82
+ name: "scaleRHandle",
83
+ cursor: "w-resize",
84
+ });
85
+ this.scaleBHandle = this.createHandle({
86
+ name: "scaleBHandle",
87
+ cursor: "s-resize",
88
+ });
89
+ this.scaleLHandle = this.createHandle({
90
+ name: "scaleLHandle",
91
+ cursor: "e-resize",
92
+ });
93
+ this.scaleTLHandle = this.createHandle({
94
+ name: "scaleTLHandle",
95
+ cursor: "nw-resize",
96
+ });
97
+ this.scaleTRHandle = this.createHandle({
98
+ name: "scaleTRHandle",
99
+ cursor: "ne-resize",
100
+ });
101
+ this.scaleBRHandle = this.createHandle({
102
+ name: "scaleBRHandle",
103
+ cursor: "se-resize",
104
+ });
105
+ this.scaleBLHandle = this.createHandle({
106
+ name: "scaleBLHandle",
107
+ cursor: "sw-resize",
108
+ });
109
+ // rotation handle
110
+ this.rotateHandle = this.createHandle({
111
+ name: "Rotate",
112
+ cursor: "pointer",
113
+ shape: "circle",
114
+ });
115
+ this.addChild(this.scaleTHandle, this.scaleRHandle, this.scaleBHandle, this.scaleLHandle, this.scaleTLHandle, this.scaleTRHandle, this.scaleBRHandle, this.scaleBLHandle, this.rotateHandle);
116
+ // ------------<MoveTool>---------------
117
+ this.moveHandle = new Pixi.Graphics({ label: this.id });
118
+ this.moveHandle.eventMode = "dynamic";
119
+ this.addToolTip(this.moveHandle, "Move", "move");
120
+ this.moveHandle
121
+ .on("pointerdown", onMoveHandleDown)
122
+ .on("pointerup", onMoveHandleUp)
123
+ .on("pointerupoutside", onMoveHandleUp);
124
+ this.handleHandleEvents(this.moveHandle, this);
125
+ this.moveHandle.hitArea = new Pixi.Rectangle(0, 0, this.controlsSize, this.controlsSize);
126
+ this.addChild(this.moveHandle);
127
+ function changeAnchorPoint(ev) {
128
+ // console.log("TAPP this, that - ", this, that);
129
+ if (!ev.ctrlKey && !ev.metaKey)
130
+ return;
131
+ if (!that.target)
132
+ return;
133
+ const eventPoint = that.convertToLocal(ev.global);
134
+ console.log("Prev anchor - ", that._anchor);
135
+ console.log("ev.position - ", eventPoint.x, eventPoint.y);
136
+ console.log("that.target.position - ", that.target.position.x, that.target.position.y);
137
+ console.log("that.target.pivot - ", that.target.pivot.x, that.target.pivot.y);
138
+ let dx, dy;
139
+ // calc new pivot point
140
+ // step 1 - translate to origin
141
+ dx = eventPoint.x - that.target.position.x;
142
+ dy = eventPoint.y - that.target.position.y;
143
+ // step 2 - rotate by -angle
144
+ const rp = rotatePoint({ x: dx, y: dy }, { x: 0, y: 0 }, -that.target.rotation);
145
+ let rdx = rp.x;
146
+ let rdy = rp.y;
147
+ const sp = { x: rdx, y: rdy };
148
+ const pSnapped = snapToEdgesAndCorners({
149
+ point: sp,
150
+ dim: { width: that.target.width, height: that.target.height },
151
+ anchor: that._anchor,
152
+ });
153
+ // console.log("UP SNAPPING - ", sp, pSnapped);
154
+ // step 3 - scale by 1/scale
155
+ rdx = pSnapped.x / that.target.scale.x;
156
+ rdy = pSnapped.y / that.target.scale.y;
157
+ // console.log("3 - dx, dy - ", dx, dy);
158
+ // step 4 - translate to new pivot
159
+ const x = that.target.pivot.x + rdx;
160
+ const y = that.target.pivot.y + rdy;
161
+ // calc new position
162
+ const rrp = rotatePoint({ x: rdx, y: rdy }, { x: 0, y: 0 }, that.target.rotation);
163
+ rrp.x = rrp.x * that.target.scale.x + that.target.position.x;
164
+ rrp.y = rrp.y * that.target.scale.y + that.target.position.y;
165
+ const posX = rrp.x;
166
+ const posY = rrp.y;
167
+ that.target.pivot.set(x, y);
168
+ that.target.position.set(posX, posY);
169
+ }
170
+ function onMoveHandleDown(downEvent) {
171
+ // console.log("onMoveHandleDown this, that - ", this, that);
172
+ if (!that.target)
173
+ return;
174
+ if (downEvent.ctrlKey || downEvent.metaKey) {
175
+ this.draggingPivot = true;
176
+ }
177
+ // data
178
+ this.targetStartPos = that.target.position.clone();
179
+ this.dragDistance = 0;
180
+ this.on("globalpointermove", onMoveHandleMove);
181
+ }
182
+ function onMoveHandleMove(moveEvent) {
183
+ // console.log("onMoveHandleMove this, that - ", this, that);
184
+ if (!this.dragging || !that.target)
185
+ return;
186
+ const eventPoint = that.convertToLocal(moveEvent.global);
187
+ if (this.draggingPivot) {
188
+ // changing pivot point
189
+ let x = eventPoint.x - that.target.x;
190
+ let y = eventPoint.y - that.target.y;
191
+ const p = rotatePoint({ x, y }, { x: 0, y: 0 }, -that.target.rotation);
192
+ const snappedPoint = snapToEdgesAndCorners({
193
+ point: p,
194
+ dim: {
195
+ width: that.target.width,
196
+ height: that.target.height,
197
+ },
198
+ anchor: that._anchor,
199
+ });
200
+ // console.log("MOVE SNAPPING - ", p, snappedPoint);
201
+ that.anchorMark.position.set(snappedPoint.x / that.target.scale.x, snappedPoint.y / that.target.scale.y);
202
+ return;
203
+ }
204
+ const moveDelta = new Pixi.Point(eventPoint.x - this.eventStartPos.x, eventPoint.y - this.eventStartPos.y);
205
+ that.target.position.x = this.targetStartPos.x + moveDelta.x;
206
+ that.target.position.y = this.targetStartPos.y + moveDelta.y;
207
+ this.dragDistance = calcDistance(eventPoint, this.eventStartPos);
208
+ that.update();
209
+ moveEvent.stopPropagation();
210
+ }
211
+ function onMoveHandleUp(upEvent) {
212
+ // console.log("onMoveHandleUp this, that - ", this, that);
213
+ upEvent.stopPropagation();
214
+ if (this.draggingPivot) {
215
+ changeAnchorPoint(upEvent);
216
+ that.anchorMark.position.set(0, 0);
217
+ }
218
+ this.draggingPivot = false;
219
+ this.off("globalpointermove", onMoveHandleMove);
220
+ }
221
+ // ------------</ MoveTool>---------------
222
+ // ------------<ScaleXYTool>---------------
223
+ for (const handle of [
224
+ this.scaleBRHandle,
225
+ this.scaleTRHandle,
226
+ this.scaleBLHandle,
227
+ this.scaleTLHandle,
228
+ ]) {
229
+ handle
230
+ .on("pointerdown", onScaleXYToolDown)
231
+ .on("pointerupoutside", onScaleXYToolUp)
232
+ .on("pointerup", onScaleXYToolUp);
233
+ }
234
+ function onScaleXYToolDown(downEvent) {
235
+ // console.log("onScaleToolDown this, that - ", this, that);
236
+ if (!that.target)
237
+ return;
238
+ this.targetResolutionStart = that.target.resolution;
239
+ this.on("globalpointermove", onScaleXYToolMove);
240
+ }
241
+ function onScaleXYToolMove(moveEvent) {
242
+ // console.log("onScaleToolMove this, that - ", this, that);
243
+ if (!this.dragging || !that.target)
244
+ return;
245
+ const eventPoint = that.convertToLocal(moveEvent.global);
246
+ const distStart = calcDistance(this.eventStartPos, that.target.position);
247
+ const distEnd = calcDistance(eventPoint, that.target.position);
248
+ const direction = getDirection(that.position, this.eventStartPos, eventPoint);
249
+ this.rescaleFactor = (distEnd / distStart) * direction;
250
+ that.target.scale.x = this.targetStartScale.x * this.rescaleFactor;
251
+ that.target.scale.y = this.targetStartScale.y * this.rescaleFactor;
252
+ that.update();
253
+ }
254
+ function onScaleXYToolUp() {
255
+ // console.log("onScaleToolUp this, that - ", this, that);
256
+ if (that.target) {
257
+ that.target.resolution =
258
+ this.targetResolutionStart * this.rescaleFactor;
259
+ }
260
+ this.off("globalpointermove", onScaleXYToolMove);
261
+ }
262
+ // ------------</ ScaleXYTool>---------------
263
+ // ------------<ScaleYTool>---------------
264
+ for (const handle of [this.scaleTHandle, this.scaleBHandle]) {
265
+ handle
266
+ .on("pointerdown", onScaleYToolDown)
267
+ .on("pointerup", onScaleYToolUp)
268
+ .on("pointerupoutside", onScaleYToolUp);
269
+ }
270
+ function onScaleYToolDown(downEvent) {
271
+ // console.log("onVScaleToolDown this, that - ", this, that);
272
+ if (!that.target)
273
+ return;
274
+ this.on("globalpointermove", onScaleYToolMove);
275
+ }
276
+ function onScaleYToolMove(moveEvent) {
277
+ // console.log("onVScaleToolMove this - ", this);
278
+ if (!this.dragging || !that.target)
279
+ return;
280
+ const eventPoint = that.convertToLocal(moveEvent.global);
281
+ const referencePoint = that.target.position.clone();
282
+ // referencePoint.x = that.target.position.x + that.target.width / 2;
283
+ const distStart = calcDistance(this.eventStartPos, referencePoint);
284
+ const distEnd = calcDistance(eventPoint, referencePoint);
285
+ const direction = getDirection(referencePoint, this.eventStartPos, eventPoint);
286
+ this.rescaleFactor = (distEnd / distStart) * direction;
287
+ that.target.scale.y = this.targetStartScale.y * this.rescaleFactor;
288
+ that.update();
289
+ }
290
+ function onScaleYToolUp(upEvent) {
291
+ // console.log("onVScaleToolUp this, that - ", this, that);
292
+ this.off("globalpointermove", onScaleYToolMove);
293
+ }
294
+ // ------------</ ScaleYTool>---------------
295
+ // ------------<ScaleXTool>---------------
296
+ for (const handle of [this.scaleRHandle, this.scaleLHandle]) {
297
+ handle
298
+ .on("pointerdown", onScaleXToolDown)
299
+ .on("pointerup", onScaleXToolUp)
300
+ .on("pointerupoutside", onScaleXToolUp);
301
+ }
302
+ this.addChild(this.scaleRHandle);
303
+ function onScaleXToolDown(downEvent) {
304
+ // console.log("onHScaleToolDown this, that - ", this, that);
305
+ if (!that.target)
306
+ return;
307
+ this.on("globalpointermove", onScaleXToolMove);
308
+ }
309
+ function onScaleXToolMove(moveEvent) {
310
+ // console.log("onHScaleToolMove this - ", this);
311
+ if (!this.dragging || !that.target)
312
+ return;
313
+ const eventPoint = that.convertToLocal(moveEvent.global);
314
+ const referencePoint = that.target.position.clone();
315
+ // referencePoint.y = that.target.position.y + that.target.height / 2;
316
+ const distStart = calcDistance(this.eventStartPos, referencePoint);
317
+ const distEnd = calcDistance(eventPoint, referencePoint);
318
+ const direction = getDirection(referencePoint, this.eventStartPos, eventPoint);
319
+ const rescaleFactor = (distEnd / distStart) * direction;
320
+ that.target.scale.x = this.targetStartScale.x * rescaleFactor;
321
+ that.update();
322
+ }
323
+ function onScaleXToolUp(upEvent) {
324
+ // console.log("onHScaleToolUp this, that - ", this, that);
325
+ this.off("globalpointermove", onScaleXToolMove);
326
+ }
327
+ // ------------</ ScaleXTool>---------------
328
+ // ------------<RotateTool>---------------
329
+ this.rotateHandle
330
+ .on("pointerdown", onRotateToolDown)
331
+ .on("pointerup", onRotateToolUp)
332
+ .on("pointerupoutside", onRotateToolUp);
333
+ function onRotateToolDown(downEvent) {
334
+ // console.log("onRotateToolDown this, that - ", this, that);
335
+ if (!that.target)
336
+ return;
337
+ this.on("globalpointermove", onRotateToolMove);
338
+ }
339
+ function onRotateToolMove(moveEvent) {
340
+ // console.log("onRotateToolMove this, that - ", this, that);
341
+ if (!this.dragging || !that.target)
342
+ return;
343
+ const eventPoint = that.convertToLocal(moveEvent.global);
344
+ // the drag point is relative to the display object x,y position on the stage (it's registration point)
345
+ const relativeStartPoint = {
346
+ x: this.eventStartPos.x - that.target.x,
347
+ y: this.eventStartPos.y - that.target.y,
348
+ };
349
+ const relativeEndPoint = {
350
+ x: eventPoint.x - that.target.x,
351
+ y: eventPoint.y - that.target.y,
352
+ };
353
+ const startAngle = calcAngleRadians(relativeStartPoint.x, relativeStartPoint.y);
354
+ const endAngle = calcAngleRadians(relativeEndPoint.x, relativeEndPoint.y);
355
+ const deltaAngle = endAngle - startAngle;
356
+ let finalRotation = this.targetStartRotation + deltaAngle;
357
+ if (moveEvent.ctrlKey || moveEvent.metaKey) {
358
+ const snapAngle = 45;
359
+ let finalAngle = (finalRotation * 180) / Math.PI;
360
+ const proximity = Math.min(Math.abs(finalAngle % snapAngle), snapAngle - Math.abs(finalAngle % snapAngle));
361
+ const snapProximity = 6;
362
+ if (proximity < snapProximity) {
363
+ finalAngle = Math.round(finalAngle / snapAngle) * snapAngle;
364
+ finalRotation = (finalAngle * Math.PI) / 180;
365
+ }
366
+ }
367
+ that.target.rotation = finalRotation;
368
+ that.update();
369
+ }
370
+ function onRotateToolUp(upEvent) {
371
+ // console.log("onRotateToolUp this, that - ", this, that);
372
+ this.off("globalpointermove", onRotateToolMove);
373
+ }
374
+ // ------------</ RotateTool>---------------
375
+ }
376
+ addToolTip(shape, name, cursor) {
377
+ shape.on("pointerover", () => {
378
+ if (!this.dragging) {
379
+ this.setTitle(name);
380
+ this.setCursor(cursor);
381
+ }
382
+ });
383
+ shape.on("pointerout", () => {
384
+ if (!this.dragging) {
385
+ this.setTitle("");
386
+ this.setCursor("default");
387
+ }
388
+ });
389
+ shape.on("pointerupoutside", () => {
390
+ this.setTitle("");
391
+ this.setCursor("default");
392
+ });
393
+ }
394
+ handleHandleEvents(handle, that) {
395
+ // console.log("handleHandleEvents this, handle - ", this, handle.label);
396
+ function onHandleDown(e) {
397
+ // console.log("onHandleDown this - ", this);
398
+ that.dragging = true;
399
+ handle.dragging = true;
400
+ handle.eventStartPos = that.convertToLocal(e.global.clone());
401
+ handle.targetStartScale = that.target.scale.clone();
402
+ handle.targetStartRotation = that.target.rotation;
403
+ handle.on("globalpointermove", onHandleMove);
404
+ // graphic - fromPoint
405
+ if (that.debug) {
406
+ that.createHandleIndicatorTo(that.fromPoint, handle.eventStartPos.x, handle.eventStartPos.y);
407
+ }
408
+ }
409
+ function onHandleMove(e) {
410
+ // console.log("onHandleMove this - ", this);
411
+ const eventPoint = that.convertToLocal(e.global.clone());
412
+ if (that.debug) {
413
+ that.createHandleIndicatorTo(that.toPoint, eventPoint.x, eventPoint.y);
414
+ that.deScale(that.fromPoint);
415
+ }
416
+ }
417
+ function onHandleUp() {
418
+ // console.log("onHandleUp this - ", this);
419
+ that.update();
420
+ handle.off("globalpointermove", onHandleMove);
421
+ if (that.debug) {
422
+ that.fromPoint.clear();
423
+ that.toPoint.clear();
424
+ }
425
+ handle.dragging = false;
426
+ that.dragging = false;
427
+ }
428
+ handle
429
+ .on("pointerdown", onHandleDown)
430
+ .on("pointerup", onHandleUp)
431
+ .on("pointerupoutside", onHandleUp);
432
+ }
433
+ createHandle({ cursor, name, shape = "square", }) {
434
+ const handle = new Pixi.Graphics();
435
+ handle.label = this.id;
436
+ handle.eventMode = "dynamic";
437
+ handle.alpha = this.handleOpacity;
438
+ this.addToolTip(handle, name, cursor);
439
+ if (shape === "circle") {
440
+ handle.ellipse(this.controlsSize / 2, this.controlsSize / 2, this.controlsSize / 2, this.controlsSize / 2);
441
+ }
442
+ else
443
+ handle.rect(0, 0, this.controlsSize, this.controlsSize);
444
+ handle
445
+ .stroke({ width: this.controlsStrokeThickness, color: this.lineColor })
446
+ .fill(this.handleColor);
447
+ handle.pivot.set(this.controlsSize / 2, this.controlsSize / 2);
448
+ this.handleHandleEvents(handle, this);
449
+ return handle;
450
+ }
451
+ createHandleIndicatorTo(handle, x, y) {
452
+ x = x - this.position.x;
453
+ y = y - this.position.y;
454
+ const p1 = new Pixi.Point(x, y);
455
+ const origin = new Pixi.Point(0, 0);
456
+ const p = rotatePoint(p1, origin, -this.rotation);
457
+ x = p.x;
458
+ y = p.y;
459
+ const size = this.controlsSize * 1.25;
460
+ handle
461
+ .clear()
462
+ .moveTo(0, 0)
463
+ .lineTo(x, y)
464
+ .stroke({ width: 5, color: 0xffffff })
465
+ .rect(x - size / 2, y - size / 2, size, size)
466
+ .fill(0xffffff);
467
+ this.deScale(handle);
468
+ }
469
+ deScale(target) {
470
+ target.scale.set(1 / this.scale.x, 1 / this.scale.y);
471
+ }
472
+ // public methods:
473
+ select(target) {
474
+ if (!target) {
475
+ this.unselect();
476
+ return;
477
+ }
478
+ // console.log("select this - ", this);
479
+ this.target = target;
480
+ let _anchor;
481
+ if (this.target.pivot) {
482
+ // console.log("target has pivot", this.target.pivot);
483
+ // when you set a width of 100 on a sprites with image with actual width of 500, the sprite container is scaled down to 0.2 by default
484
+ _anchor = new Pixi.Point(this.target.pivot.x / Math.abs(this.target.width / this.target.scale.x), this.target.pivot.y /
485
+ Math.abs(this.target.height / this.target.scale.y));
486
+ }
487
+ else {
488
+ // console.log("no-pivot|no-anchor", this.target.pivot);
489
+ _anchor = new Pixi.Point(0.5, 0.5);
490
+ }
491
+ this._anchor = _anchor;
492
+ if (this.parent !== this.target.parent) {
493
+ // console.log("Different parent - switching transformer location position");
494
+ this.target.parent.addChild(this);
495
+ }
496
+ // console.log("Anchor - ", _anchor);
497
+ this.update();
498
+ this.visible = true;
499
+ }
500
+ unselect() {
501
+ this.target = null;
502
+ this.visible = false;
503
+ this.onUpdate();
504
+ }
505
+ update() {
506
+ if (!this.target) {
507
+ console.log("no target, returning...");
508
+ return;
509
+ }
510
+ this.onUpdate();
511
+ // copy object translation/transformation
512
+ const bounds = this.target.getLocalBounds();
513
+ this.width = bounds.width;
514
+ this.height = bounds.height;
515
+ this.scale.x = this.target.scale.x;
516
+ this.scale.y = this.target.scale.y;
517
+ this.x = this.target.x;
518
+ this.y = this.target.y;
519
+ this.rotation = this.target.rotation;
520
+ let _anchor;
521
+ if (this.target.pivot) {
522
+ // console.log("target has pivot", this.target.pivot);
523
+ _anchor = new Pixi.Point(this.target.pivot.x / Math.abs(this.target.width / this.scale.x), this.target.pivot.y / Math.abs(this.target.height / this.scale.y));
524
+ }
525
+ else {
526
+ _anchor = this._anchor || new Pixi.Point(0.5, 0.5);
527
+ }
528
+ this._anchor = _anchor;
529
+ // console.log("Anchor - ", _anchor);
530
+ this.left = -bounds.width * _anchor.x;
531
+ this.top = -bounds.height * _anchor.y;
532
+ this.bottom = bounds.height * (1 - _anchor.y);
533
+ this.right = bounds.width * (1 - _anchor.x);
534
+ // anchor mark
535
+ this.generateAnchorMark?.(this.anchorMark);
536
+ this.deScale(this.anchorMark);
537
+ // this.anchorMark!.rotation = -this.rotation;
538
+ const rotationHandlePos = {
539
+ x: (this.left + this.right) / 2,
540
+ y: this.top - 80 / Math.abs(this.scale.y),
541
+ };
542
+ // borders
543
+ this.border
544
+ .clear()
545
+ .moveTo(this.left, this.top)
546
+ .lineTo(this.right, this.top)
547
+ .moveTo(this.left, this.bottom)
548
+ .lineTo(this.right, this.bottom)
549
+ .stroke({
550
+ width: this.controlsStrokeThickness / this.scale.y,
551
+ color: this.lineColor,
552
+ })
553
+ .moveTo(this.left, this.top)
554
+ .lineTo(this.left, this.bottom)
555
+ .moveTo(this.right, this.top)
556
+ .lineTo(this.right, this.bottom)
557
+ .stroke({
558
+ width: this.controlsStrokeThickness / this.scale.x,
559
+ color: this.lineColor,
560
+ })
561
+ .moveTo(rotationHandlePos.x, this.top)
562
+ .lineTo(rotationHandlePos.x, rotationHandlePos.y)
563
+ .stroke({
564
+ width: this.controlsStrokeThickness / this.scale.x,
565
+ color: this.lineColor,
566
+ });
567
+ // draw move hit area
568
+ let anchorRectPoints = [];
569
+ const isAnchorOutside = this._anchor.x < 0 ||
570
+ this._anchor.y < 0 ||
571
+ this._anchor.x > 1 ||
572
+ this._anchor.y > 1;
573
+ if (isAnchorOutside) {
574
+ anchorRectPoints = [
575
+ new Pixi.Point(-this.controlsSize, -this.controlsSize),
576
+ new Pixi.Point(-this.controlsSize, this.controlsSize),
577
+ new Pixi.Point(this.controlsSize, this.controlsSize),
578
+ new Pixi.Point(this.controlsSize, -this.controlsSize),
579
+ new Pixi.Point(-this.controlsSize, -this.controlsSize),
580
+ ];
581
+ }
582
+ this.moveHandle.hitArea = new Pixi.Polygon(new Pixi.Point(this.left, this.top), new Pixi.Point(this.right, this.top), new Pixi.Point(this.right, this.bottom), new Pixi.Point(this.left, this.bottom), new Pixi.Point(this.left, this.top), ...anchorRectPoints);
583
+ this.scaleTLHandle.position.set(this.left, this.top);
584
+ this.scaleTRHandle.position.set(this.right, this.top);
585
+ this.scaleBRHandle.position.set(this.right, this.bottom);
586
+ this.scaleBLHandle.position.set(this.left, this.bottom);
587
+ this.scaleTHandle.position.set(this.left + bounds.width / 2, this.top);
588
+ this.scaleRHandle.position.set(this.right, this.top + bounds.height / 2);
589
+ this.scaleBHandle.position.set(this.left + bounds.width / 2, this.bottom);
590
+ this.scaleLHandle.position.set(this.left, this.top + bounds.height / 2);
591
+ this.rotateHandle.position.set(rotationHandlePos.x, rotationHandlePos.y);
592
+ const allHandles = [
593
+ this.scaleTLHandle,
594
+ this.scaleTRHandle,
595
+ this.scaleBRHandle,
596
+ this.scaleBLHandle,
597
+ this.scaleTHandle,
598
+ this.scaleRHandle,
599
+ this.scaleBHandle,
600
+ this.scaleLHandle,
601
+ this.rotateHandle,
602
+ ];
603
+ // descale all handles
604
+ allHandles.forEach((handle) => {
605
+ this.deScale(handle);
606
+ });
607
+ }
608
+ setTitle(title) {
609
+ title = title || "";
610
+ this.accessibleTitle = title;
611
+ }
612
+ setCursor(cursor) {
613
+ const cursors = [
614
+ "e-resize",
615
+ "se-resize",
616
+ "s-resize",
617
+ "sw-resize",
618
+ "w-resize",
619
+ "nw-resize",
620
+ "n-resize",
621
+ "ne-resize",
622
+ ];
623
+ const index = cursors.indexOf(cursor);
624
+ if (index >= 0) {
625
+ const angle = 45;
626
+ let rotation = this.target.angle;
627
+ rotation = rotation + angle / 2;
628
+ let newIndex = index + Math.floor(rotation / angle);
629
+ newIndex = newIndex % cursors.length;
630
+ document.body.style.cursor = cursors[newIndex];
631
+ }
632
+ else {
633
+ document.body.style.cursor = cursor;
634
+ }
635
+ }
636
+ generateAnchorMark(graphic) {
637
+ const scale = 2;
638
+ const anchorInnerRadius = this.controlsSize * 0.125 * scale;
639
+ const anchorOuterRadius = this.controlsSize * 0.625 * scale;
640
+ graphic
641
+ .clear()
642
+ .circle(0, 0, this.controlsSize * 0.375 * scale)
643
+ .moveTo(0, anchorInnerRadius)
644
+ .lineTo(0, anchorOuterRadius)
645
+ .moveTo(-anchorInnerRadius, 0)
646
+ .lineTo(-anchorOuterRadius, 0)
647
+ .moveTo(0, -anchorInnerRadius)
648
+ .lineTo(0, -anchorOuterRadius)
649
+ .moveTo(anchorInnerRadius, 0)
650
+ .lineTo(anchorOuterRadius, 0)
651
+ .fill(0xff0000)
652
+ .stroke(0x111111);
653
+ }
654
+ get _target() {
655
+ return this.target;
656
+ }
657
+ get id() {
658
+ return this._id;
659
+ }
660
+ cleanup() { }
661
+ convertToLocal(point) {
662
+ if (this.target) {
663
+ const p = this.target.parent.toLocal(point);
664
+ return p;
665
+ }
666
+ return point;
667
+ }
668
+ }