2 * Copyright (C) 2012 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 * @extends {WebInspector.View}
32 * @param {boolean} isVertical
33 * @param {boolean} secondIsSidebar
34 * @param {string=} sidebarSizeSettingName
35 * @param {number=} defaultSidebarWidth
36 * @param {number=} defaultSidebarHeight
38 WebInspector.SplitView = function(isVertical, secondIsSidebar, sidebarSizeSettingName, defaultSidebarWidth, defaultSidebarHeight)
40 WebInspector.View.call(this);
42 this.registerRequiredCSS("splitView.css");
43 this.element.classList.add("split-view");
45 this._mainView = new WebInspector.View();
46 this._mainView.makeLayoutBoundary();
47 this._mainElement = this._mainView.element;
48 this._mainElement.className = "split-view-contents scroll-target split-view-main vbox"; // Override
50 this._sidebarView = new WebInspector.View();
51 this._sidebarView.makeLayoutBoundary();
52 this._sidebarElement = this._sidebarView.element;
53 this._sidebarElement.className = "split-view-contents scroll-target split-view-sidebar vbox"; // Override
55 this._resizerElement = this.element.createChild("div", "split-view-resizer");
56 if (secondIsSidebar) {
57 this._mainView.show(this.element);
58 this._sidebarView.show(this.element);
60 this._sidebarView.show(this.element);
61 this._mainView.show(this.element);
64 this._onDragStartBound = this._onDragStart.bind(this);
65 this._resizerElements = [];
67 this._resizable = true;
69 this._savedSidebarWidth = defaultSidebarWidth || 200;
70 this._savedSidebarHeight = defaultSidebarHeight || this._savedSidebarWidth;
72 if (0 < this._savedSidebarWidth && this._savedSidebarWidth < 1 &&
73 0 < this._savedSidebarHeight && this._savedSidebarHeight < 1)
74 this._useFraction = true;
76 this._sidebarSizeSettingName = sidebarSizeSettingName;
78 this.setSecondIsSidebar(secondIsSidebar);
80 this._innerSetVertical(isVertical);
82 // Should be called after isVertical has the right value.
83 this.installResizer(this._resizerElement);
86 WebInspector.SplitView.Events = {
87 SidebarSizeChanged: "SidebarSizeChanged"
90 WebInspector.SplitView.prototype = {
94 isVertical: function()
96 return this._isVertical;
100 * @param {boolean} isVertical
102 setVertical: function(isVertical)
104 if (this._isVertical === isVertical)
107 this._innerSetVertical(isVertical);
109 if (this.isShowing())
110 this._updateLayout();
112 for (var i = 0; i < this._resizerElements.length; ++i)
113 this._resizerElements[i].style.setProperty("cursor", this._isVertical ? "ew-resize" : "ns-resize");
117 * @param {boolean} isVertical
119 _innerSetVertical: function(isVertical)
121 this.element.classList.remove(this._isVertical ? "hbox" : "vbox");
122 this._isVertical = isVertical;
123 this.element.classList.add(this._isVertical ? "hbox" : "vbox");
124 delete this._resizerElementSize;
125 this._sidebarSize = -1;
129 * @param {boolean=} animate
131 _updateLayout: function(animate)
133 delete this._totalSize; // Lazy update.
134 this._innerSetSidebarSize(this._lastSidebarSize(), false, animate);
140 mainElement: function()
142 return this._mainElement;
148 sidebarElement: function()
150 return this._sidebarElement;
156 isSidebarSecond: function()
158 return this._secondIsSidebar;
162 * @param {boolean} secondIsSidebar
164 setSecondIsSidebar: function(secondIsSidebar)
166 this._mainElement.classList.toggle("split-view-contents-first", secondIsSidebar);
167 this._mainElement.classList.toggle("split-view-contents-second", !secondIsSidebar);
168 this._sidebarElement.classList.toggle("split-view-contents-first", !secondIsSidebar);
169 this._sidebarElement.classList.toggle("split-view-contents-second", secondIsSidebar);
171 // Make sure second is last in the children array.
172 if (secondIsSidebar) {
173 if (this._sidebarElement.parentElement && this._sidebarElement.nextSibling)
174 this.element.appendChild(this._sidebarElement);
176 if (this._mainElement.parentElement && this._mainElement.nextSibling)
177 this.element.appendChild(this._mainElement);
180 this._secondIsSidebar = secondIsSidebar;
186 sidebarSide: function()
188 return this._isVertical ?
189 (this._secondIsSidebar ? "right" : "left") :
190 (this._secondIsSidebar ? "bottom" : "top");
196 desiredSidebarSize: function()
198 return this._lastSidebarSize();
204 resizerElement: function()
206 return this._resizerElement;
210 * @param {boolean=} animate
212 hideMain: function(animate)
214 this._showOnly(this._sidebarView, this._mainView, animate);
218 * @param {boolean=} animate
220 hideSidebar: function(animate)
222 this._showOnly(this._mainView, this._sidebarView, animate);
226 * @param {!WebInspector.View} sideToShow
227 * @param {!WebInspector.View} sideToHide
228 * @param {boolean=} animate
230 _showOnly: function(sideToShow, sideToHide, animate)
232 this._cancelAnimation();
235 * @this {WebInspector.SplitView}
239 sideToShow.show(this.element);
241 sideToShow.element.classList.add("maximized");
242 sideToHide.element.classList.remove("maximized");
243 this._removeAllLayoutProperties();
247 this._animate(true, callback.bind(this));
253 this._isShowingOne = true;
254 this._sidebarSize = -1;
255 this.setResizable(false);
258 _removeAllLayoutProperties: function()
260 this._sidebarElement.style.removeProperty("flexBasis");
262 this._resizerElement.style.removeProperty("left");
263 this._resizerElement.style.removeProperty("right");
264 this._resizerElement.style.removeProperty("top");
265 this._resizerElement.style.removeProperty("bottom");
267 this._resizerElement.style.removeProperty("margin-left");
268 this._resizerElement.style.removeProperty("margin-right");
269 this._resizerElement.style.removeProperty("margin-top");
270 this._resizerElement.style.removeProperty("margin-bottom");
274 * @param {boolean=} animate
276 showBoth: function(animate)
278 if (!this._isShowingOne)
281 this._cancelAnimation();
282 this._mainElement.classList.remove("maximized");
283 this._sidebarElement.classList.remove("maximized");
285 this._mainView.show(this.element);
286 this._sidebarView.show(this.element);
287 // Order views in DOM properly.
288 this.setSecondIsSidebar(this._secondIsSidebar);
290 this._isShowingOne = false;
291 this._sidebarSize = -1;
292 this.setResizable(true);
293 this._updateLayout(animate);
297 * @param {boolean} resizable
299 setResizable: function(resizable)
301 this._resizable = resizable;
302 this._resizerElement.enableStyleClass("hidden", !resizable);
306 * @param {number} size
307 * @param {boolean=} ignoreConstraints
309 setSidebarSize: function(size, ignoreConstraints)
311 this._innerSetSidebarSize(size, ignoreConstraints);
312 this._saveSidebarSize();
318 sidebarSize: function()
320 return Math.max(0, this._sidebarSize);
326 totalSize: function()
328 if (!this._totalSize)
329 this._totalSize = this._isVertical ? this.element.offsetWidth : this.element.offsetHeight;
330 return this._totalSize;
334 * @param {number} size
335 * @param {boolean=} ignoreConstraints
336 * @param {boolean=} animate
338 _innerSetSidebarSize: function(size, ignoreConstraints, animate)
340 if (this._isShowingOne) {
341 this._sidebarSize = size;
345 if (!ignoreConstraints)
346 size = this._applyConstraints(size);
347 if (this._sidebarSize === size)
351 // Never apply bad values, fix it upon onResize instead.
355 this._removeAllLayoutProperties();
358 if (this._useFraction)
359 sizeValue = (size / this.totalSize()) * 100 + "%";
361 sizeValue = size + "px";
363 this.sidebarElement().style.flexBasis = sizeValue;
365 if (!this._resizerElementSize)
366 this._resizerElementSize = this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight;
369 if (this._isVertical) {
370 if (this._secondIsSidebar) {
371 this._resizerElement.style.right = sizeValue;
372 this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + "px";
374 this._resizerElement.style.left = sizeValue;
375 this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + "px";
378 if (this._secondIsSidebar) {
379 this._resizerElement.style.bottom = sizeValue;
380 this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + "px";
382 this._resizerElement.style.top = sizeValue;
383 this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + "px";
387 this._sidebarSize = size;
390 this._animate(false);
392 // No need to recalculate this._sidebarSize and this._totalSize again.
394 this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize());
399 * @param {boolean} reverse
400 * @param {function()=} callback
402 _animate: function(reverse, callback)
404 var animationTime = 50;
405 this._animationCallback = callback;
407 var animatedMarginPropertyName;
408 if (this._isVertical)
409 animatedMarginPropertyName = this._secondIsSidebar ? "margin-right" : "margin-left";
411 animatedMarginPropertyName = this._secondIsSidebar ? "margin-bottom" : "margin-top";
413 var marginFrom = reverse ? "0" : "-" + this._sidebarSize + "px";
414 var marginTo = reverse ? "-" + this._sidebarSize + "px" : "0";
416 // This order of things is important.
417 // 1. Resize main element early and force layout.
418 this.element.style.setProperty(animatedMarginPropertyName, marginFrom);
420 suppressUnused(this._mainElement.offsetWidth);
421 suppressUnused(this._sidebarElement.offsetWidth);
424 // 2. Issue onresize to the sidebar element, its size won't change.
426 this._sidebarView.doResize();
428 // 3. Configure and run animation
429 this.element.style.setProperty("transition", animatedMarginPropertyName + " " + animationTime + "ms linear");
431 var boundAnimationFrame;
434 * @this {WebInspector.SplitView}
436 function animationFrame()
438 delete this._animationFrameHandle;
441 // Kick animation on first frame.
442 this.element.style.setProperty(animatedMarginPropertyName, marginTo);
443 startTime = window.performance.now();
444 } else if (window.performance.now() < startTime + animationTime) {
445 // Process regular animation frame.
446 this._mainView.doResize();
448 // Complete animation.
449 this._cancelAnimation();
450 this._mainView.doResize();
451 this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize());
454 this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame);
456 boundAnimationFrame = animationFrame.bind(this);
457 this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame);
460 _cancelAnimation: function()
462 this.element.style.removeProperty("margin-top");
463 this.element.style.removeProperty("margin-right");
464 this.element.style.removeProperty("margin-bottom");
465 this.element.style.removeProperty("margin-left");
466 this.element.style.removeProperty("transition");
468 if (this._animationFrameHandle) {
469 window.cancelAnimationFrame(this._animationFrameHandle);
470 delete this._animationFrameHandle;
472 if (this._animationCallback) {
473 this._animationCallback();
474 delete this._animationCallback;
479 * @param {number=} minWidth
480 * @param {number=} minHeight
482 setSidebarElementConstraints: function(minWidth, minHeight)
484 if (typeof minWidth === "number")
485 this._minimumSidebarWidth = minWidth;
486 if (typeof minHeight === "number")
487 this._minimumSidebarHeight = minHeight;
491 * @param {number=} minWidth
492 * @param {number=} minHeight
494 setMainElementConstraints: function(minWidth, minHeight)
496 if (typeof minWidth === "number")
497 this._minimumMainWidth = minWidth;
498 if (typeof minHeight === "number")
499 this._minimumMainHeight = minHeight;
503 * @param {number} sidebarSize
506 _applyConstraints: function(sidebarSize)
508 const minPadding = 20;
509 var totalSize = this.totalSize();
510 var minimumSiderbarSizeContraint = this.isVertical() ? this._minimumSidebarWidth : this._minimumSidebarHeight;
511 var from = minimumSiderbarSizeContraint || 0;
512 var fromInPercents = false;
513 if (from && from < 1) {
514 fromInPercents = true;
515 from = Math.round(totalSize * from);
517 if (typeof minimumSiderbarSizeContraint !== "number")
518 from = Math.max(from, minPadding);
520 var minimumMainSizeConstraint = this.isVertical() ? this._minimumMainWidth : this._minimumMainHeight;
521 var minMainSize = minimumMainSizeConstraint || 0;
522 var toInPercents = false;
523 if (minMainSize && minMainSize < 1) {
525 minMainSize = Math.round(totalSize * minMainSize);
527 if (typeof minimumMainSizeConstraint !== "number")
528 minMainSize = Math.max(minMainSize, minPadding);
530 var to = totalSize - minMainSize;
532 return Number.constrain(sidebarSize, from, to);
534 // Respect fixed constraints over percents. This will, for example, shrink
535 // the sidebar to its minimum size when possible.
536 if (!fromInPercents && !toInPercents)
538 if (toInPercents && sidebarSize >= from && from < totalSize)
540 if (fromInPercents && sidebarSize <= to && to < totalSize)
548 this._updateLayout();
553 this._updateLayout();
557 * @param {!MouseEvent} event
560 _startResizerDragging: function(event)
562 if (!this._resizable)
565 this._saveSidebarSize();
566 this._dragOffset = (this._secondIsSidebar ? this.totalSize() - this._sidebarSize : this._sidebarSize) - (this._isVertical ? event.pageX : event.pageY);
571 * @param {!MouseEvent} event
573 _resizerDragging: function(event)
575 var newOffset = (this._isVertical ? event.pageX : event.pageY) + this._dragOffset;
576 var newSize = (this._secondIsSidebar ? this.totalSize() - newOffset : newOffset);
577 this.setSidebarSize(newSize);
578 event.preventDefault();
582 * @param {!MouseEvent} event
584 _endResizerDragging: function(event)
586 delete this._dragOffset;
587 this._saveSidebarSize();
590 hideDefaultResizer: function()
592 this.element.classList.add("split-view-no-resizer");
596 * @param {!Element} resizerElement
598 installResizer: function(resizerElement)
600 resizerElement.addEventListener("mousedown", this._onDragStartBound, false);
601 resizerElement.style.setProperty("cursor", this._isVertical ? "ew-resize" : "ns-resize");
602 if (this._resizerElements.indexOf(resizerElement) === -1)
603 this._resizerElements.push(resizerElement);
607 * @param {!Element} resizerElement
609 uninstallResizer: function(resizerElement)
611 resizerElement.removeEventListener("mousedown", this._onDragStartBound, false);
612 resizerElement.style.removeProperty("cursor");
613 this._resizerElements.remove(resizerElement);
617 * @param {?Event} event
619 _onDragStart: function(event)
621 // Only handle drags of the nodes specified.
622 if (this._resizerElements.indexOf(event.target) === -1)
624 WebInspector.elementDragStart(this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), this._isVertical ? "ew-resize" : "ns-resize", event);
628 * @return {?WebInspector.Setting}
630 _sizeSetting: function()
632 if (!this._sidebarSizeSettingName)
635 var settingName = this._sidebarSizeSettingName + (this._isVertical ? "" : "H");
636 if (!WebInspector.settings[settingName])
637 WebInspector.settings[settingName] = WebInspector.settings.createSetting(settingName, undefined);
639 return WebInspector.settings[settingName];
645 _lastSidebarSize: function()
647 var sizeSetting = this._sizeSetting();
648 var size = sizeSetting ? sizeSetting.get() : 0;
650 size = this._isVertical ? this._savedSidebarWidth : this._savedSidebarHeight;
651 if (this._useFraction)
652 size *= this.totalSize();
656 _saveSidebarSize: function()
658 var size = this._sidebarSize;
662 if (this._useFraction)
663 size /= this.totalSize();
665 if (this._isVertical)
666 this._savedSidebarWidth = size;
668 this._savedSidebarHeight = size;
670 var sizeSetting = this._sizeSetting();
672 sizeSetting.set(size);
675 __proto__: WebInspector.View.prototype