@aurigma/design-atoms
Version:
Design Atoms is a part of Customer's Canvas SDK which allows for manipulating individual design elements through your code.
422 lines • 20.5 kB
JavaScript
import { EventObject } from "@aurigma/design-atoms-model/EventObject";
import { PointF, RectangleF, RotatedRectangleF } from "@aurigma/design-atoms-model/Math";
import { AutoLayoutSettings, AutoLayoutAlignItems, AutoLayoutOrientation, AutoLayoutAnchorPoint } from "@aurigma/design-atoms-model/Product/Items";
import { BaseTextItemHandler, NewBaseTextItemHandler, PlaceholderItemHandler } from "..";
import { getTranslationTransform } from "../../Utils/Math";
import { LayoutItemHandler } from "../LayoutItemHandler";
import { GroupItemHandler } from "../GroupItemHandler";
export class AutoLayoutHandler {
constructor(_item, _itemHandlers, _itemHandler, _history) {
this._item = _item;
this._itemHandlers = _itemHandlers;
this._itemHandler = _itemHandler;
this._history = _history;
this._layoutEvent = new EventObject();
this._orderedItemHandlers = null;
this._initialized = false;
this._disposed = false;
this._isApplyingChildrenTransforms = false;
this._onChildItemHandlerChanging = (sender) => {
this.applyLayout();
};
this._onChildItemHandlerPropertyChanged = async (sender, propertyName) => {
switch (propertyName) {
case "font":
case "text":
case "tracking":
case "leading":
case "shadow":
case "stroke":
case "underline":
case "sourceRectangle":
case "borderWidth":
case "transform":
if (this._isApplyingChildrenTransforms)
return;
await sender.waitUpdate();
this.applyLayout();
break;
}
};
this._onItemPropertyChanged = (sender, propertyName) => {
switch (propertyName) {
case "layoutSettings":
this._orderedItemHandlers = null;
this._updateItemsOrder();
this.applyLayout();
break;
case "margin":
this.applyLayout();
break;
case "orientation":
this.applyLayout(true);
break;
case "alignItems":
case "justifyContent":
this.applyLayout();
break;
}
};
this._ignoreLayoutApply = false;
this._filterHiddenItems = (itemHandler) => !((itemHandler instanceof BaseTextItemHandler || itemHandler instanceof NewBaseTextItemHandler || itemHandler instanceof LayoutItemHandler) && itemHandler.isEmpty());
this._item.addPropertyChanged(this._onItemPropertyChanged);
this._itemHandlers.add_itemAdded(this._onItemHandlerAdded.bind(this));
this._itemHandlers.add_itemRemoved(this._onItemHandlerRemoved.bind(this));
this._itemHandlers.forEach(itemHandler => itemHandler.addItemPropertyChanged(this._onChildItemHandlerPropertyChanged));
this._init();
}
dispose() {
this._disposed = true;
this._item.removePropertyChanged(this._onItemPropertyChanged);
this._itemHandlers.remove_itemAdded(this._onItemHandlerAdded.bind(this));
this._itemHandlers.remove_itemRemoved(this._onItemHandlerRemoved.bind(this));
this._itemHandlers.forEach(itemHandler => itemHandler.removeItemPropertyChanged(this._onChildItemHandlerPropertyChanged));
}
getLayoutEvent() {
return this._layoutEvent;
}
onItemRemoved(data) {
this._updateItemsOrder({ removedItem: data.item });
this.applyLayout();
}
onItemHandlersCollectionChanged() {
this._orderedItemHandlers = null;
}
async _init() {
if (this._initialized)
return;
var order = this._item.getItemsOrder();
await this._itemHandler.waitUpdate();
if (!order)
this._updateItemsOrder();
this._initialized = true;
this.applyLayout();
}
_onItemHandlerAdded(data) {
data.item.addItemPropertyChanged(this._onChildItemHandlerPropertyChanged);
this._orderedItemHandlers = null;
}
_onItemHandlerRemoved(data) {
data.item.removeItemPropertyChanged(this._onChildItemHandlerPropertyChanged);
this._orderedItemHandlers = null;
}
_updateItemsOrder(params) {
if (this._item.layoutSettings instanceof AutoLayoutSettings) {
if (this._validateItemsOrder())
return;
var itemsOrder = this._item.getItemsOrder();
if (itemsOrder == null) {
this._initSortedItemHandlers();
var ids = this._getSortedItemHandlers().map(x => x.item.id);
this._item.setItemsOrder(ids);
}
}
}
_initSortedItemHandlers() {
const { center: groupCenter } = this._itemHandler.getTransformedRectangle(false, true);
const { angle } = this._item.transform;
const { orientation } = this._item.layoutSettings;
const sortFunc = (a, b) => orientation === AutoLayoutOrientation.horizontal
? a.bounds.left - b.bounds.left
: a.bounds.top - b.bounds.top;
this._orderedItemHandlers = this._itemHandlers.toArray().sort((a, b) => sortFunc(a.getTransformedRectangle(false, true).rotateAt(-angle, groupCenter), b.getTransformedRectangle(false, true).rotateAt(-angle, groupCenter)));
return this._orderedItemHandlers;
}
_validateItemsOrder() {
const layoutItemsOrder = this._item.getItemsOrder();
if (layoutItemsOrder == null)
return false;
if (layoutItemsOrder.length !== this._item.items.length)
return false;
var itemIds = this._item.items.select(x => x.id).toArray();
return layoutItemsOrder.every(x => itemIds.includes(x));
}
_getSortedItemHandlers() {
if (this._orderedItemHandlers != null)
return this._orderedItemHandlers;
var itemsOrder = this._item.getItemsOrder();
if (!itemsOrder)
return null;
let sortedItemHandlers = [];
var itemHandlers = this._itemHandlers.toArray();
if (itemHandlers.length != itemsOrder.length) {
throw new Error("itemsOrder length does not match itemHandlers length");
}
itemsOrder.forEach(itemsOrderElement => {
var i = itemHandlers.findIndex(x => x.item.id == itemsOrderElement);
if (i == -1)
throw new Error(`itemsOrder: Element with id ${itemsOrderElement} was not found in itemHandlers`);
sortedItemHandlers.push(itemHandlers[i]);
itemHandlers.splice(i, 1);
});
this._orderedItemHandlers = sortedItemHandlers;
return this._orderedItemHandlers;
}
getOrderedItems() {
var itemsOrder = this._item.getItemsOrder();
if (itemsOrder) {
let result = this._item.items.toArray();
result.sort((x, y) => itemsOrder.indexOf(x.id) - itemsOrder.indexOf(y.id));
return result;
}
return null;
}
set ignoreLayoutApply(value) {
this._ignoreLayoutApply = value;
}
applyLayout(fromCenter = false) {
var _a, _b, _c, _d, _e;
if (this._disposed)
return;
if (!this._initialized)
return;
if (this._ignoreLayoutApply)
return;
this._ignoreLayoutApply = true;
try {
const historyActive = !((_a = this._history) === null || _a === void 0 ? void 0 : _a.isPaused());
if (historyActive)
(_b = this._history) === null || _b === void 0 ? void 0 : _b.pause();
(_c = this._itemHandler.canvas) === null || _c === void 0 ? void 0 : _c.pauseRedraw();
const changed = this._applyLayoutImpl(fromCenter);
(_d = this._itemHandler.canvas) === null || _d === void 0 ? void 0 : _d.continueRedraw();
if (historyActive)
(_e = this._history) === null || _e === void 0 ? void 0 : _e.resume(!!changed, false, true);
this._layoutEvent.notify(changed);
}
finally {
this._ignoreLayoutApply = false;
}
}
_applyLayoutImpl(fromCenter = false) {
var _a;
let sortedItemHandlers = this._getSortedItemHandlers();
if (sortedItemHandlers == null || sortedItemHandlers.length == 0)
return;
var originalRectangle = this._itemHandler.rectangle;
var originalCenter = originalRectangle.center;
const { alignItems, orientation, margin, anchorPoint } = this._item.layoutSettings;
const { angle } = this._item.transform;
let layoutRect = originalRectangle.toRectangleF();
let layoutAnchorPoint = this._getAnchorPoint(layoutRect, anchorPoint, orientation);
let currentPoint = this._getOriginalPoint(layoutRect, alignItems, orientation);
sortedItemHandlers = sortedItemHandlers.filter(itemHandler => this._filterHiddenItems(itemHandler));
let rects = [];
sortedItemHandlers.forEach(element => {
// Calculate the new position of the element inside the layout,
// taking into account parent's alignItems and orientation
const elementPositionRect = this._getItemHandlerPositionRect(element, originalRectangle, currentPoint, alignItems, orientation);
// Calculates the offset of the current element along the main layout axis
// relative to the layout start.
// In other words, this defines how much the element should be shifted
// along the layout direction to be placed after previous elements.
const offset = this._getPositionRectOffset(orientation, layoutRect, elementPositionRect) + margin;
// Shift the anchor point for the next element
currentPoint = this._getOriginalPoint(layoutRect, alignItems, orientation, offset);
rects.push(elementPositionRect);
});
rects = this._applyAnchor(rects, layoutRect, anchorPoint, orientation);
let anyChildChanged = false;
this._isApplyingChildrenTransforms = true;
try {
sortedItemHandlers.forEach(element => {
let elementPositionRect = rects.shift();
// Set the actual position of the element on the canvas
const changed = this._setItemHandlerPosition(element, originalRectangle, elementPositionRect);
anyChildChanged = anyChildChanged || changed;
});
}
finally {
this._isApplyingChildrenTransforms = false;
}
const filteredItems = this._itemHandlers.cast().where(this._filterHiddenItems);
let sourceRectChanged = false;
// Recalculate the layout's own sourceRectangle so that it wraps its children
if (filteredItems.any()) {
let childItemsBounds = filteredItems
.select(x => x.getTransformedRectangle(false, true))
.aggregate((a, b) => RotatedRectangleF.union(a, b));
const newSourceRectangle = this._getSourceRectangleByAnchorPoint(childItemsBounds, layoutAnchorPoint, orientation, anchorPoint);
if (fromCenter) {
var translationTransform = getTranslationTransform(newSourceRectangle.center, originalCenter);
newSourceRectangle.setTransform(translationTransform);
}
this._itemHandler.setSourceRectangle(newSourceRectangle);
sourceRectChanged = !originalRectangle.equals(newSourceRectangle, 0.01);
}
const changed = anyChildChanged || sourceRectChanged;
// TODO: move outside
if (changed)
(_a = this._itemHandler.canvas) === null || _a === void 0 ? void 0 : _a.updateTexts();
return changed;
}
_getSourceRectangleByAnchorPoint(childBounds, layoutAnchorPoint, orientation, anchor) {
const childRect = childBounds.toRectangleF();
if (orientation == AutoLayoutOrientation.horizontal) {
if (anchor == AutoLayoutAnchorPoint.start) {
childRect.left = layoutAnchorPoint.x;
}
else if (anchor == AutoLayoutAnchorPoint.center) {
const dx = childRect.center.x - layoutAnchorPoint.x;
childRect.left -= dx;
}
else { // end
childRect.right = layoutAnchorPoint.x;
}
}
else { // vertical
if (anchor == AutoLayoutAnchorPoint.start) {
childRect.top = layoutAnchorPoint.y;
}
else if (anchor == AutoLayoutAnchorPoint.center) {
const dy = childRect.center.y - layoutAnchorPoint.y;
childRect.top -= dy;
}
else { // end
childRect.bottom = layoutAnchorPoint.y;
}
}
return RotatedRectangleF.fromRectangleF(childRect, childBounds.angle);
}
_getAnchorPoint(layoutRect, anchorPoint, orientation) {
if (orientation == AutoLayoutOrientation.horizontal) {
const y = layoutRect.center.y;
switch (anchorPoint) {
case AutoLayoutAnchorPoint.start:
return new PointF(layoutRect.left, y);
case AutoLayoutAnchorPoint.center:
return new PointF(layoutRect.center.x, y);
case AutoLayoutAnchorPoint.end:
return new PointF(layoutRect.right, y);
}
}
else {
const x = layoutRect.center.x;
switch (anchorPoint) {
case AutoLayoutAnchorPoint.start:
return new PointF(x, layoutRect.top);
case AutoLayoutAnchorPoint.center:
return new PointF(x, layoutRect.center.y);
case AutoLayoutAnchorPoint.end:
return new PointF(x, layoutRect.bottom);
}
}
}
_applyAnchor(inputRects, layoutRect, anchorPoint, orientation) {
if (inputRects.length == 0)
return inputRects;
if (anchorPoint == AutoLayoutAnchorPoint.start)
return inputRects;
let calculateDiff = (rects) => {
let dx = 0;
let dy = 0;
var unionRect = rects.reduce((prev, current) => RectangleF.union(prev, current));
if (orientation == AutoLayoutOrientation.horizontal) {
if (anchorPoint == AutoLayoutAnchorPoint.center)
dx = unionRect.center.x - layoutRect.center.x;
else if (anchorPoint == AutoLayoutAnchorPoint.end)
dx = unionRect.right - layoutRect.right;
else
throw new Error(`Invalid _applyAnchor operation. Orientation: '${orientation}' AnchorPoint: '${anchorPoint}'`);
}
else { // vertical
if (anchorPoint == AutoLayoutAnchorPoint.center)
dy = unionRect.center.y - layoutRect.center.y;
else if (anchorPoint == AutoLayoutAnchorPoint.end)
dy = unionRect.bottom - layoutRect.bottom;
else
throw new Error(`Invalid _applyAnchor operation. Orientation: '${orientation}' AnchorPoint: '${anchorPoint}'`);
}
return new PointF(dx, dy);
};
const diff = calculateDiff(inputRects);
return inputRects.map(x => new RectangleF(x.left - diff.x, x.top - diff.y, x.width, x.height));
}
_setItemHandlerPosition(itemHandler, originalRectangle, elementPositionRect) {
const setRectangle = (itemHandler) => {
const itemHandlerRect = itemHandler.getTransformedRectangle(false, true);
const originalRect = itemHandlerRect.clone();
itemHandlerRect.rotateAt(-originalRectangle.angle, originalRectangle.center);
const itemHandlerRectBounds = itemHandlerRect.bounds;
// elementPositionRect - целевая позиция
// itemHandlerRectBounds - физическая
const dx = elementPositionRect.left - itemHandlerRectBounds.left;
const dy = elementPositionRect.top - itemHandlerRectBounds.top;
itemHandlerRect.translate(dx, dy);
itemHandlerRect.rotateAt(originalRectangle.angle, originalRectangle.center);
if (originalRect.equals(itemHandlerRect))
return false;
this._setItemHandlerRectangle(itemHandler, itemHandlerRect, setRectangle);
return true;
};
return setRectangle(itemHandler);
}
_setItemHandlerRectangle(itemHandler, itemHandlerRect, setRectangleFunc) {
const margin = itemHandler.getBorderMargin();
itemHandlerRect.width -= margin * 2;
itemHandlerRect.height -= margin * 2;
if (itemHandler instanceof LayoutItemHandler) {
itemHandler.setRectangle(itemHandlerRect, true, true);
}
else {
itemHandler.setRectangle(itemHandlerRect);
if (itemHandler instanceof PlaceholderItemHandler)
itemHandler.updateContentAndFrames(setRectangleFunc);
if (itemHandler instanceof GroupItemHandler) {
itemHandler.getNestedItemHandlers(true).forEach((item) => {
setRectangleFunc(item);
});
}
}
}
_getPositionRectOffset(orientation, layoutRect, elementPositionRect) {
if (orientation == AutoLayoutOrientation.vertical) {
return elementPositionRect.bottom - layoutRect.top;
}
else { // horizontal
return elementPositionRect.right - layoutRect.left;
}
}
_getItemHandlerPositionRect(element, layoutRectangle, currentPoint, alignItems, orientation) {
let itemHandlerRectangle = element.getTransformedRectangle(false, true);
// Rotate the child so that it is in coordinates where parent's angle is 0
itemHandlerRectangle.angle -= layoutRectangle.angle;
let itemHandlerBounds = itemHandlerRectangle.bounds;
// Get the element's anchor point
let itemHandlerPoint = this._getOriginalPoint(itemHandlerBounds, alignItems, orientation);
// currentPoint is the layout's anchor point
let dx = currentPoint.x - itemHandlerPoint.x;
let dy = currentPoint.y - itemHandlerPoint.y;
if (alignItems !== AutoLayoutAlignItems.none || orientation === AutoLayoutOrientation.horizontal)
itemHandlerBounds.left += dx;
if (alignItems !== AutoLayoutAlignItems.none || orientation === AutoLayoutOrientation.vertical)
itemHandlerBounds.top += dy;
return itemHandlerBounds;
}
_getOriginalPoint(rect, alignItems, orientation, offset = 0) {
let result;
if (orientation == AutoLayoutOrientation.vertical) {
if (alignItems == AutoLayoutAlignItems.start || alignItems == AutoLayoutAlignItems.none) {
result = new PointF(rect.left, rect.top);
}
else if (alignItems == AutoLayoutAlignItems.center) {
result = new PointF(rect.center.x, rect.top);
}
else {
result = new PointF(rect.right, rect.top);
}
result.y += offset;
}
else { // horizontal
if (alignItems == AutoLayoutAlignItems.start || alignItems == AutoLayoutAlignItems.none)
result = new PointF(rect.left, rect.top);
else if (alignItems == AutoLayoutAlignItems.center)
result = new PointF(rect.left, rect.center.y);
else
result = new PointF(rect.left, rect.bottom);
result.x += offset;
}
return result;
}
}
//# sourceMappingURL=AutoLayoutHandler.js.map