DragListener¶
Overview¶
PressListener subtype customized for handling most drag-related listener needs.
DragListener uses some specific terminology that is helpful to understand:
- Drag target: The node whose trail is used for coordinate transforms. When a targetNode is specified, it will be the drag target. Otherwise, whatever was the currentTarget during event bubbling for the event that triggered press will be used (almost always the node that the listener is added to).
- Global coordinate frame: Coordinate frame of the Display (specifically its rootNode's local coordinate frame), that in some applications will be screen coordinates.
- Parent coordinate frame: The parent coordinate frame of our drag target. Basically, it's the coordinate frame you'd need to use to set dragTarget.translation = <parent coordinate frame point> for the drag target to follow the pointer.
- Local coordinate frame: The local coordinate frame of our drag target, where (0,0) would be at the drag target's origin.
- Model coordinate frame: Optionally defined by a model-view transform (treating the parent coordinate frame as the view). When a transform is provided, it's the coordinate frame needed for setting dragModelElement.position = <model coordinate frame point>. If a transform is not provided (or overridden), it will be the same as the parent coordinate frame.
The typical coordinate handling of DragListener is to: 1. When a drag is started (with press), record the pointer's position in the local coordinate frame. This is visually where the pointer is over the drag target, and typically most drags will want to move the dragged element so that the pointer continues to be over this point. 2. When the pointer is moved, compute the new parent translation to keep the pointer on the same place on the dragged element. 3. (optionally) map that to a model position, and (optionally) move that model position to satisfy any constraints of where the element can be dragged (recomputing the parent/model translation as needed) 4. Apply the required translation (with a provided drag callback, using the positionProperty, or directly transforming the Node if translateNode:true).
For example usage, see scenery/examples/input.html
For most PhET model-view usage, it's recommended to include a model position Property as the positionProperty
option, along with the transform
option specifying the MVT. By default, this will then assume that the Node with the listener is positioned in the "view" coordinate frame, and will properly handle offsets and transformations. It is assumed that when the model positionProperty
changes, that the position of the Node would also change. If it's another Node being transformed, please use the targetNode
option to specify which Node is being transformed. If something more complicated than a Node being transformed is going on (like positioning multiple items, positioning based on the center, changing something in CanvasNode), it's recommended to pass the useParentOffset
option (so that the DragListener will NOT try to compute offsets based on the Node's position), or to use applyOffset:false
(effectively having drags reposition the Node so that the origin is at the pointer).
The typical PhET usage would look like:
new DragListener( { positionProperty: someObject.positionProperty, transform: modelViewTransform } )
Additionally, for PhET usage it's also fine NOT to hook into a positionProperty
. Typically using start/end/drag, and values can be read out (like modelPoint
, localPoint
, parentPoint
, modelDelta
) from the listener to do operations. For instance, if deltas and model positions are the only thing desired:
new DragListener( { drag: ( event, listener ) => { doSomethingWith( listener.modelDelta, listener.modelPoint ); } } )
It's completely fine to use one DragListener with multiple objects, however this isn't done as much since specifying positionProperty only works with ONE model position Property (so if things are backed by the same Property it would be fine). Doing things based on modelPoint/modelDelta/etc. should be completely fine using one listener with multiple nodes. The typical pattern IS creating one DragListener per draggable view Node.
@author Jonathan Olson <jonathan.olson@colorado.edu>
Class DragListener¶
Constructor¶
new DragListener( providedOptions? : DragListenerOptions<PressedDragListener> )¶
Instance Methods¶
press( event : PressListenerEvent, targetNode? : Node, callback? : () => void ) : boolean¶
Attempts to start a drag with a press.
NOTE: This is safe to call externally in order to attempt to start a press. dragListener.canPress( event ) can be used to determine whether this will actually start a drag.
@param event @param [targetNode] - If provided, will take the place of the targetNode for this call. Useful for forwarded presses. @param [callback] - to be run at the end of the function, but only on success @returns success - Returns whether the press was actually started
release( event? : PressListenerEvent, callback? : () => void )¶
Stops the drag.
This can be called from the outside to stop the drag without the pointer having actually fired any 'up' events. If the cancel/interrupt behavior is more preferable, call interrupt() on this listener instead.
@param [event] - scenery event if there was one @param [callback] - called at the end of the release
canClick() : boolean¶
Components using DragListener should generally not be activated with a click. A single click from alternative input would pick up the component then immediately release it. But occasionally that is desirable and can be controlled with the canClick option.
drag( event : PressListenerEvent )¶
Called when move events are fired on the attached pointer listener during a drag.
tryTouchSnag( event : PressListenerEvent )¶
Attempts to start a touch snag, given a SceneryEvent.
Should be safe to be called externally with an event.
getGlobalPoint() : Vector2¶
Returns a defensive copy of the local-coordinate-frame point of the drag.
getLocalPoint() : Vector2¶
Returns a defensive copy of the local-coordinate-frame point of the drag.
getParentPoint() : Vector2¶
Returns a defensive copy of the parent-coordinate-frame point of the drag.
getModelPoint() : Vector2¶
Returns a defensive copy of the model-coordinate-frame point of the drag.
getModelDelta() : Vector2¶
Returns a defensive copy of the model-coordinate-frame delta.
globalToParentPoint( globalPoint : Vector2 ) : Vector2¶
(protected)
Maps a point from the global coordinate frame to our drag target's parent coordinate frame.
NOTE: This mutates the input vector (for performance)
Should be overridden if a custom transformation is needed.
parentToLocalPoint( parentPoint : Vector2 ) : Vector2¶
(protected)
Maps a point from the drag target's parent coordinate frame to its local coordinate frame.
NOTE: This mutates the input vector (for performance)
Should be overridden if a custom transformation is needed.
localToParentPoint( localPoint : Vector2 ) : Vector2¶
(protected)
Maps a point from the drag target's local coordinate frame to its parent coordinate frame.
NOTE: This mutates the input vector (for performance)
Should be overridden if a custom transformation is needed.
parentToModelPoint( parentPoint : Vector2 ) : Vector2¶
(protected)
Maps a point from the drag target's parent coordinate frame to the model coordinate frame.
NOTE: This mutates the input vector (for performance)
Should be overridden if a custom transformation is needed. Note that by default, unless a transform is provided, the parent coordinate frame will be the same as the model coordinate frame.
modelToParentPoint( modelPoint : Vector2 ) : Vector2¶
(protected)
Maps a point from the model coordinate frame to the drag target's parent coordinate frame.
NOTE: This mutates the input vector (for performance)
Should be overridden if a custom transformation is needed. Note that by default, unless a transform is provided, the parent coordinate frame will be the same as the model coordinate frame.
mapModelPoint( modelPoint : Vector2 ) : Vector2¶
(protected)
Apply a mapping from the drag target's model position to an allowed model position.
A common example is using dragBounds, where the position of the drag target is constrained to within a bounding box. This is done by mapping points outside the bounding box to the closest position inside the box. More general mappings can be used.
Should be overridden (or use mapPosition) if a custom transformation is needed.
@returns - A point in the model coordinate frame
applyParentOffset( parentPoint : Vector2 )¶
(protected)
Mutates the parentPoint given to account for the initial pointer's offset from the drag target's origin.
reposition( globalPoint : Vector2 )¶
Triggers an update of the drag position, potentially changing position properties.
Should be called when something that changes the output positions of the drag occurs (most often, a drag event itself).
getDragBounds() : Bounds2 | null¶
Returns the drag bounds of the listener.
setTransform( transform : Transform3 | TReadOnlyProperty<Transform3> | null )¶
Sets the drag transform of the listener.
getTransform() : Transform3 | TReadOnlyProperty<Transform3> | null¶
Returns the transform of the listener.
interrupt()¶
Interrupts the listener, releasing it (canceling behavior).
This effectively releases/ends the press, and sets the interrupted
flag to true while firing these events so that code can determine whether a release/end happened naturally, or was canceled in some way.
This can be called manually, but can also be called through node.interruptSubtreeInput().
canPress( event : PressListenerEvent ) : boolean¶
Returns whether a press can be started with a particular event.
dispose()¶
Disposes the listener, releasing references. It should not be used after this.
Instance Properties¶
isUserControlledProperty : TProperty<boolean>¶
Alias for isPressedProperty (as this name makes more sense for dragging)
Static Methods¶
createForwardingListener( down : ( event: PressListenerEvent ) => void, providedOptions? : CreateForwardingListenerOptions ) : TInputListener¶
Creates an input listener that forwards events to the specified input listener. The target listener should probably be using PressListener.options.targetNode so that the forwarded drag has the correct Trail
See https://github.com/phetsims/scenery/issues/639
Type DragListenerOptions¶
- allowTouchSnag?: boolean
If true, unattached touches that move across our node will trigger a press(). This helps sometimes for small draggable objects. - applyOffset?: boolean
If true, the initial offset of the pointer's position is taken into account, so that drags will try to keep the pointer at the same local point of our dragged node. NOTE: The default behavior is to use the given Node (either the targetNode or the node with the listener on it) and use its transform to compute the "local point" (assuming that the node's local origin is what is transformed around). This is ideal for most situations, but it's also possible to use a parent-coordinate based approach for offsets (see useParentOffset) - useParentOffset?: boolean
If set to true, then any offsets applied will be handled in the parent coordinate space using the positionProperty as the "ground truth", instead of looking at the Node's actual position and transform. This is useful if the position/transform cannot be applied directly to a single Node (e.g. positioning multiple independent nodes, or centering things instead of transforming based on the origin of the Node).
NOTE: Use this option most likely if converting from MovableDragHandler, because it transformed based in the parent's coordinate frame. See https://github.com/phetsims/scenery/issues/1014
NOTE: This also requires providing a positionProperty - trackAncestors?: boolean
If true, ancestor transforms will be watched. If they change, it will trigger a repositioning; which will usually adjust the position/transform to maintain position. - offsetPosition?: OffsetPosition<Listener> | null
If provided, its result will be added to the parentPoint before computation continues, to allow the ability to "offset" where the pointer position seems to be. Useful for touch, where things shouldn't be under the pointer directly. - canClick?: boolean
pdom Whether to allow click
events to trigger behavior in the supertype PressListener. Generally DragListener should not respond to click events, but there are some exceptions where drag functionality is nice but a click should still activate the component. See https://github.com/phetsims/sun/issues/696 - & AllDragListenerOptions<Listener, PressListenerDOMEvent> & PressListenerOptions<Listener>
Type PressedDragListener¶
DragListener & PressedPressListener
Source Code¶
See the source for DragListener.ts in the scenery repository.