/**
* @function addConstrainedDrag
* @summary
* Constrain a draggable along a line. The mousewheel also moves the draggable on all desktop browsers
* except Microsoft Edge.
* @description
* The snippet makes 2 changes to the draggable movie clip:
* 1. automatically adds an invisible rectangle to the draggable movie clip as the hit area
* based on the nominal bounds. If the draggable is text, you need to add a shape to the movie clip
* with either a background color or alpha = 0, so Animate CC will calculate the desired nominal bounds.
* 2. adds an onclick handler which calls evt.preventDefault, so mousing/touching down on the draggable does not
* trigger the default slate click-out
*
* The coordinates of the line are based on the cross-hairs (registration point) of the
* draggable movieclip. These are the values x and y draggable as viewed in it's parent object.
*
* Pass in an optional callback function in the options object with the key "dragUpdate"
* The dragUpdate function will be called at each mousemovement at maximum of 16 ms (60 fps)
* The dragUpdate function is called back with this object:
* ```
* {fraction: distance of draggable from line.a,
* event: raw Event object,
* dx: change in x position in coordinates of parent,
* dy: change in y position in coordinates of parent}
* ```
*
* Pass in an optional callback function in the options object with the key "dragEnd"
* The dragEnd function will be called when the "panend" event is fired (end of drag).
* The dragEnd function is called back with this object:
* ```
* {fraction: distance of draggable from line.a,
* event: raw Event object}
* ```
*
* Pass in an optional releaseTolerance (between 0 and 1), which allows draggable to catch up to mouse/touch
* position. Default value is 0.1. Tolerance in pixels is releaseTolerance * canvas width
*
* Inertia Options:
* * inertiaOnMobile defaults to true
* * inertiaOnDesktop defaults to false
* * inertiaMs defaults to 500 (0.5 seconds)
* * inertiaInitialFactor defaults to 2, input smaller value for greater inertiaMs
* * inertiaDeltaFactor defaults to 0.9, max = 0.99, input smaller value for shorter inertiaMs
* * inertiaThresholdVelocity defaults to 0.1, input smaller value to allow a slower movement
*
* ```
* Returns: {
* removeEventHandlers<function>,
* restoreEventHandlers<function>
* }
* ```
*
* Internal functions are also returned for unit testing.
* @param {Object} options
* @param {createjs.MovieClip} options.draggable
* @param {Object} options.line Object with keys a and b for the endpoints
* @param {Object} options.line.a Object with keys x and y for coordinates
* @param {number} options.line.a.x x coordinate
* @param {number} options.line.a.y y coordinate
* @param {Object} options.line.b Object with keys x and y for coordinates
* @param {number} options.line.b.x x coordinate
* @param {number} options.line.b.y y coordinate
* @param {function(Number)=} options.dragUpdate
* @param {boolean=} options.mouseWheelDisabled (defaults false) if true, then no mousewheel handler
* @param {number=} options.mouseWheelSpeed defaults to 1.0 (100% of actual speed)
* @param {boolean=} options.forceDragRelease (defaults true) if true, then draggable is released when the mouse is
* no longer over the boundaries of the object
* @param {number=} options.releaseTolerance a number between 0 and 1 to set a tolerance in the in draggable direction
* for the mouse/touch position to be off the draggable before it is released.
* Pixels allowed is releaseTolerance * canvas width
* @param {boolean=} options.naturalScrolling (default true) if true, then 2 finger gesture moves
* draggable in opposite direction (i.e. pulling down moves content down)
* @param {boolean=} options.inertiaOnMobile if false, then no inertia on mobile
* @param {boolean=} options.inertiaOnDesktop if false, then no inertia on desktop
* @param {number=} options.inertiaMs (default 500) number of milliseconds for inertia on dragEnd
* @param {number=} options.inertialIntialFactor (default 2) multiplier for starting value of inertia
* applied to deltaX and deltaY of dragEnd event
* @param {number=} options.inertialDeltaFactor (default 0.9) mulitplier on each inertia movement,
* so movement at each update is progressively smaller, minimum value 0, maximum value 0.99
* @param {number=} options.inertiaVelocityThreshold (default 0.1) if dragEnd event has a velocity below
* the threshold, then no inertia is created. Allows user to stop at desired location easier by stopping
* inertia when the last drag movement is slow.
* @param {boolean=} options.loop (default false) if true, dragging off either end will loop back from the other end
* Note: looping only works for vertical or horizontal lines
* @param {boolean=} options.doLogging if true, then log the fraction in dragUpdate
* @param {boolean=} options.allowDraggableClick if true, then don't make clicks call preventDefault
* @returns {{getLength: getLength, getNearestPoint: getNearestPoint, testSegment: testSegment}}
*/
function addConstrainedDrag (options) {
if (typeof snippets.initializeGestures !== 'function') {
console.error('Constrained Drag requires Initilize Gestures snippet')
return
}
var draggable = options.draggable
if (!(draggable instanceof window.createjs.MovieClip)) {
console.error('Constrained Drag must be called with a MovieClip options.draggable')
return
}
var parent = draggable.parent
var stage = window.$b.stage
var line = options.line
var isNumber = function (x) {
return typeof x === 'number'
}
if (!line || !line.a || !isNumber(line.a.x) || !isNumber(line.a.y) ||
!line.b || !isNumber(line.b.x) || !isNumber(line.b.y)) {
console.error('Constrained Drag must be called with options.line = {a: {x ,y}, b: {x, y}}')
return
}
var isMobile = window.$b.isMobile()
var mouseWheelScrollSign = options.naturalScrolling === false ? 1 : -1
var doLogging = !!options.doLogging
var mouseWheelDisabled = options.mouseWheelDisabled === true
var mouseWheelSpeed = isNumber(options.mouseWheelSpeed) ? options.mouseWheelSpeed : 1.0
mouseWheelSpeed = Math.min(mouseWheelSpeed, 1)
mouseWheelSpeed = Math.max(mouseWheelSpeed, 0)
var forceDragRelease = !(options.forceDragRelease === false)
var releaseTolerance = isNumber(options.releaseTolerance) ? options.releaseTolerance : 0.1
releaseTolerance = Math.min(releaseTolerance, 1)
releaseTolerance = Math.max(releaseTolerance, 0)
var inertiaMs = isNumber(options.inertiaMs) ? options.inertiaMs : 500
var inertiaInitialFactor = isNumber(options.inertiaInitialFactor) ? options.inertiaInitialFactor : 2
var inertiaDeltaFactor = isNumber(options.inertiaDeltaFactor) ? options.inertiaDeltaFactor : 0.9
inertiaDeltaFactor = Math.max(inertiaDeltaFactor, 0)
inertiaDeltaFactor = Math.min(inertiaDeltaFactor, 0.99)
var inertiaVelocityThreshold = isNumber(options.inertiaVelocityThreshold) ? options.inertiaVelocityThreshold : 0.1
var inertiaOnDesktop = !isMobile && (options.inertiaOnDesktop !== false)
var inertiaOnMobile = isMobile && (options.inertiaOnMobile !== false)
var inertiaOn = inertiaOnDesktop || inertiaOnMobile
var loop = !!options.loop
var xOnly = (line.a.y === line.b.y)
var yOnly = (line.a.x === line.b.x)
var xMin = Math.min(line.a.x, line.b.x)
var xMax = Math.max(line.a.x, line.b.x)
var yMin = Math.min(line.a.y, line.b.y)
var yMax = Math.max(line.a.y, line.b.y)
var dragUpdate
var inertia = function () {}
var cancelInertia = function () {}
if (typeof options.dragUpdate === 'function') {
if (window.$b.slateAutoCloseTimer) {
dragUpdate = function (updateOptions) {
window.$b.slateAutoCloseTimer.reset()
options.dragUpdate(updateOptions)
}
} else {
dragUpdate = options.dragUpdate
}
}
var dragEnd
var hasDragEnd = false
if (typeof options.dragEnd === 'function') {
dragEnd = options.dragEnd
hasDragEnd = true
}
/*
* Add inertia only on mobile
*/
var dragEndTimer
if (inertiaOn) {
var cloneEvent = function (type, event) {
var evt = new Event(type)
return Object.setPrototypeOf(evt, event)
}
/*
* 30 ms = 33.3 fps
* Autoscroll for 0.5 second unless velocity is below threshold
*/
var dtEvent = 30
var dtTotal = inertiaMs
var tEvent
var inertiaCancelled
inertia = function (evt) {
if (typeof evt !== 'object') return Promise.resolve()
if (Math.abs(evt.velocity) < inertiaVelocityThreshold) return Promise.resolve()
return new Promise(function (resolve, reject) {
doLogging && console.log('inertia started, evt', evt)
inertiaCancelled = false
tEvent = 0
function moveIncrement (deltaOptions) {
/*
* Simulate a mousewheel event which does not require a delta from lastEvt
*/
var newEvent = cloneEvent(evt.type, evt)
newEvent.type = wheelType
newEvent.deltaX = deltaOptions.dx * deltaOptions.deltaFactor * mouseWheelScrollSign
newEvent.deltaY = deltaOptions.dy * deltaOptions.deltaFactor * mouseWheelScrollSign
newEvent.deltaTime = dtEvent
updateFunction(newEvent)
}
var deltaOpts = {
dx: evt.deltaX * (dtEvent / evt.deltaTime),
dy: evt.deltaY * (dtEvent / evt.deltaTime),
deltaFactor: inertiaInitialFactor
}
var updateIncrement = function () {
if (inertiaCancelled || (tEvent >= dtTotal)) {
dragEndTimer = null
resolve()
return
}
moveIncrement(deltaOpts)
deltaOpts.deltaFactor *= inertiaDeltaFactor
tEvent += dtEvent
dragEndTimer = setTimeout(updateIncrement, dtEvent)
}
updateIncrement()
})
}
cancelInertia = function () {
inertiaCancelled = true
}
/*
* This wrapped function is called only by the 'panend' event.
*/
dragEnd = function (dragEndOpts) {
inertia(dragEndOpts.event)
.then(function () {
doLogging && console.log('inertia resolved')
hasDragEnd && options.dragEnd(dragEndOpts)
})
}
}
/*
* Set the hitArea of the draggable movie clip
*/
if (!(draggable.hitArea instanceof window.createjs.Shape)) {
var hitArea = new window.createjs.Shape()
hitArea
.graphics
.beginFill('#000')
.drawRect(draggable.nominalBounds.x, draggable.nominalBounds.y,
draggable.nominalBounds.width, draggable.nominalBounds.height)
draggable.hitArea = hitArea
draggable.mouseChildren = false
}
/*
* The event point is in the coordinates of the parent object.
* To determine if this point is in the boundaries of the of draggable,
* the draggable units must account for the registration point and scaling applied.
*
* If the draggable has a mask, then use only the coordinates of the mask to determine
* if the event should be used or discarded.
*/
var xMin0, xMax0, yMin0, yMax0
var xMinPt, xMaxPt, yMinPt, yMaxPt
var useMask = draggable.mask instanceof createjs.Shape
/*
* Precalculate variables needed for function pointInBounds
*/
if (useMask) {
/*
* The mask coordinate system is the same as the draggable.
*/
var maskBounds = getShapeBounds(draggable.mask)
/*
* These are the initial mask bounds in the coordinates system of the parent.
*/
xMinPt = draggable.mask.x
xMaxPt = xMinPt + maskBounds.width
yMinPt = draggable.mask.y
yMaxPt = yMinPt + maskBounds.height
} else {
/*
* These are the base draggable bounds in the coordinate system of the parent.
*/
xMin0 = (draggable.nominalBounds.x - draggable.regX) * draggable.scaleX
xMax0 = xMin0 + draggable.nominalBounds.width * draggable.scaleX
yMin0 = (draggable.nominalBounds.y - draggable.regY) * draggable.scaleY
yMax0 = yMin0 + draggable.nominalBounds.height * draggable.scaleY
}
var pointInBounds = function (point) {
if (!useMask) {
/*
* When the draggable moves, these are its new bounds in the coordinate system of the parent.
*/
xMinPt = draggable.x + xMin0
xMaxPt = draggable.x + xMax0
yMinPt = draggable.y + yMin0
yMaxPt = draggable.y + yMax0
}
return (
(point.x >= xMinPt - tolX) &&
(point.x <= xMaxPt + tolX) &&
(point.y >= yMinPt - tolY) &&
(point.y <= yMaxPt + tolY)
)
}
/*
* Uses closure variables isMobile, refWidth, refHeight, canvas and stage
*/
function getMousePoint (options) {
var evt = options.event
var parent = options.parent
var point
/*
* On mobile, stage.mouseX & Y does not give the correct coordinates, so they must be calculated from the
* raw event coordinates (event.center) for gesture events. Click events do not have evt.center.
*/
if (isMobile && evt.center) {
var evtOffset = options.offset || window.$b.gestures.getOffset(stage.canvas)
var x = (evt.center.x - evtOffset.x) * refWidth / canvas.clientWidth * stage.scaleX
var y = (evt.center.y - evtOffset.y) * refHeight / canvas.clientHeight * stage.scaleY
point = parent.globalToLocal(x, y)
} else {
point = parent.globalToLocal(stage.mouseX, stage.mouseY)
}
return point
}
function mouseInBounds (options) {
var point = getMousePoint(options)
return pointInBounds(point)
}
/*
* Unless options.allowDraggableClick is true, stop propagation of the event
*/
if (options.allowDraggableClick !== true) {
draggable.on('click', function (evt) {
evt.preventDefault()
evt.stopPropagation()
})
}
/*
* The event is updated with a value of deltaX and deltaY that is cumulative since the panstart event, so
* need to keep track of the last event to get the incremental delta position.
* The timer is used to reset the last event when dragging has stopped. Mobile needs a longer waitTime.
*/
var lineLength = getLength({ a: line.a, b: line.b })
var lastEvt = null
var timerId = null
var waitTime = isMobile ? 1000 : 500
var canvas = window.$b.stage.canvas
var refWidth = window.$b.adParameters.refWidth
var refHeight = window.$b.adParameters.refHeight
var draggableRegX = draggable.regX * draggable.scaleX
var draggableRegY = draggable.regY * draggable.scaleY
var fraction
var offset
/*
* Allow a tolerance in the draggable direction before it releases
* Tolerance is in both directions if not xOnly and not yOnly
*/
var tol = (refWidth * releaseTolerance) * refWidth / canvas.clientWidth
var tolX = (xOnly || !yOnly) ? tol : 0
var tolY = (yOnly || !xOnly) ? tol : 0
window.$b.on('CanvasSizeChanged', function (evt) {
if (window.$b.gestures) {
offset = window.$b.gestures.getOffset(canvas)
}
tol = (refWidth * releaseTolerance) * refWidth / canvas.clientWidth
tolX = (xOnly || !yOnly) ? tol : 0
tolY = (yOnly || !xOnly) ? tol : 0
})
var mouseWheelHandler = function (evt) {
if (!offset && window.$b.gestures) {
offset = window.$b.gestures.getCachedOffset()
}
if (offset) {
var x = (evt.clientX - offset.x) * refWidth / canvas.clientWidth
var y = (evt.clientY - offset.y) * refHeight / canvas.clientHeight
var obj = window.$b.stage.getObjectUnderPoint(x, y, 0)
/*
* The update function is attached to the draggable, since each time addConstrainedDrag is called,
* the update function will have a new invocation with different closure variables.
*/
obj && obj.hasEventListener('pan') && obj.updateFunction(evt)
}
evt.stopPropagation()
return false
}
var wheelType
/*
* Using the mousewheel on Microsoft Edge causes the entire window to move instead of just the canvas
*/
if (!mouseWheelDisabled && !window.$b.isEdge()) {
/*
* Some browsers (i.e. Firefox) use the wheel event instead of mousewheel
*/
if (canvas.onmousewheel !== undefined) {
canvas.onmousewheel = mouseWheelHandler
wheelType = 'mousewheel'
} else {
canvas.onwheel = mouseWheelHandler
wheelType = 'wheel'
}
}
function checkInitialPosition () {
var point = {
x: draggable.x - draggableRegX,
y: draggable.y - draggableRegY
}
var point0 = { x: point.x, y: point.y }
var callUpdate = false
if (xOnly) {
if (point.x > xMax) {
console.log('Initial draggable position is > xMax')
callUpdate = true
} else if (point.x < xMin) {
console.log('Initial draggable position is < xMin')
callUpdate = true
}
point.x = Math.min(point.x, xMax)
point.x = Math.max(point.x, xMin)
fraction = Math.abs(point.x - xMin) / lineLength
draggable.x = point.x + draggableRegX
} else if (yOnly) {
if (point.y > yMax) {
console.log('Initial draggable position is > yMax')
callUpdate = true
} else if (point.y < yMin) {
console.log('Initial draggable position is < yMin')
callUpdate = true
}
point.y = Math.min(point.y, yMax)
point.y = Math.max(point.y, yMin)
fraction = Math.abs(point.y - yMax) / lineLength
draggable.y = point.y + draggableRegY
} else {
point = getNearestPoint({ line: line, point: point })
if ((point.x !== point0.x) || (point.y !== point0.y)) {
fraction = getLength({ b: point, a: line.a }) / lineLength
draggable.x = point.x + draggableRegX
draggable.y = point.y + draggableRegY
console.log('Initial draggable position is beyond the constrained line')
callUpdate = true
}
}
if (callUpdate && dragUpdate) {
var dx = point.x - point0.x
var dy = point.y - point0.y
dragUpdate({ event: null, fraction: fraction, dx: dx, dy: dy })
}
return point
}
checkInitialPosition()
function updateFunction (evt) {
timerId && clearTimeout(timerId)
timerId = setTimeout(function () {
if (doLogging) {
console.log('constrained drag timed out between events')
}
lastEvt = null
hasDragEnd && options.dragEnd({ fraction: fraction, event: evt })
}, waitTime)
if (lastEvt || evt.type === wheelType) {
if (!offset && window.$b.gestures) {
/*
* window.$b.gestures is undefined until Hammer.js is loaded, so place the call to
* this function in updateFunction which is not called until a pan event is fired.
*/
offset = window.$b.gestures.getCachedOffset()
}
var dx = 0
var dy = 0
if (evt.type === wheelType) {
/*
* Multiply by a factor for a mechanical mousewheel (deltaMode = 1).
*/
var factor = evt.deltaMode === 1 ? 20 : 1
factor *= mouseWheelScrollSign
dx = evt.deltaX * refWidth / canvas.clientWidth * mouseWheelSpeed * factor
dy = evt.deltaY * refHeight / canvas.clientHeight * mouseWheelSpeed * factor
} else {
dx = (evt.deltaX - lastEvt.deltaX) * refWidth / canvas.clientWidth
dy = (evt.deltaY - lastEvt.deltaY) * refHeight / canvas.clientHeight
dragEndTimer && cancelInertia()
}
/*
* NaN's rarely occur, but if they do, they throw the draggable off the screen
*/
if (isNaN(dx) || isNaN(dy)) {
lastEvt = null
timerId && clearTimeout(timerId)
timerId = null
hasDragEnd && options.dragEnd({ fraction: fraction, event: evt })
return
}
if (forceDragRelease && !mouseInBounds({ event: evt, offset: offset, parent: parent })) {
if (doLogging) {
console.log('mouse not over draggable')
}
timerId && clearTimeout(timerId)
timerId = null
cancelInertia()
hasDragEnd && options.dragEnd({ fraction: fraction, event: evt })
lastEvt = null
window.$b.resetHandler()
return
}
var deltaReturn = moveByDelta({ dx: dx, dy: dy })
var actualDx = deltaReturn.dx
var actualDy = deltaReturn.dy
if (doLogging) {
console.log('fraction = ' + fraction)
}
if (dragUpdate) {
dragUpdate({ fraction: fraction, event: evt, dx: actualDx, dy: actualDy })
}
}
doLogging && !lastEvt && console.log('lastEvt = null')
lastEvt = evt
}
function moveByDelta (options) {
var dx = isNumber(options.dx) ? options.dx : 0
var dy = isNumber(options.dy) ? options.dy : 0
/*
* The position in the parent object is the draggable's position offset
* by the draggable's scaled registration point.
*/
var point0 = {
x: draggable.x - draggableRegX,
y: draggable.y - draggableRegY
}
var point = {
x: point0.x + dx,
y: point0.y + dy
}
if (xOnly) {
if (!loop) {
point.x = Math.min(point.x, xMax)
point.x = Math.max(point.x, xMin)
} else {
if (point.x > xMax) {
point.x = xMin + (point.x - xMax)
}
if (point.x < xMin) {
point.x = xMax - (xMin - point.x)
}
}
point.y = point0.y
fraction = Math.abs(point.x - xMin) / lineLength
draggable.x = point.x + draggableRegX
} else if (yOnly) {
/*
* y direction is opposite convention
*/
if (!loop) {
point.y = Math.min(point.y, yMax)
point.y = Math.max(point.y, yMin)
} else {
if (point.y > yMax) {
point.y = yMin - (yMax - point.y)
}
if (point.y < yMin) {
point.y = yMax + (yMin - point.y)
}
}
point.x = point0.x
fraction = Math.abs(point.y - yMax) / lineLength
draggable.y = point.y + draggableRegY
} else {
point = getNearestPoint({ line: line, point: point })
fraction = getLength({ b: point, a: line.a }) / lineLength
draggable.x = point.x + draggableRegX
draggable.y = point.y + draggableRegY
}
if (doLogging) {
console.log('fraction = ' + fraction)
}
var actualDx = point.x - point0.x
var actualDy = point.y - point0.y
return ({ dx: actualDx, dy: actualDy })
}
draggable.updateFunction = updateFunction
var updateHandler = draggable.addEventListener('pan', updateFunction)
var startHandler = draggable.addEventListener('panstart', updateFunction)
var endHandler = draggable.addEventListener('panend', function (evt) {
timerId && clearTimeout(timerId)
timerId = null
if (dragEnd) {
dragEnd({ fraction: fraction, event: evt })
}
lastEvt = null
doLogging && console.log('panend fraction', fraction)
})
function removeEventHandlers () {
draggable.removeEventListener('pan', updateHandler)
draggable.removeEventListener('panstart', startHandler)
draggable.removeEventListener('panend', endHandler)
}
function restoreEventHandlers () {
draggable.addEventListener('pan', updateHandler)
draggable.addEventListener('panstart', startHandler)
draggable.addEventListener('panend', endHandler)
}
/*
* The event CanvasSizeChanged is called at the end of blink/animate-cc-resize-canvas.
* Since dx & dy are calculated by a difference of evt and lastEvt, we must null out lastEvt
* after the draggable is released so the object does not jump.
*/
if (window.$b) {
window.$b.on('CanvasSizeChanged', function (evt) {
doLogging && console.log('CanvasSizeChanged, lastEvt set to null')
lastEvt = null
})
window.$b.on('AdPaused', function (evt) {
doLogging && console.log('AdPaused, lastEvt set to null')
lastEvt = null
})
}
/*
* On JWPlayer Mobile, the AdPaused event is not fired. Detect if an ad is paused by
* testing if the value of previousTime is not updated while the App is paused in the background.
*/
if (isMobile) {
var dt = 500
var previousTime = Date.now()
var currentTime
var heartbeat = function () {
currentTime = Date.now()
if ((currentTime - previousTime) > 2 * dt) {
window.$b.resetHandler()
lastEvt = null
if (doLogging) {
var pauseTime = ((currentTime - previousTime) / 1000).toFixed(2)
console.log('Mobile JavaScript engine paused for ' + pauseTime + 's')
}
}
previousTime = currentTime
setTimeout(heartbeat, dt)
}
heartbeat()
}
function getLength (options) {
var a = options.a
var b = options.b
var dx = b.x - a.x
var dy = b.y - a.y
return Math.sqrt(dx * dx + dy * dy)
}
function getNearestPoint (options) {
var line = options.line
var point = options.point
var x
var y
if (line.a.x !== line.b.x) {
if (line.b.y !== line.a.y) {
var slope = (line.b.y - line.a.y) / (line.b.x - line.a.x)
// x = (yc - y1 + x1 * m + xc / m) * (m / (m**2 + 1)
x = (point.y - line.a.y + line.a.x * slope + point.x / slope) * (slope / (slope * slope + 1))
// y(line) = y1 + (x - x1) * slope = m * x + (y1 - x1 * m)
y = slope * x + line.a.y - line.a.x * slope
} else {
x = point.x
y = line.a.y
}
} else {
x = line.a.x
y = point.y
}
return testSegment({ line: line, point: { x: x, y: y } })
}
function testSegment (options) {
var line = options.line
var a = line.a
var b = line.b
var p = options.point
var dy = b.y - a.y
var dx = b.x - a.x
var aDotB = dy * dy + dx * dx
dy = p.y - a.y
dx = p.x - a.x
var pDotA = dy * dy + dx * dx
dy = p.y - b.y
dx = p.x - b.x
var pDotB = dy * dy + dx * dx
if ((pDotA < aDotB) && (pDotB < aDotB)) {
return { x: p.x, y: p.y }
} else if (pDotB > pDotA) {
return { x: a.x, y: a.y }
} else {
return { x: b.x, y: b.y }
}
}
function moveToFraction (options) {
var dx = 0
var dy = 0
if (isNumber(options.fraction) && options.fraction >= 0 && options.fraction <= 1) {
fraction = options.fraction
var x0 = draggable.x
var y0 = draggable.y
var x
var y
if (xOnly) {
x = fraction * lineLength + xMin
draggable.x = x + draggableRegX
dx = draggable.x - x0
} else if (yOnly) {
/*
* y direction is opposite convention
*/
y = yMax - fraction * lineLength
draggable.y = y + draggableRegY
dy = draggable.y - y0
} else {
y = line.a.y + fraction * (line.b.y - line.a.y)
x = line.a.x + fraction * (line.b.x - line.a.x)
draggable.x = x + draggableRegX
dx = draggable.x - x0
draggable.y = y + draggableRegY
dy = draggable.y - y0
}
}
return { dx: dx, dy: dy }
}
function getFraction () {
return fraction
}
function getFractionAtPoint (options) {
if (!isNumber(options.x) || !isNumber(options.y)) {
console.error('gerFractionAtPoint must be passed options.x and options.y')
return {}
}
var fraction
if (xOnly) {
fraction = (options.x - parent.nominalBounds.x) / parent.nominalBounds.width
} else if (yOnly) {
fraction = (parent.nominalBounds.y + parent.nominalBounds.height - options.y) / parent.nominalBounds.height
} else {
var point = getNearestPoint({ line: line, point: options })
var height = draggable.nominalBounds.height
var width = draggable.nominalBounds.width
var draggableLength = Math.sqrt(height * height + width * width)
fraction = getLength({ b: point, a: line.a }) / (lineLength + draggableLength)
}
fraction = Math.max(0, fraction)
fraction = Math.min(1, fraction)
return { fraction: fraction }
}
return {
line: line,
moveByDelta: moveByDelta,
moveToFraction: moveToFraction,
getFraction: getFraction,
getFractionAtPoint: getFractionAtPoint,
getLength: getLength,
getNearestPoint: getNearestPoint,
testSegment: testSegment,
getMousePoint: getMousePoint,
mouseInBounds: mouseInBounds,
updateHandler: updateHandler,
startHandler: startHandler,
endHandler: endHandler,
removeEventHandlers: removeEventHandlers,
restoreEventHandlers: restoreEventHandlers,
dragUpdate: dragUpdate,
dragEnd: dragEnd,
inertia: inertia,
cancelInertia: cancelInertia,
checkInitialPosition: checkInitialPosition
}
}
/**
* @function getShapeBounds
* @summary
* Returns object with the bounds of the shape (graphics) object
* @description
* Works with shapes created by the Animate CC editor creates shapes or shapes created by users with a draw command.
*
* @param shape {createjs.Shape} shape you want to know the bounds
* @returns {Object} {x, y, width, height}
*/
function getShapeBounds (shape) {
if ((typeof shape !== 'object') || (!(shape.graphics instanceof createjs.Graphics))) {
console.log('getShapeBounds: shape does not have a graphics object')
return {}
}
var command = shape.graphics.command
if (command && (typeof command.w === 'number') && (typeof command.h === 'number')) {
return { x: command.x, y: command.y, width: command.w, height: command.h }
}
var instructions = shape.graphics.getInstructions()
var xArray = instructions
.map(function (inst) { return inst.x })
.filter(function (x) { return !isNaN(x) })
var xMin = Math.min.apply(null, xArray)
var width = Math.max.apply(null, xArray) - xMin
var yArray = instructions
.map(function (inst) { return inst.y })
.filter(function (y) { return !isNaN(y) })
var yMin = Math.min.apply(null, yArray)
var height = Math.max.apply(null, yArray) - yMin
return { x: xMin, y: yMin, width: width, height: height }
}
window.$b = window.$b || {}
window.$b.snippets = window.$b.snippets || {}
var snippets = window.$b.snippets
snippets.addConstrainedDrag = addConstrainedDrag
snippets.getShapeBounds = getShapeBounds
if (window.module) {
window.module.exports = addConstrainedDrag
}