UNPKG

@phosphor/widgets

Version:
1,303 lines 61.3 kB
"use strict"; var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); } return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); /*----------------------------------------------------------------------------- | Copyright (c) 2014-2017, PhosphorJS Contributors | | Distributed under the terms of the BSD 3-Clause License. | | The full license is in the file LICENSE, distributed with this software. |----------------------------------------------------------------------------*/ var algorithm_1 = require("@phosphor/algorithm"); var domutils_1 = require("@phosphor/domutils"); var messaging_1 = require("@phosphor/messaging"); var boxengine_1 = require("./boxengine"); var layout_1 = require("./layout"); var widget_1 = require("./widget"); /** * A layout which provides a flexible docking arrangement. * * #### Notes * The consumer of this layout is responsible for handling all signals * from the generated tab bars and managing the visibility of widgets * and tab bars as needed. */ var DockLayout = /** @class */ (function (_super) { __extends(DockLayout, _super); /** * Construct a new dock layout. * * @param options - The options for initializing the layout. */ function DockLayout(options) { var _this = _super.call(this) || this; _this._spacing = 4; _this._dirty = false; _this._root = null; _this._box = null; _this._items = new Map(); _this.renderer = options.renderer; if (options.spacing !== undefined) { _this._spacing = Private.clampSpacing(options.spacing); } return _this; } /** * Dispose of the resources held by the layout. * * #### Notes * This will clear and dispose all widgets in the layout. */ DockLayout.prototype.dispose = function () { // Get an iterator over the widgets in the layout. var widgets = this.iter(); // Dispose of the layout items. this._items.forEach(function (item) { item.dispose(); }); // Clear the layout state before disposing the widgets. this._box = null; this._root = null; this._items.clear(); // Dispose of the widgets contained in the old layout root. algorithm_1.each(widgets, function (widget) { widget.dispose(); }); // Dispose of the base class. _super.prototype.dispose.call(this); }; Object.defineProperty(DockLayout.prototype, "spacing", { /** * Get the inter-element spacing for the dock layout. */ get: function () { return this._spacing; }, /** * Set the inter-element spacing for the dock layout. */ set: function (value) { value = Private.clampSpacing(value); if (this._spacing === value) { return; } this._spacing = value; if (!this.parent) { return; } this.parent.fit(); }, enumerable: true, configurable: true }); Object.defineProperty(DockLayout.prototype, "isEmpty", { /** * Whether the dock layout is empty. */ get: function () { return this._root === null; }, enumerable: true, configurable: true }); /** * Create an iterator over all widgets in the layout. * * @returns A new iterator over the widgets in the layout. * * #### Notes * This iterator includes the generated tab bars. */ DockLayout.prototype.iter = function () { return this._root ? this._root.iterAllWidgets() : algorithm_1.empty(); }; /** * Create an iterator over the user widgets in the layout. * * @returns A new iterator over the user widgets in the layout. * * #### Notes * This iterator does not include the generated tab bars. */ DockLayout.prototype.widgets = function () { return this._root ? this._root.iterUserWidgets() : algorithm_1.empty(); }; /** * Create an iterator over the selected widgets in the layout. * * @returns A new iterator over the selected user widgets. * * #### Notes * This iterator yields the widgets corresponding to the current tab * of each tab bar in the layout. */ DockLayout.prototype.selectedWidgets = function () { return this._root ? this._root.iterSelectedWidgets() : algorithm_1.empty(); }; /** * Create an iterator over the tab bars in the layout. * * @returns A new iterator over the tab bars in the layout. * * #### Notes * This iterator does not include the user widgets. */ DockLayout.prototype.tabBars = function () { return this._root ? this._root.iterTabBars() : algorithm_1.empty(); }; /** * Create an iterator over the handles in the layout. * * @returns A new iterator over the handles in the layout. */ DockLayout.prototype.handles = function () { return this._root ? this._root.iterHandles() : algorithm_1.empty(); }; /** * Move a handle to the given offset position. * * @param handle - The handle to move. * * @param offsetX - The desired offset X position of the handle. * * @param offsetY - The desired offset Y position of the handle. * * #### Notes * If the given handle is not contained in the layout, this is no-op. * * The handle will be moved as close as possible to the desired * position without violating any of the layout constraints. * * Only one of the coordinates is used depending on the orientation * of the handle. This method accepts both coordinates to make it * easy to invoke from a mouse move event without needing to know * the handle orientation. */ DockLayout.prototype.moveHandle = function (handle, offsetX, offsetY) { // Bail early if there is no root or if the handle is hidden. if (!this._root || handle.classList.contains('p-mod-hidden')) { return; } // Lookup the split node for the handle. var data = this._root.findSplitNode(handle); if (!data) { return; } // Compute the desired delta movement for the handle. var delta; if (data.node.orientation === 'horizontal') { delta = offsetX - handle.offsetLeft; } else { delta = offsetY - handle.offsetTop; } // Bail if there is no handle movement. if (delta === 0) { return; } // Prevent sibling resizing unless needed. data.node.holdSizes(); // Adjust the sizers to reflect the handle movement. boxengine_1.BoxEngine.adjust(data.node.sizers, data.index, delta); // Update the layout of the widgets. if (this.parent) { this.parent.update(); } }; /** * Save the current configuration of the dock layout. * * @returns A new config object for the current layout state. * * #### Notes * The return value can be provided to the `restoreLayout` method * in order to restore the layout to its current configuration. */ DockLayout.prototype.saveLayout = function () { // Bail early if there is no root. if (!this._root) { return { main: null }; } // Hold the current sizes in the layout tree. this._root.holdAllSizes(); // Return the layout config. return { main: this._root.createConfig() }; }; /** * Restore the layout to a previously saved configuration. * * @param config - The layout configuration to restore. * * #### Notes * Widgets which currently belong to the layout but which are not * contained in the config will be unparented. */ DockLayout.prototype.restoreLayout = function (config) { var _this = this; // Create the widget set for validating the config. var widgetSet = new Set(); // Normalize the main area config and collect the widgets. var mainConfig; if (config.main) { mainConfig = Private.normalizeAreaConfig(config.main, widgetSet); } else { mainConfig = null; } // Create iterators over the old content. var oldWidgets = this.widgets(); var oldTabBars = this.tabBars(); var oldHandles = this.handles(); // Clear the root before removing the old content. this._root = null; // Unparent the old widgets which are not in the new config. algorithm_1.each(oldWidgets, function (widget) { if (!widgetSet.has(widget)) { widget.parent = null; } }); // Dispose of the old tab bars. algorithm_1.each(oldTabBars, function (tabBar) { tabBar.dispose(); }); // Remove the old handles. algorithm_1.each(oldHandles, function (handle) { if (handle.parentNode) { handle.parentNode.removeChild(handle); } }); // Reparent the new widgets to the current parent. widgetSet.forEach(function (widget) { widget.parent = _this.parent; }); // Create the root node for the new config. if (mainConfig) { this._root = Private.realizeAreaConfig(mainConfig, { createTabBar: function () { return _this._createTabBar(); }, createHandle: function () { return _this._createHandle(); } }); } else { this._root = null; } // If there is no parent, there is nothing more to do. if (!this.parent) { return; } // Attach the new widgets to the parent. widgetSet.forEach(function (widget) { _this.attachWidget(widget); }); // Post a fit request to the parent. this.parent.fit(); }; /** * Add a widget to the dock layout. * * @param widget - The widget to add to the dock layout. * * @param options - The additional options for adding the widget. * * #### Notes * The widget will be moved if it is already contained in the layout. * * An error will be thrown if the reference widget is invalid. */ DockLayout.prototype.addWidget = function (widget, options) { if (options === void 0) { options = {}; } // Parse the options. var ref = options.ref || null; var mode = options.mode || 'tab-after'; // Find the tab node which holds the reference widget. var refNode = null; if (this._root && ref) { refNode = this._root.findTabNode(ref); } // Throw an error if the reference widget is invalid. if (ref && !refNode) { throw new Error('Reference widget is not in the layout.'); } // Reparent the widget to the current layout parent. widget.parent = this.parent; // Insert the widget according to the insert mode. switch (mode) { case 'tab-after': this._insertTab(widget, ref, refNode, true); break; case 'tab-before': this._insertTab(widget, ref, refNode, false); break; case 'split-top': this._insertSplit(widget, ref, refNode, 'vertical', false); break; case 'split-left': this._insertSplit(widget, ref, refNode, 'horizontal', false); break; case 'split-right': this._insertSplit(widget, ref, refNode, 'horizontal', true); break; case 'split-bottom': this._insertSplit(widget, ref, refNode, 'vertical', true); break; } // Do nothing else if there is no parent widget. if (!this.parent) { return; } // Ensure the widget is attached to the parent widget. this.attachWidget(widget); // Post a fit request for the parent widget. this.parent.fit(); }; /** * Remove a widget from the layout. * * @param widget - The widget to remove from the layout. * * #### Notes * A widget is automatically removed from the layout when its `parent` * is set to `null`. This method should only be invoked directly when * removing a widget from a layout which has yet to be installed on a * parent widget. * * This method does *not* modify the widget's `parent`. */ DockLayout.prototype.removeWidget = function (widget) { // Remove the widget from its current layout location. this._removeWidget(widget); // Do nothing else if there is no parent widget. if (!this.parent) { return; } // Detach the widget from the parent widget. this.detachWidget(widget); // Post a fit request for the parent widget. this.parent.fit(); }; /** * Find the tab area which contains the given client position. * * @param clientX - The client X position of interest. * * @param clientY - The client Y position of interest. * * @returns The geometry of the tab area at the given position, or * `null` if there is no tab area at the given position. */ DockLayout.prototype.hitTestTabAreas = function (clientX, clientY) { // Bail early if hit testing cannot produce valid results. if (!this._root || !this.parent || !this.parent.isVisible) { return null; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = domutils_1.ElementExt.boxSizing(this.parent.node); } // Convert from client to local coordinates. var rect = this.parent.node.getBoundingClientRect(); var x = clientX - rect.left - this._box.borderLeft; var y = clientY - rect.top - this._box.borderTop; // Find the tab layout node at the local position. var tabNode = this._root.hitTestTabNodes(x, y); // Bail if a tab layout node was not found. if (!tabNode) { return null; } // Extract the data from the tab node. var tabBar = tabNode.tabBar, top = tabNode.top, left = tabNode.left, width = tabNode.width, height = tabNode.height; // Compute the right and bottom edges of the tab area. var borderWidth = this._box.borderLeft + this._box.borderRight; var borderHeight = this._box.borderTop + this._box.borderBottom; var right = rect.width - borderWidth - (left + width); var bottom = rect.height - borderHeight - (top + height); // Return the hit test results. return { tabBar: tabBar, x: x, y: y, top: top, left: left, right: right, bottom: bottom, width: width, height: height }; }; /** * Perform layout initialization which requires the parent widget. */ DockLayout.prototype.init = function () { var _this = this; // Perform superclass initialization. _super.prototype.init.call(this); // Attach each widget to the parent. algorithm_1.each(this, function (widget) { _this.attachWidget(widget); }); // Attach each handle to the parent. algorithm_1.each(this.handles(), function (handle) { _this.parent.node.appendChild(handle); }); // Post a fit request for the parent widget. this.parent.fit(); }; /** * Attach the widget to the layout parent widget. * * @param widget - The widget to attach to the parent. * * #### Notes * This is a no-op if the widget is already attached. */ DockLayout.prototype.attachWidget = function (widget) { // Do nothing if the widget is already attached. if (this.parent.node === widget.node.parentNode) { return; } // Create the layout item for the widget. this._items.set(widget, new layout_1.LayoutItem(widget)); // Send a `'before-attach'` message if the parent is attached. if (this.parent.isAttached) { messaging_1.MessageLoop.sendMessage(widget, widget_1.Widget.Msg.BeforeAttach); } // Add the widget's node to the parent. this.parent.node.appendChild(widget.node); // Send an `'after-attach'` message if the parent is attached. if (this.parent.isAttached) { messaging_1.MessageLoop.sendMessage(widget, widget_1.Widget.Msg.AfterAttach); } }; /** * Detach the widget from the layout parent widget. * * @param widget - The widget to detach from the parent. * * #### Notes * This is a no-op if the widget is not attached. */ DockLayout.prototype.detachWidget = function (widget) { // Do nothing if the widget is not attached. if (this.parent.node !== widget.node.parentNode) { return; } // Send a `'before-detach'` message if the parent is attached. if (this.parent.isAttached) { messaging_1.MessageLoop.sendMessage(widget, widget_1.Widget.Msg.BeforeDetach); } // Remove the widget's node from the parent. this.parent.node.removeChild(widget.node); // Send an `'after-detach'` message if the parent is attached. if (this.parent.isAttached) { messaging_1.MessageLoop.sendMessage(widget, widget_1.Widget.Msg.AfterDetach); } // Delete the layout item for the widget. var item = this._items.get(widget); if (item) { this._items.delete(widget); item.dispose(); } }; /** * A message handler invoked on a `'before-show'` message. */ DockLayout.prototype.onBeforeShow = function (msg) { _super.prototype.onBeforeShow.call(this, msg); this.parent.update(); }; /** * A message handler invoked on a `'before-attach'` message. */ DockLayout.prototype.onBeforeAttach = function (msg) { _super.prototype.onBeforeAttach.call(this, msg); this.parent.fit(); }; /** * A message handler invoked on a `'child-shown'` message. */ DockLayout.prototype.onChildShown = function (msg) { this.parent.fit(); }; /** * A message handler invoked on a `'child-hidden'` message. */ DockLayout.prototype.onChildHidden = function (msg) { this.parent.fit(); }; /** * A message handler invoked on a `'resize'` message. */ DockLayout.prototype.onResize = function (msg) { if (this.parent.isVisible) { this._update(msg.width, msg.height); } }; /** * A message handler invoked on an `'update-request'` message. */ DockLayout.prototype.onUpdateRequest = function (msg) { if (this.parent.isVisible) { this._update(-1, -1); } }; /** * A message handler invoked on a `'fit-request'` message. */ DockLayout.prototype.onFitRequest = function (msg) { if (this.parent.isAttached) { this._fit(); } }; /** * Remove the specified widget from the layout structure. * * #### Notes * This is a no-op if the widget is not in the layout tree. * * This does not detach the widget from the parent node. */ DockLayout.prototype._removeWidget = function (widget) { // Bail early if there is no layout root. if (!this._root) { return; } // Find the tab node which contains the given widget. var tabNode = this._root.findTabNode(widget); // Bail early if the tab node is not found. if (!tabNode) { return; } // If there are multiple tabs, just remove the widget's tab. if (tabNode.tabBar.titles.length > 1) { tabNode.tabBar.removeTab(widget.title); return; } // Otherwise, the tab node needs to be removed... // Dispose the tab bar. tabNode.tabBar.dispose(); // Handle the case where the tab node is the root. if (this._root === tabNode) { this._root = null; return; } // Otherwise, remove the tab node from its parent... // Prevent widget resizing unless needed. this._root.holdAllSizes(); // Clear the parent reference on the tab node. var splitNode = tabNode.parent; tabNode.parent = null; // Remove the tab node from its parent split node. var i = algorithm_1.ArrayExt.removeFirstOf(splitNode.children, tabNode); var handle = algorithm_1.ArrayExt.removeAt(splitNode.handles, i); algorithm_1.ArrayExt.removeAt(splitNode.sizers, i); // Remove the handle from its parent DOM node. if (handle.parentNode) { handle.parentNode.removeChild(handle); } // If there are multiple children, just update the handles. if (splitNode.children.length > 1) { splitNode.syncHandles(); return; } // Otherwise, the split node also needs to be removed... // Clear the parent reference on the split node. var maybeParent = splitNode.parent; splitNode.parent = null; // Lookup the remaining child node and handle. var childNode = splitNode.children[0]; var childHandle = splitNode.handles[0]; // Clear the split node data. splitNode.children.length = 0; splitNode.handles.length = 0; splitNode.sizers.length = 0; // Remove the child handle from its parent node. if (childHandle.parentNode) { childHandle.parentNode.removeChild(childHandle); } // Handle the case where the split node is the root. if (this._root === splitNode) { childNode.parent = null; this._root = childNode; return; } // Otherwise, move the child node to the parent node... var parentNode = maybeParent; // Lookup the index of the split node. var j = parentNode.children.indexOf(splitNode); // Handle the case where the child node is a tab node. if (childNode instanceof Private.TabLayoutNode) { childNode.parent = parentNode; parentNode.children[j] = childNode; return; } // Remove the split data from the parent. var splitHandle = algorithm_1.ArrayExt.removeAt(parentNode.handles, j); algorithm_1.ArrayExt.removeAt(parentNode.children, j); algorithm_1.ArrayExt.removeAt(parentNode.sizers, j); // Remove the handle from its parent node. if (splitHandle.parentNode) { splitHandle.parentNode.removeChild(splitHandle); } // The child node and the split parent node will have the same // orientation. Merge the grand-children with the parent node. for (var i_1 = 0, n = childNode.children.length; i_1 < n; ++i_1) { var gChild = childNode.children[i_1]; var gHandle = childNode.handles[i_1]; var gSizer = childNode.sizers[i_1]; algorithm_1.ArrayExt.insert(parentNode.children, j + i_1, gChild); algorithm_1.ArrayExt.insert(parentNode.handles, j + i_1, gHandle); algorithm_1.ArrayExt.insert(parentNode.sizers, j + i_1, gSizer); gChild.parent = parentNode; } // Clear the child node. childNode.children.length = 0; childNode.handles.length = 0; childNode.sizers.length = 0; childNode.parent = null; // Sync the handles on the parent node. parentNode.syncHandles(); }; /** * Insert a widget next to an existing tab. * * #### Notes * This does not attach the widget to the parent widget. */ DockLayout.prototype._insertTab = function (widget, ref, refNode, after) { // Do nothing if the tab is inserted next to itself. if (widget === ref) { return; } // Create the root if it does not exist. if (!this._root) { var tabNode = new Private.TabLayoutNode(this._createTabBar()); tabNode.tabBar.addTab(widget.title); this._root = tabNode; return; } // Use the first tab node as the ref node if needed. if (!refNode) { refNode = this._root.findFirstTabNode(); } // If the widget is not contained in the ref node, ensure it is // removed from the layout and hidden before being added again. if (refNode.tabBar.titles.indexOf(widget.title) === -1) { this._removeWidget(widget); widget.hide(); } // Lookup the target index for inserting the tab. var index; if (ref) { index = refNode.tabBar.titles.indexOf(ref.title); } else { index = refNode.tabBar.currentIndex; } // Insert the widget's tab relative to the target index. refNode.tabBar.insertTab(index + (after ? 1 : 0), widget.title); }; /** * Insert a widget as a new split area. * * #### Notes * This does not attach the widget to the parent widget. */ DockLayout.prototype._insertSplit = function (widget, ref, refNode, orientation, after) { // Do nothing if there is no effective split. if (widget === ref && refNode && refNode.tabBar.titles.length === 1) { return; } // Ensure the widget is removed from the current layout. this._removeWidget(widget); // Create the tab layout node to hold the widget. var tabNode = new Private.TabLayoutNode(this._createTabBar()); tabNode.tabBar.addTab(widget.title); // Set the root if it does not exist. if (!this._root) { this._root = tabNode; return; } // If the ref node parent is null, split the root. if (!refNode || !refNode.parent) { // Ensure the root is split with the correct orientation. var root = this._splitRoot(orientation); // Determine the insert index for the new tab node. var i_2 = after ? root.children.length : 0; // Normalize the split node. root.normalizeSizes(); // Create the sizer for new tab node. var sizer = Private.createSizer(refNode ? 1 : Private.GOLDEN_RATIO); // Insert the tab node sized to the golden ratio. algorithm_1.ArrayExt.insert(root.children, i_2, tabNode); algorithm_1.ArrayExt.insert(root.sizers, i_2, sizer); algorithm_1.ArrayExt.insert(root.handles, i_2, this._createHandle()); tabNode.parent = root; // Re-normalize the split node to maintain the ratios. root.normalizeSizes(); // Finally, synchronize the visibility of the handles. root.syncHandles(); return; } // Lookup the split node for the ref widget. var splitNode = refNode.parent; // If the split node already had the correct orientation, // the widget can be inserted into the split node directly. if (splitNode.orientation === orientation) { // Find the index of the ref node. var i_3 = splitNode.children.indexOf(refNode); // Normalize the split node. splitNode.normalizeSizes(); // Consume half the space for the insert location. var s = splitNode.sizers[i_3].sizeHint /= 2; // Insert the tab node sized to the other half. var j_1 = i_3 + (after ? 1 : 0); algorithm_1.ArrayExt.insert(splitNode.children, j_1, tabNode); algorithm_1.ArrayExt.insert(splitNode.sizers, j_1, Private.createSizer(s)); algorithm_1.ArrayExt.insert(splitNode.handles, j_1, this._createHandle()); tabNode.parent = splitNode; // Finally, synchronize the visibility of the handles. splitNode.syncHandles(); return; } // Remove the ref node from the split node. var i = algorithm_1.ArrayExt.removeFirstOf(splitNode.children, refNode); // Create a new normalized split node for the children. var childNode = new Private.SplitLayoutNode(orientation); childNode.normalized = true; // Add the ref node sized to half the space. childNode.children.push(refNode); childNode.sizers.push(Private.createSizer(0.5)); childNode.handles.push(this._createHandle()); refNode.parent = childNode; // Add the tab node sized to the other half. var j = after ? 1 : 0; algorithm_1.ArrayExt.insert(childNode.children, j, tabNode); algorithm_1.ArrayExt.insert(childNode.sizers, j, Private.createSizer(0.5)); algorithm_1.ArrayExt.insert(childNode.handles, j, this._createHandle()); tabNode.parent = childNode; // Synchronize the visibility of the handles. childNode.syncHandles(); // Finally, add the new child node to the original split node. algorithm_1.ArrayExt.insert(splitNode.children, i, childNode); childNode.parent = splitNode; }; /** * Ensure the root is a split node with the given orientation. */ DockLayout.prototype._splitRoot = function (orientation) { // Bail early if the root already meets the requirements. var oldRoot = this._root; if (oldRoot instanceof Private.SplitLayoutNode) { if (oldRoot.orientation === orientation) { return oldRoot; } } // Create a new root node with the specified orientation. var newRoot = this._root = new Private.SplitLayoutNode(orientation); // Add the old root to the new root. if (oldRoot) { newRoot.children.push(oldRoot); newRoot.sizers.push(Private.createSizer(0)); newRoot.handles.push(this._createHandle()); oldRoot.parent = newRoot; } // Return the new root as a convenience. return newRoot; }; /** * Fit the layout to the total size required by the widgets. */ DockLayout.prototype._fit = function () { // Set up the computed minimum size. var minW = 0; var minH = 0; // Update the size limits for the layout tree. if (this._root) { var limits = this._root.fit(this._spacing, this._items); minW = limits.minWidth; minH = limits.minHeight; } // Update the box sizing and add it to the computed min size. var box = this._box = domutils_1.ElementExt.boxSizing(this.parent.node); minW += box.horizontalSum; minH += box.verticalSum; // Update the parent's min size constraints. var style = this.parent.node.style; style.minWidth = minW + "px"; style.minHeight = minH + "px"; // Set the dirty flag to ensure only a single update occurs. this._dirty = true; // Notify the ancestor that it should fit immediately. This may // cause a resize of the parent, fulfilling the required update. if (this.parent.parent) { messaging_1.MessageLoop.sendMessage(this.parent.parent, widget_1.Widget.Msg.FitRequest); } // If the dirty flag is still set, the parent was not resized. // Trigger the required update on the parent widget immediately. if (this._dirty) { messaging_1.MessageLoop.sendMessage(this.parent, widget_1.Widget.Msg.UpdateRequest); } }; /** * Update the layout position and size of the widgets. * * The parent offset dimensions should be `-1` if unknown. */ DockLayout.prototype._update = function (offsetWidth, offsetHeight) { // Clear the dirty flag to indicate the update occurred. this._dirty = false; // Bail early if there is no root layout node. if (!this._root) { return; } // Measure the parent if the offset dimensions are unknown. if (offsetWidth < 0) { offsetWidth = this.parent.node.offsetWidth; } if (offsetHeight < 0) { offsetHeight = this.parent.node.offsetHeight; } // Ensure the parent box sizing data is computed. if (!this._box) { this._box = domutils_1.ElementExt.boxSizing(this.parent.node); } // Compute the actual layout bounds adjusted for border and padding. var x = this._box.paddingTop; var y = this._box.paddingLeft; var width = offsetWidth - this._box.horizontalSum; var height = offsetHeight - this._box.verticalSum; // Update the geometry of the layout tree. this._root.update(x, y, width, height, this._spacing, this._items); }; /** * Create a new tab bar for use by the dock layout. * * #### Notes * The tab bar will be attached to the parent if it exists. */ DockLayout.prototype._createTabBar = function () { // Create the tab bar using the renderer. var tabBar = this.renderer.createTabBar(); // Enforce necessary tab bar behavior. tabBar.orientation = 'horizontal'; // Reparent and attach the tab bar to the parent if possible. if (this.parent) { tabBar.parent = this.parent; this.attachWidget(tabBar); } // Return the initialized tab bar. return tabBar; }; /** * Create a new handle for the dock layout. * * #### Notes * The handle will be attached to the parent if it exists. */ DockLayout.prototype._createHandle = function () { // Create the handle using the renderer. var handle = this.renderer.createHandle(); // Initialize the handle layout behavior. var style = handle.style; style.position = 'absolute'; style.top = '0'; style.left = '0'; style.width = '0'; style.height = '0'; // Attach the handle to the parent if it exists. if (this.parent) { this.parent.node.appendChild(handle); } // Return the initialized handle. return handle; }; return DockLayout; }(layout_1.Layout)); exports.DockLayout = DockLayout; /** * The namespace for the module implementation details. */ var Private; (function (Private) { /** * A fraction used for sizing root panels; ~= `1 / golden_ratio`. */ Private.GOLDEN_RATIO = 0.618; /** * Clamp a spacing value to an integer >= 0. */ function clampSpacing(value) { return Math.max(0, Math.floor(value)); } Private.clampSpacing = clampSpacing; /** * Create a box sizer with an initial size hint. */ function createSizer(hint) { var sizer = new boxengine_1.BoxSizer(); sizer.sizeHint = hint; sizer.size = hint; return sizer; } Private.createSizer = createSizer; /** * Normalize an area config object and collect the visited widgets. */ function normalizeAreaConfig(config, widgetSet) { var result; if (config.type === 'tab-area') { result = normalizeTabAreaConfig(config, widgetSet); } else { result = normalizeSplitAreaConfig(config, widgetSet); } return result; } Private.normalizeAreaConfig = normalizeAreaConfig; /** * Convert a normalized area config into a layout tree. */ function realizeAreaConfig(config, renderer) { var node; if (config.type === 'tab-area') { node = realizeTabAreaConfig(config, renderer); } else { node = realizeSplitAreaConfig(config, renderer); } return node; } Private.realizeAreaConfig = realizeAreaConfig; /** * A layout node which holds the data for a tabbed area. */ var TabLayoutNode = /** @class */ (function () { /** * Construct a new tab layout node. * * @param tabBar - The tab bar to use for the layout node. */ function TabLayoutNode(tabBar) { /** * The parent of the layout node. */ this.parent = null; this._top = 0; this._left = 0; this._width = 0; this._height = 0; var tabSizer = new boxengine_1.BoxSizer(); var widgetSizer = new boxengine_1.BoxSizer(); tabSizer.stretch = 0; widgetSizer.stretch = 1; this.tabBar = tabBar; this.sizers = [tabSizer, widgetSizer]; } Object.defineProperty(TabLayoutNode.prototype, "top", { /** * The most recent value for the `top` edge of the layout box. */ get: function () { return this._top; }, enumerable: true, configurable: true }); Object.defineProperty(TabLayoutNode.prototype, "left", { /** * The most recent value for the `left` edge of the layout box. */ get: function () { return this._left; }, enumerable: true, configurable: true }); Object.defineProperty(TabLayoutNode.prototype, "width", { /** * The most recent value for the `width` of the layout box. */ get: function () { return this._width; }, enumerable: true, configurable: true }); Object.defineProperty(TabLayoutNode.prototype, "height", { /** * The most recent value for the `height` of the layout box. */ get: function () { return this._height; }, enumerable: true, configurable: true }); /** * Create an iterator for all widgets in the layout tree. */ TabLayoutNode.prototype.iterAllWidgets = function () { return algorithm_1.chain(algorithm_1.once(this.tabBar), this.iterUserWidgets()); }; /** * Create an iterator for the user widgets in the layout tree. */ TabLayoutNode.prototype.iterUserWidgets = function () { return algorithm_1.map(this.tabBar.titles, function (title) { return title.owner; }); }; /** * Create an iterator for the selected widgets in the layout tree. */ TabLayoutNode.prototype.iterSelectedWidgets = function () { var title = this.tabBar.currentTitle; return title ? algorithm_1.once(title.owner) : algorithm_1.empty(); }; /** * Create an iterator for the tab bars in the layout tree. */ TabLayoutNode.prototype.iterTabBars = function () { return algorithm_1.once(this.tabBar); }; /** * Create an iterator for the handles in the layout tree. */ TabLayoutNode.prototype.iterHandles = function () { return algorithm_1.empty(); }; /** * Find the tab layout node which contains the given widget. */ TabLayoutNode.prototype.findTabNode = function (widget) { return this.tabBar.titles.indexOf(widget.title) !== -1 ? this : null; }; /** * Find the split layout node which contains the given handle. */ TabLayoutNode.prototype.findSplitNode = function (handle) { return null; }; /** * Find the first tab layout node in a layout tree. */ TabLayoutNode.prototype.findFirstTabNode = function () { return this; }; /** * Find the tab layout node which contains the local point. */ TabLayoutNode.prototype.hitTestTabNodes = function (x, y) { if (x < this._left || x >= this._left + this._width) { return null; } if (y < this._top || y >= this._top + this._height) { return null; } return this; }; /** * Create a configuration object for the layout tree. */ TabLayoutNode.prototype.createConfig = function () { var widgets = this.tabBar.titles.map(function (title) { return title.owner; }); var currentIndex = this.tabBar.currentIndex; return { type: 'tab-area', widgets: widgets, currentIndex: currentIndex }; }; /** * Recursively hold all of the sizes in the layout tree. * * This ignores the sizers of tab layout nodes. */ TabLayoutNode.prototype.holdAllSizes = function () { return; }; /** * Fit the layout tree. */ TabLayoutNode.prototype.fit = function (spacing, items) { // Set up the limit variables. var minWidth = 0; var minHeight = 0; var maxWidth = Infinity; var maxHeight = Infinity; // Lookup the tab bar layout item. var tabBarItem = items.get(this.tabBar); // Lookup the widget layout item. var current = this.tabBar.currentTitle; var widgetItem = current ? items.get(current.owner) : undefined; // Lookup the tab bar and widget sizers. var _a = this.sizers, tabBarSizer = _a[0], widgetSizer = _a[1]; // Update the tab bar limits. if (tabBarItem) { tabBarItem.fit(); } // Update the widget limits. if (widgetItem) { widgetItem.fit(); } // Update the results and sizer for the tab bar. if (tabBarItem && !tabBarItem.isHidden) { minWidth = Math.max(minWidth, tabBarItem.minWidth); minHeight += tabBarItem.minHeight; tabBarSizer.minSize = tabBarItem.minHeight; tabBarSizer.maxSize = tabBarItem.maxHeight; } else { tabBarSizer.minSize = 0; tabBarSizer.maxSize = 0; } // Update the results and sizer for the current widget. if (widgetItem && !widgetItem.isHidden) { minWidth = Math.max(minWidth, widgetItem.minWidth); minHeight += widgetItem.minHeight; widgetSizer.minSize = widgetItem.minHeight; widgetSizer.maxSize = Infinity; } else { widgetSizer.minSize = 0; widgetSizer.maxSize = Infinity; } // Return the computed size limits for the layout node. return { minWidth: minWidth, minHeight: minHeight, maxWidth: maxWidth, maxHeight: maxHeight }; }; /** * Update the layout tree. */ TabLayoutNode.prototype.update = function (left, top, width, height, spacing, items) { // Update the layout box values. this._top = top; this._left = left; this._width = width; this._height = height; // Lookup the tab bar layout item. var tabBarItem = items.get(this.tabBar); // Lookup the widget layout item. var current = this.tabBar.currentTitle; var widgetItem = current ? items.get(current.owner) : undefined; // Distribute the layout space to the sizers. boxengine_1.BoxEngine.calc(this.sizers, height); // Update the tab bar item using the computed size. if (tabBarItem && !tabBarItem.isHidden) { var size = this.sizers[0].size; tabBarItem.update(left, top, width, size); top += size; } // Layout the widget using the computed size. if (widgetItem && !widgetItem.isHidden) { var size = this.sizers[1].size; widgetItem.update(left, top, width, size); } }; return TabLayoutNode; }()); Private.TabLayoutNode = TabLayoutNode; /** * A layout node which holds the data for a split area. */ var SplitLayoutNode = /** @class */ (function () { /** * Construct a new split layout node. * * @param orientation - The orientation of the node. */ function SplitLayoutNode(orientation) { /** * The parent of the layout node. */ this.parent = null; /** * Whether the sizers have been normalized. */ this.normalized = false; /** * The child nodes for the split node. */ this.children = []; /** * The box sizers for the layout children. */ this.sizers = []; /** * The handles for the layout children. */ this.handles = []; this.orientation = orientation; } /** * Create an iterator for all widgets in the layout tree. */ SplitLayoutNode.prototype.iterAllWidgets = function () { var children = algorithm_1.map(this.children, function (child) { return child.iterAllWidgets(); }); return new algorithm_1.ChainIterator(children); }; /** * Create an iterator for the user widgets in the layout tree. */ SplitLayoutNode.prototype.iterUserWidgets = function () { var children = algorithm_1.map(this.children, function (child) { return child.iterUserWidgets(); }); return new algorithm_1.ChainIterator(children); }; /** * Create an iterator for the selected widgets in the layout tree. */ SplitLayoutNode.prototype.iterSelectedWidgets = function () { var children = algorithm_1.map(this.children, function (child) { return child.iterSelectedWidgets(); }); return new algorithm_1.ChainIterator(children); }; /** * Create an iterator for the tab bars in the layout tree. */ SplitLayoutNode.prototype.iterTabBars = function () { var children = algorithm_1.map(this.children, function (child) { return child.iterTabBars(); }); return new algorithm_1.ChainIterator(children); }; /** * Create an iterator for the handles in the layout tree. */ SplitLayoutNode.prototype.iterHandles = function () { var children = algorithm_1.map(this.children, function (child) { return child.iterHandles(); }); return algorithm_1.chain(this.handles, new algorithm_1.ChainIterator(children)); }; /** * Find the tab layout node which contains the given widget. */ SplitLayoutNode.prototype.findTabNode = function (widget) { for (var i = 0, n = this.children.length; i < n; ++i) { var result = this.children[i].findTabNode(widget); if (result) { return result; } } return null; }; /** * Find the split layout node which contains the given handle. */ SplitLayoutNode.prototype.findSplitNode = function (handle) { var index = this.handles.indexOf(handle); if (index !== -1) { return { index: index, node: this }; } for (var i = 0, n = this.children.length; i < n; ++i) { var result = this.children[i].findSplitNode(handle); if (result) { return result; } } return null; }; /** * Find the first tab layout node in a layout tree. */ SplitLayoutNode.prototype.findFirstTabNode = function () { if (this.children.length === 0) { return null; } return this.children[0].findFirstTabNode(); }; /** * Find the tab layout node which contains the local point. */ SplitLayoutNode.prototype.hitTestTabNodes = function (x, y) { for (var i = 0, n = this.children.length; i < n; ++i) { var result = this.children[i].hitTestTabNodes(x, y); if (result) { return result; } } return null; }; /** * Create a configuratio