Skip to content

Commit

Permalink
feat(Controls) Add handlers to modify polygon points (#8556)
Browse files Browse the repository at this point in the history
  • Loading branch information
luizzappa committed Jan 9, 2023
1 parent 818a134 commit a7696d7
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- feat(PolyControl): modify the shape of a poly with control points [#8556](https://github.com/fabricjs/fabric.js/pull/8556)
- BREAKING: remove Object.stateful and Object.statefulCache [#8573](https://github.com/fabricjs/fabric.js/pull/8573)
- fix(IText): refactor clearing context top logic of itext to align with brush pattern, using the canvas rendering cycle in order to guard from edge cases #8560
- fix(Canvas): `_initRetinaScaling` initializaing the scaling regardless of settings in Canvas. [#8565](https://github.com/fabricjs/fabric.js/pull/8565)
Expand Down
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ import {
renderSquareControl,
} from './src/controls/controls.render';
import { dragHandler } from './src/controls/drag';
import { createPolyControls } from './src/controls/polyControl';
import {
rotationStyleHandler,
rotationWithSnapping,
Expand Down Expand Up @@ -306,6 +307,7 @@ const controlsUtils = {
skewHandlerX,
skewHandlerY,
dragHandler,
createPolyControls,
scaleOrSkewActionName,
rotationStyleHandler,
wrapWithFixedAnchor,
Expand Down
135 changes: 135 additions & 0 deletions src/controls/polyControl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Point } from '../point.class';
import { Control } from './control.class';
import { TMat2D } from '../typedefs';
import { iMatrix } from '../constants';
import type { Polyline } from '../shapes/polyline.class';
import { multiplyTransformMatrices } from '../util/misc/matrix';
import {
TPointerEvent,
Transform,
TransformActionHandler,
} from '../EventTypeDefs';
import { getLocalPoint } from './util';

type TTransformAnchor = Transform & { pointIndex: number };

const getSize = (poly: Polyline) => {
return new Point(poly.width, poly.height);
};

/**
* This function locates the controls.
* It'll be used both for drawing and for interaction.
*/
const factoryPolyPositionHandler = (pointIndex: number) => {
return function (dim: Point, finalMatrix: TMat2D, polyObject: Polyline) {
const x = polyObject.points[pointIndex].x - polyObject.pathOffset.x,
y = polyObject.points[pointIndex].y - polyObject.pathOffset.y;
return new Point(x, y).transform(
multiplyTransformMatrices(
polyObject.canvas?.viewportTransform ?? iMatrix,
polyObject.calcTransformMatrix()
)
);
};
};

/**
* This function defines what the control does.
* It'll be called on every mouse move after a control has been clicked and is being dragged.
* The function receives as argument the mouse event, the current transform object
* and the current position in canvas coordinate `transform.target` is a reference to the
* current object being transformed.
*/
const polyActionHandler = (
eventData: TPointerEvent,
transform: TTransformAnchor,
x: number,
y: number
) => {
const poly = transform.target as Polyline,
pointIndex = transform.pointIndex,
mouseLocalPosition = getLocalPoint(transform, 'center', 'center', x, y),
polygonBaseSize = getSize(poly),
size = poly._getTransformedDimensions(),
sizeFactor = polygonBaseSize.divide(size),
adjustFlip = new Point(poly.flipX ? -1 : 1, poly.flipY ? -1 : 1);

const finalPointPosition = mouseLocalPosition
.multiply(adjustFlip)
.multiply(sizeFactor)
.add(poly.pathOffset);

poly.points[pointIndex] = finalPointPosition;
poly.setDimensions();

return true;
};

/**
* Keep the polygon in the same position when we change its `width`/`height`/`top`/`left`.
*/
const anchorWrapper = (
pointIndex: number,
fn: TransformActionHandler<TTransformAnchor>
) => {
return function (
eventData: TPointerEvent,
transform: Transform,
x: number,
y: number
) {
const poly = transform.target as Polyline,
anchorIndex = (pointIndex > 0 ? pointIndex : poly.points.length) - 1,
pointInParentPlane = new Point(
poly.points[anchorIndex].x - poly.pathOffset.x,
poly.points[anchorIndex].y - poly.pathOffset.y
).transform(poly.calcOwnMatrix()),
actionPerformed = fn(eventData, { ...transform, pointIndex }, x, y),
polygonBaseSize = getSize(poly),
adjustFlip = new Point(poly.flipX ? -1 : 1, poly.flipY ? -1 : 1);

const newPosition = new Point(
poly.points[anchorIndex].x,
poly.points[anchorIndex].y
)
.subtract(poly.pathOffset)
.divide(polygonBaseSize)
.multiply(adjustFlip);

poly.setPositionByOrigin(
pointInParentPlane,
newPosition.x + 0.5,
newPosition.y + 0.5
);
return actionPerformed;
};
};

export function createPolyControls(
poly: Polyline,
options?: Partial<Control>
): Record<string, Control>;
export function createPolyControls(
numOfControls: number,
options?: Partial<Control>
): Record<string, Control>;
export function createPolyControls(
arg0: number | Polyline,
options: Partial<Control> = {}
) {
const controls = {} as Record<string, Control>;
for (
let idx = 0;
idx < (typeof arg0 === 'number' ? arg0 : arg0.points.length);
idx++
) {
controls[`p${idx}`] = new Control({
actionName: 'modifyPoly',
positionHandler: factoryPolyPositionHandler(idx),
actionHandler: anchorWrapper(idx, polyActionHandler),
...options,
});
}
return controls;
}
2 changes: 1 addition & 1 deletion src/shapes/polyline.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export class Polyline extends FabricObject {
*
* @private
*/
_getTransformedDimensions(options: any) {
_getTransformedDimensions(options?: any) {
return this.exactBoundingBox
? super._getTransformedDimensions({
...(options || {}),
Expand Down

0 comments on commit a7696d7

Please sign in to comment.