SpaceTool.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import {
  2. getDirection
  3. } from './SpaceUtil';
  4. import {
  5. set as cursorSet
  6. } from '../../util/Cursor';
  7. import {
  8. hasPrimaryModifier
  9. } from '../../util/Mouse';
  10. var abs = Math.abs,
  11. round = Math.round;
  12. var HIGH_PRIORITY = 1500,
  13. SPACE_TOOL_CURSOR = 'crosshair';
  14. var AXIS_TO_DIMENSION = { x: 'width', y: 'height' },
  15. AXIS_INVERTED = { x: 'y', y: 'x' };
  16. import {
  17. selfAndAllChildren as getAllChildren
  18. } from '../../util/Elements';
  19. import {
  20. assign,
  21. forEach
  22. } from 'min-dash';
  23. /**
  24. * A tool that allows users to create and remove space in a diagram.
  25. *
  26. * The tool needs to be activated manually via {@link SpaceTool#activate(MouseEvent)}.
  27. */
  28. export default function SpaceTool(
  29. eventBus, dragging, canvas,
  30. modeling, rules, toolManager) {
  31. this._canvas = canvas;
  32. this._dragging = dragging;
  33. this._modeling = modeling;
  34. this._rules = rules;
  35. this._toolManager = toolManager;
  36. var self = this;
  37. toolManager.registerTool('space', {
  38. tool: 'spaceTool.selection',
  39. dragging: 'spaceTool'
  40. });
  41. eventBus.on('spaceTool.selection.end', function(event) {
  42. var target = event.originalEvent.target;
  43. // only reactive on diagram click
  44. // on some occasions, event.hover is not set and we have to check if the target is an svg
  45. if (!event.hover && !(target instanceof SVGElement)) {
  46. return;
  47. }
  48. eventBus.once('spaceTool.selection.ended', function() {
  49. self.activateMakeSpace(event.originalEvent);
  50. });
  51. });
  52. eventBus.on('spaceTool.move', HIGH_PRIORITY , function(event) {
  53. var context = event.context;
  54. if (!context.initialized) {
  55. context.initialized = self.initializeMakeSpace(event, context);
  56. }
  57. });
  58. eventBus.on('spaceTool.end', function(event) {
  59. var context = event.context,
  60. axis = context.axis,
  61. direction = context.direction,
  62. movingShapes = context.movingShapes,
  63. resizingShapes = context.resizingShapes;
  64. // skip if create space has not been initialized yet
  65. if (!context.initialized) {
  66. return;
  67. }
  68. var delta = { x: round(event.dx), y: round(event.dy) };
  69. delta[ AXIS_INVERTED[ axis ] ] = 0;
  70. var insideBounds = true;
  71. // check if the space tool cursor is inside of bounds of
  72. // any of the shapes that would be resized.
  73. forEach(resizingShapes, function(shape) {
  74. if ((direction === 'w' && event.x > shape.x + shape.width) ||
  75. (direction === 'e' && event.x < shape.x) ||
  76. (direction === 'n' && event.y > shape.y + shape.height) ||
  77. (direction === 's' && event.y < shape.y)) {
  78. insideBounds = false;
  79. return;
  80. }
  81. });
  82. if (insideBounds) {
  83. // make space only if the cursor is inside bounds
  84. self.makeSpace(movingShapes, resizingShapes, delta, direction);
  85. }
  86. eventBus.once('spaceTool.ended', function(event) {
  87. // reactivate space tool after usage
  88. self.activateSelection(event.originalEvent, true, true);
  89. });
  90. });
  91. }
  92. SpaceTool.$inject = [
  93. 'eventBus',
  94. 'dragging',
  95. 'canvas',
  96. 'modeling',
  97. 'rules',
  98. 'toolManager'
  99. ];
  100. /**
  101. * Activate space tool selection
  102. *
  103. * @param {MouseEvent} event
  104. * @param {Boolean} autoActivate
  105. */
  106. SpaceTool.prototype.activateSelection = function(event, autoActivate, reactivate) {
  107. this._dragging.init(event, 'spaceTool.selection', {
  108. trapClick: false,
  109. cursor: SPACE_TOOL_CURSOR,
  110. autoActivate: autoActivate,
  111. data: {
  112. context: {
  113. reactivate: reactivate
  114. }
  115. }
  116. });
  117. };
  118. /**
  119. * Activate make space
  120. *
  121. * @param {MouseEvent} event
  122. */
  123. SpaceTool.prototype.activateMakeSpace = function(event) {
  124. this._dragging.init(event, 'spaceTool', {
  125. autoActivate: true,
  126. cursor: SPACE_TOOL_CURSOR,
  127. data: {
  128. context: {}
  129. }
  130. });
  131. };
  132. /**
  133. * Actually make space on the diagram
  134. *
  135. * @param {Array<djs.model.Shape>} movingShapes
  136. * @param {Array<djs.model.Shape>} resizingShapes
  137. * @param {Point} delta
  138. * @param {String} direction
  139. */
  140. SpaceTool.prototype.makeSpace = function(movingShapes, resizingShapes, delta, direction) {
  141. return this._modeling.createSpace(movingShapes, resizingShapes, delta, direction);
  142. };
  143. /**
  144. * Initialize make space and return true if that was successful.
  145. *
  146. * @param {Event} event
  147. * @param {Object} context
  148. *
  149. * @return {Boolean} true, if successful
  150. */
  151. SpaceTool.prototype.initializeMakeSpace = function(event, context) {
  152. var axis = abs(event.dx) > abs(event.dy) ? 'x' : 'y',
  153. offset = event['d' + axis],
  154. // start point of create space operation
  155. spacePos = event[axis] - offset;
  156. if (abs(offset) < 5) {
  157. return false;
  158. }
  159. // invert the offset in order to remove space when moving left
  160. if (offset < 0) {
  161. offset *= -1;
  162. }
  163. // inverts the offset to choose the shapes
  164. // on the opposite side of the resizer if
  165. // a key modifier is pressed
  166. if (hasPrimaryModifier(event)) {
  167. offset *= -1;
  168. }
  169. var rootShape = this._canvas.getRootElement();
  170. var allShapes = getAllChildren(rootShape, true);
  171. var adjustments = this.calculateAdjustments(allShapes, axis, offset, spacePos);
  172. // store data in context
  173. assign(context, adjustments, {
  174. axis: axis,
  175. direction: getDirection(axis, offset)
  176. });
  177. cursorSet('resize-' + (axis === 'x' ? 'ew' : 'ns'));
  178. return true;
  179. };
  180. /**
  181. * Calculate adjustments needed when making space
  182. *
  183. * @param {Array<djs.model.Shape>} elements
  184. * @param {String} axis
  185. * @param {Number} offset
  186. * @param {Number} spacePos
  187. *
  188. * @return {Object}
  189. */
  190. SpaceTool.prototype.calculateAdjustments = function(elements, axis, offset, spacePos) {
  191. var movingShapes = [],
  192. resizingShapes = [];
  193. var rules = this._rules;
  194. // collect all elements that need to be moved _AND_
  195. // resized given on the initial create space position
  196. elements.forEach(function(shape) {
  197. var shapeStart = shape[axis],
  198. shapeEnd = shapeStart + shape[AXIS_TO_DIMENSION[axis]];
  199. // checking if it's root
  200. if (!shape.parent) {
  201. return;
  202. }
  203. // checking if it's a shape
  204. if (shape.waypoints) {
  205. return;
  206. }
  207. // shape after spacePos
  208. if (offset > 0 && shapeStart > spacePos) {
  209. return movingShapes.push(shape);
  210. }
  211. // shape before spacePos
  212. if (offset < 0 && shapeEnd < spacePos) {
  213. return movingShapes.push(shape);
  214. }
  215. // shape on top of spacePos, resize only if allowed
  216. if (shapeStart < spacePos &&
  217. shapeEnd > spacePos &&
  218. rules.allowed('shape.resize', { shape: shape })) {
  219. return resizingShapes.push(shape);
  220. }
  221. });
  222. return {
  223. movingShapes: movingShapes,
  224. resizingShapes: resizingShapes
  225. };
  226. };
  227. SpaceTool.prototype.toggle = function() {
  228. if (this.isActive()) {
  229. this._dragging.cancel();
  230. } else {
  231. this.activateSelection();
  232. }
  233. };
  234. SpaceTool.prototype.isActive = function() {
  235. var context = this._dragging.context();
  236. return context && /^spaceTool/.test(context.prefix);
  237. };