UNPKG

@aurigma/design-atoms

Version:

Design Atoms is a part of Customer's Canvas SDK which allows for manipulating individual design elements through your code.

465 lines 25.1 kB
import { RectangleF, PointF, EqualsOfFloatNumbers, RotatedRectangleF, getTriangleAngle, ConvertDegreeToRadian, getSquareDistanceToPoint } from "@aurigma/design-atoms-model/Math"; import { assign } from "@aurigma/design-atoms-model/Utils/Utils"; import { SelectionHandler } from "../Services/Selection/SelectionHandler"; import { BaseRectangleItemHandler } from "../ItemHandlers"; import { ItemUtils } from "../Utils/ItemUtils"; import { GridItemHandler } from "../ItemHandlers/GridItemHandler"; import { MockupContainer } from "@aurigma/design-atoms-model/Product"; import { SafetyLinesHandler } from "./SafetyLinesHandler"; import { SnapElementType } from "./Interfaces"; import { getRotatedRectangleWithBorder } from "../Utils/Math"; export class SnapLinesHandler { constructor(conf) { this.setConfiguration(conf); this._snapData = { xAnchors: [], yAnchors: [], defaultBounds: null, activeXAnchorIndex: null, activeYAnchorIndex: null, //points of startRectangle (in points) through which snapline can pass xPoints: [], yPoints: [], activeXPointIndex: null, activeYPointIndex: null }; } get currentItemRectangle() { return this._currentItemRectangle; } setConfiguration(conf) { this._conf = assign(this._getDefaultConf, [conf || {}]); } get configuration() { return this._conf; } resetActiveLines() { this._snapData.activeXAnchorIndex = null; this._snapData.activeYAnchorIndex = null; } constrainMoveDataToSnapLines(moveData, canvas, allowMoveHorizontal, allowMoveVertical) { this._currentItemRectangle = null; if (!this._enabled) return moveData.delta; const resultDiff = moveData.delta.clone(); this._snapData.activeXAnchorIndex = null; if (allowMoveHorizontal) { const snapResultX = this._findSnap(canvas, moveData.delta.x, moveData.momentDelta.x, this._snapData.xAnchors, this._snapData.xPoints); resultDiff.x = snapResultX.diffValue; this._snapData.activeXAnchorIndex = snapResultX.activeAnchorIndex; this._snapData.activeXPointIndex = snapResultX.activePointIndex; } this._snapData.activeYAnchorIndex = null; if (allowMoveVertical) { const snapResultY = this._findSnap(canvas, moveData.delta.y, moveData.momentDelta.y, this._snapData.yAnchors, this._snapData.yPoints); resultDiff.y = snapResultY.diffValue; this._snapData.activeYAnchorIndex = snapResultY.activeAnchorIndex; this._snapData.activeYPointIndex = snapResultY.activePointIndex; } if (moveData.startRectangle) { const curRotRect = moveData.startRectangle.clone(); curRotRect.translate(resultDiff.x, resultDiff.y); this._currentItemRectangle = getRotatedRectangleWithBorder(curRotRect, moveData.border); } return resultDiff; } _findSnap(canvas, diff, direction, anchors, points) { let result = new SnapLinesHandler.SnapResult(); result.diffValue = diff; var anchorStart = direction > 0 ? 0 : anchors.length - 1; var anchorEnd = direction > 0 ? anchors.length - 1 : 0; var anchorStep = direction > 0 ? 1 : -1; var pointStart = direction > 0 ? points.length - 1 : 0; var pointEnd = direction > 0 ? 0 : points.length - 1; var pointStep = direction > 0 ? -1 : 1; for (var anchorIndex = anchorStart; anchorIndex !== (anchorEnd + anchorStep); anchorIndex += anchorStep) { var anchor = anchors[anchorIndex]; for (var pointIndex = pointStart; pointIndex !== (pointEnd + pointStep); pointIndex += pointStep) { var yPoint = points[pointIndex]; const tolerance = this._getSnapLineTolerance(canvas, anchor.type); if (Math.abs(yPoint + diff - anchor.position) >= tolerance) continue; const oldTypeConf = result.activeAnchorIndex != null ? this._conf.snapElements[anchors[result.activeAnchorIndex].type] : null; const currentTypeConf = this._conf.snapElements[anchor.type]; if (result.activeAnchorIndex == null || currentTypeConf.priority > oldTypeConf.priority) { result.diffValue = anchor.position - yPoint; result.activeAnchorIndex = anchorIndex; result.activePointIndex = pointIndex; } } } return result; } getVerticalLineData() { if (this._snapData.activeXAnchorIndex == null) return null; const xAnchor = this._snapData.xAnchors[this._snapData.activeXAnchorIndex]; const config = this._conf.snapElements[xAnchor.type]; return { anchor: xAnchor, config: config }; } getHorizontalLineData() { if (this._snapData.activeYAnchorIndex == null) return null; const yAnchor = this._snapData.yAnchors[this._snapData.activeYAnchorIndex]; const config = this._conf.snapElements[yAnchor.type]; return { anchor: yAnchor, config: config }; } constrainRectangleToSnapLines(rect, oldRectWithBorder, arbitraryResize, resizeIndex, rectWithFramesAndBorder, rectWithBorder, canvas, border) { this._currentItemRectangle = null; if (!this._enabled) return rect; var r = rect.clone(); this._snapData.xPoints = []; this._snapData.yPoints = []; var activePoint; //the point that the user pulls var activeFramePoint; //the point corresponding to activePoint, taking into account its frame var activePointIndex = resizeIndex - 1; if (resizeIndex > 4) { activeFramePoint = new PointF(rectWithFramesAndBorder.centerX + SelectionHandler.cw[resizeIndex] * (rectWithFramesAndBorder.width / 2), rectWithFramesAndBorder.centerY + SelectionHandler.ch[resizeIndex] * (rectWithFramesAndBorder.height / 2)); activeFramePoint.rotateAt(r.angle, rectWithFramesAndBorder.center); activePoint = new PointF(rectWithBorder.centerX + SelectionHandler.cw[resizeIndex] * (rectWithBorder.width / 2), rectWithBorder.centerY + SelectionHandler.ch[resizeIndex] * (rectWithBorder.height / 2)); activePoint.rotateAt(r.angle, rectWithBorder.center); } else { activeFramePoint = rectWithFramesAndBorder.getCornerByIndex(activePointIndex); activePoint = rectWithBorder.getCornerByIndex(activePointIndex); } var self = this; var fillSnapData = (fillX = null, fillY = null) => { fillX = fillX != null ? fillX : true; fillY = fillY != null ? fillY : true; if (!fillX && !fillY) return; const snapDataTolerance = this._getSnapLineTolerance(canvas, SnapElementType.Items); if (fillX) { self._snapData.xPoints = [activeFramePoint.x]; if (Math.abs(activeFramePoint.x - activePoint.x) > snapDataTolerance) { self._snapData.xPoints.push(activePoint.x); } } if (fillY) { self._snapData.yPoints = [activeFramePoint.y]; if (Math.abs(activeFramePoint.y - activePoint.y) > snapDataTolerance) { self._snapData.yPoints.push(activePoint.y); } } }; var targetActivePoint; //The resulting point (it belongs to either a frame or the main rectangle) var isFrameActivePoint; if (arbitraryResize) { //When resizing the rectangle by using one of the sides, a snapline appears only if the rotation angle is a multiple of 90. if (rectWithFramesAndBorder.angle % 90 !== 0 && resizeIndex > 4) { return rect; } var center = r.center; //When resizing the rectangle by using sides and rotating at 90 or 270 degrees, we will swap the Width and Height for convenience. var isReverse = resizeIndex > 4 && (r.angle - 90) % 180 === 0; fillSnapData(SelectionHandler.cw[resizeIndex] !== (0 ^ (isReverse ? 1 : 0)), SelectionHandler.ch[resizeIndex] !== (0 ^ (isReverse ? 1 : 0))); const moveData = { delta: new PointF(), momentDelta: new PointF(), startPoint: activeFramePoint, border: border }; var snapLinesDiff = this.constrainMoveDataToSnapLines(moveData, canvas, true, true); if (snapLinesDiff == null || (EqualsOfFloatNumbers(snapLinesDiff.x, 0, 0.1) && EqualsOfFloatNumbers(snapLinesDiff.y, 0, 0.01))) { return rect; } isFrameActivePoint = snapLinesDiff.x !== 0 && this._snapData.activeXPointIndex === 0 || snapLinesDiff.y !== 0 && this._snapData.activeYPointIndex === 0; targetActivePoint = isFrameActivePoint ? activeFramePoint : activePoint; var snapPoint = new PointF(targetActivePoint.x + snapLinesDiff.x, targetActivePoint.y + snapLinesDiff.y); var angle = r.angle; snapPoint.rotateAt(-angle, center); targetActivePoint.rotateAt(-r.angle, center); r.angle = 0; var arbitraryResizeDiff = new PointF((snapPoint.x - targetActivePoint.x) * (isFrameActivePoint ? (r.width / rectWithFramesAndBorder.width) : 1), (snapPoint.y - targetActivePoint.y) * (isFrameActivePoint ? (r.height / rectWithFramesAndBorder.height) : 1)); r.centerX += (arbitraryResizeDiff.x / 2); r.width += SelectionHandler.cw[resizeIndex] * arbitraryResizeDiff.x; r.centerY += (arbitraryResizeDiff.y / 2); r.height += SelectionHandler.ch[resizeIndex] * arbitraryResizeDiff.y; r.rotateAt(angle, center); } else if (!arbitraryResize) { if (oldRectWithBorder.getCornerByIndex(activePointIndex).equals(activePoint, 0.01)) { return rect; } fillSnapData(); const moveData = { delta: new PointF(), momentDelta: new PointF(), startPoint: activePoint, border: border }; var proportionalLinesDiff = this.constrainMoveDataToSnapLines(moveData, canvas, true, true); var ifXDiffIsZero = EqualsOfFloatNumbers(proportionalLinesDiff.x, 0, 0.01); var ifYDiffIsZero = EqualsOfFloatNumbers(proportionalLinesDiff.y, 0, 0.01); if (ifXDiffIsZero && ifYDiffIsZero) { return rect; } isFrameActivePoint = !ifXDiffIsZero && this._snapData.activeXPointIndex === 0 || !ifYDiffIsZero && this._snapData.activeYPointIndex === 0; targetActivePoint = isFrameActivePoint ? activeFramePoint : activePoint; //at the proportional resize we find a static point opposite to the active point. var staticPointIndex = (activePointIndex + 2) % 4; //the opposite index var staticPoint = rectWithBorder.getCornerByIndex(staticPointIndex); //Projection of the static point on the snap line. It's needed to find the angle of the resize direction. var projectionPoint; if (!ifXDiffIsZero) { projectionPoint = new PointF(targetActivePoint.x + proportionalLinesDiff.x, staticPoint.y); } else { projectionPoint = new PointF(staticPoint.x, targetActivePoint.y + proportionalLinesDiff.y); } //Find an angle near the static point for the resize direction. var triangleAngleInDegrees = getTriangleAngle(projectionPoint, targetActivePoint, staticPoint); var triangleAngle = ConvertDegreeToRadian(triangleAngleInDegrees); //Find the difference between the hypotenuse of the triangle and resize the rectangle. var newHypotenuseWidth = Math.sqrt(getSquareDistanceToPoint(staticPoint, projectionPoint)) / Math.cos(triangleAngle); var oldHypotenuseWidth = Math.sqrt(getSquareDistanceToPoint(staticPoint, targetActivePoint)); var multiplier = newHypotenuseWidth / oldHypotenuseWidth; if (multiplier == null) { return rect; } var oldStaticPoint = r.getCornerByIndex(staticPointIndex); r.width *= multiplier; r.height *= multiplier; var newStaticPoint = r.getCornerByIndex(staticPointIndex); r.centerX += (oldStaticPoint.x - newStaticPoint.x); r.centerY += (oldStaticPoint.y - newStaticPoint.y); } this._currentItemRectangle = getRotatedRectangleWithBorder(r.clone(), border); return r; } fillSnapData(startRectangle, startRectangleWithFrames, startRectangleWithBorder, region, interactiveZonesBounds, canvas /*TODO: replace to ProductHandler after refactoring ending*/, isDrag) { var _a; this.clearSnapData(); const printArea = canvas.viewer.surface.printAreas.get(0); if (printArea == null) return; this._snapData.defaultBounds = canvas.viewer.surface.printAreas.get(0).bounds; if (!this._enabled || startRectangle == null) return; var xAnchors = [], yAnchors = []; var getRectLimitPoints = (rect) => { return { vertical: [rect.top, rect.bottom], horizontal: [rect.left, rect.right] }; }; var addRectangleToAnchors = (rectangle, snapElementType, addCenter) => { const cornerPoints = [rectangle.getUpperLeftCorner(), rectangle.getUpperRightCorner(), rectangle.getBottomLeftCorner(), rectangle.getBottomRightCorner()]; const points = addCenter ? [...cornerPoints, rectangle.center] : cornerPoints; const bounds = rectangle.bounds; const center = rectangle.center; const byx = new Map(); const byy = new Map(); points.forEach(pt => { if (region != null && canvas.suppressOutOfRegionManipulation && !region.contains(pt, true)) return; if (EqualsOfFloatNumbers(pt.x, center.x)) { const anchor = { position: pt.x, limitPoints: [bounds.bottom, bounds.top], type: snapElementType }; byx.set(pt.x, anchor); } else if (EqualsOfFloatNumbers(pt.x, bounds.left) || EqualsOfFloatNumbers(pt.x, bounds.right)) { if (!byx.has(pt.x)) { const anchor = { position: pt.x, limitPoints: [pt.y], type: snapElementType }; byx.set(pt.x, anchor); } else { const anchor = byx.get(pt.x); anchor.limitPoints = [...anchor.limitPoints, pt.y]; } } if (EqualsOfFloatNumbers(pt.y, center.y)) { const anchor = { position: pt.y, limitPoints: [bounds.left, bounds.right], type: snapElementType }; byy.set(pt.y, anchor); } else if (EqualsOfFloatNumbers(pt.y, bounds.top) || EqualsOfFloatNumbers(pt.y, bounds.bottom)) { if (!byy.has(pt.y)) { const anchor = { position: pt.y, limitPoints: [pt.x], type: snapElementType }; byy.set(pt.y, anchor); } else { const anchor = byy.get(pt.y); anchor.limitPoints = [...anchor.limitPoints, pt.x]; } } }); xAnchors.push(...byx.values()); yAnchors.push(...byy.values()); }; var addBoundsToAnchors = (bounds, snapElementType) => { var rectangle = region != null && canvas.suppressOutOfRegionManipulation ? RectangleF.intersect(bounds, region) : bounds; if (!rectangle.isEmpty()) { const limitPoints = getRectLimitPoints(rectangle); xAnchors.push({ position: rectangle.left, limitPoints: limitPoints.vertical, rect: rectangle, type: snapElementType }, { position: rectangle.left + rectangle.width / 2, limitPoints: limitPoints.vertical, rect: rectangle, type: snapElementType }, { position: rectangle.right, limitPoints: limitPoints.vertical, rect: rectangle, type: snapElementType }); yAnchors.push({ position: rectangle.top, limitPoints: limitPoints.horizontal, type: snapElementType }, { position: rectangle.top + rectangle.height / 2, limitPoints: limitPoints.horizontal, type: snapElementType }, { position: rectangle.bottom, limitPoints: limitPoints.horizontal, type: snapElementType }); } }; const snapConf = this._conf.snapElements; if (region != null && snapConf.region.enabled) addBoundsToAnchors(region, SnapElementType.Region); if (this._snapData.defaultBounds != null && snapConf.printArea.enabled) addBoundsToAnchors(this._snapData.defaultBounds, SnapElementType.PrintArea); if (interactiveZonesBounds != null && snapConf.interactiveZones.enabled) { interactiveZonesBounds.forEach(x => { addBoundsToAnchors(x, SnapElementType.InteractiveZones); }); } const blackListFilter = (handler) => { return handler instanceof GridItemHandler || //skip grid handler.item.parentContainer instanceof MockupContainer || // skip mockup ItemUtils.isBgContainerItem(handler.item) || ItemUtils.isFgContainerItem(handler.item); //skip bg and fg containers }; var snapableItemHandlers = canvas.getAllItemHandlers({ onlyVisible: true, onlyUnlocked: !snapConf.items.includeLocked, whiteListFilter: SafetyLinesHandler.isSafetyLineItemHandler, blackListFilter: blackListFilter, flatGroupItems: true }); if (snapConf.items.enabled || snapConf.safetyLines.enabled) { snapableItemHandlers.forEach((itemHandler) => { if (!itemHandler.layer.visible) return; if (SafetyLinesHandler.isSafetyLineItemHandler(itemHandler)) { if (!snapConf.safetyLines.enabled) return; if (itemHandler.item.name === `${SafetyLinesHandler.handlerPrefix}_region_shape`) return; var point0 = itemHandler.controlPoints[0]; var point1 = itemHandler.controlPoints[1]; addRectangleToAnchors(RotatedRectangleF.fromRectangleF(RectangleF.fromPoints(point0, point1)), SnapElementType.SafetyLines, false); } else if (!canvas.isItemHandlerSelected(itemHandler) && snapConf.items.enabled) { if (itemHandler instanceof BaseRectangleItemHandler) { const margin = itemHandler.getBorderMargin(); const border = { left: margin, top: margin, right: margin, bottom: margin }; const rectWithBorder = getRotatedRectangleWithBorder(itemHandler.getSelectionRectangle(), border); addRectangleToAnchors(rectWithBorder, SnapElementType.Items, true); } } }); } if (((_a = snapConf.grid) === null || _a === void 0 ? void 0 : _a.enabled) && canvas.viewer.gridHandler.visible) { const grid = canvas.viewer.gridHandler.getGridParameters(); if (grid != null) { for (let i = 0; i <= grid.cols; i++) { xAnchors.push({ position: grid.location.x + i * grid.stepX, type: SnapElementType.Grid }); } for (let i = 0; i <= grid.rows; i++) { yAnchors.push({ position: grid.location.y + i * grid.stepY, type: SnapElementType.Grid }); } } } var sortAndRemoveDuplicatesFromAnchorArray = (array, tolerance = null) => { var sortedArray = array.sort((a, b) => a.position - b.position); var result = []; sortedArray.forEach((value, index) => { const lastElement = result.slice(-1)[0]; if (index === 0 || !EqualsOfFloatNumbers(lastElement.position, value.position, tolerance != null ? tolerance : 0.01) || lastElement.type !== value.type) result.push(value); }); return result; }; var sortAndRemoveDuplicates = (array, tolerance = null) => { var sortedArray = array.sort((a, b) => a - b); var result = []; sortedArray.forEach((value, index) => { if (index === 0 || !EqualsOfFloatNumbers(result.slice(-1)[0], value, tolerance != null ? tolerance : 0.01)) result.push(value); }); return result; }; this._snapData.xAnchors = sortAndRemoveDuplicatesFromAnchorArray(xAnchors); this._snapData.yAnchors = sortAndRemoveDuplicatesFromAnchorArray(yAnchors); if (!isDrag) return; var startRectangleWithFramesAndBorder = startRectangleWithFrames.bounds; this._snapData.xPoints = [ startRectangleWithBorder.left, startRectangle.centerX, startRectangleWithBorder.right ]; this._snapData.yPoints = [ startRectangleWithBorder.top, startRectangle.centerY, startRectangleWithBorder.bottom ]; var snapDataTolerance = this._getSnapLineTolerance(canvas, SnapElementType.Items); this._snapData.xPoints = sortAndRemoveDuplicates([ startRectangleWithFramesAndBorder.left, startRectangleWithBorder.left, startRectangleWithFrames.centerX, startRectangle.centerX, startRectangleWithBorder.right, startRectangleWithFramesAndBorder.right ], snapDataTolerance); this._snapData.yPoints = sortAndRemoveDuplicates([ startRectangleWithFramesAndBorder.top, startRectangleWithBorder.top, startRectangleWithFrames.centerY, startRectangle.centerY, startRectangleWithBorder.bottom, startRectangleWithFramesAndBorder.bottom ], snapDataTolerance); } clearSnapData() { this._snapData.xAnchors = []; this._snapData.yAnchors = []; this._snapData.activeXAnchorIndex = null; this._snapData.activeYAnchorIndex = null; this._snapData.xPoints = []; this._snapData.yPoints = []; } _getSnapLineTolerance(canvas, snapElementType) { return Math.abs(this._conf.snapElements[snapElementType.toString()].tolerance / canvas.zoom); } get _getDefaultConf() { const defaultElementConf = { tolerance: 5, color: "rgb(255,0,255)", enabled: false, priority: 0 }; return { snapElements: { items: Object.assign(Object.assign({}, defaultElementConf), { includeLocked: false }), printArea: Object.assign({}, defaultElementConf), safetyLines: Object.assign({}, defaultElementConf), region: Object.assign({}, defaultElementConf), grid: Object.assign({}, defaultElementConf) } }; } get _enabled() { const snapElements = this._conf.snapElements; return snapElements.items.enabled || snapElements.printArea.enabled || snapElements.region.enabled || snapElements.safetyLines.enabled || snapElements.grid.enabled; } } SnapLinesHandler.SnapResult = class { constructor() { this.diffValue = null; this.activeAnchorIndex = null; this.activePointIndex = null; } }; //# sourceMappingURL=SnapLinesHandler.js.map