From 8a5bf58a839bfb62be5621e70c27414acc662b1d Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Thu, 6 Jun 2019 11:32:44 +0200 Subject: [PATCH 1/5] test(layout): extract boundary event loop tests --- ...tSequenceFlowSpec.boundaryEventsLoops.bpmn | 41 +++++++++++++++++++ .../modeling/layout/LayoutSequenceFlowSpec.js | 16 +++++--- 2 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn new file mode 100644 index 0000000000..b18af7fef9 --- /dev/null +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js index 525720a297..0c31d55582 100644 --- a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js @@ -32,14 +32,13 @@ describe('features/modeling - layout', function() { describe('boundary events', function() { - var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEvents.bpmn'); + describe('loops', function() { - var testModules = [ coreModule, modelingModule ]; + var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn'); - beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + var testModules = [ coreModule, modelingModule ]; - - describe('loops', function() { + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); it('attached top right', function() { @@ -105,6 +104,13 @@ describe('features/modeling - layout', function() { describe('non-loops', function() { + var diagramXML = require('./LayoutSequenceFlowSpec.boundaryEvents.bpmn'); + + var testModules = [ coreModule, modelingModule ]; + + beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); + + it('attached top right, orientation top', function() { // when From d9b5fd035cbbee8a5ac318d3e69cba3b375c4e22 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Thu, 6 Jun 2019 11:36:27 +0200 Subject: [PATCH 2/5] test(layout): remove awesome test --- .../modeling/layout/LayoutSequenceFlowSpec.js | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js index 0c31d55582..9dd12a03c4 100644 --- a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js @@ -10,26 +10,12 @@ import { reconnectEnd } from './Helper'; -import Modeler from 'lib/Modeler'; - import modelingModule from 'lib/features/modeling'; import coreModule from 'lib/core'; describe('features/modeling - layout', function() { - describe.skip('overall experience, flow elements', function() { - - var diagramXML = require('./LayoutSequenceFlowSpec.flowElements.bpmn'); - - beforeEach(bootstrapModeler(diagramXML, { modules: Modeler.prototype._modules })); - - - it('should feel awesome', inject(function() { })); - - }); - - describe('boundary events', function() { describe('loops', function() { From 9a714896d987c4884946c0bf4e2f03edaa5fe574 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Fri, 7 Jun 2019 10:45:58 +0200 Subject: [PATCH 3/5] feat(bpmn-snapping): snap inside the target instead of target center --- lib/features/snapping/BpmnConnectSnapping.js | 33 ++++++- .../snapping/BpmnConnectSnapping.bpmn | 12 ++- .../snapping/BpmnConnectSnappingSpec.js | 86 +++---------------- 3 files changed, 52 insertions(+), 79 deletions(-) diff --git a/lib/features/snapping/BpmnConnectSnapping.js b/lib/features/snapping/BpmnConnectSnapping.js index 647174208b..8632775fa9 100644 --- a/lib/features/snapping/BpmnConnectSnapping.js +++ b/lib/features/snapping/BpmnConnectSnapping.js @@ -11,6 +11,10 @@ import { some } from 'min-dash'; var HIGHER_PRIORITY = 1250; +var TARGET_BOUNDS_PADDING = 20; + +var AXES = [ 'x', 'y' ]; + /** * Snap during connect. @@ -41,6 +45,10 @@ export default function BpmnConnectSnapping(eventBus, rules) { target: target }); + if (target && connectionAttrs) { + snapInsideTarget(event, target); + } + if (target && isAnyType(connectionAttrs, [ 'bpmn:Association', 'bpmn:DataInputAssociation', @@ -51,8 +59,6 @@ export default function BpmnConnectSnapping(eventBus, rules) { // snap source context.sourcePosition = mid(source); - // snap target - snapToPosition(event, mid(target)); } else if (isType(connectionAttrs, 'bpmn:MessageFlow')) { if (is(source, 'bpmn:Event')) { @@ -80,6 +86,23 @@ BpmnConnectSnapping.$inject = [ 'rules' ]; +function snapInsideTarget(event, target) { + + AXES.forEach(function(axis) { + var matchingTargetDimension = getDimensionForAxis(axis, target), + newCoordinate; + + if (event[axis] < target[axis] + TARGET_BOUNDS_PADDING) { + newCoordinate = target[axis] + TARGET_BOUNDS_PADDING; + } else if (event[axis] > target[axis] + matchingTargetDimension - TARGET_BOUNDS_PADDING) { + newCoordinate = target[axis] + matchingTargetDimension - TARGET_BOUNDS_PADDING; + } + + if (newCoordinate) { + setSnapped(event, axis, newCoordinate); + } + }); +} // helpers ////////// function snapToPosition(event, position) { @@ -95,4 +118,8 @@ function isAnyType(attrs, types) { return some(types, function(type) { return isType(attrs, type); }); -} \ No newline at end of file +} + +function getDimensionForAxis(axis, element) { + return axis === 'x' ? element.width : element.height; +} diff --git a/test/spec/features/snapping/BpmnConnectSnapping.bpmn b/test/spec/features/snapping/BpmnConnectSnapping.bpmn index a48219e32c..125edab14b 100644 --- a/test/spec/features/snapping/BpmnConnectSnapping.bpmn +++ b/test/spec/features/snapping/BpmnConnectSnapping.bpmn @@ -1,5 +1,5 @@ - + @@ -10,6 +10,8 @@ + + @@ -23,7 +25,7 @@ - + @@ -49,6 +51,12 @@ + + + + + + diff --git a/test/spec/features/snapping/BpmnConnectSnappingSpec.js b/test/spec/features/snapping/BpmnConnectSnappingSpec.js index e14c9e04d3..36ab7697dd 100644 --- a/test/spec/features/snapping/BpmnConnectSnappingSpec.js +++ b/test/spec/features/snapping/BpmnConnectSnappingSpec.js @@ -34,96 +34,34 @@ describe('features/snapping - BpmnConnectSnapping', function() { dragging.setOptions({ manual: true }); })); - - describe('association', function() { - - describe('connect', function() { - - it('should snap target', inject(function(connect, dragging, elementRegistry) { - - // given - var task = elementRegistry.get('Task_1'), - dataObjectReference = elementRegistry.get('DataObjectReference_1'), - dataObjectReferenceGfx = elementRegistry.getGraphics(dataObjectReference); - - // when - connect.start(canvasEvent({ x: 300, y: 300 }), task); - - dragging.hover({ element: dataObjectReference, gfx: dataObjectReferenceGfx }); - - dragging.move(canvasEvent({ x: 410, y: 410 })); - - dragging.end(); - - // then - var waypoints = task.outgoing[0].waypoints; - - expect(waypoints[1].original).to.eql({ x: 400, y: 400 }); - })); - - }); - - }); - - describe('sequence flow', function() { describe('connect', function() { - it('should snap target', inject(function(connect, dragging, elementRegistry) { - - // given - var startEvent = elementRegistry.get('StartEvent_1'), - endEvent = elementRegistry.get('EndEvent_1'), - endEventGfx = elementRegistry.getGraphics(endEvent); - - // when - connect.start(canvasEvent({ x: 100, y: 100 }), startEvent); - - dragging.hover({ element: endEvent, gfx: endEventGfx }); - - dragging.move(canvasEvent({ x: 210, y: 210 })); - - dragging.end(); - - // then - var waypoints = startEvent.outgoing[0].waypoints; - - expect(waypoints[3].original).to.eql({ x: 200, y: 200 }); - })); - - }); - - - describe('global connect', function() { - - it('should snap target', inject( - function(connect, dragging, elementRegistry, eventBus) { + it('should snap event if close to target bounds', + inject(function(connect, dragging, elementRegistry) { // given - var startEvent = elementRegistry.get('StartEvent_1'), - endEvent = elementRegistry.get('EndEvent_1'), - endEventGfx = elementRegistry.getGraphics(endEvent); + var boundaryEvent = elementRegistry.get('BoundaryEvent'), + subProcess = elementRegistry.get('SubProcess'), + subProcessGfx = elementRegistry.getGraphics(subProcess); // when - connect.start(null, startEvent, { x: 110, y: 110 }); + connect.start(canvasEvent({ x: 600, y: 300 }), boundaryEvent); - dragging.hover({ element: endEvent, gfx: endEventGfx }); + dragging.hover({ element: subProcess, gfx: subProcessGfx }); - dragging.move(canvasEvent({ x: 210, y: 210 })); + dragging.move(canvasEvent({ x: 400, y: 305 })); dragging.end(); // then - var waypoints = startEvent.outgoing[0].waypoints; - - expect(waypoints[0].original).to.eql({ x: 100, y: 100 }); - expect(waypoints[3].original).to.eql({ x: 200, y: 200 }); - } - )); + var waypoints = boundaryEvent.outgoing[0].waypoints; + expect(waypoints[3].y).to.eql(280); + }) + ); }); - }); From 5b7d3c31dce915baed4ac2e3100f2487360a8ba0 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Thu, 6 Jun 2019 15:05:35 +0200 Subject: [PATCH 4/5] feat(layout): correctly lay out Boundary Event loops Loops will now be laid out with respect to minimum second segment width. --- lib/features/modeling/BpmnLayouter.js | 70 ++++++-- ...tSequenceFlowSpec.boundaryEventsLoops.bpmn | 42 +++-- .../modeling/layout/LayoutSequenceFlowSpec.js | 159 +++++++++++++----- 3 files changed, 193 insertions(+), 78 deletions(-) diff --git a/lib/features/modeling/BpmnLayouter.js b/lib/features/modeling/BpmnLayouter.js index 5a1018030c..b708e6a949 100644 --- a/lib/features/modeling/BpmnLayouter.js +++ b/lib/features/modeling/BpmnLayouter.js @@ -23,6 +23,8 @@ import { import { is } from '../../util/ModelUtil'; +var BOUNDARY_TO_HOST_THRESHOLD = 40; + export default function BpmnLayouter() {} inherits(BpmnLayouter, BaseLayouter); @@ -86,7 +88,7 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) { if (is(source, 'bpmn:BoundaryEvent')) { manhattanOptions = { - preferredLayouts: getBoundaryEventPreferredLayouts(source, target) + preferredLayouts: getBoundaryEventPreferredLayouts(source, target, end) }; } else @@ -262,7 +264,7 @@ function isHorizontalOrientation(orientation) { return orientation === 'right' || orientation === 'left'; } -function getBoundaryEventPreferredLayouts(source, target) { +function getBoundaryEventPreferredLayouts(source, target, end) { var sourceMid = getMid(source), targetMid = getMid(target), attachOrientation = getAttachOrientation(source), @@ -278,16 +280,52 @@ function getBoundaryEventPreferredLayouts(source, target) { y: source.height / 2 + target.height / 2 }); + if (isLoop) { + return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end); + } + // source layout - sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop); + sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide); // target layout - targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop); + targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide); return [ sourceLayout + ':' + targetLayout ]; } -function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) { +function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) { + + var sourceLayout = orientationDirectionMapping[attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation)], + targetLayout; + + if (attachedToSide) { + if (isHorizontalOrientation(attachOrientation)) { + targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b'; + } else { + targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l'; + } + } else { + targetLayout = 'v'; + } + + return [ sourceLayout + ':' + targetLayout ]; +} + +function shouldConnectToSameSide(axis, source, target, end) { + var threshold = BOUNDARY_TO_HOST_THRESHOLD; + + return !( + areCloseOnAxis(axis, end, target, threshold) || + areCloseOnAxis(axis, end, { x: target.x + target.width, y: target.y + target.height }, threshold) || + areCloseOnAxis(axis, end, getMid(source), threshold) + ); +} + +function areCloseOnAxis(axis, a, b, threshold) { + return Math.abs(a[axis] - b[axis]) < threshold; +} + +function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) { // attached to either top, right, bottom or left side if (attachedToSide) { @@ -296,14 +334,12 @@ function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, atta // attached to either top-right, top-left, bottom-right or bottom-left corner - // loop, same vertical or opposite horizontal orientation - if (isLoop || - isSame( - getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation) - ) || - isOppositeOrientation( - getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation) - )) { + // same vertical or opposite horizontal orientation + if (isSame( + getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation) + ) || isOppositeOrientation( + getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation) + )) { return orientationDirectionMapping[getVerticalOrientation(attachOrientation)]; } @@ -311,16 +347,15 @@ function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, atta return orientationDirectionMapping[getHorizontalOrientation(attachOrientation)]; } -function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) { +function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) { // attached to either top, right, bottom or left side if (attachedToSide) { if (isHorizontalOrientation(attachOrientation)) { // orientation is 'right' or 'left' - // loop or opposite horizontal orientation or same orientation + // opposite horizontal orientation or same orientation if ( - isLoop || isOppositeHorizontalOrientation(attachOrientation, targetOrientation) || isSame(attachOrientation, targetOrientation) ) { @@ -332,9 +367,8 @@ function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, atta } else { // orientation is 'top' or 'bottom' - // loop or opposite vertical orientation or same orientation + // opposite vertical orientation or same orientation if ( - isLoop || isOppositeVerticalOrientation(attachOrientation, targetOrientation) || isSame(attachOrientation, targetOrientation) ) { diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn index b18af7fef9..852a57847f 100644 --- a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn @@ -1,40 +1,48 @@ - + - - - + + + + + - + - - - - + - - - - + - + - + + + + + + + + + + + + + - - + + diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js index 9dd12a03c4..6b5723478d 100644 --- a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js @@ -26,65 +26,138 @@ describe('features/modeling - layout', function() { beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); - it('attached top right', function() { - // when - var connection = connect('BoundaryEvent_TopRight', 'SubProcess'); + describe('in the corner', function() { - // then - expect(connection).to.have.waypoints([ - { original: { x: 650, y: 300 }, x: 650, y: 282 }, - { x: 650, y: 262 }, - { x: 475, y: 262 }, - { original: { x: 475, y: 400 }, x: 475, y: 300 } - ]); - }); + it('attached top right', function() { + // when + var connection = connect('BoundaryEvent_TopRight', 'SubProcess'); - it('attached bottom right', function() { + // then + expect(connection).to.have.waypoints([ + { original: { x: 550, y: 200 }, x: 550, y: 182 }, + { x: 550, y: 162 }, + { x: 375, y: 162 }, + { original: { x: 375, y: 300 }, x: 375, y: 200 } + ]); + }); - // when - var connection = connect('BoundaryEvent_BottomRight', 'SubProcess'); - // then - expect(connection).to.have.waypoints([ - { original: { x: 650, y: 468 }, x: 668, y: 468 }, - { x: 688, y: 468 }, - { x: 688, y: 400 }, - { original: { x: 475, y: 400 }, x: 650, y: 400 } - ]); - }); + it('attached bottom right', function() { + // when + var connection = connect('BoundaryEvent_BottomRight', 'SubProcess'); - it('attached bottom left', function() { + // then + expect(connection).to.have.waypoints([ + { original: { x: 550, y: 368 } , x: 568, y: 368 }, + { x: 588, y: 368 }, + { x: 588, y: 300 }, + { original: { x: 375, y: 300 } , x: 550, y: 300 } + ]); + }); - // when - var connection = connect('BoundaryEvent_BottomLeft', 'SubProcess'); - // then - expect(connection).to.have.waypoints([ - { original: { x: 300, y: 500 }, x: 300, y: 518 }, - { x: 300, y: 538 }, - { x: 475, y: 538 }, - { original: { x: 475, y: 400 }, x: 475, y: 500 } - ]); - }); + it('attached bottom left', function() { + // when + var connection = connect('BoundaryEvent_BottomLeft', 'SubProcess'); - it('attached top left', function() { + // then + expect(connection).to.have.waypoints([ + { original: { x: 200, y: 500 }, x: 200, y: 418 }, + { x: 200, y: 438 }, + { x: 375, y: 438 }, + { original: { x: 375, y: 300 }, x: 375, y: 400 } + ]); + }); - // when - var connection = connect('BoundaryEvent_TopLeft', 'SubProcess'); - // then - expect(connection).to.have.waypoints([ - { original: { x: 300, y: 338 }, x: 282, y: 338 }, - { x: 262, y: 338 }, - { x: 262, y: 400 }, - { original: { x: 475, y: 400 }, x: 300, y: 400 } - ]); + it('attached top left', function() { + + // when + var connection = connect('BoundaryEvent_TopLeft', 'SubProcess'); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 200, y: 238 }, x: 182, y: 238 }, + { x: 162, y: 238 }, + { x: 162, y: 300 }, + { original: { x: 375, y: 300 }, x: 200, y: 300 } + ]); + }); }); + + describe('on the side center', function() { + + var host = 'SubProcess_2'; + + + it('attached top center', function() { + + // when + var connection = connect('BoundaryEvent_TopCenter', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 375, y: 460 }, x: 375, y: 442 }, + { x:375, y: 422 }, + { x:180, y: 422 }, + { x:180, y: 560 }, + { original:{ x: 375, y: 560 }, x: 200, y: 560 } + ]); + }); + + + it('attached center right', function() { + + // when + var connection = connect('BoundaryEvent_CenterRight', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 550, y: 560 }, x: 568, y: 560 }, + { x: 588, y: 560 }, + { x: 588, y: 680 }, + { x: 375, y: 680 }, + { original: { x: 375, y: 560 }, x: 375, y: 660 } + ]); + }); + + + it('attached bottom center', function() { + + // when + var connection = connect('BoundaryEvent_BottomCenter', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 375, y: 660 }, x: 375, y: 678 }, + { x: 375, y: 698 }, + { x: 180, y: 698 }, + { x: 180, y: 560 }, + { original: { x: 375, y: 560 }, x: 200, y: 560 } + ]); + }); + + + it('attached center left', function() { + + // when + var connection = connect('BoundaryEvent_CenterLeft', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 200, y: 560 }, x: 182, y: 560 }, + { x: 162, y: 560 }, + { x: 162, y: 680 }, + { x: 375, y: 680 }, + { original: { x: 375, y: 560 }, x: 375, y: 660 } + ]); + }); + }); }); From e17b433422d32c717881a39aad1031e6234c66dc Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Wed, 12 Jun 2019 16:38:00 +0200 Subject: [PATCH 5/5] feat(bpmn-snapping): snap boundary event loop when close to source Closes #903 --- lib/features/snapping/BpmnConnectSnapping.js | 41 +++++++ .../snapping/BpmnConnectSnapping.bpmn | 4 + .../snapping/BpmnConnectSnappingSpec.js | 104 ++++++++++++++++++ 3 files changed, 149 insertions(+) diff --git a/lib/features/snapping/BpmnConnectSnapping.js b/lib/features/snapping/BpmnConnectSnapping.js index 8632775fa9..e948e6a25d 100644 --- a/lib/features/snapping/BpmnConnectSnapping.js +++ b/lib/features/snapping/BpmnConnectSnapping.js @@ -5,16 +5,23 @@ import { import { isCmd } from 'diagram-js/lib/features/keyboard/KeyboardUtil'; +import { + getOrientation +} from 'diagram-js/lib/layout/LayoutUtil'; + import { is } from '../../util/ModelUtil'; import { some } from 'min-dash'; var HIGHER_PRIORITY = 1250; +var BOUNDARY_TO_HOST_THRESHOLD = 40; + var TARGET_BOUNDS_PADDING = 20; var AXES = [ 'x', 'y' ]; +var abs = Math.abs; /** * Snap during connect. @@ -59,6 +66,9 @@ export default function BpmnConnectSnapping(eventBus, rules) { // snap source context.sourcePosition = mid(source); + if (is(source, 'bpmn:BoundaryEvent') && target === source.host) { + snapBoundaryEventLoop(event, source, target); + } } else if (isType(connectionAttrs, 'bpmn:MessageFlow')) { if (is(source, 'bpmn:Event')) { @@ -103,6 +113,37 @@ function snapInsideTarget(event, target) { } }); } + +// snap outside of Boundary Event surroundings +function snapBoundaryEventLoop(event, source, target) { + var sourceMid = mid(source), + orientation = getOrientation(sourceMid, target, -10), + snappingAxes = []; + + if (/top|bottom/.test(orientation)) { + snappingAxes.push('x'); + } + + if (/left|right/.test(orientation)) { + snappingAxes.push('y'); + } + + snappingAxes.forEach(function(axis) { + var coordinate = event[axis], newCoordinate; + + if (abs(coordinate - sourceMid[axis]) < BOUNDARY_TO_HOST_THRESHOLD) { + if (coordinate > sourceMid[axis]) { + newCoordinate = sourceMid[axis] + BOUNDARY_TO_HOST_THRESHOLD; + } + else { + newCoordinate = sourceMid[axis] - BOUNDARY_TO_HOST_THRESHOLD; + } + + setSnapped(event, axis, newCoordinate); + } + }); +} + // helpers ////////// function snapToPosition(event, position) { diff --git a/test/spec/features/snapping/BpmnConnectSnapping.bpmn b/test/spec/features/snapping/BpmnConnectSnapping.bpmn index 125edab14b..7521cfd69a 100644 --- a/test/spec/features/snapping/BpmnConnectSnapping.bpmn +++ b/test/spec/features/snapping/BpmnConnectSnapping.bpmn @@ -12,6 +12,7 @@ + @@ -57,6 +58,9 @@ + + + diff --git a/test/spec/features/snapping/BpmnConnectSnappingSpec.js b/test/spec/features/snapping/BpmnConnectSnappingSpec.js index 36ab7697dd..294948c37a 100644 --- a/test/spec/features/snapping/BpmnConnectSnappingSpec.js +++ b/test/spec/features/snapping/BpmnConnectSnappingSpec.js @@ -38,6 +38,110 @@ describe('features/snapping - BpmnConnectSnapping', function() { describe('connect', function() { + describe('Boundary Event loop', function() { + + it('should snap to the left', + inject(function(connect, dragging, elementRegistry) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent'), + subProcess = elementRegistry.get('SubProcess'), + subProcessGfx = elementRegistry.getGraphics(subProcess); + + // when + connect.start(canvasEvent({ x: 600, y: 300 }), boundaryEvent); + + dragging.hover({ element: subProcess, gfx: subProcessGfx }); + + dragging.move(canvasEvent({ x: 582, y: 300 })); + + dragging.end(); + + // then + var waypoints = boundaryEvent.outgoing[0].waypoints; + + expect(waypoints[3].x).to.eql(560); + }) + ); + + + it('should snap to the right', + inject(function(connect, dragging, elementRegistry) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEvent'), + subProcess = elementRegistry.get('SubProcess'), + subProcessGfx = elementRegistry.getGraphics(subProcess); + + // when + connect.start(canvasEvent({ x: 600, y: 300 }), boundaryEvent); + + dragging.hover({ element: subProcess, gfx: subProcessGfx }); + + dragging.move(canvasEvent({ x: 618, y: 300 })); + + dragging.end(); + + // then + var waypoints = boundaryEvent.outgoing[0].waypoints; + + expect(waypoints[3].x).to.eql(640); + }) + ); + + + it('should snap above', + inject(function(connect, dragging, elementRegistry) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEventRight'), + subProcess = elementRegistry.get('SubProcess'), + subProcessGfx = elementRegistry.getGraphics(subProcess); + + // when + connect.start(canvasEvent({ x: 761, y: 218 }), boundaryEvent); + + dragging.hover({ element: subProcess, gfx: subProcessGfx }); + + dragging.move(canvasEvent({ x: 761, y: 200 })); + + dragging.end(); + + // then + var waypoints = boundaryEvent.outgoing[0].waypoints; + + expect(waypoints[3].y).to.eql(178); + }) + ); + + + it('should snap below', + inject(function(connect, dragging, elementRegistry) { + + // given + var boundaryEvent = elementRegistry.get('BoundaryEventRight'), + subProcess = elementRegistry.get('SubProcess'), + subProcessGfx = elementRegistry.getGraphics(subProcess); + + // when + connect.start(canvasEvent({ x: 761, y: 218 }), boundaryEvent); + + dragging.hover({ element: subProcess, gfx: subProcessGfx }); + + dragging.move(canvasEvent({ x: 761, y: 230 })); + + dragging.end(); + + // then + var waypoints = boundaryEvent.outgoing[0].waypoints; + + expect(waypoints[3].y).to.eql(258); + }) + ); + + }); + + it('should snap event if close to target bounds', inject(function(connect, dragging, elementRegistry) {