/** * @module RigidBodyBehaviors * Apply the following contraints to the `container` : * - The container must be kept inside its parent * - The container must find an unallocated space within the parent * If the contraints fails, an error message will be returned */ import Swal from 'sweetalert2'; import { IContainerModel } from '../../../Interfaces/IContainerModel'; import { ISizePointer } from '../../../Interfaces/ISizePointer'; import { ENABLE_HARD_RIGID } from '../../../utils/default'; /** * "Transform the container into a rigid body" * Apply the following contraints to the `container` : * - The container must be kept inside its parent * - The container must find an unallocated space within the parent * If the contraints fails, an error message will be returned * @param container Container to apply its rigid body properties * @returns A rigid body container */ export function ApplyRigidBody( container: IContainerModel ): IContainerModel { container = ConstraintBodyInsideParent(container); if (ENABLE_HARD_RIGID) { container = ConstraintBodyInsideUnallocatedWidth(container); } return container; } /** * Limit a rect inside a parent rect by applying the following rules : * it cannot be bigger than the parent * it cannot go out of bound * Mutates and returns the container * @param container * @returns Updated container */ function ConstraintBodyInsideParent( container: IContainerModel ): IContainerModel { if (container.parent === null || container.parent === undefined) { return container; } const parentProperties = container.parent.properties; const parentWidth = parentProperties.width; const parentHeight = parentProperties.height; return ConstraintBodyInsideSpace(container, 0, 0, parentWidth, parentHeight); } /** * Limit a container inside a rectangle * Mutates and returns the container * @param container A container * @param x x of top left of the rectangle * @param y y of top left of the rectangle * @param width width of the rectangle * @param height height of the rectangle * @returns Updated container */ function ConstraintBodyInsideSpace( container: IContainerModel, x: number, y: number, width: number, height: number ): IContainerModel { const containerProperties = container.properties; const containerX = containerProperties.x; const containerY = containerProperties.y; const containerWidth = containerProperties.width; const containerHeight = containerProperties.height; // Check size bigger than parent const isBodyLargerThanParent = containerWidth > width; const isBodyTallerThanParentHeight = containerHeight > height; if (isBodyLargerThanParent || isBodyTallerThanParentHeight) { if (isBodyLargerThanParent) { containerProperties.x = x; containerProperties.width = width; } if (isBodyTallerThanParentHeight) { containerProperties.y = y; containerProperties.height = height; } return container; } // Check horizontal out of bound if (containerX < x) { containerProperties.x = x; } if (containerX + containerWidth > x + width) { containerProperties.x = x + width - containerWidth; } // Check vertical out of bound if (containerY < y) { containerProperties.y = y; } if (containerY + containerHeight > y + height) { containerProperties.y = y + height - containerHeight; } return container; } /** * Constraint the container inside unallocated width/space of the parent container * If there is no unalloacted width/space, an error will be thrown * Mutates and returns the container * @param container * @returns Updated container */ export function ConstraintBodyInsideUnallocatedWidth( container: IContainerModel ): IContainerModel { if (container.parent === null || container.parent === undefined) { return container; } // Get the available spaces of the parent const availableWidths = GetAvailableWidths(container.parent, container); const containerX = container.properties.x; const containerWidth = container.properties.width; // Check if there is still some space if (availableWidths.length === 0) { Swal.fire({ icon: 'error', title: 'Not enough space!', text: 'Try to free the parent a little bit!' }); throw new Error( 'No available space found on the parent container. Try to free the parent a bit.' ); } const middle = containerX + containerWidth / 2; // Sort the available width to find the space with the closest position availableWidths.sort( (width1, width2) => { let compared1X = width1.x; if (width1.x < containerX) { compared1X = width1.x + width1.width - containerWidth; } let compared2X = width2.x; if (width2.x < containerX) { compared2X = width2.x + width2.width - containerWidth; } return Math.abs(compared1X - middle) - Math.abs(compared2X - middle); } ); // Check if the container actually fit inside // It will usually fit if it was alrady fitting const availableWidthFound = availableWidths.find((width) => IsFitting(container.properties.width, width) ); if (availableWidthFound === undefined) { // Otherwise, it is possible that it does not fit // There is two way to reach this part of the code // 1) Enable isRigidBody such as width > availableWidth.width // 2) Resize a container such as width > availableWidth.width // We want the container to fit automatically inside the available space // even if it means to resize the container const availableWidth: ISizePointer | undefined = availableWidths.find((width) => { return IsFitting(container.properties.minWidth, width); }); if (availableWidth === undefined) { console.debug(`Container ${container.properties.id} cannot fit in any space due to its minimum width being to large.`); // Swal.fire({ // position: 'top-end', // title: `Container ${container.properties.id} cannot fit!`, // text: 'Its rigid body property is now disabled. Change its the minimum width or free the parent container.', // timerProgressBar: true, // showConfirmButton: false, // timer: 5000 // }); return container; } container.properties.x = availableWidth.x; container.properties.width = availableWidth.width; return container; } return ConstraintBodyInsideSpace( container, availableWidthFound.x, 0, availableWidthFound.width, container.parent.properties.height ); } /** * Check if a container can fit inside a size space * @param container Container to check * @param sizePointer Size space to check * @returns */ function IsFitting(containerWidth: number, sizePointer: ISizePointer): boolean { return containerWidth <= sizePointer.width; } /** * Get the unallocated widths inside a container * An allocated width is defined by its the widths of the children that are rigid bodies. * An example of this allocation system is the disk space of an hard drive * (except the fact that disk space is divided by block). * @param container Container where to find an available width * @param exception Container to exclude of the widths (since a container will be moved, it might need to be excluded) * @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space) */ function GetAvailableWidths( container: IContainerModel, exception: IContainerModel ): ISizePointer[] { // Initialize the first size pointer // which takes full width of the available space const x = 0; const width = container.properties.width; let unallocatedSpaces: ISizePointer[] = [{ x, width }]; for (const child of container.children) { if (unallocatedSpaces.length < 1) { return unallocatedSpaces; } // Ignore the exception // And we will also only uses containers that also are rigid or are anchors if (child === exception) { continue; } const childX = child.properties.x; const childWidth = child.properties.width; // get the space of the child that is inside the parent let newUnallocatedSpace: ISizePointer[] = []; // We will iterate on a mutable variable in order to divide it for (const unallocatedSpace of unallocatedSpaces) { // In order to find unallocated space, // We need to calculate the overlap between the two containers // We only works with widths meaning in 1D (with lines) const newUnallocatedWidths = GetAvailableWidthsTwoLines( unallocatedSpace, childX, childWidth ); // Concat the new list of SizePointer pointing to availables spaces newUnallocatedSpace = newUnallocatedSpace.concat(newUnallocatedWidths); } // Finally update the availables spaces found, loop again with it unallocatedSpaces = newUnallocatedSpace; } return unallocatedSpaces; } /** * Returns the unallocated widths between two lines in 1D * @param unalloctedSpace unallocated space * @param rectX left of the second line * @param rectWidth width of the second line * @returns Available widths */ function GetAvailableWidthsTwoLines( unallocatedSpace: ISizePointer, rectX: number, rectWidth: number ): ISizePointer[] { const unallocatedSpaceRight = unallocatedSpace.x + unallocatedSpace.width; const rectRight = rectX + rectWidth; const isNotOverlapping = unallocatedSpaceRight < rectX || unallocatedSpace.x > rectRight; if (isNotOverlapping) { return [unallocatedSpace]; } const isOverlappingFullWidth = rectX < unallocatedSpace.x && rectRight > unallocatedSpaceRight; if (isOverlappingFullWidth) { return []; } const isOverlappingOnTheLeft = unallocatedSpace.x >= rectX; if (isOverlappingOnTheLeft) { return GetAvailableWidthsLeft(unallocatedSpaceRight, rectRight); } const isOverlappingOnTheRight = rectRight >= unallocatedSpaceRight; if (isOverlappingOnTheRight) { return GetAvailableWidthsRight(unallocatedSpace.x, rectX); } return GetAvailableWidthsMiddle(unallocatedSpace.x, unallocatedSpaceRight, rectX, rectRight); } function GetAvailableWidthsLeft(unallocatedSpaceRight: number, rectRight: number): ISizePointer[] { if (unallocatedSpaceRight - rectRight <= 0) { return []; } return [ { x: rectRight, width: unallocatedSpaceRight - rectRight } ]; } function GetAvailableWidthsRight(unallocatedSpaceX: number, rectX: number): ISizePointer[] { if (rectX - unallocatedSpaceX <= 0) { return []; } return [ { x: unallocatedSpaceX, width: rectX - unallocatedSpaceX } ]; } function GetAvailableWidthsMiddle( unallocatedSpaceX: number, unallocatedSpaceRight: number, rectX: number, rectRight: number ): ISizePointer[] { const sizePointers: ISizePointer[] = []; if (rectX - unallocatedSpaceX > 0) { sizePointers.push({ x: unallocatedSpaceX, width: rectX - unallocatedSpaceX }); } if (unallocatedSpaceRight - rectRight > 0) { sizePointers.push({ x: rectRight, width: unallocatedSpaceRight - rectRight }); } return sizePointers; }