diff --git a/e2e/tests/controls/hit-regions/index.spec.ts b/e2e/tests/controls/hit-regions/index.spec.ts index fefa34e8874..c1c67fd40e4 100644 --- a/e2e/tests/controls/hit-regions/index.spec.ts +++ b/e2e/tests/controls/hit-regions/index.spec.ts @@ -1,27 +1,47 @@ import { expect, test } from '@playwright/test'; import setup from '../../../setup'; import { CanvasUtil } from '../../../utils/CanvasUtil'; - setup(); -test('Control hit regions', async ({ page }) => { - const canvasUtil = new CanvasUtil(page); - await canvasUtil.executeInBrowser((canvas) => { - const rect = canvas.getActiveObject(); - const render = ({ x, y }: fabric.Point, fill: string) => { - const ctx = canvas.getTopContext(); - ctx.fillStyle = fill; - ctx.beginPath(); - ctx.arc(x, y, 1, 0, Math.PI * 2); - ctx.fill(); - }; - for (let y = 0; y <= canvas.height; y++) { - for (let x = 0; x < canvas.width; x++) { - const point = new fabric.Point(x, y); - rect._findTargetCorner(point, true) && render(point, 'indigo'); - rect._findTargetCorner(point) && render(point, 'magenta'); +for (const vpt of [ + undefined, + { angle: -30 }, + { scaleX: 2, scaleY: 0.5, width: 500 }, +] as const) { + test(`Control hit regions with viewport of ${JSON.stringify( + vpt, + null, + 2 + )}`, async ({ page }) => { + const canvasUtil = new CanvasUtil(page); + await canvasUtil.executeInBrowser((canvas, vpt) => { + const rect = canvas.getActiveObject(); + const center = rect.getCenterPoint(); + vpt && + canvas.setViewportTransform( + fabric.util.multiplyTransformMatrixArray([ + fabric.util.createTranslateMatrix(center.x, center.y), + fabric.util.composeMatrix(vpt), + fabric.util.createTranslateMatrix(-center.x, -center.y), + ]) + ); + vpt?.width && canvas.setDimensions({ width: vpt.width }); + canvas.viewportCenterObject(rect); + const render = ({ x, y }: fabric.Point, fill: string) => { + const ctx = canvas.getTopContext(); + ctx.fillStyle = fill; + ctx.beginPath(); + ctx.arc(x, y, 1, 0, Math.PI * 2); + ctx.fill(); + }; + for (let y = 0; y <= canvas.height; y++) { + for (let x = 0; x < canvas.width; x++) { + const point = new fabric.Point(x, y); + rect._findTargetCorner(point, true) && render(point, 'indigo'); + rect._findTargetCorner(point) && render(point, 'magenta'); + } } - } + }, vpt); + expect(await new CanvasUtil(page).screenshot()).toMatchSnapshot(); }); - expect(await new CanvasUtil(page).screenshot()).toMatchSnapshot(); -}); +} diff --git a/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-angle--30-1.png b/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-angle--30-1.png new file mode 100644 index 00000000000..2325d760067 Binary files /dev/null and b/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-angle--30-1.png differ diff --git a/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-scaleX-2-scaleY-0-5-width-500-1.png b/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-scaleX-2-scaleY-0-5-width-500-1.png new file mode 100644 index 00000000000..44be31d5ec9 Binary files /dev/null and b/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-scaleX-2-scaleY-0-5-width-500-1.png differ diff --git a/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-1.png b/e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-undefined-1.png similarity index 100% rename from e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-1.png rename to e2e/tests/controls/hit-regions/index.spec.ts-snapshots/Control-hit-regions-with-viewport-of-undefined-1.png diff --git a/src/shapes/Object/InteractiveObject.ts b/src/shapes/Object/InteractiveObject.ts index 10678bc1e64..aef95a020a8 100644 --- a/src/shapes/Object/InteractiveObject.ts +++ b/src/shapes/Object/InteractiveObject.ts @@ -1,7 +1,10 @@ import { Point } from '../../Point'; import type { TCornerPoint, TDegree } from '../../typedefs'; import { FabricObject } from './Object'; -import { degreesToRadians } from '../../util/misc/radiansDegreesConversion'; +import { + degreesToRadians, + radiansToDegrees, +} from '../../util/misc/radiansDegreesConversion'; import type { TQrDecomposeOut } from '../../util/misc/matrix'; import { calcDimensionsMatrix, @@ -217,8 +220,11 @@ export class InteractiveFabricObject< calcOCoords(): Record { const vpt = this.getViewportTransform(); const center = this.getCenterPoint(); - const rotation = calcPlaneRotation(this.calcTransformMatrix()); + const rotation = calcPlaneRotation( + multiplyTransformMatrices(vpt, this.calcTransformMatrix()) + ); + // calculate the viewport bbox size const bboxTransform = multiplyTransformMatrixArray([ vpt, createTranslateMatrix(center.x, center.y), @@ -228,24 +234,26 @@ export class InteractiveFabricObject< this.getCoords().map((p) => p.transform(bboxTransform)) ); const size = new Point(bbox.width, bbox.height).scalarAdd(this.padding * 2); - const translation = center.transform(vpt, true); + + // calculate the transform used by controls + const translation = center.transform(vpt); const t = multiplyTransformMatrices( - createTranslateMatrix(vpt[4] + translation.x, vpt[5] + translation.y), + createTranslateMatrix(translation.x, translation.y), createRotationMatrix(rotation) ); - return Object.fromEntries( - Object.entries(this.controls).map(([key, control]) => { - const position = control.positionHandler(size, t, this, control); - return [ - key, - // 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. - Object.assign(position, this._calcCornerCoords(control, position)), - ]; - }) - ); + const controls: Record = {}; + this.forEachControl((control, key) => { + const position = control.positionHandler(size, t, 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. + controls[key] = Object.assign( + position, + this._calcCornerCoords(control, position, radiansToDegrees(rotation)) + ); + }); // debug code /* @@ -260,6 +268,8 @@ export class InteractiveFabricObject< }); } 50); */ + + return controls; } /** @@ -269,8 +279,7 @@ export class InteractiveFabricObject< * @todo evaluate simplification of code switching to circle interaction area at runtime * @private */ - private _calcCornerCoords(control: Control, position: Point) { - const angle = this.getTotalAngle(); + private _calcCornerCoords(control: Control, position: Point, angle: TDegree) { const corner = control.calcCornerCoords( angle, this.cornerSize,