blob: f0b55ed6c54bcad82fc036780b3231b50c39a303 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
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 * @unrestricted
31 */
32UI.SplitWidget = class extends UI.Widget {
33 /**
34 * @param {boolean} isVertical
35 * @param {boolean} secondIsSidebar
36 * @param {string=} settingName
37 * @param {number=} defaultSidebarWidth
38 * @param {number=} defaultSidebarHeight
39 * @param {boolean=} constraintsInDip
40 */
41 constructor(isVertical, secondIsSidebar, settingName, defaultSidebarWidth, defaultSidebarHeight, constraintsInDip) {
42 super(true);
43 this.element.classList.add('split-widget');
44 this.registerRequiredCSS('ui/splitWidget.css');
45
46 this.contentElement.classList.add('shadow-split-widget');
47 this._mainElement =
48 this.contentElement.createChild('div', 'shadow-split-widget-contents shadow-split-widget-main vbox');
49 this._mainElement.createChild('content').select = '.insertion-point-main';
50 this._sidebarElement =
51 this.contentElement.createChild('div', 'shadow-split-widget-contents shadow-split-widget-sidebar vbox');
52 this._sidebarElement.createChild('content').select = '.insertion-point-sidebar';
53 this._resizerElement = this.contentElement.createChild('div', 'shadow-split-widget-resizer');
54 this._resizerElementSize = null;
55
56 this._resizerWidget = new UI.SimpleResizerWidget();
57 this._resizerWidget.setEnabled(true);
58 this._resizerWidget.addEventListener(UI.ResizerWidget.Events.ResizeStart, this._onResizeStart, this);
59 this._resizerWidget.addEventListener(UI.ResizerWidget.Events.ResizeUpdate, this._onResizeUpdate, this);
60 this._resizerWidget.addEventListener(UI.ResizerWidget.Events.ResizeEnd, this._onResizeEnd, this);
61
62 this._defaultSidebarWidth = defaultSidebarWidth || 200;
63 this._defaultSidebarHeight = defaultSidebarHeight || this._defaultSidebarWidth;
64 this._constraintsInDip = !!constraintsInDip;
65 this._resizeStartSizeDIP = 0;
66 this._setting = settingName ? Common.settings.createSetting(settingName, {}) : null;
67
68 this._totalSizeCSS = 0;
69 this._totalSizeOtherDimensionCSS = 0;
70 /** @type {?UI.Widget} */
71 this._mainWidget = null;
72 /** @type {?UI.Widget} */
73 this._sidebarWidget = null;
74 this._animationFrameHandle = 0;
75 /** @type {?function()} */
76 this._animationCallback = null;
77 this._showHideSidebarButtonTitle = '';
78 /** @type {?UI.ToolbarButton} */
79 this._showHideSidebarButton = null;
80 this._isVertical = false;
81 this._sidebarMinimized = false;
82 this._detaching = false;
83 this._sidebarSizeDIP = -1;
84 this._savedSidebarSizeDIP = this._sidebarSizeDIP;
85 this._secondIsSidebar = false;
86 this._shouldSaveShowMode = false;
87 /** @type {?number} */
88 this._savedVerticalMainSize = null;
89 /** @type {?number} */
90 this._savedHorizontalMainSize = null;
91
92 this.setSecondIsSidebar(secondIsSidebar);
93
94 this._innerSetVertical(isVertical);
95 this._showMode = UI.SplitWidget.ShowMode.Both;
96 this._savedShowMode = this._showMode;
97
98 // Should be called after isVertical has the right value.
99 this.installResizer(this._resizerElement);
100 }
101
102 /**
103 * @return {boolean}
104 */
105 isVertical() {
106 return this._isVertical;
107 }
108
109 /**
110 * @param {boolean} isVertical
111 */
112 setVertical(isVertical) {
113 if (this._isVertical === isVertical)
114 return;
115
116 this._innerSetVertical(isVertical);
117
118 if (this.isShowing())
119 this._updateLayout();
120 }
121
122 /**
123 * @param {boolean} isVertical
124 */
125 _innerSetVertical(isVertical) {
126 this.contentElement.classList.toggle('vbox', !isVertical);
127 this.contentElement.classList.toggle('hbox', isVertical);
128 this._isVertical = isVertical;
129
130 this._resizerElementSize = null;
131 this._sidebarSizeDIP = -1;
132 this._restoreSidebarSizeFromSettings();
133 if (this._shouldSaveShowMode)
134 this._restoreAndApplyShowModeFromSettings();
135 this._updateShowHideSidebarButton();
136 // FIXME: reverse SplitWidget.isVertical meaning.
137 this._resizerWidget.setVertical(!isVertical);
138 this.invalidateConstraints();
139 }
140
141 /**
142 * @param {boolean=} animate
143 */
144 _updateLayout(animate) {
145 this._totalSizeCSS = 0; // Lazy update.
146 this._totalSizeOtherDimensionCSS = 0;
147
148 // Remove properties that might affect total size calculation.
149 this._mainElement.style.removeProperty('width');
150 this._mainElement.style.removeProperty('height');
151 this._sidebarElement.style.removeProperty('width');
152 this._sidebarElement.style.removeProperty('height');
153
154 this._innerSetSidebarSizeDIP(this._preferredSidebarSizeDIP(), !!animate);
155 }
156
157 /**
158 * @param {!UI.Widget} widget
159 */
160 setMainWidget(widget) {
161 if (this._mainWidget === widget)
162 return;
163 this.suspendInvalidations();
164 if (this._mainWidget)
165 this._mainWidget.detach();
166 this._mainWidget = widget;
167 if (widget) {
168 widget.element.classList.add('insertion-point-main');
169 widget.element.classList.remove('insertion-point-sidebar');
170 if (this._showMode === UI.SplitWidget.ShowMode.OnlyMain || this._showMode === UI.SplitWidget.ShowMode.Both)
171 widget.show(this.element);
172 }
173 this.resumeInvalidations();
174 }
175
176 /**
177 * @param {!UI.Widget} widget
178 */
179 setSidebarWidget(widget) {
180 if (this._sidebarWidget === widget)
181 return;
182 this.suspendInvalidations();
183 if (this._sidebarWidget)
184 this._sidebarWidget.detach();
185 this._sidebarWidget = widget;
186 if (widget) {
187 widget.element.classList.add('insertion-point-sidebar');
188 widget.element.classList.remove('insertion-point-main');
189 if (this._showMode === UI.SplitWidget.ShowMode.OnlySidebar || this._showMode === UI.SplitWidget.ShowMode.Both)
190 widget.show(this.element);
191 }
192 this.resumeInvalidations();
193 }
194
195 /**
196 * @return {?UI.Widget}
197 */
198 mainWidget() {
199 return this._mainWidget;
200 }
201
202 /**
203 * @return {?UI.Widget}
204 */
205 sidebarWidget() {
206 return this._sidebarWidget;
207 }
208
209 /**
210 * @override
211 * @param {!UI.Widget} widget
212 */
213 childWasDetached(widget) {
214 if (this._detaching)
215 return;
216 if (this._mainWidget === widget)
217 this._mainWidget = null;
218 if (this._sidebarWidget === widget)
219 this._sidebarWidget = null;
220 this.invalidateConstraints();
221 }
222
223 /**
224 * @return {boolean}
225 */
226 isSidebarSecond() {
227 return this._secondIsSidebar;
228 }
229
230 enableShowModeSaving() {
231 this._shouldSaveShowMode = true;
232 this._restoreAndApplyShowModeFromSettings();
233 }
234
235 /**
236 * @return {string}
237 */
238 showMode() {
239 return this._showMode;
240 }
241
242 /**
243 * @param {boolean} secondIsSidebar
244 */
245 setSecondIsSidebar(secondIsSidebar) {
246 this.contentElement.classList.toggle('shadow-split-widget-first-is-sidebar', !secondIsSidebar);
247 this._secondIsSidebar = secondIsSidebar;
248 }
249
250 /**
251 * @return {?string}
252 */
253 sidebarSide() {
254 if (this._showMode !== UI.SplitWidget.ShowMode.Both)
255 return null;
256 return this._isVertical ? (this._secondIsSidebar ? 'right' : 'left') : (this._secondIsSidebar ? 'bottom' : 'top');
257 }
258
259 /**
260 * @return {!Element}
261 */
262 resizerElement() {
263 return this._resizerElement;
264 }
265
266 /**
267 * @param {boolean=} animate
268 */
269 hideMain(animate) {
270 this._showOnly(this._sidebarWidget, this._mainWidget, this._sidebarElement, this._mainElement, animate);
271 this._updateShowMode(UI.SplitWidget.ShowMode.OnlySidebar);
272 }
273
274 /**
275 * @param {boolean=} animate
276 */
277 hideSidebar(animate) {
278 this._showOnly(this._mainWidget, this._sidebarWidget, this._mainElement, this._sidebarElement, animate);
279 this._updateShowMode(UI.SplitWidget.ShowMode.OnlyMain);
280 }
281
282 /**
283 * @param {boolean} minimized
284 */
285 setSidebarMinimized(minimized) {
286 this._sidebarMinimized = minimized;
287 this.invalidateConstraints();
288 }
289
290 /**
291 * @return {boolean}
292 */
293 isSidebarMinimized() {
294 return this._sidebarMinimized;
295 }
296
297 /**
298 * @param {?UI.Widget} sideToShow
299 * @param {?UI.Widget} sideToHide
300 * @param {!Element} shadowToShow
301 * @param {!Element} shadowToHide
302 * @param {boolean=} animate
303 */
304 _showOnly(sideToShow, sideToHide, shadowToShow, shadowToHide, animate) {
305 this._cancelAnimation();
306
307 /**
308 * @this {UI.SplitWidget}
309 */
310 function callback() {
311 if (sideToShow) {
312 // Make sure main is first in the children list.
313 if (sideToShow === this._mainWidget)
314 this._mainWidget.show(this.element, this._sidebarWidget ? this._sidebarWidget.element : null);
315 else
316 this._sidebarWidget.show(this.element);
317 }
318 if (sideToHide) {
319 this._detaching = true;
320 sideToHide.detach();
321 this._detaching = false;
322 }
323
324 this._resizerElement.classList.add('hidden');
325 shadowToShow.classList.remove('hidden');
326 shadowToShow.classList.add('maximized');
327 shadowToHide.classList.add('hidden');
328 shadowToHide.classList.remove('maximized');
329 this._removeAllLayoutProperties();
330 this.doResize();
331 this._showFinishedForTest();
332 }
333
334 if (animate)
335 this._animate(true, callback.bind(this));
336 else
337 callback.call(this);
338
339 this._sidebarSizeDIP = -1;
340 this.setResizable(false);
341 }
342
343 _showFinishedForTest() {
344 // This method is sniffed in tests.
345 }
346
347 _removeAllLayoutProperties() {
348 this._sidebarElement.style.removeProperty('flexBasis');
349
350 this._mainElement.style.removeProperty('width');
351 this._mainElement.style.removeProperty('height');
352 this._sidebarElement.style.removeProperty('width');
353 this._sidebarElement.style.removeProperty('height');
354
355 this._resizerElement.style.removeProperty('left');
356 this._resizerElement.style.removeProperty('right');
357 this._resizerElement.style.removeProperty('top');
358 this._resizerElement.style.removeProperty('bottom');
359
360 this._resizerElement.style.removeProperty('margin-left');
361 this._resizerElement.style.removeProperty('margin-right');
362 this._resizerElement.style.removeProperty('margin-top');
363 this._resizerElement.style.removeProperty('margin-bottom');
364 }
365
366 /**
367 * @param {boolean=} animate
368 */
369 showBoth(animate) {
370 if (this._showMode === UI.SplitWidget.ShowMode.Both)
371 animate = false;
372
373 this._cancelAnimation();
374 this._mainElement.classList.remove('maximized', 'hidden');
375 this._sidebarElement.classList.remove('maximized', 'hidden');
376 this._resizerElement.classList.remove('hidden');
377 this.setResizable(true);
378
379 // Make sure main is the first in the children list.
380 this.suspendInvalidations();
381 if (this._sidebarWidget)
382 this._sidebarWidget.show(this.element);
383 if (this._mainWidget)
384 this._mainWidget.show(this.element, this._sidebarWidget ? this._sidebarWidget.element : null);
385 this.resumeInvalidations();
386 // Order widgets in DOM properly.
387 this.setSecondIsSidebar(this._secondIsSidebar);
388
389 this._sidebarSizeDIP = -1;
390 this._updateShowMode(UI.SplitWidget.ShowMode.Both);
391 this._updateLayout(animate);
392 }
393
394 /**
395 * @param {boolean} resizable
396 */
397 setResizable(resizable) {
398 this._resizerWidget.setEnabled(resizable);
399 }
400
401 /**
402 * @return {boolean}
403 */
404 isResizable() {
405 return this._resizerWidget.isEnabled();
406 }
407
408 /**
409 * @param {number} size
410 */
411 setSidebarSize(size) {
412 const sizeDIP = UI.zoomManager.cssToDIP(size);
413 this._savedSidebarSizeDIP = sizeDIP;
414 this._saveSetting();
415 this._innerSetSidebarSizeDIP(sizeDIP, false, true);
416 }
417
418 /**
419 * @return {number}
420 */
421 sidebarSize() {
422 const sizeDIP = Math.max(0, this._sidebarSizeDIP);
423 return UI.zoomManager.dipToCSS(sizeDIP);
424 }
425
426 /**
427 * Returns total size in DIP.
428 * @return {number}
429 */
430 _totalSizeDIP() {
431 if (!this._totalSizeCSS) {
432 this._totalSizeCSS = this._isVertical ? this.contentElement.offsetWidth : this.contentElement.offsetHeight;
433 this._totalSizeOtherDimensionCSS =
434 this._isVertical ? this.contentElement.offsetHeight : this.contentElement.offsetWidth;
435 }
436 return UI.zoomManager.cssToDIP(this._totalSizeCSS);
437 }
438
439 /**
440 * @param {string} showMode
441 */
442 _updateShowMode(showMode) {
443 this._showMode = showMode;
444 this._saveShowModeToSettings();
445 this._updateShowHideSidebarButton();
446 this.dispatchEventToListeners(UI.SplitWidget.Events.ShowModeChanged, showMode);
447 this.invalidateConstraints();
448 }
449
450 /**
451 * @param {number} sizeDIP
452 * @param {boolean} animate
453 * @param {boolean=} userAction
454 */
455 _innerSetSidebarSizeDIP(sizeDIP, animate, userAction) {
456 if (this._showMode !== UI.SplitWidget.ShowMode.Both || !this.isShowing())
457 return;
458
459 sizeDIP = this._applyConstraints(sizeDIP, userAction);
460 if (this._sidebarSizeDIP === sizeDIP)
461 return;
462
463 if (!this._resizerElementSize) {
464 this._resizerElementSize =
465 this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight;
466 }
467
468 // Invalidate layout below.
469
470 this._removeAllLayoutProperties();
471
472 // this._totalSizeDIP is available below since we successfully applied constraints.
473 const roundSizeCSS = Math.round(UI.zoomManager.dipToCSS(sizeDIP));
474 const sidebarSizeValue = roundSizeCSS + 'px';
475 const mainSizeValue = (this._totalSizeCSS - roundSizeCSS) + 'px';
476 this._sidebarElement.style.flexBasis = sidebarSizeValue;
477
478 // Make both sides relayout boundaries.
479 if (this._isVertical) {
480 this._sidebarElement.style.width = sidebarSizeValue;
481 this._mainElement.style.width = mainSizeValue;
482 this._sidebarElement.style.height = this._totalSizeOtherDimensionCSS + 'px';
483 this._mainElement.style.height = this._totalSizeOtherDimensionCSS + 'px';
484 } else {
485 this._sidebarElement.style.height = sidebarSizeValue;
486 this._mainElement.style.height = mainSizeValue;
487 this._sidebarElement.style.width = this._totalSizeOtherDimensionCSS + 'px';
488 this._mainElement.style.width = this._totalSizeOtherDimensionCSS + 'px';
489 }
490
491 // Position resizer.
492 if (this._isVertical) {
493 if (this._secondIsSidebar) {
494 this._resizerElement.style.right = sidebarSizeValue;
495 this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + 'px';
496 } else {
497 this._resizerElement.style.left = sidebarSizeValue;
498 this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + 'px';
499 }
500 } else {
501 if (this._secondIsSidebar) {
502 this._resizerElement.style.bottom = sidebarSizeValue;
503 this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + 'px';
504 } else {
505 this._resizerElement.style.top = sidebarSizeValue;
506 this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + 'px';
507 }
508 }
509
510 this._sidebarSizeDIP = sizeDIP;
511
512 // Force layout.
513
514 if (animate) {
515 this._animate(false);
516 } else {
517 // No need to recalculate this._sidebarSizeDIP and this._totalSizeDIP again.
518 this.doResize();
519 this.dispatchEventToListeners(UI.SplitWidget.Events.SidebarSizeChanged, this.sidebarSize());
520 }
521 }
522
523 /**
524 * @param {boolean} reverse
525 * @param {function()=} callback
526 */
527 _animate(reverse, callback) {
528 const animationTime = 50;
529 this._animationCallback = callback || null;
530
531 let animatedMarginPropertyName;
532 if (this._isVertical)
533 animatedMarginPropertyName = this._secondIsSidebar ? 'margin-right' : 'margin-left';
534 else
535 animatedMarginPropertyName = this._secondIsSidebar ? 'margin-bottom' : 'margin-top';
536
537 const marginFrom = reverse ? '0' : '-' + UI.zoomManager.dipToCSS(this._sidebarSizeDIP) + 'px';
538 const marginTo = reverse ? '-' + UI.zoomManager.dipToCSS(this._sidebarSizeDIP) + 'px' : '0';
539
540 // This order of things is important.
541 // 1. Resize main element early and force layout.
542 this.contentElement.style.setProperty(animatedMarginPropertyName, marginFrom);
543 if (!reverse) {
544 suppressUnused(this._mainElement.offsetWidth);
545 suppressUnused(this._sidebarElement.offsetWidth);
546 }
547
548 // 2. Issue onresize to the sidebar element, its size won't change.
549 if (!reverse)
550 this._sidebarWidget.doResize();
551
552 // 3. Configure and run animation
553 this.contentElement.style.setProperty('transition', animatedMarginPropertyName + ' ' + animationTime + 'ms linear');
554
555 const boundAnimationFrame = animationFrame.bind(this);
556 let startTime;
557 /**
558 * @this {UI.SplitWidget}
559 */
560 function animationFrame() {
561 this._animationFrameHandle = 0;
562
563 if (!startTime) {
564 // Kick animation on first frame.
565 this.contentElement.style.setProperty(animatedMarginPropertyName, marginTo);
566 startTime = window.performance.now();
567 } else if (window.performance.now() < startTime + animationTime) {
568 // Process regular animation frame.
569 if (this._mainWidget)
570 this._mainWidget.doResize();
571 } else {
572 // Complete animation.
573 this._cancelAnimation();
574 if (this._mainWidget)
575 this._mainWidget.doResize();
576 this.dispatchEventToListeners(UI.SplitWidget.Events.SidebarSizeChanged, this.sidebarSize());
577 return;
578 }
579 this._animationFrameHandle = this.contentElement.window().requestAnimationFrame(boundAnimationFrame);
580 }
581 this._animationFrameHandle = this.contentElement.window().requestAnimationFrame(boundAnimationFrame);
582 }
583
584 _cancelAnimation() {
585 this.contentElement.style.removeProperty('margin-top');
586 this.contentElement.style.removeProperty('margin-right');
587 this.contentElement.style.removeProperty('margin-bottom');
588 this.contentElement.style.removeProperty('margin-left');
589 this.contentElement.style.removeProperty('transition');
590
591 if (this._animationFrameHandle) {
592 this.contentElement.window().cancelAnimationFrame(this._animationFrameHandle);
593 this._animationFrameHandle = 0;
594 }
595 if (this._animationCallback) {
596 this._animationCallback();
597 this._animationCallback = null;
598 }
599 }
600
601 /**
602 * @param {number} sidebarSize
603 * @param {boolean=} userAction
604 * @return {number}
605 */
606 _applyConstraints(sidebarSize, userAction) {
607 const totalSize = this._totalSizeDIP();
608 const zoomFactor = this._constraintsInDip ? 1 : UI.zoomManager.zoomFactor();
609
610 let constraints = this._sidebarWidget ? this._sidebarWidget.constraints() : new UI.Constraints();
611 let minSidebarSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height;
612 if (!minSidebarSize)
613 minSidebarSize = UI.SplitWidget.MinPadding;
614 minSidebarSize *= zoomFactor;
615 if (this._sidebarMinimized)
616 sidebarSize = minSidebarSize;
617
618 let preferredSidebarSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height;
619 if (!preferredSidebarSize)
620 preferredSidebarSize = UI.SplitWidget.MinPadding;
621 preferredSidebarSize *= zoomFactor;
622 // Allow sidebar to be less than preferred by explicit user action.
623 if (sidebarSize < preferredSidebarSize)
624 preferredSidebarSize = Math.max(sidebarSize, minSidebarSize);
625 preferredSidebarSize += zoomFactor; // 1 css pixel for splitter border.
626
627 constraints = this._mainWidget ? this._mainWidget.constraints() : new UI.Constraints();
628 let minMainSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height;
629 if (!minMainSize)
630 minMainSize = UI.SplitWidget.MinPadding;
631 minMainSize *= zoomFactor;
632
633 let preferredMainSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height;
634 if (!preferredMainSize)
635 preferredMainSize = UI.SplitWidget.MinPadding;
636 preferredMainSize *= zoomFactor;
637 const savedMainSize = this.isVertical() ? this._savedVerticalMainSize : this._savedHorizontalMainSize;
638 if (savedMainSize !== null)
639 preferredMainSize = Math.min(preferredMainSize, savedMainSize * zoomFactor);
640 if (userAction)
641 preferredMainSize = minMainSize;
642
643 // Enough space for preferred.
644 const totalPreferred = preferredMainSize + preferredSidebarSize;
645 if (totalPreferred <= totalSize)
646 return Number.constrain(sidebarSize, preferredSidebarSize, totalSize - preferredMainSize);
647
648 // Enough space for minimum.
649 if (minMainSize + minSidebarSize <= totalSize) {
650 const delta = totalPreferred - totalSize;
651 const sidebarDelta = delta * preferredSidebarSize / totalPreferred;
652 sidebarSize = preferredSidebarSize - sidebarDelta;
653 return Number.constrain(sidebarSize, minSidebarSize, totalSize - minMainSize);
654 }
655
656 // Not enough space even for minimum sizes.
657 return Math.max(0, totalSize - minMainSize);
658 }
659
660 /**
661 * @override
662 */
663 wasShown() {
664 this._forceUpdateLayout();
665 UI.zoomManager.addEventListener(UI.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this);
666 }
667
668 /**
669 * @override
670 */
671 willHide() {
672 UI.zoomManager.removeEventListener(UI.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this);
673 }
674
675 /**
676 * @override
677 */
678 onResize() {
679 this._updateLayout();
680 }
681
682 /**
683 * @override
684 */
685 onLayout() {
686 this._updateLayout();
687 }
688
689 /**
690 * @override
691 * @return {!UI.Constraints}
692 */
693 calculateConstraints() {
694 if (this._showMode === UI.SplitWidget.ShowMode.OnlyMain)
695 return this._mainWidget ? this._mainWidget.constraints() : new UI.Constraints();
696 if (this._showMode === UI.SplitWidget.ShowMode.OnlySidebar)
697 return this._sidebarWidget ? this._sidebarWidget.constraints() : new UI.Constraints();
698
699 let mainConstraints = this._mainWidget ? this._mainWidget.constraints() : new UI.Constraints();
700 let sidebarConstraints = this._sidebarWidget ? this._sidebarWidget.constraints() : new UI.Constraints();
701 const min = UI.SplitWidget.MinPadding;
702 if (this._isVertical) {
703 mainConstraints = mainConstraints.widthToMax(min).addWidth(1); // 1 for splitter
704 sidebarConstraints = sidebarConstraints.widthToMax(min);
705 return mainConstraints.addWidth(sidebarConstraints).heightToMax(sidebarConstraints);
706 } else {
707 mainConstraints = mainConstraints.heightToMax(min).addHeight(1); // 1 for splitter
708 sidebarConstraints = sidebarConstraints.heightToMax(min);
709 return mainConstraints.widthToMax(sidebarConstraints).addHeight(sidebarConstraints);
710 }
711 }
712
713 /**
714 * @param {!Common.Event} event
715 */
716 _onResizeStart(event) {
717 this._resizeStartSizeDIP = this._sidebarSizeDIP;
718 }
719
720 /**
721 * @param {!Common.Event} event
722 */
723 _onResizeUpdate(event) {
724 const offset = event.data.currentPosition - event.data.startPosition;
725 const offsetDIP = UI.zoomManager.cssToDIP(offset);
726 const newSizeDIP =
727 this._secondIsSidebar ? this._resizeStartSizeDIP - offsetDIP : this._resizeStartSizeDIP + offsetDIP;
728 const constrainedSizeDIP = this._applyConstraints(newSizeDIP, true);
729 this._savedSidebarSizeDIP = constrainedSizeDIP;
730 this._saveSetting();
731 this._innerSetSidebarSizeDIP(constrainedSizeDIP, false, true);
732 if (this.isVertical())
733 this._savedVerticalMainSize = this._totalSizeDIP() - this._sidebarSizeDIP;
734 else
735 this._savedHorizontalMainSize = this._totalSizeDIP() - this._sidebarSizeDIP;
736 }
737
738 /**
739 * @param {!Common.Event} event
740 */
741 _onResizeEnd(event) {
742 this._resizeStartSizeDIP = 0;
743 }
744
745 /**
746 * @param {boolean=} noSplitter
747 */
748 hideDefaultResizer(noSplitter) {
749 this.uninstallResizer(this._resizerElement);
Pavel Feldmanc9060ea2018-04-30 04:42:18750 this._sidebarElement.classList.toggle('no-default-splitter', !!noSplitter);
Blink Reformat4c46d092018-04-07 15:32:37751 }
752
753 /**
754 * @param {!Element} resizerElement
755 */
756 installResizer(resizerElement) {
757 this._resizerWidget.addElement(resizerElement);
758 }
759
760 /**
761 * @param {!Element} resizerElement
762 */
763 uninstallResizer(resizerElement) {
764 this._resizerWidget.removeElement(resizerElement);
765 }
766
767 /**
768 * @return {boolean}
769 */
770 hasCustomResizer() {
771 const elements = this._resizerWidget.elements();
772 return elements.length > 1 || (elements.length === 1 && elements[0] !== this._resizerElement);
773 }
774
775 /**
776 * @param {!Element} resizer
777 * @param {boolean} on
778 */
779 toggleResizer(resizer, on) {
780 if (on)
781 this.installResizer(resizer);
782 else
783 this.uninstallResizer(resizer);
784 }
785
786 /**
787 * @return {?UI.SplitWidget.SettingForOrientation}
788 */
789 _settingForOrientation() {
790 const state = this._setting ? this._setting.get() : {};
791 return this._isVertical ? state.vertical : state.horizontal;
792 }
793
794 /**
795 * @return {number}
796 */
797 _preferredSidebarSizeDIP() {
798 let size = this._savedSidebarSizeDIP;
799 if (!size) {
800 size = this._isVertical ? this._defaultSidebarWidth : this._defaultSidebarHeight;
801 // If we have default value in percents, calculate it on first use.
802 if (0 < size && size < 1)
803 size *= this._totalSizeDIP();
804 }
805 return size;
806 }
807
808 _restoreSidebarSizeFromSettings() {
809 const settingForOrientation = this._settingForOrientation();
810 this._savedSidebarSizeDIP = settingForOrientation ? settingForOrientation.size : 0;
811 }
812
813 _restoreAndApplyShowModeFromSettings() {
814 const orientationState = this._settingForOrientation();
815 this._savedShowMode = orientationState && orientationState.showMode ? orientationState.showMode : this._showMode;
816 this._showMode = this._savedShowMode;
817
818 switch (this._savedShowMode) {
819 case UI.SplitWidget.ShowMode.Both:
820 this.showBoth();
821 break;
822 case UI.SplitWidget.ShowMode.OnlyMain:
823 this.hideSidebar();
824 break;
825 case UI.SplitWidget.ShowMode.OnlySidebar:
826 this.hideMain();
827 break;
828 }
829 }
830
831 _saveShowModeToSettings() {
832 this._savedShowMode = this._showMode;
833 this._saveSetting();
834 }
835
836 _saveSetting() {
837 if (!this._setting)
838 return;
839 const state = this._setting.get();
840 const orientationState = (this._isVertical ? state.vertical : state.horizontal) || {};
841
842 orientationState.size = this._savedSidebarSizeDIP;
843 if (this._shouldSaveShowMode)
844 orientationState.showMode = this._savedShowMode;
845
846 if (this._isVertical)
847 state.vertical = orientationState;
848 else
849 state.horizontal = orientationState;
850 this._setting.set(state);
851 }
852
853 _forceUpdateLayout() {
854 // Force layout even if sidebar size does not change.
855 this._sidebarSizeDIP = -1;
856 this._updateLayout();
857 }
858
859 /**
860 * @param {!Common.Event} event
861 */
862 _onZoomChanged(event) {
863 this._forceUpdateLayout();
864 }
865
866 /**
867 * @param {string} title
868 * @return {!UI.ToolbarButton}
869 */
870 createShowHideSidebarButton(title) {
871 this._showHideSidebarButtonTitle = Common.UIString(title);
872 this._showHideSidebarButton = new UI.ToolbarButton('', '');
873 this._showHideSidebarButton.addEventListener(UI.ToolbarButton.Events.Click, buttonClicked, this);
874 this._updateShowHideSidebarButton();
875
876 /**
877 * @param {!Common.Event} event
878 * @this {UI.SplitWidget}
879 */
880 function buttonClicked(event) {
881 if (this._showMode !== UI.SplitWidget.ShowMode.Both)
882 this.showBoth(true);
883 else
884 this.hideSidebar(true);
885 }
886
887 return this._showHideSidebarButton;
888 }
889
890 _updateShowHideSidebarButton() {
891 if (!this._showHideSidebarButton)
892 return;
893 const sidebarHidden = this._showMode === UI.SplitWidget.ShowMode.OnlyMain;
894 let glyph = '';
895 if (sidebarHidden) {
896 glyph = this.isVertical() ?
897 (this.isSidebarSecond() ? 'largeicon-show-right-sidebar' : 'largeicon-show-left-sidebar') :
898 (this.isSidebarSecond() ? 'largeicon-show-bottom-sidebar' : 'largeicon-show-top-sidebar');
899 } else {
900 glyph = this.isVertical() ?
901 (this.isSidebarSecond() ? 'largeicon-hide-right-sidebar' : 'largeicon-hide-left-sidebar') :
902 (this.isSidebarSecond() ? 'largeicon-hide-bottom-sidebar' : 'largeicon-hide-top-sidebar');
903 }
904 this._showHideSidebarButton.setGlyph(glyph);
905 this._showHideSidebarButton.setTitle(
906 sidebarHidden ? Common.UIString('Show %s', this._showHideSidebarButtonTitle) :
907 Common.UIString('Hide %s', this._showHideSidebarButtonTitle));
908 }
909};
910
911/** @typedef {{showMode: string, size: number}} */
912UI.SplitWidget.SettingForOrientation;
913
914UI.SplitWidget.ShowMode = {
915 Both: 'Both',
916 OnlyMain: 'OnlyMain',
917 OnlySidebar: 'OnlySidebar'
918};
919
920/** @enum {symbol} */
921UI.SplitWidget.Events = {
922 SidebarSizeChanged: Symbol('SidebarSizeChanged'),
923 ShowModeChanged: Symbol('ShowModeChanged')
924};
925
926UI.SplitWidget.MinPadding = 20;