/**
* FlexLayout.js
*
* Released under LGPL License.
* Copyright (c) 1999-2015 Ephox Corp. All rights reserved
*
* License: http://www.tinymce.com/license
* Contributing: http://www.tinymce.com/contributing
*/
/**
* This layout manager works similar to the CSS flex box.
*
* @setting {String} direction row|row-reverse|column|column-reverse
* @setting {Number} flex A positive-number to flex by.
* @setting {String} align start|end|center|stretch
* @setting {String} pack start|end|justify
*
* @class tinymce.ui.FlexLayout
* @extends tinymce.ui.AbsoluteLayout
*/
define("tinymce/ui/FlexLayout", [
"tinymce/ui/AbsoluteLayout"
], function(AbsoluteLayout) {
"use strict";
return AbsoluteLayout.extend({
/**
* Recalculates the positions of the controls in the specified container.
*
* @method recalc
* @param {tinymce.ui.Container} container Container instance to recalc.
*/
recalc: function(container) {
// A ton of variables, needs to be in the same scope for performance
var i, l, items, contLayoutRect, contPaddingBox, contSettings, align, pack, spacing, totalFlex, availableSpace, direction;
var ctrl, ctrlLayoutRect, ctrlSettings, flex, maxSizeItems = [], size, maxSize, ratio, rect, pos, maxAlignEndPos;
var sizeName, minSizeName, posName, maxSizeName, beforeName, innerSizeName, deltaSizeName, contentSizeName;
var alignAxisName, alignInnerSizeName, alignSizeName, alignMinSizeName, alignBeforeName, alignAfterName;
var alignDeltaSizeName, alignContentSizeName;
var max = Math.max, min = Math.min;
// Get container items, properties and settings
items = container.items().filter(':visible');
contLayoutRect = container.layoutRect();
contPaddingBox = container.paddingBox;
contSettings = container.settings;
direction = container.isRtl() ? (contSettings.direction || 'row-reversed') : contSettings.direction;
align = contSettings.align;
pack = container.isRtl() ? (contSettings.pack || 'end') : contSettings.pack;
spacing = contSettings.spacing || 0;
if (direction == "row-reversed" || direction == "column-reverse") {
items = items.set(items.toArray().reverse());
direction = direction.split('-')[0];
}
// Setup axis variable name for row/column direction since the calculations is the same
if (direction == "column") {
posName = "y";
sizeName = "h";
minSizeName = "minH";
maxSizeName = "maxH";
innerSizeName = "innerH";
beforeName = 'top';
deltaSizeName = "deltaH";
contentSizeName = "contentH";
alignBeforeName = "left";
alignSizeName = "w";
alignAxisName = "x";
alignInnerSizeName = "innerW";
alignMinSizeName = "minW";
alignAfterName = "right";
alignDeltaSizeName = "deltaW";
alignContentSizeName = "contentW";
} else {
posName = "x";
sizeName = "w";
minSizeName = "minW";
maxSizeName = "maxW";
innerSizeName = "innerW";
beforeName = 'left';
deltaSizeName = "deltaW";
contentSizeName = "contentW";
alignBeforeName = "top";
alignSizeName = "h";
alignAxisName = "y";
alignInnerSizeName = "innerH";
alignMinSizeName = "minH";
alignAfterName = "bottom";
alignDeltaSizeName = "deltaH";
alignContentSizeName = "contentH";
}
// Figure out total flex, availableSpace and collect any max size elements
availableSpace = contLayoutRect[innerSizeName] - contPaddingBox[beforeName] - contPaddingBox[beforeName];
maxAlignEndPos = totalFlex = 0;
for (i = 0, l = items.length; i < l; i++) {
ctrl = items[i];
ctrlLayoutRect = ctrl.layoutRect();
ctrlSettings = ctrl.settings;
flex = ctrlSettings.flex;
availableSpace -= (i < l - 1 ? spacing : 0);
if (flex > 0) {
totalFlex += flex;
// Flexed item has a max size then we need to check if we will hit that size
if (ctrlLayoutRect[maxSizeName]) {
maxSizeItems.push(ctrl);
}
ctrlLayoutRect.flex = flex;
}
availableSpace -= ctrlLayoutRect[minSizeName];
// Calculate the align end position to be used to check for overflow/underflow
size = contPaddingBox[alignBeforeName] + ctrlLayoutRect[alignMinSizeName] + contPaddingBox[alignAfterName];
if (size > maxAlignEndPos) {
maxAlignEndPos = size;
}
}
// Calculate minW/minH
rect = {};
if (availableSpace < 0) {
rect[minSizeName] = contLayoutRect[minSizeName] - availableSpace + contLayoutRect[deltaSizeName];
} else {
rect[minSizeName] = contLayoutRect[innerSizeName] - availableSpace + contLayoutRect[deltaSizeName];
}
rect[alignMinSizeName] = maxAlignEndPos + contLayoutRect[alignDeltaSizeName];
rect[contentSizeName] = contLayoutRect[innerSizeName] - availableSpace;
rect[alignContentSizeName] = maxAlignEndPos;
rect.minW = min(rect.minW, contLayoutRect.maxW);
rect.minH = min(rect.minH, contLayoutRect.maxH);
rect.minW = max(rect.minW, contLayoutRect.startMinWidth);
rect.minH = max(rect.minH, contLayoutRect.startMinHeight);
// Resize container container if minSize was changed
if (contLayoutRect.autoResize && (rect.minW != contLayoutRect.minW || rect.minH != contLayoutRect.minH)) {
rect.w = rect.minW;
rect.h = rect.minH;
container.layoutRect(rect);
this.recalc(container);
// Forced recalc for example if items are hidden/shown
if (container._lastRect === null) {
var parentCtrl = container.parent();
if (parentCtrl) {
parentCtrl._lastRect = null;
parentCtrl.recalc();
}
}
return;
}
// Handle max size elements, check if they will become to wide with current options
ratio = availableSpace / totalFlex;
for (i = 0, l = maxSizeItems.length; i < l; i++) {
ctrl = maxSizeItems[i];
ctrlLayoutRect = ctrl.layoutRect();
maxSize = ctrlLayoutRect[maxSizeName];
size = ctrlLayoutRect[minSizeName] + ctrlLayoutRect.flex * ratio;
if (size > maxSize) {
availableSpace -= (ctrlLayoutRect[maxSizeName] - ctrlLayoutRect[minSizeName]);
totalFlex -= ctrlLayoutRect.flex;
ctrlLayoutRect.flex = 0;
ctrlLayoutRect.maxFlexSize = maxSize;
} else {
ctrlLayoutRect.maxFlexSize = 0;
}
}
// Setup new ratio, target layout rect, start position
ratio = availableSpace / totalFlex;
pos = contPaddingBox[beforeName];
rect = {};
// Handle pack setting moves the start position to end, center
if (totalFlex === 0) {
if (pack == "end") {
pos = availableSpace + contPaddingBox[beforeName];
} else if (pack == "center") {
pos = Math.round(
(contLayoutRect[innerSizeName] / 2) - ((contLayoutRect[innerSizeName] - availableSpace) / 2)
) + contPaddingBox[beforeName];
if (pos < 0) {
pos = contPaddingBox[beforeName];
}
} else if (pack == "justify") {
pos = contPaddingBox[beforeName];
spacing = Math.floor(availableSpace / (items.length - 1));
}
}
// Default aligning (start) the other ones needs to be calculated while doing the layout
rect[alignAxisName] = contPaddingBox[alignBeforeName];
// Start laying out controls
for (i = 0, l = items.length; i < l; i++) {
ctrl = items[i];
ctrlLayoutRect = ctrl.layoutRect();
size = ctrlLayoutRect.maxFlexSize || ctrlLayoutRect[minSizeName];
// Align the control on the other axis
if (align === "center") {
rect[alignAxisName] = Math.round((contLayoutRect[alignInnerSizeName] / 2) - (ctrlLayoutRect[alignSizeName] / 2));
} else if (align === "stretch") {
rect[alignSizeName] = max(
ctrlLayoutRect[alignMinSizeName] || 0,
contLayoutRect[alignInnerSizeName] - contPaddingBox[alignBeforeName] - contPaddingBox[alignAfterName]
);
rect[alignAxisName] = contPaddingBox[alignBeforeName];
} else if (align === "end") {
rect[alignAxisName] = contLayoutRect[alignInnerSizeName] - ctrlLayoutRect[alignSizeName] - contPaddingBox.top;
}
// Calculate new size based on flex
if (ctrlLayoutRect.flex > 0) {
size += ctrlLayoutRect.flex * ratio;
}
rect[sizeName] = size;
rect[posName] = pos;
ctrl.layoutRect(rect);
// Recalculate containers
if (ctrl.recalc) {
ctrl.recalc();
}
// Move x/y position
pos += size + spacing;
}
}
});
});
|