Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / SplitView.js
1 /*
2  * Copyright (C) 2012 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  * 1. Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *
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
14  * distribution.
15  *
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.
27  */
28
29 /**
30  * @constructor
31  * @extends {WebInspector.View}
32  * @param {boolean} isVertical
33  * @param {boolean} secondIsSidebar
34  * @param {string=} sidebarSizeSettingName
35  * @param {number=} defaultSidebarWidth
36  * @param {number=} defaultSidebarHeight
37  */
38 WebInspector.SplitView = function(isVertical, secondIsSidebar, sidebarSizeSettingName, defaultSidebarWidth, defaultSidebarHeight)
39 {
40     WebInspector.View.call(this);
41
42     this.registerRequiredCSS("splitView.css");
43     this.element.classList.add("split-view");
44
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
49
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
54
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);
59     } else {
60         this._sidebarView.show(this.element);
61         this._mainView.show(this.element);
62     }
63
64     this._onDragStartBound = this._onDragStart.bind(this);
65     this._resizerElements = [];
66
67     this._resizable = true;
68
69     this._savedSidebarWidth = defaultSidebarWidth || 200;
70     this._savedSidebarHeight = defaultSidebarHeight || this._savedSidebarWidth;
71
72     if (0 < this._savedSidebarWidth && this._savedSidebarWidth < 1 &&
73         0 < this._savedSidebarHeight && this._savedSidebarHeight < 1)
74         this._useFraction = true;
75
76     this._sidebarSizeSettingName = sidebarSizeSettingName;
77
78     this.setSecondIsSidebar(secondIsSidebar);
79
80     this._innerSetVertical(isVertical);
81
82     // Should be called after isVertical has the right value.
83     this.installResizer(this._resizerElement);
84 }
85
86 WebInspector.SplitView.Events = {
87     SidebarSizeChanged: "SidebarSizeChanged"
88 }
89
90 WebInspector.SplitView.prototype = {
91     /**
92      * @return {boolean}
93      */
94     isVertical: function()
95     {
96         return this._isVertical;
97     },
98
99     /**
100      * @param {boolean} isVertical
101      */
102     setVertical: function(isVertical)
103     {
104         if (this._isVertical === isVertical)
105             return;
106
107         this._innerSetVertical(isVertical);
108
109         if (this.isShowing())
110             this._updateLayout();
111
112         for (var i = 0; i < this._resizerElements.length; ++i)
113             this._resizerElements[i].style.setProperty("cursor", this._isVertical ? "ew-resize" : "ns-resize");
114     },
115
116     /**
117      * @param {boolean} isVertical
118      */
119     _innerSetVertical: function(isVertical)
120     {
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;
126     },
127
128     /**
129      * @param {boolean=} animate
130      */
131     _updateLayout: function(animate)
132     {
133         delete this._totalSize; // Lazy update.
134         this._innerSetSidebarSize(this._lastSidebarSize(), false, animate);
135     },
136
137     /**
138      * @return {!Element}
139      */
140     mainElement: function()
141     {
142         return this._mainElement;
143     },
144
145     /**
146      * @return {!Element}
147      */
148     sidebarElement: function()
149     {
150         return this._sidebarElement;
151     },
152
153     /**
154      * @return {boolean}
155      */
156     isSidebarSecond: function()
157     {
158         return this._secondIsSidebar;
159     },
160
161     /**
162      * @param {boolean} secondIsSidebar
163      */
164     setSecondIsSidebar: function(secondIsSidebar)
165     {
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);
170
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);
175         } else {
176             if (this._mainElement.parentElement && this._mainElement.nextSibling)
177                 this.element.appendChild(this._mainElement);
178         }
179
180         this._secondIsSidebar = secondIsSidebar;
181     },
182
183     /**
184      * @return {string}
185      */
186     sidebarSide: function()
187     {
188         return this._isVertical ?
189             (this._secondIsSidebar ? "right" : "left") :
190             (this._secondIsSidebar ? "bottom" : "top");
191     },
192
193     /**
194      * @return {number}
195      */
196     desiredSidebarSize: function()
197     {
198         return this._lastSidebarSize();
199     },
200
201     /**
202      * @return {!Element}
203      */
204     resizerElement: function()
205     {
206         return this._resizerElement;
207     },
208
209     /**
210      * @param {boolean=} animate
211      */
212     hideMain: function(animate)
213     {
214         this._showOnly(this._sidebarView, this._mainView, animate);
215     },
216
217     /**
218      * @param {boolean=} animate
219      */
220     hideSidebar: function(animate)
221     {
222         this._showOnly(this._mainView, this._sidebarView, animate);
223     },
224
225     /**
226      * @param {!WebInspector.View} sideToShow
227      * @param {!WebInspector.View} sideToHide
228      * @param {boolean=} animate
229      */
230     _showOnly: function(sideToShow, sideToHide, animate)
231     {
232         this._cancelAnimation();
233
234         /**
235          * @this {WebInspector.SplitView}
236          */
237         function callback()
238         {
239             sideToShow.show(this.element);
240             sideToHide.detach();
241             sideToShow.element.classList.add("maximized");
242             sideToHide.element.classList.remove("maximized");
243             this._removeAllLayoutProperties();
244         }
245
246         if (animate) {
247             this._animate(true, callback.bind(this));
248         } else {
249             callback.call(this);
250             this.doResize();
251         }
252
253         this._isShowingOne = true;
254         this._sidebarSize = -1;
255         this.setResizable(false);
256     },
257
258     _removeAllLayoutProperties: function()
259     {
260         this._sidebarElement.style.removeProperty("flexBasis");
261
262         this._resizerElement.style.removeProperty("left");
263         this._resizerElement.style.removeProperty("right");
264         this._resizerElement.style.removeProperty("top");
265         this._resizerElement.style.removeProperty("bottom");
266
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");
271     },
272
273     /**
274      * @param {boolean=} animate
275      */
276     showBoth: function(animate)
277     {
278         if (!this._isShowingOne)
279             animate = false;
280
281         this._cancelAnimation();
282         this._mainElement.classList.remove("maximized");
283         this._sidebarElement.classList.remove("maximized");
284
285         this._mainView.show(this.element);
286         this._sidebarView.show(this.element);
287         // Order views in DOM properly.
288         this.setSecondIsSidebar(this._secondIsSidebar);
289
290         this._isShowingOne = false;
291         this._sidebarSize = -1;
292         this.setResizable(true);
293         this._updateLayout(animate);
294     },
295
296     /**
297      * @param {boolean} resizable
298      */
299     setResizable: function(resizable)
300     {
301         this._resizable = resizable;
302         this._resizerElement.enableStyleClass("hidden", !resizable);
303     },
304
305     /**
306      * @param {number} size
307      * @param {boolean=} ignoreConstraints
308      */
309     setSidebarSize: function(size, ignoreConstraints)
310     {
311         this._innerSetSidebarSize(size, ignoreConstraints);
312         this._saveSidebarSize();
313     },
314
315     /**
316      * @return {number}
317      */
318     sidebarSize: function()
319     {
320         return Math.max(0, this._sidebarSize);
321     },
322
323     /**
324      * @return {number}
325      */
326     totalSize: function()
327     {
328         if (!this._totalSize)
329             this._totalSize = this._isVertical ? this.element.offsetWidth : this.element.offsetHeight;
330         return this._totalSize;
331     },
332
333     /**
334      * @param {number} size
335      * @param {boolean=} ignoreConstraints
336      * @param {boolean=} animate
337      */
338     _innerSetSidebarSize: function(size, ignoreConstraints, animate)
339     {
340         if (this._isShowingOne) {
341             this._sidebarSize = size;
342             return;
343         }
344
345         if (!ignoreConstraints)
346             size = this._applyConstraints(size);
347         if (this._sidebarSize === size)
348             return;
349
350         if (size < 0) {
351             // Never apply bad values, fix it upon onResize instead.
352             return;
353         }
354
355         this._removeAllLayoutProperties();
356
357         var sizeValue;
358         if (this._useFraction)
359             sizeValue = (size / this.totalSize()) * 100 + "%";
360         else
361             sizeValue = size + "px";
362
363         this.sidebarElement().style.flexBasis = sizeValue;
364
365         if (!this._resizerElementSize)
366             this._resizerElementSize = this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight;
367
368         // Position resizer.
369         if (this._isVertical) {
370             if (this._secondIsSidebar) {
371                 this._resizerElement.style.right = sizeValue;
372                 this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + "px";
373             } else {
374                 this._resizerElement.style.left = sizeValue;
375                 this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + "px";
376             }
377         } else {
378             if (this._secondIsSidebar) {
379                 this._resizerElement.style.bottom = sizeValue;
380                 this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + "px";
381             } else {
382                 this._resizerElement.style.top = sizeValue;
383                 this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + "px";
384             }
385         }
386
387         this._sidebarSize = size;
388
389         if (animate) {
390             this._animate(false);
391         } else {
392             // No need to recalculate this._sidebarSize and this._totalSize again.
393             this.doResize();
394             this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize());
395         }
396     },
397
398     /**
399      * @param {boolean} reverse
400      * @param {function()=} callback
401      */
402     _animate: function(reverse, callback)
403     {
404         var animationTime = 50;
405         this._animationCallback = callback;
406
407         var animatedMarginPropertyName;
408         if (this._isVertical)
409             animatedMarginPropertyName = this._secondIsSidebar ? "margin-right" : "margin-left";
410         else
411             animatedMarginPropertyName = this._secondIsSidebar ? "margin-bottom" : "margin-top";
412
413         var marginFrom = reverse ? "0" : "-" + this._sidebarSize + "px";
414         var marginTo = reverse ? "-" + this._sidebarSize + "px" : "0";
415
416         // This order of things is important.
417         // 1. Resize main element early and force layout.
418         this.element.style.setProperty(animatedMarginPropertyName, marginFrom);
419         if (!reverse) {
420             suppressUnused(this._mainElement.offsetWidth);
421             suppressUnused(this._sidebarElement.offsetWidth);
422         }
423
424         // 2. Issue onresize to the sidebar element, its size won't change.
425         if (!reverse)
426             this._sidebarView.doResize();
427
428         // 3. Configure and run animation
429         this.element.style.setProperty("transition", animatedMarginPropertyName + " " + animationTime + "ms linear");
430
431         var boundAnimationFrame;
432         var startTime;
433         /**
434          * @this {WebInspector.SplitView}
435          */
436         function animationFrame()
437         {
438             delete this._animationFrameHandle;
439
440             if (!startTime) {
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();
447             } else {
448                 // Complete animation.
449                 this._cancelAnimation();
450                 this._mainView.doResize();
451                 this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize());
452                 return;
453             }
454             this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame);
455         }
456         boundAnimationFrame = animationFrame.bind(this);
457         this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame);
458     },
459
460     _cancelAnimation: function()
461     {
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");
467
468         if (this._animationFrameHandle) {
469             window.cancelAnimationFrame(this._animationFrameHandle);
470             delete this._animationFrameHandle;
471         }
472         if (this._animationCallback) {
473             this._animationCallback();
474             delete this._animationCallback;
475         }
476     },
477
478     /**
479      * @param {number=} minWidth
480      * @param {number=} minHeight
481      */
482     setSidebarElementConstraints: function(minWidth, minHeight)
483     {
484         if (typeof minWidth === "number")
485             this._minimumSidebarWidth = minWidth;
486         if (typeof minHeight === "number")
487             this._minimumSidebarHeight = minHeight;
488     },
489
490     /**
491      * @param {number=} minWidth
492      * @param {number=} minHeight
493      */
494     setMainElementConstraints: function(minWidth, minHeight)
495     {
496         if (typeof minWidth === "number")
497             this._minimumMainWidth = minWidth;
498         if (typeof minHeight === "number")
499             this._minimumMainHeight = minHeight;
500     },
501
502     /**
503      * @param {number} sidebarSize
504      * @return {number}
505      */
506     _applyConstraints: function(sidebarSize)
507     {
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);
516         }
517         if (typeof minimumSiderbarSizeContraint !== "number")
518             from = Math.max(from, minPadding);
519
520         var minimumMainSizeConstraint = this.isVertical() ? this._minimumMainWidth : this._minimumMainHeight;
521         var minMainSize = minimumMainSizeConstraint || 0;
522         var toInPercents = false;
523         if (minMainSize && minMainSize < 1) {
524             toInPercents = true;
525             minMainSize = Math.round(totalSize * minMainSize);
526         }
527         if (typeof minimumMainSizeConstraint !== "number")
528             minMainSize = Math.max(minMainSize, minPadding);
529
530         var to = totalSize - minMainSize;
531         if (from <= to)
532             return Number.constrain(sidebarSize, from, to);
533
534         // Respect fixed constraints over percents. This will, for example, shrink
535         // the sidebar to its minimum size when possible.
536         if (!fromInPercents && !toInPercents)
537             return -1;
538         if (toInPercents && sidebarSize >= from && from < totalSize)
539             return from;
540         if (fromInPercents && sidebarSize <= to && to < totalSize)
541             return to;
542
543         return -1;
544     },
545
546     wasShown: function()
547     {
548         this._updateLayout();
549     },
550
551     onResize: function()
552     {
553         this._updateLayout();
554     },
555
556     /**
557      * @param {!MouseEvent} event
558      * @return {boolean}
559      */
560     _startResizerDragging: function(event)
561     {
562         if (!this._resizable)
563             return false;
564
565         this._saveSidebarSize();
566         this._dragOffset = (this._secondIsSidebar ? this.totalSize() - this._sidebarSize : this._sidebarSize) - (this._isVertical ? event.pageX : event.pageY);
567         return true;
568     },
569
570     /**
571      * @param {!MouseEvent} event
572      */
573     _resizerDragging: function(event)
574     {
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();
579     },
580
581     /**
582      * @param {!MouseEvent} event
583      */
584     _endResizerDragging: function(event)
585     {
586         delete this._dragOffset;
587         this._saveSidebarSize();
588     },
589
590     hideDefaultResizer: function()
591     {
592         this.element.classList.add("split-view-no-resizer");
593     },
594
595     /**
596      * @param {!Element} resizerElement
597      */
598     installResizer: function(resizerElement)
599     {
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);
604     },
605
606     /**
607      * @param {!Element} resizerElement
608      */
609     uninstallResizer: function(resizerElement)
610     {
611         resizerElement.removeEventListener("mousedown", this._onDragStartBound, false);
612         resizerElement.style.removeProperty("cursor");
613         this._resizerElements.remove(resizerElement);
614     },
615
616     /**
617      * @param {?Event} event
618      */
619     _onDragStart: function(event)
620     {
621         // Only handle drags of the nodes specified.
622         if (this._resizerElements.indexOf(event.target) === -1)
623             return;
624         WebInspector.elementDragStart(this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), this._isVertical ? "ew-resize" : "ns-resize", event);
625     },
626
627     /**
628      * @return {?WebInspector.Setting}
629      */
630     _sizeSetting: function()
631     {
632         if (!this._sidebarSizeSettingName)
633             return null;
634
635         var settingName = this._sidebarSizeSettingName + (this._isVertical ? "" : "H");
636         if (!WebInspector.settings[settingName])
637             WebInspector.settings[settingName] = WebInspector.settings.createSetting(settingName, undefined);
638
639         return WebInspector.settings[settingName];
640     },
641
642     /**
643      * @return {number}
644      */
645     _lastSidebarSize: function()
646     {
647         var sizeSetting = this._sizeSetting();
648         var size = sizeSetting ? sizeSetting.get() : 0;
649         if (!size)
650              size = this._isVertical ? this._savedSidebarWidth : this._savedSidebarHeight;
651         if (this._useFraction)
652             size *= this.totalSize();
653         return size;
654     },
655
656     _saveSidebarSize: function()
657     {
658         var size = this._sidebarSize;
659         if (size < 0)
660             return;
661
662         if (this._useFraction)
663             size /= this.totalSize();
664
665         if (this._isVertical)
666             this._savedSidebarWidth = size;
667         else
668             this._savedSidebarHeight = size;
669
670         var sizeSetting = this._sizeSetting();
671         if (sizeSetting)
672             sizeSetting.set(size);
673     },
674
675     __proto__: WebInspector.View.prototype
676 }