New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for drag events. #584
Conversation
Currently absent from this PR: tests and docs. Will tackle after we discuss appropriateness of the feature + implementation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good, it extends the existing functionality in the expected way.
My concern here is that we are currently building up about 4 or 5 different almost identical event handling systems (luma.gl, react-map-gl, deck.gl controllers, now the deck.gl component).
All of them will need similar extensions and bug fixes (touch support, support for screen offset relative coordinates, etc).
And how do all these event handlers interact? If I embed this in a controller that supports drag for panning or rotating, will (should?) this still get called?
I believe there is an opportunity to come up with a clean architecture that covers all cases and shares code and principles.
The alternative is that we keep making incremental improvements in all of these places and end up with a patch work of slightly different APIs with different capabilities and bugs.
It may well be that this is the right design, still, I think this merits a design design discussion before we merge.
@heshan0131 is my last commit going to cause problems with e.g. brushing? The synopsis: "motion" events are those that are initiated on a given object and are intended to manipulate just that object. Therefore, after the initial event (e.g. I think this is ok for brushing, as the logic for what's within the brushed area is something run in the context of the parent/container (which would be the originally-picked object, and returned by all |
No it will not, currently the brushing example listen to mousemove outside of MapGL component |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we basically just want to keep feeding coordinates with the same layer info to the app, and then a dragend when the mouse is released?
Not sure if it is a good idea, I think the event handlers could be installed dynamically on a click so it may be possible to handle this using a helper class rather than always. The EventManager class does this, I am curious if we could use it here as well.
I'd like to understand the implications of how these events interact with other events handlers, and what happens when things are redrawn during dragging.
src/lib/draw-and-pick.js
Outdated
// Note that glContextCallback is called synchronously; | ||
// code in this function is executed in the order it's written. | ||
// TODO - just return glContextWithState once luma updates | ||
glContextWithState(gl, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I agree that this function need to be split into smaller functions, I think this is split in the wrong place. Move the glContextWithState
into the sub function and rename it something meaningful.
It will make the diff a little smaller also since indentation will be unaffected. It would help my review if this was the case.
Just in case: what glContextWithState
does is that it locally sets some values on the gl context and guarantees that they are reset even if we exit with an exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, code will be cleaner to do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For future reference, ?w=1
is your friend for muting whitespace changes.
src/lib/draw-and-pick.js
Outdated
// e.g. to enable dragging behaviors. | ||
// Therefore, the picking process does not run for these events. | ||
const {layerId} = lastPickedInfo; | ||
pickedLayer = layers.find(l => l.props.id === layerId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably you inherited this code but please type out variables: l
=> layer
.
src/lib/draw-and-pick.js
Outdated
@@ -23,6 +23,12 @@ import {GL, glContextWithState} from 'luma.gl'; | |||
import {getUniformsFromViewport} from './viewport-uniforms'; | |||
import {log, getBlendMode, setBlendMode} from './utils'; | |||
|
|||
const MOTION_EVENTS = [ | |||
'dragmove', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need to separate between drag and touch? These are abstract modes right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't understand -- I'm not making a distinction between them, they're both included here...?
@@ -100,12 +108,18 @@ export default class DeckGL extends React.Component { | |||
this._updateLayers(this.props); | |||
|
|||
// Check if a mouse event has been specified and that at least one of the layers is pickable | |||
const hasEvent = this.props.onLayerClick !== noop || this.props.onLayerHover !== noop; | |||
const hasEvent = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, we need to move this code out to a non-react dependent helper. There should just be a few calls in here that delegate to that helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That will be part of the future event handling / controller refactor.
let mode; | ||
let layerEventHandler; | ||
switch (type) { | ||
case 'mousedown': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You defined touch modes but I don't see touch events handled here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR doesn't as far as supporting touch events. Since we're planning to refactor event handling out anyway, I'm deferring touch event handling for that refactor and focusing only on drag events here (for the graph-layer needs). I'll remove them from MOTION_EVENTS
for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Pessimistress should probably take a quick look before we merge, since she has done the most recent overhaul of picking.
@@ -52,7 +56,11 @@ const defaultProps = { | |||
onWebGLInitialized: noop, | |||
onLayerClick: noop, | |||
onLayerHover: noop, | |||
onAfterRender: noop | |||
onAfterRender: noop, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep the event handlers together, separated from the render callback.
src/react/deckgl.js
Outdated
this.props.onLayerDragStart !== noop || | ||
this.props.onLayerDragMove !== noop || | ||
this.props.onLayerDragEnd !== noop || | ||
this.props.onLayerDragCancel !== noop; | ||
const hasPickableLayer = this.layerManager.layers.map(l => l.props.pickable).includes(true); | ||
if (this.layerManager.layers.length && hasEvent && !hasPickableLayer) { | ||
log.once( | ||
0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should change the log level to 1.
Note that many mouse events are already supported by luma.gl; this PR extends the support for a subset of these upwards into deck.gl. Handle dragmove/dragend/touchmove/touchend differently: retain the picked object/layer from gesture initiation. Don't process motion events if nothing was picked on the initial mousedown. Skip picking process for "motion events" that only track motion of initially-picked objects. Address some comments Break picking process out into its own function.
onDragStart: this._onDragEvent, | ||
onDragMove: this._onDragEvent, | ||
onDragEnd: this._onDragEvent, | ||
onDragCancel: this._onDragCancel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're invoking picking on all of these events as long as one of them is specified. Can we add handlers only when they are not noop
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, digging deeper into this, we're always running picking, even if no layers have pickable
set. There's no check for existence of handlers or pickable
truthiness before calling addEvents
, we're just running picking on every mouse move and calling noops every frame.
Additionally, we currently have no facility for adding/removing event handlers after initialization; deck.gl calls luma's addEvents
only once, on init; and addEvents
itself does not support adding/removing handlers. We need to address this in the event handling refactor, @ibgreen.
All this indicates to me that:
- we should definitely fix this
- we should fix it in the event handling refactor, not in this PR.
If @ibgreen and @Pessimistress agree, I'd like to merge as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We definitely should not register events based on pickable
states at initialization. In most of our use cases the layers are not passed in till data is loaded. Since we're only logging a warning it's not broken at the moment.
I agree this should be fixed in a separate PR.
this.props.onLayerDragStart !== noop || | ||
this.props.onLayerDragMove !== noop || | ||
this.props.onLayerDragEnd !== noop || | ||
this.props.onLayerDragCancel !== noop; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an anti-React-pattern: these props are only checked once on itialization.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right @Pessimistress, this is an anti-pattern. However, it's only checking to offer a warning, in the case that an onLayer*
handler is passed when no layers have set pickable
; I imagine the overwhelmingly most common use case will be to set onLayer*
handlers on init, not dynamically at some point later in the program. Checking these props on every update just to conditionally issue a warning doesn't seem worth the (probably minor) cost.
My vote is we leave as-is unless it proves to be a problem.
* Add support for drag events. Note that many mouse events are already supported by luma.gl; this PR extends the support for a subset of these upwards into deck.gl. Handle dragmove/dragend/touchmove/touchend differently: retain the picked object/layer from gesture initiation. Don't process motion events if nothing was picked on the initial mousedown. Skip picking process for "motion events" that only track motion of initially-picked objects. Address some comments Break picking process out into its own function. * Address more comments.
Note that many mouse events are already supported by luma.gl; this PR extends the support for a subset of these upwards into deck.gl.
I know that we have grander plans for encapsulating interaction models for different scenarios. I don't believe this runs afoul of those, but it is possible the direction taken in this PR is setting us up for a bigger refactor when we tackle new interaction models.
/cc @gnavvy @Pessimistress as potential consumers of these additions.