OverviewGrid.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  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. * @param {boolean} enabled
  124. */
  125. setResizeEnabled: function(enabled)
  126. {
  127. this._window._setEnabled(!!enabled);
  128. }
  129. }
  130. WebInspector.OverviewGrid.MinSelectableSize = 14;
  131. WebInspector.OverviewGrid.WindowScrollSpeedFactor = .3;
  132. WebInspector.OverviewGrid.ResizerOffset = 3.5; // half pixel because offset values are not rounded but ceiled
  133. /**
  134. * @constructor
  135. * @extends {WebInspector.Object}
  136. * @param {Element} parentElement
  137. * @param {Element} dividersLabelBarElement
  138. */
  139. WebInspector.OverviewGrid.Window = function(parentElement, dividersLabelBarElement)
  140. {
  141. this._parentElement = parentElement;
  142. this._dividersLabelBarElement = dividersLabelBarElement;
  143. WebInspector.installDragHandle(this._parentElement, this._startWindowSelectorDragging.bind(this), this._windowSelectorDragging.bind(this), this._endWindowSelectorDragging.bind(this), "ew-resize");
  144. WebInspector.installDragHandle(this._dividersLabelBarElement, this._startWindowDragging.bind(this), this._windowDragging.bind(this), null, "move");
  145. this.windowLeft = 0.0;
  146. this.windowRight = 1.0;
  147. this._parentElement.addEventListener("mousewheel", this._onMouseWheel.bind(this), true);
  148. this._parentElement.addEventListener("dblclick", this._resizeWindowMaximum.bind(this), true);
  149. this._overviewWindowElement = parentElement.createChild("div", "overview-grid-window");
  150. this._overviewWindowBordersElement = parentElement.createChild("div", "overview-grid-window-rulers");
  151. parentElement.createChild("div", "overview-grid-dividers-background");
  152. this._leftResizeElement = parentElement.createChild("div", "overview-grid-window-resizer");
  153. this._leftResizeElement.style.left = 0;
  154. WebInspector.installDragHandle(this._leftResizeElement, this._resizerElementStartDragging.bind(this), this._leftResizeElementDragging.bind(this), null, "ew-resize");
  155. this._rightResizeElement = parentElement.createChild("div", "overview-grid-window-resizer overview-grid-window-resizer-right");
  156. this._rightResizeElement.style.right = 0;
  157. WebInspector.installDragHandle(this._rightResizeElement, this._resizerElementStartDragging.bind(this), this._rightResizeElementDragging.bind(this), null, "ew-resize");
  158. this._setEnabled(true);
  159. }
  160. WebInspector.OverviewGrid.Events = {
  161. WindowChanged: "WindowChanged"
  162. }
  163. WebInspector.OverviewGrid.Window.prototype = {
  164. reset: function()
  165. {
  166. this.windowLeft = 0.0;
  167. this.windowRight = 1.0;
  168. this._overviewWindowElement.style.left = "0%";
  169. this._overviewWindowElement.style.width = "100%";
  170. this._overviewWindowBordersElement.style.left = "0%";
  171. this._overviewWindowBordersElement.style.right = "0%";
  172. this._leftResizeElement.style.left = "0%";
  173. this._rightResizeElement.style.left = "100%";
  174. this._setEnabled(true);
  175. },
  176. /**
  177. * @param {boolean} enabled
  178. */
  179. _setEnabled: function(enabled)
  180. {
  181. enabled = !!enabled;
  182. if (this._enabled === enabled)
  183. return;
  184. this._enabled = enabled;
  185. this._parentElement.enableStyleClass("resize-enabled", enabled);
  186. },
  187. /**
  188. * @param {Event} event
  189. */
  190. _resizerElementStartDragging: function(event)
  191. {
  192. if (!this._enabled)
  193. return false;
  194. this._resizerParentOffsetLeft = event.pageX - event.offsetX - event.target.offsetLeft;
  195. event.preventDefault();
  196. return true;
  197. },
  198. /**
  199. * @param {Event} event
  200. */
  201. _leftResizeElementDragging: function(event)
  202. {
  203. this._resizeWindowLeft(event.pageX - this._resizerParentOffsetLeft);
  204. event.preventDefault();
  205. },
  206. /**
  207. * @param {Event} event
  208. */
  209. _rightResizeElementDragging: function(event)
  210. {
  211. this._resizeWindowRight(event.pageX - this._resizerParentOffsetLeft);
  212. event.preventDefault();
  213. },
  214. /**
  215. * @param {Event} event
  216. * @return {boolean}
  217. */
  218. _startWindowSelectorDragging: function(event)
  219. {
  220. if (!this._enabled)
  221. return false;
  222. this._offsetLeft = event.pageX - event.offsetX;
  223. var position = event.pageX - this._offsetLeft;
  224. this._overviewWindowSelector = new WebInspector.OverviewGrid.WindowSelector(this._parentElement, position);
  225. return true;
  226. },
  227. /**
  228. * @param {Event} event
  229. */
  230. _windowSelectorDragging: function(event)
  231. {
  232. this._overviewWindowSelector._updatePosition(event.pageX - this._offsetLeft);
  233. event.preventDefault();
  234. },
  235. /**
  236. * @param {Event} event
  237. */
  238. _endWindowSelectorDragging: function(event)
  239. {
  240. var window = this._overviewWindowSelector._close(event.pageX - this._offsetLeft);
  241. delete this._overviewWindowSelector;
  242. if (window.end === window.start) { // Click, not drag.
  243. var middle = window.end;
  244. window.start = Math.max(0, middle - WebInspector.OverviewGrid.MinSelectableSize / 2);
  245. window.end = Math.min(this._parentElement.clientWidth, middle + WebInspector.OverviewGrid.MinSelectableSize / 2);
  246. } else if (window.end - window.start < WebInspector.OverviewGrid.MinSelectableSize) {
  247. if (this._parentElement.clientWidth - window.end > WebInspector.OverviewGrid.MinSelectableSize)
  248. window.end = window.start + WebInspector.OverviewGrid.MinSelectableSize;
  249. else
  250. window.start = window.end - WebInspector.OverviewGrid.MinSelectableSize;
  251. }
  252. this._setWindowPosition(window.start, window.end);
  253. },
  254. /**
  255. * @param {Event} event
  256. * @return {boolean}
  257. */
  258. _startWindowDragging: function(event)
  259. {
  260. this._dragStartPoint = event.pageX;
  261. this._dragStartLeft = this.windowLeft;
  262. this._dragStartRight = this.windowRight;
  263. return true;
  264. },
  265. /**
  266. * @param {Event} event
  267. */
  268. _windowDragging: function(event)
  269. {
  270. event.preventDefault();
  271. var delta = (event.pageX - this._dragStartPoint) / this._parentElement.clientWidth;
  272. if (this._dragStartLeft + delta < 0)
  273. delta = -this._dragStartLeft;
  274. if (this._dragStartRight + delta > 1)
  275. delta = 1 - this._dragStartRight;
  276. this._setWindow(this._dragStartLeft + delta, this._dragStartRight + delta);
  277. },
  278. /**
  279. * @param {number} start
  280. */
  281. _resizeWindowLeft: function(start)
  282. {
  283. // Glue to edge.
  284. if (start < 10)
  285. start = 0;
  286. else if (start > this._rightResizeElement.offsetLeft - 4)
  287. start = this._rightResizeElement.offsetLeft - 4;
  288. this._setWindowPosition(start, null);
  289. },
  290. /**
  291. * @param {number} end
  292. */
  293. _resizeWindowRight: function(end)
  294. {
  295. // Glue to edge.
  296. if (end > this._parentElement.clientWidth - 10)
  297. end = this._parentElement.clientWidth;
  298. else if (end < this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.MinSelectableSize)
  299. end = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.MinSelectableSize;
  300. this._setWindowPosition(null, end);
  301. },
  302. _resizeWindowMaximum: function()
  303. {
  304. this._setWindowPosition(0, this._parentElement.clientWidth);
  305. },
  306. /**
  307. * @param {number} windowLeft
  308. * @param {number} windowRight
  309. */
  310. _setWindow: function(windowLeft, windowRight)
  311. {
  312. var left = windowLeft;
  313. var right = windowRight;
  314. var width = windowRight - windowLeft;
  315. // We allow actual time window to be arbitrarily small but don't want the UI window to be too small.
  316. var widthInPixels = width * this._parentElement.clientWidth;
  317. var minWidthInPixels = WebInspector.OverviewGrid.MinSelectableSize / 2;
  318. if (widthInPixels < minWidthInPixels) {
  319. var factor = minWidthInPixels / widthInPixels;
  320. left = ((windowRight + windowLeft) - width * factor) / 2;
  321. right = ((windowRight + windowLeft) + width * factor) / 2;
  322. }
  323. this.windowLeft = windowLeft;
  324. this._leftResizeElement.style.left = left * 100 + "%";
  325. this.windowRight = windowRight;
  326. this._rightResizeElement.style.left = right * 100 + "%";
  327. this._overviewWindowElement.style.left = left * 100 + "%";
  328. this._overviewWindowBordersElement.style.left = left * 100 + "%";
  329. this._overviewWindowElement.style.width = (right - left) * 100 + "%";
  330. this._overviewWindowBordersElement.style.right = (1 - right) * 100 + "%";
  331. this.dispatchEventToListeners(WebInspector.OverviewGrid.Events.WindowChanged);
  332. },
  333. /**
  334. * @param {?number} start
  335. * @param {?number} end
  336. */
  337. _setWindowPosition: function(start, end)
  338. {
  339. var clientWidth = this._parentElement.clientWidth;
  340. var windowLeft = typeof start === "number" ? start / clientWidth : this.windowLeft;
  341. var windowRight = typeof end === "number" ? end / clientWidth : this.windowRight;
  342. this._setWindow(windowLeft, windowRight);
  343. },
  344. /**
  345. * @param {Event} event
  346. */
  347. _onMouseWheel: function(event)
  348. {
  349. if (typeof event.wheelDeltaY === "number" && event.wheelDeltaY) {
  350. const zoomFactor = 1.1;
  351. const mouseWheelZoomSpeed = 1 / 120;
  352. var reference = event.offsetX / event.target.clientWidth;
  353. this._zoom(Math.pow(zoomFactor, -event.wheelDeltaY * mouseWheelZoomSpeed), reference);
  354. }
  355. if (typeof event.wheelDeltaX === "number" && event.wheelDeltaX) {
  356. var offset = Math.round(event.wheelDeltaX * WebInspector.OverviewGrid.WindowScrollSpeedFactor);
  357. var windowLeft = this._leftResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
  358. var windowRight = this._rightResizeElement.offsetLeft + WebInspector.OverviewGrid.ResizerOffset;
  359. if (windowLeft - offset < 0)
  360. offset = windowLeft;
  361. if (windowRight - offset > this._parentElement.clientWidth)
  362. offset = windowRight - this._parentElement.clientWidth;
  363. this._setWindowPosition(windowLeft - offset, windowRight - offset);
  364. event.preventDefault();
  365. }
  366. },
  367. /**
  368. * @param {number} factor
  369. * @param {number} reference
  370. */
  371. _zoom: function(factor, reference)
  372. {
  373. var left = this.windowLeft;
  374. var right = this.windowRight;
  375. var windowSize = right - left;
  376. var newWindowSize = factor * windowSize;
  377. if (newWindowSize > 1) {
  378. newWindowSize = 1;
  379. factor = newWindowSize / windowSize;
  380. }
  381. left = reference + (left - reference) * factor;
  382. left = Number.constrain(left, 0, 1 - newWindowSize);
  383. right = reference + (right - reference) * factor;
  384. right = Number.constrain(right, newWindowSize, 1);
  385. this._setWindow(left, right);
  386. },
  387. __proto__: WebInspector.Object.prototype
  388. }
  389. /**
  390. * @constructor
  391. */
  392. WebInspector.OverviewGrid.WindowSelector = function(parent, position)
  393. {
  394. this._startPosition = position;
  395. this._width = parent.offsetWidth;
  396. this._windowSelector = document.createElement("div");
  397. this._windowSelector.className = "overview-grid-window-selector";
  398. this._windowSelector.style.left = this._startPosition + "px";
  399. this._windowSelector.style.right = this._width - this._startPosition + "px";
  400. parent.appendChild(this._windowSelector);
  401. }
  402. WebInspector.OverviewGrid.WindowSelector.prototype = {
  403. _createSelectorElement: function(parent, left, width, height)
  404. {
  405. var selectorElement = document.createElement("div");
  406. selectorElement.className = "overview-grid-window-selector";
  407. selectorElement.style.left = left + "px";
  408. selectorElement.style.width = width + "px";
  409. selectorElement.style.top = "0px";
  410. selectorElement.style.height = height + "px";
  411. parent.appendChild(selectorElement);
  412. return selectorElement;
  413. },
  414. _close: function(position)
  415. {
  416. position = Math.max(0, Math.min(position, this._width));
  417. this._windowSelector.remove();
  418. return this._startPosition < position ? {start: this._startPosition, end: position} : {start: position, end: this._startPosition};
  419. },
  420. _updatePosition: function(position)
  421. {
  422. position = Math.max(0, Math.min(position, this._width));
  423. if (position < this._startPosition) {
  424. this._windowSelector.style.left = position + "px";
  425. this._windowSelector.style.right = this._width - this._startPosition + "px";
  426. } else {
  427. this._windowSelector.style.left = this._startPosition + "px";
  428. this._windowSelector.style.right = this._width - position + "px";
  429. }
  430. }
  431. }