Skip to content

Commit

Permalink
Merge pull request #442 from eclipse-sprotty/jbicker/moving-multiple-…
Browse files Browse the repository at this point in the history
…containers-428

Move also edge bendpoints of nested nodes when parent is moved
  • Loading branch information
jbicker authored Mar 6, 2024
2 parents 840530c + 6039b06 commit 31b428b
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 130 deletions.
160 changes: 45 additions & 115 deletions examples/classdiagram/src/model-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,8 @@ export class ClassDiagramModelSource extends LocalModelSource {
type: 'node:class',
expanded: false,
position: {
x: 200,
y: 350
x: 270,
y: 200
},
layout: 'vbox',
children: [
Expand Down Expand Up @@ -285,7 +285,7 @@ export class ClassDiagramModelSource extends LocalModelSource {
type: 'node:class',
expanded: false,
position: {
x: 540,
x: 740,
y: 25
},
layout: 'vbox',
Expand Down Expand Up @@ -339,7 +339,7 @@ export class ClassDiagramModelSource extends LocalModelSource {
<SLabel>{
id: 'package0_pkgname',
type: 'label:heading',
text: 'com.example.package',
text: 'com.example.package0',
position: {
x: 10,
y: 10
Expand All @@ -349,126 +349,56 @@ export class ClassDiagramModelSource extends LocalModelSource {
id: 'package0_content',
type: 'comp:pkgcontent',
children: [
node1
node1, node2
]
}
]
};
const edge0 = {
id: 'edge0',
type: 'edge:straight',
sourceId: node0.id,
targetId: node1.id,
const package1: SNode = {
id: 'package1',
type: 'node:package',
position: {
x: 60,
y: 10
},
size: {
width: 300,
height: 200
},
children: [
<SLabel> {
id: 'edge0_label_on',
type: 'label:text',
text: 'on',
edgePlacement: {
position: 0.5,
side: 'on',
rotate: false
}
},
<SLabel> {
id: 'edge0_label_top',
type: 'label:text',
text: 'top',
edgePlacement: {
position: 0.3,
side: 'top',
rotate: false
}
},
<SLabel> {
id: 'edge0_label_bottom',
type: 'label:text',
text: 'bottom',
edgePlacement: {
position: 0.3,
side: 'bottom',
rotate: false
}
},
<SLabel> {
id: 'edge0_label_left',
type: 'label:text',
text: 'left',
edgePlacement: {
position: 0.7,
side: 'left',
rotate: false
<SLabel>{
id: 'package1_pkgname',
type: 'label:heading',
text: 'com.example.package1',
position: {
x: 10,
y: 10
}
},
<SLabel & EdgeLayoutable> {
id: 'edge0_label_right',
type: 'label:text',
text: 'right',
edgePlacement: {
position: 0.7,
side: 'right',
rotate: false,
moveMode: 'edge' // optional, because it's the default anyway
}
<SCompartment>{
id: 'package1_content',
type: 'comp:pkgcontent',
children: [
node0
]
}
]
};
const edge0 = {
id: 'edge0',
type: 'edge:straight',
routerKind: 'manhattan',
sourceId: node0.id,
targetId: node1.id,
children: []
} as SEdge;
const edge1 = {
id: 'edge1',
type: 'edge:straight',
sourceId: node0.id,
sourceId: node1.id,
targetId: node2.id,
routerKind: 'manhattan',
children: [
<SLabel> {
id: 'edge1_label_on',
type: 'label:text',
text: 'on',
edgePlacement: {
position: 0.5,
side: 'on',
rotate: true
}
},
<SLabel> {
id: 'edge1_label_top',
type: 'label:text',
text: 'top',
edgePlacement: {
position: 0,
side: 'top',
}
},
<SLabel> {
id: 'edge1_label_bottom',
type: 'label:text',
text: 'bottom',
edgePlacement: {
position: 0,
side: 'bottom',
}
},
<SLabel> {
id: 'edge1_label_left',
type: 'label:text',
text: 'left',
edgePlacement: {
position: 1,
side: 'left'
}
},
<SLabel & EdgeLayoutable> {
id: 'edge1_label_right',
type: 'label:text',
text: 'right',
edgePlacement: {
position: 1,
rotate: true,
side: 'right',
moveMode: 'edge'
}
}
]
children: []
} as SEdge;
const edge2 = {
id: 'edge2',
Expand All @@ -477,11 +407,11 @@ export class ClassDiagramModelSource extends LocalModelSource {
targetId: node3.id,
routerKind: 'bezier',
routingPoints: [
{ x: 260, y: 140 },
{ x: 290, y: 80 },
{ x: 350, y: 100 },
{ x: 390, y: 120 },
{ x: 450, y: 40 }
{ x: 360, y: 140 },
{ x: 390, y: 80 },
{ x: 450, y: 100 },
{ x: 490, y: 120 },
{ x: 550, y: 40 }
],
children: [
<SLabel & EdgeLayoutable> {
Expand Down Expand Up @@ -530,7 +460,7 @@ export class ClassDiagramModelSource extends LocalModelSource {
const graph: SGraph = {
id: 'graph',
type: 'graph',
children: [node0, node2, node3, package0, edge0, edge1, edge2 ],
children: [package0, package1, node3, edge0, edge1, edge2 ],
layoutOptions: {
hGap: 5,
hAlign: 'left',
Expand Down
74 changes: 59 additions & 15 deletions packages/sprotty/src/features/move/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Bounds, Point } from 'sprotty-protocol/lib/utils/geometry';
import { Action, DeleteElementAction, ReconnectAction, SelectAction, SelectAllAction, MoveAction } from 'sprotty-protocol/lib/actions';
import { Animation, CompoundAnimation } from '../../base/animations/animation';
import { CommandExecutionContext, ICommand, MergeableCommand, CommandReturn, IStoppableCommand } from '../../base/commands/command';
import { SChildElementImpl, SModelElementImpl, SModelRootImpl } from '../../base/model/smodel';
import { SChildElementImpl, SModelElementImpl, SModelRootImpl, isParent } from '../../base/model/smodel';
import { findParentByFeature, translatePoint } from '../../base/model/smodel-utils';
import { TYPES } from '../../base/types';
import { MouseListener } from '../../base/views/mouse-tool';
Expand All @@ -33,7 +33,7 @@ import { findChildrenAtPosition, isAlignable } from '../bounds/model';
import { CreatingOnDrag, isCreatingOnDrag } from '../edit/create-on-drag';
import { SwitchEditModeAction } from '../edit/edit-routing';
import { ReconnectCommand } from '../edit/reconnect';
import { edgeInProgressID, edgeInProgressTargetHandleID, isConnectable, SRoutableElementImpl, SRoutingHandleImpl } from '../routing/model';
import { edgeInProgressID, edgeInProgressTargetHandleID, isConnectable, SConnectableElementImpl, SRoutableElementImpl, SRoutingHandleImpl } from '../routing/model';
import { EdgeMemento, EdgeRouterRegistry, EdgeSnapshot, RoutedPoint } from '../routing/routing';
import { isEdgeLayoutable } from '../edge-layout/model';
import { isSelectable } from '../select/model';
Expand Down Expand Up @@ -110,16 +110,32 @@ export class MoveCommand extends MergeableCommand implements IStoppableCommand {
if (resolvedMove) {
this.resolvedMoves.set(resolvedMove.element.id, resolvedMove);
if (this.edgeRouterRegistry) {
index.getAttachedElements(element).forEach(edge => {
if (edge instanceof SRoutableElementImpl) {
const existingDelta = attachedEdgeShifts.get(edge);
const newDelta = Point.subtract(resolvedMove.toPosition, resolvedMove.fromPosition);
const delta = (existingDelta)
? Point.linear(existingDelta, newDelta, 0.5)
: newDelta;
attachedEdgeShifts.set(edge, delta);
const handleEdges = (el: SModelElementImpl) => {
index.getAttachedElements(el).forEach(edge => {
if (edge instanceof SRoutableElementImpl) {
const existingDelta = attachedEdgeShifts.get(edge);
const newDelta = Point.subtract(resolvedMove.toPosition, resolvedMove.fromPosition);
const delta = (existingDelta)
? Point.linear(existingDelta, newDelta, 0.5)
: newDelta;
attachedEdgeShifts.set(edge, delta);
}
});
};
const handleEdgesForChildren = (el: SModelElementImpl) => {
if (isParent(el)) {
el.children.forEach(childEl => {
if (childEl instanceof SModelElementImpl) {
if (childEl instanceof SConnectableElementImpl) {
handleEdges(childEl);
}
handleEdgesForChildren(childEl);
}
});
}
});
};
handleEdgesForChildren(element);
handleEdges(element);
}
}
}
Expand Down Expand Up @@ -176,10 +192,7 @@ export class MoveCommand extends MergeableCommand implements IStoppableCommand {
if (!edge2move.get(edge)) {
const router = this.edgeRouterRegistry!.get(edge.routerKind);
const before = router.takeSnapshot(edge);
if (edge.source
&& edge.target
&& this.resolvedMoves.get(edge.source.id)
&& this.resolvedMoves.get(edge.target.id)) {
if (this.isAttachedEdge(edge)) {
// move the entire edge when both source and target are moved
edge.routingPoints = edge.routingPoints.map(rp => Point.add(rp, delta));
} else {
Expand All @@ -193,6 +206,37 @@ export class MoveCommand extends MergeableCommand implements IStoppableCommand {
});
}

// tests if the edge is attached to the moved element directly or to on of their children
protected isAttachedEdge(edge: SRoutableElementImpl): boolean {
const source = edge.source;
const target = edge.target;
const checkMovedElementsAndChildren = (sourceOrTarget: SConnectableElementImpl): boolean => {
return Array.from(this.resolvedMoves.values()).some(res => {
const recursiveCheck = (el: SModelElementImpl): boolean => {
if (isParent(el)) {
return el.children.some(child => {
if (child instanceof SModelElementImpl) {
if (child === sourceOrTarget)
return true;
return recursiveCheck(child);
}
return false;
});
}
return false;
};
return recursiveCheck(res.element);
}) || !!(this.resolvedMoves.get(sourceOrTarget.id));
};

return !!(
source &&
target &&
checkMovedElementsAndChildren(source) &&
checkMovedElementsAndChildren(target)
);
}

protected undoMove() {
this.resolvedMoves.forEach(res => {
(res.element as any).position = res.fromPosition;
Expand Down

0 comments on commit 31b428b

Please sign in to comment.