-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
Copy pathInteractiveObject.ts
727 lines (672 loc) · 25 KB
/
InteractiveObject.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
import { Point, ZERO } from '../../Point';
import type { TCornerPoint, TDegree } from '../../typedefs';
import { FabricObject } from './Object';
import { degreesToRadians } from '../../util/misc/radiansDegreesConversion';
import type { TQrDecomposeOut } from '../../util/misc/matrix';
import {
calcDimensionsMatrix,
createRotateMatrix,
createTranslateMatrix,
multiplyTransformMatrices,
qrDecompose,
} from '../../util/misc/matrix';
import type { Control } from '../../controls/Control';
import { sizeAfterTransform } from '../../util/misc/objectTransforms';
import type { ObjectEvents, TPointerEvent } from '../../EventTypeDefs';
import type { Canvas } from '../../canvas/Canvas';
import type { ControlRenderingStyleOverride } from '../../controls/controlRendering';
import type { FabricObjectProps } from './types/FabricObjectProps';
import type { TFabricObjectProps, SerializedObjectProps } from './types';
import { createObjectDefaultControls } from '../../controls/commonControls';
import { interactiveObjectDefaultValues } from './defaultValues';
import { SCALE } from '../../constants';
export type TOCoord = Point & {
corner: TCornerPoint;
touchCorner: TCornerPoint;
};
export type TControlSet = Record<string, Control>;
export type TBorderRenderingStyleOverride = Partial<
Pick<InteractiveFabricObject, 'borderColor' | 'borderDashArray'>
>;
export type TStyleOverride = ControlRenderingStyleOverride &
TBorderRenderingStyleOverride &
Partial<
Pick<InteractiveFabricObject, 'hasBorders' | 'hasControls'> & {
forActiveSelection: boolean;
}
>;
export class InteractiveFabricObject<
Props extends TFabricObjectProps = Partial<FabricObjectProps>,
SProps extends SerializedObjectProps = SerializedObjectProps,
EventSpec extends ObjectEvents = ObjectEvents,
>
extends FabricObject<Props, SProps, EventSpec>
implements FabricObjectProps
{
declare noScaleCache: boolean;
declare snapAngle?: TDegree;
declare snapThreshold?: TDegree;
declare lockMovementX: boolean;
declare lockMovementY: boolean;
declare lockRotation: boolean;
declare lockScalingX: boolean;
declare lockScalingY: boolean;
declare lockSkewingX: boolean;
declare lockSkewingY: boolean;
declare lockScalingFlip: boolean;
declare cornerSize: number;
declare touchCornerSize: number;
declare transparentCorners: boolean;
declare cornerColor: string;
declare cornerStrokeColor: string;
declare cornerStyle: 'rect' | 'circle';
declare cornerDashArray: number[] | null;
declare hasControls: boolean;
declare borderColor: string;
declare borderDashArray: number[] | null;
declare borderOpacityWhenMoving: number;
declare borderScaleFactor: number;
declare hasBorders: boolean;
declare selectionBackgroundColor: string;
declare selectable: boolean;
declare evented: boolean;
declare perPixelTargetFind: boolean;
declare activeOn: 'down' | 'up';
declare hoverCursor: CSSStyleDeclaration['cursor'] | null;
declare moveCursor: CSSStyleDeclaration['cursor'] | null;
/**
* The object's controls' position in viewport coordinates
* Calculated by {@link Control#positionHandler} and {@link Control#calcCornerCoords}, depending on {@link padding}.
* `corner/touchCorner` describe the 4 points forming the interactive area of the corner.
* Used to draw and locate controls.
*/
declare oCoords: Record<string, TOCoord>;
/**
* keeps the value of the last hovered corner during mouse move.
* 0 is no corner, or 'mt', 'ml', 'mtr' etc..
* It should be private, but there is no harm in using it as
* a read-only property.
* this isn't cleaned automatically. Non selected objects may have wrong values
* @type [string]
*/
declare __corner?: string;
/**
* a map of control visibility for this object.
* this was left when controls were introduced to not break the api too much
* this takes priority over the generic control visibility
*/
declare _controlsVisibility: Record<string, boolean>;
/**
* holds the controls for the object.
* controls are added by default_controls.js
*/
declare controls: TControlSet;
/**
* internal boolean to signal the code that the object is
* part of the move action.
*/
declare isMoving?: boolean;
/**
* A boolean used from the gesture module to keep tracking of a scaling
* action when there is no scaling transform in place.
* This is an edge case and is used twice in all codebase.
* Probably added to keep track of some performance issues
* @TODO use git blame to investigate why it was added
* DON'T USE IT. WE WILL TRY TO REMOVE IT
*/
declare _scaling?: boolean;
declare canvas?: Canvas;
static ownDefaults = interactiveObjectDefaultValues;
static getDefaults(): Record<string, any> {
return {
...super.getDefaults(),
...InteractiveFabricObject.ownDefaults,
};
}
/**
* Constructor
* @param {Object} [options] Options object
*/
constructor(options?: Props) {
super();
Object.assign(
this,
(this.constructor as typeof InteractiveFabricObject).createControls(),
InteractiveFabricObject.ownDefaults,
);
this.setOptions(options);
}
/**
* Creates the default control object.
* If you prefer to have on instance of controls shared among all objects
* make this function return an empty object and add controls to the ownDefaults
* @param {Object} [options] Options object
*/
static createControls(): { controls: Record<string, Control> } {
return { controls: createObjectDefaultControls() };
}
/**
* Update width and height of the canvas for cache
* returns true or false if canvas needed resize.
* @private
* @return {Boolean} true if the canvas has been resized
*/
_updateCacheCanvas() {
const targetCanvas = this.canvas;
if (this.noScaleCache && targetCanvas && targetCanvas._currentTransform) {
const transform = targetCanvas._currentTransform,
target = transform.target,
action = transform.action;
if (
this === (target as unknown as this) &&
action &&
action.startsWith(SCALE)
) {
return false;
}
}
return super._updateCacheCanvas();
}
getActiveControl() {
const key = this.__corner;
return key
? {
key,
control: this.controls[key],
coord: this.oCoords[key],
}
: undefined;
}
/**
* Determines which corner is under the mouse cursor, represented by `pointer`.
* This function is return a corner only if the object is the active one.
* This is done to avoid selecting corner of non active object and activating transformations
* rather than drag action. The default behavior of fabricJS is that if you want to transform
* an object, first you select it to show the control set
* @private
* @param {Object} pointer The pointer indicating the mouse position
* @param {boolean} forTouch indicates if we are looking for interaction area with a touch action
* @return {String|Boolean} corner code (tl, tr, bl, br, etc.), or 0 if nothing is found.
*/
findControl(
pointer: Point,
forTouch = false,
): { key: string; control: Control; coord: TOCoord } | undefined {
if (!this.hasControls || !this.canvas) {
return undefined;
}
this.__corner = undefined;
const cornerEntries = Object.entries(this.oCoords);
for (let i = cornerEntries.length - 1; i >= 0; i--) {
const [key, corner] = cornerEntries[i];
const control = this.controls[key];
if (
control.shouldActivate(
key,
this,
pointer,
forTouch ? corner.touchCorner : corner.corner,
)
) {
// this.canvas.contextTop.fillRect(pointer.x - 1, pointer.y - 1, 2, 2);
this.__corner = key;
return { key, control, coord: this.oCoords[key] };
}
}
return undefined;
}
/**
* Calculates the coordinates of the center of each control plus the corners of the control itself
* This basically just delegates to each control positionHandler
* WARNING: changing what is passed to positionHandler is a breaking change, since position handler
* is a public api and should be done just if extremely necessary
* @return {Record<string, TOCoord>}
*/
calcOCoords(): Record<string, TOCoord> {
const vpt = this.getViewportTransform(),
center = this.getCenterPoint(),
tMatrix = createTranslateMatrix(center.x, center.y),
rMatrix = createRotateMatrix({
angle: this.getTotalAngle() - (!!this.group && this.flipX ? 180 : 0),
}),
positionMatrix = multiplyTransformMatrices(tMatrix, rMatrix),
startMatrix = multiplyTransformMatrices(vpt, positionMatrix),
finalMatrix = multiplyTransformMatrices(startMatrix, [
1 / vpt[0],
0,
0,
1 / vpt[3],
0,
0,
]),
transformOptions = this.group
? qrDecompose(this.calcTransformMatrix())
: undefined;
// decomposing could bring negative scaling and `_calculateCurrentDimensions` can't take it
if (transformOptions) {
transformOptions.scaleX = Math.abs(transformOptions.scaleX);
transformOptions.scaleY = Math.abs(transformOptions.scaleY);
}
const dim = this._calculateCurrentDimensions(transformOptions),
coords: Record<string, TOCoord> = {};
this.forEachControl((control, key) => {
const position = control.positionHandler(dim, finalMatrix, this, control);
// coords[key] are sometimes used as points. Those are points to which we add
// the property corner and touchCorner from `_calcCornerCoords`.
// don't remove this assign for an object spread.
coords[key] = Object.assign(
position,
this._calcCornerCoords(control, position),
);
});
// debug code
/*
const canvas = this.canvas;
setTimeout(function () {
if (!canvas) return;
canvas.contextTop.clearRect(0, 0, 700, 700);
canvas.contextTop.fillStyle = 'green';
Object.keys(coords).forEach(function(key) {
const control = coords[key];
canvas.contextTop.fillRect(control.x, control.y, 3, 3);
});
} 50);
*/
return coords;
}
/**
* Sets the coordinates that determine the interaction area of each control
* note: if we would switch to ROUND corner area, all of this would disappear.
* everything would resolve to a single point and a pythagorean theorem for the distance
* @todo evaluate simplification of code switching to circle interaction area at runtime
* @private
*/
private _calcCornerCoords(control: Control, position: Point) {
const angle = this.getTotalAngle();
const corner = control.calcCornerCoords(
angle,
this.cornerSize,
position.x,
position.y,
false,
this,
);
const touchCorner = control.calcCornerCoords(
angle,
this.touchCornerSize,
position.x,
position.y,
true,
this,
);
return { corner, touchCorner };
}
/**
* @override set controls' coordinates as well
* See {@link https://github.com/fabricjs/fabric.js/wiki/When-to-call-setCoords} and {@link http://fabricjs.com/fabric-gotchas}
* @return {void}
*/
setCoords(): void {
super.setCoords();
this.canvas && (this.oCoords = this.calcOCoords());
}
/**
* Calls a function for each control. The function gets called,
* with the control, the control's key and the object that is calling the iterator
* @param {Function} fn function to iterate over the controls over
*/
forEachControl(
fn: (
control: Control,
key: string,
fabricObject: InteractiveFabricObject,
) => any,
) {
for (const i in this.controls) {
fn(this.controls[i], i, this);
}
}
/**
* Draws a colored layer behind the object, inside its selection borders.
* Requires public options: padding, selectionBackgroundColor
* this function is called when the context is transformed
* has checks to be skipped when the object is on a staticCanvas
* @todo evaluate if make this disappear in favor of a pre-render hook for objects
* this was added by Andrea Bogazzi to make possible some feature for work reasons
* it seemed a good option, now is an edge case
* @param {CanvasRenderingContext2D} ctx Context to draw on
*/
drawSelectionBackground(ctx: CanvasRenderingContext2D): void {
if (
!this.selectionBackgroundColor ||
(this.canvas && (this.canvas._activeObject as unknown as this) !== this)
) {
return;
}
ctx.save();
const center = this.getRelativeCenterPoint(),
wh = this._calculateCurrentDimensions(),
vpt = this.getViewportTransform();
ctx.translate(center.x, center.y);
ctx.scale(1 / vpt[0], 1 / vpt[3]);
ctx.rotate(degreesToRadians(this.angle));
ctx.fillStyle = this.selectionBackgroundColor;
ctx.fillRect(-wh.x / 2, -wh.y / 2, wh.x, wh.y);
ctx.restore();
}
/**
* @public override this function in order to customize the drawing of the control box, e.g. rounded corners, different border style.
* @param {CanvasRenderingContext2D} ctx ctx is rotated and translated so that (0,0) is at object's center
* @param {Point} size the control box size used
*/
strokeBorders(ctx: CanvasRenderingContext2D, size: Point): void {
ctx.strokeRect(-size.x / 2, -size.y / 2, size.x, size.y);
}
/**
* @private
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @param {Point} size
* @param {TStyleOverride} styleOverride object to override the object style
*/
_drawBorders(
ctx: CanvasRenderingContext2D,
size: Point,
styleOverride: TStyleOverride = {},
): void {
const options = {
hasControls: this.hasControls,
borderColor: this.borderColor,
borderDashArray: this.borderDashArray,
...styleOverride,
};
ctx.save();
ctx.strokeStyle = options.borderColor;
this._setLineDash(ctx, options.borderDashArray);
this.strokeBorders(ctx, size);
options.hasControls && this.drawControlsConnectingLines(ctx, size);
ctx.restore();
}
/**
* Renders controls and borders for the object
* the context here is not transformed
* @todo move to interactivity
* @param {CanvasRenderingContext2D} ctx Context to render on
* @param {TStyleOverride} [styleOverride] properties to override the object style
*/
_renderControls(
ctx: CanvasRenderingContext2D,
styleOverride: TStyleOverride = {},
) {
const { hasBorders, hasControls } = this;
const styleOptions = {
hasBorders,
hasControls,
...styleOverride,
};
const vpt = this.getViewportTransform(),
shouldDrawBorders = styleOptions.hasBorders,
shouldDrawControls = styleOptions.hasControls;
const matrix = multiplyTransformMatrices(vpt, this.calcTransformMatrix());
const options = qrDecompose(matrix);
ctx.save();
ctx.translate(options.translateX, options.translateY);
ctx.lineWidth = this.borderScaleFactor; // 1 * this.borderScaleFactor;
// since interactive groups have been introduced, an object could be inside a group and needing controls
// the following equality check `this.group === this.parent` covers:
// object without a group ( undefined === undefined )
// object inside a group
// excludes object inside a group but multi selected since group and parent will differ in value
if (this.group === this.parent) {
ctx.globalAlpha = this.isMoving ? this.borderOpacityWhenMoving : 1;
}
if (this.flipX) {
options.angle -= 180;
}
ctx.rotate(degreesToRadians(this.group ? options.angle : this.angle));
shouldDrawBorders && this.drawBorders(ctx, options, styleOverride);
shouldDrawControls && this.drawControls(ctx, styleOverride);
ctx.restore();
}
/**
* Draws borders of an object's bounding box.
* Requires public properties: width, height
* Requires public options: padding, borderColor
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @param {object} options object representing current object parameters
* @param {TStyleOverride} [styleOverride] object to override the object style
*/
drawBorders(
ctx: CanvasRenderingContext2D,
options: TQrDecomposeOut,
styleOverride: TStyleOverride,
): void {
let size;
if ((styleOverride && styleOverride.forActiveSelection) || this.group) {
const bbox = sizeAfterTransform(
this.width,
this.height,
calcDimensionsMatrix(options),
),
stroke = !this.isStrokeAccountedForInDimensions()
? (this.strokeUniform
? new Point().scalarAdd(this.canvas ? this.canvas.getZoom() : 1)
: // this is extremely confusing. options comes from the upper function
// and is the qrDecompose of a matrix that takes in account zoom too
new Point(options.scaleX, options.scaleY)
).scalarMultiply(this.strokeWidth)
: ZERO;
size = bbox
.add(stroke)
.scalarAdd(this.borderScaleFactor)
.scalarAdd(this.padding * 2);
} else {
size = this._calculateCurrentDimensions().scalarAdd(
this.borderScaleFactor,
);
}
this._drawBorders(ctx, size, styleOverride);
}
/**
* Draws lines from a borders of an object's bounding box to controls that have `withConnection` property set.
* Requires public properties: width, height
* Requires public options: padding, borderColor
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @param {Point} size object size x = width, y = height
*/
drawControlsConnectingLines(
ctx: CanvasRenderingContext2D,
size: Point,
): void {
let shouldStroke = false;
ctx.beginPath();
this.forEachControl((control, key) => {
// in this moment, the ctx is centered on the object.
// width and height of the above function are the size of the bbox.
if (control.withConnection && control.getVisibility(this, key)) {
// reset movement for each control
shouldStroke = true;
ctx.moveTo(control.x * size.x, control.y * size.y);
ctx.lineTo(
control.x * size.x + control.offsetX,
control.y * size.y + control.offsetY,
);
}
});
shouldStroke && ctx.stroke();
}
/**
* Draws corners of an object's bounding box.
* Requires public properties: width, height
* Requires public options: cornerSize, padding
* Be aware that since fabric 6.0 this function does not call setCoords anymore.
* setCoords needs to be called manually if the object of which we are rendering controls
* is outside the standard selection and transform process.
* @param {CanvasRenderingContext2D} ctx Context to draw on
* @param {ControlRenderingStyleOverride} styleOverride object to override the object style
*/
drawControls(
ctx: CanvasRenderingContext2D,
styleOverride: ControlRenderingStyleOverride = {},
) {
ctx.save();
const retinaScaling = this.getCanvasRetinaScaling();
const { cornerStrokeColor, cornerDashArray, cornerColor } = this;
const options = {
cornerStrokeColor,
cornerDashArray,
cornerColor,
...styleOverride,
};
ctx.setTransform(retinaScaling, 0, 0, retinaScaling, 0, 0);
ctx.strokeStyle = ctx.fillStyle = options.cornerColor;
if (!this.transparentCorners) {
ctx.strokeStyle = options.cornerStrokeColor;
}
this._setLineDash(ctx, options.cornerDashArray);
this.forEachControl((control, key) => {
if (control.getVisibility(this, key)) {
const p = this.oCoords[key];
control.render(ctx, p.x, p.y, options, this);
}
});
ctx.restore();
}
/**
* Returns true if the specified control is visible, false otherwise.
* @param {string} controlKey The key of the control. Possible values are usually 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr',
* but since the control api allow for any control name, can be any string.
* @returns {boolean} true if the specified control is visible, false otherwise
*/
isControlVisible(controlKey: string): boolean {
return (
this.controls[controlKey] &&
this.controls[controlKey].getVisibility(this, controlKey)
);
}
/**
* Sets the visibility of the specified control.
* please do not use.
* @param {String} controlKey The key of the control. Possible values are 'tl', 'tr', 'br', 'bl', 'ml', 'mt', 'mr', 'mb', 'mtr'.
* but since the control api allow for any control name, can be any string.
* @param {Boolean} visible true to set the specified control visible, false otherwise
* @todo discuss this overlap of priority here with the team. Andrea Bogazzi for details
*/
setControlVisible(controlKey: string, visible: boolean) {
if (!this._controlsVisibility) {
this._controlsVisibility = {};
}
this._controlsVisibility[controlKey] = visible;
}
/**
* Sets the visibility state of object controls, this is just a bulk option for setControlVisible;
* @param {Record<string, boolean>} [options] with an optional key per control
* example: {Boolean} [options.bl] true to enable the bottom-left control, false to disable it
*/
setControlsVisibility(options: Record<string, boolean> = {}) {
Object.entries(options).forEach(([controlKey, visibility]) =>
this.setControlVisible(controlKey, visibility),
);
}
/**
* Clears the canvas.contextTop in a specific area that corresponds to the object's bounding box
* that is in the canvas.contextContainer.
* This function is used to clear pieces of contextTop where we render ephemeral effects on top of the object.
* Example: blinking cursor text selection, drag effects.
* @todo discuss swapping restoreManually with a renderCallback, but think of async issues
* @param {Boolean} [restoreManually] When true won't restore the context after clear, in order to draw something else.
* @return {CanvasRenderingContext2D|undefined} canvas.contextTop that is either still transformed
* with the object transformMatrix, or restored to neutral transform
*/
clearContextTop(
restoreManually?: boolean,
): CanvasRenderingContext2D | undefined {
if (!this.canvas) {
return;
}
const ctx = this.canvas.contextTop;
if (!ctx) {
return;
}
const v = this.canvas.viewportTransform;
ctx.save();
ctx.transform(v[0], v[1], v[2], v[3], v[4], v[5]);
this.transform(ctx);
// we add 4 pixel, to be sure to do not leave any pixel out
const width = this.width + 4,
height = this.height + 4;
ctx.clearRect(-width / 2, -height / 2, width, height);
restoreManually || ctx.restore();
return ctx;
}
/**
* This callback function is called every time _discardActiveObject or _setActiveObject
* try to to deselect this object. If the function returns true, the process is cancelled
* @param {Object} [_options] options sent from the upper functions
* @param {TPointerEvent} [options.e] event if the process is generated by an event
* @param {FabricObject} [options.object] next object we are setting as active, and reason why
* this is being deselected
*/
onDeselect(_options?: {
e?: TPointerEvent;
object?: InteractiveFabricObject;
}): boolean {
// implemented by sub-classes, as needed.
return false;
}
/**
* This callback function is called every time _discardActiveObject or _setActiveObject
* try to to select this object. If the function returns true, the process is cancelled
* @param {Object} [_options] options sent from the upper functions
* @param {Event} [_options.e] event if the process is generated by an event
*/
onSelect(_options?: { e?: TPointerEvent }): boolean {
// implemented by sub-classes, as needed.
return false;
}
/**
* Override to customize Drag behavior
* Fired from {@link Canvas#_onMouseMove}
* @returns true in order for the window to start a drag session
*/
shouldStartDragging(_e: TPointerEvent) {
return false;
}
/**
* Override to customize Drag behavior\
* Fired once a drag session has started
* @returns true to handle the drag event
*/
onDragStart(_e: DragEvent) {
return false;
}
/**
* Override to customize drag and drop behavior
* @public
* @param {DragEvent} _e
* @returns {boolean} true if the object currently dragged can be dropped on the target
*/
canDrop(_e: DragEvent): boolean {
return false;
}
/**
* Override to customize drag and drop behavior
* render a specific effect when an object is the source of a drag event
* example: render the selection status for the part of text that is being dragged from a text object
* @public
* @param {DragEvent} _e
*/
renderDragSourceEffect(_e: DragEvent) {
// for subclasses
}
/**
* Override to customize drag and drop behavior
* render a specific effect when an object is the target of a drag event
* used to show that the underly object can receive a drop, or to show how the
* object will change when dropping. example: show the cursor where the text is about to be dropped
* @public
* @param {DragEvent} _e
*/
renderDropTargetEffect(_e: DragEvent) {
// for subclasses
}
}