Resize.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import {
  2. pick,
  3. assign
  4. } from 'min-dash';
  5. import {
  6. resizeBounds,
  7. ensureConstraints,
  8. computeChildrenBBox,
  9. getMinResizeBounds
  10. } from './ResizeUtil';
  11. import {
  12. asTRBL,
  13. getMid,
  14. roundBounds
  15. } from '../../layout/LayoutUtil';
  16. var DEFAULT_MIN_WIDTH = 10;
  17. /**
  18. * A component that provides resizing of shapes on the canvas.
  19. *
  20. * The following components are part of shape resize:
  21. *
  22. * * adding resize handles,
  23. * * creating a visual during resize
  24. * * checking resize rules
  25. * * committing a change once finished
  26. *
  27. *
  28. * ## Customizing
  29. *
  30. * It's possible to customize the resizing behaviour by intercepting 'resize.start'
  31. * and providing the following parameters through the 'context':
  32. *
  33. * * minDimensions ({ width, height }): minimum shape dimensions
  34. *
  35. * * childrenBoxPadding ({ left, top, bottom, right } || number):
  36. * gap between the minimum bounding box and the container
  37. *
  38. * f.ex:
  39. *
  40. * ```javascript
  41. * eventBus.on('resize.start', 1500, function(event) {
  42. * var context = event.context,
  43. *
  44. * context.minDimensions = { width: 140, height: 120 };
  45. *
  46. * // Passing general padding
  47. * context.childrenBoxPadding = 30;
  48. *
  49. * // Passing padding to a specific side
  50. * context.childrenBoxPadding.left = 20;
  51. * });
  52. * ```
  53. */
  54. export default function Resize(eventBus, rules, modeling, dragging) {
  55. this._dragging = dragging;
  56. this._rules = rules;
  57. var self = this;
  58. /**
  59. * Handle resize move by specified delta.
  60. *
  61. * @param {Object} context
  62. * @param {Point} delta
  63. */
  64. function handleMove(context, delta) {
  65. var shape = context.shape,
  66. direction = context.direction,
  67. resizeConstraints = context.resizeConstraints,
  68. newBounds;
  69. context.delta = delta;
  70. newBounds = resizeBounds(shape, direction, delta);
  71. // ensure constraints during resize
  72. context.newBounds = ensureConstraints(newBounds, resizeConstraints);
  73. // update + cache executable state
  74. context.canExecute = self.canResize(context);
  75. }
  76. /**
  77. * Handle resize start.
  78. *
  79. * @param {Object} context
  80. */
  81. function handleStart(context) {
  82. var resizeConstraints = context.resizeConstraints,
  83. // evaluate minBounds for backwards compatibility
  84. minBounds = context.minBounds;
  85. if (resizeConstraints !== undefined) {
  86. return;
  87. }
  88. if (minBounds === undefined) {
  89. minBounds = self.computeMinResizeBox(context);
  90. }
  91. context.resizeConstraints = {
  92. min: asTRBL(minBounds)
  93. };
  94. }
  95. /**
  96. * Handle resize end.
  97. *
  98. * @param {Object} context
  99. */
  100. function handleEnd(context) {
  101. var shape = context.shape,
  102. canExecute = context.canExecute,
  103. newBounds = context.newBounds;
  104. if (canExecute) {
  105. // ensure we have actual pixel values for new bounds
  106. // (important when zoom level was > 1 during move)
  107. newBounds = roundBounds(newBounds);
  108. if (!boundsChanged(shape, newBounds)) {
  109. // no resize necessary
  110. return;
  111. }
  112. // perform the actual resize
  113. modeling.resizeShape(shape, newBounds);
  114. }
  115. }
  116. eventBus.on('resize.start', function(event) {
  117. handleStart(event.context);
  118. });
  119. eventBus.on('resize.move', function(event) {
  120. var delta = {
  121. x: event.dx,
  122. y: event.dy
  123. };
  124. handleMove(event.context, delta);
  125. });
  126. eventBus.on('resize.end', function(event) {
  127. handleEnd(event.context);
  128. });
  129. }
  130. Resize.prototype.canResize = function(context) {
  131. var rules = this._rules;
  132. var ctx = pick(context, [ 'newBounds', 'shape', 'delta', 'direction' ]);
  133. return rules.allowed('shape.resize', ctx);
  134. };
  135. /**
  136. * Activate a resize operation.
  137. *
  138. * You may specify additional contextual information and must specify a
  139. * resize direction during activation of the resize event.
  140. *
  141. * @param {MouseEvent} event
  142. * @param {djs.model.Shape} shape
  143. * @param {Object|string} contextOrDirection
  144. */
  145. Resize.prototype.activate = function(event, shape, contextOrDirection) {
  146. var dragging = this._dragging,
  147. context,
  148. direction;
  149. if (typeof contextOrDirection === 'string') {
  150. contextOrDirection = {
  151. direction: contextOrDirection
  152. };
  153. }
  154. context = assign({ shape: shape }, contextOrDirection);
  155. direction = context.direction;
  156. if (!direction) {
  157. throw new Error('must provide a direction (n|w|s|e|nw|se|ne|sw)');
  158. }
  159. dragging.init(event, getReferencePoint(shape, direction), 'resize', {
  160. autoActivate: true,
  161. cursor: getCursor(direction),
  162. data: {
  163. shape: shape,
  164. context: context
  165. }
  166. });
  167. };
  168. Resize.prototype.computeMinResizeBox = function(context) {
  169. var shape = context.shape,
  170. direction = context.direction,
  171. minDimensions,
  172. childrenBounds;
  173. minDimensions = context.minDimensions || {
  174. width: DEFAULT_MIN_WIDTH,
  175. height: DEFAULT_MIN_WIDTH
  176. };
  177. // get children bounds
  178. childrenBounds = computeChildrenBBox(shape, context.childrenBoxPadding);
  179. // get correct minimum bounds from given resize direction
  180. // basically ensures that the minBounds is max(childrenBounds, minDimensions)
  181. return getMinResizeBounds(direction, shape, minDimensions, childrenBounds);
  182. };
  183. Resize.$inject = [
  184. 'eventBus',
  185. 'rules',
  186. 'modeling',
  187. 'dragging'
  188. ];
  189. // helpers //////////
  190. function boundsChanged(shape, newBounds) {
  191. return shape.x !== newBounds.x ||
  192. shape.y !== newBounds.y ||
  193. shape.width !== newBounds.width ||
  194. shape.height !== newBounds.height;
  195. }
  196. export function getReferencePoint(shape, direction) {
  197. var mid = getMid(shape),
  198. trbl = asTRBL(shape);
  199. var referencePoint = {
  200. x: mid.x,
  201. y: mid.y
  202. };
  203. if (direction.indexOf('n') !== -1) {
  204. referencePoint.y = trbl.top;
  205. } else if (direction.indexOf('s') !== -1) {
  206. referencePoint.y = trbl.bottom;
  207. }
  208. if (direction.indexOf('e') !== -1) {
  209. referencePoint.x = trbl.right;
  210. } else if (direction.indexOf('w') !== -1) {
  211. referencePoint.x = trbl.left;
  212. }
  213. return referencePoint;
  214. }
  215. function getCursor(direction) {
  216. var prefix = 'resize-';
  217. if (direction === 'n' || direction === 's') {
  218. return prefix + 'ns';
  219. } else if (direction === 'e' || direction === 'w') {
  220. return prefix + 'ew';
  221. } else if (direction === 'nw' || direction === 'se') {
  222. return prefix + 'nwse';
  223. } else {
  224. return prefix + 'nesw';
  225. }
  226. }