sprotty
Version:
A next-gen framework for graphical views
378 lines • 18 kB
JavaScript
;
/********************************************************************************
* 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