/** * Provides constraining behavior for a {@link Ext.drag.Source}. */Ext.define('Ext.drag.Constraint', { alias: 'drag.constraint.base', mixins: [ 'Ext.mixin.Factoryable' ], factoryConfig: { defaultType: 'base', type: 'drag.constraint' }, config: { /** * @cfg {Boolean/String/HTMLELement/Ext.dom.Element} element * * The element to constrain to: * - `true` to constrain to the parent of the {@link Ext.drag.Source#element}. * - The id, DOM element or Ext.dom.Element to constrain to. */ element: null, /** * @cfg {Boolean} horizontal * `true` to limit dragging to the horizontal axis. */ horizontal: false, /** * @cfg {Ext.util.Region} region * * The region to constrain to. */ region: null, /** * @cfg {Number/Object} snap * The interval to move this drag target during a drag in both dimensions. * - `{x: 30}`, snap only x * - `{y: 30}`, snap only y * - `{x: 30, y: 40}`, snap both * - `40`, snap both to `40`. * * The snap may also be a function to calculate the snap value on each tick. * * snap: { * x: function(info, x) { * return x < 300 ? 150 : 500; * } * } */ snap: null, /** * @cfg {Ext.drag.Source} source * The {@link Ext.drag.Source source} for the constraint. This will be * set automatically when constructed via the source. */ source: null, /** * @cfg {Boolean} vertical * `true` to limit dragging to the vertical axis. */ vertical: false, /** * @cfg {Number[]} x * The minimum and maximum x position. Use `null` to * not set a constraint: * - `[100, null]`, constrain only the minimum * - `[null, 100]`, constrain only the maximum * - `[200, 200]`, constrain both. */ x: null, /** * @cfg {Number[]} y * The minimum and maximum y position. Use `null` to * not set a constraint: * - `[100, null]`, constrain only the minimum * - `[null, 100]`, constrain only the maximum * - `[200, 200]`, constrain both. */ y: null }, constructor: function(config) { this.initConfig(config); }, applyElement: function(element) { if (element) { if (typeof element === 'boolean') { element = this.getSource().getElement().parent(); } else { element = Ext.get(element); } } return element || null; }, applySnap: function(snap) { if (typeof snap === 'number') { snap = { x: snap, y: snap }; } return snap; }, /** * Constrain the position of the drag proxy using the configured rules. * * @param {Number[]} xy The position. * @param {Ext.drag.Info} info The drag information. * * @return {Number[]} The xy position. */ constrain: function(xy, info) { var me = this, x = xy[0], y = xy[1], constrainInfo = me.constrainInfo, initial = constrainInfo.initial, constrainX = constrainInfo.x, constrainY = constrainInfo.y, snap = constrainInfo.snap, min, max; if (!constrainInfo.vertical) { if (snap && snap.x) { if (snap.xFn) { x = snap.x.call(me, info, x); } else { x = me.doSnap(x, initial.x, snap.x); } } if (constrainX) { min = constrainX[0]; max = constrainX[1]; if (min !== null && x < min) { x = min; } if (max !== null && x > max) { x = max; } } } else { x = initial.x; } if (!constrainInfo.horizontal) { if (snap && snap.y) { if (snap.yFn) { y = snap.y.call(me, info, y); } else { y = me.doSnap(y, initial.y, snap.y); } } if (constrainY) { min = constrainY[0]; max = constrainY[1]; if (min !== null && y < min) { y = min; } if (max !== null && y > max) { y = max; } } } else { y = initial.y; } return [x, y]; }, destroy: function() { this.setSource(null); this.setElement(null); this.callParent(); }, privates: { /** * Constrains 2 values, while taking into * account nulls. * @param {Number} a The first value. * @param {Number} b The second value. * @param {Function} resolver The function to resolve the value if * both are non null. * @return {Number} If both values are `null`, `null`. If `a` is null, `b`. * If `b` is null, `a`, otherwise the result of the resolver, passing a & b. * * @private */ constrainValue: function(a, b, resolver) { var val = null, aNull = a === null, bNull = b === null; if (!(aNull && bNull)) { if (aNull) { val = b; } else if (bNull) { val = a; } else { val = resolver(a, b); } } return val; }, /** * Calculates the position to move the proxy element * to when using snapping. * * @param {Number} position The current mouse position. * @param {Number} initial The start position. * @param {Number} snap The snap position. * @return {Number} The snapped position. * * @private */ doSnap: function(position, initial, snap) { if (!snap) { return position; } var ratio = (position - initial) / snap, floor = Math.floor(ratio); // Check whether we need to snap less than current, or // greater than current position. if (ratio - floor <= 0.5) { ratio = floor; } else { ratio = floor + 1; } return initial + (snap * ratio); }, /** * Setup data that is needed during a drag for monitoring constraints. * Attempt to merge min/max values with any constrain region. * * @param {Ext.drag.Info} info The drag info. * * @private */ onDragStart: function(info) { var me = this, snap = me.getSnap(), vertical = me.getVertical(), horizontal = me.getHorizontal(), element = me.getElement(), region = me.getRegion(), proxy = info.proxy, proxyEl = proxy.element, x = me.getX(), y = me.getY(), minX = null, maxX = null, minY = null, maxY = null, rminX = null, rmaxX = null, rminY = null, rmaxY = null; if (element) { region = element.getRegion(true); } if (region) { if (!vertical) { rminX = region.left; rmaxX = region.right - (proxyEl ? proxy.width : 0); } if (!horizontal) { rminY = region.top; rmaxY = region.bottom - (proxyEl ? proxy.height : 0); } } // The following piece sets up the numeric values for our constraint. // If there is an axis constraint, don't bother calculating the values since // it is already explicitly constrained so we can shortcut that portion. // // Attempt to merge the appropriate min/max values (if needed). With: // a) A region and a minimum, the larger value is needed (stricter constraint) // b) A region and a maximum, the smaller value is needed (stricter constraint) if (!vertical && (region || x)) { if (x) { minX = x[0]; maxX = x[1]; } if (minX !== null || maxX !== null || rminX !== null || rmaxX !== null) { minX = me.constrainValue(minX, rminX, Math.max); maxX = me.constrainValue(maxX, rmaxX, Math.min); x = [minX, maxX]; } } if (!horizontal && (region || y)) { if (y) { minY = y[0]; maxY = y[1]; } if (minY !== null || maxY !== null || rminY !== null || rmaxY !== null) { minY = me.constrainValue(minY, rminY, Math.max); maxY = me.constrainValue(maxY, rmaxY, Math.min); y = [minY, maxY]; } } if (snap) { snap = { x: snap.x, xFn: typeof snap.x === 'function', y: snap.y, yFn: typeof snap.y === 'function' }; } me.constrainInfo = { initial: info.element.initial, vertical: me.getVertical(), horizontal: me.getHorizontal(), x: x, y: y, snap: snap }; } }});