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 {string=} sidebarSizeSettingName 34 * @param {number=} defaultSidebarWidth 35 * @param {number=} defaultSidebarHeight 36 */ 37WebInspector.SplitView = function(isVertical, sidebarSizeSettingName, defaultSidebarWidth, defaultSidebarHeight) 38{ 39 WebInspector.View.call(this); 40 41 this.registerRequiredCSS("splitView.css"); 42 43 this.element.classList.add("split-view"); 44 this.element.classList.add("fill"); 45 46 this._firstElement = this.element.createChild("div", "split-view-contents scroll-target split-view-contents-first"); 47 this._secondElement = this.element.createChild("div", "split-view-contents scroll-target split-view-contents-second"); 48 49 this._resizerElement = this.element.createChild("div", "split-view-resizer"); 50 this._onDragStartBound = this._onDragStart.bind(this); 51 this._resizerElements = []; 52 53 this._resizable = true; 54 55 this._savedSidebarWidth = defaultSidebarWidth || 200; 56 this._savedSidebarHeight = defaultSidebarHeight || this._savedSidebarWidth; 57 58 if (0 < this._savedSidebarWidth && this._savedSidebarWidth < 1 && 59 0 < this._savedSidebarHeight && this._savedSidebarHeight < 1) 60 this._useFraction = true; 61 62 this._sidebarSizeSettingName = sidebarSizeSettingName; 63 64 this.setSecondIsSidebar(true); 65 66 this._innerSetVertical(isVertical); 67 68 // Should be called after isVertical has the right value. 69 this.installResizer(this._resizerElement); 70} 71 72WebInspector.SplitView.prototype = { 73 /** 74 * @return {boolean} 75 */ 76 isVertical: function() 77 { 78 return this._isVertical; 79 }, 80 81 /** 82 * @param {boolean} isVertical 83 */ 84 setVertical: function(isVertical) 85 { 86 if (this._isVertical === isVertical) 87 return; 88 89 this._innerSetVertical(isVertical); 90 91 if (this.isShowing()) 92 this._updateLayout(); 93 94 for (var i = 0; i < this._resizerElements.length; ++i) 95 this._resizerElements[i].style.setProperty("cursor", this._isVertical ? "ew-resize" : "ns-resize"); 96 }, 97 98 /** 99 * @param {boolean} isVertical 100 */ 101 _innerSetVertical: function(isVertical) 102 { 103 this.element.classList.remove(this._isVertical ? "hbox" : "vbox"); 104 this._isVertical = isVertical; 105 this.element.classList.add(this._isVertical ? "hbox" : "vbox"); 106 delete this._resizerElementSize; 107 this._sidebarSize = -1; 108 }, 109 110 _updateLayout: function() 111 { 112 delete this._totalSize; // Lazy update. 113 this._innerSetSidebarSize(this._lastSidebarSize()); 114 }, 115 116 /** 117 * @return {!Element} 118 */ 119 firstElement: function() 120 { 121 return this._firstElement; 122 }, 123 124 /** 125 * @return {!Element} 126 */ 127 secondElement: function() 128 { 129 return this._secondElement; 130 }, 131 132 /** 133 * @return {!Element} 134 */ 135 get mainElement() 136 { 137 return this.isSidebarSecond() ? this.firstElement() : this.secondElement(); 138 }, 139 140 /** 141 * @return {!Element} 142 */ 143 get sidebarElement() 144 { 145 return this.isSidebarSecond() ? this.secondElement() : this.firstElement(); 146 }, 147 148 /** 149 * @return {boolean} 150 */ 151 isSidebarSecond: function() 152 { 153 return this._secondIsSidebar; 154 }, 155 156 /** 157 * @param {boolean} secondIsSidebar 158 */ 159 setSecondIsSidebar: function(secondIsSidebar) 160 { 161 this.sidebarElement.classList.remove("split-view-sidebar"); 162 this.mainElement.classList.remove("split-view-main"); 163 this._secondIsSidebar = secondIsSidebar; 164 this.sidebarElement.classList.add("split-view-sidebar"); 165 this.mainElement.classList.add("split-view-main"); 166 }, 167 168 /** 169 * @return {!Element} 170 */ 171 resizerElement: function() 172 { 173 return this._resizerElement; 174 }, 175 176 showOnlyFirst: function() 177 { 178 this._showOnly(this._firstElement, this._secondElement); 179 }, 180 181 showOnlySecond: function() 182 { 183 this._showOnly(this._secondElement, this._firstElement); 184 }, 185 186 /** 187 * @param {!Element} sideA 188 * @param {!Element} sideB 189 */ 190 _showOnly: function(sideA, sideB) 191 { 192 sideA.classList.remove("hidden"); 193 sideA.classList.add("maximized"); 194 sideB.classList.add("hidden"); 195 sideB.classList.remove("maximized"); 196 this._removeAllLayoutProperties(); 197 198 this._isShowingOne = true; 199 this._sidebarSize = -1; 200 this.setResizable(false); 201 this.doResize(); 202 }, 203 204 _removeAllLayoutProperties: function() 205 { 206 this.sidebarElement.style.removeProperty("flexBasis"); 207 208 this._resizerElement.style.removeProperty("left"); 209 this._resizerElement.style.removeProperty("right"); 210 this._resizerElement.style.removeProperty("top"); 211 this._resizerElement.style.removeProperty("bottom"); 212 213 this._resizerElement.style.removeProperty("margin-left"); 214 this._resizerElement.style.removeProperty("margin-right"); 215 this._resizerElement.style.removeProperty("margin-top"); 216 this._resizerElement.style.removeProperty("margin-bottom"); 217 }, 218 219 showBoth: function() 220 { 221 this._firstElement.classList.remove("hidden"); 222 this._firstElement.classList.remove("maximized"); 223 this._secondElement.classList.remove("hidden"); 224 this._secondElement.classList.remove("maximized"); 225 226 this._isShowingOne = false; 227 this._sidebarSize = -1; 228 this.setResizable(true); 229 this.doResize(); 230 }, 231 232 /** 233 * @param {boolean} resizable 234 */ 235 setResizable: function(resizable) 236 { 237 this._resizable = resizable; 238 this._resizerElement.enableStyleClass("hidden", !resizable); 239 }, 240 241 /** 242 * @param {number} size 243 */ 244 setSidebarSize: function(size) 245 { 246 this._innerSetSidebarSize(size); 247 this._saveSidebarSize(); 248 }, 249 250 /** 251 * @return {number} 252 */ 253 sidebarSize: function() 254 { 255 return Math.max(0, this._sidebarSize); 256 }, 257 258 /** 259 * @return {number} 260 */ 261 totalSize: function() 262 { 263 if (!this._totalSize) 264 this._totalSize = this._isVertical ? this.element.offsetWidth : this.element.offsetHeight; 265 return this._totalSize; 266 }, 267 268 /** 269 * @param {number} size 270 */ 271 _innerSetSidebarSize: function(size) 272 { 273 if (this._isShowingOne) { 274 this._sidebarSize = size; 275 return; 276 } 277 278 size = this._applyConstraints(size); 279 if (this._sidebarSize === size) 280 return; 281 282 if (size < 0) { 283 // Never apply bad values, fix it upon onResize instead. 284 return; 285 } 286 287 this._removeAllLayoutProperties(); 288 289 var sizeValue; 290 if (this._useFraction) 291 sizeValue = (size / this.totalSize()) * 100 + "%"; 292 else 293 sizeValue = size + "px"; 294 295 if (!this._resizerElementSize) 296 this._resizerElementSize = this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight; 297 298 this.sidebarElement.style.flexBasis = sizeValue; 299 if (this._isVertical) { 300 if (this._secondIsSidebar) { 301 this._resizerElement.style.right = sizeValue; 302 this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + "px"; 303 } else { 304 this._resizerElement.style.left = sizeValue; 305 this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + "px"; 306 } 307 } else { 308 if (this._secondIsSidebar) { 309 this._resizerElement.style.bottom = sizeValue; 310 this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + "px"; 311 } else { 312 this._resizerElement.style.top = sizeValue; 313 this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + "px"; 314 } 315 } 316 317 this._sidebarSize = size; 318 319 // No need to recalculate this._sidebarSize and this._totalSize again. 320 this._muteOnResize = true; 321 this.doResize(); 322 delete this._muteOnResize; 323 }, 324 325 /** 326 * @param {number=} minWidth 327 * @param {number=} minHeight 328 */ 329 setSidebarElementConstraints: function(minWidth, minHeight) 330 { 331 if (typeof minWidth === "number") 332 this._minimumSidebarWidth = minWidth; 333 if (typeof minHeight === "number") 334 this._minimumSidebarHeight = minHeight; 335 }, 336 337 /** 338 * @param {number=} minWidth 339 * @param {number=} minHeight 340 */ 341 setMainElementConstraints: function(minWidth, minHeight) 342 { 343 if (typeof minWidth === "number") 344 this._minimumMainWidth = minWidth; 345 if (typeof minHeight === "number") 346 this._minimumMainHeight = minHeight; 347 }, 348 349 /** 350 * @param {number} sidebarSize 351 * @return {number} 352 */ 353 _applyConstraints: function(sidebarSize) 354 { 355 const minPadding = 20; 356 var totalSize = this.totalSize(); 357 var from = (this.isVertical() ? this._minimumSidebarWidth : this._minimumSidebarHeight) || 0; 358 var fromInPercents = false; 359 if (from && from < 1) { 360 fromInPercents = true; 361 from = Math.round(totalSize * from); 362 } 363 from = Math.max(from, minPadding); 364 365 var minMainSize = (this.isVertical() ? this._minimumMainWidth : this._minimumMainHeight) || 0; 366 var toInPercents = false; 367 if (minMainSize && minMainSize < 1) { 368 toInPercents = true; 369 minMainSize = Math.round(totalSize * minMainSize); 370 } 371 minMainSize = Math.max(minMainSize, minPadding); 372 373 var to = totalSize - minMainSize; 374 if (from <= to) 375 return Number.constrain(sidebarSize, from, to); 376 377 // Respect fixed constraints over percents. This will, for example, shrink 378 // the sidebar to its minimum size when possible. 379 if (!fromInPercents && !toInPercents) 380 return -1; 381 if (toInPercents && sidebarSize >= from && from < totalSize) 382 return from; 383 if (fromInPercents && sidebarSize <= to && to < totalSize) 384 return to; 385 386 return -1; 387 }, 388 389 wasShown: function() 390 { 391 this._updateLayout(); 392 }, 393 394 onResize: function() 395 { 396 if (this._muteOnResize) 397 return; 398 this._updateLayout(); 399 }, 400 401 /** 402 * @param {!MouseEvent} event 403 * @return {boolean} 404 */ 405 _startResizerDragging: function(event) 406 { 407 if (!this._resizable) 408 return false; 409 410 this._saveSidebarSizeRecursively(); 411 this._dragOffset = (this._secondIsSidebar ? this.totalSize() - this._sidebarSize : this._sidebarSize) - (this._isVertical ? event.pageX : event.pageY); 412 return true; 413 }, 414 415 /** 416 * @param {!MouseEvent} event 417 */ 418 _resizerDragging: function(event) 419 { 420 var newOffset = (this._isVertical ? event.pageX : event.pageY) + this._dragOffset; 421 var newSize = (this._secondIsSidebar ? this.totalSize() - newOffset : newOffset); 422 this.setSidebarSize(newSize); 423 event.preventDefault(); 424 }, 425 426 /** 427 * @param {!MouseEvent} event 428 */ 429 _endResizerDragging: function(event) 430 { 431 delete this._dragOffset; 432 this._saveSidebarSizeRecursively(); 433 }, 434 435 _saveSidebarSizeRecursively: function() 436 { 437 /** @this {WebInspector.View} */ 438 function doSaveSidebarSizeRecursively() 439 { 440 if (this._saveSidebarSize) 441 this._saveSidebarSize(); 442 this._callOnVisibleChildren(doSaveSidebarSizeRecursively); 443 } 444 this._saveSidebarSize(); 445 this._callOnVisibleChildren(doSaveSidebarSizeRecursively); 446 }, 447 448 /** 449 * @param {!Element} resizerElement 450 */ 451 installResizer: function(resizerElement) 452 { 453 resizerElement.addEventListener("mousedown", this._onDragStartBound, false); 454 resizerElement.style.setProperty("cursor", this._isVertical ? "ew-resize" : "ns-resize"); 455 this._resizerElements.push(resizerElement); 456 }, 457 458 /** 459 * @param {!Element} resizerElement 460 */ 461 uninstallResizer: function(resizerElement) 462 { 463 resizerElement.removeEventListener("mousedown", this._onDragStartBound, false); 464 resizerElement.style.removeProperty("cursor"); 465 this._resizerElements.remove(resizerElement); 466 }, 467 468 /** 469 * @param {?Event} event 470 */ 471 _onDragStart: function(event) 472 { 473 WebInspector.elementDragStart(this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), this._isVertical ? "ew-resize" : "ns-resize", event); 474 }, 475 476 /** 477 * @return {?WebInspector.Setting} 478 */ 479 _sizeSetting: function() 480 { 481 if (!this._sidebarSizeSettingName) 482 return null; 483 484 var settingName = this._sidebarSizeSettingName + (this._isVertical ? "" : "H"); 485 if (!WebInspector.settings[settingName]) 486 WebInspector.settings[settingName] = WebInspector.settings.createSetting(settingName, undefined); 487 488 return WebInspector.settings[settingName]; 489 }, 490 491 /** 492 * @return {number} 493 */ 494 _lastSidebarSize: function() 495 { 496 var sizeSetting = this._sizeSetting(); 497 var size = sizeSetting ? sizeSetting.get() : 0; 498 if (!size) 499 size = this._isVertical ? this._savedSidebarWidth : this._savedSidebarHeight; 500 if (this._useFraction) 501 size *= this.totalSize(); 502 return size; 503 }, 504 505 _saveSidebarSize: function() 506 { 507 var size = this._sidebarSize; 508 if (size < 0) 509 return; 510 511 if (this._useFraction) 512 size /= this.totalSize(); 513 514 if (this._isVertical) 515 this._savedSidebarWidth = size; 516 else 517 this._savedSidebarHeight = size; 518 519 var sizeSetting = this._sizeSetting(); 520 if (sizeSetting) 521 sizeSetting.set(size); 522 }, 523 524 __proto__: WebInspector.View.prototype 525} 526