SplitView.js 15 KB

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