diff --git a/lib/features/space-tool/SpaceTool.js b/lib/features/space-tool/SpaceTool.js index 3684d4559..94c666838 100644 --- a/lib/features/space-tool/SpaceTool.js +++ b/lib/features/space-tool/SpaceTool.js @@ -5,7 +5,10 @@ import { isNumber } from 'min-dash'; -import { asTRBL } from '../../layout/LayoutUtil'; +import { + asTRBL, + getMid +} from '../../layout/LayoutUtil'; import { getBBox } from '../../util/Elements'; @@ -361,10 +364,13 @@ function getSpaceToolConstraints(elements, axis, direction, start, minDimensions max; forEach(resizingShapes, function(resizingShape) { + var attachers = resizingShape.attachers, + children = resizingShape.children; + var resizingShapeBBox = asTRBL(resizingShape); // find children that are not moving or resizing - var nonMovingResizingChildren = filter(resizingShape.children, function(child) { + var nonMovingResizingChildren = filter(children, function(child) { return !isConnection(child) && !isLabel(child) && !includes(movingShapes, child) && @@ -372,13 +378,19 @@ function getSpaceToolConstraints(elements, axis, direction, start, minDimensions }); // find children that are moving - var movingChildren = filter(resizingShape.children, function(child) { + var movingChildren = filter(children, function(child) { return !isConnection(child) && !isLabel(child) && includes(movingShapes, child); }); var minOrMax, nonMovingResizingChildrenBBox, - movingChildrenBBox; + movingChildrenBBox, + movingAttachers = [], + nonMovingAttachers = [], + movingAttachersBBox, + movingAttachersConstraint, + nonMovingAttachersBBox, + nonMovingAttachersConstraint; if (nonMovingResizingChildren.length) { nonMovingResizingChildrenBBox = addPadding(asTRBL(getBBox(nonMovingResizingChildren))); @@ -416,9 +428,52 @@ function getSpaceToolConstraints(elements, axis, direction, start, minDimensions } } + if (attachers && attachers.length) { + attachers.forEach(function(attacher) { + if (movingShapes.indexOf(attacher) !== -1) { + movingAttachers.push(attacher); + } else { + nonMovingAttachers.push(attacher); + } + }); + + if (movingAttachers.length) { + movingAttachersBBox = asTRBL(getBBox(movingAttachers.map(getMid))); + + movingAttachersConstraint = resizingShapeBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] + - (movingAttachersBBox[ DIRECTION_TO_TRBL[ DIRECTION_TO_OPPOSITE[ direction ] ] ] - start); + } + + if (nonMovingAttachers.length) { + nonMovingAttachersBBox = asTRBL(getBBox(nonMovingAttachers.map(getMid))); + + nonMovingAttachersConstraint = nonMovingAttachersBBox[ DIRECTION_TO_TRBL[ direction ] ] + - (resizingShapeBBox[ DIRECTION_TO_TRBL[ direction ] ] - start); + } + + if (direction === 'n') { + minOrMax = Math.min(movingAttachersConstraint || Infinity, nonMovingAttachersConstraint || Infinity); + + spaceToolConstraints.bottom = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; + } else if (direction === 'w') { + minOrMax = Math.min(movingAttachersConstraint || Infinity, nonMovingAttachersConstraint || Infinity); + + spaceToolConstraints.right = max = isNumber(max) ? Math.min(max, minOrMax) : minOrMax; + } else if (direction === 's') { + minOrMax = Math.max(movingAttachersConstraint || -Infinity, nonMovingAttachersConstraint || -Infinity); + + spaceToolConstraints.top = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; + } else if (direction === 'e') { + minOrMax = Math.max(movingAttachersConstraint || -Infinity, nonMovingAttachersConstraint || -Infinity); + + spaceToolConstraints.left = min = isNumber(min) ? Math.max(min, minOrMax) : minOrMax; + } + } + var resizingShapeMinDimensions = minDimensions && minDimensions[ resizingShape.id ]; if (resizingShapeMinDimensions) { + if (direction === 'n') { minOrMax = start + resizingShape[ AXIS_TO_DIMENSION [ axis ] ] - diff --git a/test/spec/features/space-tool/SpaceToolSpec.js b/test/spec/features/space-tool/SpaceToolSpec.js index 42fb762b3..fa1ac8258 100644 --- a/test/spec/features/space-tool/SpaceToolSpec.js +++ b/test/spec/features/space-tool/SpaceToolSpec.js @@ -686,6 +686,287 @@ describe('features/space-tool', function() { }); + describe('attachers', function() { + + var parentShape3; + + beforeEach(inject(function(canvas, elementFactory) { + parentShape3 = elementFactory.createShape({ + id: 'parent3', + x: 1100, y: 500, + width: 250, height: 150 + }); + + canvas.addShape(parentShape3); + })); + + + describe('moving attachers', function() { + + it('should consider attachers when resizing shape (n)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x - 25, + y: parentShape3.y + parentShape3.height - 75, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 640 })); + + dragging.move(canvasEvent({ x: 0, y: 1000 }, keyModifier)); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1100, + y: 600, + width: 250, + height: 100 + }); + } + )); + + + it('should consider attachers when resizing shape (w)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x + parentShape3.width - 75, + y: parentShape3.y - 25, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 1340, y: 0 })); + + dragging.move(canvasEvent({ x: 2000, y: 0 }, keyModifier)); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1150, + y: 500, + width: 200, + height: 150 + }); + } + )); + + + it('should consider attachers when resizing shape (s)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x - 25, + y: parentShape3.y + 25, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 510 })); + + dragging.move(canvasEvent({ x: 0, y: 0 })); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1100, + y: 500, + width: 250, + height: 100 + }); + } + )); + + + it('should consider attachers when resizing shape (e)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x + 25, + y: parentShape3.y - 25, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 1110, y: 0 })); + + dragging.move(canvasEvent({ x: 0, y: 0 })); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1100, + y: 500, + width: 200, + height: 150 + }); + } + )); + + }); + + + describe('non-moving attachers', function() { + + it('should consider attachers when resizing shape (n)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x - 25, + y: parentShape3.y + parentShape3.height - 75, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 510 })); + + dragging.move(canvasEvent({ x: 0, y: 1000 }, keyModifier)); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1100, + y: 600, + width: 250, + height: 50 + }); + } + )); + + + it('should consider attachers when resizing shape (w)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x + parentShape3.width - 75, + y: parentShape3.y - 25, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 1110, y: 0 })); + + dragging.move(canvasEvent({ x: 2000, y: 0 }, keyModifier)); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1300, + y: 500, + width: 50, + height: 150 + }); + } + )); + + + it('should consider attachers when resizing shape (s)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x - 25, + y: parentShape3.y + 25, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 0, y: 640 })); + + dragging.move(canvasEvent({ x: 0, y: 0 })); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1100, + y: 500, + width: 250, + height: 50 + }); + } + )); + + + it('should consider attachers when resizing shape (e)', inject( + function(canvas, dragging, elementFactory, spaceTool) { + + // given + var attacher = elementFactory.createShape({ + id: 'attacher', + x: parentShape3.x + 25, + y: parentShape3.y - 25, + width: 50, height: 50, + host: parentShape3 + }); + + canvas.addShape(attacher); + + // when + spaceTool.activateMakeSpace(canvasEvent({ x: 1200, y: 0 })); + + dragging.move(canvasEvent({ x: 0, y: 0 })); + + dragging.end(); + + // then + expect(parentShape3).to.have.bounds({ + x: 1100, + y: 500, + width: 50, + height: 150 + }); + } + )); + + }); + + }); + + describe('minimum dimensions', function() { it('should get minimum dimensions via event bus', inject( diff --git a/test/spec/features/space-tool/rules/SpaceRules.js b/test/spec/features/space-tool/rules/SpaceRules.js index 5cf12c91a..e646873b6 100644 --- a/test/spec/features/space-tool/rules/SpaceRules.js +++ b/test/spec/features/space-tool/rules/SpaceRules.js @@ -16,6 +16,8 @@ SpaceRules.prototype.init = function() { this.addRule('shape.resize', function(context) { var shape = context.shape; - return shape.children.length > 0 || shape.id === 'shape'; + return shape.children.length > 0 + || shape.attachers.length > 0 + || shape.id === 'shape'; }); }; \ No newline at end of file