Merged PR 196: Implement Vertical orientation + Upgrade Heroicons to 2.0
Implémenter l'orientation verticale Modifier l'effet de append Implementer RigidBody Implementer Flex et simplex Implémenter Push Implémenter Swap Implement MinMaxHeight without behaviors Fix Margin for Height Implement PositionReference Fix dimension vertical position inside children Add orientation change in form Implement sortChildren Implement Anchor Fix warning message on overlapping Fix minimap when root container is vertical #7287 #7288 #7289 #7290 #7291 #7292 #7294 #7295 #7296 #7297 #7298 #7299 #7300 #7301 #7302
This commit is contained in:
parent
459e83a0c8
commit
18cbacaca1
45 changed files with 2112 additions and 1063 deletions
|
@ -9,6 +9,7 @@ import { IContainerModel, ContainerModel } from '../../../Interfaces/IContainerM
|
|||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { IPattern, GetPattern, ContainerOrPattern } from '../../../Interfaces/IPattern';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { GetDefaultContainerProps, DEFAULTCHILDTYPE_MAX_DEPTH, DEFAULTCHILDTYPE_ALLOW_CYCLIC } from '../../../utils/default';
|
||||
import { FindContainerById } from '../../../utils/itertools';
|
||||
import { ApplyMargin } from '../../../utils/svg';
|
||||
|
@ -137,12 +138,12 @@ function AddNewContainerToParent(
|
|||
let x = containerConfig.X ?? 0;
|
||||
let y = containerConfig.Y ?? 0;
|
||||
let width = containerConfig.Width ?? containerConfig.MaxWidth ?? containerConfig.MinWidth ?? parentClone.properties.width;
|
||||
let height = containerConfig.Height ?? parentClone.properties.height;
|
||||
let height = containerConfig.Height ?? containerConfig.MaxHeight ?? containerConfig.MinHeight ?? parentClone.properties.height;
|
||||
|
||||
({ x, y, width, height } = ApplyMargin(x, y, width, height, left, bottom, top, right));
|
||||
|
||||
// Apply an add method (append or insert/replace)
|
||||
x = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x);
|
||||
({ x, y } = ApplyAddMethod(index + typeIndex, containerConfig, parentClone, x, y));
|
||||
|
||||
// Set the counter of the object type in order to assign an unique id
|
||||
UpdateCounters(newCounters, type);
|
||||
|
@ -400,8 +401,9 @@ function ApplyAddMethod(
|
|||
index: number,
|
||||
containerConfig: IAvailableContainer,
|
||||
parent: IContainerModel,
|
||||
x: number
|
||||
): number {
|
||||
x: number,
|
||||
y: number
|
||||
): { x: number, y: number } {
|
||||
if (index > 0 && (
|
||||
containerConfig.AddMethod === undefined ||
|
||||
containerConfig.AddMethod === null ||
|
||||
|
@ -412,8 +414,13 @@ function ApplyAddMethod(
|
|||
.at(index - 1);
|
||||
|
||||
if (lastChild !== undefined) {
|
||||
x += (lastChild.properties.x + lastChild.properties.width);
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
if (isHorizontal) {
|
||||
x += lastChild.properties.x + lastChild.properties.width;
|
||||
} else {
|
||||
y += lastChild.properties.y + lastChild.properties.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
return x;
|
||||
return { x, y };
|
||||
}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { IHistoryState } from '../../../Interfaces/IHistoryState';
|
||||
import { ContainerModel, IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { FindContainerById, MakeIterator } from '../../../utils/itertools';
|
||||
import { FindContainerById, MakeDFSIterator } from '../../../utils/itertools';
|
||||
import { GetCurrentHistory } from '../Editor';
|
||||
import { ApplyBehaviors, ApplyBehaviorsOnSiblings, ApplyBehaviorsOnSiblingsChildren } from '../Behaviors/Behaviors';
|
||||
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
|
||||
import Swal from 'sweetalert2';
|
||||
import { PropertyType } from '../../../Enums/PropertyType';
|
||||
import { TransformX } from '../../../utils/svg';
|
||||
import { TransformX, TransformY } from '../../../utils/svg';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
|
||||
/**
|
||||
* Select a container
|
||||
|
@ -133,7 +134,7 @@ function GetSelectedContainerOnDelete(
|
|||
* @param container Container to unlink
|
||||
*/
|
||||
function UnlinkContainerFromSymbols(symbols: Map<string, ISymbolModel>, container: IContainerModel): void {
|
||||
const it = MakeIterator(container);
|
||||
const it = MakeDFSIterator(container);
|
||||
for (const child of it) {
|
||||
const symbol = symbols.get(child.properties.linkedSymbolId);
|
||||
if (symbol === undefined) {
|
||||
|
@ -194,11 +195,34 @@ export function SortChildren(parentClone: IContainerModel | null | undefined): v
|
|||
if (parentClone === null || parentClone === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isHorizontal = parentClone.properties.orientation === Orientation.Horizontal;
|
||||
const children = parentClone.children;
|
||||
|
||||
if (!isHorizontal) {
|
||||
parentClone.children.sort(
|
||||
(a, b) => {
|
||||
const yA = TransformY(a.properties.y, a.properties.height, a.properties.positionReference);
|
||||
const yB = TransformY(b.properties.y, b.properties.height, b.properties.positionReference);
|
||||
if (yA < yB) {
|
||||
return -1;
|
||||
}
|
||||
if (yB < yA) {
|
||||
return 1;
|
||||
}
|
||||
// xA = xB
|
||||
const indexA = children.indexOf(a);
|
||||
const indexB = children.indexOf(b);
|
||||
return indexA - indexB;
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
parentClone.children.sort(
|
||||
(a, b) => {
|
||||
const xA = TransformX(a.properties.x, a.properties.width, a.properties.xPositionReference);
|
||||
const xB = TransformX(b.properties.x, b.properties.width, b.properties.xPositionReference);
|
||||
const xA = TransformX(a.properties.x, a.properties.width, a.properties.positionReference);
|
||||
const xB = TransformX(b.properties.x, b.properties.width, b.properties.positionReference);
|
||||
if (xA < xB) {
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
||||
|
||||
/**
|
||||
|
@ -21,17 +22,16 @@ import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
|
|||
* Apply the following modification to the overlapping rigid body container :
|
||||
* @param container Container to impose its position
|
||||
*/
|
||||
export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === undefined ||
|
||||
container.parent === null) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const rigidBodies = container.parent.children.filter(
|
||||
export function ApplyAnchor(container: IContainerModel, parent: IContainerModel): IContainerModel {
|
||||
const rigidBodies = parent.children.filter(
|
||||
child => !child.properties.isAnchor
|
||||
);
|
||||
|
||||
const overlappingContainers = GetOverlappingContainers(container, rigidBodies);
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
const overlappingContainers = isHorizontal
|
||||
? GetHorizontallyOverlappingContainers(container, rigidBodies)
|
||||
: GetVerticallyOverlappingContainers(container, rigidBodies);
|
||||
|
||||
for (const overlappingContainer of overlappingContainers) {
|
||||
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ export function ApplyAnchor(container: IContainerModel): IContainerModel {
|
|||
* @param containers A list of containers
|
||||
* @returns A list of overlapping containers
|
||||
*/
|
||||
export function GetOverlappingContainers(
|
||||
export function GetHorizontallyOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
|
@ -68,3 +68,63 @@ export function GetOverlappingContainers(
|
|||
}
|
||||
return overlappingContainers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the overlapping containers with container
|
||||
* @param container A container
|
||||
* @param containers A list of containers
|
||||
* @returns A list of overlapping containers
|
||||
*/
|
||||
export function GetVerticallyOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
const min1 = container.properties.y;
|
||||
const max1 = container.properties.y + container.properties.height;
|
||||
const overlappingContainers: IContainerModel[] = [];
|
||||
for (const other of containers) {
|
||||
if (other === container) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const min2 = other.properties.y;
|
||||
const max2 = other.properties.y + other.properties.height;
|
||||
const isOverlapping = Math.min(max1, max2) - Math.max(min1, min2) > 0;
|
||||
|
||||
if (!isOverlapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
overlappingContainers.push(other);
|
||||
}
|
||||
return overlappingContainers;
|
||||
}
|
||||
|
||||
export function GetOverlappingContainers(
|
||||
container: IContainerModel,
|
||||
containers: IContainerModel[]
|
||||
): IContainerModel[] {
|
||||
const overlappingContainers: IContainerModel[] = [];
|
||||
for (const other of containers) {
|
||||
if (other === container) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DoOverlap(container, other) && overlappingContainers.push(other);
|
||||
}
|
||||
return overlappingContainers;
|
||||
}
|
||||
|
||||
function DoOverlap(container: IContainerModel, other: IContainerModel): boolean {
|
||||
if (container.properties.x >= other.properties.x + other.properties.width ||
|
||||
other.properties.x >= container.properties.x + container.properties.width) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container.properties.y >= other.properties.y + other.properties.height ||
|
||||
other.properties.y >= container.properties.y + container.properties.height) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -20,18 +20,22 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
|
|||
ApplySymbol(container, symbol);
|
||||
}
|
||||
|
||||
if (container.properties.isAnchor) {
|
||||
ApplyAnchor(container);
|
||||
}
|
||||
if (container.parent !== undefined && container.parent !== null) {
|
||||
const parent = container.parent;
|
||||
|
||||
if (ENABLE_SWAP) {
|
||||
ApplySwap(container);
|
||||
}
|
||||
if (container.properties.isAnchor) {
|
||||
ApplyAnchor(container, parent);
|
||||
}
|
||||
|
||||
Flex(container);
|
||||
if (ENABLE_SWAP) {
|
||||
ApplySwap(container, parent);
|
||||
}
|
||||
|
||||
if (ENABLE_RIGID) {
|
||||
ApplyRigidBody(container);
|
||||
Flex(container, parent);
|
||||
|
||||
if (ENABLE_RIGID) {
|
||||
ApplyRigidBody(container, parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (APPLY_BEHAVIORS_ON_CHILDREN) {
|
||||
|
@ -68,13 +72,9 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
|
|||
newContainer.parent.children
|
||||
.forEach((container: IContainerModel) => {
|
||||
if (container.parent != null) {
|
||||
const overlappingContainers = GetOverlappingContainers(container, container.parent.children);
|
||||
if (overlappingContainers.length > 0) {
|
||||
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
|
||||
} else {
|
||||
container.properties.warning = '';
|
||||
}
|
||||
UpdateWarning(container, container.parent);
|
||||
}
|
||||
|
||||
if (container === newContainer) {
|
||||
return;
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Iterate over the siblings of newContainer and apply the behaviors
|
||||
* @param newContainer
|
||||
|
@ -102,13 +101,9 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
|
|||
ApplyBehaviors(container, symbols);
|
||||
|
||||
if (container.parent != null) {
|
||||
const overlappingContainers = GetOverlappingContainers(container, container.parent.children);
|
||||
if (overlappingContainers.length > 0) {
|
||||
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
|
||||
} else {
|
||||
container.properties.warning = '';
|
||||
}
|
||||
UpdateWarning(container, container.parent);
|
||||
}
|
||||
|
||||
if (container === newContainer) {
|
||||
return;
|
||||
}
|
||||
|
@ -118,4 +113,11 @@ export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols:
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
function UpdateWarning(container: IContainerModel, parent: IContainerModel): void {
|
||||
const overlappingContainers = GetOverlappingContainers(container, parent.children);
|
||||
if (overlappingContainers.length > 0) {
|
||||
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
|
||||
} else {
|
||||
container.properties.warning = '';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { Simplex } from '../../../utils/simplex';
|
||||
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
|
||||
|
||||
|
@ -10,27 +11,111 @@ interface IFlexibleGroup {
|
|||
|
||||
/**
|
||||
* Flex the container and its siblings (mutate)
|
||||
* @param container Container to flex
|
||||
* @returns Flexed container
|
||||
*/
|
||||
export function Flex(container: IContainerModel): void {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
export function Flex(container: IContainerModel, parent: IContainerModel): void {
|
||||
const isVertical = parent.properties.orientation === Orientation.Vertical;
|
||||
|
||||
if (isVertical) {
|
||||
const wantedWidth = Math.min(container.properties.maxWidth, parent.properties.width);
|
||||
container.properties.width = ApplyWidthMargin(wantedWidth, container.properties.margin.left, container.properties.margin.right);
|
||||
const flexibleGroups = GetVerticalFlexibleGroups(parent);
|
||||
for (const flexibleGroup of flexibleGroups) {
|
||||
FlexGroupVertically(flexibleGroup);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const flexibleGroups = GetFlexibleGroups(container.parent);
|
||||
|
||||
const wantedHeight = Math.min(container.properties.maxHeight, parent.properties.height);
|
||||
container.properties.height = ApplyWidthMargin(wantedHeight, container.properties.margin.top, container.properties.margin.bottom);
|
||||
const flexibleGroups = GetHorizontalFlexibleGroups(parent);
|
||||
for (const flexibleGroup of flexibleGroups) {
|
||||
FlexGroup(flexibleGroup);
|
||||
FlexGroupHorizontally(flexibleGroup);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of groups of flexible containers
|
||||
* @param parent Parent in which the flexible children will be set in groups
|
||||
* @returns a list of groups of flexible containers
|
||||
*/
|
||||
export function GetHorizontalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
|
||||
const flexibleGroups: IFlexibleGroup[] = [];
|
||||
let group: IContainerModel[] = [];
|
||||
let offset = 0;
|
||||
let size = 0;
|
||||
for (const child of parent.children) {
|
||||
if (child.properties.isAnchor) {
|
||||
size = child.properties.x - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
offset = child.properties.x + child.properties.width;
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
group.push(child);
|
||||
}
|
||||
size = parent.properties.width - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
return flexibleGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of groups of flexible containers
|
||||
* @param parent Parent in which the flexible children will be set in groups
|
||||
* @returns a list of groups of flexible containers
|
||||
*/
|
||||
export function GetVerticalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
|
||||
const flexibleGroups: IFlexibleGroup[] = [];
|
||||
let group: IContainerModel[] = [];
|
||||
let offset = 0;
|
||||
let size = 0;
|
||||
for (const child of parent.children) {
|
||||
if (child.properties.isAnchor) {
|
||||
size = child.properties.y - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
offset = child.properties.y + child.properties.height;
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
group.push(child);
|
||||
}
|
||||
size = parent.properties.height - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
return flexibleGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply flex to the group
|
||||
* @param flexibleGroup Group that contains a list of flexible containers
|
||||
* @returns
|
||||
*/
|
||||
function FlexGroup(flexibleGroup: IFlexibleGroup): void {
|
||||
function FlexGroupHorizontally(flexibleGroup: IFlexibleGroup): void {
|
||||
const children = flexibleGroup.group;
|
||||
const {
|
||||
flexibleContainers,
|
||||
|
@ -96,6 +181,77 @@ function FlexGroup(flexibleGroup: IFlexibleGroup): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply flex to the group
|
||||
* @param flexibleGroup Group that contains a list of flexible containers
|
||||
* @returns
|
||||
*/
|
||||
function FlexGroupVertically(flexibleGroup: IFlexibleGroup): void {
|
||||
const children = flexibleGroup.group;
|
||||
const {
|
||||
flexibleContainers,
|
||||
nonFlexibleContainers
|
||||
} = SeparateFlexibleContainers(children);
|
||||
|
||||
if (flexibleContainers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const minHeights = flexibleContainers
|
||||
.map(sibling => sibling.properties.minHeight);
|
||||
|
||||
const fixedHeight = nonFlexibleContainers
|
||||
.map(sibling => sibling.properties.height)
|
||||
.reduce((heightSum, a) => heightSum + a, 0);
|
||||
|
||||
const requiredMaxHeight = flexibleGroup.size - fixedHeight;
|
||||
const minimumPossibleHeight = minHeights.reduce((heightSum, a) => heightSum + a, 0); // sum(minHeights)
|
||||
|
||||
const checkSumMinHeightsIsFitting = minimumPossibleHeight > requiredMaxHeight;
|
||||
if (checkSumMinHeightsIsFitting) {
|
||||
console.warn('[FlexBehavior] Cannot fit at all even when squeezing all flex containers to the minimum.');
|
||||
return;
|
||||
}
|
||||
|
||||
const maxMinHeights = Math.max(...minHeights);
|
||||
if (maxMinHeights * minHeights.length <= requiredMaxHeight) {
|
||||
const wantedHeight = requiredMaxHeight / minHeights.length;
|
||||
// it fits, flex with maxMinHeights and fixed height
|
||||
let right = flexibleGroup.offset;
|
||||
for (const sibling of children) {
|
||||
if (!sibling.properties.isFlex) {
|
||||
sibling.properties.y = right;
|
||||
right += sibling.properties.height;
|
||||
continue;
|
||||
}
|
||||
sibling.properties.y = ApplyXMargin(right, sibling.properties.margin.top);
|
||||
sibling.properties.height = ApplyWidthMargin(wantedHeight, sibling.properties.margin.top, sibling.properties.margin.bottom);
|
||||
right += wantedHeight;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// does not fit
|
||||
|
||||
/// SIMPLEX ///
|
||||
const maxHeights = flexibleContainers
|
||||
.map(sibling => sibling.properties.maxHeight);
|
||||
const solutions: number[] = Simplex(minHeights, maxHeights, requiredMaxHeight);
|
||||
|
||||
// apply the solutions
|
||||
for (let i = 0; i < flexibleContainers.length; i++) {
|
||||
flexibleContainers[i].properties.height = ApplyWidthMargin(solutions[i], flexibleContainers[i].properties.margin.top, flexibleContainers[i].properties.margin.bottom);
|
||||
}
|
||||
|
||||
// move the containers
|
||||
let right = flexibleGroup.offset;
|
||||
for (const sibling of children) {
|
||||
sibling.properties.y = ApplyXMargin(right, sibling.properties.margin.top);
|
||||
right += sibling.properties.height;
|
||||
}
|
||||
}
|
||||
|
||||
function SeparateFlexibleContainers(
|
||||
containers: IContainerModel[]
|
||||
): { flexibleContainers: IContainerModel[], nonFlexibleContainers: IContainerModel[] } {
|
||||
|
@ -114,41 +270,3 @@ function SeparateFlexibleContainers(
|
|||
nonFlexibleContainers
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of groups of flexible containers
|
||||
* @param parent Parent in which the flexible children will be set in groups
|
||||
* @returns a list of groups of flexible containers
|
||||
*/
|
||||
export function GetFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
|
||||
const flexibleGroups: IFlexibleGroup[] = [];
|
||||
let group: IContainerModel[] = [];
|
||||
let offset = 0;
|
||||
let size = 0;
|
||||
for (const child of parent.children) {
|
||||
if (child.properties.isAnchor) {
|
||||
size = child.properties.x - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
offset = child.properties.x + child.properties.width;
|
||||
group = [];
|
||||
continue;
|
||||
}
|
||||
|
||||
group.push(child);
|
||||
}
|
||||
size = parent.properties.width - offset;
|
||||
const flexibleGroup: IFlexibleGroup = {
|
||||
group,
|
||||
offset,
|
||||
size
|
||||
};
|
||||
|
||||
flexibleGroups.push(flexibleGroup);
|
||||
return flexibleGroups;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { ReversePairwise } from '../../../utils/itertools';
|
||||
import { Flex } from './FlexBehaviors';
|
||||
|
||||
|
@ -7,17 +8,27 @@ import { Flex } from './FlexBehaviors';
|
|||
* @param container
|
||||
* @returns
|
||||
*/
|
||||
export function PushContainers(container: IContainerModel): IContainerModel {
|
||||
if (container.parent === null) {
|
||||
export function ApplyPush(container: IContainerModel, parent: IContainerModel): IContainerModel {
|
||||
if (parent.children.length <= 1) {
|
||||
return container;
|
||||
}
|
||||
|
||||
if (container.parent.children.length <= 1) {
|
||||
return container;
|
||||
const children = parent.children;
|
||||
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
|
||||
|
||||
if (isHorizontal) {
|
||||
PushContainersHorizontally(container, children);
|
||||
} else {
|
||||
PushContainersVertically(container, children);
|
||||
}
|
||||
|
||||
const prevIndex = container.parent.children.length - 2;
|
||||
const prev: IContainerModel = container.parent.children[prevIndex];
|
||||
Flex(container, parent);
|
||||
return container;
|
||||
}
|
||||
|
||||
export function PushContainersHorizontally(container: IContainerModel, children: IContainerModel[]): IContainerModel {
|
||||
const prevIndex = children.length - 2;
|
||||
const prev: IContainerModel = children[prevIndex];
|
||||
const isOverlapping = prev.properties.x + prev.properties.width > container.properties.x;
|
||||
if (!isOverlapping) {
|
||||
return container;
|
||||
|
@ -32,7 +43,7 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
|||
// FIXME: A fix was applied using toFixed(2).
|
||||
// FIXME: A coverture check must be done to ensure that all scenarios are covered
|
||||
|
||||
const it = ReversePairwise<IContainerModel>(container.parent.children.filter(child => child !== container));
|
||||
const it = ReversePairwise<IContainerModel>(children.filter(child => child !== container));
|
||||
|
||||
for (const { cur, next } of it) {
|
||||
const hasSpaceBetween = next.properties.x + next.properties.width < cur.properties.x;
|
||||
|
@ -48,9 +59,9 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
|||
break;
|
||||
}
|
||||
|
||||
const indexLastContainer = container.parent.children.indexOf(lastContainer);
|
||||
for (let i = indexLastContainer; i <= container.parent.children.length - 2; i++) {
|
||||
const sibling = container.parent.children[i];
|
||||
const indexLastContainer = children.indexOf(lastContainer);
|
||||
for (let i = indexLastContainer; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.x -= space;
|
||||
}
|
||||
}
|
||||
|
@ -58,16 +69,71 @@ export function PushContainers(container: IContainerModel): IContainerModel {
|
|||
const hasNoSpaceBetween = lastContainer === null;
|
||||
if (hasNoSpaceBetween) {
|
||||
// test gap between the left of the parent and the first container
|
||||
space = container.parent.children[0].properties.x;
|
||||
space = children[0].properties.x;
|
||||
if (space > 0) {
|
||||
for (let i = 0; i <= container.parent.children.length - 2; i++) {
|
||||
const sibling = container.parent.children[i];
|
||||
for (let i = 0; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.x -= space;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
Flex(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
export function PushContainersVertically(container: IContainerModel, children: IContainerModel[]): IContainerModel {
|
||||
const prevIndex = children.length - 2;
|
||||
const prev: IContainerModel = children[prevIndex];
|
||||
const isOverlapping = prev.properties.y + prev.properties.height > container.properties.y;
|
||||
if (!isOverlapping) {
|
||||
return container;
|
||||
}
|
||||
|
||||
// find hole
|
||||
let lastContainer: IContainerModel | null = null;
|
||||
let space: number = 0;
|
||||
|
||||
while (space.toFixed(2) < container.properties.height.toFixed(2)) {
|
||||
// FIXME: possible infinite loop due to floating point
|
||||
// FIXME: A fix was applied using toFixed(2).
|
||||
// FIXME: A coverture check must be done to ensure that all scenarios are covered
|
||||
|
||||
const it = ReversePairwise<IContainerModel>(children.filter(child => child !== container));
|
||||
|
||||
for (const { cur, next } of it) {
|
||||
const hasSpaceBetween = next.properties.y + next.properties.height < cur.properties.y;
|
||||
if (hasSpaceBetween) {
|
||||
lastContainer = cur;
|
||||
space = cur.properties.y - (next.properties.y + next.properties.height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastContainer === null) {
|
||||
// no space between
|
||||
break;
|
||||
}
|
||||
|
||||
const indexLastContainer = children.indexOf(lastContainer);
|
||||
for (let i = indexLastContainer; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.y -= space;
|
||||
}
|
||||
}
|
||||
|
||||
const hasNoSpaceBetween = lastContainer === null;
|
||||
if (hasNoSpaceBetween) {
|
||||
// test gap between the left of the parent and the first container
|
||||
space = children[0].properties.y;
|
||||
if (space > 0) {
|
||||
for (let i = 0; i <= children.length - 2; i++) {
|
||||
const sibling = children[i];
|
||||
sibling.properties.y -= space;
|
||||
}
|
||||
return container;
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { ISizePointer } from '../../../Interfaces/ISizePointer';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { ENABLE_HARD_RIGID } from '../../../utils/default';
|
||||
|
||||
/**
|
||||
|
@ -20,9 +21,10 @@ import { ENABLE_HARD_RIGID } from '../../../utils/default';
|
|||
* @returns A rigid body container
|
||||
*/
|
||||
export function ApplyRigidBody(
|
||||
container: IContainerModel
|
||||
container: IContainerModel,
|
||||
parent: IContainerModel
|
||||
): IContainerModel {
|
||||
container = ConstraintBodyInsideParent(container);
|
||||
container = ConstraintBodyInsideParent(container, parent);
|
||||
|
||||
if (ENABLE_HARD_RIGID) {
|
||||
container = ConstraintBodyInsideUnallocatedWidth(container);
|
||||
|
@ -40,13 +42,10 @@ export function ApplyRigidBody(
|
|||
* @returns Updated container
|
||||
*/
|
||||
function ConstraintBodyInsideParent(
|
||||
container: IContainerModel
|
||||
container: IContainerModel,
|
||||
parent: IContainerModel
|
||||
): IContainerModel {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const parentProperties = container.parent.properties;
|
||||
const parentProperties = parent.properties;
|
||||
const parentWidth = parentProperties.width;
|
||||
const parentHeight = parentProperties.height;
|
||||
|
||||
|
@ -125,9 +124,15 @@ export function ConstraintBodyInsideUnallocatedWidth(
|
|||
}
|
||||
|
||||
// Get the available spaces of the parent
|
||||
const availableWidths = GetAvailableWidths(container.parent, container);
|
||||
const containerX = container.properties.x;
|
||||
const containerWidth = container.properties.width;
|
||||
const isHorizontal =
|
||||
container.parent.properties.orientation === Orientation.Horizontal;
|
||||
const availableWidths = GetAvailableWidths(
|
||||
0,
|
||||
container.parent.properties.width,
|
||||
container.parent.children,
|
||||
container,
|
||||
isHorizontal
|
||||
);
|
||||
|
||||
// Check if there is still some space
|
||||
if (availableWidths.length === 0) {
|
||||
|
@ -136,6 +141,103 @@ export function ConstraintBodyInsideUnallocatedWidth(
|
|||
);
|
||||
}
|
||||
|
||||
const containerId = container.properties.id;
|
||||
|
||||
if (!isHorizontal) {
|
||||
const containerY = container.properties.y;
|
||||
const containerHeight = container.properties.height;
|
||||
const containerMinHeight = container.properties.minHeight;
|
||||
|
||||
SortAvailableWidthsByClosest(containerY, containerHeight, availableWidths);
|
||||
|
||||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
IsFitting(containerHeight, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
const { x, width } = TrySqueeze(containerY, containerHeight, containerMinHeight, containerId, availableWidths);
|
||||
|
||||
container.properties.y = x;
|
||||
container.properties.height = width;
|
||||
return container;
|
||||
}
|
||||
|
||||
ConstraintBodyInsideSpace(
|
||||
container,
|
||||
0,
|
||||
availableWidthFound.x,
|
||||
container.parent.properties.width,
|
||||
availableWidthFound.width
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
const containerX = container.properties.x;
|
||||
const containerWidth = container.properties.width;
|
||||
const containerMinWidth = container.properties.minWidth;
|
||||
|
||||
SortAvailableWidthsByClosest(containerX, containerWidth, availableWidths);
|
||||
|
||||
// Check if the container actually fit inside
|
||||
// It will usually fit if it was alrady fitting
|
||||
const availableWidthFound = availableWidths.find((width) =>
|
||||
IsFitting(containerWidth, width)
|
||||
);
|
||||
|
||||
if (availableWidthFound === undefined) {
|
||||
const { x, width } = TrySqueeze(containerX, containerWidth, containerMinWidth, containerId, availableWidths);
|
||||
container.properties.x = x;
|
||||
container.properties.width = width;
|
||||
return container;
|
||||
}
|
||||
|
||||
ConstraintBodyInsideSpace(
|
||||
container,
|
||||
availableWidthFound.x,
|
||||
0,
|
||||
availableWidthFound.width,
|
||||
container.parent.properties.height
|
||||
);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
function TrySqueeze(
|
||||
containerX: number,
|
||||
containerWidth: number,
|
||||
containerMinWidth: number,
|
||||
containerId: string,
|
||||
availableWidths: ISizePointer[]
|
||||
): { x: number, width: number } {
|
||||
// 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(containerMinWidth, width);
|
||||
});
|
||||
|
||||
if (availableWidth === undefined) {
|
||||
console.debug(`Container ${containerId} cannot fit in any space due to its minimum width being to large.`);
|
||||
return {
|
||||
x: containerX,
|
||||
width: containerWidth
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
x: availableWidth.x,
|
||||
width: availableWidth.width
|
||||
};
|
||||
}
|
||||
|
||||
function SortAvailableWidthsByClosest(containerX: number, containerWidth: number, availableWidths: ISizePointer[]): void {
|
||||
const middle = containerX + containerWidth / 2;
|
||||
// Sort the available width to find the space with the closest position
|
||||
availableWidths.sort(
|
||||
|
@ -153,42 +255,6 @@ export function ConstraintBodyInsideUnallocatedWidth(
|
|||
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.`);
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -212,16 +278,17 @@ function IsFitting(containerWidth: number,
|
|||
* @returns {ISizePointer[]} Array of unallocated widths (x=position of the unallocated space, width=size of the allocated space)
|
||||
*/
|
||||
function GetAvailableWidths(
|
||||
container: IContainerModel,
|
||||
exception: IContainerModel
|
||||
x: number,
|
||||
width: number,
|
||||
children: IContainerModel[],
|
||||
exception: IContainerModel,
|
||||
isHorizontal: boolean
|
||||
): 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) {
|
||||
for (const child of children) {
|
||||
if (unallocatedSpaces.length < 1) {
|
||||
return unallocatedSpaces;
|
||||
}
|
||||
|
@ -231,8 +298,8 @@ function GetAvailableWidths(
|
|||
if (child === exception) {
|
||||
continue;
|
||||
}
|
||||
const childX = child.properties.x;
|
||||
const childWidth = child.properties.width;
|
||||
const childX = isHorizontal ? child.properties.x : child.properties.y;
|
||||
const childWidth = isHorizontal ? child.properties.width : child.properties.height;
|
||||
|
||||
// get the space of the child that is inside the parent
|
||||
let newUnallocatedSpace: ISizePointer[] = [];
|
||||
|
|
|
@ -3,15 +3,23 @@
|
|||
*/
|
||||
|
||||
import { IContainerModel } from '../../../Interfaces/IContainerModel';
|
||||
import { GetOverlappingContainers } from './AnchorBehaviors';
|
||||
import { Orientation } from '../../../Interfaces/Orientation';
|
||||
import { GetHorizontallyOverlappingContainers, GetVerticallyOverlappingContainers } from './AnchorBehaviors';
|
||||
|
||||
export function ApplySwap(container: IContainerModel): void {
|
||||
if (container.parent === null || container.parent === undefined) {
|
||||
export function ApplySwap(container: IContainerModel, parent: IContainerModel): void {
|
||||
const children = parent.children;
|
||||
|
||||
const isVertical = parent.properties.orientation === Orientation.Vertical;
|
||||
if (isVertical) {
|
||||
SwapVertically(container, children);
|
||||
return;
|
||||
}
|
||||
|
||||
const children = container.parent.children;
|
||||
const overlappingContainers = GetOverlappingContainers(container, children);
|
||||
SwapHorizontally(container, children);
|
||||
}
|
||||
|
||||
export function SwapHorizontally(container: IContainerModel, children: IContainerModel[]): void {
|
||||
const overlappingContainers = GetHorizontallyOverlappingContainers(container, children);
|
||||
|
||||
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
|
||||
return;
|
||||
|
@ -29,3 +37,23 @@ export function ApplySwap(container: IContainerModel): void {
|
|||
const indexOverlapping = children.indexOf(overlappingContainer);
|
||||
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
|
||||
}
|
||||
|
||||
export function SwapVertically(container: IContainerModel, children: IContainerModel[]): void {
|
||||
const overlappingContainers = GetVerticallyOverlappingContainers(container, children);
|
||||
|
||||
if (overlappingContainers.length > 1 || overlappingContainers.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const overlappingContainer = overlappingContainers.pop();
|
||||
|
||||
if (overlappingContainer === null || overlappingContainer === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// swap positions
|
||||
[overlappingContainer.properties.y, container.properties.y] = [container.properties.y, overlappingContainer.properties.y];
|
||||
const indexContainer = children.indexOf(container);
|
||||
const indexOverlapping = children.indexOf(overlappingContainer);
|
||||
[children[indexContainer], children[indexOverlapping]] = [children[indexOverlapping], children[indexContainer]];
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { RestoreX, TransformX } from '../../../utils/svg';
|
|||
|
||||
export function ApplySymbol(container: IContainerModel, symbol: ISymbolModel): IContainerModel {
|
||||
container.properties.x = TransformX(symbol.x, symbol.width, symbol.config.XPositionReference);
|
||||
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.xPositionReference);
|
||||
container.properties.x = RestoreX(container.properties.x, container.properties.width, container.properties.positionReference);
|
||||
const [x] = ApplyParentTransform(container.parent, container.properties.x, 0);
|
||||
container.properties.x = x;
|
||||
return container;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue