-
Notifications
You must be signed in to change notification settings - Fork 16
Added Support for Touch devices for MapD Draw #29
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,27 @@ const defaultXformStyle = { | |
| strokeWidth: 2 | ||
| } | ||
|
|
||
| const EventsTypes = { | ||
| MOUSEDOWN: "mousedown", | ||
| MOUSEUP: "mouseup", | ||
| MOUSEMOVE: "mousemove", | ||
| TOUCHSTART: "touchstart", | ||
| TOUCHEND: "touchend", | ||
| TOUCHMOVE: "touchmove" | ||
| } | ||
|
|
||
| const DOUBLE_CLICK_DELAY = 600 // To detect the double click in case of touch screen | ||
|
|
||
| // This method will Add clientX, clientY & offsetX and offsetY for Touch events | ||
| function getTouchCoordinates(event, canvas) { | ||
| event.clientX = event.touches[0].clientX | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm concerned whether grabbing the index 0 location of the touches will be consistent and produce stable interactions. For example, what would happen if you did a two-finger touch, and you removed the touch from the first finger, but kept the second finger in contact and moved it around. Or what would happen if you first pressed with one finger, then pressed with a second finger, then removed the first finger, and moved the second finger around. I wonder if it would be best to limit the scope of the touch event handling to just a single touch. Anything more than that would cancel the event. Thoughts? |
||
| event.clientY = event.touches[0].clientY | ||
| const element = canvas.getBoundingClientRect() | ||
| event.offsetX = event.touches[0].clientX - element.left | ||
| event.offsetY = event.touches[0].clientY - element.top | ||
| return event | ||
| } | ||
|
|
||
| function inCanvas(canvas, x, y) { | ||
| const domrect = canvas.getBoundingClientRect() | ||
| let localX = 0 | ||
|
|
@@ -96,7 +117,7 @@ function selectShape(selectedShape, sortedShapes, currSelectedShapes, selectStyl | |
| selectedShape.zIndex = maxZ + 1 | ||
| BasicStyle.copyBasicStyle(selectStyle, selectedShape) | ||
| selectedShape.selected = true | ||
| // const dimensions = selectedShape.getDimensions() | ||
| // const dimensions = selectedShape.getDimensions() | ||
|
|
||
| let newSelectShape = null | ||
| if (selectOpts.scalable || selectOpts.rotatable) { | ||
|
|
@@ -208,7 +229,29 @@ function updateCursorPosition(_event, target) { | |
| } | ||
|
|
||
| export default class ShapeBuilder extends DrawEngine { | ||
|
|
||
| _touchstartCB(event) { | ||
| this._mousedownCB(event) | ||
| } | ||
|
|
||
| _touchmoveCB(event) { | ||
| this._mousemoveCB(event) | ||
| } | ||
|
|
||
| _touchendCB(event) { | ||
| this._mouseupCB(event) | ||
| } | ||
|
|
||
| _mousedownCB(event) { | ||
| this.setDenyMouseEventFlag(event) | ||
| if (this.denyMouseEvent && !event.touches) { | ||
| return | ||
| } | ||
| if (event.touches) { | ||
| event = getTouchCoordinates(event, this._drawCanvas) | ||
| this.previousEventObj = event // Assign event obj to variable to avoid the use it for touchend event | ||
| } | ||
|
|
||
| if (!inCanvas(this._drawCanvas, event.clientX, event.clientY)) { | ||
| return | ||
| } | ||
|
|
@@ -300,14 +343,27 @@ export default class ShapeBuilder extends DrawEngine { | |
| shapes: getSelectedObjsFromMap(this._selectedShapes) | ||
| }) | ||
| } | ||
| event.preventDefault() | ||
| if (!event.touches) { | ||
| event.preventDefault() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| _mouseupCB(event) { | ||
| if (this.denyMouseEvent && !event.touches) { | ||
| this.setDenyMouseEventFlag(event) | ||
| return // Returning on next line to avoid ESLint error | ||
| } | ||
| if (event.touches) { | ||
| // Use previously assigned event obj to get the offsetX & Y and clientX & Y calculation | ||
| event = this.previousEventObj | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain your reasoning for using the previous event coordinates here. What's wrong with just calling |
||
| } | ||
|
|
||
| if (this._dragInfo && this._dragInfo.shape) { | ||
| event.stopImmediatePropagation() | ||
| event.preventDefault() | ||
| if (event.cancelable) { | ||
| event.preventDefault() | ||
| } | ||
| const canvas = document.querySelector(`${`#${this._parent.id} > canvas`}`) | ||
| if (canvas === null) { | ||
| this._parent.removeEventListener("mouseout", hideCursor) | ||
|
|
@@ -338,11 +394,34 @@ export default class ShapeBuilder extends DrawEngine { | |
| if (selectedShape && !selectedShape.selected) { | ||
| const selectEventObj = selectShape(selectedShape, shapes, this._selectedShapes, this._selectStyle, this._xformStyle, selectedInfo) | ||
| this.fire(EventConstants.SELECTION_CHANGED, selectEventObj) | ||
| } else { | ||
| // If user clicks anywhere outside then allow the movement of Base Map (Parents Container) | ||
| this._makeParentElementMovable() | ||
| } | ||
| } | ||
| // Added Support for Double click | ||
| if (event.touches) { | ||
| if (event.cancelable) { | ||
| event.preventDefault() | ||
| } | ||
| if ((Date.now() - this.firstTapTime) < DOUBLE_CLICK_DELAY) { | ||
| this._dblclickCB(event) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks ok. Did you verify that this works like a mouse double-click event. In particular I'm wondering if a mouse double-click event actually runs the mousedown/mouseup twice first before firing the double click event. If that's true, then this looks right because the touchstart/touchend would be fired twice before |
||
| } | ||
| } | ||
| this.firstTapTime = Date.now() | ||
| } | ||
|
|
||
| _mousemoveCB(event) { | ||
| this.setDenyMouseEventFlag(event) | ||
| if (this.denyMouseEvent && !event.touches) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's a lot of checks for |
||
| return | ||
| } | ||
|
|
||
| if (event.touches) { | ||
| event = getTouchCoordinates(event, this._drawCanvas) | ||
| this.previousEventObj = event // Assign event obj to variable to avoid the use it for touchend event | ||
| } | ||
|
|
||
| if (!(inCanvas(this._drawCanvas, event.clientX, event.clientY)) && !this._dragInfo) { | ||
| return | ||
| } | ||
|
|
@@ -352,7 +431,9 @@ export default class ShapeBuilder extends DrawEngine { | |
| addEventKeysToSelectedInfo(event, this._dragInfo) | ||
| transformSelectedShape(this._drawCanvas, event, this._dragInfo, this._camera) | ||
| event.stopImmediatePropagation() | ||
| event.preventDefault() | ||
| if (!event.touches) { | ||
| event.preventDefault() | ||
| } | ||
| } else if (!event.buttons && this._selectedShapes.size) { | ||
| Point2d.set(tmpPt1, event.offsetX, event.offsetY) | ||
| Point2d.transformMat2d(tmpPt2, tmpPt1, this._camera.screenToWorldMatrix) | ||
|
|
@@ -522,7 +603,9 @@ export default class ShapeBuilder extends DrawEngine { | |
| } else { | ||
| event.stopImmediatePropagation() | ||
| } | ||
| event.preventDefault() | ||
| if (event.cancelable) { | ||
| event.preventDefault() | ||
| } | ||
| } | ||
|
|
||
| _mouseoverCB() { | ||
|
|
@@ -553,6 +636,19 @@ export default class ShapeBuilder extends DrawEngine { | |
| this.timer = 0 | ||
| } | ||
|
|
||
| // This function allow the movement of Parent Container (In our case it is Map) when user clicks anywhere on Map except on Shape | ||
| // As well as it's changes the icon of mouse for Desktop devices | ||
| _makeParentElementMovable() { | ||
| removeCustomCursor() | ||
| this._parent.style.cursor = "default" // Change the Cursor icon for desktop device | ||
| for (let j = 0; j < this._parent.childNodes.length; j += 1) { | ||
| this._parent.childNodes[j].style.cursor = "default" // Change the Cursor icon for desktop device | ||
| if (this._parent.childNodes[j].nodeName.toLowerCase() !== "canvas") { | ||
| this._parent.childNodes[j].style.pointerEvents = "auto" // Allow movemnet of parent container i.e Map | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+639
to
+650
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you further explain the intent of this? IIRC the original intent of the mapd-draw design was to stay out of the way of DOM layer and event propogation as much as possible and instead give responsibility to a middle-layer to manage. If it's just the cursor state of sub-dom elements, my first inclination is that should be handled by such a middle layer. For immerse, that middle-layer is in mapd-charting. |
||
|
|
||
| _renderShapes(ctx, drawShapes, camera) { | ||
| const worldToScreenMat = camera.worldToScreenMatrix | ||
| drawShapes.forEach(shape => { | ||
|
|
@@ -704,6 +800,16 @@ export default class ShapeBuilder extends DrawEngine { | |
| this._activated = false | ||
| return this | ||
| } | ||
|
|
||
| // This method is used to stop Mouse Event propagation Triggered from the Touch event | ||
| setDenyMouseEventFlag(event) { | ||
| if (event.touches) { | ||
| this.denyMouseEvent = true | ||
| } else if (event.type === EventsTypes.MOUSEUP) { | ||
| // set the Flag false at the end of mouse event i.e on MouseUp Event | ||
| this.denyMouseEvent = false | ||
| } | ||
| } | ||
|
Comment on lines
+804
to
+812
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain the scenario you're trying to avoid with setting the denyMouseEvent property? If I'm following along appropriately, it seems you're trying to avoid mouse/touch conflicts with a mouse and touch-screen configured system. That's a configuration we should be concerned with, but the logic seems strange. So, if a touch event comes thru, all mouse events are ignored and continue to be ignored until a mouse click comes around again. So a mouse click needs to happen to re-activate mouse events. At first glance this seems unintuitive and I think we could avoid such secret interactions with other currently existing state, such as |
||
| } | ||
|
|
||
| Object.assign(EventConstants, DrawEngine.EventConstants) | ||
|
|
||
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.
600 ms seems a bit too much for a double-click. How did you come up with this magic number? Did you feel it was appropriate in your testing? My first thought is that it should be at max 300ms.