senangwebs-photobooth 1.0.1 → 2.0.2
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 +219 -235
- package/dist/swp.css +884 -344
- package/dist/swp.js +1 -1
- package/examples/data-attribute.html +69 -0
- package/examples/index.html +56 -51
- package/examples/studio.html +83 -0
- package/package.json +12 -5
- package/src/css/swp.css +884 -344
- package/src/js/core/Canvas.js +398 -0
- package/src/js/core/EventEmitter.js +188 -0
- package/src/js/core/History.js +250 -0
- package/src/js/core/Keyboard.js +323 -0
- package/src/js/filters/FilterManager.js +248 -0
- package/src/js/index.js +48 -0
- package/src/js/io/Clipboard.js +52 -0
- package/src/js/io/FileManager.js +150 -0
- package/src/js/layers/BlendModes.js +342 -0
- package/src/js/layers/Layer.js +415 -0
- package/src/js/layers/LayerManager.js +459 -0
- package/src/js/selection/Selection.js +167 -0
- package/src/js/swp.js +297 -709
- package/src/js/tools/BaseTool.js +264 -0
- package/src/js/tools/BrushTool.js +314 -0
- package/src/js/tools/CropTool.js +400 -0
- package/src/js/tools/EraserTool.js +155 -0
- package/src/js/tools/EyedropperTool.js +184 -0
- package/src/js/tools/FillTool.js +109 -0
- package/src/js/tools/GradientTool.js +141 -0
- package/src/js/tools/HandTool.js +51 -0
- package/src/js/tools/MarqueeTool.js +103 -0
- package/src/js/tools/MoveTool.js +465 -0
- package/src/js/tools/ShapeTool.js +285 -0
- package/src/js/tools/TextTool.js +253 -0
- package/src/js/tools/ToolManager.js +277 -0
- package/src/js/tools/ZoomTool.js +68 -0
- package/src/js/ui/ColorManager.js +71 -0
- package/src/js/ui/UI.js +1211 -0
- package/swp_preview1.png +0 -0
- package/swp_preview2.png +0 -0
- package/webpack.config.js +4 -11
- package/dist/styles.js +0 -1
- package/examples/customization.html +0 -360
- package/spec.md +0 -239
- package/swp_preview.png +0 -0
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SenangWebs Studio - Move Tool
|
|
3
|
+
* Move and transform layers
|
|
4
|
+
* @version 2.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { BaseTool } from './BaseTool.js';
|
|
8
|
+
|
|
9
|
+
export class MoveTool extends BaseTool {
|
|
10
|
+
constructor(app) {
|
|
11
|
+
super(app);
|
|
12
|
+
this.name = 'move';
|
|
13
|
+
this.icon = 'move';
|
|
14
|
+
this.cursor = 'move';
|
|
15
|
+
this.shortcut = 'v';
|
|
16
|
+
|
|
17
|
+
this.options = {
|
|
18
|
+
autoSelect: false,
|
|
19
|
+
showTransform: true,
|
|
20
|
+
snapToEdges: true,
|
|
21
|
+
snapThreshold: 4
|
|
22
|
+
};
|
|
23
|
+
this.defaultOptions = { ...this.options };
|
|
24
|
+
|
|
25
|
+
// Transform state
|
|
26
|
+
this.transforming = false;
|
|
27
|
+
this.transformHandle = null;
|
|
28
|
+
this.originalBounds = null;
|
|
29
|
+
this.originalImageData = null; // Store original image for quality resize
|
|
30
|
+
|
|
31
|
+
// Snap state
|
|
32
|
+
this.activeSnaps = { x: null, y: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onPointerDown(e) {
|
|
36
|
+
super.onPointerDown(e);
|
|
37
|
+
|
|
38
|
+
const layer = this.app.layers.getActiveLayer();
|
|
39
|
+
if (!layer || layer.locked) return;
|
|
40
|
+
|
|
41
|
+
// Check if clicking on transform handle
|
|
42
|
+
if (this.options.showTransform) {
|
|
43
|
+
this.transformHandle = this.hitTestTransformHandles(this.startPoint);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (this.transformHandle) {
|
|
47
|
+
this.transforming = true;
|
|
48
|
+
this.originalBounds = this.getLayerBounds(layer);
|
|
49
|
+
// Store original image data for quality resize
|
|
50
|
+
if (layer.canvas) {
|
|
51
|
+
this.originalImageData = layer.ctx.getImageData(0, 0, layer.width, layer.height);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
// Auto-select layer at click position if enabled
|
|
55
|
+
if (this.options.autoSelect) {
|
|
56
|
+
this.selectLayerAtPoint(this.startPoint);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Reset snaps
|
|
61
|
+
this.activeSnaps = { x: null, y: null };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
onPointerMove(e) {
|
|
65
|
+
super.onPointerMove(e);
|
|
66
|
+
|
|
67
|
+
if (!this.isDrawing) return;
|
|
68
|
+
|
|
69
|
+
const layer = this.app.layers.getActiveLayer();
|
|
70
|
+
if (!layer || layer.locked) return;
|
|
71
|
+
|
|
72
|
+
const dx = this.currentPoint.x - this.lastPoint.x;
|
|
73
|
+
const dy = this.currentPoint.y - this.lastPoint.y;
|
|
74
|
+
|
|
75
|
+
if (this.transforming && this.transformHandle) {
|
|
76
|
+
this.applyTransform(layer, this.transformHandle, dx, dy);
|
|
77
|
+
} else {
|
|
78
|
+
// Move layer
|
|
79
|
+
layer.position.x += dx;
|
|
80
|
+
layer.position.y += dy;
|
|
81
|
+
|
|
82
|
+
// Apply snapping
|
|
83
|
+
if (this.options.snapToEdges) {
|
|
84
|
+
const snap = this.calculateSnap(layer);
|
|
85
|
+
if (snap.x !== null) {
|
|
86
|
+
layer.position.x = snap.x;
|
|
87
|
+
}
|
|
88
|
+
if (snap.y !== null) {
|
|
89
|
+
layer.position.y = snap.y;
|
|
90
|
+
}
|
|
91
|
+
this.activeSnaps = { x: snap.snapX, y: snap.snapY };
|
|
92
|
+
} else {
|
|
93
|
+
this.activeSnaps = { x: null, y: null };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.app.canvas.scheduleRender();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onPointerUp(e) {
|
|
101
|
+
if (this.isDrawing) {
|
|
102
|
+
this.app.history.pushState('Move Layer');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.transforming = false;
|
|
106
|
+
this.transformHandle = null;
|
|
107
|
+
this.originalBounds = null;
|
|
108
|
+
this.originalImageData = null;
|
|
109
|
+
this.activeSnaps = { x: null, y: null };
|
|
110
|
+
|
|
111
|
+
super.onPointerUp(e);
|
|
112
|
+
this.app.canvas.scheduleRender();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Calculate snap positions for layer
|
|
117
|
+
* @param {Layer} layer - Layer to snap
|
|
118
|
+
* @returns {Object} Snap result with x, y positions and snap line positions
|
|
119
|
+
*/
|
|
120
|
+
calculateSnap(layer) {
|
|
121
|
+
const threshold = this.options.snapThreshold;
|
|
122
|
+
const canvasWidth = this.app.canvas.width;
|
|
123
|
+
const canvasHeight = this.app.canvas.height;
|
|
124
|
+
|
|
125
|
+
const bounds = this.getLayerBounds(layer);
|
|
126
|
+
const layerLeft = bounds.x;
|
|
127
|
+
const layerRight = bounds.x + bounds.width;
|
|
128
|
+
const layerTop = bounds.y;
|
|
129
|
+
const layerBottom = bounds.y + bounds.height;
|
|
130
|
+
const layerCenterX = bounds.x + bounds.width / 2;
|
|
131
|
+
const layerCenterY = bounds.y + bounds.height / 2;
|
|
132
|
+
|
|
133
|
+
const canvasCenterX = canvasWidth / 2;
|
|
134
|
+
const canvasCenterY = canvasHeight / 2;
|
|
135
|
+
|
|
136
|
+
let snapX = null;
|
|
137
|
+
let snapY = null;
|
|
138
|
+
let newX = null;
|
|
139
|
+
let newY = null;
|
|
140
|
+
|
|
141
|
+
// Horizontal snapping (X axis)
|
|
142
|
+
// Left edge to canvas left
|
|
143
|
+
if (Math.abs(layerLeft) < threshold) {
|
|
144
|
+
newX = 0;
|
|
145
|
+
snapX = 0;
|
|
146
|
+
}
|
|
147
|
+
// Right edge to canvas right
|
|
148
|
+
else if (Math.abs(layerRight - canvasWidth) < threshold) {
|
|
149
|
+
newX = canvasWidth - bounds.width;
|
|
150
|
+
snapX = canvasWidth;
|
|
151
|
+
}
|
|
152
|
+
// Center to canvas center
|
|
153
|
+
else if (Math.abs(layerCenterX - canvasCenterX) < threshold) {
|
|
154
|
+
newX = canvasCenterX - bounds.width / 2;
|
|
155
|
+
snapX = canvasCenterX;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Vertical snapping (Y axis)
|
|
159
|
+
// Top edge to canvas top
|
|
160
|
+
if (Math.abs(layerTop) < threshold) {
|
|
161
|
+
newY = 0;
|
|
162
|
+
snapY = 0;
|
|
163
|
+
}
|
|
164
|
+
// Bottom edge to canvas bottom
|
|
165
|
+
else if (Math.abs(layerBottom - canvasHeight) < threshold) {
|
|
166
|
+
newY = canvasHeight - bounds.height;
|
|
167
|
+
snapY = canvasHeight;
|
|
168
|
+
}
|
|
169
|
+
// Center to canvas center
|
|
170
|
+
else if (Math.abs(layerCenterY - canvasCenterY) < threshold) {
|
|
171
|
+
newY = canvasCenterY - bounds.height / 2;
|
|
172
|
+
snapY = canvasCenterY;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return { x: newX, y: newY, snapX, snapY };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get layer bounds
|
|
180
|
+
* @param {Layer} layer - Layer
|
|
181
|
+
* @returns {Object} Bounds
|
|
182
|
+
*/
|
|
183
|
+
getLayerBounds(layer) {
|
|
184
|
+
return {
|
|
185
|
+
x: layer.position.x,
|
|
186
|
+
y: layer.position.y,
|
|
187
|
+
width: layer.width,
|
|
188
|
+
height: layer.height
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Hit test transform handles
|
|
194
|
+
* @param {Object} point - Point to test
|
|
195
|
+
* @returns {string|null} Handle name or null
|
|
196
|
+
*/
|
|
197
|
+
hitTestTransformHandles(point) {
|
|
198
|
+
const layer = this.app.layers.getActiveLayer();
|
|
199
|
+
if (!layer) return null;
|
|
200
|
+
|
|
201
|
+
const bounds = this.getLayerBounds(layer);
|
|
202
|
+
const handleSize = 8 / (this.app.canvas.zoom / 100);
|
|
203
|
+
|
|
204
|
+
const handles = {
|
|
205
|
+
'nw': { x: bounds.x, y: bounds.y },
|
|
206
|
+
'n': { x: bounds.x + bounds.width / 2, y: bounds.y },
|
|
207
|
+
'ne': { x: bounds.x + bounds.width, y: bounds.y },
|
|
208
|
+
'e': { x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 },
|
|
209
|
+
'se': { x: bounds.x + bounds.width, y: bounds.y + bounds.height },
|
|
210
|
+
's': { x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height },
|
|
211
|
+
'sw': { x: bounds.x, y: bounds.y + bounds.height },
|
|
212
|
+
'w': { x: bounds.x, y: bounds.y + bounds.height / 2 }
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
for (const [name, pos] of Object.entries(handles)) {
|
|
216
|
+
if (Math.abs(point.x - pos.x) < handleSize &&
|
|
217
|
+
Math.abs(point.y - pos.y) < handleSize) {
|
|
218
|
+
return name;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Apply transform based on handle
|
|
227
|
+
* @param {Layer} layer - Layer
|
|
228
|
+
* @param {string} handle - Handle name
|
|
229
|
+
* @param {number} dx - Delta X
|
|
230
|
+
* @param {number} dy - Delta Y
|
|
231
|
+
*/
|
|
232
|
+
applyTransform(layer, handle, dx, dy) {
|
|
233
|
+
if (!this.originalBounds || !this.originalImageData) return;
|
|
234
|
+
|
|
235
|
+
const minSize = 10;
|
|
236
|
+
|
|
237
|
+
// Calculate new bounds based on delta from start point
|
|
238
|
+
const totalDx = this.currentPoint.x - this.startPoint.x;
|
|
239
|
+
const totalDy = this.currentPoint.y - this.startPoint.y;
|
|
240
|
+
|
|
241
|
+
let newX = this.originalBounds.x;
|
|
242
|
+
let newY = this.originalBounds.y;
|
|
243
|
+
let newWidth = this.originalBounds.width;
|
|
244
|
+
let newHeight = this.originalBounds.height;
|
|
245
|
+
|
|
246
|
+
switch (handle) {
|
|
247
|
+
case 'nw':
|
|
248
|
+
newX += totalDx;
|
|
249
|
+
newY += totalDy;
|
|
250
|
+
newWidth -= totalDx;
|
|
251
|
+
newHeight -= totalDy;
|
|
252
|
+
break;
|
|
253
|
+
case 'n':
|
|
254
|
+
newY += totalDy;
|
|
255
|
+
newHeight -= totalDy;
|
|
256
|
+
break;
|
|
257
|
+
case 'ne':
|
|
258
|
+
newY += totalDy;
|
|
259
|
+
newWidth += totalDx;
|
|
260
|
+
newHeight -= totalDy;
|
|
261
|
+
break;
|
|
262
|
+
case 'e':
|
|
263
|
+
newWidth += totalDx;
|
|
264
|
+
break;
|
|
265
|
+
case 'se':
|
|
266
|
+
newWidth += totalDx;
|
|
267
|
+
newHeight += totalDy;
|
|
268
|
+
break;
|
|
269
|
+
case 's':
|
|
270
|
+
newHeight += totalDy;
|
|
271
|
+
break;
|
|
272
|
+
case 'sw':
|
|
273
|
+
newX += totalDx;
|
|
274
|
+
newWidth -= totalDx;
|
|
275
|
+
newHeight += totalDy;
|
|
276
|
+
break;
|
|
277
|
+
case 'w':
|
|
278
|
+
newX += totalDx;
|
|
279
|
+
newWidth -= totalDx;
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Enforce minimum size
|
|
284
|
+
if (newWidth < minSize) {
|
|
285
|
+
if (handle.includes('w')) {
|
|
286
|
+
newX = this.originalBounds.x + this.originalBounds.width - minSize;
|
|
287
|
+
}
|
|
288
|
+
newWidth = minSize;
|
|
289
|
+
}
|
|
290
|
+
if (newHeight < minSize) {
|
|
291
|
+
if (handle.includes('n')) {
|
|
292
|
+
newY = this.originalBounds.y + this.originalBounds.height - minSize;
|
|
293
|
+
}
|
|
294
|
+
newHeight = minSize;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
newWidth = Math.round(newWidth);
|
|
298
|
+
newHeight = Math.round(newHeight);
|
|
299
|
+
|
|
300
|
+
// Apply position change
|
|
301
|
+
layer.position.x = newX;
|
|
302
|
+
layer.position.y = newY;
|
|
303
|
+
|
|
304
|
+
// Resize from original image to prevent quality loss
|
|
305
|
+
if (newWidth !== layer.width || newHeight !== layer.height) {
|
|
306
|
+
// Create temp canvas with original image
|
|
307
|
+
const tempCanvas = document.createElement('canvas');
|
|
308
|
+
tempCanvas.width = this.originalImageData.width;
|
|
309
|
+
tempCanvas.height = this.originalImageData.height;
|
|
310
|
+
const tempCtx = tempCanvas.getContext('2d');
|
|
311
|
+
tempCtx.putImageData(this.originalImageData, 0, 0);
|
|
312
|
+
|
|
313
|
+
// Resize layer canvas
|
|
314
|
+
layer.canvas.width = newWidth;
|
|
315
|
+
layer.canvas.height = newHeight;
|
|
316
|
+
layer.width = newWidth;
|
|
317
|
+
layer.height = newHeight;
|
|
318
|
+
|
|
319
|
+
// Draw scaled original to layer
|
|
320
|
+
layer.ctx.imageSmoothingEnabled = true;
|
|
321
|
+
layer.ctx.imageSmoothingQuality = 'high';
|
|
322
|
+
layer.ctx.drawImage(tempCanvas, 0, 0, newWidth, newHeight);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Select layer at point
|
|
328
|
+
* @param {Object} point - Point
|
|
329
|
+
*/
|
|
330
|
+
selectLayerAtPoint(point) {
|
|
331
|
+
// Find topmost non-transparent layer at point
|
|
332
|
+
const layers = this.app.layers.getLayers().reverse();
|
|
333
|
+
|
|
334
|
+
for (const layer of layers) {
|
|
335
|
+
if (!layer.visible || !layer.canvas) continue;
|
|
336
|
+
|
|
337
|
+
const x = Math.floor(point.x - layer.position.x);
|
|
338
|
+
const y = Math.floor(point.y - layer.position.y);
|
|
339
|
+
|
|
340
|
+
if (x >= 0 && x < layer.width && y >= 0 && y < layer.height) {
|
|
341
|
+
const pixel = layer.ctx.getImageData(x, y, 1, 1).data;
|
|
342
|
+
if (pixel[3] > 0) {
|
|
343
|
+
this.app.layers.setActiveLayer(layer.id);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Check if layer has any visible content
|
|
352
|
+
* @param {Layer} layer - Layer to check
|
|
353
|
+
* @returns {boolean} True if layer is empty
|
|
354
|
+
*/
|
|
355
|
+
isLayerEmpty(layer) {
|
|
356
|
+
if (!layer || !layer.canvas || !layer.ctx) return true;
|
|
357
|
+
|
|
358
|
+
// Sample pixels to check if layer has any content
|
|
359
|
+
// For performance, we check a grid of points rather than every pixel
|
|
360
|
+
const sampleSize = 20;
|
|
361
|
+
const stepX = Math.max(1, Math.floor(layer.width / sampleSize));
|
|
362
|
+
const stepY = Math.max(1, Math.floor(layer.height / sampleSize));
|
|
363
|
+
|
|
364
|
+
for (let y = 0; y < layer.height; y += stepY) {
|
|
365
|
+
for (let x = 0; x < layer.width; x += stepX) {
|
|
366
|
+
const pixel = layer.ctx.getImageData(x, y, 1, 1).data;
|
|
367
|
+
if (pixel[3] > 0) {
|
|
368
|
+
return false; // Found non-transparent pixel
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return true; // No visible content found
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
renderOverlay(ctx) {
|
|
377
|
+
const layer = this.app.layers.getActiveLayer();
|
|
378
|
+
if (!layer) return;
|
|
379
|
+
|
|
380
|
+
const bounds = this.getLayerBounds(layer);
|
|
381
|
+
const handleSize = 8;
|
|
382
|
+
const canvasWidth = this.app.canvas.width;
|
|
383
|
+
const canvasHeight = this.app.canvas.height;
|
|
384
|
+
|
|
385
|
+
// Draw snap guides
|
|
386
|
+
if (this.options.snapToEdges && this.isDrawing) {
|
|
387
|
+
ctx.save();
|
|
388
|
+
ctx.strokeStyle = '#00FFFF';
|
|
389
|
+
ctx.lineWidth = 1;
|
|
390
|
+
ctx.setLineDash([4, 4]);
|
|
391
|
+
|
|
392
|
+
// Vertical snap guide
|
|
393
|
+
if (this.activeSnaps.x !== null) {
|
|
394
|
+
ctx.beginPath();
|
|
395
|
+
ctx.moveTo(this.activeSnaps.x, 0);
|
|
396
|
+
ctx.lineTo(this.activeSnaps.x, canvasHeight);
|
|
397
|
+
ctx.stroke();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Horizontal snap guide
|
|
401
|
+
if (this.activeSnaps.y !== null) {
|
|
402
|
+
ctx.beginPath();
|
|
403
|
+
ctx.moveTo(0, this.activeSnaps.y);
|
|
404
|
+
ctx.lineTo(canvasWidth, this.activeSnaps.y);
|
|
405
|
+
ctx.stroke();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
ctx.restore();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Draw transform controls
|
|
412
|
+
if (!this.options.showTransform) return;
|
|
413
|
+
|
|
414
|
+
// Don't show bounding box for empty layers
|
|
415
|
+
if (this.isLayerEmpty(layer)) return;
|
|
416
|
+
|
|
417
|
+
// Draw bounding box
|
|
418
|
+
ctx.strokeStyle = '#0066ff';
|
|
419
|
+
ctx.lineWidth = 1;
|
|
420
|
+
ctx.setLineDash([]);
|
|
421
|
+
ctx.strokeRect(bounds.x, bounds.y, bounds.width, bounds.height);
|
|
422
|
+
|
|
423
|
+
// Draw handles
|
|
424
|
+
ctx.fillStyle = '#ffffff';
|
|
425
|
+
ctx.strokeStyle = '#0066ff';
|
|
426
|
+
|
|
427
|
+
const handles = [
|
|
428
|
+
{ x: bounds.x, y: bounds.y },
|
|
429
|
+
{ x: bounds.x + bounds.width / 2, y: bounds.y },
|
|
430
|
+
{ x: bounds.x + bounds.width, y: bounds.y },
|
|
431
|
+
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height / 2 },
|
|
432
|
+
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
|
|
433
|
+
{ x: bounds.x + bounds.width / 2, y: bounds.y + bounds.height },
|
|
434
|
+
{ x: bounds.x, y: bounds.y + bounds.height },
|
|
435
|
+
{ x: bounds.x, y: bounds.y + bounds.height / 2 }
|
|
436
|
+
];
|
|
437
|
+
|
|
438
|
+
handles.forEach(pos => {
|
|
439
|
+
ctx.fillRect(pos.x - handleSize / 2, pos.y - handleSize / 2, handleSize, handleSize);
|
|
440
|
+
ctx.strokeRect(pos.x - handleSize / 2, pos.y - handleSize / 2, handleSize, handleSize);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
getOptionsUI() {
|
|
445
|
+
return {
|
|
446
|
+
autoSelect: {
|
|
447
|
+
type: 'checkbox',
|
|
448
|
+
label: 'Auto-Select Layer',
|
|
449
|
+
value: this.options.autoSelect
|
|
450
|
+
},
|
|
451
|
+
showTransform: {
|
|
452
|
+
type: 'checkbox',
|
|
453
|
+
label: 'Show Transform Controls',
|
|
454
|
+
value: this.options.showTransform
|
|
455
|
+
},
|
|
456
|
+
snapToEdges: {
|
|
457
|
+
type: 'checkbox',
|
|
458
|
+
label: 'Snap to Edges',
|
|
459
|
+
value: this.options.snapToEdges
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
export default MoveTool;
|