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