Make crop control hitbox bigger
Co-authored-by: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
parent
26d56c2fcc
commit
65ee31efa5
1 changed files with 86 additions and 131 deletions
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import { fabric } from 'fabric';
|
import { fabric } from 'fabric';
|
||||||
import { clamp } from 'lodash';
|
import { clamp } from 'lodash';
|
||||||
|
import { strictAssert } from '../util/assert';
|
||||||
|
|
||||||
export class MediaEditorFabricCropRect extends fabric.Rect {
|
export class MediaEditorFabricCropRect extends fabric.Rect {
|
||||||
static PADDING = 4;
|
static PADDING = 4;
|
||||||
|
@ -14,37 +15,41 @@ export class MediaEditorFabricCropRect extends fabric.Rect {
|
||||||
...(options || {}),
|
...(options || {}),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('modified', this.containBounds.bind(this));
|
this.on('scaling', this.containBounds);
|
||||||
|
this.on('moving', this.containBounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private containBounds() {
|
private containBounds = () => {
|
||||||
if (!this.canvas) {
|
if (!this.canvas) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoom = this.canvas.getZoom() || 1;
|
const zoom = this.canvas.getZoom() ?? 1;
|
||||||
|
const { left, top, width, height } = this.getBoundingRect(true, true);
|
||||||
|
const { scaleX, scaleY } = this;
|
||||||
|
|
||||||
const { left, top, height, width } = this.getBoundingRect();
|
strictAssert(scaleX, 'Expected scaleX to be defined');
|
||||||
|
strictAssert(scaleY, 'Expected scaleY to be defined');
|
||||||
|
|
||||||
const canvasHeight = this.canvas.getHeight();
|
const canvasHeight = this.canvas.getHeight() / zoom;
|
||||||
const canvasWidth = this.canvas.getWidth();
|
const canvasWidth = this.canvas.getWidth() / zoom;
|
||||||
|
|
||||||
const nextLeft = clamp(
|
const padding = MediaEditorFabricCropRect.PADDING / zoom;
|
||||||
left / zoom,
|
|
||||||
MediaEditorFabricCropRect.PADDING / zoom,
|
|
||||||
(canvasWidth - width - MediaEditorFabricCropRect.PADDING) / zoom
|
|
||||||
);
|
|
||||||
const nextTop = clamp(
|
|
||||||
top / zoom,
|
|
||||||
MediaEditorFabricCropRect.PADDING / zoom,
|
|
||||||
(canvasHeight - height - MediaEditorFabricCropRect.PADDING) / zoom
|
|
||||||
);
|
|
||||||
|
|
||||||
this.set('left', nextLeft);
|
const nextLeft = clamp(left, padding, canvasWidth - width - padding);
|
||||||
this.set('top', nextTop);
|
const nextTop = clamp(top, padding, canvasHeight - height - padding);
|
||||||
|
|
||||||
|
const nextScaleX = clamp(scaleX, 0, 1);
|
||||||
|
const nextScaleY = clamp(scaleY, 0, 1);
|
||||||
|
|
||||||
|
this.left = nextLeft;
|
||||||
|
this.top = nextTop;
|
||||||
|
this.scaleX = nextScaleX;
|
||||||
|
this.scaleY = nextScaleY;
|
||||||
|
|
||||||
this.setCoords();
|
this.setCoords();
|
||||||
}
|
this.saveState();
|
||||||
|
};
|
||||||
|
|
||||||
override render(ctx: CanvasRenderingContext2D): void {
|
override render(ctx: CanvasRenderingContext2D): void {
|
||||||
super.render(ctx);
|
super.render(ctx);
|
||||||
|
@ -73,121 +78,71 @@ export class MediaEditorFabricCropRect extends fabric.Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CONTROL_DEFAULT_SIZE = 24;
|
||||||
|
const CONTROL_HITBOX_SIZE = 48;
|
||||||
|
|
||||||
|
enum Corner {
|
||||||
|
TopLeft,
|
||||||
|
TopRight,
|
||||||
|
BottomLeft,
|
||||||
|
BottomRight,
|
||||||
|
}
|
||||||
|
|
||||||
|
const cursorStyle: Record<Corner, string> = {
|
||||||
|
[Corner.TopLeft]: 'nwse-resize',
|
||||||
|
[Corner.TopRight]: 'nesw-resize',
|
||||||
|
[Corner.BottomLeft]: 'nesw-resize',
|
||||||
|
[Corner.BottomRight]: 'nwse-resize',
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMinSize(width: number | undefined): number {
|
||||||
|
return Math.min(width ?? CONTROL_DEFAULT_SIZE, CONTROL_DEFAULT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createControl(corner: Corner) {
|
||||||
|
const onTopSide = corner === Corner.TopLeft || corner === Corner.TopRight;
|
||||||
|
const onLeftSide = corner === Corner.TopLeft || corner === Corner.BottomLeft;
|
||||||
|
return new fabric.Control({
|
||||||
|
x: onLeftSide ? -0.5 : 0.5,
|
||||||
|
y: onTopSide ? -0.5 : 0.5,
|
||||||
|
actionHandler: fabric.controlsUtils.scalingEqually,
|
||||||
|
cursorStyle: cursorStyle[corner],
|
||||||
|
sizeX: CONTROL_HITBOX_SIZE,
|
||||||
|
sizeY: CONTROL_HITBOX_SIZE,
|
||||||
|
|
||||||
|
render: (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
left: number,
|
||||||
|
top: number,
|
||||||
|
_,
|
||||||
|
rect: fabric.Object
|
||||||
|
) => {
|
||||||
|
const WIDTH = getMinSize(rect.width);
|
||||||
|
ctx.save();
|
||||||
|
ctx.fillStyle = '#fff';
|
||||||
|
ctx.strokeStyle = '#fff';
|
||||||
|
ctx.lineWidth = 3;
|
||||||
|
ctx.beginPath();
|
||||||
|
const yStart = onTopSide ? top + WIDTH : top - WIDTH;
|
||||||
|
const yEnd = onTopSide ? top - 2 : top + 2;
|
||||||
|
const xStart = onLeftSide ? left - 2 : left + 2;
|
||||||
|
const xEnd = onLeftSide ? left + WIDTH : left - WIDTH;
|
||||||
|
ctx.moveTo(xStart, yStart);
|
||||||
|
ctx.lineTo(xStart, yEnd);
|
||||||
|
ctx.lineTo(xEnd, yEnd);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.restore();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
MediaEditorFabricCropRect.prototype.controls = {
|
MediaEditorFabricCropRect.prototype.controls = {
|
||||||
tl: new fabric.Control({
|
tl: createControl(Corner.TopLeft),
|
||||||
x: -0.5,
|
tr: createControl(Corner.TopRight),
|
||||||
y: -0.5,
|
bl: createControl(Corner.BottomLeft),
|
||||||
actionHandler: fabric.controlsUtils.scalingEqually,
|
br: createControl(Corner.BottomRight),
|
||||||
cursorStyle: 'nwse-resize',
|
|
||||||
render: (
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
left: number,
|
|
||||||
top: number,
|
|
||||||
_,
|
|
||||||
rect: fabric.Object
|
|
||||||
) => {
|
|
||||||
const WIDTH = getMinSize(rect.width);
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = '#fff';
|
|
||||||
ctx.strokeStyle = '#fff';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(left - 2, top + WIDTH);
|
|
||||||
ctx.lineTo(left - 2, top - 2);
|
|
||||||
ctx.lineTo(left + WIDTH, top - 2);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
tr: new fabric.Control({
|
|
||||||
x: 0.5,
|
|
||||||
y: -0.5,
|
|
||||||
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
||||||
cursorStyle: 'nesw-resize',
|
|
||||||
render: (
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
left: number,
|
|
||||||
top: number,
|
|
||||||
_,
|
|
||||||
rect: fabric.Object
|
|
||||||
) => {
|
|
||||||
const WIDTH = getMinSize(rect.width);
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = '#fff';
|
|
||||||
ctx.strokeStyle = '#fff';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(left + 2, top + WIDTH);
|
|
||||||
ctx.lineTo(left + 2, top - 2);
|
|
||||||
ctx.lineTo(left - WIDTH, top - 2);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
bl: new fabric.Control({
|
|
||||||
x: -0.5,
|
|
||||||
y: 0.5,
|
|
||||||
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
||||||
cursorStyle: 'nesw-resize',
|
|
||||||
render: (
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
left: number,
|
|
||||||
top: number,
|
|
||||||
_,
|
|
||||||
rect: fabric.Object
|
|
||||||
) => {
|
|
||||||
const WIDTH = getMinSize(rect.width);
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = '#fff';
|
|
||||||
ctx.strokeStyle = '#fff';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(left - 2, top - WIDTH);
|
|
||||||
ctx.lineTo(left - 2, top + 2);
|
|
||||||
ctx.lineTo(left + WIDTH, top + 2);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
br: new fabric.Control({
|
|
||||||
x: 0.5,
|
|
||||||
y: 0.5,
|
|
||||||
actionHandler: fabric.controlsUtils.scalingEqually,
|
|
||||||
cursorStyle: 'nwse-resize',
|
|
||||||
render: (
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
left: number,
|
|
||||||
top: number,
|
|
||||||
_,
|
|
||||||
rect: fabric.Object
|
|
||||||
) => {
|
|
||||||
const WIDTH = getMinSize(rect.width);
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.fillStyle = '#fff';
|
|
||||||
ctx.strokeStyle = '#fff';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(left + 2, top - WIDTH);
|
|
||||||
ctx.lineTo(left + 2, top + 2);
|
|
||||||
ctx.lineTo(left - WIDTH, top + 2);
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
ctx.restore();
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
MediaEditorFabricCropRect.prototype.excludeFromExport = true;
|
MediaEditorFabricCropRect.prototype.excludeFromExport = true;
|
||||||
MediaEditorFabricCropRect.prototype.borderColor = '#ffffff';
|
MediaEditorFabricCropRect.prototype.borderColor = '#ffffff';
|
||||||
MediaEditorFabricCropRect.prototype.cornerColor = '#ffffff';
|
MediaEditorFabricCropRect.prototype.cornerColor = '#ffffff';
|
||||||
|
|
||||||
function getMinSize(width: number | undefined): number {
|
|
||||||
return Math.min(width || 24, 24);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue