Skip to content

Commit

Permalink
#738 Implement routing points feature
Browse files Browse the repository at this point in the history
Implement routing points support for the node glsp server and ensure that the API mirrors the latest changes of the java glsp server (eclipse-glsp/glsp-server#181)

Fixes eclipse-glsp/glsp#738
  • Loading branch information
tortmayr committed Dec 6, 2022
1 parent e0d359d commit 51816fc
Show file tree
Hide file tree
Showing 9 changed files with 482 additions and 188 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Below is a list of features that are currently supported.
| Node Change Bounds<br>- Move<br>- Resize | <br>✓<br>✓ | <br>✓<br>✓ |
| Node Change Container |||
| Edge Reconnect |||
| Edge Routing Points | ||
| Edge Routing Points | ||
| Element Text Editing |||
| Clipboard (Cut, Copy, Paste) | ||
| Undo / Redo | ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/********************************************************************************
* Copyright (c) 2022 EclipseSource 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
********************************************************************************/

import { ChangeRoutingPointsOperation } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ModelState } from '../features/model/model-state';
import { OperationHandler } from '../operations/operation-handler';
import { GLSPServerError } from '../utils/glsp-server-error';
import { applyRoutingPoints } from '../utils/layout-util';

@injectable()
export class ChangeRoutingPointsOperationHandler implements OperationHandler {
operationType = ChangeRoutingPointsOperation.KIND;

@inject(ModelState)
protected modelState: ModelState;

execute(operation: ChangeRoutingPointsOperation): void {
if (!operation.newRoutingPoints) {
throw new GLSPServerError('Incomplete change routingPoints action');
}

const index = this.modelState.index;
operation.newRoutingPoints.forEach(routingPoints => applyRoutingPoints(routingPoints, index));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { GModelRoot, isGAlignable, isGBoundsAware } from '@eclipse-glsp/graph';
import { GModelRoot } from '@eclipse-glsp/graph';
import { Action, ComputedBoundsAction } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ActionHandler } from '../actions/action-handler';
import { ModelState } from '../features/model/model-state';
import { ModelSubmissionHandler } from '../features/model/model-submission-handler';
import { applyAlignment, applyBounds, applyRoute } from '../utils/layout-util';

@injectable()
export class ComputedBoundsActionHandler implements ActionHandler {
Expand All @@ -40,20 +41,10 @@ export class ComputedBoundsActionHandler implements ActionHandler {
}

protected applyBounds(root: GModelRoot, action: ComputedBoundsAction): void {
action.bounds.forEach(b => {
const element = this.modelState.index.get(b.elementId);
if (isGBoundsAware(element)) {
element.position = b.newPosition ?? element.position;
element.size = b.newSize;
}
});

action.alignments?.forEach(a => {
const element = this.modelState.index.get(a.elementId);
if (isGAlignable(element)) {
element.alignment = a.newAlignment;
}
});
const index = this.modelState.index;
action.bounds.forEach(bounds => applyBounds(bounds, index));
(action.alignments ?? []).forEach(alignment => applyAlignment(alignment, index));
(action.routes ?? []).forEach(route => applyRoute(route, index));
}

priority?: number | undefined;
Expand Down
2 changes: 2 additions & 0 deletions packages/server-node/src/gmodel-lib/gmodel-diagram-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { CutOperationHandler } from '../operations/cut-operation-handler';
import { OperationHandlerConstructor } from '../operations/operation-handler';
import { ApplyLabelEditOperationHandler } from './apply-label-edit-operation-handler';
import { ChangeBoundsOperationHandler } from './change-bounds-operation-handler';
import { ChangeRoutingPointsOperationHandler } from './change-routing-points-operation-handler';
import { ComputedBoundsActionHandler } from './computed-bounds-action-handler';
import { GModelDeleteOperationHandler } from './delete-operation-handler';
import { GModelStorage } from './gmodel-storage';
Expand Down Expand Up @@ -82,5 +83,6 @@ export abstract class GModelDiagramModule extends DiagramModule {
binding.add(PasteOperationHandler);
binding.add(ReconnectEdgeOperationHandler);
binding.add(LayoutOperationHandler);
binding.add(ChangeRoutingPointsOperationHandler);
}
}
1 change: 1 addition & 0 deletions packages/server-node/src/gmodel-lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

export * from './apply-label-edit-operation-handler';
export * from './change-bounds-operation-handler';
export * from './change-routing-points-operation-handler';
export * from './computed-bounds-action-handler';
export * from './delete-operation-handler';
export * from './gmodel-create-edge-operation-handler';
Expand Down
47 changes: 3 additions & 44 deletions packages/server-node/src/operations/create-operation-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { GBoundsAware, GGraph, GModelElement, GModelRoot, isGBoundsAware } from '@eclipse-glsp/graph';
import { GModelElement } from '@eclipse-glsp/graph';
import {
CreateEdgeOperation,
CreateNodeOperation,
Expand All @@ -25,6 +25,7 @@ import {
} from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { ModelState } from '../features/model/model-state';
import { getRelativeLocation } from '../utils/layout-util';
import { OperationHandler } from './operation-handler';

/**
Expand Down Expand Up @@ -99,48 +100,6 @@ export abstract class CreateNodeOperationHandler extends CreateOperationHandler
getRelativeLocation(operation: CreateNodeOperation): Point | undefined {
const container = this.getContainer(operation) ?? this.modelState.root;
const absoluteLocation = this.getLocation(operation) ?? Point.ORIGIN;
const allowNegativeCoordinates = container instanceof GGraph;
if (isGBoundsAware(container)) {
const relativePosition = absoluteToRelative(absoluteLocation, container);
const relativeLocation = allowNegativeCoordinates
? relativePosition
: { x: Math.max(0, relativePosition.x), y: Math.max(0, relativePosition.y) };
return relativeLocation;
}
return undefined;
}
}

/**
* Convert a point in absolute coordinates to a point relative to the specified GBoundsAware.
* Note: this method only works if the specified {@link GBoundsAware} is part of a
* hierarchy of {@link GBoundsAware}. If any of its parents (recursively) does not implement
* {@link GBoundsAware}, this method will throw an exception.
*
* @param absolutePoint
* @param modelElement
* @returns
* A new point, relative to the coordinates space of the specified {@link GBoundsAware}
* @throws Error if the modelElement is not part of a {@link GBoundsAware} hierarchy
*/
export function absoluteToRelative(absolutePoint: Point, modelElement: GModelElement & GBoundsAware): Point {
let parentElement;
if (!(modelElement instanceof GModelRoot)) {
parentElement = modelElement.parent;
}

let relativeToParent: Point;
if (!parentElement) {
relativeToParent = { x: absolutePoint.x, y: absolutePoint.y };
} else {
if (!isGBoundsAware(parentElement)) {
throw new Error(`The element is not part of a GBoundsAware hierarchy: ${modelElement}`);
}
relativeToParent = absoluteToRelative(absolutePoint, parentElement);
return getRelativeLocation(absoluteLocation, container);
}

const x = modelElement.position?.x ?? 0;
const y = modelElement.position?.y ?? 0;

return { x: relativeToParent.x - x, y: relativeToParent.y - y };
}
62 changes: 54 additions & 8 deletions packages/server-node/src/utils/args-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,72 @@
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { Args } from '@eclipse-glsp/protocol';
import { Args, Point } from '@eclipse-glsp/protocol';

export namespace ArgsUtil {
export const KEY_EDGE_PADDING = 'edgePadding';
export const KEY_EDGE_SOURCE_POINT_X = 'edgeSourcePointX';
export const KEY_EDGE_SOURCE_POINT_Y = 'edgeSourcePointY';
export const KEY_EDGE_TARGET_POINT_X = 'edgeTargetPointX';
export const KEY_EDGE_TARGET_POINT_Y = 'edgeTargetPointY';

export const KEY_RADIUS_TOP_LEFT = 'radiusTopLeft';
export const KEY_RADIUS_TOP_RIGHT = 'radiusTopRight';
export const KEY_RADIUS_BOTTOM_RIGHT = 'radiusBottomRight';
export const KEY_RADIUS_BOTTOM_LEFT = 'radiusBottomLeft';

export function cornerRadius(radius: number): Args;
export function cornerRadius(topLeftBottomRight: number, topRightBottomLeft: number): Args;
export function cornerRadius(topLeft: number, topRight: number, bottomRight: number, bottomLeft: number): Args;
export function cornerRadius(radiusOrTopLeft: number, topRightOpt?: number, bottomRightOpt?: number, bottomLeftOpt?: number): Args {
const topLeft = radiusOrTopLeft;
let topRight = radiusOrTopLeft;
let bottomLeft = radiusOrTopLeft;
let bottomRight = radiusOrTopLeft;

if (topRightOpt) {
topRight = topRightOpt;
if (bottomRightOpt && bottomLeftOpt) {
bottomLeft = bottomLeftOpt;
bottomRight = bottomRightOpt;
} else {
bottomRight = radiusOrTopLeft;
bottomLeft = topRight;
}
}

return {
[KEY_RADIUS_TOP_RIGHT]: topLeft,
[KEY_RADIUS_BOTTOM_LEFT]: bottomLeft,
[KEY_RADIUS_BOTTOM_RIGHT]: bottomRight,
[KEY_RADIUS_TOP_RIGHT]: topRight
};
}

export function edgePadding(edgePadding: number): Args {
return { [KEY_EDGE_PADDING]: edgePadding };
}

export function getEdgePadding(args: Args): number | undefined {
return getNumber(args, KEY_EDGE_PADDING);
}

export function cornerRadius(radius: number): Args {
return {
[KEY_RADIUS_TOP_RIGHT]: radius,
[KEY_RADIUS_BOTTOM_LEFT]: radius,
[KEY_RADIUS_BOTTOM_RIGHT]: radius,
[KEY_RADIUS_TOP_RIGHT]: radius
};
export function getEdgeSourcePoint(args: Args = {}): Point | undefined {
const x = getNumber(args, KEY_EDGE_SOURCE_POINT_X);
const y = getNumber(args, KEY_EDGE_SOURCE_POINT_Y);
if (x && y) {
return { x, y };
}
return undefined;
}

export function getEdgeTargetPoint(args: Args = {}): Point | undefined {
const x = getNumber(args, KEY_EDGE_TARGET_POINT_X);
const y = getNumber(args, KEY_EDGE_TARGET_POINT_Y);
if (x && y) {
return { x, y };
}
return undefined;
}

export function get(args: Args = {}, key: string): string | undefined {
Expand Down
Loading

0 comments on commit 51816fc

Please sign in to comment.