transformations.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. #ifdef 0
  2. /* This Source Code Form is subject to the terms of the Mozilla Public
  3. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  4. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  5. #endif
  6. /**
  7. * This singleton allows to transform the grid by repositioning a site's node
  8. * in the DOM and by showing or hiding the node. It additionally provides
  9. * convenience methods to work with a site's DOM node.
  10. */
  11. var gTransformation = {
  12. /**
  13. * Returns the width of the left and top border of a cell. We need to take it
  14. * into account when measuring and comparing site and cell positions.
  15. */
  16. get _cellBorderWidths() {
  17. let cstyle = window.getComputedStyle(gGrid.cells[0].node, null);
  18. let widths = {
  19. left: parseInt(cstyle.getPropertyValue("border-left-width")),
  20. top: parseInt(cstyle.getPropertyValue("border-top-width"))
  21. };
  22. // Cache this value, overwrite the getter.
  23. Object.defineProperty(this, "_cellBorderWidths",
  24. {value: widths, enumerable: true});
  25. return widths;
  26. },
  27. /**
  28. * Gets a DOM node's position.
  29. * @param aNode The DOM node.
  30. * @return A Rect instance with the position.
  31. */
  32. getNodePosition: function(aNode) {
  33. let {left, top, width, height} = aNode.getBoundingClientRect();
  34. return new Rect(left + scrollX, top + scrollY, width, height);
  35. },
  36. /**
  37. * Fades a given node from zero to full opacity.
  38. * @param aNode The node to fade.
  39. * @param aCallback The callback to call when finished.
  40. */
  41. fadeNodeIn: function(aNode, aCallback) {
  42. this._setNodeOpacity(aNode, 1, function() {
  43. // Clear the style property.
  44. aNode.style.opacity = "";
  45. if (aCallback)
  46. aCallback();
  47. });
  48. },
  49. /**
  50. * Fades a given node from full to zero opacity.
  51. * @param aNode The node to fade.
  52. * @param aCallback The callback to call when finished.
  53. */
  54. fadeNodeOut: function(aNode, aCallback) {
  55. this._setNodeOpacity(aNode, 0, aCallback);
  56. },
  57. /**
  58. * Fades a given site from zero to full opacity.
  59. * @param aSite The site to fade.
  60. * @param aCallback The callback to call when finished.
  61. */
  62. showSite: function(aSite, aCallback) {
  63. this.fadeNodeIn(aSite.node, aCallback);
  64. },
  65. /**
  66. * Fades a given site from full to zero opacity.
  67. * @param aSite The site to fade.
  68. * @param aCallback The callback to call when finished.
  69. */
  70. hideSite: function(aSite, aCallback) {
  71. this.fadeNodeOut(aSite.node, aCallback);
  72. },
  73. /**
  74. * Allows to set a site's position.
  75. * @param aSite The site to re-position.
  76. * @param aPosition The desired position for the given site.
  77. */
  78. setSitePosition: function(aSite, aPosition) {
  79. let style = aSite.node.style;
  80. let {top, left} = aPosition;
  81. style.top = top + "px";
  82. style.left = left + "px";
  83. },
  84. /**
  85. * Freezes a site in its current position by positioning it absolute.
  86. * @param aSite The site to freeze.
  87. */
  88. freezeSitePosition: function(aSite) {
  89. if (this._isFrozen(aSite))
  90. return;
  91. let style = aSite.node.style;
  92. let comp = getComputedStyle(aSite.node, null);
  93. style.width = comp.getPropertyValue("width");
  94. style.height = comp.getPropertyValue("height");
  95. aSite.node.setAttribute("frozen", "true");
  96. this.setSitePosition(aSite, this.getNodePosition(aSite.node));
  97. },
  98. /**
  99. * Unfreezes a site by removing its absolute positioning.
  100. * @param aSite The site to unfreeze.
  101. */
  102. unfreezeSitePosition: function(aSite) {
  103. if (!this._isFrozen(aSite))
  104. return;
  105. let style = aSite.node.style;
  106. style.left = style.top = style.width = style.height = "";
  107. aSite.node.removeAttribute("frozen");
  108. },
  109. /**
  110. * Slides the given site to the target node's position.
  111. * @param aSite The site to move.
  112. * @param aTarget The slide target.
  113. * @param aOptions Set of options (see below).
  114. * unfreeze - unfreeze the site after sliding
  115. * callback - the callback to call when finished
  116. */
  117. slideSiteTo: function(aSite, aTarget, aOptions) {
  118. let currentPosition = this.getNodePosition(aSite.node);
  119. let targetPosition = this.getNodePosition(aTarget.node)
  120. let callback = aOptions && aOptions.callback;
  121. let self = this;
  122. function finish() {
  123. if (aOptions && aOptions.unfreeze)
  124. self.unfreezeSitePosition(aSite);
  125. if (callback)
  126. callback();
  127. }
  128. // We need to take the width of a cell's border into account.
  129. targetPosition.left += this._cellBorderWidths.left;
  130. targetPosition.top += this._cellBorderWidths.top;
  131. // Nothing to do here if the positions already match.
  132. if (currentPosition.left == targetPosition.left &&
  133. currentPosition.top == targetPosition.top) {
  134. finish();
  135. } else {
  136. this.setSitePosition(aSite, targetPosition);
  137. this._whenTransitionEnded(aSite.node, ["left", "top"], finish);
  138. }
  139. },
  140. /**
  141. * Rearranges a given array of sites and moves them to their new positions or
  142. * fades in/out new/removed sites.
  143. * @param aSites An array of sites to rearrange.
  144. * @param aOptions Set of options (see below).
  145. * unfreeze - unfreeze the site after rearranging
  146. * callback - the callback to call when finished
  147. */
  148. rearrangeSites: function(aSites, aOptions) {
  149. let batch = [];
  150. let cells = gGrid.cells;
  151. let callback = aOptions && aOptions.callback;
  152. let unfreeze = aOptions && aOptions.unfreeze;
  153. aSites.forEach(function(aSite, aIndex) {
  154. // Do not re-arrange empty cells or the dragged site.
  155. if (!aSite || aSite == gDrag.draggedSite)
  156. return;
  157. batch.push(new Promise(resolve => {
  158. if (!cells[aIndex]) {
  159. // The site disappeared from the grid, hide it.
  160. this.hideSite(aSite, resolve);
  161. } else if (this._getNodeOpacity(aSite.node) != 1) {
  162. // The site disappeared before but is now back, show it.
  163. this.showSite(aSite, resolve);
  164. } else {
  165. // The site's position has changed, move it around.
  166. this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
  167. }
  168. }));
  169. }, this);
  170. if (callback) {
  171. Promise.all(batch).then(callback);
  172. }
  173. },
  174. /**
  175. * Listens for the 'transitionend' event on a given node and calls the given
  176. * callback.
  177. * @param aNode The node that is transitioned.
  178. * @param aProperties The properties we'll wait to be transitioned.
  179. * @param aCallback The callback to call when finished.
  180. */
  181. _whenTransitionEnded:
  182. function(aNode, aProperties, aCallback) {
  183. let props = new Set(aProperties);
  184. aNode.addEventListener("transitionend", function onEnd(e) {
  185. if (props.has(e.propertyName)) {
  186. aNode.removeEventListener("transitionend", onEnd);
  187. aCallback();
  188. }
  189. });
  190. },
  191. /**
  192. * Gets a given node's opacity value.
  193. * @param aNode The node to get the opacity value from.
  194. * @return The node's opacity value.
  195. */
  196. _getNodeOpacity: function(aNode) {
  197. let cstyle = window.getComputedStyle(aNode, null);
  198. return cstyle.getPropertyValue("opacity");
  199. },
  200. /**
  201. * Sets a given node's opacity.
  202. * @param aNode The node to set the opacity value for.
  203. * @param aOpacity The opacity value to set.
  204. * @param aCallback The callback to call when finished.
  205. */
  206. _setNodeOpacity:
  207. function(aNode, aOpacity, aCallback) {
  208. if (this._getNodeOpacity(aNode) == aOpacity) {
  209. if (aCallback)
  210. aCallback();
  211. } else {
  212. if (aCallback) {
  213. this._whenTransitionEnded(aNode, ["opacity"], aCallback);
  214. }
  215. aNode.style.opacity = aOpacity;
  216. }
  217. },
  218. /**
  219. * Moves a site to the cell with the given index.
  220. * @param aSite The site to move.
  221. * @param aIndex The target cell's index.
  222. * @param aOptions Options that are directly passed to slideSiteTo().
  223. */
  224. _moveSite: function(aSite, aIndex, aOptions) {
  225. this.freezeSitePosition(aSite);
  226. this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
  227. },
  228. /**
  229. * Checks whether a site is currently frozen.
  230. * @param aSite The site to check.
  231. * @return Whether the given site is frozen.
  232. */
  233. _isFrozen: function(aSite) {
  234. return aSite.node.hasAttribute("frozen");
  235. }
  236. };