diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebInspectorUI/UserInterface/Views/NavigationBar.js | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Views/NavigationBar.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Views/NavigationBar.js | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Views/NavigationBar.js b/Source/WebInspectorUI/UserInterface/Views/NavigationBar.js new file mode 100644 index 000000000..382c36754 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Views/NavigationBar.js @@ -0,0 +1,398 @@ +/* + * 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.NavigationBar = class NavigationBar extends WebInspector.View +{ + constructor(element, navigationItems, role, label) + { + super(element); + + this.element.classList.add(this.constructor.StyleClassName || "navigation-bar"); + this.element.tabIndex = 0; + + if (role) + this.element.setAttribute("role", role); + if (label) + this.element.setAttribute("aria-label", label); + + this.element.addEventListener("focus", this._focus.bind(this), false); + this.element.addEventListener("blur", this._blur.bind(this), false); + this.element.addEventListener("keydown", this._keyDown.bind(this), false); + this.element.addEventListener("mousedown", this._mouseDown.bind(this), false); + + this._forceLayout = false; + this._minimumWidth = NaN; + this._navigationItems = []; + + if (navigationItems) { + for (var i = 0; i < navigationItems.length; ++i) + this.addNavigationItem(navigationItems[i]); + } + } + + // Public + + addNavigationItem(navigationItem, parentElement) + { + return this.insertNavigationItem(navigationItem, this._navigationItems.length, parentElement); + } + + insertNavigationItem(navigationItem, index, parentElement) + { + console.assert(navigationItem instanceof WebInspector.NavigationItem); + if (!(navigationItem instanceof WebInspector.NavigationItem)) + return null; + + if (navigationItem.parentNavigationBar) + navigationItem.parentNavigationBar.removeNavigationItem(navigationItem); + + navigationItem._parentNavigationBar = this; + + console.assert(index >= 0 && index <= this._navigationItems.length); + index = Math.max(0, Math.min(index, this._navigationItems.length)); + + this._navigationItems.splice(index, 0, navigationItem); + + if (!parentElement) + parentElement = this.element; + + var nextSibling = this._navigationItems[index + 1]; + var nextSiblingElement = nextSibling ? nextSibling.element : null; + if (nextSiblingElement && nextSiblingElement.parentNode !== parentElement) + nextSiblingElement = null; + + parentElement.insertBefore(navigationItem.element, nextSiblingElement); + + this._minimumWidth = NaN; + + this.needsLayout(); + + return navigationItem; + } + + removeNavigationItem(navigationItemOrIdentifierOrIndex) + { + var navigationItem = this._findNavigationItem(navigationItemOrIdentifierOrIndex); + if (!navigationItem) + return null; + + navigationItem._parentNavigationBar = null; + + if (this._selectedNavigationItem === navigationItem) + this.selectedNavigationItem = null; + + this._navigationItems.remove(navigationItem); + navigationItem.element.remove(); + + this._minimumWidth = NaN; + + this.needsLayout(); + + return navigationItem; + } + + get selectedNavigationItem() + { + return this._selectedNavigationItem || null; + } + + set selectedNavigationItem(navigationItemOrIdentifierOrIndex) + { + var navigationItem = this._findNavigationItem(navigationItemOrIdentifierOrIndex); + + // Only radio navigation items can be selected. + if (!(navigationItem instanceof WebInspector.RadioButtonNavigationItem)) + navigationItem = null; + + if (this._selectedNavigationItem === navigationItem) + return; + + if (this._selectedNavigationItem) + this._selectedNavigationItem.selected = false; + + this._selectedNavigationItem = navigationItem || null; + + if (this._selectedNavigationItem) + this._selectedNavigationItem.selected = true; + + // When the mouse is down don't dispatch the selected event, it will be dispatched on mouse up. + // This prevents sending the event while the user is scrubbing the bar. + if (!this._mouseIsDown) + this.dispatchEventToListeners(WebInspector.NavigationBar.Event.NavigationItemSelected); + } + + get navigationItems() + { + return this._navigationItems; + } + + get minimumWidth() + { + if (isNaN(this._minimumWidth)) + this._minimumWidth = this._calculateMinimumWidth(); + return this._minimumWidth; + } + + get sizesToFit() + { + // Can be overriden by subclasses. + return false; + } + + needsLayout() + { + this._forceLayout = true; + + super.needsLayout(); + } + + layout() + { + if (this.layoutReason !== WebInspector.View.LayoutReason.Resize && !this._forceLayout) + return; + + this._forceLayout = false; + + // Remove the collapsed style class to test if the items can fit at full width. + this.element.classList.remove(WebInspector.NavigationBar.CollapsedStyleClassName); + + // Tell each navigation item to update to full width if needed. + for (let navigationItem of this._navigationItems) + navigationItem.updateLayout(true); + + let totalItemWidth = 0; + for (let navigationItem of this._navigationItems) { + // Skip flexible space items since they can take up no space at the minimum width. + if (navigationItem instanceof WebInspector.FlexibleSpaceNavigationItem) + continue; + + totalItemWidth += navigationItem.element.realOffsetWidth; + } + + const barWidth = this.element.realOffsetWidth; + + // Add the collapsed class back if the items are wider than the bar. + if (totalItemWidth > barWidth) + this.element.classList.add(WebInspector.NavigationBar.CollapsedStyleClassName); + + // Give each navigation item the opportunity to collapse further. + for (let navigationItem of this._navigationItems) + navigationItem.updateLayout(false); + } + + // Private + + _findNavigationItem(navigationItemOrIdentifierOrIndex) + { + var navigationItem = null; + + if (navigationItemOrIdentifierOrIndex instanceof WebInspector.NavigationItem) { + if (this._navigationItems.includes(navigationItemOrIdentifierOrIndex)) + navigationItem = navigationItemOrIdentifierOrIndex; + } else if (typeof navigationItemOrIdentifierOrIndex === "number") { + navigationItem = this._navigationItems[navigationItemOrIdentifierOrIndex]; + } else if (typeof navigationItemOrIdentifierOrIndex === "string") { + for (var i = 0; i < this._navigationItems.length; ++i) { + if (this._navigationItems[i].identifier === navigationItemOrIdentifierOrIndex) { + navigationItem = this._navigationItems[i]; + break; + } + } + } + + return navigationItem; + } + + _mouseDown(event) + { + // Only handle left mouse clicks. + if (event.button !== 0) + return; + + // Remove the tabIndex so clicking the navigation bar does not give it focus. + // Only keep the tabIndex if already focused from keyboard navigation. This matches Xcode. + if (!this._focused) + this.element.removeAttribute("tabindex"); + + var itemElement = event.target.enclosingNodeOrSelfWithClass(WebInspector.RadioButtonNavigationItem.StyleClassName); + if (!itemElement || !itemElement.navigationItem) + return; + + this._previousSelectedNavigationItem = this.selectedNavigationItem; + this.selectedNavigationItem = itemElement.navigationItem; + + this._mouseIsDown = true; + + this._mouseMovedEventListener = this._mouseMoved.bind(this); + this._mouseUpEventListener = this._mouseUp.bind(this); + + if (typeof this.selectedNavigationItem.dontPreventDefaultOnNavigationBarMouseDown === "function" + && this.selectedNavigationItem.dontPreventDefaultOnNavigationBarMouseDown() + && this._previousSelectedNavigationItem === this.selectedNavigationItem) + return; + + // Register these listeners on the document so we can track the mouse if it leaves the navigation bar. + document.addEventListener("mousemove", this._mouseMovedEventListener, false); + document.addEventListener("mouseup", this._mouseUpEventListener, false); + + event.preventDefault(); + event.stopPropagation(); + } + + _mouseMoved(event) + { + console.assert(event.button === 0); + console.assert(this._mouseIsDown); + if (!this._mouseIsDown) + return; + + event.preventDefault(); + event.stopPropagation(); + + var itemElement = event.target.enclosingNodeOrSelfWithClass(WebInspector.RadioButtonNavigationItem.StyleClassName); + if (!itemElement || !itemElement.navigationItem || !this.element.contains(itemElement)) { + // Find the element that is at the X position of the mouse, even when the mouse is no longer + // vertically in the navigation bar. + var element = document.elementFromPoint(event.pageX, this.element.totalOffsetTop + (this.element.offsetHeight / 2)); + if (!element) + return; + + itemElement = element.enclosingNodeOrSelfWithClass(WebInspector.RadioButtonNavigationItem.StyleClassName); + if (!itemElement || !itemElement.navigationItem || !this.element.contains(itemElement)) + return; + } + + if (this.selectedNavigationItem) + this.selectedNavigationItem.active = false; + + this.selectedNavigationItem = itemElement.navigationItem; + + this.selectedNavigationItem.active = true; + } + + _mouseUp(event) + { + console.assert(event.button === 0); + console.assert(this._mouseIsDown); + if (!this._mouseIsDown) + return; + + if (this.selectedNavigationItem) + this.selectedNavigationItem.active = false; + + this._mouseIsDown = false; + + document.removeEventListener("mousemove", this._mouseMovedEventListener, false); + document.removeEventListener("mouseup", this._mouseUpEventListener, false); + + delete this._mouseMovedEventListener; + delete this._mouseUpEventListener; + + // Restore the tabIndex so the navigation bar can be in the keyboard tab loop. + this.element.tabIndex = 0; + + // Dispatch the selected event here since the selectedNavigationItem setter surpresses it + // while the mouse is down to prevent sending it while scrubbing the bar. + if (this._previousSelectedNavigationItem !== this.selectedNavigationItem) + this.dispatchEventToListeners(WebInspector.NavigationBar.Event.NavigationItemSelected); + + delete this._previousSelectedNavigationItem; + + event.preventDefault(); + event.stopPropagation(); + } + + _keyDown(event) + { + if (!this._focused) + return; + + if (event.keyIdentifier !== "Left" && event.keyIdentifier !== "Right") + return; + + event.preventDefault(); + event.stopPropagation(); + + var selectedNavigationItemIndex = this._navigationItems.indexOf(this._selectedNavigationItem); + + if (event.keyIdentifier === "Left") { + if (selectedNavigationItemIndex === -1) + selectedNavigationItemIndex = this._navigationItems.length; + + do { + selectedNavigationItemIndex = Math.max(0, selectedNavigationItemIndex - 1); + } while (selectedNavigationItemIndex && !(this._navigationItems[selectedNavigationItemIndex] instanceof WebInspector.RadioButtonNavigationItem)); + } else if (event.keyIdentifier === "Right") { + do { + selectedNavigationItemIndex = Math.min(selectedNavigationItemIndex + 1, this._navigationItems.length - 1); + } while (selectedNavigationItemIndex < this._navigationItems.length - 1 && !(this._navigationItems[selectedNavigationItemIndex] instanceof WebInspector.RadioButtonNavigationItem)); + } + + if (!(this._navigationItems[selectedNavigationItemIndex] instanceof WebInspector.RadioButtonNavigationItem)) + return; + + this.selectedNavigationItem = this._navigationItems[selectedNavigationItemIndex]; + } + + _focus(event) + { + this._focused = true; + } + + _blur(event) + { + this._focused = false; + } + + _calculateMinimumWidth() + { + const wasCollapsed = this.element.classList.contains(WebInspector.NavigationBar.CollapsedStyleClassName); + + // Add the collapsed style class to calculate the width of the items when they are collapsed. + if (!wasCollapsed) + this.element.classList.add(WebInspector.NavigationBar.CollapsedStyleClassName); + + let totalItemWidth = 0; + for (let item of this._navigationItems) { + // Skip flexible space items since they can take up no space at the minimum width. + if (item instanceof WebInspector.FlexibleSpaceNavigationItem) + continue; + + totalItemWidth += item.minimumWidth; + } + + // Remove the collapsed style class if we were not collapsed before. + if (!wasCollapsed) + this.element.classList.remove(WebInspector.NavigationBar.CollapsedStyleClassName); + + return totalItemWidth; + } +}; + +WebInspector.NavigationBar.CollapsedStyleClassName = "collapsed"; + +WebInspector.NavigationBar.Event = { + NavigationItemSelected: "navigation-bar-navigation-item-selected" +}; |