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