UNPKG

@eclipse-scout/core

Version:
267 lines (236 loc) 9.98 kB
/* * Copyright (c) 2010, 2023 BSI Business Systems Integration AG * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * SPDX-License-Identifier: EPL-2.0 */ import {AbstractLayout, Dimension, graphics, Insets, Popup, Rectangle, scout} from '../index'; export class PopupLayout extends AbstractLayout { popup: Popup; /** enables popups with a height which depends on the width (= popups with wrapping content) */ doubleCalcPrefSize: boolean; autoPosition: boolean; autoSize: boolean; resizeAnimationRunning: boolean; resizeAnimationDuration: JQuery.Duration; protected _autoPositionOrig: boolean; constructor(popup: Popup) { super(); this.popup = popup; this.doubleCalcPrefSize = true; this.autoPosition = true; this.autoSize = true; this.resizeAnimationRunning = false; this.resizeAnimationDuration = null; this._autoPositionOrig = null; } override layout($container: JQuery) { if (this.popup.isOpeningAnimationRunning()) { this.popup.$container.oneAnimationEnd(this.layout.bind(this, $container)); return; } if (this.popup.removalPending || this.popup.removing || !this.popup.rendered) { return; } if (!this.autoSize) { // Just layout the popup with the current size this._setSize(this.popup.htmlComp.size({exact: true})); return; } let htmlComp = this.popup.htmlComp; // Read current bounds before calling pref size, because pref size may change position (_calcMaxSize) let currentBounds = graphics.bounds(htmlComp.$comp); let prefSize = this.preferredLayoutSize($container, { exact: true, widthOnly: this.doubleCalcPrefSize }); prefSize = this.adjustSize(prefSize); if (this.doubleCalcPrefSize) { prefSize = this.preferredLayoutSize($container, { exact: true, widthHint: prefSize.width - htmlComp.insets().horizontal() }); prefSize = this.adjustSize(prefSize); } this._setSize(prefSize); if (htmlComp.layouted && this.popup.animateResize) { this._resizeAnimated(currentBounds, prefSize); } } protected _resizeAnimated(currentBounds: Rectangle, prefSize: Dimension) { this._position(); let htmlComp = this.popup.htmlComp; let prefPosition = htmlComp.$comp.position(); // Preferred size are exact, current bounds are rounded -> round preferred size up to make compare work let prefBounds = new Rectangle(prefPosition.left, prefPosition.top, Math.ceil(prefSize.width), Math.ceil(prefSize.height)); if (currentBounds.equals(prefBounds)) { // Bounds did not change -> do nothing return; } this.resizeAnimationRunning = true; htmlComp.$comp .stop(true) .cssHeight(currentBounds.height) .cssWidth(currentBounds.width) .cssLeft(currentBounds.x) .cssTop(currentBounds.y) .animate({ height: prefSize.height, width: prefSize.width, left: prefPosition.left, top: prefPosition.top }, { duration: this.resizeAnimationDuration, complete: () => { this.resizeAnimationRunning = false; if (!this.popup.rendered) { return; } // Ensure the arrow is at the correct position after the animation this._position(); } }); } protected _position(switchIfNecessary?: boolean) { if (this.autoPosition) { this.popup.position(switchIfNecessary); } } protected _setSize(prefSize: Dimension) { graphics.setSize(this.popup.htmlComp.$comp, prefSize); } adjustSize(prefSize: Dimension): Dimension { // Consider CSS min/max rules this.popup.htmlComp._adjustPrefSizeWithMinMaxSize(prefSize); // Consider window boundaries if (this.popup.boundToAnchor && (this.popup.anchorBounds || this.popup.$anchor)) { return this._adjustSizeWithAnchor(prefSize); } return this._adjustSize(prefSize); } protected _adjustSize(prefSize: Dimension): Dimension { let popupSize = new Dimension(), maxSize = this._calcMaxSize(); // Ensure the popup is not larger than max size popupSize.width = Math.min(maxSize.width, prefSize.width); popupSize.height = Math.min(maxSize.height, prefSize.height); return popupSize; } /** * Considers window boundaries. * */ protected _calcMaxSize(): Dimension { let maxWidth, maxHeight, htmlComp = this.popup.htmlComp, windowPaddingX = this.popup.windowPaddingX, windowPaddingY = this.popup.windowPaddingY, popupMargins = htmlComp.margins(), windowSize = this.popup.getWindowSize(); maxWidth = (windowSize.width - popupMargins.horizontal() - windowPaddingX); maxHeight = (windowSize.height - popupMargins.vertical() - windowPaddingY); return new Dimension(maxWidth, maxHeight); } protected _adjustSizeWithAnchor(prefSize: Dimension): Dimension { let popupSize = new Dimension(), maxSize = this._calcMaxSizeAroundAnchor(), windowSize = this._calcMaxSize(), Alignment = Popup.Alignment, horizontalAlignment = this.popup.horizontalAlignment, verticalAlignment = this.popup.verticalAlignment; // Decide whether the prefSize can be used or the popup needs to be shrunken so that it fits into the viewport // The decision is based on the preferred opening direction // Example: The popup would like to be opened leftedge and bottom // If there is enough space on the right and on the bottom -> pref size is used // If there is not enough space on the right it checks whether there is enough space on the left // If there is enough space on the left -> use preferred width -> The opening direction will be switched using position() at the end // If there is not enough space on the left as well, the greater width is used -> Position() will either switch the direction or not, depending on the size of the popup // The same happens for y direction if there is not enough space on the bottom popupSize.width = prefSize.width; if (this.popup.trimWidth) { if (this.popup.horizontalSwitch) { if (prefSize.width > maxSize.right && prefSize.width > maxSize.left) { popupSize.width = Math.max(maxSize.right, maxSize.left); } } else { if (horizontalAlignment === Alignment.RIGHT) { popupSize.width = Math.min(popupSize.width, maxSize.right); } else if (horizontalAlignment === Alignment.LEFT) { popupSize.width = Math.min(popupSize.width, maxSize.left); } else { popupSize.width = Math.min(popupSize.width, windowSize.width); } } } popupSize.height = prefSize.height; if (this.popup.trimHeight) { if (this.popup.verticalSwitch) { if (prefSize.height > maxSize.bottom && prefSize.height > maxSize.top) { popupSize.height = Math.max(maxSize.bottom, maxSize.top); } } else { if (verticalAlignment === Alignment.BOTTOM) { popupSize.height = Math.min(popupSize.height, maxSize.bottom); } else if (verticalAlignment === Alignment.TOP) { popupSize.height = Math.min(popupSize.height, maxSize.top); } else { popupSize.height = Math.min(popupSize.height, windowSize.height); } } } // On CENTER alignment, the anchor must ne be considered. Instead make sure the popup does not exceed window boundaries (same as in adjustSize) if (verticalAlignment === Alignment.CENTER || horizontalAlignment === Alignment.CENTER) { if (horizontalAlignment === Alignment.CENTER) { popupSize.width = Math.min(windowSize.width, prefSize.width); } if (verticalAlignment === Alignment.CENTER) { popupSize.height = Math.min(windowSize.height, prefSize.height); } } return popupSize; } /** * Considers window boundaries. * */ protected _calcMaxSizeAroundAnchor(): Insets { let maxWidthLeft, maxWidthRight, maxHeightDown, maxHeightUp, htmlComp = this.popup.htmlComp, windowPaddingX = this.popup.windowPaddingX, windowPaddingY = this.popup.windowPaddingY, popupMargins = htmlComp.margins(), anchorBounds = this.popup.getAnchorBounds(), windowSize = this.popup.getWindowSize(), horizontalAlignment = this.popup.horizontalAlignment, verticalAlignment = this.popup.verticalAlignment, Alignment = Popup.Alignment; if (scout.isOneOf(horizontalAlignment, Alignment.LEFTEDGE, Alignment.RIGHTEDGE)) { maxWidthRight = windowSize.width - anchorBounds.x - popupMargins.horizontal() - windowPaddingX; maxWidthLeft = anchorBounds.right() - popupMargins.horizontal() - windowPaddingX; } else { // LEFT or RIGHT maxWidthRight = windowSize.width - anchorBounds.right() - popupMargins.horizontal() - windowPaddingX; maxWidthLeft = anchorBounds.x - popupMargins.horizontal() - windowPaddingX; } if (scout.isOneOf(verticalAlignment, Alignment.BOTTOMEDGE, Alignment.TOPEDGE)) { maxHeightDown = windowSize.height - anchorBounds.y - popupMargins.vertical() - windowPaddingY; maxHeightUp = anchorBounds.bottom() - popupMargins.vertical() - windowPaddingY; } else { // BOTTOM or TOP maxHeightDown = windowSize.height - anchorBounds.bottom() - popupMargins.vertical() - windowPaddingY; maxHeightUp = anchorBounds.y - popupMargins.vertical() - windowPaddingY; } return new Insets(maxHeightUp, maxWidthRight, maxHeightDown, maxWidthLeft); } disableAutoPosition() { if (this._autoPositionOrig === null) { this._autoPositionOrig = this.autoPosition; this.autoPosition = false; } } resetAutoPosition() { this.autoPosition = this._autoPositionOrig; this._autoPositionOrig = null; } }