@quinninc/pixi-transformer 0.0.5 → 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.
package/README.md CHANGED
@@ -1,61 +1,61 @@
1
- # Free Transform Tool for PIXI.js
2
-
3
- Works with pixi.js v8 and above.
4
-
5
- ![preview](https://cdn.quinn.live/transformer-image.png)
6
-
7
- ### ✨ Features
8
-
9
- - Translate - Drag to move anywhere
10
- - Scale - Scale on X, Y or both axis'
11
- - Rotate - drag to rotate
12
- - Anchor point - update anchor point of transforms (Ctrl + Click)
13
- - Snapping - hold ctrl to snap rotation at 45degrees, snap anchor point to edges and corners.
14
- - Theming - custom border color, control size and color, and anchor point graphic
15
-
16
- > Note - all transformations are applied wrt the pivot point, update pivot point to achieve the desired result.
17
-
18
- ## Installation
19
-
20
- ```bash
21
- npm install @quinninc/pixi-transformer
22
- ```
23
-
24
- ## Usage
25
-
26
- ```tsx
27
- const root = new Container();
28
- app.stage.addChild(root);
29
-
30
- // create tool and add to stage
31
- const selectTool = new FreeTransformTool();
32
- selectTool.label = "transform-tool";
33
- root.addChild(selectTool);
34
-
35
- const obj = new Sprite({
36
- texture: Texture.from("lenna"),
37
- label: "lenna",
38
- eventMode: "static",
39
- });
40
-
41
- root.addChild(obj);
42
-
43
- obj.on("pointertap", (ev) => {
44
- console.log("select - ", ev.currentTarget.label);
45
- selectTool.select(ev.currentTarget);
46
- });
47
- ```
48
-
49
- ## API
50
-
51
- ### `FreeTransformTool` (class)
52
-
53
- **Constructor Parameter Object:**
54
-
55
- - `lineColor` - border line color. default - `0x66cfff`
56
- - `handleColor` - (`number`) - handles' fill color. default - `0xffffff`
57
- - `controlsSize` - (`number`) - handles' size in pixels. default - `10`
58
- - `debug` - (`boolean`) - show scaling distance debug lines. default - `false`
59
- - `generateAnchorMark` - a function to generate your custom anchor mark. default - `undefined` (uses default anchor mark)
60
-
61
- **returns:** `PIXI.Container` (the select tool)
1
+ # Free Transform Tool for PIXI.js
2
+
3
+ Works with pixi.js v8 and above.
4
+
5
+ ![preview](https://cdn.quinn.live/transformer-image.png)
6
+
7
+ ### ✨ Features
8
+
9
+ - Translate - Drag to move anywhere
10
+ - Scale - Scale on X, Y or both axis'
11
+ - Rotate - drag to rotate
12
+ - Anchor point - update anchor point of transforms (Ctrl + Click)
13
+ - Snapping - hold ctrl to snap rotation at 45degrees, snap anchor point to edges and corners.
14
+ - Theming - custom border color, control size and color, and anchor point graphic
15
+
16
+ > Note - all transformations are applied wrt the pivot point, update pivot point to achieve the desired result.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @quinninc/pixi-transformer
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```tsx
27
+ const root = new Container();
28
+ app.stage.addChild(root);
29
+
30
+ // create tool and add to stage
31
+ const selectTool = new FreeTransformTool();
32
+ selectTool.label = "transform-tool";
33
+ root.addChild(selectTool);
34
+
35
+ const obj = new Sprite({
36
+ texture: Texture.from("lenna"),
37
+ label: "lenna",
38
+ eventMode: "static",
39
+ });
40
+
41
+ root.addChild(obj);
42
+
43
+ obj.on("pointertap", (ev) => {
44
+ console.log("select - ", ev.currentTarget.label);
45
+ selectTool.select(ev.currentTarget);
46
+ });
47
+ ```
48
+
49
+ ## API
50
+
51
+ ### `FreeTransformTool` (class)
52
+
53
+ **Constructor Parameter Object:**
54
+
55
+ - `lineColor` - border line color. default - `0x66cfff`
56
+ - `handleColor` - (`number`) - handles' fill color. default - `0xffffff`
57
+ - `controlsSize` - (`number`) - handles' size in pixels. default - `10`
58
+ - `debug` - (`boolean`) - show scaling distance debug lines. default - `false`
59
+ - `generateAnchorMark` - a function to generate your custom anchor mark. default - `undefined` (uses default anchor mark)
60
+
61
+ **returns:** `PIXI.Container` (the select tool)
@@ -0,0 +1,74 @@
1
+ import { Container, Graphics, Sprite } from "pixi.js";
2
+ export declare class FreeTransformTool extends Container {
3
+ private debug;
4
+ private moveHandle;
5
+ private rotateHandle;
6
+ private scaleTHandle;
7
+ private scaleRHandle;
8
+ private scaleBHandle;
9
+ private scaleLHandle;
10
+ private scaleTLHandle;
11
+ private scaleTRHandle;
12
+ private scaleBRHandle;
13
+ private scaleBLHandle;
14
+ private border;
15
+ private anchorMark;
16
+ private fromPoint;
17
+ private toPoint;
18
+ private target;
19
+ private movedThreshold;
20
+ private handleOpacity;
21
+ private controlsSize;
22
+ private controlsDimOpacity;
23
+ private controlsStrokeThickness;
24
+ private lineColor;
25
+ private handleColor;
26
+ private left;
27
+ private top;
28
+ private bottom;
29
+ private right;
30
+ private dragging;
31
+ /**
32
+ *
33
+ * @param p.lineColor - border line color
34
+ * @param p.handleColor - handles' fill color
35
+ * @param p.controlsSize - handles' size in pixels
36
+ * @param p.debug - show scaling distance debug lines
37
+ * @param p.generateAnchorMark - a function to generate your custom anchor mark
38
+ *
39
+ * @example
40
+ * const selectTool = new FreeTransformTool({
41
+ * lineColor: 0x66cfff,
42
+ * handleColor: 0xffffff,
43
+ * controlsSize: 10,
44
+ * debug: false,
45
+ * 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.
46
+ * });
47
+ *
48
+ * root.addChild(selectTool); // by default the tool has a z-index of 1e7 (10000000). you can change it according to your requirement
49
+ *
50
+ * selectTool.select(obj);
51
+ *
52
+ * selectTool.unselect();
53
+ */
54
+ constructor({ lineColor, handleColor, controlsSize, debug, generateAnchorMark, }?: {
55
+ lineColor?: string | number;
56
+ handleColor?: string | number;
57
+ controlsSize?: number;
58
+ debug?: boolean;
59
+ generateAnchorMark?: false | ((graphic: Graphics) => void);
60
+ });
61
+ private addToolTip;
62
+ private handleHandleEvents;
63
+ private createHandle;
64
+ private createHandleIndicatorTo;
65
+ private deScale;
66
+ select(target: Container): void;
67
+ unselect(): void;
68
+ update(): void;
69
+ setTitle(title: string): void;
70
+ setCursor(cursor: string): void;
71
+ private generateAnchorMark;
72
+ get _target(): Container<import("pixi.js").ContainerChild> | Sprite | null;
73
+ cleanup(): void;
74
+ }
@@ -0,0 +1,549 @@
1
+ import { Container, Graphics, Point, Rectangle, } from "pixi.js";
2
+ import { calcAngleRadians, calcDistance, getDirection, rotatePoint, } from "./vector";
3
+ export class FreeTransformTool extends 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({ lineColor = 0x66cfff, handleColor = 0xffffff, controlsSize = 10, debug = false, generateAnchorMark, } = {}) {
28
+ super();
29
+ // target 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.dragging = false;
37
+ const that = this;
38
+ this.debug = debug;
39
+ this.lineColor = lineColor;
40
+ this.handleColor = handleColor;
41
+ this.handleOpacity = 1;
42
+ this.controlsSize = controlsSize;
43
+ this.controlsDimOpacity = 1;
44
+ this.controlsStrokeThickness = 2;
45
+ this.movedThreshold = 10;
46
+ if (typeof generateAnchorMark == "function")
47
+ this.generateAnchorMark = generateAnchorMark;
48
+ else if (generateAnchorMark === false)
49
+ this.generateAnchorMark = () => { };
50
+ this.zIndex = 1e7;
51
+ // Container Properties
52
+ this.visible = false;
53
+ // create border
54
+ this.border = new Graphics({ label: "Border" });
55
+ this.addChild(this.border);
56
+ // Anchor mark
57
+ this.anchorMark = new Graphics({ label: "AnchorMark" });
58
+ this.anchorMark.alpha = this.handleOpacity;
59
+ this.addChild(this.anchorMark);
60
+ // debug indicators
61
+ this.fromPoint = new Graphics({
62
+ label: "fromPoint",
63
+ alpha: this.handleOpacity * 0.75,
64
+ });
65
+ this.toPoint = new Graphics({
66
+ label: "toPoint",
67
+ alpha: this.handleOpacity * 0.75,
68
+ });
69
+ this.addChild(this.fromPoint, this.toPoint);
70
+ // scaling handles
71
+ this.scaleTHandle = this.createHandle({
72
+ name: "scaleTHandle",
73
+ cursor: "n-resize",
74
+ });
75
+ this.scaleRHandle = this.createHandle({
76
+ name: "scaleRHandle",
77
+ cursor: "w-resize",
78
+ });
79
+ this.scaleBHandle = this.createHandle({
80
+ name: "scaleBHandle",
81
+ cursor: "s-resize",
82
+ });
83
+ this.scaleLHandle = this.createHandle({
84
+ name: "scaleLHandle",
85
+ cursor: "e-resize",
86
+ });
87
+ this.scaleTLHandle = this.createHandle({
88
+ name: "scaleTLHandle",
89
+ cursor: "nw-resize",
90
+ });
91
+ this.scaleTRHandle = this.createHandle({
92
+ name: "scaleTRHandle",
93
+ cursor: "ne-resize",
94
+ });
95
+ this.scaleBRHandle = this.createHandle({
96
+ name: "scaleBRHandle",
97
+ cursor: "se-resize",
98
+ });
99
+ this.scaleBLHandle = this.createHandle({
100
+ name: "scaleBLHandle",
101
+ cursor: "sw-resize",
102
+ });
103
+ // rotation handle
104
+ this.rotateHandle = this.createHandle({
105
+ name: "Rotate",
106
+ cursor: "pointer",
107
+ shape: "circle",
108
+ });
109
+ this.addChild(this.scaleTHandle, this.scaleRHandle, this.scaleBHandle, this.scaleLHandle, this.scaleTLHandle, this.scaleTRHandle, this.scaleBRHandle, this.scaleBLHandle, this.rotateHandle);
110
+ // ------------<MoveTool>---------------
111
+ this.moveHandle = new Graphics({ label: "moveHandle" });
112
+ this.moveHandle.interactive = true;
113
+ this.addToolTip(this.moveHandle, "Move", "move");
114
+ this.moveHandle
115
+ .on("pointerdown", onMoveHandleDown)
116
+ .on("pointerup", onMoveHandleUp)
117
+ .on("pointerupoutside", onMoveHandleUp);
118
+ this.handleHandleEvents(this.moveHandle, this);
119
+ this.moveHandle.hitArea = new Rectangle(0, 0, this.controlsSize, this.controlsSize);
120
+ this.addChild(this.moveHandle);
121
+ function onMoveHandleDown(downEvent) {
122
+ // console.log("onMoveHandleDown this, that - ", this, that);
123
+ if (!that.target)
124
+ return;
125
+ // data
126
+ this.targetStartPos = that.target.position.clone();
127
+ this.dragDistance = 0;
128
+ this.on("globalpointermove", onMoveHandleMove);
129
+ }
130
+ function onMoveHandleMove(moveEvent) {
131
+ // console.log("onMoveHandleMove this, that - ", this, that);
132
+ if (!this.dragging || !that.target)
133
+ return;
134
+ const moveDelta = new Point(moveEvent.global.x - this.eventStartGlobalPos.x, moveEvent.global.y - this.eventStartGlobalPos.y);
135
+ that.target.position.x = this.targetStartPos.x + moveDelta.x;
136
+ that.target.position.y = this.targetStartPos.y + moveDelta.y;
137
+ this.dragDistance = calcDistance(moveEvent.global, this.eventStartGlobalPos);
138
+ that.update();
139
+ moveEvent.stopPropagation();
140
+ }
141
+ function onMoveHandleUp(upEvent) {
142
+ // console.log("onMoveHandleUp this, that - ", this, that);
143
+ upEvent.stopPropagation();
144
+ // only deselect if there was very little movement on click
145
+ // which helps on mobile devices, where it's difficult to
146
+ // tap without dragging slightly
147
+ if (!this.dragDistance || this.dragDistance < that.movedThreshold) {
148
+ that.unselect();
149
+ }
150
+ this.off("globalpointermove", onMoveHandleMove);
151
+ }
152
+ // ------------</ MoveTool>---------------
153
+ // ------------<ScaleXYTool>---------------
154
+ for (const handle of [
155
+ this.scaleBRHandle,
156
+ this.scaleTRHandle,
157
+ this.scaleBLHandle,
158
+ this.scaleTLHandle,
159
+ ]) {
160
+ handle
161
+ .on("pointerdown", onScaleXYToolDown)
162
+ .on("pointerupoutside", onScaleXYToolUp)
163
+ .on("pointerup", onScaleXYToolUp);
164
+ }
165
+ function onScaleXYToolDown(downEvent) {
166
+ // console.log("onScaleToolDown this, that - ", this, that);
167
+ if (!that.target)
168
+ return;
169
+ this.targetResolutionStart = that.target.resolution;
170
+ this.on("globalpointermove", onScaleXYToolMove);
171
+ }
172
+ function onScaleXYToolMove(moveEvent) {
173
+ // console.log("onScaleToolMove this, that - ", this, that);
174
+ if (!this.dragging || !that.target)
175
+ return;
176
+ const distStart = calcDistance(this.eventStartGlobalPos, that.target.position);
177
+ const distEnd = calcDistance(moveEvent.global, that.target.position);
178
+ const direction = getDirection(new Point(that.position.x, that.position.y), new Point(this.eventStartGlobalPos.x, this.eventStartGlobalPos.y), new Point(moveEvent.global.x, moveEvent.global.y));
179
+ this.rescaleFactor = (distEnd / distStart) * direction;
180
+ that.target.scale.x = this.targetStartScale.x * this.rescaleFactor;
181
+ that.target.scale.y = this.targetStartScale.y * this.rescaleFactor;
182
+ that.update();
183
+ }
184
+ function onScaleXYToolUp() {
185
+ // console.log("onScaleToolUp this, that - ", this, that);
186
+ if (that.target) {
187
+ that.target.resolution =
188
+ this.targetResolutionStart * this.rescaleFactor;
189
+ }
190
+ this.off("globalpointermove", onScaleXYToolMove);
191
+ }
192
+ // ------------</ ScaleXYTool>---------------
193
+ // ------------<ScaleYTool>---------------
194
+ for (const handle of [this.scaleTHandle, this.scaleBHandle]) {
195
+ handle
196
+ .on("pointerdown", onScaleYToolDown)
197
+ .on("pointerup", onScaleYToolUp)
198
+ .on("pointerupoutside", onScaleYToolUp);
199
+ }
200
+ function onScaleYToolDown(downEvent) {
201
+ // console.log("onVScaleToolDown this, that - ", this, that);
202
+ if (!that.target)
203
+ return;
204
+ this.on("globalpointermove", onScaleYToolMove);
205
+ }
206
+ function onScaleYToolMove(moveEvent) {
207
+ // console.log("onVScaleToolMove this - ", this);
208
+ if (!this.dragging || !that.target)
209
+ return;
210
+ const referencePoint = that.target.position.clone();
211
+ // referencePoint.x = that.target.position.x + that.target.width / 2;
212
+ const distStart = calcDistance(this.eventStartGlobalPos, referencePoint);
213
+ const distEnd = calcDistance(moveEvent.global, referencePoint);
214
+ const direction = getDirection(referencePoint, this.eventStartGlobalPos, moveEvent.global);
215
+ this.rescaleFactor = (distEnd / distStart) * direction;
216
+ that.target.scale.y = this.targetStartScale.y * this.rescaleFactor;
217
+ that.update();
218
+ }
219
+ function onScaleYToolUp(upEvent) {
220
+ // console.log("onVScaleToolUp this, that - ", this, that);
221
+ this.off("globalpointermove", onScaleYToolMove);
222
+ }
223
+ // ------------</ ScaleYTool>---------------
224
+ // ------------<ScaleXTool>---------------
225
+ for (const handle of [this.scaleRHandle, this.scaleLHandle]) {
226
+ handle
227
+ .on("pointerdown", onScaleXToolDown)
228
+ .on("pointerup", onScaleXToolUp)
229
+ .on("pointerupoutside", onScaleXToolUp);
230
+ }
231
+ this.addChild(this.scaleRHandle);
232
+ function onScaleXToolDown(downEvent) {
233
+ // console.log("onHScaleToolDown this, that - ", this, that);
234
+ if (!that.target)
235
+ return;
236
+ this.on("globalpointermove", onScaleXToolMove);
237
+ }
238
+ function onScaleXToolMove(moveEvent) {
239
+ // console.log("onHScaleToolMove this - ", this);
240
+ if (!this.dragging || !that.target)
241
+ return;
242
+ const referencePoint = that.target.position.clone();
243
+ // referencePoint.y = that.target.position.y + that.target.height / 2;
244
+ const distStart = calcDistance(this.eventStartGlobalPos, referencePoint);
245
+ const distEnd = calcDistance(moveEvent.global, referencePoint);
246
+ const direction = getDirection(referencePoint, this.eventStartGlobalPos, moveEvent.global);
247
+ const rescaleFactor = (distEnd / distStart) * direction;
248
+ that.target.scale.x = this.targetStartScale.x * rescaleFactor;
249
+ that.update();
250
+ }
251
+ function onScaleXToolUp(upEvent) {
252
+ // console.log("onHScaleToolUp this, that - ", this, that);
253
+ this.off("globalpointermove", onScaleXToolMove);
254
+ }
255
+ // ------------</ ScaleXTool>---------------
256
+ // ------------<RotateTool>---------------
257
+ this.rotateHandle
258
+ .on("pointerdown", onRotateToolDown)
259
+ .on("pointerup", onRotateToolUp)
260
+ .on("pointerupoutside", onRotateToolUp);
261
+ function onRotateToolDown(downEvent) {
262
+ // console.log("onRotateToolDown this, that - ", this, that);
263
+ if (!that.target)
264
+ return;
265
+ this.on("globalpointermove", onRotateToolMove);
266
+ }
267
+ function onRotateToolMove(moveEvent) {
268
+ // console.log("onRotateToolMove this, that - ", this, that);
269
+ if (!this.dragging || !that.target)
270
+ return;
271
+ // the drag point is relative to the display object x,y position on the stage (it's registration point)
272
+ const relativeStartPoint = {
273
+ x: this.eventStartGlobalPos.x - that.target.x,
274
+ y: this.eventStartGlobalPos.y - that.target.y,
275
+ };
276
+ const relativeEndPoint = {
277
+ x: moveEvent.global.x - that.target.x,
278
+ y: moveEvent.global.y - that.target.y,
279
+ };
280
+ const startAngle = calcAngleRadians(relativeStartPoint.x, relativeStartPoint.y);
281
+ const endAngle = calcAngleRadians(relativeEndPoint.x, relativeEndPoint.y);
282
+ const deltaAngle = endAngle - startAngle;
283
+ let finalRotation = this.targetStartRotation + deltaAngle;
284
+ if (moveEvent.ctrlKey) {
285
+ const snapAngle = 45;
286
+ let finalAngle = (finalRotation * 180) / Math.PI;
287
+ const proximity = Math.min(Math.abs(finalAngle % snapAngle), snapAngle - Math.abs(finalAngle % snapAngle));
288
+ const snapProximity = 6;
289
+ if (proximity < snapProximity) {
290
+ finalAngle = Math.round(finalAngle / snapAngle) * snapAngle;
291
+ finalRotation = (finalAngle * Math.PI) / 180;
292
+ }
293
+ }
294
+ that.target.rotation = finalRotation;
295
+ that.update();
296
+ }
297
+ function onRotateToolUp(upEvent) {
298
+ // console.log("onRotateToolUp this, that - ", this, that);
299
+ this.off("globalpointermove", onRotateToolMove);
300
+ }
301
+ // ------------</ RotateTool>---------------
302
+ }
303
+ addToolTip(shape, name, cursor) {
304
+ shape.on("pointerover", () => {
305
+ if (!this.dragging) {
306
+ this.setTitle(name);
307
+ this.setCursor(cursor);
308
+ }
309
+ });
310
+ shape.on("pointerout", () => {
311
+ if (!this.dragging) {
312
+ this.setTitle("");
313
+ this.setCursor("default");
314
+ }
315
+ });
316
+ shape.on("pointerupoutside", () => {
317
+ this.setTitle("");
318
+ this.setCursor("default");
319
+ });
320
+ }
321
+ handleHandleEvents(handle, that) {
322
+ // console.log("handleHandleEvents this, handle - ", this, handle.label);
323
+ function onHandleDown(e) {
324
+ // console.log("onHandleDown this - ", this);
325
+ that.dragging = true;
326
+ handle.dragging = true;
327
+ handle.eventStartGlobalPos = e.global.clone();
328
+ handle.targetStartScale = that.target.scale.clone();
329
+ handle.targetStartRotation = that.target.rotation;
330
+ handle.on("globalpointermove", onHandleMove);
331
+ // graphic - fromPoint
332
+ if (that.debug) {
333
+ that.createHandleIndicatorTo(that.fromPoint, handle.eventStartGlobalPos.x, handle.eventStartGlobalPos.y);
334
+ }
335
+ }
336
+ function onHandleMove(e) {
337
+ // console.log("onHandleMove this - ", this);
338
+ if (that.debug) {
339
+ that.createHandleIndicatorTo(that.toPoint, e.globalX, e.globalY);
340
+ that.deScale(that.fromPoint);
341
+ }
342
+ }
343
+ function onHandleUp() {
344
+ // console.log("onHandleUp this - ", this);
345
+ that.update();
346
+ handle.off("globalpointermove", onHandleMove);
347
+ if (that.debug) {
348
+ that.fromPoint.clear();
349
+ that.toPoint.clear();
350
+ }
351
+ handle.dragging = false;
352
+ that.dragging = false;
353
+ }
354
+ handle
355
+ .on("pointerdown", onHandleDown)
356
+ .on("pointerup", onHandleUp)
357
+ .on("pointerupoutside", onHandleUp);
358
+ }
359
+ createHandle({ cursor, name, shape = "square", }) {
360
+ const handle = new Graphics();
361
+ handle.label = name;
362
+ handle.interactive = true;
363
+ handle.alpha = this.handleOpacity;
364
+ this.addToolTip(handle, name, cursor);
365
+ if (shape === "circle") {
366
+ handle.ellipse(this.controlsSize / 2, this.controlsSize / 2, this.controlsSize / 2, this.controlsSize / 2);
367
+ }
368
+ else
369
+ handle.rect(0, 0, this.controlsSize, this.controlsSize);
370
+ handle
371
+ .stroke({ width: this.controlsStrokeThickness, color: this.lineColor })
372
+ .fill(this.handleColor);
373
+ handle.pivot.set(this.controlsSize / 2, this.controlsSize / 2);
374
+ this.handleHandleEvents(handle, this);
375
+ return handle;
376
+ }
377
+ createHandleIndicatorTo(handle, x, y) {
378
+ x = x - this.position.x;
379
+ y = y - this.position.y;
380
+ const p1 = new Point(x, y);
381
+ const origin = new Point(0, 0);
382
+ const p = rotatePoint(p1, origin, -this.rotation);
383
+ x = p.x;
384
+ y = p.y;
385
+ const size = this.controlsSize * 1.25;
386
+ handle
387
+ .clear()
388
+ .moveTo(0, 0)
389
+ .lineTo(x, y)
390
+ .stroke({ width: 5, color: 0xffffff })
391
+ .rect(x - size / 2, y - size / 2, size, size)
392
+ .fill(0xffffff);
393
+ this.deScale(handle);
394
+ }
395
+ deScale(target) {
396
+ target.scale.set(1 / this.scale.x, 1 / this.scale.y);
397
+ }
398
+ // public methods:
399
+ select(target) {
400
+ if (!target) {
401
+ this.unselect();
402
+ return;
403
+ }
404
+ // copy object translation/transformation
405
+ this.target = target;
406
+ const bounds = target.getLocalBounds();
407
+ this.width = bounds.width;
408
+ this.height = bounds.height;
409
+ this.scale.x = target.scale.x;
410
+ this.scale.y = target.scale.y;
411
+ this.x = target.x;
412
+ this.y = target.y;
413
+ this.rotation = target.rotation;
414
+ let anchor;
415
+ if (target.anchor) {
416
+ anchor = target.anchor;
417
+ }
418
+ else if (target.pivot) {
419
+ anchor = new Point(target.pivot.x / target.width, target.pivot.y / target.height);
420
+ }
421
+ else {
422
+ anchor = new Point(0.5, 0.5);
423
+ }
424
+ target.anchor = anchor;
425
+ this.left = -bounds.width * anchor.x;
426
+ this.top = -bounds.height * anchor.y;
427
+ this.bottom = bounds.height * (1 - anchor.y);
428
+ this.right = bounds.width * (1 - anchor.x);
429
+ // anchor mark
430
+ this.generateAnchorMark?.(this.anchorMark);
431
+ this.deScale(this.anchorMark);
432
+ this.anchorMark.rotation = -this.rotation;
433
+ const rotationHandlePos = {
434
+ x: (this.left + this.right) / 2,
435
+ y: this.top - 80 / Math.abs(this.scale.y),
436
+ };
437
+ // borders
438
+ this.border
439
+ .clear()
440
+ .moveTo(this.left, this.top)
441
+ .lineTo(this.right, this.top)
442
+ .moveTo(this.left, this.bottom)
443
+ .lineTo(this.right, this.bottom)
444
+ .stroke({
445
+ width: this.controlsStrokeThickness / this.scale.y,
446
+ color: this.lineColor,
447
+ })
448
+ .moveTo(this.left, this.top)
449
+ .lineTo(this.left, this.bottom)
450
+ .moveTo(this.right, this.top)
451
+ .lineTo(this.right, this.bottom)
452
+ .stroke({
453
+ width: this.controlsStrokeThickness / this.scale.x,
454
+ color: this.lineColor,
455
+ })
456
+ .moveTo(rotationHandlePos.x, this.top)
457
+ .lineTo(rotationHandlePos.x, rotationHandlePos.y)
458
+ .stroke({
459
+ width: this.controlsStrokeThickness / this.scale.x,
460
+ color: this.lineColor,
461
+ });
462
+ // draw move hit area
463
+ this.moveHandle.hitArea = new Rectangle(this.left, this.top, bounds.width, bounds.height);
464
+ this.scaleTLHandle.position.set(this.left, this.top);
465
+ this.scaleTRHandle.position.set(this.right, this.top);
466
+ this.scaleBRHandle.position.set(this.right, this.bottom);
467
+ this.scaleBLHandle.position.set(this.left, this.bottom);
468
+ this.scaleTHandle.position.set(this.left + bounds.width / 2, this.top);
469
+ this.scaleRHandle.position.set(this.right, this.top + bounds.height / 2);
470
+ this.scaleBHandle.position.set(this.left + bounds.width / 2, this.bottom);
471
+ this.scaleLHandle.position.set(this.left, this.top + bounds.height / 2);
472
+ this.rotateHandle.position.set(rotationHandlePos.x, rotationHandlePos.y);
473
+ const allHandles = [
474
+ this.scaleTLHandle,
475
+ this.scaleTRHandle,
476
+ this.scaleBRHandle,
477
+ this.scaleBLHandle,
478
+ this.scaleTHandle,
479
+ this.scaleRHandle,
480
+ this.scaleBHandle,
481
+ this.scaleLHandle,
482
+ this.rotateHandle,
483
+ ];
484
+ // descale all handles
485
+ allHandles.forEach((handle) => {
486
+ this.deScale(handle);
487
+ });
488
+ this.visible = true;
489
+ // console.log("select this - ", this);
490
+ }
491
+ unselect() {
492
+ this.target = null;
493
+ this.visible = false;
494
+ }
495
+ update() {
496
+ if (this.target) {
497
+ this.select(this.target);
498
+ }
499
+ }
500
+ setTitle(title) {
501
+ title = title || "";
502
+ this.accessibleTitle = title;
503
+ }
504
+ setCursor(cursor) {
505
+ const cursors = [
506
+ "e-resize",
507
+ "se-resize",
508
+ "s-resize",
509
+ "sw-resize",
510
+ "w-resize",
511
+ "nw-resize",
512
+ "n-resize",
513
+ "ne-resize",
514
+ ];
515
+ const index = cursors.indexOf(cursor);
516
+ if (index >= 0) {
517
+ const angle = 45;
518
+ let rotation = this.target.angle;
519
+ rotation = rotation + angle / 2;
520
+ let newIndex = index + Math.floor(rotation / angle);
521
+ newIndex = newIndex % cursors.length;
522
+ document.body.style.cursor = cursors[newIndex];
523
+ }
524
+ else {
525
+ document.body.style.cursor = cursor;
526
+ }
527
+ }
528
+ generateAnchorMark(graphic) {
529
+ const anchorInnerRadius = this.controlsSize * 0.125;
530
+ const anchorOuterRadius = this.controlsSize * 0.625;
531
+ graphic
532
+ .clear()
533
+ .circle(0, 0, this.controlsSize * 0.375)
534
+ .moveTo(0, anchorInnerRadius)
535
+ .lineTo(0, anchorOuterRadius)
536
+ .moveTo(-anchorInnerRadius, 0)
537
+ .lineTo(-anchorOuterRadius, 0)
538
+ .moveTo(0, -anchorInnerRadius)
539
+ .lineTo(0, -anchorOuterRadius)
540
+ .moveTo(anchorInnerRadius, 0)
541
+ .lineTo(anchorOuterRadius, 0)
542
+ .fill(0xff0000)
543
+ .stroke(0x111111);
544
+ }
545
+ get _target() {
546
+ return this.target;
547
+ }
548
+ cleanup() { }
549
+ }
@@ -1,7 +1,7 @@
1
1
  import { Pixi } from "./pixi";
2
2
  export declare class Transformer extends Pixi.Container {
3
3
  private debug;
4
- private id;
4
+ private _id;
5
5
  private moveHandle;
6
6
  private rotateHandle;
7
7
  private scaleTHandle;
@@ -74,6 +74,7 @@ export declare class Transformer extends Pixi.Container {
74
74
  setCursor(cursor: string): void;
75
75
  private generateAnchorMark;
76
76
  get _target(): Pixi.Container<Pixi.ContainerChild> | Pixi.Sprite | null;
77
+ get id(): string;
77
78
  cleanup(): void;
78
79
  private convertToLocal;
79
80
  }
@@ -37,7 +37,7 @@ export class Transformer extends Pixi.Container {
37
37
  this.dragging = false;
38
38
  this.onUpdate = () => { };
39
39
  const that = this;
40
- this.id = id;
40
+ this._id = id;
41
41
  this.debug = debug;
42
42
  this.lineColor = lineColor;
43
43
  this.handleColor = handleColor;
@@ -654,6 +654,9 @@ export class Transformer extends Pixi.Container {
654
654
  get _target() {
655
655
  return this.target;
656
656
  }
657
+ get id() {
658
+ return this._id;
659
+ }
657
660
  cleanup() { }
658
661
  convertToLocal(point) {
659
662
  if (this.target) {
@@ -0,0 +1,23 @@
1
+ import { Point } from "pixi.js";
2
+ /**
3
+ *
4
+ * @param origin source from where distance will be calculated
5
+ * @param p1 point 1 - positive direction is towards this point
6
+ * @param p2 point 2
7
+ * @returns 1 for same direction, -1 if the angle is more than 90 degrees
8
+ */
9
+ export declare function getDirection(origin: Point, p1: Point, p2: Point): -1 | 1;
10
+ /**
11
+ *
12
+ * @param point point to rotate
13
+ * @param origin origin point
14
+ * @param rotation angle in radians
15
+ * @returns rotated point
16
+ */
17
+ export declare function rotatePoint(point: Point, origin: Point, rotation: number): Point;
18
+ /**
19
+ * Calculate the euclidean distance between two points.
20
+ */
21
+ export declare function calcDistance(p1: Point, p2: Point): number;
22
+ export declare function calcAngleDegrees(x: number, y: number): number;
23
+ export declare function calcAngleRadians(x: number, y: number): number;
package/dist/vector.js ADDED
@@ -0,0 +1,45 @@
1
+ import { Point } from "pixi.js";
2
+ /**
3
+ *
4
+ * @param origin source from where distance will be calculated
5
+ * @param p1 point 1 - positive direction is towards this point
6
+ * @param p2 point 2
7
+ * @returns 1 for same direction, -1 if the angle is more than 90 degrees
8
+ */
9
+ export function getDirection(origin, p1, p2) {
10
+ const v1 = new Point(p1.x - origin.x, p1.y - origin.y);
11
+ const v2 = new Point(p2.x - origin.x, p2.y - origin.y);
12
+ const dot = v1.x * v2.x + v1.y * v2.y;
13
+ return dot >= 0 ? 1 : -1;
14
+ }
15
+ /**
16
+ *
17
+ * @param point point to rotate
18
+ * @param origin origin point
19
+ * @param rotation angle in radians
20
+ * @returns rotated point
21
+ */
22
+ export function rotatePoint(point, origin, rotation) {
23
+ // Translate point to origin
24
+ const translatedX = point.x - origin.x;
25
+ const translatedY = point.y - origin.y;
26
+ // Apply rotation
27
+ const rotatedX = translatedX * Math.cos(rotation) - translatedY * Math.sin(rotation);
28
+ const rotatedY = translatedX * Math.sin(rotation) + translatedY * Math.cos(rotation);
29
+ // Translate point back
30
+ const finalX = rotatedX + origin.x;
31
+ const finalY = rotatedY + origin.y;
32
+ return new Point(finalX, finalY);
33
+ }
34
+ /**
35
+ * Calculate the euclidean distance between two points.
36
+ */
37
+ export function calcDistance(p1, p2) {
38
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
39
+ }
40
+ export function calcAngleDegrees(x, y) {
41
+ return (calcAngleRadians(x, y) * 180) / Math.PI;
42
+ }
43
+ export function calcAngleRadians(x, y) {
44
+ return Math.atan2(y, x);
45
+ }
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
- {
2
- "name": "@quinninc/pixi-transformer",
3
- "private": false,
4
- "version": "0.0.5",
5
- "repository": {
6
- "url": "https://github.com/Quinn-Care-Private-Limited/pixi-transformer"
7
- },
8
- "type": "module",
9
- "main": "dist/index.js",
10
- "module": "dist/index.js",
11
- "types": "dist/index.d.ts",
12
- "files": [
13
- "dist",
14
- "package.json",
15
- "README.md"
16
- ],
17
- "keywords": [
18
- "pixi.js",
19
- "transformer",
20
- "pixi.js v8"
21
- ],
22
- "author": {
23
- "name": "Rohit Kaushal",
24
- "url": "https://github.com/RohitKaushal7"
25
- },
26
- "license": "MIT",
27
- "scripts": {
28
- "dev": "tsc -w",
29
- "build": "tsc",
30
- "deploy": "npm run build && npm version patch && npm publish --access public"
31
- },
32
- "devDependencies": {
33
- "typescript": "^5.2.2",
34
- "vite": "^5.3.1",
35
- "pixi.js": "^8.2.1"
36
- },
37
- "dependencies": {},
38
- "peerDependencies": {
39
- "pixi.js": "^8.2.1"
40
- }
41
- }
1
+ {
2
+ "name": "@quinninc/pixi-transformer",
3
+ "private": false,
4
+ "version": "0.0.6",
5
+ "repository": {
6
+ "url": "https://github.com/Quinn-Care-Private-Limited/pixi-transformer"
7
+ },
8
+ "type": "module",
9
+ "main": "dist/index.js",
10
+ "module": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "files": [
13
+ "dist",
14
+ "package.json",
15
+ "README.md"
16
+ ],
17
+ "keywords": [
18
+ "pixi.js",
19
+ "transformer",
20
+ "pixi.js v8"
21
+ ],
22
+ "author": {
23
+ "name": "Rohit Kaushal",
24
+ "url": "https://github.com/RohitKaushal7"
25
+ },
26
+ "license": "MIT",
27
+ "scripts": {
28
+ "dev": "tsc -w",
29
+ "build": "tsc",
30
+ "deploy": "npm run build && npm version patch && npm publish --access public"
31
+ },
32
+ "devDependencies": {
33
+ "typescript": "^5.2.2",
34
+ "vite": "^5.3.1",
35
+ "pixi.js": "^8.2.1"
36
+ },
37
+ "dependencies": {},
38
+ "peerDependencies": {
39
+ "pixi.js": "^8.2.1"
40
+ }
41
+ }