From 268af6817af5ce9e8b2407495b50b2325204f6df Mon Sep 17 00:00:00 2001 From: Michael Charfadi Date: Wed, 5 Mar 2025 15:38:41 +0100 Subject: [PATCH] [4664] Make ConnectionLine snap to the border of a node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-web/issues/4664 Signed-off-by: Michaƫl Charfadi --- CHANGELOG.adoc | 1 + .../src/renderer/DiagramRenderer.tsx | 1 + .../src/renderer/edge/ConnectionLine.tsx | 37 +++++++++++++++++-- .../src/renderer/edge/EdgeLayout.ts | 35 +++++++++++++++++- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 184979b406..d5573bb53f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -51,6 +51,7 @@ Given that it is an application service, it comes with some constraints with reg - https://github.com/eclipse-sirius/sirius-web/issues/4588[#4588] [core] Added support for custom ordering in representation creation modal. See the new interface `org.eclipse.sirius.components.emf.services.api.IRepresentationDescriptionMetadataSorter`. - https://github.com/eclipse-sirius/sirius-web/issues/4616[#4616] [sirius-web] Allow end users to see the content of a library in a workbench, the path of the new page is `/libraries/:namespace/:name/:version` +- https://github.com/eclipse-sirius/sirius-web/issues/4664[#4664] [diagram] Make ConnectionLine snap to the border of a node === Improvements diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx index f609db1ceb..861d51daa9 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/DiagramRenderer.tsx @@ -426,6 +426,7 @@ export const DiagramRenderer = memo(({ diagramRefreshedEventPayload }: DiagramRe onConnectStart: onConnectStart, onConnectEnd: onConnectEnd, connectionLineComponent: ConnectionLine, + connectionRadius: 0, onEdgesChange: handleEdgesChange, onReconnect: reconnectEdge, onPaneContextMenu: handlePaneContextMenu, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/ConnectionLine.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/ConnectionLine.tsx index 18922b6f3f..40115f0d87 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/ConnectionLine.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/ConnectionLine.tsx @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 2025 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -10,10 +10,10 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ - import { Theme, useTheme } from '@mui/material/styles'; import { ConnectionLineComponentProps, getSmoothStepPath } from '@xyflow/react'; import React, { memo } from 'react'; +import { getNearestPointInPerimeter } from './EdgeLayout'; const connectionLineStyle = (theme: Theme): React.CSSProperties => { return { @@ -23,9 +23,40 @@ const connectionLineStyle = (theme: Theme): React.CSSProperties => { }; export const ConnectionLine = memo( - ({ fromX, fromY, toX, toY, fromPosition, toPosition }: ConnectionLineComponentProps) => { + ({ fromX, fromY, toX, toY, fromPosition, toPosition, fromNode, toNode }: ConnectionLineComponentProps) => { const theme = useTheme(); + // Snap the ConnectionLine to the border of the targetted node + if (toNode && toNode.width && toNode.height) { + const pointToSnap = getNearestPointInPerimeter( + toNode.internals.positionAbsolute.x, + toNode.internals.positionAbsolute.y, + toNode.width, + toNode.height, + toX, + toY + ); + + toX = pointToSnap.XYpostion.x; + toY = pointToSnap.XYpostion.y; + toPosition = pointToSnap.position; + } + + if (fromNode && fromNode.width && fromNode.height) { + const pointToSnap = getNearestPointInPerimeter( + fromNode.internals.positionAbsolute.x, + fromNode.internals.positionAbsolute.y, + fromNode.width, + fromNode.height, + fromX, + fromY + ); + + fromX = pointToSnap.XYpostion.x; + fromY = pointToSnap.XYpostion.y; + fromPosition = pointToSnap.position; + } + const [edgePath] = getSmoothStepPath({ sourceX: fromX, sourceY: fromY, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts index b260cdfb8c..69b4b92a33 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/renderer/edge/EdgeLayout.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023, 2024 Obeo. + * Copyright (c) 2023, 2025 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -27,6 +27,39 @@ import { NodeCenter, } from './EdgeLayout.types'; +const clamp = (n: number, lower: number, upper: number) => Math.max(lower, Math.min(upper, n)); + +export const getNearestPointInPerimeter = ( + rectX: number, + rectY: number, + width: number, + height: number, + x: number, + y: number +): { XYpostion: XYPosition; position: Position } => { + const rectX2 = rectX + width; + const rectY2 = rectY + height; + + x = clamp(x, rectX, rectX2); + y = clamp(y, rectY, rectY2); + + const left = Math.abs(x - rectX); + const right = Math.abs(x - rectX2); + const top = Math.abs(y - rectY); + const bottom = Math.abs(y - rectY2); + const m = Math.min(left, right, top, bottom); + + if (m === top) { + return { XYpostion: { x, y: rectY }, position: Position.Top }; + } else if (m === bottom) { + return { XYpostion: { x, y: rectY2 }, position: Position.Bottom }; + } else if (m === left) { + return { XYpostion: { x: rectX, y }, position: Position.Left }; + } else { + return { XYpostion: { x: rectX2, y }, position: Position.Right }; + } +}; + export const getUpdatedConnectionHandles: GetUpdatedConnectionHandlesParameters = ( sourceNode, targetNode,