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