UNPKG

plotboilerplate

Version:

A simple javascript plotting boilerplate for 2d stuff.

756 lines (705 loc) 32.1 kB
/** * Interfaces and class for automatically handling Bézier curves. * * @requires AlloyFinger * @requires BezierPath * @requires CubicBezierPath * @requires KeyHandler * @requires MouseHandler * @requires PlotBoilerplate * @requires VertEvent * @requires Vertex * @requires XMouseEvent * @requires XYCoords * * * @author Ikaros Kappler * @date 2020-07-31 * @modified 2020-08-03 Ported this class from vanilla JS to Typescript. * @modified 2020-08-12 Added a distance check before handling the click/tap event. * @modified 2021-01-03 Changed property to `autoAdjustPaths` in the HandlerOptions interface (typo). * @modified 2021-01-03 Added following new functions: `addPathVertexDragStartListeners`, `removePathVertexDragStartListeners`, `addPathVertexDragEndListeners` and `removePathVertexDragEndListeners`. * @modified 2021-03-31 Fixed the issue with the new AlloyFinger (Typescript). * @modified 2022-02-03 Changing the element to catch events (eventCatcher instead of canvas). * @modified 2024-03-10 Fixing some types for Typescript 5 compatibility. * @modified 2025-04-14 Added the `BezierPathInteractionHelper.drawHandleLines` method. * @modified 2025-04-14 Fixing correct event types for touch events in `BezierPathInteractionHelper`. * @modified 2025-04-14 BezierPathInteractionHelper: Changed default value of `HelperOptions.autoAdjustPaths` from `true` to `false`. * @modified 2025-05-05 Added optional params `draw` and `fill` to BezierPathInteractionHelper.drawHandleLines` method. * @modified 2025-05-05 Class `BezierPathInteractionHelper` now implementing `IShapeInteractionHelper`. * @modified 2025-05-07 Tweaking performance of `BezierPathInteractionHelper`: only triggering redraw now when mouse move within given detect range. * @version 1.2.1 * * @file BezierPathInteractionHelper * @public **/ // I would like to use AlloyFinger from the node_modules, but it seems // AlloyTeam has forgotten to publish their d.ts file. import AlloyFinger from "alloyfinger-typescript"; import { BezierPath } from "../../BezierPath"; import { CubicBezierCurve } from "../../CubicBezierCurve"; import { KeyHandler } from "../../KeyHandler"; import { MouseHandler } from "../../MouseHandler"; import { PlotBoilerplate } from "../../PlotBoilerplate"; import { VertListener } from "../../VertexListeners"; import { Vertex } from "../../Vertex"; import { XMouseEvent } from "../../MouseHandler"; import { DrawLib, IShapeInteractionHelper, XYCoords } from "../../interfaces"; import { AFTouchEvent } from "alloyfinger-typescript/src/cjs/alloy_finger"; /** * Handler type for mouse-pointer-moved listeners. */ type OnPointerMoved = (pathIndex: number, pathPoint: Vertex | null, pointerPos: Vertex | null, t: number) => void; /** * Handler type for vertex-inserted listeners. */ type OnVertexInserted = (pathIndex: number, insertIndex: number, newPath: BezierPath, oldPath: BezierPath) => void; /** * Handler type for vertex-removed listeners. */ type OnVerticesDeleted = ( pathIndex: number, removedVertexIndices: Array<number>, newPath: BezierPath, oldPath: BezierPath ) => void; /** * Handler type for path-removed listeners. */ type OnPathRemoved = (pathIndex: number, oldPath: BezierPath) => void; /** * Options passed to the constructor. */ interface HelperOptions { autoAdjustPaths?: boolean; // default true allowPathRemoval?: boolean; // default true maxDetectDistance?: number; // default Number.MAX_VALUE onPointerMoved?: OnPointerMoved; onVertexInserted?: OnVertexInserted; onVerticesDeleted?: OnVerticesDeleted; onPathRemoved?: OnPathRemoved; } /** * @classdesc A helper for adding vertices to and remove vertices from Bézier paths. * By default the 'delete' key is used to remove vertices or paths. * * For convenience this helper is capable of handling multiple paths which are kept * in an array. * * [Demo](https://www.plotboilerplate.io/repo/demos/23-bezier-point-distance/ "Demo") * * @public **/ export class BezierPathInteractionHelper implements IShapeInteractionHelper { /** * @member {PlotBoilerplate} pb * @memberof BezierPathInteractionHelper * @type {PlotBoilerplate} * @instance */ private pb: PlotBoilerplate; private paths: Array<BezierPath>; private onPointerMoved: OnPointerMoved; private onVertexInserted: OnVertexInserted; private onVerticesDeleted: OnVerticesDeleted; private onPathRemoved: OnPathRemoved; private autoAdjustPaths: boolean; private allowPathRemoval: boolean; private maxDetectDistance: number; private mouseIsOver: boolean; private currentPathIndex: number; private currentDistance: number; private currentT: number; private currentA: Vertex; // Position on the curve private currentB: Vertex; // mouse/touch position private _mouseHandler: MouseHandler; private _touchHandler: AlloyFinger; private _keyHandler: KeyHandler; private _mouseEnterListener: () => void; private _mouseLeaveListener: () => void; /** * Pre: all paths must have been added to the PlotBoilerplate's drawable buffer (use the add(Drawable) function). * * The move callback accepts four params: * * The point on the closest curve (Vertex) * * The mouse or touch position (Vertex) * * The curve position (float t) * * The curve index on the array (integer) * * * @constructor * @name BezierPathInteractionHelper * @param {PlotBoilerplate} pb * @param {Array<BezierPath>} paths * @param {boolean} options.autoAdjustPaths - If true then inner path points will be auto-adjusted to keep the curve smooth. * @param {boolean} options.allowPathRemoval - If true then full paths can be removed (by removing selected vertices). * @param {number} maxDetectDistance - The max detection distance. No events will be fired if the mouse/touch pointer is outside this range (default is Number.MAX_VALUE). * @param {function(number,Vertex,Vertex,number)} options.onPointerMoved (pathIndex,pathPoint,pointer,t) * @param {function(number,number,BezierPath,BezierPath)} options.onVertexInserted (pathIndex,insertIndex,newPath,oldPath) * @param {function(number,number[],BezierPath,BezierPath)} options.onVerticesDeleted (pathIndex,removedVertexIndices,newPath,oldPath) * @param {function(number,BezierPath)} options.onPathRemoved (pathIndex,oldPath) **/ constructor(pb: PlotBoilerplate, paths: Array<BezierPath>, options: HelperOptions) { options = options || {}; this.pb = pb; this.paths = []; this.onPointerMoved = typeof options.onPointerMoved === "function" ? options.onPointerMoved : (i: number, a: Vertex | null, b: Vertex | null, t: number) => {}; this.onVertexInserted = typeof options.onVertexInserted === "function" ? options.onVertexInserted : (i: number, j: number, n: BezierPath, o: BezierPath) => {}; this.onVerticesDeleted = typeof options.onVerticesDeleted === "function" ? options.onVerticesDeleted : (i: number, r: Array<number>, n: BezierPath, o: BezierPath) => {}; this.onPathRemoved = typeof options.onPathRemoved === "function" ? options.onPathRemoved : (i: number, o: BezierPath) => {}; this.autoAdjustPaths = typeof options.autoAdjustPaths === "boolean" ? options.autoAdjustPaths : false; // true; this.allowPathRemoval = typeof options.allowPathRemoval === "boolean" ? options.allowPathRemoval : true; this.maxDetectDistance = typeof options.maxDetectDistance === "number" ? options.maxDetectDistance : Number.MAX_VALUE; this.mouseIsOver = false; this.currentPathIndex = -1; this.currentDistance = Number.MAX_VALUE; this.currentT = 0.0; this.currentA = new Vertex(0, 0); // Position on the curve this.currentB = new Vertex(0, 0); // Mouse/Touch position // Rebuild the array to avoid outside manipulations. for (var i = 0; i < paths.length; i++) { this.addPath(paths[i]); } this._mouseEnterListener = () => { this.mouseIsOver = true; }; this._mouseLeaveListener = () => { this.mouseIsOver = false; this._clearMoveEvent(); }; this._mouseHandler = this._installMouseListener(); this._touchHandler = this._installTouchListener(); this._keyHandler = this._installKeyListener(); // Paths might have changed by auto-adjustment. if (this.autoAdjustPaths) { pb.redraw(); } } /** * Manually add a path to this helper. * Note that if `autoAdjustPaths==true` then listeners will be installed to the path's vertices to * keep the path smooth at all times. * * @method addPath * @instance * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The path to add. * @return {boolean} Duplicate path instances cannot be added; function will return false if path already exists. **/ addPath(path: BezierPath): boolean { const pathIndex: number = this._locatePath(path); if (pathIndex != -1) return false; this.paths.push(path); if (this.autoAdjustPaths) BezierPathInteractionHelper.setPathAutoAdjust(path); return true; } /** * Manually remove a path from this helper. * Note that this method ignores the `allowPathRemoval` option. * * @method removePath * @instance * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The path to remove. * @return {boolean} Returns false if the path could not be found. **/ removePath(path: BezierPath): boolean { const pathIndex: number = this._locatePath(path); if (pathIndex == -1) return false; this.removePathAt(pathIndex); return true; } /** * Remove the path at the given index. * * @method removePathAt * @instance * @memberof BezierPathInteractionHelper * @param {number} pathIndex - The index of the path (must be inside bounds, see `this.paths` array). * @return {void} **/ removePathAt(pathIndex: number): void { const path: BezierPath = this.paths[pathIndex]; this.paths = this.paths.filter((value: BezierPath, index: number) => index != pathIndex); this._removeDefaultPathListeners(path); this.pb.remove(path, false, true); // Remove with vertices this.onPathRemoved(pathIndex, path); } /** * Update the inner status by running the distance calculation again with the current settings. * * Call this if any of the properties changed (like maxDetecDistance). * * @method update * @instance * @memberof BezierPathInteractionHelper * @return {void} **/ update(): void { // Just re-run the calculation with the recent mouse/touch position this._handleMoveEvent(this.currentB.x, this.currentB.y); } /** * Draw grey handle lines. * * @param {DrawLib<any>} draw - (optional) The draw library to use. If not provided then `pb.draw` will be used. * @param {DrawLib<any>} fill - (optional) The fill library to use. If not provided then `pb.fill` will be used. */ drawHandleLines(draw?: DrawLib<any>, fill?: DrawLib<any>) { draw = draw || this.pb.draw; fill = fill || this.pb.fill; this.paths.forEach((path: BezierPath) => { path.bezierCurves.forEach((curve: CubicBezierCurve) => { this.pb.draw.line(curve.startPoint, curve.startControlPoint, "rgba(64,192,128,0.333)", 1.0, { dashOffset: 0.0, dashArray: [4, 2] }); this.pb.draw.line(curve.endPoint, curve.endControlPoint, "rgba(64,192,128,0.333)", 1.0, { dashOffset: 0.0, dashArray: [4, 2] }); }); }); } /** * This function should invalidate any installed listeners and invalidate this object. * After calling this function the object might not hold valid data any more and * should not be used any more. * * @method destroy * @instance * @memberof BezierPathInteractionHelper * @return {void} **/ destroy(): void { for (var i = 0; i < this.paths.length; i++) { this._removeDefaultPathListeners(this.paths[i]); // removePathVertexDragStartListeners(this.paths[i], listener); } this.paths = []; this.pb.eventCatcher.removeEventListener("mouseenter", this._mouseEnterListener); this.pb.eventCatcher.removeEventListener("mouseleave", this._mouseLeaveListener); this._mouseHandler.destroy(); this._keyHandler.destroy(); } // +--------------------------------------------------------------------------------- // | A helper function to locate a given path instance inside the array. // | // | @return The index of the path or -1 if not found. // +------------------------------- private _locatePath(path: BezierPath): number { for (var i = 0; i < this.paths.length; i++) { if (this.paths[i] == path) return i; } return -1; } // +--------------------------------------------------------------------------------- // | Handle deletion of any selecte vertex and/or paths. // | Note that this function will trigger a `redraw`. // +------------------------------- private _handleDelete(): void { const pathDeleteIndices: Array<number> = this._handleSingleVertexDelete(); // Remove enqueued paths if (this.allowPathRemoval) { // Remove paths starting with the last (!) index. for (var i = pathDeleteIndices.length - 1; i >= 0; i--) { this.removePathAt(pathDeleteIndices[i]); } } this.pb.redraw(); } // +--------------------------------------------------------------------------------- // | This function removes all selected vertices on the paths without deleting // | full paths (at least two path vertices remaining). // | // | Returned (sorted) array contains indices of those paths that should // | be deleted completely. // +------------------------------- private _handleSingleVertexDelete(): Array<number> { // Check all path points (on all paths) for deletion. // Note: whole paths are not meant to be removed this way. // Keep track of their indices (ascending order) for later removal. const pathDeleteIndices: Array<number> = []; for (var p = 0; p < this.paths.length; p++) { const allVerticesSelected: boolean = this._handleDeleteOnPath(p); if (allVerticesSelected) pathDeleteIndices.push(p); } return pathDeleteIndices; } // +--------------------------------------------------------------------------------- // | This function removes all selected vertices on the given path (index) // | without deleting the full path (at least two path vertices remaining). // | // | Returns true if path should be fully removed, false otherwise. // +------------------------------- private _handleDeleteOnPath(pathIndex: number): boolean { const path: BezierPath = this.paths[pathIndex]; const newCurves: Array<Array<Vertex>> = []; const deletedVertIndices: Array<number> = []; // Find first non-selected path point let curveIndex: number = 0; while (curveIndex < path.bezierCurves.length && path.bezierCurves[curveIndex].startPoint.attr.isSelected) { deletedVertIndices.push(curveIndex), curveIndex++; } // All points selected? Enqueue for deletion. if (curveIndex == path.bezierCurves.length) { // Indicate: path removal required. return true; } // Only keep those curves that have no selected path point (=delete selected) let curStart: Vertex = path.bezierCurves[curveIndex].startPoint; let curStartControl: Vertex = path.bezierCurves[curveIndex].startControlPoint; for (var i = curveIndex; i < path.bezierCurves.length; i++) { if (!path.bezierCurves[i].endPoint.attr.isSelected) { newCurves.push([ curStart.clone(), path.bezierCurves[i].endPoint.clone(), curStartControl.clone(), path.bezierCurves[i].endControlPoint.clone() ]); if (i + 1 < path.bezierCurves.length) { curStart = path.bezierCurves[i].endPoint; curStartControl = path.bezierCurves[i + 1].startControlPoint; } } else { // TODO: remove drag listener from removed vertex! deletedVertIndices.push(i); } } // Do not remove the whole path. // Do not replace the path if no vertices were deleted. if (newCurves.length != 0 && newCurves.length != path.bezierCurves.length) { const newPath: BezierPath = BezierPath.fromArray(newCurves); const oldPath: BezierPath = this.paths[pathIndex]; this._replacePathAt(pathIndex, newPath); this.onVerticesDeleted(pathIndex, deletedVertIndices, newPath, oldPath); // Indicate: no path removal required return false; } else { // Indicate full path removal if no curve would be left. return newCurves.length == 0; } } // +--------------------------------------------------------------------------------- // | This function replaces a path at the given index with a new one (after change // | of vertex count). // +------------------------------- private _replacePathAt(pathIndex: number, newPath: BezierPath): void { const oldPath: BezierPath = this.paths[pathIndex]; this.pb.remove(oldPath, false, true); // Remove with vertices oldPath.destroy(); this._removeDefaultPathListeners(oldPath); BezierPathInteractionHelper.setPathAutoAdjust(newPath); this.paths[pathIndex] = newPath; this.pb.add(newPath); } // +--------------------------------------------------------------------------------- // | Touch and mouse events should call this fuction when the pointer was moved. // +------------------------------- private _handleMoveEvent(posX: number, posY: number): void { const oldA = this.currentA.clone(); const oldB = this.currentB.clone(); const oldDistance = this.currentDistance; const point: XYCoords = this.pb.transformMousePosition(posX, posY); this.currentB.set(point); this._updateMinDistance(); // Always fire even if nothing visually changed? if (this.currentDistance <= this.maxDetectDistance && this.mouseIsOver && this.pb.getDraggedElementCount() == 0) { this.onPointerMoved(this.currentPathIndex, this.currentA, this.currentB, this.currentT); } else { this.onPointerMoved(-1, null, null, 0.0); } // Only redraw when moving inside the detection distance if ( (this.currentDistance <= this.maxDetectDistance || (this.currentDistance > this.maxDetectDistance && oldDistance <= this.maxDetectDistance)) && (!oldA.equals(this.currentA) || !oldB.equals(this.currentB)) ) { this.pb.redraw(); } } // +--------------------------------------------------------------------------------- // | This is called when the mouse pointer leaves the canvas or // | when the touch progress ends. // +------------------------------- private _clearMoveEvent(): void { this.onPointerMoved(-1, null, null, 0.0); this.pb.redraw(); } // +--------------------------------------------------------------------------------- // | Called once upon initialization. // +------------------------------- private _installTouchListener(): AlloyFinger { var _self: BezierPathInteractionHelper = this; const afProps = { touchStart: function (_event: AFTouchEvent<"touchStart">) { _self.mouseIsOver = true; }, touchMove: function (event: AFTouchEvent<"touchMove">) { if (_self.pb.getDraggedElementCount() == 0 && event.touches.length > 0) { _self._handleMoveEvent(event.touches[0].clientX, event.touches[0].clientY); } }, touchEnd: function (_event: AFTouchEvent<"touchEnd">) { _self.mouseIsOver = false; _self._clearMoveEvent(); } }; if (window["createAlloyFinger" as keyof Object]) { return (window["createAlloyFinger" as keyof Object] as any)( this.pb.eventCatcher ? this.pb.eventCatcher : this.pb.eventCatcher, afProps ); } else { return new AlloyFinger(this.pb.eventCatcher ? this.pb.eventCatcher : this.pb.eventCatcher, afProps); } } // +--------------------------------------------------------------------------------- // | Called once upon initialization. // +------------------------------- private _installMouseListener(): MouseHandler { var _self: BezierPathInteractionHelper = this; var mouseHandler = new MouseHandler(this.pb.eventCatcher) .up(function (e: XMouseEvent) { if (e.params.wasDragged) return; if (_self._keyHandler.isDown("shift")) return; if (_self.currentDistance > _self.maxDetectDistance || !_self.mouseIsOver) return; const path: BezierPath = _self.paths[_self.currentPathIndex]; const vertex: Vertex | undefined = _self.pb.getVertexNear(e.params.pos, PlotBoilerplate.DEFAULT_CLICK_TOLERANCE); if (vertex) return; // Check if there is already a path point at the given split position const pathPoint: Vertex = path.getPointAt(_self.currentT); const pointNear: Vertex | undefined = _self.pb.getVertexNear(_self.pb.revertMousePosition(pathPoint.x, pathPoint.y), 6.0); if (pointNear) { for (var i = 0; i < path.bezierCurves.length; i++) { if ( path.bezierCurves[i].startPoint.distance(pointNear) <= 6.0 || path.bezierCurves[i].endPoint.distance(pointNear) <= 6.0 ) { // console.log("There is already a path point near this position."); return; } } } //console.log('Inserting vertex at', _self.currentT ); const leftPath: BezierPath = path.getSubPathAt(0.0, _self.currentT); const rightPath: BezierPath = path.getSubPathAt(_self.currentT, 1.0); const newCurves: Array<CubicBezierCurve> = []; for (var i = 0; i < leftPath.bezierCurves.length; i++) { newCurves.push(leftPath.bezierCurves[i]); } for (var i = 0; i < rightPath.bezierCurves.length; i++) { newCurves.push(rightPath.bezierCurves[i]); } const newPath: BezierPath = BezierPath.fromArray(newCurves); const oldPath: BezierPath = _self.paths[_self.currentPathIndex]; _self._replacePathAt(_self.currentPathIndex, newPath); _self.onVertexInserted(_self.currentPathIndex, leftPath.bezierCurves.length, newPath, oldPath); }) .move(function (e: XMouseEvent) { _self.mouseIsOver = true; _self._handleMoveEvent(e.params.pos.x, e.params.pos.y); }); this.pb.eventCatcher.addEventListener("mouseenter", this._mouseEnterListener); this.pb.eventCatcher.addEventListener("mouseleave", this._mouseLeaveListener); return mouseHandler; } // +--------------------------------------------------------------------------------- // | Called once upon initialization. // | // | @return {KeyHandler} // +------------------------------- private _installKeyListener(): KeyHandler { var _self = this; return new KeyHandler({ trackAll: true }).down("delete", function () { _self._handleDelete(); }); } // +--------------------------------------------------------------------------------- // | Removes vertex listeners from all path points. // | // | @param {BezierPath} path - The path to remove vertex listeners from. // +------------------------------- private _removeDefaultPathListeners(path: BezierPath): void { BezierPathInteractionHelper.removePathVertexDragListeners(path, this._updateMinDistance); } // +--------------------------------------------------------------------------------- // | Update the min distance from point `line.b` to the curve. And redraw. // +------------------------------- private _updateMinDistance(): void { if (this.paths.length == 0) { return; } let pathIndex: number = -1; let minDist: number = Number.MAX_VALUE; let closestPoint: Vertex | null = null; let closestT: number = 0.0; for (var i = 0; i < this.paths.length; i++) { let path: BezierPath = this.paths[i]; let t: number = path.getClosestT(this.currentB); let point: Vertex = path.getPointAt(t); let dist: number = point.distance(this.currentB); if (dist < minDist) { pathIndex = i; minDist = dist; closestT = t; closestPoint = point; } } this.currentT = closestT; this.currentPathIndex = pathIndex; this.currentDistance = minDist; closestPoint && this.currentA.set(closestPoint); } // +--------------------------------------------------------------------------------- // | Sets all vertices on the given path to `bezierAutoAdjust=true`. // | // | @static // +------------------------------- static setPathAutoAdjust(path: BezierPath): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; path.adjustPredecessorControlPoint( i, true, // obtainHandleLength false // updateArcLength (we will do this after the loop) ); if (i > 0 || path.adjustCircular) curve.startPoint.attr.bezierAutoAdjust = true; } path.updateArcLengths(); } /** * A helper function to add drag-start listeners to all vertices of the given path. * * @static * @method addPathVertexDragStartListeners * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The Bézier path to add vertex listeners to. * @param {function} vertexDragStartListener - The drag listeners to add to each path vertex. * @return void **/ static addPathVertexDragStartListeners(path: BezierPath, vertexDragStartListener: VertListener): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; curve.startPoint.listeners.addDragStartListener(vertexDragStartListener); curve.startControlPoint.listeners.addDragStartListener(vertexDragStartListener); curve.endControlPoint.listeners.addDragStartListener(vertexDragStartListener); if (i + 1 == path.bezierCurves.length && !path.adjustCircular) curve.endPoint.listeners.addDragStartListener(vertexDragStartListener); // if( i+1 == path.bezierCurves.length && !path.adjustCircular ) // BezierPathIntractionHelper._addVertsDragListener( [curve.startPoint, curve.startControlPoint, curve.endPoint, curve.endControlPoint ], vertexDragListener ); } } /** * A helper function to remove drag-start listeners to all vertices of the given path. * * @static * @method removePathVertexDragStartListeners * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The Bézier path to remove vertex listeners from. * @param {function} vertexDragListener - The drag listeners to remove from each path vertex. * @return void **/ static removePathVertexDragStartListeners(path: BezierPath, vertexDragStartListener: VertListener): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; curve.startPoint.listeners.removeDragStartListener(vertexDragStartListener); curve.startControlPoint.listeners.removeDragStartListener(vertexDragStartListener); curve.endControlPoint.listeners.removeDragStartListener(vertexDragStartListener); if (i + 1 == path.bezierCurves.length && !path.adjustCircular) curve.endPoint.listeners.removeDragStartListener(vertexDragStartListener); } } /** * A helper function to add drag listeners to all vertices of the given path. * * @static * @method addPathVertexDragListeners * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The Bézier path to add vertex listeners to. * @param {function} vertexDragListener - The drag listeners to add to each path vertex. * @return void **/ static addPathVertexDragListeners(path: BezierPath, vertexDragListener: VertListener): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; curve.startPoint.listeners.addDragListener(vertexDragListener); curve.startControlPoint.listeners.addDragListener(vertexDragListener); curve.endControlPoint.listeners.addDragListener(vertexDragListener); if (i + 1 == path.bezierCurves.length && !path.adjustCircular) curve.endPoint.listeners.addDragListener(vertexDragListener); // if( i+1 == path.bezierCurves.length && !path.adjustCircular ) // BezierPathIntractionHelper._addVertsDragListener( [curve.startPoint, curve.startControlPoint, curve.endPoint, curve.endControlPoint ], vertexDragListener ); } } /** * A helper function to remove drag listeners to all vertices of the given path. * * @static * @method removePathVertexDragListeners * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The Bézier path to remove vertex listeners from. * @param {function} vertexDragListener - The drag listeners to remove from each path vertex. * @return void **/ static removePathVertexDragListeners(path: BezierPath, vertexDragListener: VertListener): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; curve.startPoint.listeners.removeDragListener(vertexDragListener); curve.startControlPoint.listeners.removeDragListener(vertexDragListener); curve.endControlPoint.listeners.removeDragListener(vertexDragListener); if (i + 1 == path.bezierCurves.length && !path.adjustCircular) curve.endPoint.listeners.removeDragListener(vertexDragListener); } } /** * A helper function to add drag-end listeners to all vertices of the given path. * * @static * @method addPathVertexDragEndListeners * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The Bézier path to add vertex listeners to. * @param {function} vertexDragEndListener - The drag listeners to add to each path vertex. * @return void **/ static addPathVertexDragEndListeners(path: BezierPath, vertexDragEndListener: VertListener): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; curve.startPoint.listeners.addDragEndListener(vertexDragEndListener); curve.startControlPoint.listeners.addDragEndListener(vertexDragEndListener); curve.endControlPoint.listeners.addDragEndListener(vertexDragEndListener); if (i + 1 == path.bezierCurves.length && !path.adjustCircular) curve.endPoint.listeners.addDragEndListener(vertexDragEndListener); // if( i+1 == path.bezierCurves.length && !path.adjustCircular ) // BezierPathIntractionHelper._addVertsDragListener( [curve.startPoint, curve.startControlPoint, curve.endPoint, curve.endControlPoint ], vertexDragListener ); } } /** * A helper function to remove drag-end listeners to all vertices of the given path. * * @static * @method removePathVertexDragEndListeners * @memberof BezierPathInteractionHelper * @param {BezierPath} path - The Bézier path to remove vertex listeners from. * @param {function} vertexDragListener - The drag listeners to remove from each path vertex. * @return void **/ static removePathVertexDragEndListeners(path: BezierPath, vertexDragEndListener: VertListener): void { for (var i = 0; i < path.bezierCurves.length; i++) { const curve: CubicBezierCurve = path.bezierCurves[i]; curve.startPoint.listeners.removeDragEndListener(vertexDragEndListener); curve.startControlPoint.listeners.removeDragEndListener(vertexDragEndListener); curve.endControlPoint.listeners.removeDragEndListener(vertexDragEndListener); if (i + 1 == path.bezierCurves.length && !path.adjustCircular) curve.endPoint.listeners.removeDragEndListener(vertexDragEndListener); } } }