From 6039b06761187ed10c2ca1a6f7dde79f2fccc49a Mon Sep 17 00:00:00 2001 From: Jan Bicker Date: Fri, 1 Mar 2024 10:25:45 +0000 Subject: [PATCH] when multiple nodes are moved, the edges of their children are now also taken into account Fixes Moving multiple containers does not show feedback move of internal edge bendpoints #428 Signed-off-by: Jan Bicker --- examples/classdiagram/src/model-source.ts | 160 ++++++--------------- packages/sprotty/src/features/move/move.ts | 74 ++++++++-- 2 files changed, 104 insertions(+), 130 deletions(-) diff --git a/examples/classdiagram/src/model-source.ts b/examples/classdiagram/src/model-source.ts index 2b0fb08..bd027ef 100644 --- a/examples/classdiagram/src/model-source.ts +++ b/examples/classdiagram/src/model-source.ts @@ -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: [ @@ -285,7 +285,7 @@ export class ClassDiagramModelSource extends LocalModelSource { type: 'node:class', expanded: false, position: { - x: 540, + x: 740, y: 25 }, layout: 'vbox', @@ -339,7 +339,7 @@ export class ClassDiagramModelSource extends LocalModelSource { { id: 'package0_pkgname', type: 'label:heading', - text: 'com.example.package', + text: 'com.example.package0', position: { x: 10, y: 10 @@ -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: [ - { - id: 'edge0_label_on', - type: 'label:text', - text: 'on', - edgePlacement: { - position: 0.5, - side: 'on', - rotate: false - } - }, - { - id: 'edge0_label_top', - type: 'label:text', - text: 'top', - edgePlacement: { - position: 0.3, - side: 'top', - rotate: false - } - }, - { - id: 'edge0_label_bottom', - type: 'label:text', - text: 'bottom', - edgePlacement: { - position: 0.3, - side: 'bottom', - rotate: false - } - }, - { - id: 'edge0_label_left', - type: 'label:text', - text: 'left', - edgePlacement: { - position: 0.7, - side: 'left', - rotate: false + { + id: 'package1_pkgname', + type: 'label:heading', + text: 'com.example.package1', + position: { + x: 10, + y: 10 } }, - { - 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 - } + { + 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: [ - { - id: 'edge1_label_on', - type: 'label:text', - text: 'on', - edgePlacement: { - position: 0.5, - side: 'on', - rotate: true - } - }, - { - id: 'edge1_label_top', - type: 'label:text', - text: 'top', - edgePlacement: { - position: 0, - side: 'top', - } - }, - { - id: 'edge1_label_bottom', - type: 'label:text', - text: 'bottom', - edgePlacement: { - position: 0, - side: 'bottom', - } - }, - { - id: 'edge1_label_left', - type: 'label:text', - text: 'left', - edgePlacement: { - position: 1, - side: 'left' - } - }, - { - 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', @@ -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: [ { @@ -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', diff --git a/packages/sprotty/src/features/move/move.ts b/packages/sprotty/src/features/move/move.ts index 9dbe2e3..8cc8bf2 100644 --- a/packages/sprotty/src/features/move/move.ts +++ b/packages/sprotty/src/features/move/move.ts @@ -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'; @@ -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'; @@ -106,16 +106,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); } } } @@ -172,10 +188,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 { @@ -189,6 +202,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;