Skip to content

Commit

Permalink
[3450] Calculate custom node border node position to follow the real …
Browse files Browse the repository at this point in the history
…layout

Bug: #3450
Signed-off-by: Florian ROUËNÉ <florian.rouene@obeosoft.com>
  • Loading branch information
frouene committed May 13, 2024
1 parent be265b5 commit 83ea18f
Show file tree
Hide file tree
Showing 5 changed files with 226 additions and 96 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ They still support returning an `java.time.Instant` object directly.
- https://github.com/eclipse-sirius/sirius-web/issues/3391[#3391] [diagram] Accept gradient for node background
- https://github.com/eclipse-sirius/sirius-web/issues/3435[#3435] [diagram] Extract diagram style from useDropNode
- https://github.com/eclipse-sirius/sirius-web/issues/3453[#3453] [diagram] Memoizing edges and nodes style
- https://github.com/eclipse-sirius/sirius-web/issues/3450[#3450] [diagram] Add `calculateCustomNodeBorderNodePosition` method to position border nodes according to the real layout of the custom node

== v2024.3.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import { useCallback } from 'react';
import { useCallback, useContext } from 'react';
import { Node, NodeChange, useReactFlow, XYPosition } from 'reactflow';
import { EdgeData, NodeData, BorderNodePosition } from '../DiagramRenderer.types';
import { findBorderNodePosition } from '../layout/layoutBorderNodes';
import { borderNodeOffset } from '../layout/layoutParams';
import { DiagramNodeType } from '../node/NodeTypes.types';
import { UseBorderChangeValue } from './useBorderChange.types';
import { NodeTypeContextValue } from '../../contexts/NodeContext.types';
import { NodeTypeContext } from '../../contexts/NodeContext';

const isNewPositionInsideIsParent = (newNodePosition: XYPosition, movedNode: Node, parentNode: Node): boolean => {
if (movedNode.width && movedNode.height && parentNode?.positionAbsolute && parentNode.width && parentNode.height) {
Expand Down Expand Up @@ -55,17 +58,22 @@ const findNearestBorderPosition = (

export const useBorderChange = (): UseBorderChangeValue => {
const { getNodes } = useReactFlow<NodeData, EdgeData>();
const { nodeLayoutHandlers } = useContext<NodeTypeContextValue>(NodeTypeContext);

const transformBorderNodeChanges = useCallback((changes: NodeChange[], oldNodes: Node<NodeData>[]): NodeChange[] => {
return changes.map((change) => {
if (change.type === 'position' && change.position && change.positionAbsolute) {
const movedNode = getNodes().find((node) => change.id === node.id);
if (movedNode && movedNode.data.isBorderNode) {
const parentNode = getNodes().find((node) => movedNode.parentNode === node.id);
const parentLayoutHandler = nodeLayoutHandlers.find((nodeLayoutHandler) =>
nodeLayoutHandler.canHandle(parentNode as Node<NodeData, DiagramNodeType>)
);
if (
parentNode &&
parentNode.positionAbsolute &&
isNewPositionInsideIsParent(change.positionAbsolute, movedNode, parentNode)
isNewPositionInsideIsParent(change.positionAbsolute, movedNode, parentNode) &&
!parentLayoutHandler?.calculateCustomNodeBorderNodePosition
) {
const nearestBorder = findNearestBorderPosition(change.positionAbsolute, parentNode);
if (nearestBorder === BorderNodePosition.NORTH) {
Expand All @@ -91,6 +99,18 @@ export const useBorderChange = (): UseBorderChangeValue => {
if (oldMovedNode && oldMovedNode.data.borderNodePosition !== newPosition) {
oldMovedNode.data.borderNodePosition = newPosition;
}
if (parentLayoutHandler?.calculateCustomNodeBorderNodePosition && parentNode) {
change.position = parentLayoutHandler.calculateCustomNodeBorderNodePosition(
parentNode,
{
x: change.position.x,
y: change.position.y,
width: movedNode.width ?? 0,
height: movedNode.height ?? 0,
},
true
);
}
}
}
return change;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { HandleElement, Node, Position, XYPosition } from 'reactflow';
import { Dimensions, HandleElement, Node, Position, XYPosition } from 'reactflow';
import { NodeData } from '../DiagramRenderer.types';
import { DiagramNodeType } from '../node/NodeTypes.types';
import { RawDiagram, ForcedDimensions } from './layout.types';
Expand Down Expand Up @@ -46,4 +46,10 @@ export interface INodeLayoutHandler<T extends NodeData> {
handlePosition: Position,
handle: HandleElement
): XYPosition;

calculateCustomNodeBorderNodePosition?(
node: Node<NodeData>,
borderNode: XYPosition & Dimensions,
isDragging: boolean
): XYPosition;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/
import { Box, Node, Rect, XYPosition, boxToRect, rectToBox } from 'reactflow';
import { Box, Dimensions, Node, Rect, XYPosition, boxToRect, rectToBox } from 'reactflow';
import { NodeData, InsideLabel } from '../DiagramRenderer.types';
import { RawDiagram } from './layout.types';
import {
Expand Down Expand Up @@ -247,101 +247,122 @@ const getRightMostSibling = (
export const setBorderNodesPosition = (
borderNodes: Node<NodeData, string>[],
nodeToLayout: Node<NodeData>,
previousDiagram: RawDiagram | null
previousDiagram: RawDiagram | null,
calculateCustomNodeBorderNodePosition?:
| ((node: Node<NodeData>, borderNode: XYPosition & Dimensions, isDragging: boolean) => XYPosition)
| undefined
): void => {
const borderNodesEast = borderNodes.filter(isEastBorderNode);
borderNodesEast.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newY = previousPosition.y;
if (nodeToLayout.height && newY > nodeToLayout.height) {
newY = nodeToLayout.height - borderNodeOffset;
}
child.position = {
x: nodeToLayout.width ?? 0,
y: newY,
};
} else {
child.position = { x: nodeToLayout.width ?? 0, y: defaultNodeMargin };
const previousSibling = getLowestSibling(borderNodesEast, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, y: previousSibling.position.y + (previousSibling.height ?? 0) + gap };
child.extent = getBorderNodeExtent(nodeToLayout, child);
if (calculateCustomNodeBorderNodePosition) {
borderNodes.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
child.position = calculateCustomNodeBorderNodePosition(
nodeToLayout,
{
...previousPosition,
width: child.width ?? 0,
height: child.height ?? 0,
},
false
);
}
}
child.position.x = child.position.x - borderNodeOffset;
});

const borderNodesWest = borderNodes.filter(isWestBorderNode);
borderNodesWest.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newY = previousPosition.y;
if (nodeToLayout.height && newY > nodeToLayout.height) {
newY = nodeToLayout.height - borderNodeOffset;
}
child.position = {
x: 0 - (child.width ?? 0),
y: newY,
};
} else {
child.position = { x: 0 - (child.width ?? 0), y: defaultNodeMargin };
const previousSibling = getLowestSibling(borderNodesWest, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, y: previousSibling.position.y + (previousSibling.height ?? 0) + gap };
}
}
child.position.x = child.position.x + borderNodeOffset;
});

const borderNodesSouth = borderNodes.filter(isSouthBorderNode);
borderNodesSouth.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newX = previousPosition.x;
if (nodeToLayout.width && newX > nodeToLayout.width) {
newX = nodeToLayout.width - borderNodeOffset;
});
} else {
const borderNodesEast = borderNodes.filter(isEastBorderNode);
borderNodesEast.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newY = previousPosition.y;
if (nodeToLayout.height && newY > nodeToLayout.height) {
newY = nodeToLayout.height - borderNodeOffset;
}
child.position = {
x: nodeToLayout.width ?? 0,
y: newY,
};
} else {
child.position = { x: nodeToLayout.width ?? 0, y: defaultNodeMargin };
const previousSibling = getLowestSibling(borderNodesEast, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, y: previousSibling.position.y + (previousSibling.height ?? 0) + gap };
child.extent = getBorderNodeExtent(nodeToLayout, child);
}
}
child.position = {
x: newX,
y: nodeToLayout.height ?? 0,
};
} else {
child.position = { x: defaultNodeMargin, y: nodeToLayout.height ?? 0 };
const previousSibling = getRightMostSibling(borderNodesSouth, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, x: previousSibling.position.x + (previousSibling.width ?? 0) + gap };
child.extent = getBorderNodeExtent(nodeToLayout, child);
child.position.x = child.position.x - borderNodeOffset;
});

const borderNodesWest = borderNodes.filter(isWestBorderNode);
borderNodesWest.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newY = previousPosition.y;
if (nodeToLayout.height && newY > nodeToLayout.height) {
newY = nodeToLayout.height - borderNodeOffset;
}
child.position = {
x: 0 - (child.width ?? 0),
y: newY,
};
} else {
child.position = { x: 0 - (child.width ?? 0), y: defaultNodeMargin };
const previousSibling = getLowestSibling(borderNodesWest, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, y: previousSibling.position.y + (previousSibling.height ?? 0) + gap };
}
}
}
child.position.y = child.position.y - borderNodeOffset;
});

const borderNodesNorth = borderNodes.filter(isNorthBorderNode);
borderNodesNorth.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newX = previousPosition.x;
if (nodeToLayout.width && newX > nodeToLayout.width) {
newX = nodeToLayout.width - borderNodeOffset;
child.position.x = child.position.x + borderNodeOffset;
});

const borderNodesSouth = borderNodes.filter(isSouthBorderNode);
borderNodesSouth.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newX = previousPosition.x;
if (nodeToLayout.width && newX > nodeToLayout.width) {
newX = nodeToLayout.width - borderNodeOffset;
}
child.position = {
x: newX,
y: nodeToLayout.height ?? 0,
};
} else {
child.position = { x: defaultNodeMargin, y: nodeToLayout.height ?? 0 };
const previousSibling = getRightMostSibling(borderNodesSouth, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, x: previousSibling.position.x + (previousSibling.width ?? 0) + gap };
child.extent = getBorderNodeExtent(nodeToLayout, child);
}
}
child.position = {
x: newX,
y: 0 - (child.height ?? 0),
};
} else {
child.position = { x: defaultNodeMargin, y: 0 - (child.height ?? 0) };
const previousSibling = getRightMostSibling(borderNodesNorth, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, x: previousSibling.position.x + (previousSibling.width ?? 0) + gap };
child.position.y = child.position.y - borderNodeOffset;
});

const borderNodesNorth = borderNodes.filter(isNorthBorderNode);
borderNodesNorth.forEach((child) => {
const previousBorderNode = (previousDiagram?.nodes ?? []).find((previousNode) => previousNode.id === child.id);
const previousPosition = computePreviousPosition(previousBorderNode, child);
if (previousPosition) {
let newX = previousPosition.x;
if (nodeToLayout.width && newX > nodeToLayout.width) {
newX = nodeToLayout.width - borderNodeOffset;
}
child.position = {
x: newX,
y: 0 - (child.height ?? 0),
};
} else {
child.position = { x: defaultNodeMargin, y: 0 - (child.height ?? 0) };
const previousSibling = getRightMostSibling(borderNodesNorth, previousDiagram);
if (previousSibling) {
child.position = { ...child.position, x: previousSibling.position.x + (previousSibling.width ?? 0) + gap };
}
}
}
child.position.y = child.position.y + borderNodeOffset;
});
child.position.y = child.position.y + borderNodeOffset;
});
}
};

/**
Expand Down
Loading

0 comments on commit 83ea18f

Please sign in to comment.