UNPKG

sprotty

Version:

A next-gen framework for graphical views

378 lines 18 kB
"use strict"; /******************************************************************************** * Copyright (c) 2021 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); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var BezierEdgeRouter_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.AddRemoveBezierSegmentCommand = exports.AddRemoveBezierSegmentAction = exports.BezierMouseListener = exports.BezierEdgeRouter = void 0; const inversify_1 = require("inversify"); const geometry_1 = require("sprotty-protocol/lib/utils/geometry"); const model_1 = require("./model"); const routing_1 = require("./routing"); const abstract_edge_router_1 = require("./abstract-edge-router"); const mouse_tool_1 = require("../../base/views/mouse-tool"); const command_1 = require("../../base/commands/command"); const types_1 = require("../../base/types"); let BezierEdgeRouter = BezierEdgeRouter_1 = class BezierEdgeRouter extends abstract_edge_router_1.AbstractEdgeRouter { get kind() { return BezierEdgeRouter_1.KIND; } route(edge) { if (!edge.source || !edge.target) return []; const rpCount = edge.routingPoints.length; const source = edge.source; const target = edge.target; const result = []; result.push({ kind: 'source', x: 0, y: 0 }); if (rpCount === 0) { // initial values const [h1, h2] = this.createDefaultBezierHandles(source.position, target.position); result.push({ kind: 'bezier-control-after', x: h1.x, y: h1.y, pointIndex: 0 }); result.push({ kind: 'bezier-control-before', x: h2.x, y: h2.y, pointIndex: 1 }); edge.routingPoints.push(h1); edge.routingPoints.push(h2); } else if (rpCount >= 2) { for (let i = 0; i < rpCount; i++) { const p = edge.routingPoints[i]; if (i % 3 === 0) { result.push({ kind: 'bezier-control-after', x: p.x, y: p.y, pointIndex: i }); } if ((i + 1) % 3 === 0) { result.push({ kind: 'bezier-junction', x: p.x, y: p.y, pointIndex: i }); } else if ((i + 2) % 3 === 0) { result.push({ kind: 'bezier-control-before', x: p.x, y: p.y, pointIndex: i }); } } } result.push({ kind: 'target', x: 0, y: 0 }); // use "ends" of edge as reference or next bezier-junction const p0 = rpCount > 2 ? edge.routingPoints[2] : target.position; const pn = rpCount > 2 ? edge.routingPoints[edge.routingPoints.length - 3] : source.position; const sourceAnchor = this.getTranslatedAnchor(source, p0, target.parent, edge, edge.sourceAnchorCorrection); const targetAnchor = this.getTranslatedAnchor(target, pn, source.parent, edge, edge.targetAnchorCorrection); result[0] = { kind: 'source', x: sourceAnchor.x, y: sourceAnchor.y }; result[result.length - 1] = { kind: 'target', x: targetAnchor.x, y: targetAnchor.y }; return result; } createDefaultBezierHandles(relH1, relH2) { const h1 = { x: relH1.x - BezierEdgeRouter_1.DEFAULT_BEZIER_HANDLE_OFFSET, y: relH1.y }; const h2 = { x: relH2.x + BezierEdgeRouter_1.DEFAULT_BEZIER_HANDLE_OFFSET, y: relH2.y }; return [h1, h2]; } createRoutingHandles(edge) { // route ensure there are at least 2 routed points this.route(edge); this.rebuildHandles(edge); } rebuildHandles(edge) { this.addHandle(edge, 'source', 'routing-point', -2); this.addHandle(edge, 'bezier-control-after', 'bezier-routing-point', 0); this.addHandle(edge, 'bezier-add', 'bezier-create-routing-point', 0); const rpCount = edge.routingPoints.length; if (rpCount > 2) { for (let i = 1; i < rpCount - 1; i += 3) { this.addHandle(edge, 'bezier-control-before', 'bezier-routing-point', i); // Add two circle for add/remove segments this.addHandle(edge, 'bezier-add', 'bezier-create-routing-point', i + 1); this.addHandle(edge, 'bezier-junction', 'routing-point', i + 1); this.addHandle(edge, 'bezier-remove', 'bezier-remove-routing-point', i + 1); this.addHandle(edge, 'bezier-control-after', 'bezier-routing-point', i + 2); // re-position control-pairs this.moveBezierControlPair(edge.routingPoints[i], i, edge); } } this.addHandle(edge, 'bezier-control-before', 'bezier-routing-point', rpCount - 1); this.addHandle(edge, 'target', 'routing-point', -1); } getInnerHandlePosition(edge, route, handle) { if (handle.kind === 'bezier-control-before' || handle.kind === 'bezier-junction' || handle.kind === 'bezier-control-after') { for (let i = 0; i < route.length; i++) { const p = route[i]; if (p.pointIndex === handle.pointIndex && p.kind === handle.kind) return p; } } else if (handle.kind === 'bezier-add') { const ctrlPoint = this.findBezierControl(edge, route, handle.pointIndex); return { x: ctrlPoint.x, y: ctrlPoint.y + 12.5 }; } else if (handle.kind === 'bezier-remove') { const ctrlPoint = this.findBezierControl(edge, route, handle.pointIndex); return { x: ctrlPoint.x, y: ctrlPoint.y - 12.5 }; } return undefined; } findBezierControl(edge, route, handleIndex) { let result = { x: route[0].x, y: route[0].y }; if (handleIndex > 0) { for (const rp of route) { if (rp.pointIndex !== undefined && rp.pointIndex === handleIndex && rp.kind === 'bezier-junction') { result = { x: rp.x, y: rp.y }; break; } } } return result; } applyHandleMoves(edge, moves) { moves.forEach(move => { const handle = move.handle; let orgPosition = { x: 0, y: 0 }; let relativePos, newControlPos, ctrlPointIndex; const moveToPos = move.toPosition; switch (handle.kind) { case 'bezier-control-before': case 'bezier-control-after': // find potential other handle/rp and move this.moveBezierControlPair(moveToPos, move.handle.pointIndex, edge); break; case 'bezier-junction': const index = handle.pointIndex; if (index >= 0 && index < edge.routingPoints.length) { ctrlPointIndex = index - 1; orgPosition = edge.routingPoints[index]; relativePos = edge.routingPoints[ctrlPointIndex]; newControlPos = this.calcRelativeMove(orgPosition, moveToPos, relativePos); edge.routingPoints[index] = moveToPos; this.moveBezierControlPair(newControlPos, ctrlPointIndex, edge); } break; case 'source': ctrlPointIndex = 0; relativePos = edge.routingPoints[ctrlPointIndex]; if (!(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; if (edge.source) orgPosition = edge.source.position; } else if (handle.danglingAnchor) { orgPosition = handle.danglingAnchor.position; handle.danglingAnchor.position = moveToPos; } newControlPos = this.calcRelativeMove(orgPosition, moveToPos, relativePos); this.moveBezierControlPair(newControlPos, ctrlPointIndex, edge); break; case 'target': ctrlPointIndex = edge.routingPoints.length - 1; relativePos = edge.routingPoints[ctrlPointIndex]; if (!(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 = moveToPos; handle.root.add(anchor); handle.danglingAnchor = anchor; edge.targetId = anchor.id; if (edge.target) orgPosition = edge.target.position; } else if (handle.danglingAnchor) { orgPosition = handle.danglingAnchor.position; handle.danglingAnchor.position = moveToPos; } newControlPos = this.calcRelativeMove(orgPosition, moveToPos, relativePos); this.moveBezierControlPair(newControlPos, ctrlPointIndex, edge); break; default: break; } }); } applyInnerHandleMoves(edge, moves) { // not required } getOptions(edge) { return { minimalPointDistance: 2, standardDistance: 0.1, selfEdgeOffset: 20 }; } calcRelativeMove(oldPos, newPos, relativePoint) { return { x: relativePoint.x - (oldPos.x - newPos.x), y: relativePoint.y - (oldPos.y - newPos.y) }; } createNewBezierSegment(index, edge) { const routingPoints = edge.routingPoints; let bezierJunctionPos, start, end; if (routingPoints.length === 2) { start = routingPoints[index < 0 ? 0 : index]; end = routingPoints[routingPoints.length - 1]; bezierJunctionPos = (0, geometry_1.centerOfLine)(start, end); } else { start = routingPoints[index]; end = routingPoints[index + 2]; bezierJunctionPos = (0, geometry_1.centerOfLine)(start, end); } const [h1, h2] = this.createDefaultBezierHandles(bezierJunctionPos, bezierJunctionPos); routingPoints.splice(index + 1, 0, h1); routingPoints.splice(index + 2, 0, bezierJunctionPos); routingPoints.splice(index + 3, 0, h2); // ensure handles are correctly positioned this.moveBezierControlPair(h1, index + 1, edge); // simple solution for now: just rebuildHandles edge.removeAll(c => c instanceof model_1.SRoutingHandleImpl); this.rebuildHandles(edge); } removeBezierSegment(index, edge) { const routingPoints = edge.routingPoints; routingPoints.splice(index - 1, 3); // simple solution for now: just rebuildHandles edge.removeAll(c => c instanceof model_1.SRoutingHandleImpl); this.rebuildHandles(edge); } moveBezierControlPair(newPos, ctrlPointIndex, edge) { if (ctrlPointIndex >= 0 && ctrlPointIndex < edge.routingPoints.length) { // find neighbors const before = ctrlPointIndex - 1; const after = ctrlPointIndex + 1; // this is the first control or the last control => nothing to do further if (before < 0 || after === edge.routingPoints.length) { edge.routingPoints[ctrlPointIndex] = newPos; } else { // behind bezier-junction if (ctrlPointIndex % 3 === 0) { this.setBezierMirror(edge, newPos, ctrlPointIndex, false); // before bezier-junction } else if ((ctrlPointIndex + 2) % 3 === 0) { this.setBezierMirror(edge, newPos, ctrlPointIndex, true); } } } } setBezierMirror(edge, newPos, pointIndex, before) { edge.routingPoints[pointIndex] = newPos; const jct = edge.routingPoints[before ? (pointIndex + 1) : (pointIndex - 1)]; edge.routingPoints[before ? (pointIndex + 2) : (pointIndex - 2)] = { x: jct.x - (newPos.x - jct.x), y: jct.y - (newPos.y - jct.y) }; } }; exports.BezierEdgeRouter = BezierEdgeRouter; BezierEdgeRouter.KIND = 'bezier'; BezierEdgeRouter.DEFAULT_BEZIER_HANDLE_OFFSET = 25; exports.BezierEdgeRouter = BezierEdgeRouter = BezierEdgeRouter_1 = __decorate([ (0, inversify_1.injectable)() ], BezierEdgeRouter); /** * Reacts on mouseDown events if the target kind is bezier-add or bezier-remove */ class BezierMouseListener extends mouse_tool_1.MouseListener { mouseDown(target, event) { const result = []; if (target instanceof model_1.SRoutingHandleImpl && (target.kind === 'bezier-add' || target.kind === 'bezier-remove')) { if (target.type === 'bezier-create-routing-point') { result.push(AddRemoveBezierSegmentAction.create('add', target.id)); } else if (target.type === 'bezier-remove-routing-point') { result.push(AddRemoveBezierSegmentAction.create('remove', target.id)); } } return result; } } exports.BezierMouseListener = BezierMouseListener; ; var AddRemoveBezierSegmentAction; (function (AddRemoveBezierSegmentAction) { AddRemoveBezierSegmentAction.KIND = 'addRemoveBezierSegment'; function create(actionTask, targetId) { return { kind: AddRemoveBezierSegmentAction.KIND, actionTask, targetId }; } AddRemoveBezierSegmentAction.create = create; })(AddRemoveBezierSegmentAction || (exports.AddRemoveBezierSegmentAction = AddRemoveBezierSegmentAction = {})); let AddRemoveBezierSegmentCommand = class AddRemoveBezierSegmentCommand extends command_1.Command { constructor(action, edgeRouterRegistry) { super(); this.action = action; this.edgeRouterRegistry = edgeRouterRegistry; } execute(context) { const index = context.root.index; const target = index.getById(this.action.targetId); if (this.edgeRouterRegistry && target instanceof model_1.SRoutingHandleImpl) { const raw = this.edgeRouterRegistry.get(target.parent.routerKind); if (raw instanceof BezierEdgeRouter) { const router = raw; for (const child of context.root.children) { if (child.id === target.parent.id) { if (this.action.actionTask === 'add') { router.createNewBezierSegment(target.pointIndex, child); } else if (this.action.actionTask === 'remove') { router.removeBezierSegment(target.pointIndex, child); } break; } } } } return context.root; } undo(context) { throw new Error('Method not implemented.'); } redo(context) { throw new Error('Method not implemented.'); } }; exports.AddRemoveBezierSegmentCommand = AddRemoveBezierSegmentCommand; AddRemoveBezierSegmentCommand.KIND = AddRemoveBezierSegmentAction.KIND; exports.AddRemoveBezierSegmentCommand = AddRemoveBezierSegmentCommand = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(types_1.TYPES.Action)), __param(1, (0, inversify_1.inject)(routing_1.EdgeRouterRegistry)), __metadata("design:paramtypes", [Object, routing_1.EdgeRouterRegistry]) ], AddRemoveBezierSegmentCommand); //# sourceMappingURL=bezier-edge-router.js.map