From e3df7d1c69755bb61358bdb4106da1e3a04c3f7f Mon Sep 17 00:00:00 2001 From: peterkogo Date: Mon, 29 Jan 2024 11:47:05 +0100 Subject: [PATCH] children with parent=extent and expandParent restrict the resizing of the parent --- packages/system/src/xyresizer/XYResizer.ts | 110 ++++++++++++--------- packages/system/src/xyresizer/utils.ts | 84 +++++++--------- 2 files changed, 102 insertions(+), 92 deletions(-) diff --git a/packages/system/src/xyresizer/XYResizer.ts b/packages/system/src/xyresizer/XYResizer.ts index ae81b1a0..43b75066 100644 --- a/packages/system/src/xyresizer/XYResizer.ts +++ b/packages/system/src/xyresizer/XYResizer.ts @@ -1,7 +1,7 @@ import { drag } from 'd3-drag'; import { select } from 'd3-selection'; -import { getControlDirection, getDimensionsAfterResize, getPositionAfterResize, getResizeDirection } from './utils'; +import { getControlDirection, getDimensionsAfterResize, getResizeDirection } from './utils'; import { getPointerPosition } from '../utils'; import type { CoordinateExtent, NodeBase, NodeLookup, Transform } from '../types'; import type { OnResize, OnResizeEnd, OnResizeStart, ResizeDragEvent, ShouldResize, ControlPosition } from './types'; @@ -77,6 +77,16 @@ function nodeToParentExtent(node: NodeBase): CoordinateExtent { ]; } +function nodeToChildExtent(child: NodeBase, parent: NodeBase): CoordinateExtent { + return [ + [parent.position.x + child.position.x, parent.position.y + child.position.y], + [ + parent.position.x + child.position.x + child.computed!.width!, + parent.position.y + child.position.y + child.computed!.height!, + ], + ]; +} + export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResizerParams): XYResizerInstance { const selection = select(domNode); @@ -94,44 +104,71 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize const controlDirection = getControlDirection(controlPosition); + let node: NodeBase | undefined = undefined; let childNodes: XYResizerChildChange[] = []; + let parentExtent: CoordinateExtent | undefined = undefined; + let childExtent: CoordinateExtent | undefined = undefined; const dragHandler = drag() .on('start', (event: ResizeDragEvent) => { const { nodeLookup, transform, snapGrid, snapToGrid } = getStoreItems(); - const node = nodeLookup.get(nodeId); - const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); + node = nodeLookup.get(nodeId); - prevValues = { - width: node?.computed?.width ?? 0, - height: node?.computed?.height ?? 0, - x: node?.position.x ?? 0, - y: node?.position.y ?? 0, - }; + if (node) { + const { xSnapped, ySnapped } = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); - startValues = { - ...prevValues, - pointerX: xSnapped, - pointerY: ySnapped, - aspectRatio: prevValues.width / prevValues.height, - }; + prevValues = { + width: node.computed?.width ?? 0, + height: node.computed?.height ?? 0, + x: node.position.x ?? 0, + y: node.position.y ?? 0, + }; - childNodes = []; - nodeLookup.forEach((_node, _nodeId) => { - if (_node.parentNode === nodeId) { - childNodes.push({ - id: _nodeId, - position: { ..._node.position }, - extent: _node.extent, - }); + startValues = { + ...prevValues, + pointerX: xSnapped, + pointerY: ySnapped, + aspectRatio: prevValues.width / prevValues.height, + }; + + if (node.extent === 'parent') { + const parentNode = nodeLookup.get(node.parentNode!); + if (parentNode) { + parentExtent = nodeToParentExtent(parentNode); + } } - }); - onResizeStart?.(event, { ...prevValues }); + + // Collect all child nodes to correct their relative positions when top/left changes + // Determine largest minimal extent the parent node is allowed to resize to + childNodes = []; + childExtent = undefined; + nodeLookup.forEach((child, childId) => { + if (child.parentNode === nodeId) { + childNodes.push({ + id: childId, + position: { ...child.position }, + extent: child.extent, + }); + if (child.extent === 'parent' || child.expandParent) { + const extent = nodeToChildExtent(child, node!); + if (childExtent) { + childExtent = [ + [Math.min(extent[0][0], childExtent[0][0]), Math.min(extent[0][1], childExtent[0][1])], + [Math.max(extent[1][0], childExtent[1][0]), Math.max(extent[1][1], childExtent[1][1])], + ]; + } else { + childExtent = extent; + } + } + } + }); + + onResizeStart?.(event, { ...prevValues }); + } }) .on('drag', (event: ResizeDragEvent) => { - const { nodeLookup, transform, snapGrid, snapToGrid } = getStoreItems(); + const { transform, snapGrid, snapToGrid } = getStoreItems(); const pointerPosition = getPointerPosition(event.sourceEvent, { transform, snapGrid, snapToGrid }); - const node = nodeLookup.get(nodeId); let childChanges: XYResizerChildChange[] = []; @@ -140,32 +177,20 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues; - let extent = undefined; - if (node.extent === 'parent') { - const parentNode = nodeLookup.get(node.parentNode!); - if (parentNode) { - extent = nodeToParentExtent(parentNode); - } - } - const { width, height, x, y } = getDimensionsAfterResize( startValues, controlDirection, pointerPosition, boundaries, keepAspectRatio, - extent + parentExtent, + childExtent ); const isWidthChange = width !== prevWidth; const isHeightChange = height !== prevHeight; if (controlDirection.affectsX || controlDirection.affectsY) { - // const { - // x, - // y, - // } = getPositionAfterResize(startValues, controlDirection, width, height, extent); - // only transform the node if the width or height changes const isXPosChange = x !== prevX && isWidthChange; const isYPosChange = y !== prevY && isHeightChange; @@ -193,9 +218,6 @@ export function XYResizer({ domNode, nodeId, getStoreItems, onChange }: XYResize } if (isWidthChange || isHeightChange) { - if (extent) { - } - // console.log(clampedX, clampedY); change.isWidthChange = isWidthChange; change.isHeightChange = isHeightChange; change.width = width; diff --git a/packages/system/src/xyresizer/utils.ts b/packages/system/src/xyresizer/utils.ts index ab54163a..f284c4e7 100644 --- a/packages/system/src/xyresizer/utils.ts +++ b/packages/system/src/xyresizer/utils.ts @@ -88,8 +88,12 @@ function getSizeClamp(size: number, minSize: number, maxSize: number) { return Math.max(0, minSize - size, size - maxSize); } +function xor(a: boolean, b: boolean) { + return a ? !b : b; +} + /** - * Calculates new width & height of node after resize based on pointer position + * Calculates new width & height and x & y of node after resize based on pointer position * @param startValues - starting values of resize * @param controlDirection - dimensions affected by the resize * @param pointerPosition - the current pointer position corrected for snapping @@ -103,7 +107,8 @@ export function getDimensionsAfterResize( pointerPosition: ReturnType, boundaries: { minWidth: number; maxWidth: number; minHeight: number; maxHeight: number }, keepAspectRatio: boolean, - extent?: CoordinateExtent + extent?: CoordinateExtent, + childExtent?: CoordinateExtent ) { let { affectsX, affectsY } = controlDirection; const { isHorizontal, isVertical } = controlDirection; @@ -143,6 +148,26 @@ export function getDimensionsAfterResize( clampY = Math.max(clampY, yExtentClamp); } + // Check if the child extent is restricting the resize + if (childExtent) { + let xExtentClamp = 0; + let yExtentClamp = 0; + if (affectsX && distX > 0) { + xExtentClamp = getUpperExtentClamp(startX + distX, childExtent[0][0]); + } else if (!affectsX && distX < 0) { + xExtentClamp = getLowerExtentClamp(startX + newWidth, childExtent[1][0]); + } + + if (affectsY && distY > 0) { + yExtentClamp = getUpperExtentClamp(startY + distY, childExtent[0][1]); + } else if (!affectsY && distY < 0) { + yExtentClamp = getLowerExtentClamp(startY + newHeight, childExtent[1][1]); + } + + clampX = Math.max(clampX, xExtentClamp); + clampY = Math.max(clampY, yExtentClamp); + } + // Check if the aspect ratio resizing of the other side is restricting the resize if (keepAspectRatio) { if (isHorizontal) { @@ -161,8 +186,11 @@ export function getDimensionsAfterResize( } clampX = Math.max(clampX, aspectExtentClamp); } + + // TODO: Check if the child extent is restricting the resize } + // Do the same thing for vertical resizing if (isVertical) { const aspectWidthClamp = getSizeClamp(newHeight * aspectRatio, minWidth, maxWidth) / aspectRatio; clampY = Math.max(clampY, aspectWidthClamp); @@ -177,16 +205,14 @@ export function getDimensionsAfterResize( } clampY = Math.max(clampY, aspectExtentClamp); } + + // TODO: Check if the child extent is restricting the resize } } distY = distY + (distY < 0 ? clampY : -clampY); distX = distX + (distX < 0 ? clampX : -clampX); - function xor(a: boolean, b: boolean) { - return a ? !b : b; - } - if (keepAspectRatio) { if (isDiagonal) { if (newWidth > newHeight * aspectRatio) { @@ -196,19 +222,11 @@ export function getDimensionsAfterResize( } } else { if (isHorizontal) { - if (affectsX) { - distY = distX / aspectRatio; - affectsY = true; - } else { - distY = distX / aspectRatio; - } + distY = distX / aspectRatio; + affectsY = affectsX; } else { - if (affectsY) { - distX = distY * aspectRatio; - affectsX = true; - } else { - distX = distY * aspectRatio; - } + distX = distY * aspectRatio; + affectsX = affectsY; } } } @@ -226,33 +244,3 @@ export function getDimensionsAfterResize( y, }; } - -/** - * Determines new x & y position of node after resize based on new width & height - * @param startValues - starting values of resize - * @param controlDirection - dimensions affected by the resize - * @param width - new width of node - * @param height - new height of node - * @returns x: new x position of node, y: new y position of node - */ -export function getPositionAfterResize( - startValues: StartValues, - controlDirection: ReturnType, - width: number, - height: number, - extent?: CoordinateExtent -) { - let x = controlDirection.affectsX ? startValues.x - (width - startValues.width) : startValues.x; - let y = controlDirection.affectsY ? startValues.y - (height - startValues.height) : startValues.y; - let clampedX = 0; - let clampedY = 0; - - if (extent) { - clampedX = Math.max(Math.max(0, x - extent[1][0]), Math.max(0, extent[0][0] - x)); - clampedY = Math.max(Math.max(0, y - extent[1][1]), Math.max(0, extent[0][1] - y)); - x = x + clampedX; - y = y + clampedY; - } - - return { x, y, clampedX, clampedY }; -}