diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js b/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js new file mode 100644 index 000000000..88f963b63 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/HierarchicalPathNavigationItem.js @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2013, 2015 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +WebInspector.HierarchicalPathNavigationItem = class HierarchicalPathNavigationItem extends WebInspector.NavigationItem +{ + constructor(identifier, components) + { + super(identifier); + + this.components = components; + } + + // Public + + get components() + { + return this._components; + } + + set components(newComponents) + { + if (!newComponents) + newComponents = []; + + for (var i = 0; this._components && i < this._components.length; ++i) + this._components[i].removeEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this); + + // Make a shallow copy of the newComponents array using slice. + this._components = newComponents.slice(0); + + for (var i = 0; i < this._components.length; ++i) + this._components[i].addEventListener(WebInspector.HierarchicalPathComponent.Event.SiblingWasSelected, this._siblingPathComponentWasSelected, this); + + this.element.removeChildren(); + delete this._collapsedComponent; + + for (var i = 0; i < newComponents.length; ++i) + this.element.appendChild(newComponents[i].element); + + // Update layout for the so other items can adjust to the extra space (or lack thereof) too. + if (this.parentNavigationBar) + this.parentNavigationBar.needsLayout(); + } + + get lastComponent() + { + return this._components.lastValue || null; + } + + get alwaysShowLastPathComponentSeparator() + { + return this.element.classList.contains(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName); + } + + set alwaysShowLastPathComponentSeparator(flag) + { + if (flag) + this.element.classList.add(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName); + else + this.element.classList.remove(WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName); + } + + updateLayout(expandOnly) + { + var navigationBar = this.parentNavigationBar; + if (!navigationBar) + return; + + if (this._collapsedComponent) { + this.element.removeChild(this._collapsedComponent.element); + delete this._collapsedComponent; + } + + // Expand our components to full width to test if the items can fit at full width. + for (var i = 0; i < this._components.length; ++i) { + this._components[i].hidden = false; + this._components[i].forcedWidth = null; + } + + if (expandOnly) + return; + + if (navigationBar.sizesToFit) + return; + + // Iterate over all the other navigation items in the bar and calculate their width. + var totalOtherItemsWidth = 0; + for (var i = 0; i < navigationBar.navigationItems.length; ++i) { + // Skip ourself. + if (navigationBar.navigationItems[i] === this) + continue; + + // Skip flexible space items since they can take up no space at the minimum width. + if (navigationBar.navigationItems[i] instanceof WebInspector.FlexibleSpaceNavigationItem) + continue; + + totalOtherItemsWidth += navigationBar.navigationItems[i].element.realOffsetWidth; + } + + // Calculate the width for all the components. + var thisItemWidth = 0; + var componentWidths = []; + for (var i = 0; i < this._components.length; ++i) { + var componentWidth = this._components[i].element.realOffsetWidth; + componentWidths.push(componentWidth); + thisItemWidth += componentWidth; + } + + // If all our components fit with the other navigation items in the width of the bar, + // then we don't need to collapse any components. + var barWidth = navigationBar.element.realOffsetWidth; + if (totalOtherItemsWidth + thisItemWidth <= barWidth) + return; + + // Calculate the width we need to remove from our components, then iterate over them + // and force their width to be smaller. + var widthToRemove = totalOtherItemsWidth + thisItemWidth - barWidth; + for (var i = 0; i < this._components.length; ++i) { + var componentWidth = componentWidths[i]; + + // Try to take the whole width we need to remove from each component. + var forcedWidth = componentWidth - widthToRemove; + this._components[i].forcedWidth = forcedWidth; + + // Since components have a minimum width, we need to see how much was actually + // removed and subtract that from what remans to be removed. + componentWidths[i] = Math.max(this._components[i].minimumWidth, forcedWidth); + widthToRemove -= (componentWidth - componentWidths[i]); + + // If there is nothing else to remove, then we can stop. + if (widthToRemove <= 0) + break; + } + + // If there is nothing else to remove, then we can stop. + if (widthToRemove <= 0) + return; + + // If there are 3 or fewer components, then we can stop. Collapsing the middle of 3 components + // does not save more than a few pixels over just the icon, so it isn't worth it unless there + // are 4 or more components. + if (this._components.length <= 3) + return; + + // We want to collapse the middle components, so find the nearest middle index. + var middle = this._components.length >> 1; + var distance = -1; + var i = middle; + + // Create a component that will represent the hidden components with a ellipse as the display name. + this._collapsedComponent = new WebInspector.HierarchicalPathComponent(ellipsis, []); + this._collapsedComponent.collapsed = true; + + // Insert it in the middle, it doesn't matter exactly where since the elements around it will be hidden soon. + this.element.insertBefore(this._collapsedComponent.element, this._components[middle].element); + + // Add the width of the collapsed component to the width we need to remove. + widthToRemove += this._collapsedComponent.minimumWidth; + + var hiddenDisplayNames = []; + + // Loop through the components starting at the middle and fanning out in each direction. + while (i >= 0 && i <= this._components.length - 1) { + // Only hide components in the middle and never the ends. + if (i > 0 && i < this._components.length - 1) { + var component = this._components[i]; + component.hidden = true; + + // Remember the displayName so it can be put in the tool tip of the collapsed component. + if (distance > 0) + hiddenDisplayNames.unshift(component.displayName); + else + hiddenDisplayNames.push(component.displayName); + + // Fully subtract the hidden component's width. + widthToRemove -= componentWidths[i]; + + // If there is nothing else to remove, then we can stop. + if (widthToRemove <= 0) + break; + } + + // Calculate the next index. + i = middle + distance; + + // Increment the distance when it is in the positive direction. + if (distance > 0) + ++distance; + + // Flip the direction of the distance. + distance *= -1; + } + + // Set the tool tip of the collapsed component. + this._collapsedComponent.element.title = hiddenDisplayNames.join("\n"); + } + + // Protected + + get additionalClassNames() + { + return ["hierarchical-path"]; + } + + // Private + + _siblingPathComponentWasSelected(event) + { + this.dispatchEventToListeners(WebInspector.HierarchicalPathNavigationItem.Event.PathComponentWasSelected, event.data); + } +}; + +WebInspector.HierarchicalPathNavigationItem.AlwaysShowLastPathComponentSeparatorStyleClassName = "always-show-last-path-component-separator"; + +WebInspector.HierarchicalPathNavigationItem.Event = { + PathComponentWasSelected: "hierarchical-path-navigation-item-path-component-was-selected" +}; |