OverviewGrid.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /*
  2. * Copyright (C) 2013 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. * * Redistributions of source code must retain the above copyright
  9. * notice, this list of conditions and the following disclaimer.
  10. * * Redistributions in binary form must reproduce the above
  11. * copyright notice, this list of conditions and the following disclaimer
  12. * in the documentation and/or other materials provided with the
  13. * distribution.
  14. * * Neither the name of Google Inc. nor the names of its
  15. * contributors may be used to endorse or promote products derived from
  16. * this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. */
  30. /**
  31. * @constructor
  32. * @param {string} prefix
  33. */
  34. WebInspector.OverviewGrid = function(prefix)
  35. {
  36. this.element = document.createElement("div");
  37. this.element.className = "fill";
  38. this.element.id = prefix + "-overview-container";
  39. this._grid = new WebInspector.TimelineGrid();
  40. this._grid.element.id = prefix + "-overview-grid";
  41. this._grid.setScrollAndDividerTop(0, 0);
  42. this.element.appendChild(this._grid.element);
  43. this._window = new WebInspector.OverviewGrid.Window(this.element, this._grid.dividersLabelBarElement);
  44. }
  45. WebInspector.OverviewGrid.prototype = {
  46. /**
  47. * @return {number}
  48. */
  49. clientWidth: function()
  50. {
  51. return this.element.clientWidth;
  52. },
  53. /**
  54. * @param {!WebInspector.TimelineGrid.Calculator} calculator
  55. */
  56. updateDividers: function(calculator)
  57. {
  58. this._grid.updateDividers(calculator);
  59. },
  60. /**
  61. * @param {!Array.<Element>} dividers
  62. */
  63. addEventDividers: function(dividers)
  64. {
  65. this._grid.addEventDividers(dividers);
  66. },
  67. removeEventDividers: function()
  68. {
  69. this._grid.removeEventDividers();
  70. },
  71. /**
  72. * @param {?number} start
  73. * @param {?number} end
  74. */
  75. setWindowPosition: function(start, end)
  76. {
  77. this._window._setWindowPosition(start, end);
  78. },
  79. reset: function()
  80. {
  81. this._window.reset();
  82. },
  83. /**
  84. * @return {number}
  85. */
  86. windowLeft: function()
  87. {
  88. return this._window.windowLeft;
  89. },
  90. /**
  91. * @return {number}
  92. */
  93. windowRight: function()
  94. {
  95. return this._window.windowRight;
  96. },
  97. /**
  98. * @param {number} left
  99. * @param {number} right
  100. */
  101. setWindow: function(left, right)
  102. {
  103. this._window._setWindow(left, right);
  104. },
  105. /**
  106. * @param {string} eventType
  107. * @param {function(WebInspector.Event)} listener
  108. * @param {Object=} thisObject
  109. */
  110. addEventListener: function(eventType, listener, thisObject)
  111. {
  112. this._window.addEventListener(eventType, listener, thisObject);
  113. },
  114. /**
  115. * @param {!number} zoomFactor
  116. * @param {!number} referencePoint
  117. */
  118. zoom: function(zoomFactor, referencePoint)
  119. {
  120. this._window._zoom(zoomFactor, referencePoint);
  121. }
  122. }
  123. WebInspector.OverviewGrid.MinSelectableSize = 12;
  124. WebInspector.OverviewGrid.WindowScrollSpeedFactor = .3;
  125. WebInspector.OverviewGrid.ResizerOffset = 3.5; // half pixel because offset values are not rounded but ceiled
  126. /**
  127. * @constructor
  128. * @extends {WebInspector.Object}
  129. * @param {Element} parentElement
  130. * @param {Element} dividersLabelBarElement
  131. */
  132. WebInspector.OverviewGrid.Window = function(parentElement, dividersLabelBarElement)
  133. {
  134. this._parentElement = parentElement;
  135. this._dividersLabelBarElement = dividersLabelBarElement;
  136. WebInspector.installDragHandle(this._parentElement, this._startWindowSelectorDragging.bind(this), this._windowSelectorDragging.bind(this), this._endWindowSelectorDragging.bind(this), "ew-resize");
  137. WebInspector.installDragHandle(this._dividersLabelBarElement, this._startWindowDragging.bind(this), this._windowDragging.bind(this), null, "move");
  138. this.windowLeft = 0.0;
  139. this.windowRight = 1.0;
  140. this._parentElement.addEventListener("mousewheel", this._onMouseWheel.bind(this), true);
  141. this._parentElement.addEventListener("dblclick", this._resizeWindowMaximum.bind(this), true);
  142. this._overviewWindowElement = document.createElement("div");
  143. this._overviewWindowElement.className = "overview-grid-window";
  144. parentElement.appendChild(this._overviewWindowElement);
  145. this._overviewWindowBordersElement = document.createElement("div");
  146. this._overviewWindowBordersElement.className = "overview-grid-window-rulers";
  147. parentElement.appendChild(this._overviewWindowBordersElement);
  148. var overviewDividersBackground = document.createElement("div");
  149. overviewDividersBackground.className = "overview-grid-dividers-background";
  150. parentElement.appendChild(overviewDividersBackground);
  151. this._leftResizeElement = document.createElement("div");
  152. this._leftResizeElement.className = "overview-grid-window-resizer";
  153. this._leftResizeElement.style.left = 0;
  154. parentElement.appendChild(this._leftResizeElement);
  155. WebInspector.installDragHandle(this._leftResizeElement, this._resizerElementStartDragging.bind(this), this._leftResizeElementDragging.bind(this), null, "ew-resize");
  156. this._rightResizeElement = document.createElement("div");
  157. this._rightResizeElement.className = "overview-grid-window-resizer overview-grid-window-resizer-right";
  158. this._rightResizeElement.style.right = 0;
  159. parentElement.appendChild(this._rightResizeElement);
  160. WebInspector.installDragHandle(this._rightResizeElement, this._resizerElementStartDragging.bind(this), this._rightResizeElementDragging.bind(this), null, "ew-resize");
  161. }
  162. WebInspector.OverviewGrid.Events = {
  163. WindowChanged: "WindowChanged"
  164. }
  165. WebInspector.OverviewGrid.Window.prototype = {
  166. reset: function()
  167. {
  168. this.windowLeft = 0.0;
  169. this.windowRight = 1.0;
  170. this._overviewWindowElement.style.left = "0%";
  171. this._overviewWindowElement.style.width = "100%";
  172. this._overviewWindowBordersElement.style.left = "0%";
  173. this._overviewWindowBordersElement.style.right = "0%";
  174. this._leftResizeElement.style.left = "0%";
  175. this._rightResizeElement.style.left = "100%";
  176. },
  177. /**
  178. * @param {Event} event
  179. */
  180. _resizerElementStartDragging: function(event)
  181. {
  182. this._resizerParentOffsetLeft = event.pageX - event.offsetX - event.target.offsetLeft;
  183. event.preventDefault();
  184. return true;
  185. },
  186. /**
  187. * @param {Event} event
  188. */
  189. _leftResizeElementDragging: function(event)
  190. {
  191. this._resizeWindowLeft(event.pageX - this._resizerParentOffsetLeft);
  192. event.preventDefault();
  193. },
  194. /**
  195. * @param {Event} event
  196. */
  197. _rightResizeElementDragging: function(event)
  198. {
  199. this._resizeWindowRight(event.pageX - this._resizerParentOffsetLeft);
  200. event.preventDefault();
  201. },
  202. /**
  203. * @param {Event} event
  204. * @return {boolean}
  205. */
  206. _startWindowSelectorDragging: function(event)
  207. {
  208. this._offsetLeft = event.pageX - event.offsetX;
  209. var position = event.pageX - this._offsetLeft;
  210. this._overviewWindowSelector = new WebInspector.OverviewGrid.WindowSelector(this._parentElement, position);
  211. return true;
  212. },
  213. /**
  214. * @param {Event} event
  215. */
  216. _windowSelectorDragging: function(event)
  217. {
  218. this._overviewWindowSelector._updatePosition(event.pageX - this._offsetLeft);
  219. event.preventDefault();
  220. },
  221. /**
  222. * @param {Event} event
  223. */
  224. _endWindowSelectorDragging: function(event)
  225. {
  226. var window = this._overviewWindowSelector._close(event.pageX - this._offsetLeft);
  227. delete this._overviewWindowSelector;
  228. if (window.end === window.start) { // Click, not drag.
  229. var middle = window.end;
  230. window.start = Math.max(0, middle - WebInspector.OverviewGrid.MinSelectableSize / 2);
  231. window.end = Math.min(this._parentElement.clientWidth, middle + WebInspector.OverviewGrid.MinSelectableSize / 2);
  232. } else if (window.end - window.start < WebInspector.OverviewGrid.MinSelectableSize) {
  233. if (this._parentElement.clientWidth - window.end > WebInspector.OverviewGrid.MinSelectableSize)
  234. window.end = window.start + WebInspector.OverviewGrid.MinSelectableSize;
  235. else
  236. window.start = window.end - WebInspector.OverviewGrid.MinSelectableSize;
  237. }
  238. this._setWindowPosition(window.start, window.end);
  239. },
  240. /**
  241. * @param {Event} event
  242. * @return {boolean}
  243. */
  244. _startWindowDragging: function(event)
  245. {
  246. this._dragStartPoint = event.pageX;
  247. this._dragStartLeft = this.windowLeft;
  248. this._dragStartRight = this.windowRight;
  249. return true;
  250. },
  251. /**
  252. * @param {Event} event
  253. */
  254. _windowDragging: function(event)
  255. {
  256. event.preventDefault();
  257. var delta = (event.pageX - this._dragStartPoint) / this._parentElement.clientWidth;
  258. if (this._dragStartLeft + delta < 0)
  259. delta = -this._dragStartLeft;
  260. if (this._dragStartRight + delta > 1)
  261. delta = 1 - this._dragStartRight;
  262. this._setWindow(this._dragStartLeft + delta, this._dragStartRight + delta);
  263. },
  264. /**
  265. * @param {number} start
  266. */
  267. _resizeWindowLeft: function(start)
  268. {
  269. // Glue to edge.
  270. if (start < 10)
  271. start = 0;
  272. else if (start > this._rightResizeElement.offsetLeft - 4)
  273. start = this._rightResizeElement.offsetLeft - 4;
  274. this._setWindowPosition(start, null);
  275. },
  276. /**
  277. * @param {number} end
  278. */
  279. _resizeWindowRight: function(end)
  280. {
  281. // Glue to edge.
  282. if (end > this._parentElement.clientWidth - 10)
  283. end = this._parentElement.clientWidth;
  284. else if (end < this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.MinSelectableSize)
  285. end = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.MinSelectableSize;
  286. this._setWindowPosition(null, end);
  287. },
  288. _resizeWindowMaximum: function()
  289. {
  290. this._setWindowPosition(0, this._parentElement.clientWidth);
  291. },
  292. /**
  293. * @param {number} left
  294. * @param {number} right
  295. */
  296. _setWindow: function(left, right)
  297. {
  298. var clientWidth = this._parentElement.clientWidth;
  299. this._setWindowPosition(left * clientWidth, right * clientWidth);
  300. },
  301. /**
  302. * @param {?number} start
  303. * @param {?number} end
  304. */
  305. _setWindowPosition: function(start, end)
  306. {
  307. var clientWidth = this._parentElement.clientWidth;
  308. const rulerAdjustment = 1 / clientWidth;
  309. if (typeof start === "number") {
  310. this.windowLeft = start / clientWidth;
  311. this._leftResizeElement.style.left = this.windowLeft * 100 + "%";
  312. this._overviewWindowElement.style.left = this.windowLeft * 100 + "%";
  313. this._overviewWindowBordersElement.style.left = (this.windowLeft - rulerAdjustment) * 100 + "%";
  314. }
  315. if (typeof end === "number") {
  316. this.windowRight = end / clientWidth;
  317. this._rightResizeElement.style.left = this.windowRight * 100 + "%";
  318. }
  319. this._overviewWindowElement.style.width = (this.windowRight - this.windowLeft) * 100 + "%";
  320. this._overviewWindowBordersElement.style.right = (1 - this.windowRight + 2 * rulerAdjustment) * 100 + "%";
  321. this.dispatchEventToListeners(WebInspector.OverviewGrid.Events.WindowChanged);
  322. },
  323. /**
  324. * @param {Event} event
  325. */
  326. _onMouseWheel: function(event)
  327. {
  328. const zoomFactor = 1.1;
  329. const mouseWheelZoomSpeed = 1 / 120;
  330. if (typeof event.wheelDeltaY === "number" && event.wheelDeltaY) {
  331. var referencePoint = event.offsetX;
  332. this._zoom(Math.pow(zoomFactor, -event.wheelDeltaY * mouseWheelZoomSpeed), referencePoint);
  333. }
  334. if (typeof event.wheelDeltaX === "number" && event.wheelDeltaX) {
  335. var offset = Math.round(event.wheelDeltaX * WebInspector.OverviewGrid.WindowScrollSpeedFactor);
  336. var windowLeft = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
  337. var windowRight = this._rightResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
  338. if (windowLeft - offset < 0)
  339. offset = windowLeft;
  340. if (windowRight - offset > this._parentElement.clientWidth)
  341. offset = windowRight - this._parentElement.clientWidth;
  342. this._setWindowPosition(windowLeft - offset, windowRight - offset);
  343. event.preventDefault();
  344. }
  345. },
  346. /**
  347. * @param {number} factor
  348. * @param {number} referencePoint
  349. */
  350. _zoom: function(factor, referencePoint)
  351. {
  352. var left = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
  353. var right = this._rightResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
  354. var delta = factor * (right - left);
  355. if (factor < 1 && delta < WebInspector.OverviewGrid.MinSelectableSize)
  356. return;
  357. var max = this._parentElement.clientWidth;
  358. if (typeof referencePoint !== "number")
  359. referencePoint = (right + left) / 2;
  360. left = Math.max(0, Math.min(max - delta, referencePoint + (left - referencePoint) * factor));
  361. right = Math.min(max, left + delta);
  362. this._setWindowPosition(left, right);
  363. },
  364. __proto__: WebInspector.Object.prototype
  365. }
  366. /**
  367. * @constructor
  368. */
  369. WebInspector.OverviewGrid.WindowSelector = function(parent, position)
  370. {
  371. this._startPosition = position;
  372. this._width = parent.offsetWidth;
  373. this._windowSelector = document.createElement("div");
  374. this._windowSelector.className = "overview-grid-window-selector";
  375. this._windowSelector.style.left = this._startPosition + "px";
  376. this._windowSelector.style.right = this._width - this._startPosition + "px";
  377. parent.appendChild(this._windowSelector);
  378. }
  379. WebInspector.OverviewGrid.WindowSelector.prototype = {
  380. _createSelectorElement: function(parent, left, width, height)
  381. {
  382. var selectorElement = document.createElement("div");
  383. selectorElement.className = "overview-grid-window-selector";
  384. selectorElement.style.left = left + "px";
  385. selectorElement.style.width = width + "px";
  386. selectorElement.style.top = "0px";
  387. selectorElement.style.height = height + "px";
  388. parent.appendChild(selectorElement);
  389. return selectorElement;
  390. },
  391. _close: function(position)
  392. {
  393. position = Math.max(0, Math.min(position, this._width));
  394. this._windowSelector.parentNode.removeChild(this._windowSelector);
  395. return this._startPosition < position ? {start: this._startPosition, end: position} : {start: position, end: this._startPosition};
  396. },
  397. _updatePosition: function(position)
  398. {
  399. position = Math.max(0, Math.min(position, this._width));
  400. if (position < this._startPosition) {
  401. this._windowSelector.style.left = position + "px";
  402. this._windowSelector.style.right = this._width - this._startPosition + "px";
  403. } else {
  404. this._windowSelector.style.left = this._startPosition + "px";
  405. this._windowSelector.style.right = this._width - position + "px";
  406. }
  407. }
  408. }