| /* |
| * Copyright (C) 2012 Google 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 GOOGLE 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 GOOGLE 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. |
| */ |
| |
| /** |
| * @unrestricted |
| */ |
| export default class SplitWidget extends UI.Widget { |
| /** |
| * @param {boolean} isVertical |
| * @param {boolean} secondIsSidebar |
| * @param {string=} settingName |
| * @param {number=} defaultSidebarWidth |
| * @param {number=} defaultSidebarHeight |
| * @param {boolean=} constraintsInDip |
| */ |
| constructor(isVertical, secondIsSidebar, settingName, defaultSidebarWidth, defaultSidebarHeight, constraintsInDip) { |
| super(true); |
| this.element.classList.add('split-widget'); |
| this.registerRequiredCSS('ui/splitWidget.css'); |
| |
| this.contentElement.classList.add('shadow-split-widget'); |
| this._sidebarElement = |
| this.contentElement.createChild('div', 'shadow-split-widget-contents shadow-split-widget-sidebar vbox'); |
| this._mainElement = |
| this.contentElement.createChild('div', 'shadow-split-widget-contents shadow-split-widget-main vbox'); |
| this._mainElement.createChild('slot').name = 'insertion-point-main'; |
| this._sidebarElement.createChild('slot').name = 'insertion-point-sidebar'; |
| this._resizerElement = this.contentElement.createChild('div', 'shadow-split-widget-resizer'); |
| this._resizerElementSize = null; |
| |
| this._resizerWidget = new UI.SimpleResizerWidget(); |
| this._resizerWidget.setEnabled(true); |
| this._resizerWidget.addEventListener(UI.ResizerWidget.Events.ResizeStart, this._onResizeStart, this); |
| this._resizerWidget.addEventListener(UI.ResizerWidget.Events.ResizeUpdate, this._onResizeUpdate, this); |
| this._resizerWidget.addEventListener(UI.ResizerWidget.Events.ResizeEnd, this._onResizeEnd, this); |
| |
| this._defaultSidebarWidth = defaultSidebarWidth || 200; |
| this._defaultSidebarHeight = defaultSidebarHeight || this._defaultSidebarWidth; |
| this._constraintsInDip = !!constraintsInDip; |
| this._resizeStartSizeDIP = 0; |
| this._setting = settingName ? Common.settings.createSetting(settingName, {}) : null; |
| |
| this._totalSizeCSS = 0; |
| this._totalSizeOtherDimensionCSS = 0; |
| /** @type {?UI.Widget} */ |
| this._mainWidget = null; |
| /** @type {?UI.Widget} */ |
| this._sidebarWidget = null; |
| this._animationFrameHandle = 0; |
| /** @type {?function()} */ |
| this._animationCallback = null; |
| this._showHideSidebarButtonTitle = ''; |
| /** @type {?UI.ToolbarButton} */ |
| this._showHideSidebarButton = null; |
| this._isVertical = false; |
| this._sidebarMinimized = false; |
| this._detaching = false; |
| this._sidebarSizeDIP = -1; |
| this._savedSidebarSizeDIP = this._sidebarSizeDIP; |
| this._secondIsSidebar = false; |
| this._shouldSaveShowMode = false; |
| /** @type {?number} */ |
| this._savedVerticalMainSize = null; |
| /** @type {?number} */ |
| this._savedHorizontalMainSize = null; |
| |
| this.setSecondIsSidebar(secondIsSidebar); |
| |
| this._innerSetVertical(isVertical); |
| this._showMode = ShowMode.Both; |
| this._savedShowMode = this._showMode; |
| |
| // Should be called after isVertical has the right value. |
| this.installResizer(this._resizerElement); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isVertical() { |
| return this._isVertical; |
| } |
| |
| /** |
| * @param {boolean} isVertical |
| */ |
| setVertical(isVertical) { |
| if (this._isVertical === isVertical) { |
| return; |
| } |
| |
| this._innerSetVertical(isVertical); |
| |
| if (this.isShowing()) { |
| this._updateLayout(); |
| } |
| } |
| |
| /** |
| * @param {boolean} isVertical |
| */ |
| _innerSetVertical(isVertical) { |
| this.contentElement.classList.toggle('vbox', !isVertical); |
| this.contentElement.classList.toggle('hbox', isVertical); |
| this._isVertical = isVertical; |
| |
| this._resizerElementSize = null; |
| this._sidebarSizeDIP = -1; |
| this._restoreSidebarSizeFromSettings(); |
| if (this._shouldSaveShowMode) { |
| this._restoreAndApplyShowModeFromSettings(); |
| } |
| this._updateShowHideSidebarButton(); |
| // FIXME: reverse SplitWidget.isVertical meaning. |
| this._resizerWidget.setVertical(!isVertical); |
| this.invalidateConstraints(); |
| } |
| |
| /** |
| * @param {boolean=} animate |
| */ |
| _updateLayout(animate) { |
| this._totalSizeCSS = 0; // Lazy update. |
| this._totalSizeOtherDimensionCSS = 0; |
| |
| // Remove properties that might affect total size calculation. |
| this._mainElement.style.removeProperty('width'); |
| this._mainElement.style.removeProperty('height'); |
| this._sidebarElement.style.removeProperty('width'); |
| this._sidebarElement.style.removeProperty('height'); |
| |
| this._innerSetSidebarSizeDIP(this._preferredSidebarSizeDIP(), !!animate); |
| } |
| |
| /** |
| * @param {!UI.Widget} widget |
| */ |
| setMainWidget(widget) { |
| if (this._mainWidget === widget) { |
| return; |
| } |
| this.suspendInvalidations(); |
| if (this._mainWidget) { |
| this._mainWidget.detach(); |
| } |
| this._mainWidget = widget; |
| if (widget) { |
| widget.element.slot = 'insertion-point-main'; |
| if (this._showMode === ShowMode.OnlyMain || this._showMode === ShowMode.Both) { |
| widget.show(this.element); |
| } |
| } |
| this.resumeInvalidations(); |
| } |
| |
| /** |
| * @param {!UI.Widget} widget |
| */ |
| setSidebarWidget(widget) { |
| if (this._sidebarWidget === widget) { |
| return; |
| } |
| this.suspendInvalidations(); |
| if (this._sidebarWidget) { |
| this._sidebarWidget.detach(); |
| } |
| this._sidebarWidget = widget; |
| if (widget) { |
| widget.element.slot = 'insertion-point-sidebar'; |
| if (this._showMode === ShowMode.OnlySidebar || this._showMode === ShowMode.Both) { |
| widget.show(this.element); |
| } |
| } |
| this.resumeInvalidations(); |
| } |
| |
| /** |
| * @return {?UI.Widget} |
| */ |
| mainWidget() { |
| return this._mainWidget; |
| } |
| |
| /** |
| * @return {?UI.Widget} |
| */ |
| sidebarWidget() { |
| return this._sidebarWidget; |
| } |
| |
| /** |
| * @override |
| * @param {!UI.Widget} widget |
| */ |
| childWasDetached(widget) { |
| if (this._detaching) { |
| return; |
| } |
| if (this._mainWidget === widget) { |
| this._mainWidget = null; |
| } |
| if (this._sidebarWidget === widget) { |
| this._sidebarWidget = null; |
| } |
| this.invalidateConstraints(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isSidebarSecond() { |
| return this._secondIsSidebar; |
| } |
| |
| enableShowModeSaving() { |
| this._shouldSaveShowMode = true; |
| this._restoreAndApplyShowModeFromSettings(); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| showMode() { |
| return this._showMode; |
| } |
| |
| /** |
| * @param {boolean} secondIsSidebar |
| */ |
| setSecondIsSidebar(secondIsSidebar) { |
| if (secondIsSidebar === this._secondIsSidebar) { |
| return; |
| } |
| this._secondIsSidebar = secondIsSidebar; |
| if (!this._mainWidget || !this._mainWidget.shouldHideOnDetach()) { |
| if (secondIsSidebar) { |
| this.contentElement.insertBefore(this._mainElement, this._sidebarElement); |
| } else { |
| this.contentElement.insertBefore(this._mainElement, this._resizerElement); |
| } |
| } else if (!this._sidebarWidget || !this._sidebarWidget.shouldHideOnDetach()) { |
| if (secondIsSidebar) { |
| this.contentElement.insertBefore(this._sidebarElement, this._resizerElement); |
| } else { |
| this.contentElement.insertBefore(this._sidebarElement, this._mainElement); |
| } |
| } else { |
| console.error('Could not swap split widget side. Both children widgets contain iframes.'); |
| this._secondIsSidebar = !secondIsSidebar; |
| } |
| } |
| |
| /** |
| * @return {?string} |
| */ |
| sidebarSide() { |
| if (this._showMode !== ShowMode.Both) { |
| return null; |
| } |
| return this._isVertical ? (this._secondIsSidebar ? 'right' : 'left') : (this._secondIsSidebar ? 'bottom' : 'top'); |
| } |
| |
| /** |
| * @return {!Element} |
| */ |
| resizerElement() { |
| return this._resizerElement; |
| } |
| |
| /** |
| * @param {boolean=} animate |
| */ |
| hideMain(animate) { |
| this._showOnly(this._sidebarWidget, this._mainWidget, this._sidebarElement, this._mainElement, animate); |
| this._updateShowMode(ShowMode.OnlySidebar); |
| } |
| |
| /** |
| * @param {boolean=} animate |
| */ |
| hideSidebar(animate) { |
| this._showOnly(this._mainWidget, this._sidebarWidget, this._mainElement, this._sidebarElement, animate); |
| this._updateShowMode(ShowMode.OnlyMain); |
| } |
| |
| /** |
| * @param {boolean} minimized |
| */ |
| setSidebarMinimized(minimized) { |
| this._sidebarMinimized = minimized; |
| this.invalidateConstraints(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isSidebarMinimized() { |
| return this._sidebarMinimized; |
| } |
| |
| /** |
| * @param {?UI.Widget} sideToShow |
| * @param {?UI.Widget} sideToHide |
| * @param {!Element} shadowToShow |
| * @param {!Element} shadowToHide |
| * @param {boolean=} animate |
| */ |
| _showOnly(sideToShow, sideToHide, shadowToShow, shadowToHide, animate) { |
| this._cancelAnimation(); |
| |
| /** |
| * @this {SplitWidget} |
| */ |
| function callback() { |
| if (sideToShow) { |
| // Make sure main is first in the children list. |
| if (sideToShow === this._mainWidget) { |
| this._mainWidget.show(this.element, this._sidebarWidget ? this._sidebarWidget.element : null); |
| } else { |
| this._sidebarWidget.show(this.element); |
| } |
| } |
| if (sideToHide) { |
| this._detaching = true; |
| sideToHide.detach(); |
| this._detaching = false; |
| } |
| |
| this._resizerElement.classList.add('hidden'); |
| shadowToShow.classList.remove('hidden'); |
| shadowToShow.classList.add('maximized'); |
| shadowToHide.classList.add('hidden'); |
| shadowToHide.classList.remove('maximized'); |
| this._removeAllLayoutProperties(); |
| this.doResize(); |
| this._showFinishedForTest(); |
| } |
| |
| if (animate) { |
| this._animate(true, callback.bind(this)); |
| } else { |
| callback.call(this); |
| } |
| |
| this._sidebarSizeDIP = -1; |
| this.setResizable(false); |
| } |
| |
| _showFinishedForTest() { |
| // This method is sniffed in tests. |
| } |
| |
| _removeAllLayoutProperties() { |
| this._sidebarElement.style.removeProperty('flexBasis'); |
| |
| this._mainElement.style.removeProperty('width'); |
| this._mainElement.style.removeProperty('height'); |
| this._sidebarElement.style.removeProperty('width'); |
| this._sidebarElement.style.removeProperty('height'); |
| |
| this._resizerElement.style.removeProperty('left'); |
| this._resizerElement.style.removeProperty('right'); |
| this._resizerElement.style.removeProperty('top'); |
| this._resizerElement.style.removeProperty('bottom'); |
| |
| this._resizerElement.style.removeProperty('margin-left'); |
| this._resizerElement.style.removeProperty('margin-right'); |
| this._resizerElement.style.removeProperty('margin-top'); |
| this._resizerElement.style.removeProperty('margin-bottom'); |
| } |
| |
| /** |
| * @param {boolean=} animate |
| */ |
| showBoth(animate) { |
| if (this._showMode === ShowMode.Both) { |
| animate = false; |
| } |
| |
| this._cancelAnimation(); |
| this._mainElement.classList.remove('maximized', 'hidden'); |
| this._sidebarElement.classList.remove('maximized', 'hidden'); |
| this._resizerElement.classList.remove('hidden'); |
| this.setResizable(true); |
| |
| // Make sure main is the first in the children list. |
| this.suspendInvalidations(); |
| if (this._sidebarWidget) { |
| this._sidebarWidget.show(this.element); |
| } |
| if (this._mainWidget) { |
| this._mainWidget.show(this.element, this._sidebarWidget ? this._sidebarWidget.element : null); |
| } |
| this.resumeInvalidations(); |
| // Order widgets in DOM properly. |
| this.setSecondIsSidebar(this._secondIsSidebar); |
| |
| this._sidebarSizeDIP = -1; |
| this._updateShowMode(ShowMode.Both); |
| this._updateLayout(animate); |
| } |
| |
| /** |
| * @param {boolean} resizable |
| */ |
| setResizable(resizable) { |
| this._resizerWidget.setEnabled(resizable); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| isResizable() { |
| return this._resizerWidget.isEnabled(); |
| } |
| |
| /** |
| * @param {number} size |
| */ |
| setSidebarSize(size) { |
| const sizeDIP = UI.zoomManager.cssToDIP(size); |
| this._savedSidebarSizeDIP = sizeDIP; |
| this._saveSetting(); |
| this._innerSetSidebarSizeDIP(sizeDIP, false, true); |
| } |
| |
| /** |
| * @return {number} |
| */ |
| sidebarSize() { |
| const sizeDIP = Math.max(0, this._sidebarSizeDIP); |
| return UI.zoomManager.dipToCSS(sizeDIP); |
| } |
| |
| /** |
| * Returns total size in DIP. |
| * @return {number} |
| */ |
| _totalSizeDIP() { |
| if (!this._totalSizeCSS) { |
| this._totalSizeCSS = this._isVertical ? this.contentElement.offsetWidth : this.contentElement.offsetHeight; |
| this._totalSizeOtherDimensionCSS = |
| this._isVertical ? this.contentElement.offsetHeight : this.contentElement.offsetWidth; |
| } |
| return UI.zoomManager.cssToDIP(this._totalSizeCSS); |
| } |
| |
| /** |
| * @param {string} showMode |
| */ |
| _updateShowMode(showMode) { |
| this._showMode = showMode; |
| this._saveShowModeToSettings(); |
| this._updateShowHideSidebarButton(); |
| this.dispatchEventToListeners(SplitWidget.Events.ShowModeChanged, showMode); |
| this.invalidateConstraints(); |
| } |
| |
| /** |
| * @param {number} sizeDIP |
| * @param {boolean} animate |
| * @param {boolean=} userAction |
| */ |
| _innerSetSidebarSizeDIP(sizeDIP, animate, userAction) { |
| if (this._showMode !== ShowMode.Both || !this.isShowing()) { |
| return; |
| } |
| |
| sizeDIP = this._applyConstraints(sizeDIP, userAction); |
| if (this._sidebarSizeDIP === sizeDIP) { |
| return; |
| } |
| |
| if (!this._resizerElementSize) { |
| this._resizerElementSize = |
| this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight; |
| } |
| |
| // Invalidate layout below. |
| |
| this._removeAllLayoutProperties(); |
| |
| // this._totalSizeDIP is available below since we successfully applied constraints. |
| const roundSizeCSS = Math.round(UI.zoomManager.dipToCSS(sizeDIP)); |
| const sidebarSizeValue = roundSizeCSS + 'px'; |
| const mainSizeValue = (this._totalSizeCSS - roundSizeCSS) + 'px'; |
| this._sidebarElement.style.flexBasis = sidebarSizeValue; |
| |
| // Make both sides relayout boundaries. |
| if (this._isVertical) { |
| this._sidebarElement.style.width = sidebarSizeValue; |
| this._mainElement.style.width = mainSizeValue; |
| this._sidebarElement.style.height = this._totalSizeOtherDimensionCSS + 'px'; |
| this._mainElement.style.height = this._totalSizeOtherDimensionCSS + 'px'; |
| } else { |
| this._sidebarElement.style.height = sidebarSizeValue; |
| this._mainElement.style.height = mainSizeValue; |
| this._sidebarElement.style.width = this._totalSizeOtherDimensionCSS + 'px'; |
| this._mainElement.style.width = this._totalSizeOtherDimensionCSS + 'px'; |
| } |
| |
| // Position resizer. |
| if (this._isVertical) { |
| if (this._secondIsSidebar) { |
| this._resizerElement.style.right = sidebarSizeValue; |
| this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + 'px'; |
| } else { |
| this._resizerElement.style.left = sidebarSizeValue; |
| this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + 'px'; |
| } |
| } else { |
| if (this._secondIsSidebar) { |
| this._resizerElement.style.bottom = sidebarSizeValue; |
| this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + 'px'; |
| } else { |
| this._resizerElement.style.top = sidebarSizeValue; |
| this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + 'px'; |
| } |
| } |
| |
| this._sidebarSizeDIP = sizeDIP; |
| |
| // Force layout. |
| |
| if (animate) { |
| this._animate(false); |
| } else { |
| // No need to recalculate this._sidebarSizeDIP and this._totalSizeDIP again. |
| this.doResize(); |
| this.dispatchEventToListeners(SplitWidget.Events.SidebarSizeChanged, this.sidebarSize()); |
| } |
| } |
| |
| /** |
| * @param {boolean} reverse |
| * @param {function()=} callback |
| */ |
| _animate(reverse, callback) { |
| const animationTime = 50; |
| this._animationCallback = callback || null; |
| |
| let animatedMarginPropertyName; |
| if (this._isVertical) { |
| animatedMarginPropertyName = this._secondIsSidebar ? 'margin-right' : 'margin-left'; |
| } else { |
| animatedMarginPropertyName = this._secondIsSidebar ? 'margin-bottom' : 'margin-top'; |
| } |
| |
| const marginFrom = reverse ? '0' : '-' + UI.zoomManager.dipToCSS(this._sidebarSizeDIP) + 'px'; |
| const marginTo = reverse ? '-' + UI.zoomManager.dipToCSS(this._sidebarSizeDIP) + 'px' : '0'; |
| |
| // This order of things is important. |
| // 1. Resize main element early and force layout. |
| this.contentElement.style.setProperty(animatedMarginPropertyName, marginFrom); |
| if (!reverse) { |
| suppressUnused(this._mainElement.offsetWidth); |
| suppressUnused(this._sidebarElement.offsetWidth); |
| } |
| |
| // 2. Issue onresize to the sidebar element, its size won't change. |
| if (!reverse) { |
| this._sidebarWidget.doResize(); |
| } |
| |
| // 3. Configure and run animation |
| this.contentElement.style.setProperty('transition', animatedMarginPropertyName + ' ' + animationTime + 'ms linear'); |
| |
| const boundAnimationFrame = animationFrame.bind(this); |
| let startTime; |
| /** |
| * @this {SplitWidget} |
| */ |
| function animationFrame() { |
| this._animationFrameHandle = 0; |
| |
| if (!startTime) { |
| // Kick animation on first frame. |
| this.contentElement.style.setProperty(animatedMarginPropertyName, marginTo); |
| startTime = window.performance.now(); |
| } else if (window.performance.now() < startTime + animationTime) { |
| // Process regular animation frame. |
| if (this._mainWidget) { |
| this._mainWidget.doResize(); |
| } |
| } else { |
| // Complete animation. |
| this._cancelAnimation(); |
| if (this._mainWidget) { |
| this._mainWidget.doResize(); |
| } |
| this.dispatchEventToListeners(SplitWidget.Events.SidebarSizeChanged, this.sidebarSize()); |
| return; |
| } |
| this._animationFrameHandle = this.contentElement.window().requestAnimationFrame(boundAnimationFrame); |
| } |
| this._animationFrameHandle = this.contentElement.window().requestAnimationFrame(boundAnimationFrame); |
| } |
| |
| _cancelAnimation() { |
| this.contentElement.style.removeProperty('margin-top'); |
| this.contentElement.style.removeProperty('margin-right'); |
| this.contentElement.style.removeProperty('margin-bottom'); |
| this.contentElement.style.removeProperty('margin-left'); |
| this.contentElement.style.removeProperty('transition'); |
| |
| if (this._animationFrameHandle) { |
| this.contentElement.window().cancelAnimationFrame(this._animationFrameHandle); |
| this._animationFrameHandle = 0; |
| } |
| if (this._animationCallback) { |
| this._animationCallback(); |
| this._animationCallback = null; |
| } |
| } |
| |
| /** |
| * @param {number} sidebarSize |
| * @param {boolean=} userAction |
| * @return {number} |
| */ |
| _applyConstraints(sidebarSize, userAction) { |
| const totalSize = this._totalSizeDIP(); |
| const zoomFactor = this._constraintsInDip ? 1 : UI.zoomManager.zoomFactor(); |
| |
| let constraints = this._sidebarWidget ? this._sidebarWidget.constraints() : new UI.Constraints(); |
| let minSidebarSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height; |
| if (!minSidebarSize) { |
| minSidebarSize = MinPadding; |
| } |
| minSidebarSize *= zoomFactor; |
| if (this._sidebarMinimized) { |
| sidebarSize = minSidebarSize; |
| } |
| |
| let preferredSidebarSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height; |
| if (!preferredSidebarSize) { |
| preferredSidebarSize = MinPadding; |
| } |
| preferredSidebarSize *= zoomFactor; |
| // Allow sidebar to be less than preferred by explicit user action. |
| if (sidebarSize < preferredSidebarSize) { |
| preferredSidebarSize = Math.max(sidebarSize, minSidebarSize); |
| } |
| preferredSidebarSize += zoomFactor; // 1 css pixel for splitter border. |
| |
| constraints = this._mainWidget ? this._mainWidget.constraints() : new UI.Constraints(); |
| let minMainSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height; |
| if (!minMainSize) { |
| minMainSize = MinPadding; |
| } |
| minMainSize *= zoomFactor; |
| |
| let preferredMainSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height; |
| if (!preferredMainSize) { |
| preferredMainSize = MinPadding; |
| } |
| preferredMainSize *= zoomFactor; |
| const savedMainSize = this.isVertical() ? this._savedVerticalMainSize : this._savedHorizontalMainSize; |
| if (savedMainSize !== null) { |
| preferredMainSize = Math.min(preferredMainSize, savedMainSize * zoomFactor); |
| } |
| if (userAction) { |
| preferredMainSize = minMainSize; |
| } |
| |
| // Enough space for preferred. |
| const totalPreferred = preferredMainSize + preferredSidebarSize; |
| if (totalPreferred <= totalSize) { |
| return Number.constrain(sidebarSize, preferredSidebarSize, totalSize - preferredMainSize); |
| } |
| |
| // Enough space for minimum. |
| if (minMainSize + minSidebarSize <= totalSize) { |
| const delta = totalPreferred - totalSize; |
| const sidebarDelta = delta * preferredSidebarSize / totalPreferred; |
| sidebarSize = preferredSidebarSize - sidebarDelta; |
| return Number.constrain(sidebarSize, minSidebarSize, totalSize - minMainSize); |
| } |
| |
| // Not enough space even for minimum sizes. |
| return Math.max(0, totalSize - minMainSize); |
| } |
| |
| /** |
| * @override |
| */ |
| wasShown() { |
| this._forceUpdateLayout(); |
| UI.zoomManager.addEventListener(UI.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this); |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| UI.zoomManager.removeEventListener(UI.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this); |
| } |
| |
| /** |
| * @override |
| */ |
| onResize() { |
| this._updateLayout(); |
| } |
| |
| /** |
| * @override |
| */ |
| onLayout() { |
| this._updateLayout(); |
| } |
| |
| /** |
| * @override |
| * @return {!UI.Constraints} |
| */ |
| calculateConstraints() { |
| if (this._showMode === ShowMode.OnlyMain) { |
| return this._mainWidget ? this._mainWidget.constraints() : new UI.Constraints(); |
| } |
| if (this._showMode === ShowMode.OnlySidebar) { |
| return this._sidebarWidget ? this._sidebarWidget.constraints() : new UI.Constraints(); |
| } |
| |
| let mainConstraints = this._mainWidget ? this._mainWidget.constraints() : new UI.Constraints(); |
| let sidebarConstraints = this._sidebarWidget ? this._sidebarWidget.constraints() : new UI.Constraints(); |
| const min = MinPadding; |
| if (this._isVertical) { |
| mainConstraints = mainConstraints.widthToMax(min).addWidth(1); // 1 for splitter |
| sidebarConstraints = sidebarConstraints.widthToMax(min); |
| return mainConstraints.addWidth(sidebarConstraints).heightToMax(sidebarConstraints); |
| } else { |
| mainConstraints = mainConstraints.heightToMax(min).addHeight(1); // 1 for splitter |
| sidebarConstraints = sidebarConstraints.heightToMax(min); |
| return mainConstraints.widthToMax(sidebarConstraints).addHeight(sidebarConstraints); |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onResizeStart(event) { |
| this._resizeStartSizeDIP = this._sidebarSizeDIP; |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onResizeUpdate(event) { |
| const offset = event.data.currentPosition - event.data.startPosition; |
| const offsetDIP = UI.zoomManager.cssToDIP(offset); |
| const newSizeDIP = |
| this._secondIsSidebar ? this._resizeStartSizeDIP - offsetDIP : this._resizeStartSizeDIP + offsetDIP; |
| const constrainedSizeDIP = this._applyConstraints(newSizeDIP, true); |
| this._savedSidebarSizeDIP = constrainedSizeDIP; |
| this._saveSetting(); |
| this._innerSetSidebarSizeDIP(constrainedSizeDIP, false, true); |
| if (this.isVertical()) { |
| this._savedVerticalMainSize = this._totalSizeDIP() - this._sidebarSizeDIP; |
| } else { |
| this._savedHorizontalMainSize = this._totalSizeDIP() - this._sidebarSizeDIP; |
| } |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onResizeEnd(event) { |
| this._resizeStartSizeDIP = 0; |
| } |
| |
| /** |
| * @param {boolean=} noSplitter |
| */ |
| hideDefaultResizer(noSplitter) { |
| this.uninstallResizer(this._resizerElement); |
| this._sidebarElement.classList.toggle('no-default-splitter', !!noSplitter); |
| } |
| |
| /** |
| * @param {!Element} resizerElement |
| */ |
| installResizer(resizerElement) { |
| this._resizerWidget.addElement(resizerElement); |
| } |
| |
| /** |
| * @param {!Element} resizerElement |
| */ |
| uninstallResizer(resizerElement) { |
| this._resizerWidget.removeElement(resizerElement); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| hasCustomResizer() { |
| const elements = this._resizerWidget.elements(); |
| return elements.length > 1 || (elements.length === 1 && elements[0] !== this._resizerElement); |
| } |
| |
| /** |
| * @param {!Element} resizer |
| * @param {boolean} on |
| */ |
| toggleResizer(resizer, on) { |
| if (on) { |
| this.installResizer(resizer); |
| } else { |
| this.uninstallResizer(resizer); |
| } |
| } |
| |
| /** |
| * @return {?SplitWidget.SettingForOrientation} |
| */ |
| _settingForOrientation() { |
| const state = this._setting ? this._setting.get() : {}; |
| return this._isVertical ? state.vertical : state.horizontal; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| _preferredSidebarSizeDIP() { |
| let size = this._savedSidebarSizeDIP; |
| if (!size) { |
| size = this._isVertical ? this._defaultSidebarWidth : this._defaultSidebarHeight; |
| // If we have default value in percents, calculate it on first use. |
| if (0 < size && size < 1) { |
| size *= this._totalSizeDIP(); |
| } |
| } |
| return size; |
| } |
| |
| _restoreSidebarSizeFromSettings() { |
| const settingForOrientation = this._settingForOrientation(); |
| this._savedSidebarSizeDIP = settingForOrientation ? settingForOrientation.size : 0; |
| } |
| |
| _restoreAndApplyShowModeFromSettings() { |
| const orientationState = this._settingForOrientation(); |
| this._savedShowMode = orientationState && orientationState.showMode ? orientationState.showMode : this._showMode; |
| this._showMode = this._savedShowMode; |
| |
| switch (this._savedShowMode) { |
| case ShowMode.Both: |
| this.showBoth(); |
| break; |
| case ShowMode.OnlyMain: |
| this.hideSidebar(); |
| break; |
| case ShowMode.OnlySidebar: |
| this.hideMain(); |
| break; |
| } |
| } |
| |
| _saveShowModeToSettings() { |
| this._savedShowMode = this._showMode; |
| this._saveSetting(); |
| } |
| |
| _saveSetting() { |
| if (!this._setting) { |
| return; |
| } |
| const state = this._setting.get(); |
| const orientationState = (this._isVertical ? state.vertical : state.horizontal) || {}; |
| |
| orientationState.size = this._savedSidebarSizeDIP; |
| if (this._shouldSaveShowMode) { |
| orientationState.showMode = this._savedShowMode; |
| } |
| |
| if (this._isVertical) { |
| state.vertical = orientationState; |
| } else { |
| state.horizontal = orientationState; |
| } |
| this._setting.set(state); |
| } |
| |
| _forceUpdateLayout() { |
| // Force layout even if sidebar size does not change. |
| this._sidebarSizeDIP = -1; |
| this._updateLayout(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onZoomChanged(event) { |
| this._forceUpdateLayout(); |
| } |
| |
| /** |
| * @param {string} title |
| * @return {!UI.ToolbarButton} |
| */ |
| createShowHideSidebarButton(title) { |
| this._showHideSidebarButtonTitle = title; |
| this._showHideSidebarButton = new UI.ToolbarButton('', ''); |
| this._showHideSidebarButton.addEventListener(UI.ToolbarButton.Events.Click, buttonClicked, this); |
| this._updateShowHideSidebarButton(); |
| |
| /** |
| * @param {!Common.Event} event |
| * @this {SplitWidget} |
| */ |
| function buttonClicked(event) { |
| if (this._showMode !== ShowMode.Both) { |
| this.showBoth(true); |
| } else { |
| this.hideSidebar(true); |
| } |
| } |
| |
| return this._showHideSidebarButton; |
| } |
| |
| _updateShowHideSidebarButton() { |
| if (!this._showHideSidebarButton) { |
| return; |
| } |
| const sidebarHidden = this._showMode === ShowMode.OnlyMain; |
| let glyph = ''; |
| if (sidebarHidden) { |
| glyph = this.isVertical() ? |
| (this.isSidebarSecond() ? 'largeicon-show-right-sidebar' : 'largeicon-show-left-sidebar') : |
| (this.isSidebarSecond() ? 'largeicon-show-bottom-sidebar' : 'largeicon-show-top-sidebar'); |
| } else { |
| glyph = this.isVertical() ? |
| (this.isSidebarSecond() ? 'largeicon-hide-right-sidebar' : 'largeicon-hide-left-sidebar') : |
| (this.isSidebarSecond() ? 'largeicon-hide-bottom-sidebar' : 'largeicon-hide-top-sidebar'); |
| } |
| this._showHideSidebarButton.setGlyph(glyph); |
| this._showHideSidebarButton.setTitle( |
| sidebarHidden ? Common.UIString('Show %s', this._showHideSidebarButtonTitle) : |
| Common.UIString('Hide %s', this._showHideSidebarButtonTitle)); |
| } |
| } |
| |
| export const ShowMode = { |
| Both: 'Both', |
| OnlyMain: 'OnlyMain', |
| OnlySidebar: 'OnlySidebar' |
| }; |
| |
| /** @enum {symbol} */ |
| export const Events = { |
| SidebarSizeChanged: Symbol('SidebarSizeChanged'), |
| ShowModeChanged: Symbol('ShowModeChanged') |
| }; |
| |
| const MinPadding = 20; |
| |
| /* Legacy exported object*/ |
| self.UI = self.UI || {}; |
| |
| /* Legacy exported object*/ |
| UI = UI || {}; |
| |
| /** @constructor */ |
| UI.SplitWidget = SplitWidget; |
| |
| UI.SplitWidget.ShowMode = ShowMode; |
| |
| /** @enum {symbol} */ |
| UI.SplitWidget.Events = Events; |
| |
| /** @typedef {{showMode: string, size: number}} */ |
| UI.SplitWidget.SettingForOrientation; |