354 lines
7.6 KiB
JavaScript
354 lines
7.6 KiB
JavaScript
import {
|
|
setSnapped,
|
|
isSnapped
|
|
} from '../snapping/SnapUtil';
|
|
|
|
import { isCmd } from '../keyboard/KeyboardUtil';
|
|
|
|
import {
|
|
assign,
|
|
isNumber
|
|
} from 'min-dash';
|
|
|
|
import {
|
|
SPACING,
|
|
quantize
|
|
} from './GridUtil';
|
|
|
|
/**
|
|
* @typedef {import('../../core/ElementRegistry').default} ElementRegistry
|
|
* @typedef {import('../../core/EventBus').default} EventBus
|
|
*/
|
|
|
|
var LOWER_PRIORITY = 1200;
|
|
var LOW_PRIORITY = 800;
|
|
|
|
/**
|
|
* Basic grid snapping that covers connecting, creating, moving, resizing shapes, moving bendpoints
|
|
* and connection segments.
|
|
*
|
|
* @param {ElementRegistry} elementRegistry
|
|
* @param {EventBus} eventBus
|
|
* @param {Object} config
|
|
*/
|
|
export default function GridSnapping(elementRegistry, eventBus, config) {
|
|
|
|
var active = !config || config.active !== false;
|
|
|
|
this._eventBus = eventBus;
|
|
|
|
var self = this;
|
|
|
|
eventBus.on('diagram.init', LOW_PRIORITY, function() {
|
|
self.setActive(active);
|
|
});
|
|
|
|
eventBus.on([
|
|
'create.move',
|
|
'create.end',
|
|
'bendpoint.move.move',
|
|
'bendpoint.move.end',
|
|
'connect.move',
|
|
'connect.end',
|
|
'connectionSegment.move.move',
|
|
'connectionSegment.move.end',
|
|
'resize.move',
|
|
'resize.end',
|
|
'shape.move.move',
|
|
'shape.move.end'
|
|
], LOWER_PRIORITY, function(event) {
|
|
var originalEvent = event.originalEvent;
|
|
|
|
if (!self.active || (originalEvent && isCmd(originalEvent))) {
|
|
return;
|
|
}
|
|
|
|
var context = event.context,
|
|
gridSnappingContext = context.gridSnappingContext;
|
|
|
|
if (!gridSnappingContext) {
|
|
gridSnappingContext = context.gridSnappingContext = {};
|
|
}
|
|
|
|
[ 'x', 'y' ].forEach(function(axis) {
|
|
var options = {};
|
|
|
|
// allow snapping with offset
|
|
var snapOffset = getSnapOffset(event, axis, elementRegistry);
|
|
|
|
if (snapOffset) {
|
|
options.offset = snapOffset;
|
|
}
|
|
|
|
// allow snapping with min and max
|
|
var snapConstraints = getSnapConstraints(event, axis);
|
|
|
|
if (snapConstraints) {
|
|
assign(options, snapConstraints);
|
|
}
|
|
|
|
if (!isSnapped(event, axis)) {
|
|
self.snapEvent(event, axis, options);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Snap an events x or y with optional min, max and offset.
|
|
*
|
|
* @param {Object} event
|
|
* @param {string} axis
|
|
* @param {number} [options.min]
|
|
* @param {number} [options.max]
|
|
* @param {number} [options.offset]
|
|
*/
|
|
GridSnapping.prototype.snapEvent = function(event, axis, options) {
|
|
var snappedValue = this.snapValue(event[ axis ], options);
|
|
|
|
setSnapped(event, axis, snappedValue);
|
|
};
|
|
|
|
/**
|
|
* Expose grid spacing for third parties (i.e. extensions).
|
|
*
|
|
* @return {number} spacing of grid dots
|
|
*/
|
|
GridSnapping.prototype.getGridSpacing = function() {
|
|
return SPACING;
|
|
};
|
|
|
|
/**
|
|
* Snap value with optional min, max and offset.
|
|
*
|
|
* @param {number} value
|
|
* @param {Object} options
|
|
* @param {number} [options.min]
|
|
* @param {number} [options.max]
|
|
* @param {number} [options.offset]
|
|
*/
|
|
GridSnapping.prototype.snapValue = function(value, options) {
|
|
var offset = 0;
|
|
|
|
if (options && options.offset) {
|
|
offset = options.offset;
|
|
}
|
|
|
|
value += offset;
|
|
|
|
value = quantize(value, SPACING);
|
|
|
|
var min, max;
|
|
|
|
if (options && options.min) {
|
|
min = options.min;
|
|
|
|
if (isNumber(min)) {
|
|
min = quantize(min + offset, SPACING, 'ceil');
|
|
|
|
value = Math.max(value, min);
|
|
}
|
|
}
|
|
|
|
if (options && options.max) {
|
|
max = options.max;
|
|
|
|
if (isNumber(max)) {
|
|
max = quantize(max + offset, SPACING, 'floor');
|
|
|
|
value = Math.min(value, max);
|
|
}
|
|
}
|
|
|
|
value -= offset;
|
|
|
|
return value;
|
|
};
|
|
|
|
GridSnapping.prototype.isActive = function() {
|
|
return this.active;
|
|
};
|
|
|
|
GridSnapping.prototype.setActive = function(active) {
|
|
this.active = active;
|
|
|
|
this._eventBus.fire('gridSnapping.toggle', { active: active });
|
|
};
|
|
|
|
GridSnapping.prototype.toggleActive = function() {
|
|
this.setActive(!this.active);
|
|
};
|
|
|
|
GridSnapping.$inject = [
|
|
'elementRegistry',
|
|
'eventBus',
|
|
'config.gridSnapping'
|
|
];
|
|
|
|
// helpers //////////
|
|
|
|
/**
|
|
* Get minimum and maximum snap constraints.
|
|
* Constraints are cached.
|
|
*
|
|
* @param {Object} event
|
|
* @param {Object} event.context
|
|
* @param {string} axis
|
|
*
|
|
* @returns {boolean|Object}
|
|
*/
|
|
function getSnapConstraints(event, axis) {
|
|
var context = event.context,
|
|
createConstraints = context.createConstraints,
|
|
resizeConstraints = context.resizeConstraints || {},
|
|
gridSnappingContext = context.gridSnappingContext,
|
|
snapConstraints = gridSnappingContext.snapConstraints;
|
|
|
|
// cache snap constraints
|
|
if (snapConstraints && snapConstraints[ axis ]) {
|
|
return snapConstraints[ axis ];
|
|
}
|
|
|
|
if (!snapConstraints) {
|
|
snapConstraints = gridSnappingContext.snapConstraints = {};
|
|
}
|
|
|
|
if (!snapConstraints[ axis ]) {
|
|
snapConstraints[ axis ] = {};
|
|
}
|
|
|
|
var direction = context.direction;
|
|
|
|
// create
|
|
if (createConstraints) {
|
|
if (isHorizontal(axis)) {
|
|
snapConstraints.x.min = createConstraints.left;
|
|
snapConstraints.x.max = createConstraints.right;
|
|
} else {
|
|
snapConstraints.y.min = createConstraints.top;
|
|
snapConstraints.y.max = createConstraints.bottom;
|
|
}
|
|
}
|
|
|
|
// resize
|
|
var minResizeConstraints = resizeConstraints.min,
|
|
maxResizeConstraints = resizeConstraints.max;
|
|
|
|
if (minResizeConstraints) {
|
|
if (isHorizontal(axis)) {
|
|
|
|
if (isWest(direction)) {
|
|
snapConstraints.x.max = minResizeConstraints.left;
|
|
} else {
|
|
snapConstraints.x.min = minResizeConstraints.right;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isNorth(direction)) {
|
|
snapConstraints.y.max = minResizeConstraints.top;
|
|
} else {
|
|
snapConstraints.y.min = minResizeConstraints.bottom;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (maxResizeConstraints) {
|
|
if (isHorizontal(axis)) {
|
|
|
|
if (isWest(direction)) {
|
|
snapConstraints.x.min = maxResizeConstraints.left;
|
|
} else {
|
|
snapConstraints.x.max = maxResizeConstraints.right;
|
|
}
|
|
|
|
} else {
|
|
|
|
if (isNorth(direction)) {
|
|
snapConstraints.y.min = maxResizeConstraints.top;
|
|
} else {
|
|
snapConstraints.y.max = maxResizeConstraints.bottom;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return snapConstraints[ axis ];
|
|
}
|
|
|
|
/**
|
|
* Get snap offset.
|
|
* Offset is cached.
|
|
*
|
|
* @param {Object} event
|
|
* @param {string} axis
|
|
* @param {ElementRegistry} elementRegistry
|
|
*
|
|
* @returns {number}
|
|
*/
|
|
function getSnapOffset(event, axis, elementRegistry) {
|
|
var context = event.context,
|
|
shape = event.shape,
|
|
gridSnappingContext = context.gridSnappingContext,
|
|
snapLocation = gridSnappingContext.snapLocation,
|
|
snapOffset = gridSnappingContext.snapOffset;
|
|
|
|
// cache snap offset
|
|
if (snapOffset && isNumber(snapOffset[ axis ])) {
|
|
return snapOffset[ axis ];
|
|
}
|
|
|
|
if (!snapOffset) {
|
|
snapOffset = gridSnappingContext.snapOffset = {};
|
|
}
|
|
|
|
if (!isNumber(snapOffset[ axis ])) {
|
|
snapOffset[ axis ] = 0;
|
|
}
|
|
|
|
if (!shape) {
|
|
return snapOffset[ axis ];
|
|
}
|
|
|
|
if (!elementRegistry.get(shape.id)) {
|
|
|
|
if (isHorizontal(axis)) {
|
|
snapOffset[ axis ] += shape[ axis ] + shape.width / 2;
|
|
} else {
|
|
snapOffset[ axis ] += shape[ axis ] + shape.height / 2;
|
|
}
|
|
}
|
|
|
|
if (!snapLocation) {
|
|
return snapOffset[ axis ];
|
|
}
|
|
|
|
if (axis === 'x') {
|
|
if (/left/.test(snapLocation)) {
|
|
snapOffset[ axis ] -= shape.width / 2;
|
|
} else if (/right/.test(snapLocation)) {
|
|
snapOffset[ axis ] += shape.width / 2;
|
|
}
|
|
} else {
|
|
if (/top/.test(snapLocation)) {
|
|
snapOffset[ axis ] -= shape.height / 2;
|
|
} else if (/bottom/.test(snapLocation)) {
|
|
snapOffset[ axis ] += shape.height / 2;
|
|
}
|
|
}
|
|
|
|
return snapOffset[ axis ];
|
|
}
|
|
|
|
function isHorizontal(axis) {
|
|
return axis === 'x';
|
|
}
|
|
|
|
function isNorth(direction) {
|
|
return direction.indexOf('n') !== -1;
|
|
}
|
|
|
|
function isWest(direction) {
|
|
return direction.indexOf('w') !== -1;
|
|
} |