UNPKG

sprotty

Version:

A next-gen framework for graphical views

413 lines 19.7 kB
"use strict"; /******************************************************************************** * Copyright (c) 2019-2020 TypeFox and others. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0. * * This Source Code may also be made available under the following Secondary * Licenses when the conditions for such availability set forth in the Eclipse * Public License v. 2.0 are satisfied: GNU General Public License, version 2 * with the GNU Classpath Exception which is available at * https://www.gnu.org/software/classpath/license.html. * * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AbstractEdgeRouter = exports.DefaultAnchors = exports.Side = void 0; const inversify_1 = require("inversify"); const geometry_1 = require("sprotty-protocol/lib/utils/geometry"); const smodel_utils_1 = require("../../base/model/smodel-utils"); const model_1 = require("./model"); const anchor_1 = require("./anchor"); const model_2 = require("./model"); var Side; (function (Side) { Side[Side["RIGHT"] = 0] = "RIGHT"; Side[Side["LEFT"] = 1] = "LEFT"; Side[Side["TOP"] = 2] = "TOP"; Side[Side["BOTTOM"] = 3] = "BOTTOM"; })(Side || (exports.Side = Side = {})); class DefaultAnchors { constructor(element, edgeParent, kind) { this.element = element; this.kind = kind; const bounds = element.bounds; this.bounds = (0, smodel_utils_1.translateBounds)(bounds, element.parent, edgeParent); this.left = { x: this.bounds.x, y: this.bounds.y + 0.5 * this.bounds.height, kind }; this.right = { x: this.bounds.x + this.bounds.width, y: this.bounds.y + 0.5 * this.bounds.height, kind }; this.top = { x: this.bounds.x + 0.5 * this.bounds.width, y: this.bounds.y, kind }; this.bottom = { x: this.bounds.x + 0.5 * this.bounds.width, y: this.bounds.y + this.bounds.height, kind }; } get(side) { return this[Side[side].toLowerCase()]; } getNearestSide(point) { const leftDistance = geometry_1.Point.euclideanDistance(point, this.left); const rightDistance = geometry_1.Point.euclideanDistance(point, this.right); const topDistance = geometry_1.Point.euclideanDistance(point, this.top); const bottomDistance = geometry_1.Point.euclideanDistance(point, this.bottom); let currentNearestSide = Side.LEFT; let currentMinDist = leftDistance; if (rightDistance < currentMinDist) { currentMinDist = rightDistance; currentNearestSide = Side.RIGHT; } if (topDistance < currentMinDist) { currentMinDist = topDistance; currentNearestSide = Side.TOP; } if (bottomDistance < currentMinDist) { currentMinDist = bottomDistance; currentNearestSide = Side.BOTTOM; } return currentNearestSide; } } exports.DefaultAnchors = DefaultAnchors; let AbstractEdgeRouter = class AbstractEdgeRouter { findOrthogonalIntersection(edge, point) { const calcOrthogonalIntersectionForSegment = (p1, p2) => { // Calculate the direction vector d of the edge and vector pq from p1 to point q const d = geometry_1.Point.subtract(p2, p1); const pq = geometry_1.Point.subtract(point, p1); // Calculate the scalar t for the direction vector d const t = geometry_1.Point.dotProduct(pq, d) / geometry_1.Point.dotProduct(d, d); // Check if the intersection point lies on the edge segment if (t >= 0 && t <= 1) { // Calculate and return the intersection point x return geometry_1.Point.linear(p1, p2, t); } else if (t < 0) { return p1; } else { return p2; } }; // Calculate the intersection for each segment of the edge and return the closest one const routedPoints = this.route(edge); let intersectionPoint = routedPoints[0]; let index = 0; for (let i = 0; i < routedPoints.length - 1; ++i) { const intersection = calcOrthogonalIntersectionForSegment(routedPoints[i], routedPoints[i + 1]); if (geometry_1.Point.euclideanDistance(point, intersection) < geometry_1.Point.euclideanDistance(point, intersectionPoint)) { intersectionPoint = intersection; index = i; } } const derivative = geometry_1.Point.subtract(routedPoints[index + 1], routedPoints[index]); return { point: intersectionPoint, derivative }; } pointAt(edge, t) { const segments = this.calculateSegment(edge, t); if (!segments) return undefined; const { segmentStart, segmentEnd, lambda } = segments; return geometry_1.Point.linear(segmentStart, segmentEnd, lambda); } derivativeAt(edge, t) { const segments = this.calculateSegment(edge, t); if (!segments) return undefined; const { segmentStart, segmentEnd } = segments; return { x: segmentEnd.x - segmentStart.x, y: segmentEnd.y - segmentStart.y }; } calculateSegment(edge, t) { if (t < 0 || t > 1) return undefined; const routedPoints = this.route(edge); if (routedPoints.length < 2) return undefined; const segmentLengths = []; let totalLength = 0; for (let i = 0; i < routedPoints.length - 1; ++i) { segmentLengths[i] = geometry_1.Point.euclideanDistance(routedPoints[i], routedPoints[i + 1]); totalLength += segmentLengths[i]; } let currentLenght = 0; const tAsLenght = t * totalLength; for (let i = 0; i < routedPoints.length - 1; ++i) { const newLength = currentLenght + segmentLengths[i]; // avoid division by (almost) zero if (segmentLengths[i] > 1E-8) { if (newLength >= tAsLenght) { const lambda = Math.max(0, (tAsLenght - currentLenght)) / segmentLengths[i]; return { segmentStart: routedPoints[i], segmentEnd: routedPoints[i + 1], lambda }; } } currentLenght = newLength; } return { segmentEnd: routedPoints.pop(), segmentStart: routedPoints.pop(), lambda: 1 }; } addHandle(edge, kind, type, routingPointIndex) { const handle = new model_1.SRoutingHandleImpl(); handle.kind = kind; handle.pointIndex = routingPointIndex; handle.type = type; if (kind === 'target' && edge.id === model_1.edgeInProgressID) handle.id = model_1.edgeInProgressTargetHandleID; edge.add(handle); return handle; } getHandlePosition(edge, route, handle) { switch (handle.kind) { case 'source': if (edge.source instanceof model_1.SDanglingAnchorImpl) return edge.source.position; else return route[0]; case 'target': if (edge.target instanceof model_1.SDanglingAnchorImpl) return edge.target.position; else { return route[route.length - 1]; } default: const position = this.getInnerHandlePosition(edge, route, handle); if (position !== undefined) return position; if (handle.pointIndex >= 0 && handle.pointIndex < edge.routingPoints.length) return edge.routingPoints[handle.pointIndex]; } return undefined; } findRouteSegment(edge, route, handleIndex) { const getIndex = (rp) => { if (rp.pointIndex !== undefined) return rp.pointIndex; else if (rp.kind === 'target') return edge.routingPoints.length; else return -2; }; let start, end; for (const rp of route) { const i = getIndex(rp); if (i <= handleIndex && (start === undefined || i > getIndex(start))) start = rp; if (i > handleIndex && (end === undefined || i < getIndex(end))) end = rp; } return { start, end }; } getTranslatedAnchor(connectable, refPoint, refContainer, edge, anchorCorrection = 0) { const translatedRefPoint = (0, smodel_utils_1.translatePoint)(refPoint, refContainer, connectable.parent); const anchorComputer = this.getAnchorComputer(connectable); const strokeCorrection = 0.5 * connectable.strokeWidth; const anchor = anchorComputer.getAnchor(connectable, translatedRefPoint, anchorCorrection + strokeCorrection); return (0, smodel_utils_1.translatePoint)(anchor, connectable.parent, edge.parent); } getAnchorComputer(connectable) { return this.anchorRegistry.get(this.kind, connectable.anchorKind); } applyHandleMoves(edge, moves) { const remainingMoves = moves.slice(); moves.forEach(move => { const handle = move.handle; if (handle.kind === 'source' && !(edge.source instanceof model_1.SDanglingAnchorImpl)) { // detach source const anchor = new model_1.SDanglingAnchorImpl(); anchor.id = edge.id + '_dangling-source'; anchor.original = edge.source; anchor.position = move.toPosition; handle.root.add(anchor); handle.danglingAnchor = anchor; edge.sourceId = anchor.id; } else if (handle.kind === 'target' && !(edge.target instanceof model_1.SDanglingAnchorImpl)) { // detach target const anchor = new model_1.SDanglingAnchorImpl(); anchor.id = edge.id + '_dangling-target'; anchor.original = edge.target; anchor.position = move.toPosition; handle.root.add(anchor); handle.danglingAnchor = anchor; edge.targetId = anchor.id; } if (handle.danglingAnchor) { handle.danglingAnchor.position = move.toPosition; remainingMoves.splice(remainingMoves.indexOf(move), 1); } }); if (remainingMoves.length > 0) this.applyInnerHandleMoves(edge, remainingMoves); this.cleanupRoutingPoints(edge, edge.routingPoints, true, true); } cleanupRoutingPoints(edge, routingPoints, updateHandles, addRoutingPoints) { const sourceAnchors = new DefaultAnchors(edge.source, edge.parent, "source"); const targetAnchors = new DefaultAnchors(edge.target, edge.parent, "target"); this.resetRoutingPointsOnReconnect(edge, routingPoints, updateHandles, sourceAnchors, targetAnchors); } resetRoutingPointsOnReconnect(edge, routingPoints, updateHandles, sourceAnchors, targetAnchors) { if (routingPoints.length === 0 || edge.source instanceof model_1.SDanglingAnchorImpl || edge.target instanceof model_1.SDanglingAnchorImpl) { const options = this.getOptions(edge); const corners = this.calculateDefaultCorners(edge, sourceAnchors, targetAnchors, options); routingPoints.splice(0, routingPoints.length, ...corners); if (updateHandles) { let maxPointIndex = -2; edge.children.forEach(child => { if (child instanceof model_1.SRoutingHandleImpl) { if (child.kind === 'target') child.pointIndex = routingPoints.length; else if (child.kind === 'line' && child.pointIndex >= routingPoints.length) edge.remove(child); else maxPointIndex = Math.max(child.pointIndex, maxPointIndex); } }); for (let i = maxPointIndex; i < routingPoints.length - 1; ++i) this.addHandle(edge, 'manhattan-50%', 'volatile-routing-point', i); } return true; } return false; } applyReconnect(edge, newSourceId, newTargetId) { let hasChanged = false; if (newSourceId) { const newSource = edge.root.index.getById(newSourceId); if (newSource instanceof model_2.SConnectableElementImpl) { edge.sourceId = newSource.id; hasChanged = true; } } if (newTargetId) { const newTarget = edge.root.index.getById(newTargetId); if (newTarget instanceof model_2.SConnectableElementImpl) { edge.targetId = newTarget.id; hasChanged = true; } } if (hasChanged) { // reset attached elements in index edge.index.remove(edge); if (edge.id === model_1.edgeInProgressID) { // create a proper edge id after connecting the edge in progress const idGen = (counter) => `${edge.sourceId}_to_${edge.targetId}_${counter}`; let idx = 0; let newId = idGen(idx); while (edge.index.getById(newId) !== undefined) { newId = idGen(++idx); } edge.id = newId; const progressTargetHandle = edge.children.find(child => child.id === model_1.edgeInProgressTargetHandleID); if (progressTargetHandle instanceof model_1.SRoutingHandleImpl) { // remove in progress target handle edge.remove(progressTargetHandle); if (progressTargetHandle.danglingAnchor) { // remove dangling anchor progressTargetHandle.danglingAnchor.parent.remove(progressTargetHandle.danglingAnchor); } } } edge.index.add(edge); if (this.getSelfEdgeIndex(edge) > -1) { edge.routingPoints = []; this.cleanupRoutingPoints(edge, edge.routingPoints, true, true); } } } takeSnapshot(edge) { return { routingPoints: edge.routingPoints.slice(), routingHandles: edge.children .filter(child => child instanceof model_1.SRoutingHandleImpl) .map(child => child), routedPoints: this.route(edge), router: this, source: edge.source, target: edge.target }; } applySnapshot(edge, snapshot) { edge.routingPoints = snapshot.routingPoints; edge.removeAll(child => child instanceof model_1.SRoutingHandleImpl); edge.routerKind = snapshot.router.kind; snapshot.routingHandles.forEach(handle => edge.add(handle)); if (snapshot.source) edge.sourceId = snapshot.source.id; if (snapshot.target) edge.targetId = snapshot.target.id; // update index edge.root.index.remove(edge); edge.root.index.add(edge); } calculateDefaultCorners(edge, sourceAnchors, targetAnchors, options) { const selfEdgeIndex = this.getSelfEdgeIndex(edge); if (selfEdgeIndex >= 0) { const standardDist = options.standardDistance; const delta = options.selfEdgeOffset * Math.min(sourceAnchors.bounds.width, sourceAnchors.bounds.height); switch (selfEdgeIndex % 4) { case 0: return [ { x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.RIGHT).y + delta }, { x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.BOTTOM).y + standardDist }, { x: sourceAnchors.get(Side.BOTTOM).x + delta, y: sourceAnchors.get(Side.BOTTOM).y + standardDist }, ]; case 1: return [ { x: sourceAnchors.get(Side.BOTTOM).x - delta, y: sourceAnchors.get(Side.BOTTOM).y + standardDist }, { x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.BOTTOM).y + standardDist }, { x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.LEFT).y + delta }, ]; case 2: return [ { x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.LEFT).y - delta }, { x: sourceAnchors.get(Side.LEFT).x - standardDist, y: sourceAnchors.get(Side.TOP).y - standardDist }, { x: sourceAnchors.get(Side.TOP).x - delta, y: sourceAnchors.get(Side.TOP).y - standardDist }, ]; case 3: return [ { x: sourceAnchors.get(Side.TOP).x + delta, y: sourceAnchors.get(Side.TOP).y - standardDist }, { x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.TOP).y - standardDist }, { x: sourceAnchors.get(Side.RIGHT).x + standardDist, y: sourceAnchors.get(Side.RIGHT).y - delta }, ]; } } return []; } getSelfEdgeIndex(edge) { if (!edge.source || edge.source !== edge.target) return -1; return edge.source.outgoingEdges .filter(otherEdge => otherEdge.target === edge.source) .indexOf(edge); } commitRoute(edge, routedPoints) { const newRoutingPoints = []; for (let i = 1; i < routedPoints.length - 1; ++i) newRoutingPoints.push({ x: routedPoints[i].x, y: routedPoints[i].y }); edge.routingPoints = newRoutingPoints; } }; exports.AbstractEdgeRouter = AbstractEdgeRouter; __decorate([ (0, inversify_1.inject)(anchor_1.AnchorComputerRegistry), __metadata("design:type", anchor_1.AnchorComputerRegistry) ], AbstractEdgeRouter.prototype, "anchorRegistry", void 0); exports.AbstractEdgeRouter = AbstractEdgeRouter = __decorate([ (0, inversify_1.injectable)() ], AbstractEdgeRouter); //# sourceMappingURL=abstract-edge-router.js.map